diff --git a/Makefile b/Makefile index f81f210e..681e3ab5 100644 --- a/Makefile +++ b/Makefile @@ -203,7 +203,7 @@ develop: $Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \ -Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \ -Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \ - -Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5 \ + -Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \ -Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \ -Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \ -Wnested-externs -Wno-aggressive-loop-optimizations -Winline \ diff --git a/include/helpers.h b/include/helpers.h index 59af3301..32b21087 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -32,6 +32,58 @@ static inline _Noreturn unreachable_(void) {} #endif +// Use builtins whenever possible, and shim them otherwise +#ifdef __GNUC__ // GCC or compatible + #define ctz __builtin_ctz + #define clz __builtin_clz + +#elif defined(_MSC_VER) + #include + #include + #pragma intrinsic(_BitScanReverse, _BitScanForward) + static inline int ctz(unsigned int x) + { + unsigned long cnt; + + assert(x != 0); + _BitScanForward(&cnt, x); + return cnt; + } + static inline int clz(unsigned int x) + { + unsigned long cnt; + + assert(x != 0); + _BitScanReverse(&cnt, x); + return 31 - cnt; + } + +#else + // FIXME: these are rarely used, and need testing... + #include + static inline int ctz(unsigned int x) + { + int cnt = 0; + + while (!(x & 1)) { + x >>= 1; + cnt++; + } + return cnt; + } + + static inline int clz(unsigned int x) + { + int cnt = 0; + + while (x <= UINT_MAX / 2) { + x <<= 1; + cnt++; + } + return cnt; + } +#endif + // Macros for stringification #define STR(x) #x #define EXPAND_AND_STR(x) STR(x) diff --git a/include/platform.h b/include/platform.h index 9fbd95cc..8be9e893 100644 --- a/include/platform.h +++ b/include/platform.h @@ -34,9 +34,15 @@ /* MSVC doesn't use POSIX types or defines for `read` */ #ifdef _MSC_VER +# include # define STDIN_FILENO 0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 # define ssize_t int # define SSIZE_MAX INT_MAX +#else +# include +# include #endif /* MSVC doesn't support `[static N]` for array arguments from C99 */ @@ -46,4 +52,22 @@ # define MIN_NB_ELMS(N) static (N) #endif +// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag +#ifdef _MSC_VER +# include +# define O_RDWR _O_RDWR +# define S_ISREG(field) ((field) & _S_IFREG) +# define O_BINARY _O_BINARY +#elif !defined(O_BINARY) // Cross-compilers define O_BINARY +# define O_BINARY 0 // POSIX says we shouldn't care! +#endif // _MSC_VER + +// Windows has stdin and stdout open as text by default, which we may not want +#if defined(_MSC_VER) || defined(__MINGW32__) +# include +# define setmode(fd, mode) _setmode(fd, mode) +#else +# define setmode(fd, mode) ((void)0) +#endif + #endif /* RGBDS_PLATFORM_H */ diff --git a/src/fix/main.c b/src/fix/main.c index 5742d01f..bc2acf74 100644 --- a/src/fix/main.c +++ b/src/fix/main.c @@ -1,24 +1,36 @@ /* * This file is part of RGBDS. * - * Copyright (c) 2010-2018, Anthony J. Bentley and RGBDS contributors. + * Copyright (c) 2020, Eldred habert and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include -#include "extern/err.h" #include "extern/getopt.h" +#include "helpers.h" +#include "platform.h" #include "version.h" +#define UNSPECIFIED 0x100 // May not be in byte range + +#define BANK_SIZE 0x4000 + /* Short options */ -static char const *optstring = "Ccf:i:jk:l:m:n:p:r:st:Vv"; +static const char *optstring = "Ccf:i:jk:l:m:n:p:r:st:Vv"; /* * Equivalent long options @@ -49,12 +61,12 @@ static struct option const longopts[] = { { NULL, no_argument, NULL, 0 } }; -static void print_usage(void) +static void printUsage(void) { fputs( "Usage: rgbfix [-jsVv] [-C | -c] [-f ] [-i ] [-k ]\n" " [-l ] [-m ] [-n ]\n" -" [-p ] [-r ] [-t ] \n" +" [-p ] [-r ] [-t ] [ ...]\n" "Useful options:\n" " -m, --mbc-type set the MBC type byte to this value; refer\n" " to the man page for a list of values\n" @@ -65,535 +77,1109 @@ static void print_usage(void) "\n" "For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n", stderr); - exit(1); } -/* - * Cartridge type names from Pan Docs, also allowing "_" instead of " " - * and with "ROM" as an alias for "ROM ONLY". - * https://gbdev.io/pandocs/#_0147-cartridge-type - */ -struct { - long value; - const char *name; -} cartridge_types[] = { - {0x00, "ROM ONLY"}, - {0x00, "ROM_ONLY"}, - {0x00, "ROM"}, - {0x01, "MBC1"}, - {0x02, "MBC1+RAM"}, - {0x03, "MBC1+RAM+BATTERY"}, - {0x05, "MBC2"}, - {0x06, "MBC2+BATTERY"}, - {0x08, "ROM+RAM"}, - {0x09, "ROM+RAM+BATTERY"}, - {0x0B, "MMM01"}, - {0x0C, "MMM01+RAM"}, - {0x0D, "MMM01+RAM+BATTERY"}, - {0x0F, "MBC3+TIMER+BATTERY"}, - {0x10, "MBC3+TIMER+RAM+BATTERY"}, - {0x11, "MBC3"}, - {0x12, "MBC3+RAM"}, - {0x13, "MBC3+RAM+BATTERY"}, - {0x19, "MBC5"}, - {0x1A, "MBC5+RAM"}, - {0x1B, "MBC5+RAM+BATTERY"}, - {0x1C, "MBC5+RUMBLE"}, - {0x1D, "MBC5+RUMBLE+RAM"}, - {0x1E, "MBC5+RUMBLE+RAM+BATTERY"}, - {0x20, "MBC6"}, - {0x22, "MBC7+SENSOR+RUMBLE+RAM+BATTERY"}, - {0xFC, "POCKET CAMERA"}, - {0xFC, "POCKET_CAMERA"}, - {0xFD, "BANDAI TAMA5"}, - {0xFD, "BANDAI_TAMA5"}, - {0xFE, "HuC3"}, - {0xFF, "HuC1+RAM+BATTERY"}, +enum MbcType { + ROM = 0x00, + ROM_RAM = 0x08, + ROM_RAM_BATTERY = 0x09, + + MBC1 = 0x01, + MBC1_RAM = 0x02, + MBC1_RAM_BATTERY = 0x03, + + MBC2 = 0x05, + MBC2_BATTERY = 0x06, + + MMM01 = 0x0B, + MMM01_RAM = 0x0C, + MMM01_RAM_BATTERY = 0x0D, + + MBC3 = 0x11, + MBC3_TIMER_BATTERY = 0x0F, + MBC3_TIMER_RAM_BATTERY = 0x10, + MBC3_RAM = 0x12, + MBC3_RAM_BATTERY = 0x13, + + MBC5 = 0x19, + MBC5_RAM = 0x1A, + MBC5_RAM_BATTERY = 0x1B, + MBC5_RUMBLE = 0x1C, + MBC5_RUMBLE_RAM = 0x1D, + MBC5_RUMBLE_RAM_BATTERY = 0x1E, + + MBC6 = 0x20, + + MBC7_SENSOR_RUMBLE_RAM_BATTERY = 0x22, + + POCKET_CAMERA = 0xFC, + + BANDAI_TAMA5 = 0xFD, + + HUC3 = 0xFE, + + HUC1_RAM_BATTERY = 0xFF, + + // Error values + MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it + MBC_BAD, // Specified MBC does not exist / syntax error + MBC_WRONG_FEATURES, // MBC incompatible with specified features + MBC_BAD_RANGE, // MBC number out of range }; -static long parse_cartridge(char *arg, char **ep) +/** + * @return False on failure + */ +static bool readMBCSlice(char const **name, char const *expected) { - for (int i = 0; i < sizeof(cartridge_types) / sizeof(cartridge_types[0]); i++) { - if (!strcmp(arg, cartridge_types[i].name)) { - *ep = arg + strlen(arg); - return cartridge_types[i].value; + while (*expected) { + char c = *(*name)++; + + if (c == '\0') // Name too short + return false; + + if (c >= 'a' && c <= 'z') // Perform the comparison case-insensitive + c = c - 'a' + 'A'; + else if (c == '_') // Treat underscores as spaces + c = ' '; + + if (c != *expected++) + return false; + } + return true; +} + +static enum MbcType parseMBC(char const *name) +{ + if (name[0] >= '0' && name[0] <= '9') { + // Parse number, and return it as-is (unless it's too large) + char *endptr; + unsigned long mbc = strtoul(name, &endptr, 0); + + if (*endptr) + return MBC_BAD; + if (mbc > 0xFF) + return MBC_BAD_RANGE; + return mbc; + + } else { + // Begin by reading the MBC type: + uint16_t mbc; + char const *ptr = name; + + // Trim off leading whitespace + while (*ptr == ' ' || *ptr == '\t') + ptr++; + +#define tryReadSlice(expected) \ +do { \ + if (!readMBCSlice(&ptr, expected)) \ + return MBC_BAD; \ +} while (0) + + switch (*ptr++) { + case 'R': // ROM / ROM_ONLY + case 'r': + tryReadSlice("OM"); + // Handle optional " ONLY" + while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') + ptr++; + if (*ptr == 'O' || *ptr == 'o') { + ptr++; + tryReadSlice("NLY"); + } + mbc = ROM; + break; + + case 'M': // MBC{1, 2, 3, 5, 6, 7} / MMM01 + case 'm': + switch (*ptr++) { + case 'B': + case 'b': + switch (*ptr++) { + case 'C': + case 'c': + break; + default: + return MBC_BAD; + } + switch (*ptr++) { + case '1': + mbc = MBC1; + break; + case '2': + mbc = MBC2; + break; + case '3': + mbc = MBC3; + break; + case '5': + mbc = MBC5; + break; + case '6': + mbc = MBC6; + break; + case '7': + mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY; + break; + default: + return MBC_BAD; + } + break; + case 'M': + case 'm': + tryReadSlice("M01"); + mbc = MMM01; + break; + default: + return MBC_BAD; + } + break; + + case 'P': // POCKET_CAMERA + case 'p': + tryReadSlice("OCKET CAMERA"); + mbc = POCKET_CAMERA; + break; + + case 'B': // BANDAI_TAMA5 + case 'b': + tryReadSlice("ANDAI TAMA5"); + mbc = BANDAI_TAMA5; + break; + + case 'T': // TAMA5 + case 't': + tryReadSlice("AMA5"); + mbc = BANDAI_TAMA5; + break; + + case 'H': // HuC{1, 3} + case 'h': + tryReadSlice("UC"); + switch (*ptr++) { + case '1': + mbc = HUC1_RAM_BATTERY; + break; + case '3': + mbc = HUC3; + break; + default: + return MBC_BAD; + } + break; + + default: + return MBC_BAD; + } + + // Read "additional features" + uint8_t features = 0; +#define RAM 0x80 +#define BATTERY 0x40 +#define TIMER 0x20 +#define RUMBLE 0x10 +#define SENSOR 0x08 + + for (;;) { + // Trim off trailing whitespace + while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') + ptr++; + + // If done, start processing "features" + if (!*ptr) + break; + // We expect a '+' at this point + if (*ptr++ != '+') + return MBC_BAD; + // Trim off leading whitespace + while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') + ptr++; + + switch (*ptr++) { + case 'B': // BATTERY + case 'b': + tryReadSlice("ATTERY"); + features |= BATTERY; + break; + + case 'R': // RAM or RUMBLE + case 'r': + switch (*ptr++) { + case 'U': + case 'u': + tryReadSlice("MBLE"); + features |= RUMBLE; + break; + case 'A': + case 'a': + if (*ptr != 'M' && *ptr != 'm') + return MBC_BAD; + ptr++; + features |= RAM; + break; + default: + return MBC_BAD; + } + break; + + case 'S': // SENSOR + case 's': + tryReadSlice("ENSOR"); + features |= SENSOR; + break; + + case 'T': // TIMER + case 't': + tryReadSlice("IMER"); + features |= TIMER; + break; + + default: + return MBC_BAD; + } + } +#undef tryReadSlice + + switch (mbc) { + case ROM: + if (!features) + break; + mbc = ROM_RAM - 1; + static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!"); + static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!"); + static_assert(MBC1 + 2 == MBC1_RAM_BATTERY, "Enum sanity check failed!"); + static_assert(MMM01 + 1 == MMM01_RAM, "Enum sanity check failed!"); + static_assert(MMM01 + 2 == MMM01_RAM_BATTERY, "Enum sanity check failed!"); + // fallthrough + case MBC1: + case MMM01: + if (features == RAM) + mbc++; + else if (features == (RAM | BATTERY)) + mbc += 2; + else if (features) + return MBC_WRONG_FEATURES; + break; + + case MBC2: + if (features == BATTERY) + mbc = MBC2_BATTERY; + else if (features) + return MBC_WRONG_FEATURES; + break; + + case MBC3: + // Handle timer, which also requires battery + if (features & (TIMER & BATTERY)) { + features &= ~(TIMER | BATTERY); // Reset those bits + mbc = MBC3_TIMER_BATTERY; + // RAM is handled below + } + static_assert(MBC3 + 1 == MBC3_RAM, "Enum sanity check failed!"); + static_assert(MBC3 + 2 == MBC3_RAM_BATTERY, "Enum sanity check failed!"); + static_assert(MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, + "Enum sanity check failed!"); + if (features == RAM) + mbc++; + else if (features == (RAM | BATTERY)) + mbc += 2; + else if (features) + return MBC_WRONG_FEATURES; + break; + + case MBC5: + if (features & RUMBLE) { + features &= ~RUMBLE; + mbc = MBC5_RUMBLE; + } + static_assert(MBC5 + 1 == MBC5_RAM, "Enum sanity check failed!"); + static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!"); + static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!"); + static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, + "Enum sanity check failed!"); + if (features == RAM) + mbc++; + else if (features == (RAM | BATTERY)) + mbc += 2; + else if (features) + return MBC_WRONG_FEATURES; + break; + + case MBC6: + case POCKET_CAMERA: + case BANDAI_TAMA5: + case HUC3: + // No extra features accepted + if (features) + return MBC_WRONG_FEATURES; + break; + + case MBC7_SENSOR_RUMBLE_RAM_BATTERY: + if (features != (SENSOR | RUMBLE | RAM | BATTERY)) + return MBC_WRONG_FEATURES; + break; + + case HUC1_RAM_BATTERY: + if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY + return MBC_WRONG_FEATURES; + break; + } + + // Trim off trailing whitespace + while (*ptr == ' ' || *ptr == '\t') + ptr++; + + // If there is still something past the whitespace, error out + if (*ptr) + return MBC_BAD; + + return mbc; + } +} + +static char const *mbcName(enum MbcType type) +{ + switch (type) { + case ROM: + return "ROM"; + case ROM_RAM: + return "ROM+RAM"; + case ROM_RAM_BATTERY: + return "ROM+RAM+BATTERY"; + case MBC1: + return "MBC1"; + case MBC1_RAM: + return "MBC1+RAM"; + case MBC1_RAM_BATTERY: + return "MBC1+RAM+BATTERY"; + case MBC2: + return "MBC2"; + case MBC2_BATTERY: + return "MBC2+BATTERY"; + case MMM01: + return "MMM01"; + case MMM01_RAM: + return "MMM01+RAM"; + case MMM01_RAM_BATTERY: + return "MMM01+RAM+BATTERY"; + case MBC3: + return "MBC3"; + case MBC3_TIMER_BATTERY: + return "MBC3+TIMER+BATTERY"; + case MBC3_TIMER_RAM_BATTERY: + return "MBC3+TIMER+RAM+BATTERY"; + case MBC3_RAM: + return "MBC3+RAM"; + case MBC3_RAM_BATTERY: + return "MBC3+RAM+BATTERY"; + case MBC5: + return "MBC5"; + case MBC5_RAM: + return "MBC5+RAM"; + case MBC5_RAM_BATTERY: + return "MBC5+RAM+BATTERY"; + case MBC5_RUMBLE: + return "MBC5+RUMBLE"; + case MBC5_RUMBLE_RAM: + return "MBC5+RUMBLE+RAM"; + case MBC5_RUMBLE_RAM_BATTERY: + return "MBC5+RUMBLE+RAM+BATTERY"; + case MBC6: + return "MBC6"; + case MBC7_SENSOR_RUMBLE_RAM_BATTERY: + return "MBC7+SENSOR+RUMBLE+RAM+BATTERY"; + case POCKET_CAMERA: + return "POCKET CAMERA"; + case BANDAI_TAMA5: + return "BANDAI TAMA5"; + case HUC3: + return "HUC3"; + case HUC1_RAM_BATTERY: + return "HUC1+RAM+BATTERY"; + + // Error values + case MBC_NONE: + case MBC_BAD: + case MBC_WRONG_FEATURES: + case MBC_BAD_RANGE: + unreachable_(); + } + + unreachable_(); +} + +static bool hasRAM(enum MbcType type) +{ + switch (type) { + case ROM: + case MBC1: + case MBC2: // Technically has RAM, but not marked as such + case MBC2_BATTERY: + case MMM01: + case MBC3: + case MBC3_TIMER_BATTERY: + case MBC5: + case MBC5_RUMBLE: + case MBC6: // TODO: not sure + case BANDAI_TAMA5: // TODO: not sure + case MBC_NONE: + case MBC_BAD: + case MBC_WRONG_FEATURES: + case MBC_BAD_RANGE: + return false; + + case ROM_RAM: + case ROM_RAM_BATTERY: + case MBC1_RAM: + case MBC1_RAM_BATTERY: + case MMM01_RAM: + case MMM01_RAM_BATTERY: + case MBC3_TIMER_RAM_BATTERY: + case MBC3_RAM: + case MBC3_RAM_BATTERY: + case MBC5_RAM: + case MBC5_RAM_BATTERY: + case MBC5_RUMBLE_RAM: + case MBC5_RUMBLE_RAM_BATTERY: + case MBC7_SENSOR_RUMBLE_RAM_BATTERY: + case POCKET_CAMERA: + case HUC3: + case HUC1_RAM_BATTERY: + return true; + } + + unreachable_(); +} + +static uint8_t nbErrors; + +static format_(printf, 1, 2) void report(char const *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (nbErrors != UINT8_MAX) + nbErrors++; +} + +static const uint8_t ninLogo[] = { + 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, + 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, + 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, + 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, + 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E +}; + +static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone +#define FIX_LOGO 0x80 +#define TRASH_LOGO 0x40 +#define FIX_HEADER_SUM 0x20 +#define TRASH_HEADER_SUM 0x10 +#define FIX_GLOBAL_SUM 0x08 +#define TRASH_GLOBAL_SUM 0x04 +static uint8_t fixSpec = 0; +static const char *gameID = NULL; +static uint8_t gameIDLen; +static bool japanese = true; +static const char *newLicensee = NULL; +static uint8_t newLicenseeLen; +static uint16_t oldLicensee = UNSPECIFIED; +static enum MbcType cartridgeType = MBC_NONE; +static uint16_t romVersion = UNSPECIFIED; +static uint16_t padValue = UNSPECIFIED; +static uint16_t ramSize = UNSPECIFIED; +static bool sgb = false; // If false, SGB flags are left alone +static const char *title = NULL; +static uint8_t titleLen; + +static uint8_t maxTitleLen(void) +{ + return gameID ? 11 : model != DMG ? 15 : 16; +} + +static ssize_t readBytes(int fd, uint8_t *buf, size_t len) +{ + // POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results + assert(len <= SSIZE_MAX); + + ssize_t total = 0; + + while (len) { + ssize_t ret = read(fd, buf, len); + + if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted + return -1; + // EOF reached + if (ret == 0) + return total; + // If anything was read, accumulate it, and continue + if (ret != -1) { + total += ret; + len -= ret; + buf += ret; } } - return strtoul(arg, ep, 0); + + return total; +} + +static ssize_t writeBytes(int fd, void *buf, size_t len) +{ + // POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results + assert(len <= SSIZE_MAX); + + ssize_t total = 0; + + while (len) { + ssize_t ret = write(fd, buf, len); + + if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted + return -1; + // EOF reached + if (ret == 0) + return total; + // If anything was read, accumulate it, and continue + if (ret != -1) { + total += ret; + len -= ret; + } + } + + return total; +} + +/** + * @param input File descriptor to be used for reading + * @param output File descriptor to be used for writing, may be equal to `input` + * @param name The file's name, to be displayed for error output + * @param fileSize The file's size if known, 0 if not. + */ +static void processFile(int input, int output, char const *name, off_t fileSize) +{ + // Both of these should be true for seekable files, and neither otherwise + if (input == output) + assert(fileSize != 0); + else + assert(fileSize == 0); + + uint8_t rom0[BANK_SIZE]; + ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0)); + + if (rom0Len == -1) { + report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); + return; + } else if (rom0Len < 0x150) { + report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n", + name, rom0Len); + return; + } + // Accept partial reads if the file contains at least the header + + if (fixSpec & (FIX_LOGO | TRASH_LOGO)) { + if (fixSpec & FIX_LOGO) { + memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo)); + } else { + for (uint8_t i = 0; i < sizeof(ninLogo); i++) + rom0[i + 0x104] = ~ninLogo[i]; + } + } + + if (title) + memcpy(&rom0[0x134], title, titleLen); + + if (gameID) + memcpy(&rom0[0x13f], gameID, gameIDLen); + + if (model != DMG) + rom0[0x143] = model == BOTH ? 0x80 : 0xc0; + + if (newLicensee) + memcpy(&rom0[0x144], newLicensee, newLicenseeLen); + + if (sgb) + rom0[0x146] = 0x03; + + // If a valid MBC was specified... + if (cartridgeType < MBC_NONE) + rom0[0x147] = cartridgeType; + + if (ramSize != UNSPECIFIED) + rom0[0x149] = ramSize; + + if (!japanese) + rom0[0x14a] = 0x01; + + if (oldLicensee != UNSPECIFIED) + rom0[0x14b] = oldLicensee; + + if (romVersion != UNSPECIFIED) + rom0[0x14c] = romVersion; + + // Remain to be handled the ROM size, and header checksum. + // The latter depends on the former, and so will be handled after it. + // The former requires knowledge of the file's total size, so read that first. + + uint16_t globalSum = 0; + + // To keep file sizes fairly reasonable, we'll cap the amount of banks at 65536 + // Official mappers only go up to 512 banks, but at least the TPP1 spec allows up to + // 65536 banks = 1 GiB. + // This should be reasonable for the time being, and may be extended later. + uint8_t *romx = NULL; // Pointer to ROMX banks when they've been buffered + uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0 + size_t totalRomxLen = 0; // *Actual* size of ROMX data + uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data + + // Handle ROMX + if (input == output) { + if (fileSize >= 0x10000 * BANK_SIZE) { + report("FATAL: \"%s\" has more than 65536 banks\n", name); + return; + } + // This should be guaranteed from the size cap... + static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"); + // Compute number of banks and ROMX len from file size + nbBanks = (fileSize + (BANK_SIZE - 1)) / BANK_SIZE; + // = ceil(totalRomxLen / BANK_SIZE) + totalRomxLen = fileSize >= BANK_SIZE ? fileSize - BANK_SIZE : 0; + } else if (rom0Len == BANK_SIZE) { + // Copy ROMX when reading a pipe, and we're not at EOF yet + for (;;) { + romx = realloc(romx, nbBanks * BANK_SIZE); + if (!romx) { + report("FATAL: Failed to realloc ROMX buffer: %s\n", + strerror(errno)); + return; + } + ssize_t bankLen = readBytes(input, &romx[(nbBanks - 1) * BANK_SIZE], + BANK_SIZE); + + // Update bank count, ONLY IF at least one byte was read + if (bankLen) { + // We're gonna read another bank, check that it won't be too much + static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"); + if (nbBanks == 0x10000) { + report("FATAL: \"%s\" has more than 65536 banks\n", name); + free(romx); + return; + } + nbBanks++; + + // Update global checksum, too + for (uint16_t i = 0; i < bankLen; i++) + globalSum += romx[totalRomxLen + i]; + totalRomxLen += bankLen; + } + // Stop when an incomplete bank has been read + if (bankLen != BANK_SIZE) + break; + } + } + + // Handle setting the ROM size if padding was requested + // Pad to the next valid power of 2. This is because padding is required by flashers, which + // flash to ROM chips, whose size is always a power of 2... so there'd be no point in + // padding to something else. + // Additionally, a ROM must be at least 32k, so we guarantee a whole amount of banks... + if (padValue != UNSPECIFIED) { + // We want at least 2 banks + if (nbBanks == 1) { + if (rom0Len != sizeof(rom0)) { + memset(&rom0[rom0Len], padValue, sizeof(rom0) - rom0Len); + // The global checksum hasn't taken ROM0 into consideration yet! + // ROM0 was padded, so treat it as entirely written: update its size + // Update how many bytes were read in total, too + rom0Len = sizeof(rom0); + } + nbBanks = 2; + } else { + assert(rom0Len == sizeof(rom0)); + } + assert(nbBanks >= 2); + // Alter number of banks to reflect required value + // x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero, + // so this is true (non-zero) when we don't have a power of 2 + if (nbBanks & (nbBanks - 1)) + nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks)); + // Write final ROM size + rom0[0x148] = ctz(nbBanks / 2); + // Alter global checksum based on how many bytes will be added (not counting ROM0) + globalSum += padValue * ((nbBanks - 1) * BANK_SIZE - totalRomxLen); + } + + // Handle the header checksum after the ROM size has been written + if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) { + uint8_t sum = 0; + + for (uint16_t i = 0x134; i < 0x14d; i++) + sum -= rom0[i] + 1; + rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum; + } + + if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) { + // Computation of the global checksum assumes 0s being stored in its place + rom0[0x14e] = 0; + rom0[0x14f] = 0; + for (uint16_t i = 0; i < rom0Len; i++) + globalSum += rom0[i]; + // Pipes have already read ROMX and updated globalSum, but not regular files + if (input == output) { + for (;;) { + ssize_t romxLen = readBytes(input, bank, sizeof(bank)); + + for (uint16_t i = 0; i < romxLen; i++) + globalSum += bank[i]; + if (romxLen != sizeof(bank)) + break; + } + } + + if (fixSpec & TRASH_GLOBAL_SUM) + globalSum = ~globalSum; + rom0[0x14e] = globalSum >> 8; + rom0[0x14f] = globalSum & 0xff; + } + + // In case the output depends on the input, reset to the beginning of the file, and only + // write the header + if (input == output) { + if (lseek(output, 0, SEEK_SET) == (off_t)-1) { + report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno)); + goto free_romx; + } + // If modifying the file in-place, we only need to edit the header + // However, padding may have modified ROM0 (added padding), so don't in that case + if (padValue == UNSPECIFIED) + rom0Len = 0x150; + } + ssize_t writeLen = writeBytes(output, rom0, rom0Len); + + if (writeLen == -1) { + report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); + goto free_romx; + } else if (writeLen < rom0Len) { + report("FATAL: Could only write %ld of \"%s\"'s %ld ROM0 bytes\n", + writeLen, name, rom0Len); + goto free_romx; + } + + // Output ROMX if it was buffered + if (romx) { + writeLen = writeBytes(output, romx, totalRomxLen); + if (writeLen == -1) { + report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); + goto free_romx; + } else if (writeLen < totalRomxLen) { + report("FATAL: Could only write %ld of \"%s\"'s %ld ROMX bytes\n", + writeLen, name, totalRomxLen); + goto free_romx; + } + } + + // Output padding + if (padValue != UNSPECIFIED) { + if (input == output) { + if (lseek(output, 0, SEEK_END) == (off_t)-1) { + report("FATAL: Failed to seek to end of \"%s\": %s\n", + name, strerror(errno)); + goto free_romx; + } + } + memset(bank, padValue, sizeof(bank)); + size_t len = (nbBanks - 1) * BANK_SIZE - totalRomxLen; // Don't count ROM0! + + while (len) { + static_assert(sizeof(bank) <= SSIZE_MAX, "Bank too large for reading"); + size_t thisLen = len > sizeof(bank) ? sizeof(bank) : len; + ssize_t ret = writeBytes(output, bank, thisLen); + + if (ret != thisLen) { + report("FATAL: Failed to write \"%s\"'s padding: %s\n", + name, strerror(errno)); + break; + } + len -= thisLen; + } + } + +free_romx: + free(romx); +} + +#undef trySeek + +static bool processFilename(char const *name) +{ + nbErrors = 0; + if (!strcmp(name, "-")) { + setmode(STDIN_FILENO, O_BINARY); + setmode(STDOUT_FILENO, O_BINARY); + name = ""; + processFile(STDIN_FILENO, STDOUT_FILENO, name, 0); + + } else { + // POSIX specifies that the results of O_RDWR on a FIFO are undefined. + // However, this is necessary to avoid a TOCTTOU, if the file was changed between + // `stat()` and `open(O_RDWR)`, which could trigger the UB anyway. + // Thus, we're going to hope that either the `open` fails, or it succeeds but IO + // operations may fail, all of which we handle. + int input = open(name, O_RDWR | O_BINARY); + struct stat stat; + + if (input == -1) { + report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", + name, strerror(errno)); + goto fail; + } + + if (fstat(input, &stat) == -1) { + report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno)); + } else if (!S_ISREG(stat.st_mode)) { // FIXME: Do we want to support other types? + report("FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", + name); + } else if (stat.st_size < 0x150) { + // This check is in theory redundant with the one in `processFile`, but it + // prevents passing a file size of 0, which usually indicates pipes + report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n", + name, stat.st_size); + } else { + processFile(input, input, name, stat.st_size); + } + + close(input); + } + if (nbErrors) +fail: + fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n", + name, nbErrors, nbErrors == 1 ? "" : "s"); + return nbErrors; } int main(int argc, char *argv[]) { - FILE *rom; - int ch; - char *ep; + nbErrors = 0; + char ch; - /* - * Parse command-line options - */ - - /* all flags default to false unless options specify otherwise */ - bool fixlogo = false; - bool fixheadsum = false; - bool fixglobalsum = false; - bool trashlogo = false; - bool trashheadsum = false; - bool trashglobalsum = false; - bool settitle = false; - bool setid = false; - bool colorcompatible = false; - bool coloronly = false; - bool nonjapan = false; - bool setlicensee = false; - bool setnewlicensee = false; - bool super = false; - bool setcartridge = false; - bool setramsize = false; - bool resize = false; - bool setversion = false; - - char *title = NULL; /* game title in ASCII */ - char *id = NULL; /* game ID in ASCII */ - char *newlicensee = NULL; /* new licensee ID, two ASCII characters */ - - int licensee = 0; /* old licensee ID */ - int cartridge = 0; /* cartridge hardware ID */ - int ramsize = 0; /* RAM size ID */ - int version = 0; /* mask ROM version number */ - int padvalue = 0; /* to pad the rom with if it changes size */ - - while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, - NULL)) != -1) { + while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) { switch (ch) { + size_t len; +#define parseByte(output, name) \ +do { \ + char *endptr; \ + unsigned long tmp; \ + \ + if (optarg[0] == 0) { \ + report("error: Argument to option '" name "' may not be empty\n"); \ + } else { \ + if (optarg[0] == '$') { \ + tmp = strtoul(&optarg[1], &endptr, 16); \ + } else { \ + tmp = strtoul(optarg, &endptr, 0); \ + } \ + if (*endptr) \ + report("error: Expected number as argument to option '" name "', got %s\n", \ + optarg); \ + else if (tmp > 0xFF) \ + report("error: Argument to option '" name "' is larger than 255: %lu\n", tmp); \ + else \ + output = tmp; \ + } \ +} while (0) + case 'C': - coloronly = true; - /* FALLTHROUGH */ case 'c': - colorcompatible = true; + model = ch == 'c' ? BOTH : CGB; + if (titleLen > 15) { + titleLen = 15; + fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n", + title); + } break; + case 'f': - fixlogo = strchr(optarg, 'l'); - fixheadsum = strchr(optarg, 'h'); - fixglobalsum = strchr(optarg, 'g'); - trashlogo = strchr(optarg, 'L'); - trashheadsum = strchr(optarg, 'H'); - trashglobalsum = strchr(optarg, 'G'); + fixSpec = 0; + while (*optarg) { + switch (*optarg) { +#define SPEC_l FIX_LOGO +#define SPEC_L TRASH_LOGO +#define SPEC_h FIX_HEADER_SUM +#define SPEC_H TRASH_HEADER_SUM +#define SPEC_g FIX_GLOBAL_SUM +#define SPEC_G TRASH_GLOBAL_SUM +#define or(new, bad) \ +do { \ + if (fixSpec & SPEC_##bad) \ + fprintf(stderr, \ + "warning: '" #new "' overriding '" #bad "' in fix spec\n"); \ + fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \ +} while (0) + case 'l': + or(l, L); + break; + case 'L': + or(L, l); + break; + + case 'h': + or(h, H); + break; + case 'H': + or(H, h); + break; + + case 'g': + or(g, G); + break; + case 'G': + or(G, g); + break; + + default: + fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", + *optarg); +#undef or + } + optarg++; + } break; + case 'i': - setid = true; - - if (strlen(optarg) != 4) - errx(1, "Game ID %s must be exactly 4 characters", - optarg); - - id = optarg; + gameID = optarg; + len = strlen(gameID); + if (len > 4) { + len = 4; + fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n", + gameID); + } + gameIDLen = len; + if (titleLen > 11) { + titleLen = 11; + fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n", + title); + } break; + case 'j': - nonjapan = true; + japanese = false; break; + case 'k': - setnewlicensee = true; - - if (strlen(optarg) != 2) - errx(1, "New licensee code %s is not the correct length of 2 characters", - optarg); - - newlicensee = optarg; + newLicensee = optarg; + len = strlen(newLicensee); + if (len > 2) { + len = 2; + fprintf(stderr, + "warning: Truncating new licensee \"%s\" to 2 chars\n", + newLicensee); + } + newLicenseeLen = len; break; + case 'l': - setlicensee = true; - - licensee = strtoul(optarg, &ep, 0); - if (optarg[0] == '\0' || *ep != '\0') - errx(1, "Invalid argument for option 'l'"); - - if (licensee < 0 || licensee > 0xFF) - errx(1, "Argument for option 'l' must be between 0 and 255"); - + parseByte(oldLicensee, "l"); break; + case 'm': - setcartridge = true; - - cartridge = parse_cartridge(optarg, &ep); - if (optarg[0] == '\0' || *ep != '\0') - errx(1, "Invalid argument for option 'm'"); - - if (cartridge < 0 || cartridge > 0xFF) - errx(1, "Argument for option 'm' must be between 0 and 255"); - + cartridgeType = parseMBC(optarg); + if (cartridgeType == MBC_BAD) { + report("error: Unknown MBC \"%s\"\n", optarg); + } else if (cartridgeType == MBC_WRONG_FEATURES) { + report("error: Features incompatible with MBC (\"%s\")\n", optarg); + } else if (cartridgeType == MBC_BAD_RANGE) { + report("error: Specified MBC ID out of range 0-255: %s\n", + optarg); + } else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { + fprintf(stderr, "warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly supported\n"); + } break; + case 'n': - setversion = true; - - version = strtoul(optarg, &ep, 0); - - if (optarg[0] == '\0' || *ep != '\0') - errx(1, "Invalid argument for option 'n'"); - - if (version < 0 || version > 0xFF) - errx(1, "Argument for option 'n' must be between 0 and 255"); - + parseByte(romVersion, "n"); break; + case 'p': - resize = true; - - padvalue = strtoul(optarg, &ep, 0); - - if (optarg[0] == '\0' || *ep != '\0') - errx(1, "Invalid argument for option 'p'"); - - if (padvalue < 0 || padvalue > 0xFF) - errx(1, "Argument for option 'p' must be between 0 and 255"); - + parseByte(padValue, "p"); break; + case 'r': - setramsize = true; - - ramsize = strtoul(optarg, &ep, 0); - - if (optarg[0] == '\0' || *ep != '\0') - errx(1, "Invalid argument for option 'r'"); - - if (ramsize < 0 || ramsize > 0xFF) - errx(1, "Argument for option 'r' must be between 0 and 255"); - + parseByte(ramSize, "r"); break; + case 's': - super = true; + sgb = true; break; + case 't': - settitle = true; - - if (strlen(optarg) > 16) - errx(1, "Title \"%s\" is greater than the maximum of 16 characters", - optarg); - - if (strlen(optarg) == 16) - warnx("Title \"%s\" is 16 chars, it is best to keep it to 15 or fewer", - optarg); - title = optarg; + len = strlen(title); + uint8_t maxLen = maxTitleLen(); + + if (len > maxLen) { + len = maxLen; + fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n", + title, maxLen); + } + titleLen = len; break; + case 'V': printf("rgbfix %s\n", get_package_version_string()); exit(0); + case 'v': - fixlogo = true; - fixheadsum = true; - fixglobalsum = true; + fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM; break; + default: - print_usage(); - /* NOTREACHED */ + fprintf(stderr, "FATAL: unknown option '%c'\n", ch); + printUsage(); + exit(1); + } +#undef parseByte + } + + if (ramSize != UNSPECIFIED && cartridgeType < UNSPECIFIED) { + if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { + if (ramSize != 1) + fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n", + mbcName(cartridgeType)); + } else if (hasRAM(cartridgeType)) { + if (!ramSize) { + fprintf(stderr, + "warning: MBC \"%s\" has RAM, but RAM size was set to 0\n", + mbcName(cartridgeType)); + } else if (ramSize == 1) { + fprintf(stderr, + "warning: RAM size 1 (2 kiB) was specified for MBC \"%s\"\n", + mbcName(cartridgeType)); + } // TODO: check possible values? + } else if (ramSize) { + fprintf(stderr, + "warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n", + mbcName(cartridgeType), ramSize); } } - argc -= optind; + if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) + fprintf(stderr, + "warning: SGB compatibility enabled, but old licensee is %#x, not 0x33\n", + oldLicensee); + argv += optind; + bool failed = nbErrors; - if (argc == 0) { - fputs("FATAL: no input files\n", stderr); - print_usage(); + if (!*argv) { + failed |= processFilename("-"); + } else { + do { + failed |= processFilename(*argv); + } while (*++argv); } - /* - * Open the ROM file - */ - - rom = fopen(argv[argc - 1], "rb+"); - - if (rom == NULL) - err(1, "Error opening file %s", argv[argc - 1]); - - /* - * Read ROM header - * - * Offsets in the buffer are 0x100 less than the equivalent in ROM. - */ - - uint8_t header[0x50]; - - if (fseek(rom, 0x100, SEEK_SET) != 0) - err(1, "Could not locate ROM header"); - if (fread(header, sizeof(uint8_t), sizeof(header), rom) - != sizeof(header)) - err(1, "Could not read ROM header"); - - if (fixlogo || trashlogo) { - /* - * Offset 0x104–0x133: Nintendo Logo - * This is a bitmap image that displays when the Game Boy is - * turned on. It must be intact, or the game will not boot. - */ - - /* - * See also: global checksums at 0x14D–0x14F, They must - * also be correct for the game to boot, so we fix them - * as well when requested with the -f flag. - */ - - uint8_t ninlogo[48] = { - 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, - 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, - 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, - 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, - 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, - 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E - }; - - if (trashlogo) { - for (int i = 0; i < sizeof(ninlogo); i++) - ninlogo[i] = ~ninlogo[i]; - } - - memcpy(header + 0x04, ninlogo, sizeof(ninlogo)); - } - - if (settitle) { - /* - * Offset 0x134–0x143: Game Title - * This is a sixteen-character game title in ASCII (no high- - * bit characters). - */ - - /* - * See also: CGB flag at 0x143. The sixteenth character of - * the title is co-opted for use as the CGB flag, so they - * may conflict. - */ - - /* - * See also: Game ID at 0x13F–0x142. These four ASCII - * characters may conflict with the title. - */ - - strncpy((char *)header + 0x34, title, 16); - } - - if (setid) { - /* - * Offset 0x13F–0x142: Game ID - * This is a four-character game ID in ASCII (no high-bit - * characters). - */ - - memcpy(header + 0x3F, id, 4); - } - - if (colorcompatible) { - /* - * Offset 0x143: Game Boy Color Flag - * If bit 7 is set, the ROM has Game Boy Color features. - * If bit 6 is also set, the ROM is for the Game Boy Color - * only. (However, this is not actually enforced by the - * Game Boy.) - */ - - /* - * See also: Game Title at 0x134–0x143. The sixteenth - * character of the title overlaps with this flag, so they - * may conflict. - */ - - header[0x43] |= 1 << 7; - if (coloronly) - header[0x43] |= 1 << 6; - - if (header[0x43] & 0x3F) - warnx("Color flag conflicts with game title"); - } - - if (setnewlicensee) { - /* - * Offset 0x144–0x145: New Licensee Code - * This is a two-character code identifying which company - * created the game. - */ - - /* - * See also: the original Licensee ID at 0x14B. - * This is deprecated and in all newer games is used instead - * as a Super Game Boy flag. - */ - - header[0x44] = newlicensee[0]; - header[0x45] = newlicensee[1]; - } - - if (super) { - /* - * Offset 0x146: Super Game Boy Flag - * If not equal to 3, Super Game Boy functions will be - * disabled. - */ - - /* - * See also: the original Licensee ID at 0x14B. - * If the Licensee code is not equal to 0x33, Super Game Boy - * functions will be disabled. - */ - - if (!setlicensee) - warnx("You should probably set both '-s' and '-l 0x33'"); - - header[0x46] = 3; - } - - if (setcartridge) { - /* - * Offset 0x147: Cartridge Type - * Identifies whether the ROM uses a memory bank controller, - * external RAM, timer, rumble, or battery. - */ - - header[0x47] = cartridge; - } - - if (resize) { - /* - * Offset 0x148: Cartridge Size - * Identifies the size of the cartridge ROM. - */ - - /* We will pad the ROM to match the size given in the header. */ - long romsize, newsize; - int headbyte; - uint8_t *buf; - - if (fseek(rom, 0, SEEK_END) != 0) - err(1, "Could not pad ROM file"); - - romsize = ftell(rom); - if (romsize == -1) - err(1, "Could not pad ROM file"); - - newsize = 0x8000; - - headbyte = 0; - while (romsize > newsize) { - newsize <<= 1; - headbyte++; - } - - if (newsize > 0x800000) /* ROM is bigger than 8MiB */ - warnx("ROM size is bigger than 8MiB"); - - buf = malloc(newsize - romsize); - if (buf == NULL) - errx(1, "Couldn't allocate memory for padded ROM."); - - memset(buf, padvalue, newsize - romsize); - if (fwrite(buf, 1, newsize - romsize, rom) != newsize - romsize) - err(1, "Could not pad ROM file"); - - header[0x48] = headbyte; - - free(buf); - } - - if (setramsize) { - /* - * Offset 0x149: RAM Size - */ - - header[0x49] = ramsize; - } - - if (nonjapan) { - /* - * Offset 0x14A: Non-Japanese Region Flag - */ - - header[0x4A] = 1; - } - - if (setlicensee) { - /* - * Offset 0x14B: Licensee Code - * This identifies which company created the game. - * - * This byte is deprecated and should be set to 0x33 in new - * releases. - */ - - /* - * See also: the New Licensee ID at 0x144–0x145. - */ - - header[0x4B] = licensee; - } - - if (setversion) { - /* - * Offset 0x14C: Mask ROM Version Number - * Which version of the ROM this is. - */ - - header[0x4C] = version; - } - - if (fixheadsum || trashheadsum) { - /* - * Offset 0x14D: Header Checksum - */ - - uint8_t headcksum = 0; - - for (int i = 0x34; i < 0x4D; ++i) - headcksum = headcksum - header[i] - 1; - - if (trashheadsum) - headcksum = ~headcksum; - - header[0x4D] = headcksum; - } - - /* - * Before calculating the global checksum, we must write the modified - * header to the ROM. - */ - - if (fseek(rom, 0x100, SEEK_SET) != 0) - err(1, "Could not locate header for writing"); - - if (fwrite(header, sizeof(uint8_t), sizeof(header), rom) - != sizeof(header)) - err(1, "Could not write modified ROM header"); - - if (fixglobalsum || trashglobalsum) { - /* - * Offset 0x14E–0x14F: Global Checksum - */ - - uint16_t globalcksum = 0; - - if (fseek(rom, 0, SEEK_SET) != 0) - err(1, "Could not start calculating global checksum"); - - int i = 0; - int byte; - - while ((byte = fgetc(rom)) != EOF) { - if (i != 0x14E && i != 0x14F) - globalcksum += byte; - i++; - } - - if (ferror(rom)) - err(1, "Could not calculate global checksum"); - - if (trashglobalsum) - globalcksum = ~globalcksum; - - fseek(rom, 0x14E, SEEK_SET); - fputc(globalcksum >> 8, rom); - fputc(globalcksum & 0xFF, rom); - if (ferror(rom)) - err(1, "Could not write global checksum"); - } - - if (fclose(rom) != 0) - err(1, "Could not complete ROM write"); - - return 0; + return failed; } diff --git a/src/fix/rgbfix.1 b/src/fix/rgbfix.1 index 06e0e5a9..93a9288e 100644 --- a/src/fix/rgbfix.1 +++ b/src/fix/rgbfix.1 @@ -24,39 +24,48 @@ .Op Fl p Ar pad_value .Op Fl r Ar ram_size .Op Fl t Ar title_str -.Ar file +.Op Ar .Sh DESCRIPTION The .Nm -program changes headers of Game Boy ROM images. +program changes headers of Game Boy ROM images, typically generated by +.Xr rgblink 1 , +though it will work with +.Em any +Game Boy ROM. It also performs other correctness operations, such as padding. +.Nm +only changes the fields for which it has values specified. +Developers are advised to fill those fields with 0x00 bytes in their source code before running +.Nm , +and to have already populated whichever fields they don't specify using +.Nm . .Pp Note that options can be abbreviated as long as the abbreviation is unambiguous: -.Fl Fl verb +.Fl Fl color-o is -.Fl Fl verbose , +.Fl Fl color-only , but -.Fl Fl ver +.Fl Fl color is invalid because it could also be -.Fl Fl version . -The arguments are as follows: +.Fl Fl color-compatible . +Options later in the command line override those set earlier. +Accepted options are as follows: .Bl -tag -width Ds .It Fl C , Fl Fl color-only -Set the Game Boy Color\(enonly flag: -.Ad 0x143 -= 0xC0. -If both this and the +Set the Game Boy Color\(enonly flag +.Pq Ad 0x143 +to 0xC0. +This overrides .Fl c -flag are set, this takes precedence. +if it was set prior. .It Fl c , Fl Fl color-compatible Set the Game Boy Color\(encompatible flag: -.Ad 0x143 -= 0x80. -If both this and the -.Fl C -flag are set, -.Fl C -takes precedence. +.Pq Ad 0x143 +to 0x80. +This overrides +.Fl c +if it was set prior. .It Fl f Ar fix_spec , Fl Fl fix-spec Ar fix_spec Fix certain header values that the Game Boy checks for correctness. Alternatively, intentionally trash these values by writing their binary inverse instead. @@ -83,55 +92,63 @@ Trash the global checksum. .It Fl i Ar game_id , Fl Fl game-id Ar game_id Set the game ID string .Pq Ad 0x13F Ns \(en Ns Ad 0x142 -to a given string of exactly 4 characters. -If both this and the title are set, the game ID will overwrite the overlapping portion of the title. +to a given string. +If it's longer than 4 chars, it will be truncated, and a warning emitted. .It Fl j , Fl Fl non-japanese -Set the non-Japanese region flag: -.Ad 0x14A -= 1. +Set the non-Japanese region flag +.Pq Ad 0x14A +to 0x01. .It Fl k Ar licensee_str , Fl Fl new-licensee Ar licensee_str Set the new licensee string .Pq Ad 0x144 Ns \(en Ns Ad 0x145 -to a given string, truncated to at most two characters. +to a given string. +If it's longer than 2 chars, it will be truncated, and a warning emitted. .It Fl l Ar licensee_id , Fl Fl old-licensee Ar licensee_id -Set the old licensee code, -.Ad 0x14B , +Set the old licensee code +.Pq Ad 0x14B to a given value from 0 to 0xFF. This value is deprecated and should be set to 0x33 in all new software. .It Fl m Ar mbc_type , Fl Fl mbc-type Ar mbc_type -Set the MBC type, -.Ad 0x147 , +Set the MBC type +.Pq Ad 0x147 to a given value from 0 to 0xFF. This value may also be an MBC name from the Pan Docs. .It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version -Set the ROM version, -.Ad 0x14C , +Set the ROM version +.Pq Ad 0x14C to a given value from 0 to 0xFF. .It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value -Pad the image to a valid size with a given pad value from 0 to 0xFF. +Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF). .Nm will automatically pick a size from 32 KiB, 64 KiB, 128 KiB, ..., 8192 KiB. The cartridge size byte .Pq Ad 0x148 will be changed to reflect this new size. +The recommended padding value is 0xFF, to speed up writing the ROM to flash chips, and to avoid "nop slides" into VRAM. .It Fl r Ar ram_size , Fl Fl ram-size Ar ram_size -Set the RAM size, -.Ad 0x149 , +Set the RAM size +.Pq Ad 0x149 to a given value from 0 to 0xFF. .It Fl s , Fl Fl sgb-compatible -Set the SGB flag: -.Ad 0x146 -= 3. This flag will be ignored by the SGB unless the old licensee code is 0x33! +Set the SGB flag +.Pq Ad 0x146 +to 0x03. +This flag will be ignored by the SGB unless the old licensee code is 0x33! +If this is given as well as +.Fl l , +but is not set to 0x33, a warning will be printed. .It Fl t Ar title , Fl Fl title Ar title Set the title string .Pq Ad 0x134 Ns \(en Ns Ad 0x143 -to a given string, truncated to at most 16 characters. -It is recommended to use 15 characters instead, to avoid clashing with the CGB flag -.Po Fl c +to a given string. +If the title is longer than the max length, it will be truncated, and a warning emitted. +The max length is 11 characters if the game ID +.Pq Fl i +is specified, 15 characters if the CGB flag +.Fl ( c or -.Fl C -.Pc . -If both this and the game ID are set, the game ID will overwrite the overlapping portion of the title. +.Fl C ) +is specified but the game ID is not, and 16 characters otherwise. .It Fl V , Fl Fl version Print the version of the program and exit. .It Fl v , Fl Fl validate @@ -139,7 +156,7 @@ Equivalent to .Fl f Cm lhg . .El .Sh EXAMPLES -Most values in the ROM header are only cosmetic. +Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway. The bare minimum requirements for a workable program are the header checksum, the Nintendo logo, and (if needed) the CGB/SGB flags. It is a good idea to pad the image to a valid size as well .Pq Do valid Dc meaning a power of 2, times 32 KiB . @@ -152,14 +169,13 @@ a valid size: The following will make a SGB-enabled, color-enabled game with a title of .Dq foobar , and pad it to a valid size. -.Po -The Game Boy itself does not use the title, but some emulators or ROM managers do. -.Pc +.Pq The Game Boy itself does not use the title, but some emulators or ROM managers do. .Pp .D1 $ rgbfix -vcs -l 0x33 -p 255 -t foobar baz.gb .Pp -The following will duplicate the header (sans global checksum) of the game -.Dq Survival Kids : +The following will duplicate the header of the game +.Dq Survival Kids , +sans global checksum: .Pp .D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \ SurvivalKids.gbc diff --git a/test/fix/.gitignore b/test/fix/.gitignore new file mode 100644 index 00000000..0364cfde --- /dev/null +++ b/test/fix/.gitignore @@ -0,0 +1 @@ +/padding*_* diff --git a/test/fix/README.md b/test/fix/README.md new file mode 100644 index 00000000..5976ad11 --- /dev/null +++ b/test/fix/README.md @@ -0,0 +1,16 @@ +# RGBFIX tests + +These tests check that RGBFIX behaves properly. + +## Structure of a test + +- `test.bin`: The file passed as input to RGBFIX. +- `test.flags`: The command-line flags passed to RGBFIX's invocation. + Actually, only the first line is considered; the rest of the file may contain comments about the test. +- `test.gb`: The expected output. + May not exist, generally when the test expects an error, in which case the comparison is skipped. +- `test.err`: The expected error output. + +## Special tests + +- `noexist.err` is the expected error output when RGBFIX is given a non-existent input file. diff --git a/test/fix/bad-fix-char.bin b/test/fix/bad-fix-char.bin new file mode 100644 index 00000000..f5bfb2c6 --- /dev/null +++ b/test/fix/bad-fix-char.bin @@ -0,0 +1,3 @@ +!H0;<N˗=^{ KC}bQ&3 /]dZ=bai:eS>U@Ay s_Cӛ &nWFoKX`'bl{ +8<$:V"㳙ځhDжQc㸺ZTVۼtŒ4Bd T I +(/׻{h:Qm$*| VP.^"&<k!`2 &B l7fZeyf.m !N5dEqF \ No newline at end of file diff --git a/test/fix/bad-fix-char.err b/test/fix/bad-fix-char.err new file mode 100644 index 00000000..114b1d62 --- /dev/null +++ b/test/fix/bad-fix-char.err @@ -0,0 +1,3 @@ +warning: Ignoring 'm' in fix spec +warning: Ignoring 'a' in fix spec +warning: Ignoring 'o' in fix spec diff --git a/test/fix/bad-fix-char.flags b/test/fix/bad-fix-char.flags new file mode 100644 index 00000000..afd50bdd --- /dev/null +++ b/test/fix/bad-fix-char.flags @@ -0,0 +1 @@ +-f lmao diff --git a/test/fix/color.bin b/test/fix/color.bin new file mode 100644 index 00000000..827a2954 --- /dev/null +++ b/test/fix/color.bin @@ -0,0 +1,2 @@ ++2l}~Ѧ}1x&)Yq*ɒ^ Hp@v6}[&hBn~T0uf\8ÑW 5^Bq]/Kd~"gF* AU"W7]}/}]/Kd~"gF* AU"W7]}/}" too short, expected at least 336 ($150) bytes, got only 0 +Fixing "" failed with 1 error diff --git a/test/fix/empty.flags b/test/fix/empty.flags new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/empty.gb b/test/fix/empty.gb new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/fix-override.bin b/test/fix/fix-override.bin new file mode 100644 index 00000000..dfba3457 Binary files /dev/null and b/test/fix/fix-override.bin differ diff --git a/test/fix/fix-override.err b/test/fix/fix-override.err new file mode 100644 index 00000000..0ca83c19 --- /dev/null +++ b/test/fix/fix-override.err @@ -0,0 +1 @@ +warning: 'l' overriding 'L' in fix spec diff --git a/test/fix/fix-override.flags b/test/fix/fix-override.flags new file mode 100644 index 00000000..3f8890cf --- /dev/null +++ b/test/fix/fix-override.flags @@ -0,0 +1 @@ +-f Ll diff --git a/test/fix/fix-override.gb b/test/fix/fix-override.gb new file mode 100644 index 00000000..f4f9f0a2 Binary files /dev/null and b/test/fix/fix-override.gb differ diff --git a/test/fix/gameid-trunc.bin b/test/fix/gameid-trunc.bin new file mode 100644 index 00000000..2a299f1e Binary files /dev/null and b/test/fix/gameid-trunc.bin differ diff --git a/test/fix/gameid-trunc.err b/test/fix/gameid-trunc.err new file mode 100644 index 00000000..67e81afa --- /dev/null +++ b/test/fix/gameid-trunc.err @@ -0,0 +1 @@ +warning: Truncating game ID "FOUR!" to 4 chars diff --git a/test/fix/gameid-trunc.flags b/test/fix/gameid-trunc.flags new file mode 100644 index 00000000..1ccf56c6 --- /dev/null +++ b/test/fix/gameid-trunc.flags @@ -0,0 +1 @@ +-i 'FOUR!' diff --git a/test/fix/gameid-trunc.gb b/test/fix/gameid-trunc.gb new file mode 100644 index 00000000..1b1e7510 Binary files /dev/null and b/test/fix/gameid-trunc.gb differ diff --git a/test/fix/gameid.bin b/test/fix/gameid.bin new file mode 100644 index 00000000..a9c5d8d0 Binary files /dev/null and b/test/fix/gameid.bin differ diff --git a/test/fix/gameid.err b/test/fix/gameid.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/gameid.flags b/test/fix/gameid.flags new file mode 100644 index 00000000..01b0c1e3 --- /dev/null +++ b/test/fix/gameid.flags @@ -0,0 +1 @@ +-i RGBD diff --git a/test/fix/gameid.gb b/test/fix/gameid.gb new file mode 100644 index 00000000..c28cc9f8 Binary files /dev/null and b/test/fix/gameid.gb differ diff --git a/test/fix/global-large.bin b/test/fix/global-large.bin new file mode 100644 index 00000000..a8315685 Binary files /dev/null and b/test/fix/global-large.bin differ diff --git a/test/fix/global-large.err b/test/fix/global-large.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/global-large.flags b/test/fix/global-large.flags new file mode 100644 index 00000000..0d815de5 --- /dev/null +++ b/test/fix/global-large.flags @@ -0,0 +1 @@ +-fg diff --git a/test/fix/global-large.gb b/test/fix/global-large.gb new file mode 100644 index 00000000..8ff2826c Binary files /dev/null and b/test/fix/global-large.gb differ diff --git a/test/fix/global-larger.bin b/test/fix/global-larger.bin new file mode 100644 index 00000000..4f8f1a1d Binary files /dev/null and b/test/fix/global-larger.bin differ diff --git a/test/fix/global-larger.err b/test/fix/global-larger.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/global-larger.flags b/test/fix/global-larger.flags new file mode 100644 index 00000000..0d815de5 --- /dev/null +++ b/test/fix/global-larger.flags @@ -0,0 +1 @@ +-fg diff --git a/test/fix/global-larger.gb b/test/fix/global-larger.gb new file mode 100644 index 00000000..cb09137c Binary files /dev/null and b/test/fix/global-larger.gb differ diff --git a/test/fix/global-trash.bin b/test/fix/global-trash.bin new file mode 100644 index 00000000..45c1fa93 Binary files /dev/null and b/test/fix/global-trash.bin differ diff --git a/test/fix/global-trash.err b/test/fix/global-trash.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/global-trash.flags b/test/fix/global-trash.flags new file mode 100644 index 00000000..c5b3f96c --- /dev/null +++ b/test/fix/global-trash.flags @@ -0,0 +1 @@ +-f G diff --git a/test/fix/global-trash.gb b/test/fix/global-trash.gb new file mode 100644 index 00000000..250133e6 Binary files /dev/null and b/test/fix/global-trash.gb differ diff --git a/test/fix/global.bin b/test/fix/global.bin new file mode 100644 index 00000000..45c1fa93 Binary files /dev/null and b/test/fix/global.bin differ diff --git a/test/fix/global.err b/test/fix/global.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/global.flags b/test/fix/global.flags new file mode 100644 index 00000000..099fa9ae --- /dev/null +++ b/test/fix/global.flags @@ -0,0 +1 @@ +-f g diff --git a/test/fix/global.gb b/test/fix/global.gb new file mode 100644 index 00000000..2b267f0a Binary files /dev/null and b/test/fix/global.gb differ diff --git a/test/fix/header-edit.bin b/test/fix/header-edit.bin new file mode 100644 index 00000000..9ae875e7 Binary files /dev/null and b/test/fix/header-edit.bin differ diff --git a/test/fix/header-edit.err b/test/fix/header-edit.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/header-edit.flags b/test/fix/header-edit.flags new file mode 100644 index 00000000..fc6cb560 --- /dev/null +++ b/test/fix/header-edit.flags @@ -0,0 +1,2 @@ +-Cf h +Checks that the header checksum properly accounts for header modifications diff --git a/test/fix/header-edit.gb b/test/fix/header-edit.gb new file mode 100644 index 00000000..3698ae5f Binary files /dev/null and b/test/fix/header-edit.gb differ diff --git a/test/fix/header-trash.bin b/test/fix/header-trash.bin new file mode 100644 index 00000000..21910c65 Binary files /dev/null and b/test/fix/header-trash.bin differ diff --git a/test/fix/header-trash.err b/test/fix/header-trash.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/header-trash.flags b/test/fix/header-trash.flags new file mode 100644 index 00000000..676b9233 --- /dev/null +++ b/test/fix/header-trash.flags @@ -0,0 +1 @@ +-f H diff --git a/test/fix/header-trash.gb b/test/fix/header-trash.gb new file mode 100644 index 00000000..4ea8386b Binary files /dev/null and b/test/fix/header-trash.gb differ diff --git a/test/fix/header.bin b/test/fix/header.bin new file mode 100644 index 00000000..21910c65 Binary files /dev/null and b/test/fix/header.bin differ diff --git a/test/fix/header.err b/test/fix/header.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/header.flags b/test/fix/header.flags new file mode 100644 index 00000000..8364ae76 --- /dev/null +++ b/test/fix/header.flags @@ -0,0 +1 @@ +-f h diff --git a/test/fix/header.gb b/test/fix/header.gb new file mode 100644 index 00000000..2ce368c5 Binary files /dev/null and b/test/fix/header.gb differ diff --git a/test/fix/jp.bin b/test/fix/jp.bin new file mode 100644 index 00000000..552d0334 Binary files /dev/null and b/test/fix/jp.bin differ diff --git a/test/fix/jp.err b/test/fix/jp.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/jp.flags b/test/fix/jp.flags new file mode 100644 index 00000000..1b8f44bc --- /dev/null +++ b/test/fix/jp.flags @@ -0,0 +1 @@ +-j diff --git a/test/fix/jp.gb b/test/fix/jp.gb new file mode 100644 index 00000000..f15b4c1b Binary files /dev/null and b/test/fix/jp.gb differ diff --git a/test/fix/logo-trash.bin b/test/fix/logo-trash.bin new file mode 100644 index 00000000..ddcece7c Binary files /dev/null and b/test/fix/logo-trash.bin differ diff --git a/test/fix/logo-trash.err b/test/fix/logo-trash.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/logo-trash.flags b/test/fix/logo-trash.flags new file mode 100644 index 00000000..02125988 --- /dev/null +++ b/test/fix/logo-trash.flags @@ -0,0 +1 @@ +-f L diff --git a/test/fix/logo-trash.gb b/test/fix/logo-trash.gb new file mode 100644 index 00000000..171982ce Binary files /dev/null and b/test/fix/logo-trash.gb differ diff --git a/test/fix/logo.bin b/test/fix/logo.bin new file mode 100644 index 00000000..ddcece7c Binary files /dev/null and b/test/fix/logo.bin differ diff --git a/test/fix/logo.err b/test/fix/logo.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/logo.flags b/test/fix/logo.flags new file mode 100644 index 00000000..566e6a78 --- /dev/null +++ b/test/fix/logo.flags @@ -0,0 +1 @@ +-f l diff --git a/test/fix/logo.gb b/test/fix/logo.gb new file mode 100644 index 00000000..c197759d Binary files /dev/null and b/test/fix/logo.gb differ diff --git a/test/fix/mbc.bin b/test/fix/mbc.bin new file mode 100644 index 00000000..a19945dd Binary files /dev/null and b/test/fix/mbc.bin differ diff --git a/test/fix/mbc.err b/test/fix/mbc.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/mbc.flags b/test/fix/mbc.flags new file mode 100644 index 00000000..dbe1d65d --- /dev/null +++ b/test/fix/mbc.flags @@ -0,0 +1 @@ +-m 177 diff --git a/test/fix/mbc.gb b/test/fix/mbc.gb new file mode 100644 index 00000000..b57621cc Binary files /dev/null and b/test/fix/mbc.gb differ diff --git a/test/fix/mbcless-ram.bin b/test/fix/mbcless-ram.bin new file mode 100644 index 00000000..49307e56 Binary files /dev/null and b/test/fix/mbcless-ram.bin differ diff --git a/test/fix/mbcless-ram.err b/test/fix/mbcless-ram.err new file mode 100644 index 00000000..d4246354 --- /dev/null +++ b/test/fix/mbcless-ram.err @@ -0,0 +1 @@ +warning: MBC "ROM" has no RAM, but RAM size was set to 2 diff --git a/test/fix/mbcless-ram.flags b/test/fix/mbcless-ram.flags new file mode 100644 index 00000000..f0fefa77 --- /dev/null +++ b/test/fix/mbcless-ram.flags @@ -0,0 +1 @@ +-m ROM -r 2 diff --git a/test/fix/mbcless-ram.gb b/test/fix/mbcless-ram.gb new file mode 100644 index 00000000..f7121bc7 Binary files /dev/null and b/test/fix/mbcless-ram.gb differ diff --git a/test/fix/new-lic-trunc.bin b/test/fix/new-lic-trunc.bin new file mode 100644 index 00000000..cbf09eb9 --- /dev/null +++ b/test/fix/new-lic-trunc.bin @@ -0,0 +1,3 @@ +i& ySaJ'ZDy1n 9QHpyFߘ8^]b Fmœ +ьMFWwk O`U0rΙܥ7""ffw X:޸XpVĪ\-`x*Nya4J܊p.7c+QՎBHez1O#`\_w +w4 "qyڷp$i! z/nuVCDuA5,﹥3M IO=H̲u1x־z_0=iQ;,t*ێv;EZka>)E8 u+f@}oHDqJ#dљs"m8V; ']|x \ No newline at end of file diff --git a/test/fix/new-lic-trunc.err b/test/fix/new-lic-trunc.err new file mode 100644 index 00000000..ceb621c9 --- /dev/null +++ b/test/fix/new-lic-trunc.err @@ -0,0 +1 @@ +warning: Truncating new licensee "HOMEBREW" to 2 chars diff --git a/test/fix/new-lic-trunc.flags b/test/fix/new-lic-trunc.flags new file mode 100644 index 00000000..d0619f73 --- /dev/null +++ b/test/fix/new-lic-trunc.flags @@ -0,0 +1 @@ +-k HOMEBREW diff --git a/test/fix/new-lic-trunc.gb b/test/fix/new-lic-trunc.gb new file mode 100644 index 00000000..fe16a29d --- /dev/null +++ b/test/fix/new-lic-trunc.gb @@ -0,0 +1,3 @@ +i& ySaJ'ZDy1n 9QHpyFߘ8^]b Fmœ +ьMFWwk O`U0rΙܥ7""ffw X:޸XpVĪ\-`x*Nya4J܊p.7c+QՎBHez1O#`\_w +w4 "qyڷp$i! z/nuVCDuA5,﹥3M IO=H̲u1x־z_0=iQ;,t*ێv;EZka>)E8 u+f@}oHDqJ#dљs"m8V; ']|x \ No newline at end of file diff --git a/test/fix/new-lic.bin b/test/fix/new-lic.bin new file mode 100644 index 00000000..cbf09eb9 --- /dev/null +++ b/test/fix/new-lic.bin @@ -0,0 +1,3 @@ +i& ySaJ'ZDy1n 9QHpyFߘ8^]b Fmœ +ьMFWwk O`U0rΙܥ7""ffw X:޸XpVĪ\-`x*Nya4J܊p.7c+QՎBHez1O#`\_w +w4 "qyڷp$i! z/nuVCDuA5,﹥3M IO=H̲u1x־z_0=iQ;,t*ێv;EZka>)E8 u+f@}oHDqJ#dљs"m8V; ']|x \ No newline at end of file diff --git a/test/fix/new-lic.err b/test/fix/new-lic.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/new-lic.flags b/test/fix/new-lic.flags new file mode 100644 index 00000000..90fd715e --- /dev/null +++ b/test/fix/new-lic.flags @@ -0,0 +1 @@ +-k HB diff --git a/test/fix/new-lic.gb b/test/fix/new-lic.gb new file mode 100644 index 00000000..4f4af93b --- /dev/null +++ b/test/fix/new-lic.gb @@ -0,0 +1,3 @@ +i& ySaJ'ZDy1n 9QHpyFߘ8^]b Fmœ +ьMFWwk O`U0rΙܥ7""ffw X:޸XpVĪ\-`x*Nya4J܊p.7c+QՎBHez1O#`\_w +w4 "qyڷp$i! z/nuVCDuA5,﹥3M IO=H̲u1x־z_0=iQ;,t*ێv;EZka>)E8 u+f@}oHDqJ#dљs"m8V; ']|x \ No newline at end of file diff --git a/test/fix/noexist.err b/test/fix/noexist.err new file mode 100644 index 00000000..833f0d15 --- /dev/null +++ b/test/fix/noexist.err @@ -0,0 +1,2 @@ +FATAL: Failed to open "noexist" for reading+writing: No such file or directory +Fixing "noexist" failed with 1 error diff --git a/test/fix/old-lic-hex.bin b/test/fix/old-lic-hex.bin new file mode 100644 index 00000000..07991b7f Binary files /dev/null and b/test/fix/old-lic-hex.bin differ diff --git a/test/fix/old-lic-hex.err b/test/fix/old-lic-hex.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/old-lic-hex.flags b/test/fix/old-lic-hex.flags new file mode 100644 index 00000000..4670567a --- /dev/null +++ b/test/fix/old-lic-hex.flags @@ -0,0 +1 @@ +-l 0x2a diff --git a/test/fix/old-lic-hex.gb b/test/fix/old-lic-hex.gb new file mode 100644 index 00000000..570e561c Binary files /dev/null and b/test/fix/old-lic-hex.gb differ diff --git a/test/fix/old-lic.bin b/test/fix/old-lic.bin new file mode 100644 index 00000000..7b761ffb --- /dev/null +++ b/test/fix/old-lic.bin @@ -0,0 +1,2 @@ +EZ/:ЀJ/_<&AXgAP +`1@Wh||øe뻬e^"7(*YVTOY!̔]=yV~xa];¡zUm z:d@֪b 'vʆqW_2)Gv(kg,p*@!Dõ0ke%ǚod$r <:cgi"aF'=fEcVcL.03"w), HQiDf3s(-$?a0@.OB{yzʫ h/\ S]om'2D#/grW3Χ&UEgMh-A64M|1q0O*$O/ng2RQX_[숔p\)ƧTSTaP*'[x>߅Ub[Ԧ9sIP ģdn$(ɔEysڈƇ6vH AqnKO{ֻwk& kQOjCe@5 \ No newline at end of file diff --git a/test/fix/old-lic.err b/test/fix/old-lic.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/old-lic.flags b/test/fix/old-lic.flags new file mode 100644 index 00000000..459dc132 --- /dev/null +++ b/test/fix/old-lic.flags @@ -0,0 +1 @@ +-l 42 diff --git a/test/fix/old-lic.gb b/test/fix/old-lic.gb new file mode 100644 index 00000000..9180effc --- /dev/null +++ b/test/fix/old-lic.gb @@ -0,0 +1,2 @@ +EZ/:ЀJ/_<&AXgAP +`1@Wh||øe뻬e^"7(*YVTOY!̔]=yV~xa];¡zUm z:d@֪b 'vʆqW_2)Gv(kg,p*@!Dõ0ke%ǚod$r <:cgi"aF'=fEcVcL.03"w), HQiDf3s(-$?a0@.OB{yzʫ h/\ S]om'2D#/grW3Χ&UEgMh-A*64M|1q0O*$O/ng2RQX_[숔p\)ƧTSTaP*'[x>߅Ub[Ԧ9sIP ģdn$(ɔEysڈƇ6vH AqnKO{ֻwk& kQOjCe@5 \ No newline at end of file diff --git a/test/fix/padding-bigperfect.bin b/test/fix/padding-bigperfect.bin new file mode 100644 index 00000000..8a07734a Binary files /dev/null and b/test/fix/padding-bigperfect.bin differ diff --git a/test/fix/padding-bigperfect.err b/test/fix/padding-bigperfect.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-bigperfect.flags b/test/fix/padding-bigperfect.flags new file mode 100644 index 00000000..cd19e415 --- /dev/null +++ b/test/fix/padding-bigperfect.flags @@ -0,0 +1 @@ +-p0xff diff --git a/test/fix/padding-bigperfect.gb b/test/fix/padding-bigperfect.gb new file mode 100644 index 00000000..6f774250 Binary files /dev/null and b/test/fix/padding-bigperfect.gb differ diff --git a/test/fix/padding-imperfect.bin b/test/fix/padding-imperfect.bin new file mode 100644 index 00000000..3e6775ec Binary files /dev/null and b/test/fix/padding-imperfect.bin differ diff --git a/test/fix/padding-imperfect.err b/test/fix/padding-imperfect.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-imperfect.flags b/test/fix/padding-imperfect.flags new file mode 100644 index 00000000..bb5e4c7b --- /dev/null +++ b/test/fix/padding-imperfect.flags @@ -0,0 +1 @@ +-p255 diff --git a/test/fix/padding-imperfect.gb b/test/fix/padding-imperfect.gb new file mode 100644 index 00000000..8111164e Binary files /dev/null and b/test/fix/padding-imperfect.gb differ diff --git a/test/fix/padding-large.bin b/test/fix/padding-large.bin new file mode 100644 index 00000000..b61425f6 Binary files /dev/null and b/test/fix/padding-large.bin differ diff --git a/test/fix/padding-large.err b/test/fix/padding-large.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-large.flags b/test/fix/padding-large.flags new file mode 100644 index 00000000..aa248d4e --- /dev/null +++ b/test/fix/padding-large.flags @@ -0,0 +1 @@ +-p 0xff diff --git a/test/fix/padding-large.gb b/test/fix/padding-large.gb new file mode 100644 index 00000000..309ac2b8 Binary files /dev/null and b/test/fix/padding-large.gb differ diff --git a/test/fix/padding-larger.bin b/test/fix/padding-larger.bin new file mode 100644 index 00000000..39a4050e Binary files /dev/null and b/test/fix/padding-larger.bin differ diff --git a/test/fix/padding-larger.err b/test/fix/padding-larger.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-larger.flags b/test/fix/padding-larger.flags new file mode 100644 index 00000000..637ef53f --- /dev/null +++ b/test/fix/padding-larger.flags @@ -0,0 +1 @@ +-p 255 diff --git a/test/fix/padding-larger.gb b/test/fix/padding-larger.gb new file mode 100644 index 00000000..f06be6b6 Binary files /dev/null and b/test/fix/padding-larger.gb differ diff --git a/test/fix/padding-perfect.bin b/test/fix/padding-perfect.bin new file mode 100644 index 00000000..aae05901 Binary files /dev/null and b/test/fix/padding-perfect.bin differ diff --git a/test/fix/padding-perfect.err b/test/fix/padding-perfect.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-perfect.flags b/test/fix/padding-perfect.flags new file mode 100644 index 00000000..18e77019 --- /dev/null +++ b/test/fix/padding-perfect.flags @@ -0,0 +1 @@ +-p 0xFF diff --git a/test/fix/padding-perfect.gb b/test/fix/padding-perfect.gb new file mode 100644 index 00000000..dfbf28e3 Binary files /dev/null and b/test/fix/padding-perfect.gb differ diff --git a/test/fix/padding-rom0.bin b/test/fix/padding-rom0.bin new file mode 100644 index 00000000..dbe9f609 Binary files /dev/null and b/test/fix/padding-rom0.bin differ diff --git a/test/fix/padding-rom0.err b/test/fix/padding-rom0.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/padding-rom0.flags b/test/fix/padding-rom0.flags new file mode 100644 index 00000000..637ef53f --- /dev/null +++ b/test/fix/padding-rom0.flags @@ -0,0 +1 @@ +-p 255 diff --git a/test/fix/padding-rom0.gb b/test/fix/padding-rom0.gb new file mode 100644 index 00000000..bfb3be10 Binary files /dev/null and b/test/fix/padding-rom0.gb differ diff --git a/test/fix/padding.bin b/test/fix/padding.bin new file mode 100644 index 00000000..f01b9b2a --- /dev/null +++ b/test/fix/padding.bin @@ -0,0 +1,6 @@ +Icܐ1ΡF֣d sEE<3aO |[8$Y.v;zD +v*eM^ZD\bP e~ +]{6 "gf,z aH9?^Dl +)p, Qz_Q0$e!0R 6^ +F7Q[5@'A0$\ds}Ȓ'­dJ9D e+" 3pδ*v'Y׭Mr0!*sh1?ȪZepX^'ڔ쓷lad`^kout.err')" ]]; then + echo "${bold}${red}Fixing $1 in-place shouldn't output anything on stdout!${rescolors}${resbold}" + our_rc=1 + fi + subst='out.gb' + else + # Stop! This is not a Useless Use Of Cat. Using cat instead of + # stdin redirection makes the input an unseekable pipe - a scenario + # that's harder to deal with. + cat "$2/$1.bin" | eval ./rgbfix "$flags" '>out.gb' '2>out.err' + subst='' + fi + + sed "s/$subst//g" "out.err" | tryDiff "$2/$1.err" - "$1.err${variant}" + our_rc=$(($? || $our_rc)) + if [[ -r "$2/$1.gb" ]]; then + tryCmp "$2/$1.gb" "out.gb" "$1.gb${variant}" + our_rc=$(($? || $our_rc)) + fi + + rc=$(($rc || $our_rc)) + if [[ $our_rc -ne 0 ]]; then break; fi + done +} + +rm -f padding*_* # Delete padding test cases generated but not deleted (e.g. interrupted) + +progress=1 +for i in "$src"/*.bin; do + runTest "$(basename "$i" .bin)" "$src" +done + +# Check the result with all different padding bytes +echo "${bold}Checking padding...${resbold}" +cp "$src"/padding{,-large,-larger}.bin . +touch padding{,-large,-larger}.err +progress=0 +for b in {0..254}; do + printf "\r$b..." + for suffix in '' -large -larger; do + cat <<<'-p $b' >padding$suffix.flags + tr '\377' \\$(($b / 64))$((($b / 8) % 8))$(($b % 8)) <"$src/padding$suffix.gb" >padding$suffix.gb # OK because $FF bytes are only used for padding + runTest padding${suffix} . + done +done +printf "\rDone! \n" + +# TODO: check MBC names + +# Check that RGBFIX errors out when inputting a non-existent file... +./rgbfix noexist 2>out.err +rc=$(($rc || $? != 1)) +tryDiff "$src/noexist.err" out.err noexist.err +rc=$(($rc || $?)) + +exit $rc diff --git a/test/fix/title-color-trunc-rev.bin b/test/fix/title-color-trunc-rev.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-color-trunc-rev.bin differ diff --git a/test/fix/title-color-trunc-rev.err b/test/fix/title-color-trunc-rev.err new file mode 100644 index 00000000..59a9ecfe --- /dev/null +++ b/test/fix/title-color-trunc-rev.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 15 chars diff --git a/test/fix/title-color-trunc-rev.flags b/test/fix/title-color-trunc-rev.flags new file mode 100644 index 00000000..9df669f0 --- /dev/null +++ b/test/fix/title-color-trunc-rev.flags @@ -0,0 +1,3 @@ +-t 0123456789ABCDEF -C +Checks that the CGB flag correctly truncates the title to 15 chars only, +even when it's specified *after* the title..! diff --git a/test/fix/title-color-trunc-rev.gb b/test/fix/title-color-trunc-rev.gb new file mode 100644 index 00000000..4744ed43 Binary files /dev/null and b/test/fix/title-color-trunc-rev.gb differ diff --git a/test/fix/title-color-trunc.bin b/test/fix/title-color-trunc.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-color-trunc.bin differ diff --git a/test/fix/title-color-trunc.err b/test/fix/title-color-trunc.err new file mode 100644 index 00000000..59a9ecfe --- /dev/null +++ b/test/fix/title-color-trunc.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 15 chars diff --git a/test/fix/title-color-trunc.flags b/test/fix/title-color-trunc.flags new file mode 100644 index 00000000..141ecbfd --- /dev/null +++ b/test/fix/title-color-trunc.flags @@ -0,0 +1,2 @@ +-C -t 0123456789ABCDEF +Checks that the CGB flag correctly truncates the title to 15 chars only diff --git a/test/fix/title-color-trunc.gb b/test/fix/title-color-trunc.gb new file mode 100644 index 00000000..4744ed43 Binary files /dev/null and b/test/fix/title-color-trunc.gb differ diff --git a/test/fix/title-compat-trunc-rev.bin b/test/fix/title-compat-trunc-rev.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-compat-trunc-rev.bin differ diff --git a/test/fix/title-compat-trunc-rev.err b/test/fix/title-compat-trunc-rev.err new file mode 100644 index 00000000..59a9ecfe --- /dev/null +++ b/test/fix/title-compat-trunc-rev.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 15 chars diff --git a/test/fix/title-compat-trunc-rev.flags b/test/fix/title-compat-trunc-rev.flags new file mode 100644 index 00000000..dd3c4fca --- /dev/null +++ b/test/fix/title-compat-trunc-rev.flags @@ -0,0 +1,3 @@ +-t 0123456789ABCDEF -c +Checks that the CGB compat flag correctly truncates the title to 15 chars only, +even when it's specified *after* the title..! diff --git a/test/fix/title-compat-trunc-rev.gb b/test/fix/title-compat-trunc-rev.gb new file mode 100644 index 00000000..851e090c Binary files /dev/null and b/test/fix/title-compat-trunc-rev.gb differ diff --git a/test/fix/title-compat-trunc.bin b/test/fix/title-compat-trunc.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-compat-trunc.bin differ diff --git a/test/fix/title-compat-trunc.err b/test/fix/title-compat-trunc.err new file mode 100644 index 00000000..59a9ecfe --- /dev/null +++ b/test/fix/title-compat-trunc.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 15 chars diff --git a/test/fix/title-compat-trunc.flags b/test/fix/title-compat-trunc.flags new file mode 100644 index 00000000..b0f3a762 --- /dev/null +++ b/test/fix/title-compat-trunc.flags @@ -0,0 +1,2 @@ +-c -t 0123456789ABCDEF +Checks that the CGB compat flag correctly truncates the title to 15 chars only diff --git a/test/fix/title-compat-trunc.gb b/test/fix/title-compat-trunc.gb new file mode 100644 index 00000000..851e090c Binary files /dev/null and b/test/fix/title-compat-trunc.gb differ diff --git a/test/fix/title-gameid-trunc-rev.bin b/test/fix/title-gameid-trunc-rev.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-gameid-trunc-rev.bin differ diff --git a/test/fix/title-gameid-trunc-rev.err b/test/fix/title-gameid-trunc-rev.err new file mode 100644 index 00000000..a0aff509 --- /dev/null +++ b/test/fix/title-gameid-trunc-rev.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 11 chars diff --git a/test/fix/title-gameid-trunc-rev.flags b/test/fix/title-gameid-trunc-rev.flags new file mode 100644 index 00000000..4ed79f35 --- /dev/null +++ b/test/fix/title-gameid-trunc-rev.flags @@ -0,0 +1,3 @@ +-t 0123456789ABCDEF -i rgbd +Checks that the game ID flag correctly truncates the title to 11 chars only, +even when it's specified *after* the title..! diff --git a/test/fix/title-gameid-trunc-rev.gb b/test/fix/title-gameid-trunc-rev.gb new file mode 100644 index 00000000..d2f1f536 Binary files /dev/null and b/test/fix/title-gameid-trunc-rev.gb differ diff --git a/test/fix/title-gameid-trunc.bin b/test/fix/title-gameid-trunc.bin new file mode 100644 index 00000000..0c7c3058 Binary files /dev/null and b/test/fix/title-gameid-trunc.bin differ diff --git a/test/fix/title-gameid-trunc.err b/test/fix/title-gameid-trunc.err new file mode 100644 index 00000000..a0aff509 --- /dev/null +++ b/test/fix/title-gameid-trunc.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEF" to 11 chars diff --git a/test/fix/title-gameid-trunc.flags b/test/fix/title-gameid-trunc.flags new file mode 100644 index 00000000..c6273db4 --- /dev/null +++ b/test/fix/title-gameid-trunc.flags @@ -0,0 +1,2 @@ +-i rgbd -t 0123456789ABCDEF +Checks that the game ID flag correctly truncates the title to 11 chars only diff --git a/test/fix/title-gameid-trunc.gb b/test/fix/title-gameid-trunc.gb new file mode 100644 index 00000000..d2f1f536 Binary files /dev/null and b/test/fix/title-gameid-trunc.gb differ diff --git a/test/fix/title-pad.bin b/test/fix/title-pad.bin new file mode 100644 index 00000000..25bb672c Binary files /dev/null and b/test/fix/title-pad.bin differ diff --git a/test/fix/title-pad.err b/test/fix/title-pad.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/title-pad.flags b/test/fix/title-pad.flags new file mode 100644 index 00000000..f156c554 --- /dev/null +++ b/test/fix/title-pad.flags @@ -0,0 +1 @@ +-t "I LOVE YOU" diff --git a/test/fix/title-pad.gb b/test/fix/title-pad.gb new file mode 100644 index 00000000..3b5cb293 Binary files /dev/null and b/test/fix/title-pad.gb differ diff --git a/test/fix/title-trunc.bin b/test/fix/title-trunc.bin new file mode 100644 index 00000000..ade3935a Binary files /dev/null and b/test/fix/title-trunc.bin differ diff --git a/test/fix/title-trunc.err b/test/fix/title-trunc.err new file mode 100644 index 00000000..24e5093a --- /dev/null +++ b/test/fix/title-trunc.err @@ -0,0 +1 @@ +warning: Truncating title "0123456789ABCDEFGHIJK" to 16 chars diff --git a/test/fix/title-trunc.flags b/test/fix/title-trunc.flags new file mode 100644 index 00000000..bf5084eb --- /dev/null +++ b/test/fix/title-trunc.flags @@ -0,0 +1 @@ +-t 0123456789ABCDEFGHIJK diff --git a/test/fix/title-trunc.gb b/test/fix/title-trunc.gb new file mode 100644 index 00000000..e0075ca7 Binary files /dev/null and b/test/fix/title-trunc.gb differ diff --git a/test/fix/title.bin b/test/fix/title.bin new file mode 100644 index 00000000..d842d62c Binary files /dev/null and b/test/fix/title.bin differ diff --git a/test/fix/title.err b/test/fix/title.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/title.flags b/test/fix/title.flags new file mode 100644 index 00000000..3d8cc99a --- /dev/null +++ b/test/fix/title.flags @@ -0,0 +1 @@ +-t "Game Boy dev rox" diff --git a/test/fix/title.gb b/test/fix/title.gb new file mode 100644 index 00000000..614591b1 Binary files /dev/null and b/test/fix/title.gb differ diff --git a/test/fix/tooshort.bin b/test/fix/tooshort.bin new file mode 100644 index 00000000..24d9423f --- /dev/null +++ b/test/fix/tooshort.bin @@ -0,0 +1 @@ +g1Bmk&)v \ No newline at end of file diff --git a/test/fix/tooshort.err b/test/fix/tooshort.err new file mode 100644 index 00000000..062743b4 --- /dev/null +++ b/test/fix/tooshort.err @@ -0,0 +1,2 @@ +FATAL: "" too short, expected at least 336 ($150) bytes, got only 20 +Fixing "" failed with 1 error diff --git a/test/fix/tooshort.flags b/test/fix/tooshort.flags new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/verify-pad.bin b/test/fix/verify-pad.bin new file mode 100644 index 00000000..512fb85a Binary files /dev/null and b/test/fix/verify-pad.bin differ diff --git a/test/fix/verify-pad.err b/test/fix/verify-pad.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/verify-pad.flags b/test/fix/verify-pad.flags new file mode 100644 index 00000000..aa139d70 --- /dev/null +++ b/test/fix/verify-pad.flags @@ -0,0 +1,4 @@ +-vp 69 +Check that the global checksum is correctly affected by padding: +Padding adds extra bytes (carefully picked *not* to be 0, or other values), +which must be properly accounted for. diff --git a/test/fix/verify-pad.gb b/test/fix/verify-pad.gb new file mode 100644 index 00000000..709443b1 Binary files /dev/null and b/test/fix/verify-pad.gb differ diff --git a/test/fix/verify-trash.bin b/test/fix/verify-trash.bin new file mode 100644 index 00000000..512fb85a Binary files /dev/null and b/test/fix/verify-trash.bin differ diff --git a/test/fix/verify-trash.err b/test/fix/verify-trash.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/verify-trash.flags b/test/fix/verify-trash.flags new file mode 100644 index 00000000..a9b4f7c1 --- /dev/null +++ b/test/fix/verify-trash.flags @@ -0,0 +1,2 @@ +-f LHG +Checks that the global checksum is correctly affected by the header checksum diff --git a/test/fix/verify-trash.gb b/test/fix/verify-trash.gb new file mode 100644 index 00000000..5c14c139 Binary files /dev/null and b/test/fix/verify-trash.gb differ diff --git a/test/fix/verify.bin b/test/fix/verify.bin new file mode 100644 index 00000000..512fb85a Binary files /dev/null and b/test/fix/verify.bin differ diff --git a/test/fix/verify.err b/test/fix/verify.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/verify.flags b/test/fix/verify.flags new file mode 100644 index 00000000..a9105b8d --- /dev/null +++ b/test/fix/verify.flags @@ -0,0 +1,2 @@ +-v +Checks that the global checksum is correctly affected by the header checksum diff --git a/test/fix/verify.gb b/test/fix/verify.gb new file mode 100644 index 00000000..53188fd2 Binary files /dev/null and b/test/fix/verify.gb differ diff --git a/test/fix/version.bin b/test/fix/version.bin new file mode 100644 index 00000000..d354c437 Binary files /dev/null and b/test/fix/version.bin differ diff --git a/test/fix/version.err b/test/fix/version.err new file mode 100644 index 00000000..e69de29b diff --git a/test/fix/version.flags b/test/fix/version.flags new file mode 100644 index 00000000..b69bc770 --- /dev/null +++ b/test/fix/version.flags @@ -0,0 +1 @@ +-n 11 diff --git a/test/fix/version.gb b/test/fix/version.gb new file mode 100644 index 00000000..db2cea90 Binary files /dev/null and b/test/fix/version.gb differ diff --git a/test/run-tests.sh b/test/run-tests.sh index b533b412..0d46315a 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -14,13 +14,11 @@ fi # Tests included with the repository -pushd asm -./test.sh -popd - -pushd link -./test.sh -popd +for dir in asm link fix; do + pushd $dir + ./test.sh + popd +done # Test some significant external projects that use RGBDS # When adding new ones, don't forget to add them to the .gitignore!