diff --git a/contrib/bash_compl/_rgbfix.bash b/contrib/bash_compl/_rgbfix.bash index 66972fda..fa94a11c 100755 --- a/contrib/bash_compl/_rgbfix.bash +++ b/contrib/bash_compl/_rgbfix.bash @@ -16,6 +16,7 @@ _rgbfix_completions() { [f]="fix-spec:fix-spec" [i]="game-id:unk" [k]="new-licensee:unk" + [L]="custom-logo:glob-*.1bpp" [l]="old-licensee:unk" [m]="mbc-type:mbc" [n]="rom-version:unk" diff --git a/contrib/zsh_compl/_rgbfix b/contrib/zsh_compl/_rgbfix index ab18137a..56b54ba4 100644 --- a/contrib/zsh_compl/_rgbfix +++ b/contrib/zsh_compl/_rgbfix @@ -49,6 +49,7 @@ local args=( '(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:' '(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:' '(-l --old-licensee)'{-l,--old-licensee}'+[Set old licensee ID]:licensee number:' + '(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:' '(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names" '(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:' '(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:' diff --git a/man/rgbfix.1 b/man/rgbfix.1 index 8729ad8b..e0089c35 100644 --- a/man/rgbfix.1 +++ b/man/rgbfix.1 @@ -13,6 +13,7 @@ .Op Fl f Ar fix_spec .Op Fl i Ar game_id .Op Fl k Ar licensee_str +.Op Fl L Ar logo_file .Op Fl l Ar licensee_id .Op Fl m Ar mbc_type .Op Fl n Ar rom_version @@ -104,6 +105,9 @@ Set the new licensee string .Pq Ad 0x144 Ns \(en Ns Ad 0x145 to a given string. If it's longer than 2 chars, it will be truncated, and a warning emitted. +.It Fl L Ar logo_file , Fl \-logo Ar logo_file +Specify a logo file to use instead of the official Nintendo logo. +The file must be 48 bytes of 1bpp tile data; the source image should be 48 pixels wide and 8 pixels tall. .It Fl l Ar licensee_id , Fl \-old-licensee Ar licensee_id Set the old licensee code .Pq Ad 0x14B diff --git a/src/fix/main.cpp b/src/fix/main.cpp index eaf14987..185ba697 100644 --- a/src/fix/main.cpp +++ b/src/fix/main.cpp @@ -22,7 +22,7 @@ #define BANK_SIZE 0x4000 // Short options -static char const *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv"; +static char const *optstring = "Ccf:i:jk:L:l:m:n:Op:r:st:Vv"; /* * Equivalent long options @@ -41,6 +41,7 @@ static option const longopts[] = { {"game-id", required_argument, nullptr, 'i'}, {"non-japanese", no_argument, nullptr, 'j'}, {"new-licensee", required_argument, nullptr, 'k'}, + {"logo", required_argument, nullptr, 'L'}, {"old-licensee", required_argument, nullptr, 'l'}, {"mbc-type", required_argument, nullptr, 'm'}, {"rom-version", required_argument, nullptr, 'n'}, @@ -57,8 +58,9 @@ static option const longopts[] = { static void printUsage() { fputs( "Usage: rgbfix [-jOsVv] [-C | -c] [-f ] [-i ] [-k ]\n" - " [-l ] [-m ] [-n ]\n" - " [-p ] [-r ] [-t ] ...\n" + " [-L ] [-l ] [-m ]\n" + " [-n ] [-p ] [-r ] [-t ]\n" + " ...\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" @@ -728,22 +730,12 @@ static bool hasRAM(MbcType type) { unreachable_(); } -static uint8_t const ninLogo[] = { +static uint8_t const nintendoLogo[] = { 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 uint8_t const trashLogo[] = { - 0xFF ^ 0xCE, 0xFF ^ 0xED, 0xFF ^ 0x66, 0xFF ^ 0x66, 0xFF ^ 0xCC, 0xFF ^ 0x0D, 0xFF ^ 0x00, - 0xFF ^ 0x0B, 0xFF ^ 0x03, 0xFF ^ 0x73, 0xFF ^ 0x00, 0xFF ^ 0x83, 0xFF ^ 0x00, 0xFF ^ 0x0C, - 0xFF ^ 0x00, 0xFF ^ 0x0D, 0xFF ^ 0x00, 0xFF ^ 0x08, 0xFF ^ 0x11, 0xFF ^ 0x1F, 0xFF ^ 0x88, - 0xFF ^ 0x89, 0xFF ^ 0x00, 0xFF ^ 0x0E, 0xFF ^ 0xDC, 0xFF ^ 0xCC, 0xFF ^ 0x6E, 0xFF ^ 0xE6, - 0xFF ^ 0xDD, 0xFF ^ 0xDD, 0xFF ^ 0xD9, 0xFF ^ 0x99, 0xFF ^ 0xBB, 0xFF ^ 0xBB, 0xFF ^ 0x67, - 0xFF ^ 0x63, 0xFF ^ 0x6E, 0xFF ^ 0x0E, 0xFF ^ 0xEC, 0xFF ^ 0xCC, 0xFF ^ 0xDD, 0xFF ^ 0xDC, - 0xFF ^ 0x99, 0xFF ^ 0x9F, 0xFF ^ 0xBB, 0xFF ^ 0xB9, 0xFF ^ 0x33, 0xFF ^ 0x3E, -}; - static uint8_t fixSpec = 0; #define FIX_LOGO (1 << 7) #define TRASH_LOGO (1 << 6) @@ -756,6 +748,8 @@ static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone static char const *gameID = nullptr; static uint8_t gameIDLen; static bool japanese = true; +static char const *logoFilename = nullptr; +static uint8_t logo[sizeof(nintendoLogo)] = {}; static char const *newLicensee = nullptr; static uint8_t newLicenseeLen; static uint16_t oldLicensee = UNSPECIFIED; @@ -893,12 +887,8 @@ static void processFile(int input, int output, char const *name, off_t fileSize) } // Accept partial reads if the file contains at least the header - if (fixSpec & (FIX_LOGO | TRASH_LOGO)) { - if (fixSpec & FIX_LOGO) - overwriteBytes(rom0, 0x0104, ninLogo, sizeof(ninLogo), "Nintendo logo"); - else - overwriteBytes(rom0, 0x0104, trashLogo, sizeof(trashLogo), "Nintendo logo"); - } + if (fixSpec & (FIX_LOGO | TRASH_LOGO)) + overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo"); if (title) overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title"); @@ -1310,6 +1300,10 @@ int main(int argc, char *argv[]) { newLicenseeLen = len; break; + case 'L': + logoFilename = musl_optarg; + break; + case 'l': parseByte(oldLicensee, 'l'); break; @@ -1433,6 +1427,55 @@ int main(int argc, char *argv[]) { argv += musl_optind; bool failed = nbErrors; + if (logoFilename) { + FILE *logoFile; + if (strcmp(logoFilename, "-")) { + logoFile = fopen(logoFilename, "rb"); + } else { + logoFilename = ""; + logoFile = fdopen(STDIN_FILENO, "rb"); + } + if (!logoFile) { + fprintf( + stderr, + "FATAL: Failed to open \"%s\" for reading: %s\n", + logoFilename, + strerror(errno) + ); + exit(1); + } + Defer closeLogo{[&] { fclose(logoFile); }}; + uint8_t logoBpp[sizeof(logo)]; + if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile); + nbRead != sizeof(logo) || fgetc(logoFile) != EOF || ferror(logoFile)) { + fprintf(stderr, "FATAL: \"%s\" is not %zu bytes\n", logoFilename, sizeof(logo)); + exit(1); + } + auto highs = [&logoBpp](size_t i) { + return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4); + }; + auto lows = [&logoBpp](size_t i) { + return ((logoBpp[i * 2] & 0x0F) << 4) | (logoBpp[i * 2 + 1] & 0x0F); + }; + constexpr size_t mid = sizeof(logo) / 2; + for (size_t i = 0; i < mid; i += 4) { + logo[i + 0] = highs(i + 0); + logo[i + 1] = highs(i + 1); + logo[i + 2] = lows(i + 0); + logo[i + 3] = lows(i + 1); + logo[mid + i + 0] = highs(i + 2); + logo[mid + i + 1] = highs(i + 3); + logo[mid + i + 2] = lows(i + 2); + logo[mid + i + 3] = lows(i + 3); + } + } else { + memcpy(logo, nintendoLogo, sizeof(nintendoLogo)); + } + if (fixSpec & TRASH_LOGO) { + for (uint16_t i = 0; i < sizeof(logo); i++) + logo[i] = 0xFF ^ logo[i]; + } + if (!*argv) { fputs( "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr diff --git a/test/fix/custom-logo.1bpp b/test/fix/custom-logo.1bpp new file mode 100644 index 00000000..210eb85c Binary files /dev/null and b/test/fix/custom-logo.1bpp differ diff --git a/test/fix/custom-logo.bin b/test/fix/custom-logo.bin new file mode 100644 index 00000000..fd23e611 Binary files /dev/null and b/test/fix/custom-logo.bin differ diff --git a/test/fix/custom-logo.err b/test/fix/custom-logo.err new file mode 100644 index 00000000..8fb4635a --- /dev/null +++ b/test/fix/custom-logo.err @@ -0,0 +1 @@ +warning: Overwrote a non-zero byte in the logo diff --git a/test/fix/custom-logo.flags b/test/fix/custom-logo.flags new file mode 100644 index 00000000..0fbbe4c4 --- /dev/null +++ b/test/fix/custom-logo.flags @@ -0,0 +1 @@ +-f l -L custom-logo.1bpp diff --git a/test/fix/custom-logo.gb b/test/fix/custom-logo.gb new file mode 100644 index 00000000..97f3616b Binary files /dev/null and b/test/fix/custom-logo.gb differ diff --git a/test/fix/test.sh b/test/fix/test.sh index a82f4b2b..37993b8b 100755 --- a/test/fix/test.sh +++ b/test/fix/test.sh @@ -38,7 +38,10 @@ tryCmp () { } runTest () { - flags="$(head -n 1 "$2/$1.flags")" # Allow other lines to serve as comments + flags=$( + head -n 1 "$2/$1.flags" | # Allow other lines to serve as comments + sed "s#-L #-L ${src//#/\\#}/#g" # Prepend src directory to logo file + ) for variant in '' ' piped'; do (( tests++ ))