Specify a custom logo file to use instead of the Nintendo logo (#1400)

Fixes #1398
This commit is contained in:
Sylvie
2024-06-18 14:02:50 -04:00
committed by GitHub
parent 8c3ca462fe
commit 9cc595b2cc
10 changed files with 75 additions and 21 deletions

View File

@@ -16,6 +16,7 @@ _rgbfix_completions() {
[f]="fix-spec:fix-spec" [f]="fix-spec:fix-spec"
[i]="game-id:unk" [i]="game-id:unk"
[k]="new-licensee:unk" [k]="new-licensee:unk"
[L]="custom-logo:glob-*.1bpp"
[l]="old-licensee:unk" [l]="old-licensee:unk"
[m]="mbc-type:mbc" [m]="mbc-type:mbc"
[n]="rom-version:unk" [n]="rom-version:unk"

View File

@@ -49,6 +49,7 @@ local args=(
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:' '(-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:' '(-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 --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" '(-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:' '(-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:' '(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'

View File

@@ -13,6 +13,7 @@
.Op Fl f Ar fix_spec .Op Fl f Ar fix_spec
.Op Fl i Ar game_id .Op Fl i Ar game_id
.Op Fl k Ar licensee_str .Op Fl k Ar licensee_str
.Op Fl L Ar logo_file
.Op Fl l Ar licensee_id .Op Fl l Ar licensee_id
.Op Fl m Ar mbc_type .Op Fl m Ar mbc_type
.Op Fl n Ar rom_version .Op Fl n Ar rom_version
@@ -104,6 +105,9 @@ Set the new licensee string
.Pq Ad 0x144 Ns \(en Ns Ad 0x145 .Pq Ad 0x144 Ns \(en Ns Ad 0x145
to a given string. to a given string.
If it's longer than 2 chars, it will be truncated, and a warning emitted. 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 .It Fl l Ar licensee_id , Fl \-old-licensee Ar licensee_id
Set the old licensee code Set the old licensee code
.Pq Ad 0x14B .Pq Ad 0x14B

View File

@@ -22,7 +22,7 @@
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
// Short options // 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 * Equivalent long options
@@ -41,6 +41,7 @@ static option const longopts[] = {
{"game-id", required_argument, nullptr, 'i'}, {"game-id", required_argument, nullptr, 'i'},
{"non-japanese", no_argument, nullptr, 'j'}, {"non-japanese", no_argument, nullptr, 'j'},
{"new-licensee", required_argument, nullptr, 'k'}, {"new-licensee", required_argument, nullptr, 'k'},
{"logo", required_argument, nullptr, 'L'},
{"old-licensee", required_argument, nullptr, 'l'}, {"old-licensee", required_argument, nullptr, 'l'},
{"mbc-type", required_argument, nullptr, 'm'}, {"mbc-type", required_argument, nullptr, 'm'},
{"rom-version", required_argument, nullptr, 'n'}, {"rom-version", required_argument, nullptr, 'n'},
@@ -57,8 +58,9 @@ static option const longopts[] = {
static void printUsage() { static void printUsage() {
fputs( fputs(
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" "Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n" " [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] <file> ...\n" " [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
" <file> ...\n"
"Useful options:\n" "Useful options:\n"
" -m, --mbc-type <value> set the MBC type byte to this value; refer\n" " -m, --mbc-type <value> set the MBC type byte to this value; refer\n"
" to the man page for a list of values\n" " to the man page for a list of values\n"
@@ -728,22 +730,12 @@ static bool hasRAM(MbcType type) {
unreachable_(); 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, 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, 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, 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; static uint8_t fixSpec = 0;
#define FIX_LOGO (1 << 7) #define FIX_LOGO (1 << 7)
#define TRASH_LOGO (1 << 6) #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 char const *gameID = nullptr;
static uint8_t gameIDLen; static uint8_t gameIDLen;
static bool japanese = true; static bool japanese = true;
static char const *logoFilename = nullptr;
static uint8_t logo[sizeof(nintendoLogo)] = {};
static char const *newLicensee = nullptr; static char const *newLicensee = nullptr;
static uint8_t newLicenseeLen; static uint8_t newLicenseeLen;
static uint16_t oldLicensee = UNSPECIFIED; 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 // Accept partial reads if the file contains at least the header
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) { if (fixSpec & (FIX_LOGO | TRASH_LOGO))
if (fixSpec & FIX_LOGO) overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
overwriteBytes(rom0, 0x0104, ninLogo, sizeof(ninLogo), "Nintendo logo");
else
overwriteBytes(rom0, 0x0104, trashLogo, sizeof(trashLogo), "Nintendo logo");
}
if (title) if (title)
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title"); overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
@@ -1310,6 +1300,10 @@ int main(int argc, char *argv[]) {
newLicenseeLen = len; newLicenseeLen = len;
break; break;
case 'L':
logoFilename = musl_optarg;
break;
case 'l': case 'l':
parseByte(oldLicensee, 'l'); parseByte(oldLicensee, 'l');
break; break;
@@ -1433,6 +1427,55 @@ int main(int argc, char *argv[]) {
argv += musl_optind; argv += musl_optind;
bool failed = nbErrors; bool failed = nbErrors;
if (logoFilename) {
FILE *logoFile;
if (strcmp(logoFilename, "-")) {
logoFile = fopen(logoFilename, "rb");
} else {
logoFilename = "<stdin>";
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) { if (!*argv) {
fputs( fputs(
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr

BIN
test/fix/custom-logo.1bpp Normal file

Binary file not shown.

BIN
test/fix/custom-logo.bin Normal file

Binary file not shown.

1
test/fix/custom-logo.err Normal file
View File

@@ -0,0 +1 @@
warning: Overwrote a non-zero byte in the logo

View File

@@ -0,0 +1 @@
-f l -L custom-logo.1bpp

BIN
test/fix/custom-logo.gb Normal file

Binary file not shown.

View File

@@ -38,7 +38,10 @@ tryCmp () {
} }
runTest () { 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 for variant in '' ' piped'; do
(( tests++ )) (( tests++ ))