Rewrite rgbfix from scratch, under a free ISC license.

Slight changes in usage; the man page has been updated accordingly.
This commit is contained in:
Anthony J. Bentley
2010-12-22 13:48:44 -07:00
parent 05be8e0011
commit d2f52fdd0c
2 changed files with 406 additions and 17 deletions

View File

@@ -7,9 +7,11 @@
.\" SECTION .\" SECTION
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm rgbfix .Nm rgbfix
.Op Fl Ccdjqrsv .Op Fl Ccjsv
.Op Fl k Ar licensee_str .Op Fl k Ar licensee_str
.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 p Ar pad_value .Op Fl p Ar pad_value
.Op Fl r Ar ram_size .Op Fl r Ar ram_size
.Op Fl t Ar title_str .Op Fl t Ar title_str
@@ -24,19 +26,18 @@ The arguments are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
.\" ITEM .\" ITEM
.It Fl C .It Fl C
Set the Game Boy Color compatible flag: [0x143] = 0x80. Set the Game Boy Color\(enonly flag: [0x143] = 0xC0.
This flag and the If both this and the
.Fl c .Fl c
flag are mutually exclusive. flag are set, this takes precedence.
.\" ITEM .\" ITEM
.It Fl c .It Fl c
Set the Game Boy Color only flag: [0x143] = 0xC0. Set the Game Boy Color\(encompatible flag: [0x143] = 0x80.
This flag and the If both this and the
.Fl C .Fl C
flag are mutually exclusive. flag are set,
.\" ITEM .Fl C
.It Fl d takes precedence.
Don't perform any changes, just pretend to for debugging.
.\" ITEM .\" ITEM
.It Fl j .It Fl j
Set the non-Japanese region flag: [0x14A] = 1. Set the non-Japanese region flag: [0x14A] = 1.
@@ -45,9 +46,16 @@ Set the non-Japanese region flag: [0x14A] = 1.
Set the new licensee string ([0x144\(en0x145]) to a given string, truncated Set the new licensee string ([0x144\(en0x145]) to a given string, truncated
to at most two characters. to at most two characters.
.\" ITEM .\" ITEM
.It Fl l Ar licensee_id
Set the old licensee code, [0x14B], to a given value from 0 to 0xFF.
This value is deprecated and should be set to 0x33 in all new software.
.\" ITEM
.It Fl m Ar mbc_type .It Fl m Ar mbc_type
Set the MBC type, [0x147], to a given value from 0 to 0xFF. Set the MBC type, [0x147], to a given value from 0 to 0xFF.
.\" ITEM .\" ITEM
.It Fl n Ar rom_version
Set the ROM version, [0x14C], to a given value from 0 to 0xFF.
.\" ITEM
.It Fl p Ar pad_value .It Fl p Ar pad_value
Pad the image to a valid size with a given pad value from 0 to 0xFF. Pad the image to a valid size with a given pad value from 0 to 0xFF.
.Nm .Nm
@@ -55,14 +63,11 @@ will automatically pick a size from 32KiB, 64KiB, 128KiB, ..., 8192KiB and
give a warning thereafter. give a warning thereafter.
The cartridge size byte ([0x148]) will be changed to reflect this new size. The cartridge size byte ([0x148]) will be changed to reflect this new size.
.\" ITEM .\" ITEM
.It Fl q
Enable quiet mode, suppressing all text except errors.
.\" ITEM
.It Fl r Ar ram_size .It Fl r Ar ram_size
Set the RAM size, [0x149], to a given value from 0 to 0xFF. Set the RAM size, [0x149], to a given value from 0 to 0xFF.
.\" ITEM .\" ITEM
.It Fl s .It Fl s
Set the SGB flag: [0x146] = 3, and set the old licensee code: [0x14B] = 0x33. Set the SGB flag: [0x146] = 3.
.\" ITEM .\" ITEM
.It Fl t Ar title .It Fl t Ar title
Set the title string ([0x134\(en0x143]) to a given string, truncated to at Set the title string ([0x134\(en0x143]) to a given string, truncated to at
@@ -97,12 +102,12 @@ The following will make a SGB-enabled, color-enabled game with a title of
(The Game Boy itself does not use the title, but some emulators or ROM managers (The Game Boy itself does not use the title, but some emulators or ROM managers
might.) might.)
.Pp .Pp
.D1 $ rgbfix \-vCs \-p 0 \-t foobar baz.gb .D1 $ rgbfix \-vcs \-p 0 \-t foobar baz.gb
.Pp .Pp
The following will duplicate the header (sans global checksum) of the game The following will duplicate the header (sans global checksum) of the game
"Survival Kids": "Survival Kids":
.Pp .Pp
.D1 $ rgbfix \-Cjsv \-k A4 \-m 0x1B \-p 0xFF \-r 3 \-t SURVIVALKIDAVKE SurvivalKids.gbc .D1 $ rgbfix \-cjsv \-k A4 \-m 0x1B \-p 0xFF \-r 3 \-t SURVIVALKIDAVKE SurvivalKids.gbc
.\" SECTION .\" SECTION
.Sh SEE ALSO .Sh SEE ALSO
.Xr rgbds 7 , .Xr rgbds 7 ,

View File

@@ -15,19 +15,403 @@
*/ */
#include <err.h> #include <err.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void
usage(void)
{
printf("usage: rgbfix [-Ccjsv] [-k licensee_str] [-l licensee_id] "
"[-m mbc_type]\n" " [-n rom_version] [-p pad_value] "
"[-r ram_size] [-t title_str] file.gb\n");
}
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
FILE *rom; FILE *rom;
int ch;
char *ep;
/*
* Open the ROM file
*/
if (argc < 2) if (argc < 2)
errx(1, "Usage: rgbfix romfile"); usage();
if ((rom = fopen(argv[argc - 1], "rb+")) == NULL) if ((rom = fopen(argv[argc - 1], "rb+")) == NULL)
err(1, "Error opening file %s", argv[argc - 1]); err(1, "Error opening file %s", argv[argc - 1]);
/*
* Parse command-line options
*/
/* all flags default to false unless options specify otherwise */
bool validate = false;
bool settitle = 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 *newlicensee = NULL; /* new licensee ID, two ASCII characters */
int licensee = -1; /* old licensee ID */
int cartridge = -1; /* cartridge hardware ID */
int ramsize = -1; /* RAM size ID */
int version = -1; /* mask ROM version number */
int padvalue = -1; /* to pad the rom with if it changes size */
while ((ch = getopt(argc, argv, "Ccjk:l:m:n:p:sr:t:v")) != -1) {
switch (ch) {
case 'C':
coloronly = true;
/* FALLTHROUGH */
case 'c':
colorcompatible = true;
break;
case 'j':
nonjapan = true;
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;
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");
break;
case 'm':
setcartridge = true;
cartridge = strtoul(optarg, &ep, 0);
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");
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");
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");
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");
break;
case 's':
super = 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;
break;
case 'v':
validate = true;
break;
default:
usage();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
/*
* Write changes to ROM
*/
if (validate) {
/*
* Offset 0x1040x133: 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 0x14D0x14F, They must
* also be correct for the game to boot, so we fix them
* as well when the -v flag is set.
*/
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
};
fseek(rom, 0x104, SEEK_SET);
fwrite(ninlogo, 1, 48, rom);
}
if (settitle) {
/*
* Offset 0x1340x143: 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.
*/
fseek(rom, 0x134, SEEK_SET);
fwrite(title, 1, strlen(title) + 1, rom);
while (ftell(rom) < 0x143)
fputc(0, rom);
}
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 0x1340x143. The sixteenth
* character of the title overlaps with this flag, so they
* may conflict.
*/
uint8_t byte;
fseek(rom, 0x143, SEEK_SET);
byte = fgetc(rom);
byte |= 1 << 7;
if (coloronly)
byte |= 1 << 6;
if (byte & 0x3F)
warnx("Color flag conflicts with game title");
fseek(rom, 0x143, SEEK_SET);
fputc(byte, rom);
}
if (setnewlicensee) {
/*
* Offset 0x1440x145: 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.
*/
fseek(rom, 0x144, SEEK_SET);
fwrite(newlicensee, 1, 2, rom);
}
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'");
fseek(rom, 0x146, SEEK_SET);
fputc(3, rom);
}
if (setcartridge) {
/*
* Offset 0x147: Cartridge Type
* Identifies whether the ROM uses a memory bank controller,
* external RAM, timer, rumble, or battery.
*/
fseek(rom, 0x147, SEEK_SET);
fputc(cartridge, rom);
}
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. */
int romsize, newsize, headbyte;
fseek(rom, 0, SEEK_END);
romsize = ftell(rom);
newsize = 0x8000;
headbyte = 0;
while (romsize > newsize) {
newsize <<= 1;
headbyte++;
}
while (newsize != ftell(rom)) /* ROM needs resizing */
fputc(padvalue, rom);
if (newsize > 0x800000) /* ROM is bigger than 8MiB */
warnx("ROM size is bigger than 8MiB");
fseek(rom, 0x148, SEEK_SET);
fputc(headbyte, rom);
}
if (setramsize) {
/*
* Offset 0x149: RAM Size
*/
fseek(rom, 0x149, SEEK_SET);
fputc(ramsize, rom);
}
if (nonjapan) {
/*
* Offset 0x14A: Non-Japanese Region Flag
*/
fseek(rom, 0x14A, SEEK_SET);
fputc(1, rom);
}
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 0x1440x145.
*/
fseek(rom, 0x14B, SEEK_SET);
fputc(licensee, rom);
}
if (setversion) {
/*
* Offset 0x14C: Mask ROM Version Number
* Which version of the ROM this is.
*/
fseek(rom, 0x14C, SEEK_SET);
fputc(version, rom);
}
if (validate) {
/*
* Offset 0x14D: Header Checksum
*/
uint8_t headcksum;
headcksum = 0;
fseek(rom, 0x134, SEEK_SET);
for (int i = 0; i < (0x14D - 0x134); ++i)
headcksum = headcksum - fgetc(rom) - 1;
fseek(rom, 0x14D, SEEK_SET);
fputc(headcksum, rom);
/*
* Offset 0x14E0x14F: Global Checksum
*/
uint16_t globalcksum;
globalcksum = 0;
rewind(rom);
for (int i = 0; i < 0x14E; ++i)
globalcksum += fgetc(rom);
int byte;
fseek(rom, 0x150, SEEK_SET);
while ((byte = fgetc(rom)) != EOF)
globalcksum += byte;
fseek(rom, 0x14E, SEEK_SET);
fputc(globalcksum >> 8, rom);
fputc(globalcksum & 0xFF, rom);
}
fclose(rom); fclose(rom);
return 0; return 0;