IPS patch loading support
@@ -82,6 +82,9 @@
void GBAMemoryDeinit(struct GBA* gba) { mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM); mappedMemoryFree(gba->memory.iwram, SIZE_WORKING_IRAM); + if (gba->memory.rom) { + mappedMemoryFree(gba->memory.rom, gba->memory.romSize); + } GBASavedataDeinit(&gba->memory.savedata); }
@@ -6,6 +6,8 @@ #include "gba-serialize.h"
#include "debugger/debugger.h" +#include "util/patch.h" + #include <signal.h> #ifdef USE_PTHREADS@@ -52,6 +54,7 @@ #endif
struct GBA gba; struct ARMCore cpu; + struct Patch patch; struct GBAThread* threadContext = context; struct ARMComponent* components[1] = {}; int numComponents = 0;@@ -110,6 +113,10 @@ GBALoadROM(&gba, threadContext->fd, threadContext->fname);
if (threadContext->biosFd >= 0) { GBALoadBIOS(&gba, threadContext->biosFd); } + + if (threadContext->patchFd >= 0 && loadPatch(threadContext->patchFd, &patch)) { + GBAApplyPatch(&gba, &patch); + } } if (threadContext->debugger) {@@ -169,6 +176,7 @@ void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
threadContext->fd = opts->fd; threadContext->fname = opts->fname; threadContext->biosFd = opts->biosFd; + threadContext->patchFd = opts->patchFd; threadContext->frameskip = opts->frameskip; threadContext->logLevel = opts->logLevel; threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
@@ -46,6 +46,7 @@ struct GBASIODriverSet sioDrivers;
struct ARMDebugger* debugger; int fd; int biosFd; + int patchFd; const char* fname; int activeKeys; int frameskip;
@@ -6,6 +6,7 @@ #include "gba-sio.h"
#include "gba-thread.h" #include "util/memory.h" +#include "util/patch.h" #include <sys/stat.h>@@ -139,6 +140,10 @@ gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
} void GBADestroy(struct GBA* gba) { + if (gba->pristineRom == gba->memory.rom) { + gba->memory.rom = 0; + } + mappedMemoryFree(gba->pristineRom, gba->pristineRomSize); GBAMemoryDeinit(gba); GBAVideoDeinit(&gba->video); GBAAudioDeinit(&gba->audio);@@ -360,10 +365,12 @@ }
void GBALoadROM(struct GBA* gba, int fd, const char* fname) { struct stat info; - gba->memory.rom = fileMemoryMap(fd, SIZE_CART0, MEMORY_READ); + gba->pristineRom = fileMemoryMap(fd, SIZE_CART0, MEMORY_READ); + gba->memory.rom = gba->pristineRom; gba->activeFile = fname; fstat(fd, &info); - gba->memory.romSize = info.st_size; + gba->pristineRomSize = info.st_size; + gba->memory.romSize = gba->pristineRomSize; if (gba->savefile) { GBASavedataInit(&gba->memory.savedata, gba->savefile); }@@ -389,6 +396,18 @@ if ((gba->cpu->gprs[ARM_PC] >> BASE_OFFSET) == BASE_BIOS) {
gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); } // TODO: error check +} + +void GBAApplyPatch(struct GBA* gba, struct Patch* patch) { + size_t patchedSize = patch->outputSize(patch, gba->memory.romSize); + gba->memory.rom = anonymousMemoryMap(patchedSize); + memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize > patchedSize ? patchedSize : gba->memory.romSize); + if (!patch->applyPatch(patch, gba->memory.rom, patchedSize)) { + mappedMemoryFree(gba->memory.rom, patchedSize); + gba->memory.rom = gba->pristineRom; + return; + } + gba->memory.romSize = patchedSize; } void GBATimerUpdateRegister(struct GBA* gba, int timer) {
@@ -62,6 +62,8 @@ };
struct GBARotationSource; struct GBA; +struct Patch; + typedef void (*GBALogHandler)(struct GBA*, enum GBALogLevel, const char* format, va_list args); struct GBA {@@ -95,6 +97,8 @@ uint32_t biosChecksum;
int* keySource; struct GBARotationSource* rotationSource; struct GBARumble* rumble; + void* pristineRom; + size_t pristineRomSize; const char* activeFile; const char* savefile;@@ -139,6 +143,7 @@ void GBADetachDebugger(struct GBA* gba);
void GBALoadROM(struct GBA* gba, int fd, const char* fname); void GBALoadBIOS(struct GBA* gba, int fd); +void GBAApplyPatch(struct GBA* gba, struct Patch* patch); __attribute__((format (printf, 3, 4))) void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...);
@@ -25,6 +25,7 @@ static const char* _defaultFilename = "test.rom";
static const struct option _options[] = { { "bios", 1, 0, 'b' }, + { "patch", 1, 0, 'p' }, { "frameskip", 1, 0, 's' }, #ifdef USE_CLI_DEBUGGER { "debug", 1, 0, 'd' },@@ -41,10 +42,11 @@ int parseCommandArgs(struct StartupOptions* opts, int argc, char* const* argv, struct SubParser* subparser) {
memset(opts, 0, sizeof(*opts)); opts->fd = -1; opts->biosFd = -1; + opts->patchFd = -1; int ch; char options[64] = - "b:l:s:" + "b:l:p:s:" #ifdef USE_CLI_DEBUGGER "d" #endif@@ -79,6 +81,9 @@ break;
#endif case 'l': opts->logLevel = atoi(optarg); + break; + case 'p': + opts->patchFd = open(optarg, O_RDONLY); break; case 's': opts->frameskip = atoi(optarg);
@@ -18,6 +18,7 @@ struct StartupOptions {
int fd; const char* fname; int biosFd; + int patchFd; int logLevel; int frameskip; int rewindBufferCapacity;
@@ -0,0 +1,88 @@
+#include "util/patch-ips.h" + +#include "util/patch.h" + +#include <stdint.h> +#include <unistd.h> + +static size_t _IPSOutputSize(struct Patch* patch, size_t inSize); +static int _IPSApplyPatch(struct Patch* patch, void* out, size_t outSize); + +int loadPatchIPS(struct Patch* patch) { + lseek(patch->patchfd, 0, SEEK_SET); + + char buffer[5]; + if (read(patch->patchfd, buffer, 5) != 5) { + return 0; + } + + if (memcmp(buffer, "PATCH", 5) != 0) { + return 0; + } + + lseek(patch->patchfd, -3, SEEK_END); + if (read(patch->patchfd, buffer, 3) != 3) { + return 0; + } + + if (memcmp(buffer, "EOF", 3) != 0) { + return 0; + } + + patch->outputSize = _IPSOutputSize; + patch->applyPatch = _IPSApplyPatch; + return 1; +} + +size_t _IPSOutputSize(struct Patch* patch, size_t inSize) { + (void) (patch); + return inSize; +} + +int _IPSApplyPatch(struct Patch* patch, void* out, size_t outSize) { + if (lseek(patch->patchfd, 5, SEEK_SET) != 5) { + return 0; + } + uint8_t* buf = out; + + while (1) { + uint32_t offset = 0; + uint16_t size = 0; + + if (read(patch->patchfd, &offset, 3) != 3) { + return 0; + } + + if (offset == 0x464F45) { + return 1; + } + + offset = (offset >> 16) | (offset & 0xFF00) | ((offset << 16) & 0xFF0000); + if (read(patch->patchfd, &size, 2) != 2) { + return 0; + } + if (!size) { + // RLE chunk + if (read(patch->patchfd, &size, 2) != 2) { + return 0; + } + size = (size >> 8) | (size << 8); + uint8_t byte; + if (read(patch->patchfd, &byte, 1) != 1) { + return 0; + } + if (offset + size > outSize) { + return 0; + } + memset(&buf[offset], byte, size); + } else { + size = (size >> 8) | (size << 8); + if (offset + size > outSize) { + return 0; + } + if (read(patch->patchfd, &buf[offset], size) != size) { + return 0; + } + } + } +}
@@ -0,0 +1,8 @@
+#ifndef PATCH_IPS_H +#define PATCH_IPS_H + +struct Patch; + +int loadPatchIPS(struct Patch* patch); + +#endif
@@ -0,0 +1,15 @@
+#include "util/patch.h" + +#include "util/patch-ips.h" + +int loadPatch(int patchfd, struct Patch* patch) { + patch->patchfd = patchfd; + + if (loadPatchIPS(patch)) { + return 1; + } + + patch->outputSize = 0; + patch->applyPatch = 0; + return 0; +}
@@ -0,0 +1,15 @@
+#ifndef PATCH_H +#define PATCH_H + +#include <string.h> + +struct Patch { + int patchfd; + + size_t (*outputSize)(struct Patch* patch, size_t inSize); + int (*applyPatch)(struct Patch* patch, void* out, size_t outSize); +}; + +int loadPatch(int patchfd, struct Patch* patch); + +#endif