gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 4a714bec8c987635e09693f7e25b56365531d76f parent a2aa923973cf26d1d1ef9a40e480f2d91ff5cc44 Author: Michel Iwaniec <46843052+michel-iwaniec@users.noreply.github.com> Date: Fri, 21 Mar 2025 00:00:13 +0000 NES: Move .nes header functionality out of makebin (#750) NES: Move .nes header functionality out of makebin - Move all .nes header related functionality into a new utility gbdk-support/makenes - Update LCC to use makenes for .postproc member of build process, with mapper30 header parameters hard-coded for now - Update makefiles to build makenes - Add Makefile commands to generate output for the toolchain settings doc page - Add documentation section for makenes in 06_toolchain.md Diffstat:
| M | Makefile | 10 | ++++++++++ |
| M | docs/pages/06_toolchain.md | 5 | +++++ |
| M | gbdk-support/lcc/gb.c | 3 | ++- |
| M | gbdk-support/lcc/targets.c | 2 | +- |
| M | gbdk-support/makebin/makebin.c | 48 | ++---------------------------------------------- |
| A | gbdk-support/makenes/Makefile | 31 | +++++++++++++++++++++++++++++++ |
| A | gbdk-support/makenes/makenes.c | 205 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 256 insertions(+), 48 deletions(-)
diff --git a/Makefile b/Makefile @@ -149,6 +149,8 @@ endif @$(MAKE) -C $(GBDKSUPPORTDIR)/gbcompress TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory @echo Building makecom @$(MAKE) -C $(GBDKSUPPORTDIR)/makecom TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory + @echo Building makenes + @$(MAKE) -C $(GBDKSUPPORTDIR)/makenes TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory @echo Building makebin @$(MAKE) -C $(GBDKSUPPORTDIR)/makebin TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory @echo Building png2hicolorgb @@ -182,6 +184,8 @@ gbdk-support-install: gbdk-support-build $(BUILDDIR)/bin @echo Installing gbcompress @cp $(GBDKSUPPORTDIR)/gbcompress/gbcompress$(EXEEXTENSION) $(BUILDDIR)/bin/gbcompress$(EXEEXTENSION) @$(TARGETSTRIP) $(BUILDDIR)/bin/gbcompress$(EXEEXTENSION) + @echo Installing makenes + @cp $(GBDKSUPPORTDIR)/makenes/makenes$(EXEEXTENSION) $(BUILDDIR)/bin/makenes$(EXEEXTENSION) @echo Installing makecom @cp $(GBDKSUPPORTDIR)/makecom/makecom$(EXEEXTENSION) $(BUILDDIR)/bin/makecom$(EXEEXTENSION) @$(TARGETSTRIP) $(BUILDDIR)/bin/makecom$(EXEEXTENSION) @@ -470,6 +474,12 @@ ifneq (,$(wildcard $(BUILDDIR)/bin/)) echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); $(BUILDDIR)/bin/makecom -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1 || true echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); +# makenes + echo \@anchor makenes-settings >> $(TOOLCHAIN_DOCS_FILE); + echo \# makenes settings >> $(TOOLCHAIN_DOCS_FILE); + echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); + $(BUILDDIR)/bin/makenes -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1 + echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); # gbcompress echo \@anchor gbcompress-settings >> $(TOOLCHAIN_DOCS_FILE); echo \# gbcompress settings >> $(TOOLCHAIN_DOCS_FILE); diff --git a/docs/pages/06_toolchain.md b/docs/pages/06_toolchain.md @@ -308,6 +308,11 @@ Converts a binary .rom file to .msxdos com format, including splitting the banks - For detailed settings see @ref makecom-settings +@anchor utility_makenes +## makenes +Converts a binary .rom file to a .nes file, prepending a 16-byte header. + +- For an explanation of the interpretation of header bits see https://www.nesdev.org/wiki/INES @anchor utility_png2hicolorgb ## png2hicolorgb diff --git a/gbdk-support/lcc/gb.c b/gbdk-support/lcc/gb.c @@ -72,7 +72,8 @@ static struct { { "mkbin", "%sdccdir%makebin" }, { "crt0dir", "%libdir%%plat%/crt0.o"}, { "libs_include", "-k %libdir%%port%/ -l %port%.lib -k %libdir%%plat%/ -l %plat%.lib"}, - { "mkcom", "%sdccdir%makecom"} + { "mkcom", "%sdccdir%makecom"}, + { "mknes", "%sdccdir%makenes"} }; static char *getTokenVal(const char *key) diff --git a/gbdk-support/lcc/targets.c b/gbdk-support/lcc/targets.c @@ -166,7 +166,7 @@ CLASS classes[] = { .ld = "%ld_6808% -a nes -n -i -j $1 %libs_include% $3 %crt0dir% $2", .ihxcheck = "%ihxcheck% $2 $1", .mkbin = "%mkbin% -N -yo A $1 $2 $3", - .postproc = "", + .postproc = "%mknes% -m 30 -n 0 -b 0 -a 1 $1 $2", .llist0_defaults = llist0_defaults_nes, .llist0_defaults_len= ARRAY_LEN(llist0_defaults_nes), } diff --git a/gbdk-support/makebin/makebin.c b/gbdk-support/makebin/makebin.c @@ -157,16 +157,6 @@ struct sms_opt_s uint8_t nb_ram_banks; /* Number of ram banks (default: 0) */ }; -struct nes_opt_s -{ - BYTE mapper; - BYTE num_prg_banks; - BYTE num_chr_banks; - bool vertical_mirroring; - bool four_screen; - bool battery; -}; - void gb_postproc (BYTE * rom, int size, int *real_size, struct gb_opt_s *o) { @@ -740,30 +730,6 @@ read_ihx (FILE *fin, BYTE **rom, int *size, int *real_size, struct gb_opt_s *o) return 1; } -void write_ines_header(FILE* fout, struct nes_opt_s* nes_opt) -{ - char id_string[] = { 0x4E, 0x45, 0x53, 0x1A }; - BYTE flags6 = ((nes_opt->mapper & 0xF) << 4) | - (nes_opt->four_screen << 3) | - (nes_opt->battery << 1) | - nes_opt->vertical_mirroring; - BYTE flags7 = (nes_opt->mapper & 0xF0); - BYTE flags8 = 0; - BYTE flags9 = 0; - BYTE flags10 = 0; - BYTE padding[5] = { 0, 0, 0, 0, 0, }; - // "NES" + end-of-file - fwrite(&id_string, sizeof(char), 4, fout); - fwrite(&nes_opt->num_prg_banks, sizeof(BYTE), 1, fout); - fwrite(&nes_opt->num_chr_banks, sizeof(BYTE), 1, fout); - fwrite(&flags6, sizeof(BYTE), 1, fout); - fwrite(&flags7, sizeof(BYTE), 1, fout); - fwrite(&flags8, sizeof(BYTE), 1, fout); - fwrite(&flags9, sizeof(BYTE), 1, fout); - fwrite(&flags10, sizeof(BYTE), 1, fout); - fwrite(padding, sizeof(BYTE), 5, fout); -} - int main (int argc, char **argv) { @@ -797,14 +763,6 @@ main (int argc, char **argv) .version=0, .nb_ram_banks=0 }; - struct nes_opt_s nes_opt = { - .mapper = 30, - .num_prg_banks = 8, - .num_chr_banks = 0, - .vertical_mirroring = 0, - .four_screen = 1, - .battery = 0 }; - #if defined(_WIN32) setmode (fileno (stdout), O_BINARY); #endif @@ -980,7 +938,7 @@ main (int argc, char **argv) break; case 'N': - /* generate iNES binary file */ + /* generate binary file for NES, with .ihx file banks rotated to turn first bank into last bank */ gb = 0, sms = 0, nes = 1; break; @@ -1126,9 +1084,7 @@ main (int argc, char **argv) if (nes) { - nes_opt.num_prg_banks = gb_opt.nb_rom_banks; - write_ines_header(fout, &nes_opt); - // .ihx file has fixed bank incorrectly placed as first - we fix this when writing out the .nes file. + // .ihx file has fixed bank incorrectly placed as first - we fix this when writing out the .bin file. // Write the N-1 switchable banks at .nes file start, skipping the first (fixed) bank fwrite (rom + BANK_SIZE, 1, (pack ? real_size : size) - offset - BANK_SIZE, fout); // Write the fixed bank to end of .nes file diff --git a/gbdk-support/makenes/Makefile b/gbdk-support/makenes/Makefile @@ -0,0 +1,31 @@ +# makebin makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -g3 -O0 -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +LDFLAGS = -g3 +OBJ = makenes.o +BIN = makenes + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f *.exe + diff --git a/gbdk-support/makenes/makenes.c b/gbdk-support/makenes/makenes.c @@ -0,0 +1,205 @@ +/* + makenes - Prepend an iNES header to a binary ROM image + + Copyright (c) 2025 Michel Iwaniec + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> + +#if defined(_WIN32) +#include <fcntl.h> +#include <io.h> +#else +#include <unistd.h> +#endif + +#define BANK_SIZE 16384 +uint8_t bank_data[BANK_SIZE]; // Static work ram for copying banks from input to output file + +void usage(void) +{ + fprintf (stderr, + "makenes: Prepend an iNES header to a binary ROM image file.\n" + "Usage: makenes [options] [<in_file> [<out_file>]]\n" + "Options:\n" + " -m Mapper number (default: 0)\n" + " -n Nametable arrangement:\n" + " 0: vertical arrangement / horizontal mirroring\n" + " 1: horizontal arrangement / vertical mirroring\n" + " -b Battery bit set (default: 0)\n", + " -a Alternative nametable layout (default: 0)\n" + + "Arguments:\n" + " <in_file> optional binary input file, '-' means stdin. (default: stdin)\n" + " <out_file> optional .nes output file, '-' means stdout. (default: stdout)\n"); +} + +struct nes_opt_s +{ + uint8_t mapper; + uint8_t num_prg_banks; + uint8_t num_chr_banks; + bool nametable_layout; + bool alternative_nametables; + bool battery; +}; + +void write_ines_header(FILE* fout, struct nes_opt_s* nes_opt) +{ + char id_string[] = { 0x4E, 0x45, 0x53, 0x1A }; + uint8_t flags6 = ((nes_opt->mapper & 0xF) << 4) | + (nes_opt->alternative_nametables << 3) | + (nes_opt->battery << 1) | + (nes_opt->nametable_layout << 0); + uint8_t flags7 = (nes_opt->mapper & 0xF0); + uint8_t flags8 = 0; + uint8_t flags9 = 0; + uint8_t flags10 = 0; + uint8_t padding[5] = { 0, 0, 0, 0, 0, }; + // "NES" + end-of-file + fwrite(&id_string, sizeof(char), 4, fout); + fwrite(&nes_opt->num_prg_banks, sizeof(uint8_t), 1, fout); + fwrite(&nes_opt->num_chr_banks, sizeof(uint8_t), 1, fout); + fwrite(&flags6, sizeof(uint8_t), 1, fout); + fwrite(&flags7, sizeof(uint8_t), 1, fout); + fwrite(&flags8, sizeof(uint8_t), 1, fout); + fwrite(&flags9, sizeof(uint8_t), 1, fout); + fwrite(&flags10, sizeof(uint8_t), 1, fout); + fwrite(padding, sizeof(uint8_t), 5, fout); +} + +void parse_cmd_line(int argc, const char** argv, struct nes_opt_s* nes_opt, const char** input_filename, const char** output_filename) +{ + int i = 1; + while(i < argc) + { + if(argv[i][0] == '-') + { + switch(argv[i][1]) + { + case 'm': + nes_opt->mapper = strtoul(argv[i+1], NULL, 0); + break; + case 'n': + nes_opt->nametable_layout = strtoul(argv[i+1], NULL, 0); + break; + case 'b': + nes_opt->battery = strtoul(argv[i+1], NULL, 0); + break; + case 'a': + nes_opt->alternative_nametables = strtoul(argv[i+1], NULL, 0); + break; + case 'h': + usage(); + exit(0); + default: + usage(); + exit(1); + } + i++; + } + else + { + if(*input_filename == NULL) + { + *input_filename = argv[i]; + } + else if(*output_filename == NULL) + { + *output_filename = argv[i]; + } + else + { + usage(); + exit(1); + } + } + i++; + } +} + +int main (int argc, char** argv) +{ + int i = 0; + size_t input_bin_size; + uint8_t num_banks; + FILE* f_input_bin; + FILE* f_output_nes; + char* input_filename = NULL; + char* output_filename = NULL; + struct nes_opt_s nes_opt = { + .mapper = 0, + .num_prg_banks = 0, + .num_chr_banks = 0, + .nametable_layout = 0, + .alternative_nametables = 0, + .battery = 0 + }; +#if defined(_WIN32) + setmode (fileno (stdout), O_BINARY); +#endif + parse_cmd_line(argc, argv, &nes_opt, &input_filename, &output_filename); + // Open input .bin file + f_input_bin = stdin; + if(input_filename) + { + f_input_bin = fopen(input_filename, "rb"); + if(f_input_bin == NULL) + { + fprintf(stderr, "Error: Failed to open input file `%s`\n", input_filename); + } + } + // Get file size and number of banks + fseek(f_input_bin, 0, SEEK_END); + input_bin_size = ftell(f_input_bin); + fseek(f_input_bin, 0, SEEK_SET); + if(input_bin_size % BANK_SIZE != 0) + { + fprintf(stderr, "Error: input file size not a multiple of bank size %d\n", BANK_SIZE); + exit(1); + } + nes_opt.num_prg_banks = (uint8_t)(input_bin_size / BANK_SIZE); + // Open output .nes file + f_output_nes = stdout; + if(output_filename) + { + f_output_nes = fopen(output_filename, "wb"); + if(f_output_nes == NULL) + { + fprintf(stderr, "Error: Failed to open output file `%s`\n", output_filename); + exit(1); + } + } + // Write iNES header to output file + write_ines_header(f_output_nes, &nes_opt); + // Copy input file to output file, one 16kB bank at a time + for(uint8_t i = 0; i < nes_opt.num_prg_banks; i++) + { + size_t num_bytes = fread(bank_data, 1, BANK_SIZE, f_input_bin); + fwrite(bank_data, 1, BANK_SIZE, f_output_nes); + } + fclose(f_input_bin); + fclose(f_output_nes); + return 0; +}
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.