all repos — mgba @ c0826109925cecab27860453c357e8cae97eb3ea

mGBA Game Boy Advance Emulator

Merge branch 'master' (early part) into medusa
Vicki Pfau vi@endrift.com
Wed, 17 Jun 2020 22:13:35 -0700
commit

c0826109925cecab27860453c357e8cae97eb3ea

parent

e5c6373f880844e74718d64b6a0ff32c8b1717c3

M CHANGESCHANGES

@@ -20,6 +20,7 @@ - DS Video: Simplify VRAM mapping

0.9.0: (Future) Features: + - e-Reader card scanning - Add APNG recording Emulation fixes: - ARM: Fix ALU reading PC after shifting

@@ -33,6 +34,8 @@ - GBA Video: Fix Hblank timing

Other fixes: - Core: Ensure ELF regions can be written before trying - Core: Fix ELF loading regression (fixes mgba.io/i/1669) + - Core: Fix crash modifying hash table entry (fixes mgba.io/i/1673) + - GBA: Reject incorrectly sized BIOSes - Debugger: Don't skip undefined instructions when debugger attached - Qt: Force OpenGL paint engine creation thread (fixes mgba.io/i/1642) Misc:

@@ -40,6 +43,7 @@ - FFmpeg: Add looping option for GIF/APNG

- Qt: Renderer can be changed while a game is running - Qt: Add hex index to palette view - Qt: Add transformation matrix info to sprite view + - Qt: Disable Replace ROM option when no game loaded 0.8.1: (2020-02-16) Emulation fixes:
M README.mdREADME.md

@@ -28,7 +28,8 @@ - Frameskip, configurable up to 10.

- Screenshot support. - Cheat code support[<sup>[2]</sup>](#dscaveat). - 9 savestate slots. Savestates are also viewable as screenshots[<sup>[2]</sup>](#dscaveat). -- Video and GIF recording. +- Video, GIF and APNG recording. +- e-Reader support. - Remappable controls for both keyboards and gamepads. - Loading from ZIP and 7z files. - IPS, UPS and BPS patch support.

@@ -69,7 +70,6 @@ - M4A audio mixing, for higher quality sound than hardware.

- Re-recording support for tool-assist runs. - Lua support for scripting. - A comprehensive debug suite. -- e-Reader support. - Wireless adapter support. - OpenGL renderer. - HLE support for DS BIOS and DS ARM7 processor.
M README_DE.mdREADME_DE.md

@@ -28,6 +28,7 @@ - Unterstützung für Screenshots.

- Unterstützung für Cheat-Codes. - 9 Speicherstände für Savestates/Spielzustände. Savestates können auch als Screenshots dargestellt werden. - Video- und GIF-Aufzeichnung. +- e-Reader-Unterstützung. - Frei wählbare Tastenbelegungen für Tastaturen und Controller. - Unterstützung für ZIP- und 7z-Archive. - Unterstützung für Patches im IPS-, UPS- und BPS-Format.

@@ -68,7 +69,6 @@ - M4A-Audio-Abmischung für höhere Audio-Qualität als echte Hardware.

- Unterstützung für Tool-Assisted Speedruns. - Lua-Unterstützung für Scripting. - Eine umfangreiche Debugging-Suite. -- e-Reader-Unterstützung. - Unterstützung für Drahtlosadapter. Unterstützte Plattformen
M include/mgba/internal/gba/hardware.hinclude/mgba/internal/gba/hardware.h

@@ -34,7 +34,8 @@ HW_LIGHT_SENSOR = 4,

HW_GYRO = 8, HW_TILT = 16, HW_GB_PLAYER = 32, - HW_GB_PLAYER_DETECTION = 64 + HW_GB_PLAYER_DETECTION = 64, + HW_EREADER = 128 }; enum GPIORegister {

@@ -107,6 +108,40 @@ };

DECL_BITFIELD(GPIOPin, uint16_t); +DECL_BITFIELD(EReaderControl0, uint8_t); +DECL_BIT(EReaderControl0, Data, 0); +DECL_BIT(EReaderControl0, Clock, 1); +DECL_BIT(EReaderControl0, Direction, 2); +DECL_BIT(EReaderControl0, LedEnable, 3); +DECL_BIT(EReaderControl0, Scan, 4); +DECL_BIT(EReaderControl0, Phi, 5); +DECL_BIT(EReaderControl0, PowerEnable, 6); +DECL_BITFIELD(EReaderControl1, uint8_t); +DECL_BIT(EReaderControl1, Scanline, 1); +DECL_BIT(EReaderControl1, Unk1, 4); +DECL_BIT(EReaderControl1, Voltage, 5); + +enum EReaderStateMachine { + EREADER_SERIAL_INACTIVE = 0, + EREADER_SERIAL_STARTING, + EREADER_SERIAL_BIT_0, + EREADER_SERIAL_BIT_1, + EREADER_SERIAL_BIT_2, + EREADER_SERIAL_BIT_3, + EREADER_SERIAL_BIT_4, + EREADER_SERIAL_BIT_5, + EREADER_SERIAL_BIT_6, + EREADER_SERIAL_BIT_7, + EREADER_SERIAL_END_BIT, +}; + +enum EReaderCommand { + EREADER_COMMAND_IDLE = 0, // TODO: Verify on hardware + EREADER_COMMAND_WRITE_DATA = 1, + EREADER_COMMAND_SET_INDEX = 0x22, + EREADER_COMMAND_READ_DATA = 0x23, +}; + struct GBACartridgeHardware { struct GBA* p; uint32_t devices;

@@ -134,6 +169,23 @@ int gbpTxPosition;

struct mTimingEvent gbpNextEvent; struct GBAGBPKeyCallback gbpCallback; struct GBAGBPSIODriver gbpDriver; + + uint16_t eReaderData[44]; + uint8_t eReaderSerial[92]; + uint16_t eReaderRegisterUnk; + uint16_t eReaderRegisterReset; + EReaderControl0 eReaderRegisterControl0; + EReaderControl1 eReaderRegisterControl1; + uint16_t eReaderRegisterLed; + + // TODO: Serialize these + enum EReaderStateMachine eReaderState; + enum EReaderCommand eReaderCommand; + uint8_t eReaderActiveRegister; + uint8_t eReaderByte; + int eReaderX; + int eReaderY; + uint8_t* eReaderDots; }; void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);

@@ -155,6 +207,13 @@

struct GBAVideo; void GBAHardwarePlayerUpdate(struct GBA* gba); bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video); + +void GBAHardwareInitEReader(struct GBACartridgeHardware* hw); +void GBAHardwareEReaderWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value); +void GBAHardwareEReaderWriteFlash(struct GBACartridgeHardware* hw, uint32_t address, uint8_t value); +uint16_t GBAHardwareEReaderRead(struct GBACartridgeHardware* hw, uint32_t address); +uint8_t GBAHardwareEReaderReadFlash(struct GBACartridgeHardware* hw, uint32_t address); +void GBAHardwareEReaderScan(struct GBACartridgeHardware* hw, const void* data, size_t size); struct GBASerializedState; void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
M src/gba/gba.csrc/gba/gba.c

@@ -441,6 +441,10 @@ }

void GBALoadBIOS(struct GBA* gba, struct VFile* vf) { gba->biosVf = vf; + if (vf->size(vf) != SIZE_BIOS) { + mLOG(GBA, WARN, "Incorrect BIOS size"); + return; + } uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ); if (!bios) { mLOG(GBA, WARN, "Couldn't map BIOS");
M src/gba/hardware.csrc/gba/hardware.c

@@ -10,10 +10,99 @@ #include <mgba/internal/gba/io.h>

#include <mgba/internal/gba/serialize.h> #include <mgba-util/formatting.h> #include <mgba-util/hash.h> +#include <mgba-util/memory.h> + +#define EREADER_BLOCK_SIZE 40 +#define EREADER_DOTCODE_STRIDE 1200 +#define EREADER_DOTCODE_SIZE (EREADER_DOTCODE_STRIDE * 40 + 200) mLOG_DEFINE_CATEGORY(GBA_HW, "GBA Pak Hardware", "gba.hardware"); MGBA_EXPORT const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; +const uint16_t EREADER_ADDRESS_CODES[] = { + 1023, + 1174, + 2628, + 3373, + 4233, + 6112, + 6450, + 7771, + 8826, + 9491, + 11201, + 11432, + 12556, + 13925, + 14519, + 16350, + 16629, + 18332, + 18766, + 20007, + 21379, + 21738, + 23096, + 23889, + 24944, + 26137, + 26827, + 28578, + 29190, + 30063, + 31677, + 31956, + 33410, + 34283, + 35641, + 35920, + 37364, + 38557, + 38991, + 40742, + 41735, + 42094, + 43708, + 44501, + 45169, + 46872, + 47562, + 48803, + 49544, + 50913, + 51251, + 53082, + 54014, + 54679 +}; + +const int EREADER_NYBBLE_5BIT[16][5] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 1, 0 }, + { 1, 0, 0, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 1 }, + { 0, 0, 1, 1, 0 }, + { 1, 0, 1, 1, 0 }, + { 0, 1, 0, 0, 0 }, + { 0, 1, 0, 0, 1 }, + { 0, 1, 0, 1, 0 }, + { 1, 0, 1, 0, 0 }, + { 0, 1, 1, 0, 0 }, + { 0, 1, 1, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 0 } +}; + +const uint8_t EREADER_CALIBRATION_TEMPLATE[] = { + 0x43, 0x61, 0x72, 0x64, 0x2d, 0x45, 0x20, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x32, 0x30, + 0x30, 0x31, 0x00, 0x00, 0xcf, 0x72, 0x2f, 0x37, 0x3a, 0x3a, 0x3a, 0x38, 0x33, 0x30, 0x30, 0x37, + 0x3a, 0x39, 0x37, 0x35, 0x33, 0x2f, 0x2f, 0x34, 0x36, 0x36, 0x37, 0x36, 0x34, 0x31, 0x2d, 0x30, + 0x32, 0x34, 0x35, 0x35, 0x34, 0x30, 0x2a, 0x2d, 0x2d, 0x2f, 0x31, 0x32, 0x31, 0x2f, 0x29, 0x2a, + 0x2c, 0x2b, 0x2c, 0x2e, 0x2e, 0x2d, 0x18, 0x2d, 0x8f, 0x03, 0x00, 0x00, 0xc0, 0xfd, 0x77, 0x00, + 0x00, 0x00, 0x01 +}; static void _readPins(struct GBACartridgeHardware* hw); static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);

@@ -31,6 +120,11 @@

static uint16_t _gbpRead(struct mKeyCallback*); static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate); + +static void _eReaderReset(struct GBACartridgeHardware* hw); +static void _eReaderWriteControl0(struct GBACartridgeHardware* hw, uint8_t value); +static void _eReaderWriteControl1(struct GBACartridgeHardware* hw, uint8_t value); +static void _eReaderReadData(struct GBACartridgeHardware* hw); static const int RTC_BYTES[8] = { 1, // Status register 1

@@ -45,6 +139,7 @@ };

void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { hw->gpioBase = base; + hw->eReaderDots = NULL; GBAHardwareClear(hw); hw->gbpCallback.d.readKeys = _gbpRead;

@@ -66,6 +161,11 @@ hw->devices = HW_NONE | (hw->devices & HW_GB_PLAYER_DETECTION);

hw->readWrite = GPIO_WRITE_ONLY; hw->pinState = 0; hw->direction = 0; + + if (hw->eReaderDots) { + mappedMemoryFree(hw->eReaderDots, EREADER_DOTCODE_SIZE); + hw->eReaderDots = NULL; + } if (hw->p->sio.drivers.normal == &hw->gbpDriver.d) { GBASIOSetDriver(&hw->p->sio, 0, SIO_NORMAL_32);

@@ -582,6 +682,357 @@ GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);

} gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt); gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080; +} + +// == e-Reader + +void GBAHardwareInitEReader(struct GBACartridgeHardware* hw) { + hw->devices |= HW_EREADER; + _eReaderReset(hw); + + if (hw->p->memory.savedata.data[0xD000] == 0xFF) { + memset(&hw->p->memory.savedata.data[0xD000], 0, 0x1000); + memcpy(&hw->p->memory.savedata.data[0xD000], EREADER_CALIBRATION_TEMPLATE, sizeof(EREADER_CALIBRATION_TEMPLATE)); + } + if (hw->p->memory.savedata.data[0xE000] == 0xFF) { + memset(&hw->p->memory.savedata.data[0xE000], 0, 0x1000); + memcpy(&hw->p->memory.savedata.data[0xE000], EREADER_CALIBRATION_TEMPLATE, sizeof(EREADER_CALIBRATION_TEMPLATE)); + } +} + +void GBAHardwareEReaderWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) { + address &= 0x700FF; + switch (address >> 17) { + case 0: + hw->eReaderRegisterUnk = value & 0xF; + break; + case 1: + hw->eReaderRegisterReset = (value & 0x8A) | 4; + if (value & 2) { + _eReaderReset(hw); + } + break; + case 2: + mLOG(GBA_HW, GAME_ERROR, "e-Reader write to read-only registers: %05X:%04X", address, value); + break; + default: + mLOG(GBA_HW, STUB, "Unimplemented e-Reader write: %05X:%04X", address, value); + } +} + +void GBAHardwareEReaderWriteFlash(struct GBACartridgeHardware* hw, uint32_t address, uint8_t value) { + address &= 0xFFFF; + switch (address) { + case 0xFFB0: + _eReaderWriteControl0(hw, value); + break; + case 0xFFB1: + _eReaderWriteControl1(hw, value); + break; + case 0xFFB2: + hw->eReaderRegisterLed &= 0xFF00; + hw->eReaderRegisterLed |= value; + break; + case 0xFFB3: + hw->eReaderRegisterLed &= 0x00FF; + hw->eReaderRegisterLed |= value << 8; + break; + default: + mLOG(GBA_HW, STUB, "Unimplemented e-Reader write to flash: %04X:%02X", address, value); + } +} + +uint16_t GBAHardwareEReaderRead(struct GBACartridgeHardware* hw, uint32_t address) { + address &= 0x700FF; + uint16_t value; + switch (address >> 17) { + case 0: + return hw->eReaderRegisterUnk; + case 1: + return hw->eReaderRegisterReset; + case 2: + if (address > 0x40088) { + return 0; + } + LOAD_16(value, address & 0xFE, hw->eReaderData); + return value; + } + mLOG(GBA_HW, STUB, "Unimplemented e-Reader read: %05X", address); + return 0; +} + +uint8_t GBAHardwareEReaderReadFlash(struct GBACartridgeHardware* hw, uint32_t address) { + address &= 0xFFFF; + switch (address) { + case 0xFFB0: + return hw->eReaderRegisterControl0; + case 0xFFB1: + return hw->eReaderRegisterControl1; + default: + mLOG(GBA_HW, STUB, "Unimplemented e-Reader read from flash: %04X", address); + return 0; + } +} + +static void _eReaderAnchor(uint8_t* origin) { + origin[EREADER_DOTCODE_STRIDE * 0 + 1] = 1; + origin[EREADER_DOTCODE_STRIDE * 0 + 2] = 1; + origin[EREADER_DOTCODE_STRIDE * 0 + 3] = 1; + origin[EREADER_DOTCODE_STRIDE * 1 + 0] = 1; + origin[EREADER_DOTCODE_STRIDE * 1 + 1] = 1; + origin[EREADER_DOTCODE_STRIDE * 1 + 2] = 1; + origin[EREADER_DOTCODE_STRIDE * 1 + 3] = 1; + origin[EREADER_DOTCODE_STRIDE * 1 + 4] = 1; + origin[EREADER_DOTCODE_STRIDE * 2 + 0] = 1; + origin[EREADER_DOTCODE_STRIDE * 2 + 1] = 1; + origin[EREADER_DOTCODE_STRIDE * 2 + 2] = 1; + origin[EREADER_DOTCODE_STRIDE * 2 + 3] = 1; + origin[EREADER_DOTCODE_STRIDE * 2 + 4] = 1; + origin[EREADER_DOTCODE_STRIDE * 3 + 0] = 1; + origin[EREADER_DOTCODE_STRIDE * 3 + 1] = 1; + origin[EREADER_DOTCODE_STRIDE * 3 + 2] = 1; + origin[EREADER_DOTCODE_STRIDE * 3 + 3] = 1; + origin[EREADER_DOTCODE_STRIDE * 3 + 4] = 1; + origin[EREADER_DOTCODE_STRIDE * 4 + 1] = 1; + origin[EREADER_DOTCODE_STRIDE * 4 + 2] = 1; + origin[EREADER_DOTCODE_STRIDE * 4 + 3] = 1; +} + +static void _eReaderAlignment(uint8_t* origin) { + origin[8] = 1; + origin[10] = 1; + origin[12] = 1; + origin[14] = 1; + origin[16] = 1; + origin[18] = 1; + origin[21] = 1; + origin[23] = 1; + origin[25] = 1; + origin[27] = 1; + origin[29] = 1; + origin[31] = 1; +} + +static void _eReaderAddress(uint8_t* origin, int a) { + origin[EREADER_DOTCODE_STRIDE * 7 + 2] = 1; + uint16_t addr = EREADER_ADDRESS_CODES[a]; + int i; + for (i = 0; i < 16; ++i) { + origin[EREADER_DOTCODE_STRIDE * (16 + i) + 2] = (addr >> (15 - i)) & 1; + } +} + +void GBAHardwareEReaderScan(struct GBACartridgeHardware* hw, const void* data, size_t size) { + if (!hw->eReaderDots) { + hw->eReaderDots = anonymousMemoryMap(EREADER_DOTCODE_SIZE); + } + memset(hw->eReaderDots, 0, EREADER_DOTCODE_SIZE); + + int base; + switch (size) { + case 2912: + base = 25; + break; + case 1872: + base = 1; + break; + default: + return; + } + + size_t i; + for (i = 0; i < (size / 104) + 1; ++i) { + uint8_t* origin = &hw->eReaderDots[35 * i + 200]; + _eReaderAnchor(&origin[EREADER_DOTCODE_STRIDE * 0]); + _eReaderAnchor(&origin[EREADER_DOTCODE_STRIDE * 35]); + _eReaderAddress(origin, base + i); + } + for (i = 0; i < size / 104; ++i) { + uint8_t block[1040]; + uint8_t* origin = &hw->eReaderDots[35 * i + 200]; + _eReaderAlignment(&origin[EREADER_DOTCODE_STRIDE * 2]); + _eReaderAlignment(&origin[EREADER_DOTCODE_STRIDE * 37]); + + int b; + for (b = 0; b < 104; ++b) { + const int* nybble5; + nybble5 = EREADER_NYBBLE_5BIT[((const uint8_t*) data)[i * 104 + b] >> 4]; + block[b * 10 + 0] = nybble5[0]; + block[b * 10 + 1] = nybble5[1]; + block[b * 10 + 2] = nybble5[2]; + block[b * 10 + 3] = nybble5[3]; + block[b * 10 + 4] = nybble5[4]; + nybble5 = EREADER_NYBBLE_5BIT[((const uint8_t*) data)[i * 104 + b] & 0xF]; + block[b * 10 + 5] = nybble5[0]; + block[b * 10 + 6] = nybble5[1]; + block[b * 10 + 7] = nybble5[2]; + block[b * 10 + 8] = nybble5[3]; + block[b * 10 + 9] = nybble5[4]; + } + + b = 0; + int y; + for (y = 0; y < 3; ++y) { + memcpy(&origin[EREADER_DOTCODE_STRIDE * (4 + y) + 7], &block[b], 26); + b += 26; + } + for (y = 0; y < 26; ++y) { + memcpy(&origin[EREADER_DOTCODE_STRIDE * (7 + y) + 3], &block[b], 34); + b += 34; + } + for (y = 0; y < 3; ++y) { + memcpy(&origin[EREADER_DOTCODE_STRIDE * (33 + y) + 7], &block[b], 26); + b += 26; + } + } + hw->eReaderX = -24; +} + +void _eReaderReset(struct GBACartridgeHardware* hw) { + memset(hw->eReaderData, 0, sizeof(hw->eReaderData)); + hw->eReaderRegisterUnk = 0; + hw->eReaderRegisterReset = 4; + hw->eReaderRegisterControl0 = 0; + hw->eReaderRegisterControl1 = 0x80; + hw->eReaderRegisterLed = 0; + hw->eReaderState = 0; + hw->eReaderActiveRegister = 0; +} + +void _eReaderWriteControl0(struct GBACartridgeHardware* hw, uint8_t value) { + EReaderControl0 control = value & 0x7F; + EReaderControl0 oldControl = hw->eReaderRegisterControl0; + if (hw->eReaderState == EREADER_SERIAL_INACTIVE) { + if (EReaderControl0IsClock(oldControl) && EReaderControl0IsData(oldControl) && !EReaderControl0IsData(control)) { + hw->eReaderState = EREADER_SERIAL_STARTING; + } + } else if (EReaderControl0IsClock(oldControl) && !EReaderControl0IsData(oldControl) && EReaderControl0IsData(control)) { + hw->eReaderState = EREADER_SERIAL_INACTIVE; + + } else if (hw->eReaderState == EREADER_SERIAL_STARTING) { + if (EReaderControl0IsClock(oldControl) && !EReaderControl0IsData(oldControl) && !EReaderControl0IsClock(control)) { + hw->eReaderState = EREADER_SERIAL_BIT_0; + hw->eReaderCommand = EREADER_COMMAND_IDLE; + } + } else if (EReaderControl0IsClock(oldControl) && !EReaderControl0IsClock(control)) { + mLOG(GBA_HW, DEBUG, "[e-Reader] Serial falling edge: %c %i", EReaderControl0IsDirection(control) ? '>' : '<', EReaderControl0GetData(control)); + // TODO: Improve direction control + if (EReaderControl0IsDirection(control)) { + hw->eReaderByte |= EReaderControl0GetData(control) << (7 - (hw->eReaderState - EREADER_SERIAL_BIT_0)); + ++hw->eReaderState; + if (hw->eReaderState == EREADER_SERIAL_END_BIT) { + mLOG(GBA_HW, DEBUG, "[e-Reader] Wrote serial byte: %02x", hw->eReaderByte); + switch (hw->eReaderCommand) { + case EREADER_COMMAND_IDLE: + hw->eReaderCommand = hw->eReaderByte; + break; + case EREADER_COMMAND_SET_INDEX: + hw->eReaderActiveRegister = hw->eReaderByte; + hw->eReaderCommand = EREADER_COMMAND_WRITE_DATA; + break; + case EREADER_COMMAND_WRITE_DATA: + switch (hw->eReaderActiveRegister & 0x7F) { + case 0: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + // Read-only + mLOG(GBA_HW, GAME_ERROR, "Writing to read-only e-Reader serial register: %02X", hw->eReaderActiveRegister); + break; + default: + if ((hw->eReaderActiveRegister & 0x7F) > 0x5A) { + mLOG(GBA_HW, GAME_ERROR, "Writing to non-existent e-Reader serial register: %02X", hw->eReaderActiveRegister); + break; + } + hw->eReaderSerial[hw->eReaderActiveRegister & 0x7F] = hw->eReaderByte; + break; + } + ++hw->eReaderActiveRegister; + break; + default: + mLOG(GBA_HW, ERROR, "Hit undefined state %02X in e-Reader state machine", hw->eReaderCommand); + break; + } + hw->eReaderState = EREADER_SERIAL_BIT_0; + hw->eReaderByte = 0; + } + } else if (hw->eReaderCommand == EREADER_COMMAND_READ_DATA) { + int bit = hw->eReaderSerial[hw->eReaderActiveRegister & 0x7F] >> (7 - (hw->eReaderState - EREADER_SERIAL_BIT_0)); + control = EReaderControl0SetData(control, bit); + ++hw->eReaderState; + if (hw->eReaderState == EREADER_SERIAL_END_BIT) { + ++hw->eReaderActiveRegister; + mLOG(GBA_HW, DEBUG, "[e-Reader] Read serial byte: %02x", hw->eReaderSerial[hw->eReaderActiveRegister & 0x7F]); + } + } + } else if (!EReaderControl0IsDirection(control)) { + // Clear the error bit + control = EReaderControl0ClearData(control); + } + hw->eReaderRegisterControl0 = control; + if (!EReaderControl0IsScan(oldControl) && EReaderControl0IsScan(control)) { + hw->eReaderX = 0; + hw->eReaderY = 0; + } else if (EReaderControl0IsLedEnable(control) && EReaderControl0IsScan(control) && !EReaderControl1IsScanline(hw->eReaderRegisterControl1)) { + _eReaderReadData(hw); + } + mLOG(GBA_HW, STUB, "Unimplemented e-Reader Control0 write: %02X", value); +} + +void _eReaderWriteControl1(struct GBACartridgeHardware* hw, uint8_t value) { + EReaderControl1 control = (value & 0x32) | 0x80; + hw->eReaderRegisterControl1 = control; + if (EReaderControl0IsScan(hw->eReaderRegisterControl0) && !EReaderControl1IsScanline(control)) { + ++hw->eReaderY; + if (hw->eReaderY == (hw->eReaderSerial[0x15] | (hw->eReaderSerial[0x14] << 8))) { + hw->eReaderY = 0; + if (hw->eReaderX < 3400) { + hw->eReaderX += 220; + } + } + _eReaderReadData(hw); + } + mLOG(GBA_HW, STUB, "Unimplemented e-Reader Control1 write: %02X", value); +} + +void _eReaderReadData(struct GBACartridgeHardware* hw) { + memset(hw->eReaderData, 0, EREADER_BLOCK_SIZE); + if (hw->eReaderDots) { + int y = hw->eReaderY - 10; + if (y < 0 || y >= 120) { + memset(hw->eReaderData, 0, EREADER_BLOCK_SIZE); + } else { + int i; + uint8_t* origin = &hw->eReaderDots[EREADER_DOTCODE_STRIDE * (y / 3) + 16]; + for (i = 0; i < 20; ++i) { + uint16_t word = 0; + int x = hw->eReaderX + i * 16; + word |= origin[(x + 0) / 3] << 8; + word |= origin[(x + 1) / 3] << 9; + word |= origin[(x + 2) / 3] << 10; + word |= origin[(x + 3) / 3] << 11; + word |= origin[(x + 4) / 3] << 12; + word |= origin[(x + 5) / 3] << 13; + word |= origin[(x + 6) / 3] << 14; + word |= origin[(x + 7) / 3] << 15; + word |= origin[(x + 8) / 3]; + word |= origin[(x + 9) / 3] << 1; + word |= origin[(x + 10) / 3] << 2; + word |= origin[(x + 11) / 3] << 3; + word |= origin[(x + 12) / 3] << 4; + word |= origin[(x + 13) / 3] << 5; + word |= origin[(x + 14) / 3] << 6; + word |= origin[(x + 15) / 3] << 7; + STORE_16(word, (19 - i) << 1, hw->eReaderData); + } + } + } + hw->eReaderRegisterControl1 = EReaderControl1FillScanline(hw->eReaderRegisterControl1); + if (EReaderControl0IsLedEnable(hw->eReaderRegisterControl0)) { + uint16_t led = 2754; // TODO: Figure out why this breaks if using the LED register + GBARaiseIRQ(hw->p, IRQ_GAMEPAK, -led); + } } // == Serialization
M src/gba/hle-bios.csrc/gba/hle-bios.c

@@ -39,8 +39,8 @@ 0xc0, 0x00, 0x00, 0x02, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,

0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe0, 0xa0, 0x03, - 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0x20, 0xa0, 0xe3, 0x01, 0xc3, 0xa0, 0xe3, - 0x01, 0x23, 0xcc, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, + 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0xb0, 0xa0, 0xe3, 0x01, 0xc3, 0xa0, 0xe3, + 0x01, 0xb3, 0xcc, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1,
M src/gba/hle-bios.ssrc/gba/hle-bios.s

@@ -170,9 +170,9 @@ NopCall:

bx lr Halt: -mov r2, #0 +mov r11, #0 mov r12, #0x04000000 -strb r2, [r12, #0x301] +strb r11, [r12, #0x301] bx lr VBlankIntrWait:
M src/gba/memory.csrc/gba/memory.c

@@ -570,6 +570,8 @@ case REGION_CART2_EX:

wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if (memory->savedata.type == SAVEDATA_EEPROM || memory->savedata.type == SAVEDATA_EEPROM512) { value = GBASavedataReadEEPROM(&memory->savedata); + } else if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) { + value = GBAHardwareEReaderRead(&memory->hw, address); } else if ((address & (SIZE_CART0 - 1)) < memory->romSize) { LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {

@@ -683,7 +685,9 @@ }

if (gba->performingDMA == 1) { break; } - if (memory->savedata.type == SAVEDATA_SRAM) { + if (memory->hw.devices & HW_EREADER && (address & 0xE00FF80) >= 0xE00FF80) { + value = GBAHardwareEReaderReadFlash(&memory->hw, address); + } else if (memory->savedata.type == SAVEDATA_SRAM) { value = memory->savedata.data[address & (SIZE_CART_SRAM - 1)]; } else if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) { value = GBASavedataReadFlash(&memory->savedata, address);

@@ -911,7 +915,10 @@ }

mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); break; case REGION_CART2_EX: - if (memory->savedata.type == SAVEDATA_AUTODETECT) { + if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) { + GBAHardwareEReaderWrite(&memory->hw, address, value); + break; + } else if (memory->savedata.type == SAVEDATA_AUTODETECT) { mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata); }

@@ -992,7 +999,9 @@ mLOG(GBA_MEM, INFO, "Detected SRAM savegame");

GBASavedataInitSRAM(&memory->savedata); } } - if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) { + if (memory->hw.devices & HW_EREADER && (address & 0xE00FF80) >= 0xE00FF80) { + GBAHardwareEReaderWriteFlash(&memory->hw, address, value); + } else if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) { GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == SAVEDATA_SRAM) { if (memory->vfame.cartType) {
M src/gba/overrides.csrc/gba/overrides.c

@@ -50,6 +50,11 @@ // Drill Dozer

{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE, false }, { "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE, false }, + // e-Reader + { "PEAJ", SAVEDATA_FLASH1M, HW_EREADER, IDLE_LOOP_NONE }, + { "PSAJ", SAVEDATA_FLASH1M, HW_EREADER, IDLE_LOOP_NONE }, + { "PSAE", SAVEDATA_FLASH1M, HW_EREADER, IDLE_LOOP_NONE }, + // Final Fantasy Tactics Advance { "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428, false },

@@ -326,6 +331,10 @@ }

if (override->hardware & HW_TILT) { GBAHardwareInitTilt(&gba->memory.hw); + } + + if (override->hardware & HW_EREADER) { + GBAHardwareInitEReader(&gba->memory.hw); } if (override->hardware & HW_GB_PLAYER_DETECTION) {
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -668,6 +668,20 @@ m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;

m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); } +void CoreController::scanCard(const QString& path) { +#ifdef M_CORE_GBA + QFile file(path); + file.open(QIODevice::ReadOnly); + m_eReaderData = file.read(2912); + + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) { + CoreController* controller = static_cast<CoreController*>(thread->userData); + GBAHardwareEReaderScan(&static_cast<GBA*>(thread->core->board)->memory.hw, controller->m_eReaderData.constData(), controller->m_eReaderData.size()); + }); +#endif +} + + void CoreController::importSharkport(const QString& path) { #ifdef M_CORE_GBA if (platform() != PLATFORM_GBA) {
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once #include <QByteArray> +#include <QFile> #include <QList> #include <QMutex> #include <QObject>

@@ -128,6 +129,7 @@ void saveBackupState();

void loadSave(const QString&, bool temporary); void loadPatch(const QString&); + void scanCard(const QString&); void replaceGame(const QString&); void yankPak();

@@ -252,6 +254,7 @@ #endif

#ifdef M_CORE_GBA GBASIOBattlechipGate m_battlechip; + QByteArray m_eReaderData; #endif };
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -472,6 +472,13 @@ }

} } +void Window::scanCard() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select e-Reader dotcode")); + if (!filename.isEmpty()) { + m_controller->scanCard(filename); + } +} + void Window::openView(QWidget* widget) { connect(this, &Window::shutdown, widget, &QWidget::close); widget->setAttribute(Qt::WA_DeleteOnClose);

@@ -1183,7 +1190,11 @@ setController(m_manager->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")), QString());

}, "file"); #endif - m_actions.addAction(tr("Replace ROM..."), "replaceROM", this, &Window::replaceROM, "file"); + addGameAction(tr("Replace ROM..."), "replaceROM", this, &Window::replaceROM, "file"); +#ifdef M_CORE_GBA + Action* scanCard = addGameAction(tr("Scan e-Reader dotcode..."), "scanCard", this, &Window::scanCard, "file"); + m_platformActions.insert(PLATFORM_GBA, scanCard); +#endif Action* romInfo = addGameAction(tr("ROM &info..."), "romInfo", openControllerTView<ROMInfo>(), "file");
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -75,6 +75,7 @@ #endif

void selectSave(bool temporary); void selectState(bool load); void selectPatch(); + void scanCard(); void enterFullScreen(); void exitFullScreen(); void toggleFullScreen();
M src/platform/qt/library/LibraryController.cppsrc/platform/qt/library/LibraryController.cpp

@@ -1,4 +1,5 @@

/* Copyright (c) 2014-2017 waddlesplash + * Copyright (c) 2014-2020 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this

@@ -10,7 +11,7 @@ #include "GBAApp.h"

#include "LibraryGrid.h" #include "LibraryTree.h" -namespace QGBA { +using namespace QGBA; LibraryEntry::LibraryEntry(mLibraryEntry* entry) : entry(entry)

@@ -57,6 +58,7 @@ refresh();

} LibraryController::~LibraryController() { + freeLibrary(); mLibraryListingDeinit(&m_listing); }

@@ -134,7 +136,7 @@

QStringList allEntries; QList<LibraryEntryRef> newEntries; - mLibraryListingClear(&m_listing); + freeLibrary(); mLibraryGetEntries(m_library.get(), &m_listing, 0, 0, nullptr); for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) { mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i);

@@ -185,4 +187,9 @@ std::shared_ptr<mLibrary> library = m_library;

mLibraryLoadDirectory(library.get(), dir.toUtf8().constData()); } -} +void LibraryController::freeLibrary() { + for (size_t i = 0; i < mLibraryListingSize(&m_listing); ++i) { + mLibraryEntryFree(mLibraryListingGetPointer(&m_listing, i)); + } + mLibraryListingClear(&m_listing); +}
M src/platform/qt/library/LibraryController.hsrc/platform/qt/library/LibraryController.h

@@ -1,4 +1,5 @@

/* Copyright (c) 2014-2017 waddlesplash + * Copyright (c) 2014-2020 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this

@@ -97,6 +98,7 @@ void refresh();

private: void loadDirectory(const QString&); // Called on separate thread + void freeLibrary(); ConfigController* m_config = nullptr; std::shared_ptr<mLibrary> m_library;
M src/util/table.csrc/util/table.c

@@ -187,7 +187,9 @@ uint32_t hash = hash32(key, strlen(key), 0);

struct TableList* list; TABLE_LOOKUP_START(HASH_TABLE_COMPARATOR, list, hash) { if (value != lookupResult->value) { - table->deinitializer(lookupResult->value); + if (table->deinitializer) { + table->deinitializer(lookupResult->value); + } lookupResult->value = value; } return;
M src/util/vfs/vfs-lzma.csrc/util/vfs/vfs-lzma.c

@@ -105,6 +105,7 @@ SRes res = SzArEx_Open(&vd->db, &vd->lookStream.vt, &vd->allocImp, &vd->allocTempImp);

if (res != SZ_OK) { SzArEx_Free(&vd->db, &vd->allocImp); File_Close(&vd->archiveStream.file); + free(vd->lookStream.buf); free(vd); return 0; }
A tools/make-dotcode.py

@@ -0,0 +1,154 @@

+import numpy as np +import PIL.Image +import PIL.ImageChops +import sys + +blocksize = 104 +pow = [0] * 256 +rev = [0] * 256 +rev[0] = 0xFF +x = 1 +for i in range(0xFF): + pow[i]=x + rev[x]=i + x *= 2 + if x >= 0x100: + x ^= 0x187 +gg = [0] * 16 +gg[0] = pow[0x78] +for i in range(16): + gg[i] = 1 + for j in range(i + 1): + j = i - j + if j == 0: + y = 0 + else: + y = gg[j - 1] + x = gg[j] + if x != 0: + x = rev[x] + 0x78 + i + if x >= 0xFF: + x -= 0xFF + y ^= pow[x] + gg[j] = y +for i in range(16): + gg[i] = rev[gg[i]] + +print(*[hex(x) for x in gg]) + +def interleave(data, header): + data = data.reshape([-1, 48]).T + new_data = np.zeros((64, data.shape[1]), dtype=data.dtype) + for i, row in enumerate(data.T): + new_data[:,i] = rs(row) + data = new_data.reshape([-1]) + new_data = np.zeros(data.shape[0] + (102 - data.shape[0] % 102), dtype=data.dtype) + new_data[:data.shape[0]] = data + new_data = new_data.reshape([-1, 102]) + data = new_data + new_data = np.zeros((data.shape[0], 104), dtype=data.dtype) + new_data[:,2:] = data + for i in range(new_data.shape[0]): + x = (i * 2) % len(header) + new_data[i,:2] = header[x:x + 2] + data = new_data.reshape([-1]) + return data + +def rs(data): + new_data = np.zeros(data.shape[0] + 16, dtype=data.dtype) + new_data[:data.shape[0]] = data + new_data = np.flipud(new_data) + for i in range(data.shape[0]): + i = new_data.shape[0] - i - 1 + z = rev[new_data[i] ^ new_data[15]] + for j in range(16): + j = 15 - j + if j == 0: + x = 0 + else: + x = new_data[j - 1] + if z != 0xFF: + y = gg[j] + if y != 0xFF: + y += z + if y >= 0xFF: + y -= 0xFF + x ^= pow[y] + new_data[j] = x + new_data[:16] = ~new_data[:16] + new_data = np.flipud(new_data) + return new_data + +def bin2raw(data): + if len(data) == 1344: + header = np.array([0x00, 0x02, 0x00, 0x01, 0x40, 0x10, 0x00, 0x1C], dtype=np.uint8) + else: + header = np.array([0x00, 0x03, 0x00, 0x19, 0x40, 0x10, 0x00, 0x2C], dtype=np.uint8) + header = rs(header) + new_data = interleave(np.frombuffer(data, np.uint8), header) + return new_data.tobytes() + +with open(sys.argv[1], 'rb') as f: + data = f.read() +size = len(data) + +if size in (1344, 2112): + data = bin2raw(data) + size = len(data) + with open('dotcode.raw', 'wb') as f: + f.write(data) + +blocks = size // blocksize +height = 36 +width = 35 +margin = 2 + +dots = np.zeros((width * blocks + margin * 2 + 1, height + margin * 2), dtype=np.bool) +anchor = np.array([[0, 1, 1, 1, 0], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [0, 1, 1, 1, 0]], dtype=np.bool) +alignment = np.array([1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0], dtype=np.bool) +nybbles = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [1, 0, 0, 1, 0], + [0, 0, 1, 0, 0], [0, 0, 1, 0, 1], [0, 0, 1, 1, 0], [1, 0, 1, 1, 0], + [0, 1, 0, 0, 0], [0, 1, 0, 0, 1], [0, 1, 0, 1, 0], [1, 0, 1, 0, 0], + [0, 1, 1, 0, 0], [0, 1, 1, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 0]] +addr = [0x03FF] +for i in range(1, 54): + addr.append(addr[i - 1] ^ ((i & -i) * 0x769)) + if (i & 0x07) == 0: + addr[i] ^= 0x769 + if (i & 0x0F) == 0: + addr[i] ^= 0x769 << 1 + if (i & 0x1F) == 0: + addr[i] ^= (0x769 << 2) ^ 0x769 + +base = 1 if blocks == 18 else 25 +for i in range(blocks + 1): + dots[i * width:i * width + 5, 0:5] = anchor + dots[i * width:i * width + 5, height + margin * 2 - 5:height + margin * 2] = anchor + dots[i * width + margin, margin + 5] = 1 + a = addr[base + i] + for j in range(16): + dots[i * width + margin, margin + 14 + j] = a & (1 << (15 - j)) +for i in range(blocks): + dots[i * width:(i + 1) * width, margin] = alignment + dots[i * width:(i + 1) * width, height + margin - 1] = alignment + block = [] + for byte in data[i * blocksize:(i + 1) * blocksize]: + block.extend(nybbles[byte >> 4]) + block.extend(nybbles[byte & 0xF]) + j = 0 + for y in range(3): + dots[i * width + margin + 5:i * width + margin + 31, margin + 2 + y] = block[j:j + 26] + j += 26 + for y in range(26): + dots[i * width + margin + 1:i * width + margin + 35, margin + 5 + y] = block[j:j + 34] + j += 34 + for y in range(3): + dots[i * width + margin + 5:i * width + margin + 31, margin + 31 + y] = block[j:j + 26] + j += 26 +im = PIL.Image.fromarray(dots.T) +im = PIL.ImageChops.invert(im) +im.save('dotcode.png')