gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 29809c3a05cb1f9c3996218aec9d735bdfae8937 parent 3a7d09e19dcc152c387c16442d3fc2241520ae5d Author: Toxa <56631470+untoxa@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:09:25 +0300 Merge pull request #604 from bbbbbr/romusage/initial tools: add romusage Diffstat:
| M | .gitignore | 1 | + |
| M | Makefile | 15 | ++++++++++++++- |
| M | docs/pages/02_links_and_tools.md | 2 | +- |
| M | docs/pages/05_banking_mbcs.md | 4 | ++-- |
| M | docs/pages/20_toolchain_settings.md | 59 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| A | gbdk-support/romusage/Makefile | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/bank_list.h | 3 | +++ |
| A | gbdk-support/romusage/src/bank_templates.c | 136 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/bank_templates.h | 13 | +++++++++++++ |
| A | gbdk-support/romusage/src/banks.c | 790 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks.h | 114 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks_color.c | 179 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks_color.h | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks_print.c | 345 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks_print.h | 15 | +++++++++++++++ |
| A | gbdk-support/romusage/src/banks_summarized.c | 209 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/banks_summarized.h | 14 | ++++++++++++++ |
| A | gbdk-support/romusage/src/cdb_file.c | 294 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/cdb_file.h | 23 | +++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/common.c | 258 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/common.h | 103 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/ihx_file.c | 309 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/ihx_file.h | 5 | +++++ |
| A | gbdk-support/romusage/src/list.c | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/list.h | 21 | +++++++++++++++++++++ |
| A | gbdk-support/romusage/src/logging.c | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/logging.h | 24 | ++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/map_file.c | 148 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/map_file.h | 8 | ++++++++ |
| A | gbdk-support/romusage/src/noi_file.c | 178 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/noi_file.h | 23 | +++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/rom_file.c | 171 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-support/romusage/src/rom_file.h | 5 | +++++ |
| A | gbdk-support/romusage/src/romusage.c | 327 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | licenses/LICENSE_romusage | 24 | ++++++++++++++++++++++++ |
35 files changed, 4067 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -65,6 +65,7 @@ gbdk-support/png2asset/png2asset gbdk-support/makebin/makebin gbdk-support/makecom/makecom gbdk-support/png2hicolorgb/png2hicolorgb +gbdk-support/romusage/romusage as/bin link/bin maccer diff --git a/Makefile b/Makefile @@ -153,6 +153,8 @@ endif @$(MAKE) -C $(GBDKSUPPORTDIR)/makebin TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory @echo Building png2hicolorgb @$(MAKE) -C $(GBDKSUPPORTDIR)/png2hicolorgb TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory + @echo Building romusage + @$(MAKE) -C $(GBDKSUPPORTDIR)/romusage TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory @echo gbdk-support-install: gbdk-support-build $(BUILDDIR)/bin @@ -189,6 +191,9 @@ gbdk-support-install: gbdk-support-build $(BUILDDIR)/bin @echo Installing png2hicolorgb @cp $(GBDKSUPPORTDIR)/png2hicolorgb/png2hicolorgb$(EXEEXTENSION) $(BUILDDIR)/bin/png2hicolorgb$(EXEEXTENSION) @$(TARGETSTRIP) $(BUILDDIR)/bin/png2hicolorgb$(EXEEXTENSION) + @echo Installing romusage + @cp $(GBDKSUPPORTDIR)/romusage/romusage$(EXEEXTENSION) $(BUILDDIR)/bin/romusage$(EXEEXTENSION) + @$(TARGETSTRIP) $(BUILDDIR)/bin/romusage$(EXEEXTENSION) @echo gbdk-support-clean: @@ -209,6 +214,8 @@ gbdk-support-clean: @$(MAKE) -C $(GBDKSUPPORTDIR)/makebin clean @echo Cleaning png2hicolorgb @$(MAKE) -C $(GBDKSUPPORTDIR)/png2hicolorgb clean + @echo Cleaning romusage + @$(MAKE) -C $(GBDKSUPPORTDIR)/romusage clean @echo # Rules for gbdk-lib @@ -473,7 +480,7 @@ ifneq (,$(wildcard $(BUILDDIR)/bin/)) echo \@anchor png2asset-settings >> $(TOOLCHAIN_DOCS_FILE); echo \# png2asset settings >> $(TOOLCHAIN_DOCS_FILE); echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); - $(BUILDDIR)/bin/png2asset >> $(TOOLCHAIN_DOCS_FILE) 2>&1 + $(BUILDDIR)/bin/png2asset >> $(TOOLCHAIN_DOCS_FILE) 2>&1 || true echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE) # png2hicolorgb echo \@anchor png2hicolorgb-settings >> $(TOOLCHAIN_DOCS_FILE); @@ -481,6 +488,12 @@ ifneq (,$(wildcard $(BUILDDIR)/bin/)) echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); $(BUILDDIR)/bin/png2hicolorgb -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1 echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE) +# romusage + echo \@anchor romusage-settings >> $(TOOLCHAIN_DOCS_FILE); + echo \# romusage settings >> $(TOOLCHAIN_DOCS_FILE); + echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE); + $(BUILDDIR)/bin/romusage -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1 + echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE) endif diff --git a/docs/pages/02_links_and_tools.md b/docs/pages/02_links_and_tools.md @@ -142,7 +142,7 @@ This is a brief list of useful tools and information. It is not meant to be comp - @anchor romusage __romusage__ Calculate used and free space in banks (ROM/RAM) and warn about errors such as bank overflows. - https://github.com/bbbbbr/romusage + See @ref romusage-settings - @anchor bgb_symbol_conversion __noi file to sym conversion for bgb__ diff --git a/docs/pages/05_banking_mbcs.md b/docs/pages/05_banking_mbcs.md @@ -321,12 +321,12 @@ A _bank overflow_ during compile/link time (in @ref makebin) is when more code a See the @ref faq_bank_overflow_errors "FAQ entry about bank overflow errors". -The current toolchain can only detect and warn (using @ref ihxcheck) when one bank overflows into another bank that has data at its start. It cannot warn if a bank overflows into an empty one. For more complete detection, you can use the third-party @ref romusage tool. +The current toolchain can only detect and warn (using @ref ihxcheck) when one bank overflows into another bank that has data at its start. It cannot warn if a bank overflows into an empty one. For more complete detection, you can use the @ref romusage tool. # Bank space usage -In order to see how much space is used or remains available in a bank, you can use the third-party @ref romusage tool. +In order to see how much space is used or remains available in a bank you can use the @ref romusage tool. ## Other important notes diff --git a/docs/pages/20_toolchain_settings.md b/docs/pages/20_toolchain_settings.md @@ -579,7 +579,7 @@ usage: png2asset <file>.png [options] -keep_palette_order use png palette -repair_indexed_pal try to repair indexed tile palettes (implies "-keep_palette_order") -noflip disable tile flip --map Export as map (tileset + bg) +-map Export as map (tileset + bg) instead of default metasprite output -use_map_attributes Use CGB BG Map attributes -use_nes_attributes Use NES BG Map attributes -use_nes_colors Convert RGB color values to NES PPU colors @@ -597,6 +597,7 @@ usage: png2asset <file>.png [options] -no_palettes do not export palette data -bin export to binary format -transposed export transposed (column-by-column instead of row-by-row) +decoder error empty input buffer given to decoder. Maybe caused by non-existing file? ``` @anchor png2hicolorgb-settings # png2hicolorgb settings @@ -637,3 +638,59 @@ Historical credits and info: Quantiser Conversion : Glen Cook ``` +@anchor romusage-settings +# romusage settings +``` +romusage input_file.[map|noi|ihx|cdb|.gb[c]|.pocket|.duck|.gg|.sms] [options] +version 1.2.8, by bbbbbr + +Options +-h : Show this help +-p:SMS_GG : Set platform to GBDK SMS/Game Gear (changes memory map templates) + +-a : Show Areas in each Bank. Optional sort by, address:"-aA" or size:"-aS" +-g : Show a small usage graph per bank (-gA for ascii style) +-G : Show a large usage graph per bank (-GA for ascii style) +-B : Brief (summarized) output for banked regions. Auto scales max bank + shows [Region]_[Max Used Bank] / [auto-sized Max Bank Num] +-F : Force Max ROM and SRAM bank num for -B. (0 based) -F:ROM:SRAM (ex: -F:255:15) + +-m : Manually specify an Area -m:NAME:HEXADDR:HEXLENGTH +-e : Manually specify an Area that should not overlap -e:NAME:HEXADDR:HEXLENGTH +-E : All areas are exclusive (except HEADERs), warn for any overlaps +-q : Quiet, no output except warnings and errors +-Q : Suppress output of warnings and errors +-R : Return error code for Area warnings and errors + +-sR : [Rainbow] Color output (-sRe for Row Ends, -sRd for Center Dimmed, -sRp % based) +-sP : Custom Color Palette. Colon separated entries are decimal VT100 color codes + -sP:DEFAULT:ROM:VRAM:SRAM:WRAM:HRAM (section based color only) +-sC : Show Compact Output, hide non-essential columns +-sH : Show HEADER Areas (normally hidden) +-smROM : Show Merged ROM_0 and ROM_1 output (i.e. bare 32K ROM) +-smWRAM : Show Merged WRAM_0 and WRAM_1 output (i.e DMG/MGB not CGB) + -sm* compatible with banked ROM_x or WRAM_x when used with -B +-sJ : Show JSON output. Some options not applicable. When used, -Q recommended +-nB : Hide warning banner (for .cdb output) +-nA : Hide areas (shown by default in .cdb output) +-z : Hide areas smaller than SIZE -z:DECSIZE + +Use: Read a .map, .noi, .cdb or .ihx file to display area sizes +Example 1: "romusage build/MyProject.map" +Example 2: "romusage build/MyProject.noi -a -e:STACK:DEFF:100 -e:SHADOW_OAM:C000:A0" +Example 3: "romusage build/MyProject.ihx -g" +Example 4: "romusage build/MyProject.map -q -R" +Example 5: "romusage build/MyProject.noi -sR -sP:90:32:90:35:33:36" +Example 6: "romusage build/MyProject.map -sRp -g -B -F:255:15 -smROM -smWRAM" + +Notes: + * GBDK / RGBDS map file format detection is automatic. + * Estimates are as close as possible, but may not be complete. + Unless specified with -m/-e they *do not* factor regions lacking + complete ranges in the Map/Noi/Ihx file, for example Shadow OAM and Stack. + * IHX files can only detect overlaps, not detect memory region overflows. + * CDB file output ONLY counts (most) data from C sources. + It cannot count functions and data from ASM and LIBs, + so bank totals may be incorrect/missing. + * GB/GBC/ROM files are just guessing, no promises. +``` diff --git a/gbdk-support/romusage/Makefile b/gbdk-support/romusage/Makefile @@ -0,0 +1,47 @@ +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT + LDFLAGS = -s -static +else + BUILD_OS := $(shell uname -s) + LDFLAGS = -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 = -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +CFLAGS += -Isrc +OBJ = src/banks.c \ +src/banks_print.c \ +src/bank_templates.c \ +src/common.c \ +src/list.c \ +src/map_file.c \ +src/rom_file.c \ +src/banks_color.c \ +src/banks_summarized.c \ +src/cdb_file.c \ +src/ihx_file.c \ +src/logging.c \ +src/noi_file.c \ +src/romusage.c + + +BIN = romusage + +all: $(BIN) + +$(BIN): $(OBJ) + $(CC) -o $(BIN) $^ $(CFLAGS) $(LDFLAGS) + +clean: + rm -f *.o $(BIN) *~ src/*.o + rm -f *.exe + diff --git a/gbdk-support/romusage/src/bank_list.h b/gbdk-support/romusage/src/bank_list.h @@ -0,0 +1,2 @@ +#include "banks.h" +void banklist_printall(bank_item []); + diff --git a/gbdk-support/romusage/src/bank_templates.c b/gbdk-support/romusage/src/bank_templates.c @@ -0,0 +1,135 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "banks.h" +#include "bank_templates.h" + +// Bank info from pandocs +// 0000-3FFF 16KB ROM Bank 00 (in cartridge, fixed at bank 00) +// 4000-7FFF 16KB ROM Bank 01..NN (in cartridge, switchable bank number) +// 8000-9FFF 8KB Video RAM (VRAM) (switchable bank 0-1 in CGB Mode) +// A000-BFFF 8KB External RAM (in cartridge, switchable bank, if any) +// C000-CFFF 4KB Work RAM Bank 0 (WRAM) +// D000-DFFF 4KB Work RAM Bank 1 (WRAM) (switchable bank 1-7 in CGB Mode) +// E000-FDFF Same as C000-DDFF (ECHO) (typically not used) + + +// // Previous unified bank declaration +// const bank_item bank_templates_default[] = { +// {"ROM_0", 0x0000, 0x3FFF, BANKED_NO, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_0}, +// {"ROM_", 0x4000, 0x7FFF, BANKED_YES, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_1}, +// {"VRAM_", 0x8000, 0x9FFF, BANKED_YES, 0x9FFF, 0,0,0, BANK_MEM_TYPE_VRAM, BANK_STARTNUM_0}, +// {"SRAM_", 0xA000, 0xBFFF, BANKED_YES, 0xBFFF, 0,0,0, BANK_MEM_TYPE_SRAM, BANK_STARTNUM_0}, +// {"WRAM_LO", 0xC000, 0xCFFF, BANKED_NO, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_0}, +// {"WRAM_HI_",0xD000, 0xDFFF, BANKED_YES, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_1}, +// {"HRAM", 0xFF80, 0xFFFE, BANKED_NO, 0xFFFE, 0,0,0, BANK_MEM_TYPE_HRAM, BANK_STARTNUM_0}, +// }; + + +// ===== Game Boy ===== + +// ROM +const bank_item ROM_0 = {"ROM_0", 0x0000, 0x3FFF, BANKED_NO, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_0, BANK_MERGED_NO}; +const bank_item ROM_X_banked = {"ROM_", 0x4000, 0x7FFF, BANKED_YES, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_1, BANK_MERGED_NO}; +// Merged version +const bank_item ROM_nonbanked = {"ROM", 0x0000, 0x7FFF, BANKED_YES, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_0, BANK_MERGED_YES}; + +// VRAM +const bank_item VRAM = {"VRAM_", 0x8000, 0x9FFF, BANKED_YES, 0x9FFF, 0,0,0, BANK_MEM_TYPE_VRAM, BANK_STARTNUM_0, BANK_MERGED_NO}; + +// SRAM +const bank_item SRAM = {"SRAM_", 0xA000, 0xBFFF, BANKED_YES, 0xBFFF, 0,0,0, BANK_MEM_TYPE_SRAM, BANK_STARTNUM_0, BANK_MERGED_NO}; + +// WRAM +const bank_item WRAM_0 = {"WRAM_LO", 0xC000, 0xCFFF, BANKED_NO, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_0, BANK_MERGED_NO}; +const bank_item WRAM_X_banked = {"WRAM_HI_",0xD000, 0xDFFF, BANKED_YES, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_1, BANK_MERGED_NO}; +// Merged version +const bank_item WRAM_nonbanked = {"WRAM", 0xC000, 0xDFFF, BANKED_YES, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_0, BANK_MERGED_YES}; + +// HRAM +const bank_item HRAM = {"HRAM", 0xFF80, 0xFFFE, BANKED_NO, 0xFFFE, 0,0,0, BANK_MEM_TYPE_HRAM, BANK_STARTNUM_0, BANK_MERGED_NO}; + + +// ===== Game Gear ===== + +// https://www.smspower.org/Development/MemoryMap + +// _CODE_<N> is at base address 0x4000 (code and assets) +// _LIT_<N> is at base address 0x8000 (assets) +// _DATA_N is also at base address 0x8000 (RAM) + +const bank_item smsgg_ROM_0 = {"ROM_0", 0x0000, 0x3FFF, BANKED_NO, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_0, BANK_MERGED_NO}; +const bank_item smsgg_ROM_X_banked = {"ROM_", 0x4000, 0x7FFF, BANKED_YES, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_1, BANK_MERGED_NO}; +// Merged version +const bank_item smsgg_ROM_nonbanked = {"ROM", 0x0000, 0x7FFF, BANKED_YES, 0x7FFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_0, BANK_MERGED_YES}; + +const bank_item smsgg_LIT_X_banked = {"LIT_", 0x8000, 0xBFFF, BANKED_YES, 0xBFFF, 0,0,0, BANK_MEM_TYPE_ROM, BANK_STARTNUM_1, BANK_MERGED_NO}; +// Data can also be in the 0x8000 region.. requires some special handling in banks_check() +const bank_item smsgg_DATA_X_banked = {"DATA_", 0x8000, 0xBFFF, BANKED_YES, 0xBFFF, 0,0,0, BANK_MEM_TYPE_SRAM, BANK_STARTNUM_1, BANK_MERGED_NO}; +const bank_item smsgg_RAM_nonbanked = {"RAM", 0xC000, 0xDFFF, BANKED_YES, 0xDFFF, 0,0,0, BANK_MEM_TYPE_WRAM, BANK_STARTNUM_0, BANK_MERGED_YES}; + + +static int bank_template_add(int idx, bank_item * p_bank_templates, const bank_item * p_bank) { + + if (idx >= BANK_TEMPLATES_MAX) { + log_error("Error: exceeded max bank template\n"); + exit(EXIT_FAILURE); + } + + p_bank_templates[idx++] = *p_bank; + return idx; +} + + +// TODO: FIXME: With -B and -cWRAM and -cROM +// - Calc total size is wrong... maybe 2x? +// - Graph renders wrong (maybe due to total size wrong) + +int bank_templates_load(bank_item * p_bank_templates) { + + int idx = 0; + + if (get_option_platform() == OPT_PLAT_SMS_GG_GBDK) { + if (option_merged_banks & OPT_MERGED_BANKS_ROM) { + idx = bank_template_add(idx, p_bank_templates, &smsgg_ROM_nonbanked); + } else { + idx = bank_template_add(idx, p_bank_templates, &smsgg_ROM_0); + idx = bank_template_add(idx, p_bank_templates, &smsgg_ROM_X_banked); + } + idx = bank_template_add(idx, p_bank_templates, &smsgg_LIT_X_banked); + idx = bank_template_add(idx, p_bank_templates, &smsgg_DATA_X_banked); + idx = bank_template_add(idx, p_bank_templates, &smsgg_RAM_nonbanked); + + } + else { // implied: if (get_option_platform() == OPT_PLAT_GAMEBOY) { + + if (option_merged_banks & OPT_MERGED_BANKS_ROM) { + idx = bank_template_add(idx, p_bank_templates, &ROM_nonbanked); + } else { + idx = bank_template_add(idx, p_bank_templates, &ROM_0); + idx = bank_template_add(idx, p_bank_templates, &ROM_X_banked); + } + + idx = bank_template_add(idx, p_bank_templates, &VRAM); + idx = bank_template_add(idx, p_bank_templates, &SRAM); + + if (option_merged_banks & OPT_MERGED_BANKS_WRAM) { + idx = bank_template_add(idx, p_bank_templates, &WRAM_nonbanked); + } else { + idx = bank_template_add(idx, p_bank_templates, &WRAM_0); + idx = bank_template_add(idx, p_bank_templates, &WRAM_X_banked); + } + + idx = bank_template_add(idx, p_bank_templates, &HRAM); + } + + return idx; +} + diff --git a/gbdk-support/romusage/src/bank_templates.h b/gbdk-support/romusage/src/bank_templates.h @@ -0,0 +1,12 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2023 + +#ifndef _BANK_TEMPLATES_H +#define _BANK_TEMPLATES_H + +#define BANK_TEMPLATES_MAX 20 + +int bank_templates_load(bank_item *); + +#endif // _BANK_TEMPLATES_H + diff --git a/gbdk-support/romusage/src/banks.c b/gbdk-support/romusage/src/banks.c @@ -0,0 +1,790 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "list.h" +#include "banks.h" +#include "bank_templates.h" +#include "banks_print.h" +#include "banks_summarized.h" + + +static int area_item_compare(const void* a, const void* b); +static int bank_item_compare(const void* a, const void* b); +static bool banks_check_larger_than_32k(void); +static void areas_check_rom0_overflow(void); + +// Not ready for use until a call to banks_init_templates() +bank_item bank_templates[BANK_TEMPLATES_MAX]; +int bank_templates_count; + +list_type bank_list; +list_type bank_list_summarized; + +#define AREA_MANUAL_QUEUE_SZ 20 +int area_manual_queue_count = 0; +area_item areas_manual_queue[AREA_MANUAL_QUEUE_SZ]; + +// Initialize the main banklist +void banks_init(void) { + + list_init(&bank_list, sizeof(bank_item)); + list_init(&bank_list_summarized, sizeof(bank_item)); +} + + +// Free all banks and their areas +void banks_cleanup(void) { + + bank_item * banks = (bank_item *)bank_list.p_array; + int c; + + for (c = 0; c < bank_list.count; c++) { + list_cleanup(&(banks[c].area_list)); + } + list_cleanup(&bank_list); +} + + +// Load templates used for assigning areas to banks +void banks_init_templates(void) { + bank_templates_count = bank_templates_load(bank_templates); +} + + +// Returns size of overlap between two address ranges, +// if zero then no overlap +static uint32_t addrs_get_overlap(uint32_t a_start, uint32_t a_end, uint32_t b_start, uint32_t b_end) { + + uint32_t size_used; + + // Check whether the address range *doesn't* overlap + if ((b_start > a_end) || (b_end < a_start)) { + size_used = 0; // no overlap, size = 0 + } else { + size_used = min(b_end, a_end) - max(b_start, a_start) + 1; // Calculate minimum overlap + } + return size_used; + +} + + +// Fixes up the missing bank number virtual addressing mask bits +// for when ROM0 overflows into ROM1. Ex: 0x00004000 -> 0x00014000 +// +// Note: When called from banks_check() the address will be +// clipped to the start of the matched bank template +static uint32_t addr_fixup_ROM0_overflow_bank_num(uint32_t addr) { + + // If it's in the upper bank range yet has + // a bank number of zero then it needs fixing + if ((WITHOUT_BANK(addr) >= BANK_ADDR_ROM_UPPER_ST) && + (WITHOUT_BANK(addr) <= BANK_ADDR_ROM_UPPER_END) && + (BANK_GET_NUM(addr) == BANK_NUM_ROM0)) { + // OR in the virtual addressing equivalent of bank ROM1 + addr |= BANK_NUM_ROM1_VADDR; + } + + return addr; +} + + +// Clips an address range to be within a bank address range +static void area_clip_to_range(uint32_t start, uint32_t end, area_item * p_area) { + // Clip address range to bank range + p_area->start = max(p_area->start, start); + p_area->end = min(p_area->end, end); + // Trim to zero length if end is before start + if (p_area->end < p_area->start) p_area->end = p_area->start - 1; +} + + +static void area_check_region_overflow(area_item area) { + + int c; + + // Find bank template the area starts in and check to see + // whether the area extends past the end of it's memory region. + // + // Non-banked areas with banks above them have the upper bound + // set to the end of the bank above them. + for(c = 0; c < bank_templates_count; c++) { + + // Warn about overflow in any ROM bank GBZ80 areas that cross past the (relative) end of their region + if ((WITHOUT_BANK(area.start) >= bank_templates[c].start) && + (WITHOUT_BANK(area.start) <= bank_templates[c].end) && + (area.end > (BANK_ONLY(area.start) + bank_templates[c].overflow_end))) { + log_warning("* WARNING: Area %-8s at %5x -> %5x extends past end of memory region at %5x (Overflow by %d bytes)\n", + area.name, + // BANK_GET_NUM(area.start), + area.start, area.end, + BANK_ONLY(area.start) + bank_templates[c].overflow_end, + area.end - (BANK_ONLY(area.start) + bank_templates[c].overflow_end)); + + if (option_error_on_warning) + set_exit_error(); + } + } +} + + +// Warn if length extends past end of unbanked address space (0xFFFF) +// and will underflow / wrap around +static bool area_check_underflow(area_item area, bool notify) { + + if (area.end > (BANK_ONLY(area.start) + MAX_ADDR_UNBANKED)) { + + if (notify) { + log_warning("* WARNING: Area %-8s at %5x -> %5x extends past end of address space at %5x (Underflow error by %d bytes)\n", + area.name, + area.start, area.end, + BANK_ONLY(area.start) + MAX_ADDR_UNBANKED, + area.end - (BANK_ONLY(area.start) + MAX_ADDR_UNBANKED)); + + if (option_error_on_warning) + set_exit_error(); + } + + return true; + } + else return false; +} + + +// Attempt to flag if non-banked areas have overflowed ROM0 and rom size is > 32k +// Must be called after all areas are processed so that it can check rom size accurately +static void areas_check_rom0_overflow(void) { + + bank_item * banks = (bank_item *)bank_list.p_array; + area_item * areas; + int b, c; + bool has_overflow = false; + + if (banks_check_larger_than_32k() == false) return; + if (get_option_platform() != OPT_PLAT_GAMEBOY) return; + + for (b=0; b < bank_list.count; b++) { + areas = (area_item *)banks[b].area_list.p_array; + + for(c=0;c < banks[b].area_list.count; c++) { + + if (areas[c].end >= BANK_ADDR_ROM_UPPER_ST) { + if ((strcmp(areas[c].name,"_CODE") == 0) || + (strcmp(areas[c].name,"_HOME") == 0) || + (strcmp(areas[c].name,"_INITIALIZER") == 0) || + (strcmp(areas[c].name,"_GSINIT") == 0) || + (strcmp(areas[c].name,"_GSFINAL") == 0)) { + + log_warning("* WARNING: Possible overflow beyond Bank 0 for non-banked area %s (0x%x -> 0x%x). \n", + areas[c].name, areas[c].start, areas[c].end); + has_overflow = true; + } + } + } + } + + if (option_error_on_warning && has_overflow) + set_exit_error(); +} + + +static void area_check_warnings(area_item area, uint32_t size_assigned) { + + // Unassigned warning is mostly redundant with area_check_bank_overflow() + // + // // Warn if there are unassigned bytes left over + // if (size_assigned < RANGE_SIZE(area.start, area.end)) { + // log_warning("\n* Warning: Area %s 0x%x -> 0x%x (%d bytes): %d bytes not assigned to any bank (overflow error)\n", + // area.name, + // area.start, area.end, + // RANGE_SIZE(area.start, area.end), + // RANGE_SIZE(area.start, area.end) - size_assigned); + // } + + area_check_underflow(area, true); + area_check_region_overflow(area); +} + + +static void area_check_warn_overlap(area_item area_a, area_item area_b) { + + uint32_t overlap_size; + + // HEADER areas almost always overlap, ignore them + if ((strstr(area_a.name,"HEADER")) || (strstr(area_b.name,"HEADER"))) + return; + + // Check to see if an there are overlaps with exclusive areas + if (area_a.exclusive || area_b.exclusive) { + overlap_size = addrs_get_overlap(WITHOUT_BANK(area_a.start), WITHOUT_BANK(area_a.end), + WITHOUT_BANK(area_b.start), WITHOUT_BANK(area_b.end)); + if (overlap_size > 0) { + log_warning("\n* WARNING: Areas overlapp by %d bytes: Possible bank overflow.\n" + "%15s 0x%04x -> 0x%04x (%d bytes%s)\n" + "%15s 0x%04x -> 0x%04x (%d bytes%s)\n", + overlap_size, + area_a.name, area_a.start, area_a.end, RANGE_SIZE(area_a.start, area_a.end), + (area_a.exclusive) ? ", EXCLUSIVE" : " ", + area_b.name, area_b.start, area_b.end, RANGE_SIZE(area_b.start, area_b.end), + (area_b.exclusive) ? ", EXCLUSIVE" : " "); + + if (option_error_on_warning) + set_exit_error(); + } + } +} + + +static bool banks_check_larger_than_32k(void) { + + bank_item * banks = (bank_item *)bank_list.p_array; + int c; + + for (c=0; c < bank_list.count; c++) { + if ((banks[c].bank_num > BANK_NUM_ROM1) && + (banks[c].bank_mem_type == BANK_MEM_TYPE_ROM)) { + + return true; + } + } + + return false; +} + + +// Calculate free and used percentages of space in a given bank +int bank_calc_percent_free(bank_item * p_bank) { + + // Round to nearest whole percent instead of truncate (hence: + total / 2) + return (int)(((p_bank->size_total - p_bank->size_used) * 100) + + (p_bank->size_total / 2 )) / p_bank->size_total; +} + +int bank_calc_percent_used(bank_item * p_bank) { + + // Base Used amount on Free space so that total comes out to 100% + return (100 - bank_calc_percent_free(p_bank)); +} + + +// Calculates amount of space used by areas in a bank. +// Attempts to merge overlapping areas to avoid +// counting shared space multiple times. +// +uint32_t bank_areas_calc_used(bank_item * p_bank, uint32_t clip_start, uint32_t clip_end) { + + area_item * areas = (area_item *)p_bank->area_list.p_array; + int c,sub; + uint32_t start, end; + uint32_t size_used; + area_item t_area, sub_area; + + size_used = 0; + + // The calculation requires areas to first be + // sorted ascending by .start addr then by .end addr + qsort (p_bank->area_list.p_array, p_bank->area_list.count, sizeof(area_item), area_item_compare); + + // Iterate over all areas + c = 0; + while (c < p_bank->area_list.count) { + + // Copy area so it can be clipped, then clip to param range + t_area = areas[c]; + area_clip_to_range(clip_start, clip_end, &t_area); // clip to param range + + // // Store start/end of range for current area + start = t_area.start; + end = t_area.end; + + // Iterate over remaining areas and stop when they cease to overlap + sub = c + 1; + while (sub < p_bank->area_list.count) { + + // Copy area so it can be clipped, then clip to param range + sub_area = areas[sub]; + area_clip_to_range(clip_start, clip_end, &sub_area); + + // Check for overlap with next entry + if (addrs_get_overlap(start, end, sub_area.start, sub_area.end)) { + + // Expand overlapped area to new end size + // Just end, start shouldn't be necessary due to expected sorting + if (sub_area.end > end) { + end = sub_area.end; + } + + // Update main loop to next area after current merged, + c = sub; + } + // Increment to next area to check for overlap + sub++; + } + // Move to next area + c++; + + // Store space used by updated range + size_used += RANGE_SIZE(start, end); + // fprintf(stdout," * %d, %d Final Size> 0x%04X -> 0x%04X = %d ((%d))\n",c, sub, start, end, RANGE_SIZE(start, end), size_used); + } + + return size_used; +} + + + +// Add an area to a bank's list of areas +static void bank_add_area(bank_item * p_bank, area_item area) { + + area_item * areas = (area_item *)p_bank->area_list.p_array; + int c; + + // Make sure the area length/size is set + area.length = RANGE_SIZE(area.start, area.end); + + // Check for duplicate entries + // (happens due to paginating in .map file) + for(c=0;c < p_bank->area_list.count; c++) { + // Abort add if it's already present + if (option_suppress_duplicates == true) { + if ((strstr(area.name, areas[c].name)) && + (area.start == areas[c].start) && + (area.end == areas[c].end)) { + return; + } + } + + area_check_warn_overlap(area, areas[c]); + } + + // no match was found, add area + list_additem(&(p_bank->area_list), &area); + p_bank->size_used += area.length; +} + + +// Add/Update a bank with an area entry +static void banklist_addto(bank_item bank_template, area_item area, int bank_num) { + + int c; + bank_item * banks = (bank_item *)bank_list.p_array; + bank_item newbank; + + // Strip bank indicator bits and limit area range to within bank + area.start = area.start_unbanked; + area.end = area.end_unbanked; + area_clip_to_range(bank_template.start, bank_template.end, &area); + + // Check to see if key matches any entries, + for (c=0; c < bank_list.count; c++) { + + // If a match was found, update it + if ((bank_template.start == banks[c].start) && + (bank_num == banks[c].bank_num)) { + + // Append area + bank_add_area(&(banks[c]), area); + return; + } + } + + // No match was found, initialize new bank + + // Copy bank info from template + newbank = bank_template; + + // Update size used, total size and append bank name if needed + newbank.size_used = 0; + newbank.size_total = RANGE_SIZE(bank_template.start, bank_template.end); + newbank.bank_num = bank_num; + + // Don't append bank name for merged banks + if ((bank_template.is_banked == BANKED_YES) && (!bank_template.is_merged_bank)) { + if (snprintf(newbank.name, sizeof(newbank.name), "%s%d", bank_template.name, bank_num) > sizeof(newbank.name)) + log_warning("Warning: truncated bank name to :%s\n", newbank.name); + } + + // Initialize new bank's area list and add the area + list_init(&(newbank.area_list), sizeof(area_item)); + bank_add_area(&newbank, area); + + // Now add the new bank to the main list + list_additem(&bank_list, &newbank); +} + + +// Strip banks from address start and end, set start/end_unbanked +static void area_calc_unbanked_range(area_item * p_area) { + + p_area->start_unbanked = WITHOUT_BANK(p_area->start); + + // * Calculating End relative to start is important for + // not accidentally loosing it's full size. + // * Unbanked End is also capped at 0xFFFF to + // prevent wraparound range size errors + if (area_check_underflow(*p_area, false)) { + // area_check_warnings() will warn about this later + p_area->end_unbanked = MAX_ADDR_UNBANKED; + } else { + p_area->end_unbanked = UNBANKED_END(p_area->start, p_area->end); + } +} + + +// Returns true if the template should be skipped +// +// On GBDK SMS/GG the banked LIT_ and DATA_ areas get mapped into the +// same memory region (only one active at a time) : 0x8000 - 0xBFFF +// +// So skip the template of one type if the area is of the other type +bool banks_sms_gg_checkskip_template(bool sms_gg_is_banked_DATA, bool sms_gg_is_banked_LIT, char * template_name) { + if (sms_gg_is_banked_DATA) { + if (strstr(template_name,"LIT_")) + return true; + } else if (sms_gg_is_banked_LIT) { + if (strstr(template_name,"DATA_")) + return true; + } + return false; +} + +// Check to see if an area overlaps with any of the bank templates. +// If it does then try to create/update a bank entry +// and add/append the area entry +void banks_check(area_item area) { + + int c; + uint32_t size_used; + uint32_t size_assigned = 0; + int bank_num; + + // Set the unbanked address range for comparison + // with (unbanked) bank templates + area_calc_unbanked_range(&area); + + // On GBDK SMS/GG the banked LIT_ and DATA_ areas get mapped into the + // same memory region (only one active at a time) : 0x8000 - 0xBFFF + // TODO: kind of sloppy, could be moved to a function + bool sms_gg_is_banked_DATA = false; + bool sms_gg_is_banked_LIT = false; + if ((get_option_platform() == OPT_PLAT_SMS_GG_GBDK) && strstr(area.name,"DATA_")) + sms_gg_is_banked_DATA = true; + if ((get_option_platform() == OPT_PLAT_SMS_GG_GBDK) && strstr(area.name,"LIT_")) + sms_gg_is_banked_LIT = true; + + // Loop through all banks and log any that overlap + // (may be more than one) + for(c = 0; c < bank_templates_count; c++) { + + // Skip LIT_X banked template if this is a DATA_X area (and same for inverse) + if (banks_sms_gg_checkskip_template(sms_gg_is_banked_DATA, sms_gg_is_banked_LIT, bank_templates[c].name)) + continue; + + // Check a given ROM/RAM bank template for overlap + size_used = addrs_get_overlap(bank_templates[c].start, bank_templates[c].end, + area.start_unbanked, area.end_unbanked); + + // If overlap was found, determine bank number and log it + if (size_used > 0) { + // Area items can span multiple banks, so don't use area.start + // on it's own to get bank number since it might originate + // in a lower bank (handled in a previous iteration of the loop). + // Instead use the current matched bank template start address. + // Then fixup missing bank number if needed + uint32_t addr_start_banknum = BANK_ONLY(area.start) | WITHOUT_BANK(bank_templates[c].start); + addr_start_banknum = addr_fixup_ROM0_overflow_bank_num(addr_start_banknum); + bank_num = BANK_GET_NUM(addr_start_banknum); + + // Area range added to bank will get clipped to bank range + banklist_addto(bank_templates[c], area, bank_num); + size_assigned += size_used; // Log space assigned to bank + + // Only allow overflow to other banks if first bank is non-banked + if (bank_templates[c].is_banked != BANKED_NO) + break; + } + } + + area_check_warnings(area, size_assigned); +} + +#define MAX_SPLIT_WORDS 4 +#define ARG_AREA_REC_COUNT_MATCH 4 + + +// Apply manually queued areas +void area_manual_apply_queued(void) { + for (int c = 0; c < area_manual_queue_count; c++) { + banks_check(areas_manual_queue[c]); + } +} + + +// -m:NAME:HEX_ADDR:HEX_LENGTH or -e[same] +// Queue areas to manually add from command line arguments +// +// Note: They're added to a queue for processing add AFTER +// banks_init_templates() has been called, otherwise they get erased. +// Follow up call is area_manual_apply_queued() +bool area_manual_queue(char * arg_str) { + + char cols; + char * p_str; + char * p_words[MAX_SPLIT_WORDS]; + + // Split string into words separated by spaces + cols = 0; + p_str = strtok(arg_str,"-:"); + while (p_str != NULL) + { + p_words[cols++] = p_str; + p_str = strtok(NULL, "-:"); + if (cols >= MAX_SPLIT_WORDS) break; + } + + if (cols == ARG_AREA_REC_COUNT_MATCH) { + area_item * p_area_to_queue = &areas_manual_queue[area_manual_queue_count++]; + + snprintf(p_area_to_queue->name, sizeof(p_area_to_queue->name), "%s", p_words[1]); // [1] Area Name + p_area_to_queue->start = strtol(p_words[2], NULL, 16); // [2] Area Hex Address Start + p_area_to_queue->end = p_area_to_queue->start + strtol(p_words[3], NULL, 16) - 1; // Start + [3] Hex Size - 1 = Area End + p_area_to_queue->exclusive = (p_words[0][0] == 'e') ? true : false; // [0] shared/exclusive + return true; + } else + return false; // Signal failure +} + +// NOTE: All the comparisons and their particular order are +// required for bank_areas_calc_used() to work properly. +// qsort compare rule function +static int area_item_compare(const void* a, const void* b) { + + // First sort by start address + if (((area_item *)a)->start != ((area_item *)b)->start) + return (((area_item *)a)->start < ((area_item *)b)->start) ? -1 : 1; + + // Otherwise end address + if (((area_item *)a)->end != ((area_item *)b)->end) + return (((area_item *)a)->end < ((area_item *)b)->end) ? -1 : 1; + + // If above match, then sort based on name + return strcmp(((area_item *)a)->name, ((area_item *)b)->name); + +} + + +// qsort compare rule function: sort by size descending first, then name +static int area_item_compare_size_desc(const void* a, const void* b) { + + if (((area_item *)a)->length != ((area_item *)b)->length) + return (((area_item *)a)->length < ((area_item *)b)->length) ? 1 : -1; + else + return strcmp(((area_item *)a)->name, ((area_item *)b)->name); +} + + +// qsort compare rule function: sort by start address ascending +static int area_item_compare_addr_asc(const void* a, const void* b) { + + return (((area_item *)a)->start < ((area_item *)b)->start) ? -1 : 1; +} + + +// qsort compare rule function +static int bank_item_compare(const void* a, const void* b) { + + // First sort by start address + if (((bank_item *)a)->start != ((bank_item *)b)->start) + return (((bank_item *)a)->start < ((bank_item *)b)->start) ? -1 : 1; + + // Otherwise based on bank number + if (((bank_item *)a)->bank_num != ((bank_item *)b)->bank_num) + return (((bank_item *)a)->bank_num < ((bank_item *)b)->bank_num) ? -1 : 1; + + return 0; // Otherwise return equivalent +} + + + +// Fill in gaps between symbols with "?" symbols --TODO: rename function to symbols +static void bank_fill_area_gaps_with_unknown(void) { + + bank_item * banks = (bank_item *)bank_list.p_array; + area_item * areas; + uint32_t last_addr, cur_addr; + int c, b, t_area_count; + area_item area; + + for (c = 0; c < bank_list.count; c++) { + // Load the area list for the bank + areas = (area_item *)banks[c].area_list.p_array; + + // Sort areas by ascending address so that gaps can be found + qsort (banks[c].area_list.p_array, banks[c].area_list.count, sizeof(area_item), area_item_compare_addr_asc); + + t_area_count = banks[c].area_list.count; // Temp area count to avoid processing newly added areas + last_addr = banks[c].start; // Set last to start of current bank + + for(b = 0; b < t_area_count; b++) { + + if ((banks_display_headers) || !(strstr(areas[b].name,"HEADER"))) { + + cur_addr = areas[b].start; + + if (cur_addr > last_addr + 1) { + + snprintf(area.name, sizeof(area.name), "-?-"); + area.start = last_addr + 1; + area.end = cur_addr - 1; + area.length = area.end - area.start + 1; + area.exclusive = false; + bank_add_area(&(banks[c]), area); // Add to bank, skip bank_check since parent bank is known + } + + // Update previous area reference + last_addr = areas[b].end; + } + } + } +} + + +// Print banks to output +void banklist_finalize_and_show(void) { + + bank_item * banks = (bank_item *)bank_list.p_array; + int c; + + // Sort banks by start address then bank num + qsort (bank_list.p_array, bank_list.count, sizeof(bank_item), bank_item_compare); + + if (get_option_input_source() == OPT_INPUT_SRC_CDB) + bank_fill_area_gaps_with_unknown(); + + for (c = 0; c < bank_list.count; c++) { + // Sort areas in bank and calculate usage + banks[c].size_used = bank_areas_calc_used(&banks[c], banks[c].start, banks[c].end); + + if (get_option_area_sort() == OPT_AREA_SORT_SIZE_DESC) + qsort (banks[c].area_list.p_array, banks[c].area_list.count, sizeof(area_item), area_item_compare_size_desc); + else if (get_option_area_sort() == OPT_AREA_SORT_ADDR_ASC) + qsort (banks[c].area_list.p_array, banks[c].area_list.count, sizeof(area_item), area_item_compare_addr_asc); + else + qsort (banks[c].area_list.p_array, banks[c].area_list.count, sizeof(area_item), area_item_compare); + } + + areas_check_rom0_overflow(); + + // Only print if quiet mode is not enabled + if (!option_quiet_mode) { + if (option_summarized_mode) { + banklist_collapse_to_summary(&bank_list, &bank_list_summarized); + if (option_json_output) + banklist_printall_json(&bank_list_summarized); + else + banklist_printall(&bank_list_summarized); + } + else { + if (option_json_output) + banklist_printall_json(&bank_list); + else + banklist_printall(&bank_list); + } + } +} + + +// Split a banks usage into N buckets +// +// Attempts to merge overlapping areas to avoid +// counting shared space multiple times. +// +// Avoids losing some address slots to integer rounding errors (when +// bucket_count is an imperfect divisor of range size) by using floats, +// with the trade-off that bucket size is slightly variable between buckets. +void bank_areas_split_to_buckets(bank_item * p_bank, uint32_t range_start, uint32_t range_size, uint32_t bucket_count, uint32_t * p_buckets) { + + float bucket_size = (float)range_size / (float)bucket_count; + if (bucket_size == 0.0) return; + + uint32_t range_end = range_start + (range_size - 1); + uint32_t bucket_start, bucket_end; + uint32_t bucket_id; + uint32_t start, end; + + // Make a working copy of the bank and it's areas to modify since the + // required sorting of areas would override any user level sorting option + bank_item bank_copy = *p_bank; + bank_copy.area_list.p_array = (void *)malloc(bank_copy.area_list.size * bank_copy.area_list.typesize); + if (!bank_copy.area_list.p_array) { + log_error("ERROR: Failed to reallocate memory for list!\n"); + exit(EXIT_FAILURE); + } + // Copy main list of areas to copy of bank for modification + memcpy(bank_copy.area_list.p_array, p_bank->area_list.p_array, + bank_copy.area_list.size * bank_copy.area_list.typesize); + + area_item * areas = (area_item *)bank_copy.area_list.p_array; + + // The calculation requires areas to be sorted ascending by .start addr then by .end addr + qsort (bank_copy.area_list.p_array, bank_copy.area_list.count, sizeof(area_item), area_item_compare); + + // Iterate over all areas, splitting areas into any buckets they overlaps with + int c = 0; + uint32_t highest_addr_used = range_start; + while (c < bank_copy.area_list.count) { + + // Only process areas not entirely covered by previous area + // Works since areas are sorted so current will never start before previous, + // and highest_addr_used is set to max from all processed areas so far + if (areas[c].end > highest_addr_used) { + + // Calc starting bucket to skip non-overlapping ones + bucket_id = ((areas[c].start - range_start) / bucket_size); + + // Break out if bucket exceeds range or + while (bucket_id < bucket_count) { + + bucket_start = (uint32_t)(bucket_size * (float)bucket_id) + range_start; + bucket_end = (uint32_t)((bucket_size * ((float)bucket_id + 1.0)) - 1.0) + range_start; + + // Break out of bucket updates for this area once past area end + if (bucket_start > areas[c].end) + break; + + // Factor in highest addr used if it's been initialized + // Use that to avoid counting parts where areas overlap multiple times. + // +1 since start should be the address _after_ the highest used + if (highest_addr_used != bucket_start) + start = max(bucket_start, highest_addr_used + 1); + + // Clip area to be within the bucket range + start = max(areas[c].start, bucket_start); + end = min(areas[c].end, bucket_end); + + if (start <= end) + p_buckets[bucket_id] += (end - start) + 1; + + // Move to next bucket and track high water mark for end of all areas + bucket_id++; + highest_addr_used = max(end, highest_addr_used); + + } // End processing buckets for a given area + } + // Move to next area + c++; + } + + if (bank_copy.area_list.p_array) { + free(bank_copy.area_list.p_array); + bank_copy.area_list.p_array = NULL; // Pointless, but out of habit + } +} diff --git a/gbdk-support/romusage/src/banks.h b/gbdk-support/romusage/src/banks.h @@ -0,0 +1,113 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _BANKS_H +#define _BANKS_H + +#include "list.h" + +#define WRAM_X_MAX_BANKS 7 + +#define MAX_ADDR_UNBANKED 0x0000FFFFU +#define BANK_ADDR_ROM_UPPER_ST 0x00004000U // Upper ROM Bank address bit +#define BANK_ADDR_ROM_UPPER_END 0x00007FFFU // Upper ROM Bank address bit +#define BANK_ADDR_VADDR_MASK 0xFFFF0000U // Virtual addressing mask for where .noi files/etc store bank number +#define BANK_NUM_ROM1_VADDR (1u << 16) +#define BANK_NUM_ROM0 (0u) +#define BANK_NUM_ROM1 (1u) + +#define AREA_COPY_ADDRESS_TRANSLATE_YES true +#define AREA_COPY_ADDRESS_TRANSLATE_NO false + +#define ARRAY_LEN(A) (sizeof(A) / sizeof(A[0])) +#define WITHOUT_BANK(addr) ((addr) & MAX_ADDR_UNBANKED) +#define BANK_GET_NUM(addr) (((addr) & BANK_ADDR_VADDR_MASK) >> 16) +#define BANK_ONLY(addr) ((addr) & BANK_ADDR_VADDR_MASK) +#define RANGE_SIZE(MIN, MAX) (MAX - MIN + 1) +#define UNBANKED_END(start, end) ((end - start) + WITHOUT_BANK(start)) + +#define AREA_MAX_STR DEFAULT_STR_LEN + +#define BANK_MAX_STR DEFAULT_STR_LEN +#define BANKED_NO 0 +#define BANKED_YES 1 + +#define BANK_STARTNUM_0 0 +#define BANK_STARTNUM_1 1 + +#define BANK_MERGED_NO false +#define BANK_MERGED_YES true + +#define MINIGRAPH_SIZE (2 * 14) // Number of characters wide (inside edge brackets) +#define LARGEGRAPH_BYTES_PER_CHAR 16 + +typedef enum { + BANK_MEM_TYPE_ROM, + BANK_MEM_TYPE_VRAM, + BANK_MEM_TYPE_SRAM, + BANK_MEM_TYPE_WRAM, + BANK_MEM_TYPE_HRAM +} bank_mem_types; + + +typedef struct area_item { + char name[AREA_MAX_STR]; + uint32_t start; + uint32_t end; + uint32_t start_unbanked; + uint32_t end_unbanked; + uint32_t length; + bool exclusive; +} area_item; + + +typedef struct bank_item { + // Mostly fixed values + char name[BANK_MAX_STR]; + uint32_t start; + uint32_t end; // May get modified for summarized output + int is_banked; + uint32_t overflow_end; + + // Updateable values + uint32_t size_total; + uint32_t size_used; + int bank_num; + + int bank_mem_type; + int base_bank_num; + bool is_merged_bank; + // End of templating vars + + // TODO: track overflow bytes and report them in graph + list_type area_list; +} bank_item; + +void area_manual_apply_queued(void); +bool area_manual_queue(char * arg_str); + +int bank_calc_percent_free(bank_item * p_bank); +int bank_calc_percent_used(bank_item * p_bank); + +uint32_t bank_areas_calc_used(bank_item *, uint32_t, uint32_t); + +void banks_output_show_areas(bool do_show); +void banks_output_show_headers(bool do_show); +void banks_output_show_minigraph(bool do_show); +void banks_output_show_largegraph(bool do_show); + +void banks_init(void); +void banks_cleanup(void); +void banks_init_templates(void); + +void set_exit_error(void); +bool get_exit_error(void); + +void banks_check(area_item area); +void banklist_finalize_and_show(void); + +void bank_areas_split_to_buckets(bank_item * p_bank, uint32_t range_start, uint32_t range_size, uint32_t range_buckets, uint32_t * p_buckes); + + +#endif // _BANKS_H + diff --git a/gbdk-support/romusage/src/banks_color.c b/gbdk-support/romusage/src/banks_color.c @@ -0,0 +1,179 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2022 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "banks_color.h" + + +#ifdef _WIN32 + #ifndef _WIN32 + #define __WIN32__ + #endif +#endif + +// For GetConsoleMode()/etc.. +#ifdef __WIN32__ + #include <windows.h> +#endif + +#ifdef __WIN32__ +// Enables Windows virtual terminal sequences for handling VT escape codes +// MS recommends this over SetConsoleTextAttribute() +bool colors_try_windows_enable_virtual_term_for_vt_codes(void) { + + // Set output mode to handle virtual terminal sequences + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) { + + DWORD dwMode = 0; + if (GetConsoleMode(hOut, &dwMode)) { + + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(hOut, dwMode)) { + return true; // success + } + } + } + return false; // failure +} +#endif + + +// Default colots +color_pal_t bank_colors = { + .default_color = PRINT_COLOR_DEFAULT, + .rom = PRINT_COLOR_ROM_DEFAULT, + .vram = PRINT_COLOR_VRAM_DEFAULT, + .sram = PRINT_COLOR_SRAM_DEFAULT, + .wram = PRINT_COLOR_WRAM_DEFAULT, + .hram = PRINT_COLOR_HRAM_DEFAULT, +}; + + +static uint8_t bank_get_color(bank_item * p_bank) { + + uint8_t color_esc_code = bank_colors.default_color; + + if (get_option_percentage_based_color()) { + + int perc_free = bank_calc_percent_free(p_bank); + + // Color ramp from Default -> Green -> Yellow -> Red + if (perc_free == 0) color_esc_code = VT_COLOR_RED_LIGHT; + else if (perc_free <= 10) color_esc_code = VT_COLOR_YELLOW_LIGHT; + else if (perc_free <= 25) color_esc_code = VT_COLOR_GREEN_LIGHT; + // implied: else if (> 25) PRINT_COLOR_DEFAULT + } + else { + switch (p_bank->bank_mem_type) { + case BANK_MEM_TYPE_ROM: color_esc_code = bank_colors.rom; break; + case BANK_MEM_TYPE_VRAM: color_esc_code = bank_colors.vram; break; + case BANK_MEM_TYPE_SRAM: color_esc_code = bank_colors.sram; break; + case BANK_MEM_TYPE_WRAM: color_esc_code = bank_colors.wram; break; + case BANK_MEM_TYPE_HRAM: color_esc_code = bank_colors.hram; break; + default: break; + } + } + + return color_esc_code; +} + + +// Write out the actual color/etc escape codes +static void write_out_color_code(uint8_t esc_num) { + + #ifdef __WIN32__ + + // Instead of Dim/Dim Off VT100 codes Windows virtual terminal uses + // Bright/Bright Off with basically inverted meaning for escape code 22. + // So flip them: + // Dim ON (2) -> Bright OFF (22) + // Dim OFF (22) -> Bright ON (1) + if (esc_num == VT_ATTR_DIM) esc_num = WINCON_ATTR_BRIGHT_RESET; + else if (esc_num == VT_ATTR_DIM_RESET) esc_num = WINCON_ATTR_BRIGHT; + + printf("\x1b[%dm", esc_num); + #else // VT100 compatible + printf("\x1b[%dm", esc_num); + #endif +} + + +// Writes out a color escape code based on current printing region and bank type +void bank_render_color(bank_item * p_bank, int mode) { + + if (get_option_color_mode() != OPT_PRINT_COLOR_OFF) { + + switch(mode) { + // Start row colorizing + case PRINT_REGION_ROW_START: + write_out_color_code(bank_get_color(p_bank)); + break; + + // Based on mode, either set to dim-color, turn off coloring, or continue coloring (do nothing) + case PRINT_REGION_ROW_MIDDLE_START: + if (get_option_color_mode() == OPT_PRINT_COLOR_WHOLE_ROW_DIMMED) write_out_color_code(VT_ATTR_DIM); + else if (get_option_color_mode() == OPT_PRINT_COLOR_ROW_ENDS) write_out_color_code(VT_ATTR_ALL_RESET); + // implied: else ( == OPT_PRINT_COLOR_WHOLE_ROW) : Don't change attributes in middle + break; + + // Based on mode, either turn off dim-color, turn coloring back on, or continue coloring (do nothing) + case PRINT_REGION_ROW_MIDDLE_END: + if (get_option_color_mode() == OPT_PRINT_COLOR_WHOLE_ROW_DIMMED) write_out_color_code(VT_ATTR_DIM_RESET); + else if (get_option_color_mode() == OPT_PRINT_COLOR_ROW_ENDS) write_out_color_code(bank_get_color(p_bank)); + // implied: else ( == OPT_PRINT_COLOR_WHOLE_ROW) : Don't change attributes in middle + break; + + // Start end colorizing + case PRINT_REGION_ROW_END: + write_out_color_code(VT_ATTR_ALL_RESET); + break; + } + } +} + + + +#define MAX_SPLIT_WORDS 8 +#define ARG_COLORS_CUSTOM_PAL 7u + +// -sP : Custom Color Palette. Each colon separated entry is decimal VT100 color code +// -sP:DEFAULT:ROM:VRAM:SRAM:WRAM:HRAM +// +// Custom color scheme for output +bool set_option_custom_bank_colors(char * arg_str) { + + char cols; + char * p_str; + char * p_words[MAX_SPLIT_WORDS]; + area_item area; + + // Split string into words separated by - and : chars + cols = 0; + p_str = strtok(arg_str,"-:"); + while (p_str != NULL) + { + p_words[cols++] = p_str; + p_str = strtok(NULL, "-:"); + if (cols >= MAX_SPLIT_WORDS) break; + } + + if (cols == ARG_COLORS_CUSTOM_PAL) { + bank_colors.default_color = strtol(p_words[1], NULL, 10); + bank_colors.rom = strtol(p_words[2], NULL, 10); + bank_colors.vram = strtol(p_words[3], NULL, 10); + bank_colors.sram = strtol(p_words[4], NULL, 10); + bank_colors.wram = strtol(p_words[5], NULL, 10); + bank_colors.hram = strtol(p_words[6], NULL, 10); + + return true; + } else + return false; // Signal failure +} diff --git a/gbdk-support/romusage/src/banks_color.h b/gbdk-support/romusage/src/banks_color.h @@ -0,0 +1,77 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2022 + + +#ifndef _BANKS_COLOR_H +#define _BANKS_COLOR_H + +#include "banks.h" + + +// VT100 color codes +// https://misc.flogisoft.com/bash/tip_colors_and_formatting + +// Windows Console Virtual Terminal Sequences +// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences + +#define VT_ATTR_ALL_RESET 0 +#define VT_ATTR_DIM 2 +#define VT_ATTR_DIM_RESET 22 + +// 0 Default Returns all attributes to the default state prior to modification +// 1 Bold/Bright Applies brightness/intensity flag to foreground color +// 22 No bold/bright Removes brightness/intensity flag from foreground color +#define WINCON_ATTR_BRIGHT 1 +#define WINCON_ATTR_BRIGHT_RESET 22 + +#define VT_COLOR_GREY_LIGHT 37 +#define VT_COLOR_RED_LIGHT 91 +#define VT_COLOR_GREEN_LIGHT 92 +#define VT_COLOR_YELLOW_LIGHT 93 +#define VT_COLOR_BLUE_LIGHT 94 +#define VT_COLOR_MAGENTA_LIGHT 95 +#define VT_COLOR_CYAN_LIGHT 96 + + +#define VT_COLOR_GREY_DARK 90 +#define VT_COLOR_RED_DARK 31 +#define VT_COLOR_GREEN_DARK 32 +#define VT_COLOR_YELLOW_DARK 33 +#define VT_COLOR_BLUE_DARK 34 +#define VT_COLOR_MAGENTA_DARK 35 +#define VT_COLOR_CYAN_DARK 36 + +// Note: The preset Default color just uses the "reset to no attributes" code +// which should revert to the user's default terminal font color. +// -> Using VT_COLOR_GREY_LIGHT is also an option +#define PRINT_COLOR_DEFAULT (VT_ATTR_ALL_RESET) +#define PRINT_COLOR_ROM_DEFAULT (VT_COLOR_GREEN_LIGHT) +#define PRINT_COLOR_VRAM_DEFAULT (PRINT_COLOR_DEFAULT) +#define PRINT_COLOR_WRAM_DEFAULT (VT_COLOR_MAGENTA_LIGHT) +#define PRINT_COLOR_SRAM_DEFAULT (VT_COLOR_YELLOW_LIGHT) +#define PRINT_COLOR_HRAM_DEFAULT (VT_COLOR_CYAN_LIGHT) + +typedef enum { + PRINT_REGION_ROW_START, + PRINT_REGION_ROW_MIDDLE_START, + PRINT_REGION_ROW_MIDDLE_END, + PRINT_REGION_ROW_END, +} bank_print_regions; + +typedef struct color_pal_t { + uint8_t default_color; + uint8_t rom; + uint8_t vram; + uint8_t sram; + uint8_t wram; + uint8_t hram; +} color_pal_t; + +bool colors_try_windows_enable_virtual_term_for_vt_codes(void); + +void bank_render_color(bank_item * p_bank, int mode); + +bool set_option_custom_bank_colors(char * arg_str); + +#endif // _BANKS_PRINT_H + diff --git a/gbdk-support/romusage/src/banks_print.c b/gbdk-support/romusage/src/banks_print.c @@ -0,0 +1,345 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "list.h" +#include "banks.h" +#include "banks_print.h" +#include "banks_color.h" + + +#ifdef _WIN32 + #ifndef _WIN32 + #define __WIN32__ + #endif +#endif + + + +// Ascii art style characters +// Print a block character to stdout based on the percentage used (int 0 - 100) +static void print_graph_char_asciistyle(uint32_t perc_used) { + + #ifdef __WIN32__ + // https://en.wikipedia.org/wiki/Code_page_437 + // https://sourceforge.net/p/mingw/mailman/message/14065664/ + // Code Page 437 (appears to be default for windows console, + if (perc_used >= 95) fprintf(stdout, "%c", 219u); // Full Shade Block + else if (perc_used >= 75) fprintf(stdout, "%c", 178u); // Dark Shade Block + else if (perc_used >= 50) fprintf(stdout, "%c", 177u); // Med Shade Block + else if (perc_used >= 25) fprintf(stdout, "%c", 176u); // Light Shade Block + else fprintf(stdout, "_"); // "."); + + // All Block characters + // if (perc_used >= 99) fprintf(stdout, "%c", 219u); // Full Shade Block + // else if (perc_used >= 75) fprintf(stdout, "%c", 178u); // Dark Shade Block + // else if (perc_used >= 25) fprintf(stdout, "%c", 177u); // Med Shade Block + // else fprintf(stdout, "%c", 176u); // Light Shade Block + + + #else // Non-Windows + // https://www.fileformat.info/info/unicode/block/block_elements/utf8test.htm + // https://www.fileformat.info/info/unicode/char/2588/index.htm + if (perc_used >= 95) fprintf(stdout, "%s", u8"\xE2\x96\x88"); // Full Block in UTF-8 0xE2 0x96 0x88 (e29688) + else if (perc_used >= 75) fprintf(stdout, "%s", u8"\xE2\x96\x93"); // Dark Shade Block in UTF-8 0xE2 0x96 0x93 (e29693) + else if (perc_used >= 50) fprintf(stdout, "%s", u8"\xE2\x96\x92"); // Med Shade Block in UTF-8 0xE2 0x96 0x92 (e29692) + else if (perc_used >= 25) fprintf(stdout, "%s", u8"\xE2\x96\x91"); // Light Shade Block in UTF-8 0xE2 0x96 0x91 (e29691) + else fprintf(stdout, "%s", u8"_"); // u8"."); + + // All Blocks characters + // if (perc_used >= 95) fprintf(stdout, "%s", u8"\xE2\x96\x88"); // Full Block in UTF-8 0xE2 0x96 0x88 (e29688) + // else if (perc_used >= 75) fprintf(stdout, "%s", u8"\xE2\x96\x93"); // Dark Shade Block in UTF-8 0xE2 0x96 0x93 (e29693) + // else if (perc_used >= 25) fprintf(stdout, "%s", u8"\xE2\x96\x92"); // Med Shade Block in UTF-8 0xE2 0x96 0x92 (e29692) + // else fprintf(stdout, "%s", u8"\xE2\x96\x91"); // Light Shade Block in UTF-8 0xE2 0x96 0x91 (e29691) + #endif +} + + +// Standard character style +// Print a block character to stdout based on the percentage used (int 0 - 100) +static void print_graph_char_standard(uint32_t perc_used) { + + char ch; + + if (perc_used > 95) ch = '#'; + else if (perc_used > 25) ch = '-'; + else ch = '.'; + fprintf(stdout, "%c", ch); +} + + +// Prints a graph for the bank +// Each character represents an arbitrary amount based on the bank size +static void bank_print_graph(bank_item * p_bank, uint32_t num_chars) { + + float bytes_per_char = p_bank->size_total / num_chars; + unsigned int perc_used; + uint32_t bucket_start, bucket_end; + uint32_t bucket_id; + uint32_t bucket_buf_size = num_chars * sizeof(uint32_t); + + uint32_t * p_buckets = (uint32_t *)malloc(bucket_buf_size); + if (p_buckets == NULL) { + log_error("Error: Failed to allocate buffer for graph!\n"); + return; + } + memset(p_buckets, 0, bucket_buf_size); + + bank_areas_split_to_buckets(p_bank, p_bank->start, p_bank->size_total, num_chars, p_buckets); + + for (bucket_id = 0; bucket_id <= (num_chars - 1); bucket_id++) { + + // Calculate range size this way so it matches the slightly variable bucket size. + // See bank_areas_split_to_buckets() for details + bucket_start = (uint32_t)(bytes_per_char * (float)bucket_id) + p_bank->start; + bucket_end = (uint32_t)((bytes_per_char * ((float)bucket_id + 1.0)) - 1.0) + p_bank->start; + + perc_used = (uint32_t)((float)p_buckets[bucket_id] * 100.0) / ((bucket_end - bucket_start) + 1); + + // Non-ascii style output + if (!get_option_display_asciistyle()) + print_graph_char_standard(perc_used); + else + print_graph_char_asciistyle(perc_used); + + // Periodic line break if needed (for multi-line large graphs) + if (((bucket_id + 1) % 64) == 0) + fprintf(stdout, "\n"); + } + + if (p_buckets) + free(p_buckets); +} + + +// Show a large usage graph for each bank +// Currently 16 bytes per character +static void banklist_print_large_graph(list_type * p_bank_list) { + + bank_item * banks = (bank_item *)p_bank_list->p_array; + int c; + + for (c = 0; c < p_bank_list->count; c++) { + + fprintf(stdout,"\n\nStart: %s ",banks[c].name); // Name + fprintf(stdout,"0x%04X -> 0x%04X",banks[c].start, + banks[c].end); // Address Start -> End + fprintf(stdout,"\n"); // Name + + uint32_t bytes_per_char = LARGEGRAPH_BYTES_PER_CHAR; + + // Scale large graph unit size by number of banks it uses for banked items + // (factoring in whether bank start is 0 or 1 based) + if (option_summarized_mode) + bytes_per_char *= ((banks[c].bank_num - banks[c].base_bank_num) + 1); + + bank_print_graph(&banks[c], banks[c].size_total / bytes_per_char); + + fprintf(stdout,"End: %s\n",banks[c].name); // Name + } +} + + +// Display all areas for a bank +static void bank_print_area(bank_item *p_bank) { + + area_item * areas; + int b; + int hidden_count = 0; + uint32_t hidden_total = 0; + + for(b = 0; b < p_bank->area_list.count; b++) { + + // Load the area list for the bank + areas = (area_item *)p_bank->area_list.p_array; + + if (b == 0) { + fprintf(stdout,"|\n"); + + // Only show sub column headers for CDB output since there are a lot more areas + if (get_option_input_source() == OPT_INPUT_SRC_CDB) + fprintf(stdout, + "| Name Start -> End Size \n" + "| --------------------- ---------------- -----\n"); + } + + // Don't display headers unless requested + if ((banks_display_headers) || !(strstr(areas[b].name,"HEADER"))) { + + // Optionally hide areas below a given size + if (areas[b].length >= get_option_area_hide_size()) { + + fprintf(stdout,"+ %-32s", areas[b].name); // Name + fprintf(stdout,"0x%04X -> 0x%04X",areas[b].start, + areas[b].end); // Address Start -> End + fprintf(stdout,"%8d", areas[b].length); + fprintf(stdout,"\n"); + } else { + hidden_count++; + hidden_total += areas[b].length; + } + } + } + if (hidden_count > 0) + fprintf(stdout,"+ (%d items < %d hidden = %d total bytes)\n", + hidden_count, get_option_area_hide_size(), hidden_total); + + fprintf(stdout,"\n"); +} + + +static void bank_print_info(bank_item *p_bank) { + + bank_render_color(p_bank, PRINT_REGION_ROW_START); + fprintf(stdout,"%-13s",p_bank->name); // Name + + bank_render_color(p_bank, PRINT_REGION_ROW_MIDDLE_START); + // Skip some info if compact mode is enabled. + if (!option_compact_mode) { + fprintf(stdout,"0x%04X -> 0x%04X",p_bank->start, p_bank->end); // Address Start -> End + fprintf(stdout,"%9d", p_bank->size_total); // Total size + } + fprintf(stdout,"%9d", p_bank->size_used); // Used + + if (!option_compact_mode) { + fprintf(stdout," %4d%%", bank_calc_percent_used(p_bank)); // Percent Used + } + + bank_render_color(p_bank, PRINT_REGION_ROW_MIDDLE_END); + fprintf(stdout,"%9d", (int32_t)p_bank->size_total - (int32_t)p_bank->size_used); // Free + fprintf(stdout," %3d%%", bank_calc_percent_free(p_bank)); // Percent Free + + // Print a small bar graph if requested + if (banks_display_minigraph) { + fprintf(stdout, " |"); + bank_print_graph(p_bank, MINIGRAPH_SIZE); + fprintf(stdout, "|"); + } + + bank_render_color(p_bank, PRINT_REGION_ROW_END); +} + + +// Display all banks along with space used. +// Optionally show areas. +void banklist_printall(list_type * p_bank_list) { + + bank_item * banks = (bank_item *)p_bank_list->p_array; + int c; + int b; + + #ifdef __WIN32__ + if (get_option_color_mode() != OPT_PRINT_COLOR_OFF) + if (!colors_try_windows_enable_virtual_term_for_vt_codes()) + log_warning("Warning: Failed to enable Windows virtual terminal sequences for color!\n"); + #endif + + fprintf(stdout, "\n"); + if (option_compact_mode) { + fprintf(stdout,"Bank Used Free Free%% \n" + "-------- ------- ------- -----\n"); + } else { + fprintf(stdout,"Bank Range Size Used Used%% Free Free%% \n" + "-------- ---------------- ------- ------- ----- ------- -----\n"); + } + + // Print all banks + for (c = 0; c < p_bank_list->count; c++) { + + bank_print_info(&banks[c]); + fprintf(stdout,"\n"); + + if (get_option_area_sort() != OPT_AREA_SORT_HIDE) // This is a hack-workaround, TODO:fixme + if (banks_display_areas) + bank_print_area(&banks[c]); + + } // End: Print all banks loop + + // Print a large graph per-bank if requested + if (banks_display_largegraph) + banklist_print_large_graph(p_bank_list); +} + + +// ====== JSON OUTPUT ====== + +static void bank_print_info_json(bank_item *p_bank) { + + fprintf(stdout," {\n"); + fprintf(stdout," \"name\": \"%s\",\n",p_bank->name); + + fprintf(stdout," \"type\": \"%d\",\n",p_bank->bank_mem_type); + fprintf(stdout," \"baseBankNum\": \"%d\",\n",p_bank->base_bank_num); + fprintf(stdout," \"isBanked\": \"%d\",\n",p_bank->is_banked); + fprintf(stdout," \"isMergedBank\": \"%d\",\n",p_bank->is_merged_bank); + + fprintf(stdout," \"rangeStart\": \"%d\",\n",p_bank->start); + fprintf(stdout," \"rangeEnd\": \"%d\",\n",p_bank->end); + fprintf(stdout," \"size\": \"%d\",\n",p_bank->size_total); + fprintf(stdout," \"used\": \"%d\",\n",p_bank->size_used); + fprintf(stdout," \"free\": \"%d\",\n",(int32_t)p_bank->size_total - (int32_t)p_bank->size_used); + fprintf(stdout," \"usedPercent\": \"%d\",\n",bank_calc_percent_used(p_bank)); + fprintf(stdout," \"freePercent\": \"%d\",\n",bank_calc_percent_free(p_bank)); + + fprintf(stdout," \"miniGraph\": \""); + bank_print_graph(p_bank, MINIGRAPH_SIZE); + fprintf(stdout,"\"\n"); + + fprintf(stdout," }\n"); +} + + +// Render all banks and space used as json format +// Example: +// +// {"banks": +// [ +// { +// "name": "ROM_2/3", +// "type": "0", +// "baseBankNum": "0", +// "isBanked": "1", +// "isMergedBank": "1", +// "rangeStart": "0", +// "rangeEnd": "32767", +// "size": "65536", +// "used": "24740", +// "free": "40796", +// "usedPercent": "38", +// "freePercent": "62", +// "miniGraph": "-##-...#######.............." +// } +// , +// ... +// ] +// } +void banklist_printall_json(list_type * p_bank_list) { + + bank_item * banks = (bank_item *)p_bank_list->p_array; + int c; + int b; + + // JSON array header + fprintf(stdout,"{\"banks\":\n" + " [\n"); + + // Print each bank as an array object item + for (c = 0; c < p_bank_list->count; c++) { + // Comma separator between array items + if (c > 0) fprintf(stdout," ,\n"); + + bank_print_info_json(&banks[c]); + } + + // JSON array footer + fprintf(stdout," ]\n" + "}\n"); +} diff --git a/gbdk-support/romusage/src/banks_print.h b/gbdk-support/romusage/src/banks_print.h @@ -0,0 +1,14 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + + +#ifndef _BANKS_PRINT_H +#define _BANKS_PRINT_H + +#include "banks.h" + +void banklist_printall(list_type *); +void banklist_printall_json(list_type *); + +#endif // _BANKS_PRINT_H + diff --git a/gbdk-support/romusage/src/banks_summarized.c b/gbdk-support/romusage/src/banks_summarized.c @@ -0,0 +1,209 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2023 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "list.h" +#include "banks.h" + +// === Summarized mode === + +static void summarize_copy_areas(bank_item *, const bank_item *); +static void summarize_copy_bank(bank_item *, const bank_item *); +static bool check_apply_forced_max_bank(int * p_banknum, int bank_num_max_used, int bank_mem_type); +static void summarize_fixup_sizes_and_names(list_type *); +static bool summarize_try_merge_bank(const bank_item *, list_type *); + +// Copy (append) areas from one bank to another +// +// Translate banked areas addresses based on their bank number +// Assumes: source and dest bank are valid, that areas from different banks don't overlap +static void summarize_copy_modified_areas(bank_item * p_dest_bank, const bank_item * p_src_bank) { + + area_item * areas_to_copy = (area_item *)p_src_bank->area_list.p_array; + + // Copy areas from source bank to new summary bank + for (int c = 0; c < p_src_bank->area_list.count; c++) { + + area_item new_area = areas_to_copy[c]; + // Scale area address up by bank number * bank size (factoring in whether bank start is 0 or 1 based) + // + // Handle special (-cWRAM or -cROM) case of a non-banked region merged with banked region. + // This causes address range size to be 2x bank size and throws of base address calc for banked data + // (to fix: bank - 1, and bank size / 2 ) + if ((p_src_bank->is_merged_bank) && (p_src_bank->bank_num > 0)) + new_area.start += (((p_src_bank->bank_num - p_src_bank->base_bank_num) - 1) * (p_src_bank->size_total / 2)); + else + new_area.start += ((p_src_bank->bank_num - p_src_bank->base_bank_num) * p_src_bank->size_total); + + new_area.end = new_area.start + (new_area.length - 1); + + list_additem(&(p_dest_bank->area_list), &new_area); + p_dest_bank->size_used += new_area.length; + } +} + + +// Copy a bank entry and it's areas +static void summarize_copy_bank(bank_item * p_dest_bank, const bank_item * p_src_bank) { + + // Duplicate bank, re-initialize bank list & copy areas from source + *p_dest_bank = *p_src_bank; + p_dest_bank->size_used = 0; + list_init(&(p_dest_bank->area_list), sizeof(area_item)); + summarize_copy_modified_areas(p_dest_bank, p_src_bank); +} + + +// Checks to see whether -F : Force displayed Max ROM and SRAM bank range needs to be applied +// Modifies p_banknum if so and returns true +static bool check_apply_forced_max_bank(int * p_banknum, int bank_num_max_used, int bank_mem_type) { + + int forced_bank_num; + char str_ROM[] = "ROM"; + char str_SRAM[] = "SRAM"; + char str_NONE[] = "UNKNOWN"; + char * p_str_banktype = str_NONE; + + if (option_forced_display_max_bank_ROM) { + + switch (bank_mem_type) { + case BANK_MEM_TYPE_ROM: + forced_bank_num = option_forced_display_max_bank_ROM; + p_str_banktype = str_ROM; + break; + case BANK_MEM_TYPE_SRAM: + forced_bank_num = option_forced_display_max_bank_SRAM; + p_str_banktype = str_SRAM; + break; + } + + if (forced_bank_num < bank_num_max_used) { + log_warning("Warning! Forced Max %s Bank %d is smaller than Max Used Bank %d\n", p_str_banktype, forced_bank_num, bank_num_max_used); + if (option_error_on_warning) + set_exit_error(); + } + + *p_banknum = forced_bank_num; + return true; // Success + } + return false; // Failure +} + + +// After banked regions have been merged, fix their names and sizes +static void summarize_fixup_sizes_and_names(list_type * p_bank_list_summarized) { + + bank_item * banks_summarized = (bank_item *)p_bank_list_summarized->p_array; + + for (int c=0; c < p_bank_list_summarized->count; c++) { + if (banks_summarized[c].is_banked) { + + // Some banked region bank nums are 0, don't process those + // (no need to resize, resize code not built to handle it) + if (banks_summarized[c].bank_num) { + + // Save current as max "used" before replacing with max possible + int bank_num_max_used = banks_summarized[c].bank_num; + + // ROM_X size: Round up bank num to next (power of 2) -1 + // SRAM_X size: Round up to next (power of 4) - 1 + // WRAM_X: Fixed number of banks on CGB, 7 always max + switch (banks_summarized[c].bank_mem_type) { + case BANK_MEM_TYPE_ROM: + if (! check_apply_forced_max_bank(&banks_summarized[c].bank_num, bank_num_max_used, banks_summarized[c].bank_mem_type)) + banks_summarized[c].bank_num = round_up_power_of_2((uint32_t)banks_summarized[c].bank_num + 1) - 1; + break; + case BANK_MEM_TYPE_SRAM: + if (! check_apply_forced_max_bank(&banks_summarized[c].bank_num, bank_num_max_used, banks_summarized[c].bank_mem_type)) { + if (banks_summarized[c].bank_num > 7) banks_summarized[c].bank_num = 15; + else if (banks_summarized[c].bank_num > 3) banks_summarized[c].bank_num = 7; + else if (banks_summarized[c].bank_num > 1) banks_summarized[c].bank_num = 3; + } + break; + case BANK_MEM_TYPE_WRAM: banks_summarized[c].bank_num = WRAM_X_MAX_BANKS; + break; + } + + // Update bank total size (factoring in whether bank start is 0 or 1 based) + banks_summarized[c].size_total = ((banks_summarized[c].bank_num - banks_summarized[c].base_bank_num) + 1) * banks_summarized[c].size_total; + + // Handle special (-cWRAM or -cROM) case of a non-banked region merged with banked region. + // This causes address range size to be 2x which inflates total bank size. Divide by 2 to fix. + if (banks_summarized[c].is_merged_bank) + banks_summarized[c].size_total /= 2; + + // Don't resize .end so that bank region shows correctly when rendered. charts will use ".size_total" + // banks_summarized[c].end = (banks_summarized[c].size_total - 1) + banks_summarized[c].start; + + // Update Bank name with Used / Max - example: "ROM_1" with "ROM_Used/Max" + char new_name[BANK_MAX_STR]; + // Strip bank name and underscore off the end if present + char * p_chr_underscore = strstr(banks_summarized[c].name, "_"); + if (p_chr_underscore != NULL) + *p_chr_underscore = '\0'; + + int ret; // save result to suppress truncation warning, bank names will never be > 100 chars and truncation is fine if it got to that + ret = snprintf(new_name, sizeof(new_name), "%s_%d/%d", banks_summarized[c].name, bank_num_max_used, banks_summarized[c].bank_num); + ret = snprintf(banks_summarized[c].name, sizeof(banks_summarized[c].name), "%s", new_name); + } + } + } +} + + +// Try to find a matching bank in summarized data +// If found merge source bank into it +static bool summarize_try_merge_bank(const bank_item * p_src_bank, list_type * p_bank_list_summarized) { + + bank_item * banks_summarized = (bank_item *)p_bank_list_summarized->p_array; + + // Check if bank type already exists in summarized data by start of memory region + for (int dest_idx=0; dest_idx < p_bank_list_summarized->count; dest_idx++) { + if (banks_summarized[dest_idx].start == p_src_bank->start) { + // Found matching bank type, copy areas and bump up max bank used if needed + summarize_copy_modified_areas(&banks_summarized[dest_idx], p_src_bank); + banks_summarized[dest_idx].bank_num = max(banks_summarized[dest_idx].bank_num, p_src_bank->bank_num); + return true; // Success + } + } + return false; // Failed: no existing bank found to merge into +} + + +// Generate a summarized view with all banked +// categories collapsed into to single ranges +// +// Data in the bank list is assumed to be good +// and not requiring validation at this point +void banklist_collapse_to_summary(const list_type * p_bank_list, list_type * p_bank_list_summarized) { + + bank_item * banks = (bank_item *)p_bank_list->p_array; + bank_item * banks_summarized = (bank_item *)p_bank_list_summarized->p_array; + bank_item new_bank; + + for (int src_idx=0; src_idx < p_bank_list->count; src_idx++) { + + // Add non-banked items directly without aggregating them + // Assumes no dupes, or if dupes then intentional + if (! banks[src_idx].is_banked) { + summarize_copy_bank(&new_bank, &banks[src_idx]); + list_additem(p_bank_list_summarized, &new_bank); + } else { + // Otherwise try to merge with an existing bank, or create a new entry in sumamrized data if needed + if (! summarize_try_merge_bank(&banks[src_idx], p_bank_list_summarized)) { + summarize_copy_bank(&new_bank, &banks[src_idx]); + list_additem(p_bank_list_summarized, &new_bank); + } + } + } + + summarize_fixup_sizes_and_names(p_bank_list_summarized); +} diff --git a/gbdk-support/romusage/src/banks_summarized.h b/gbdk-support/romusage/src/banks_summarized.h @@ -0,0 +1,13 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _BANKS_SUMMARIZED_H +#define _BANKS_SUMMARIZED_H + +#include "list.h" + +// Summarized mode +void banklist_collapse_to_summary(const list_type * p_banks, list_type * p_banks_summarized); + +#endif // _BANKS_SUMMARIZED_H + diff --git a/gbdk-support/romusage/src/cdb_file.c b/gbdk-support/romusage/src/cdb_file.c @@ -0,0 +1,294 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "list.h" +#include "banks.h" +#include "cdb_file.h" + + +list_type symbol_list; + +#define CDB_L_REC_FUNC_START_GLOBAL 'G' +#define CDB_L_REC_FUNC_START_LOCAL 'F' +#define CDB_L_REC_FUNC_END_GLOBAL "XG" +#define CDB_L_REC_FUNC_END_LOCAL "XF" + +// More compelte CDB coverage is affected by this SDCC bug +// #3662 Adding a function removes const arrays above it from .adb/.cdb output +// https://sourceforge.net/p/sdcc/bugs/3662/ + + +// Example data to parse from a .cdb file: +// +// 0 1 2---------- 3-- 4 5---- +// L:G$big_const_4$0_0$0:24039 <-- bank 2 +// L:G$big_const_3$0_0$0:1784E <-- Bank 1 +// +// S:Fsrcfile_2$func_2_local_scope$0_0$0({2}DF,SV:S),C,0,0 +// L:Fsrcfile_2$func_2_local_scope$0$0:14000 +// L:XFsrcfile_2$func_2_local_scope$0$0:14003 +// F:Fsrcfile_2$func_2_local_scope$0_0$0({2}DF,SV:S),C,0,0,0,0,0 +// +// 0 1 2---------- 3-- 4 5-- 6----- 7 8 9 10 11 +// S:G$big_const_3$0_0$0({256}DA256d,SC:U),D,0,0 <--- size 256 bytes (SC=signed char) +// S:G$big_const_4$0_0$0({15360}DA15360d,SC:U),D,0,0 <--- size 15360 bytes (SC=signed char) +// +// 0 1--------- 2------------ 3-- 4 5--- +// L:Fsrcfile_2$local_const_2$0_0$0:14027 +// 0 1--------- 2------------ 3-- 4 5---- 6------- 7 8 9 10 11 +// S:Fsrcfile_2$local_const_2$0_0$0({14336}DA14336d,SC:U),D,0,0 <--- size 14336 bytes + +// 4.8 Link Address of Symbol +// +// L Link record type indicator +// G Symbol has file scope +// F <Filename> Symbol has file scope. +// L <Function> Symbol has function scope +// +// functions use L:XG to mark their last line +// basxto: L:XG$init_map$0$0:BCA - L:G$init_map$0$0:AFB = should be the length +// L:G$board_handle_new_piece$0$0:6284 +// L:XG$board_handle_new_piece$0$0:62DD +// +// https://github.com/roybaer/sdcc-wiki/wiki/CDB-File-Format +// +// Address Space field (S: record) +// A External stack +// B Internal stack +// C Code +// D Code / static segment +// E Internal ram (lower 128) bytes +// F External ram +// G Internal ram +// H Bit addressable +// I SFR space +// J SBIT space +// R Register space +// Z Used for function records, or any undefined space code +// +// Types in a Section 4.4 Type Chain Record (S: record) +// DA <n> Array of n elements +// DF Function +// DG Generic pointer +// DC Code pointer +// DX External ram pointer +// DD Internal ram pointer +// DP Paged pointer +// DI Upper 128 byte pointer +// SL long +// SI int +// SC char +// SS short +// SV void +// SF float +// ST <name> Structure of name <name> +// SX sbit +// SB <n> Bit field of <n> bits + + +// Initialize the symbol list +void cdb_init(void) { + + list_init(&symbol_list, sizeof(area_item)); +} + + +// Free the symbol list +void cdb_cleanup(void) { + + list_cleanup(&symbol_list); +} + + +// Find a matching symbol, if none matches a new one is added and returned +static int symbollist_get_id_by_name(char * symbol_name) { + + area_item * symbols = (area_item *)symbol_list.p_array; + area_item new_symbol; + + int c; + + // Check for matching symbol name + for(c=0;c < symbol_list.count; c++) { + // Return matching symbol index if present + if (strncmp(symbol_name, symbols[c].name, AREA_MAX_STR) == 0) { + return c; + } + } + + snprintf(new_symbol.name, sizeof(new_symbol.name), "%s", symbol_name); + new_symbol.start = AREA_VAL_UNSET; + new_symbol.end = AREA_VAL_UNSET; + new_symbol.length = AREA_VAL_UNSET; + if (strstr(symbol_name,"HEADER")) + new_symbol.exclusive = false; // HEADER symbols almost always overlap, ignore them + else + new_symbol.exclusive = option_all_areas_exclusive; // Default is false + + list_additem(&symbol_list, &new_symbol); + + return (symbol_list.count - 1); +} + + +// Process list of symbols and add them to banks +static void cdb_symbollist_add_all_to_banks() { + + area_item * symbols = (area_item *)symbol_list.p_array; + int c; + + // Only process completed symbols (start and length both set) + for(c=0;c < symbol_list.count; c++) { + + // Functions need length calculated from start and end + if ((symbols[c].length == AREA_VAL_UNSET) && + (symbols[c].start != AREA_VAL_UNSET) && + (symbols[c].end != AREA_VAL_UNSET)) { + symbols[c].length = symbols[c].end - symbols[c].start + 1; + } + + + if ((symbols[c].start != AREA_VAL_UNSET) && + (symbols[c].length != AREA_VAL_UNSET)) { + symbols[c].end = symbols[c].start + symbols[c].length - 1; + banks_check(symbols[c]); + } + } +} + + +// Adds start/end address from a Linker Record +// Requires either separate calls for Start and End, or one call for +// start and a separate cdb_add_record_symbol() call to set length +static void cdb_add_record_linker(char * type, char * name, char * address) { + + area_item * symbols = (area_item *)symbol_list.p_array; + + // Retrieve existing symbol or create a new one + int symbol_id = symbollist_get_id_by_name(name); + + if (symbol_id != ERR_NO_AREAS_LEFT) { + + // Check Linker record for start-address or end-address + // Bank number is in the address bits, no need to modify it + if ((strncmp(type, CDB_L_REC_FUNC_END_GLOBAL, 4) == 0) || + (strncmp(type, CDB_L_REC_FUNC_END_LOCAL, 4) == 0)) { + symbols[symbol_id].end = strtol(address, NULL, 16); // End address + } + else if ((type[0] == CDB_L_REC_FUNC_START_GLOBAL) || + (type[0] == CDB_L_REC_FUNC_START_LOCAL)) { + symbols[symbol_id].start = strtol(address, NULL, 16); // Start address + } + // else + // printf("Rejected L record %s, %s, %s\n", type, name, address); + } +} + + +// Adds length from a symbol record +// To get a complete entry requires a start address call to cdb_add_record_linker() +static void cdb_add_record_symbol(char * addr_space, char * name, char * length, char * dcl_type) { + + area_item * symbols = (area_item *)symbol_list.p_array; + + // Only allow certain address spaces + if ((addr_space[0] == 'C') || // Address Space: Code + (addr_space[0] == 'D') || // Address Space: Code / static segment + (addr_space[0] == 'E') || // Address Space: Internal RAM (lower 128) bytes + (addr_space[0] == 'F') || // Address Space: External RAM + (addr_space[0] == 'G')) { // Address Space: Internal RAM + + // Exclude zero length entries + // Don't let function ~entry points override function bodies(<DCLType> = DF, function which are two bytes in size) + if ((strtol(length, NULL, 10) > 0) && + (!strstr(dcl_type, "DF"))) + { + // Retrieve existing symbol or create a new one + int symbol_id = symbollist_get_id_by_name(name); // [2] Area Name + if (symbol_id != ERR_NO_AREAS_LEFT) { + symbols[symbol_id].length = strtol(length, NULL, 10); // [5] Symbol decimal length + } + } + } + // else + // printf("Rejected S record %s, %s, %s, %s\n", addr_space, name, length, dcl_type); +} + + +int cdb_file_process_symbols(char * filename_in) { + + char cols; + char * p_str; + char * p_words[CDB_MAX_SPLIT_WORDS]; + char strline_in[CDB_MAX_STR_LEN] = ""; + FILE * cdb_file = fopen(filename_in, "r"); + area_item symbol; + int symbol_id; + + // CDB defaults to showing areas + banks_output_show_areas(true); + + // CDB defaults to size descending, but don't override explicit options + if (get_option_area_sort() == OPT_AREA_SORT_DEFAULT) + set_option_area_sort(OPT_AREA_SORT_SIZE_DESC); + + set_option_input_source(OPT_INPUT_SRC_CDB); + + if (cdb_file) { + + // Read one line at a time into \0 terminated string + while ( fgets(strline_in, sizeof(strline_in), cdb_file) != NULL) { + + // Require minimum length to match + if (strlen(strline_in) >= CDB_REC_START_LEN) { + + // Match either _S_egment or _L_ength records + if ( (strncmp(strline_in, "L:", CDB_REC_START_LEN) == 0) || + (strncmp(strline_in, "S:", CDB_REC_START_LEN) == 0)) { + + // Split string into words separated by spaces + cols = 0; + p_str = strtok(strline_in,":$({}),"); + while (p_str != NULL) + { + p_words[cols++] = p_str; + p_str = strtok(NULL, ":$({}),"); + if (cols >= CDB_MAX_SPLIT_WORDS) break; + } + + // Linker record (start or end address) + if ((p_words[0][0] == CDB_REC_L) && + (cols == CDB_REC_L_COUNT_MATCH)) { + // [1] Start/End, [2] Area Name1, [5] Address + cdb_add_record_linker(p_words[1], p_words[2], p_words[5]); + } + // Symbol record (length) + if ((p_words[0][0] == CDB_REC_S) && + (cols == CDB_REC_S_COUNT_MATCH)) { + // [9] address space, [2] Area Name, [5] Symbol decimal length, [6] DCLType + cdb_add_record_symbol(p_words[9], p_words[2], p_words[5], p_words[6]); + } + + } // end: if valid start of line + } // end: valid min chars to process line + } // end: while still lines to process + + fclose(cdb_file); + + // Process all the symbols + cdb_symbollist_add_all_to_banks(); + + } // end: if valid file + else return (false); + + return true; +} diff --git a/gbdk-support/romusage/src/cdb_file.h b/gbdk-support/romusage/src/cdb_file.h @@ -0,0 +1,23 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#define CDB_MAX_STR_LEN 4096 +// #define CDB_MAX_STR_AREALEN 100 +#define CDB_MAX_SPLIT_WORDS 12 + +#define CDB_REC_L_COUNT_MATCH 6 +#define CDB_REC_S_COUNT_MATCH 12 + +#define ERR_NO_AREAS_LEFT -1 +#define AREA_VAL_UNSET 0xFFFFFFFF + +#define CDB_REC_START_LEN 2 // length of "DEF l__" +#define CDB_REC_L 'L' // Linker (address) Record +#define CDB_REC_S 'S' // Symbol (size) Record + + +int cdb_file_process_symbols(char * filename_in); + +void cdb_init(void); +void cdb_cleanup(void); diff --git a/gbdk-support/romusage/src/common.c b/gbdk-support/romusage/src/common.c @@ -0,0 +1,258 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdbool.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" + +bool banks_display_areas = false; +bool banks_display_headers = false; +bool banks_display_minigraph = false; +bool banks_display_largegraph = false; +bool option_compact_mode = false; +bool option_json_output = false; +bool option_summarized_mode = false; + +// -B +unsigned int option_merged_banks = OPT_MERGED_BANKS_NONE; +// -F +unsigned int option_forced_display_max_bank_ROM = 0; +unsigned int option_forced_display_max_bank_SRAM = 0; + +unsigned int option_platform = OPT_PLAT_GAMEBOY; +bool option_display_asciistyle = false; +bool option_all_areas_exclusive = false; +bool option_quiet_mode = false; +bool option_suppress_duplicates = true; +bool option_error_on_warning = false; +bool option_hide_banners = false; +int option_input_source = OPT_INPUT_SRC_NONE; +int option_area_sort = OPT_AREA_SORT_DEFAULT; +int option_color_mode = OPT_PRINT_COLOR_OFF; +bool option_percentage_based_color = false; +uint32_t option_area_hide_size = OPT_AREA_HIDE_SIZE_DEFAULT; +bool exit_error = false; + + + +// Turn on/off display of areas within bank +void banks_output_show_areas(bool do_show) { + banks_display_areas = do_show; +} + +// Turn on/off display of areas within bank +void banks_output_show_headers(bool do_show) { + banks_display_headers = do_show; +} + +// Turn on/off display of mini usage graph per bank +void banks_output_show_minigraph(bool do_show) { + banks_display_minigraph = do_show; +} + +// Turn on/off display of large usage graph per bank +void banks_output_show_largegraph(bool do_show) { + banks_display_largegraph = do_show; +} + +// Turn on/off compact display mode +void set_option_show_compact(bool value) { + option_compact_mode = value; +} + +// Turn on/off JSON output mode +void set_option_show_json(bool value) { + option_json_output = value; +} + +// Turn on/off brief / summarized mode for banked regions +void set_option_summarized(bool value) { + option_summarized_mode = value; +} + +// Turns on merged WRAM_0 + WRAM_1 display +void set_option_merged_banks(unsigned int value) { + option_merged_banks |= value; +} + +// Sets console platform (changes memory map templates) +void set_option_platform(unsigned int value) { + option_platform = value; +} + +// Turn on/off whether to use ascii style +// block characters for graphs +void set_option_display_asciistyle(bool value) { + option_display_asciistyle = value; +} + +// Turn on/off whether all areas are exclusive, +// and whether to warn for any overlap +void set_option_all_areas_exclusive(bool value) { + option_all_areas_exclusive = value; +} + +// Turn on/off quiet mode +void set_option_quiet_mode(bool value) { + option_quiet_mode = value; +} + +// Turn on/off suppression of duplicates +void set_option_suppress_duplicates(bool value) { + option_suppress_duplicates = value; +} + +// Turn on/off setting an error on exit for serious warnings encountered +void set_option_error_on_warning(bool value) { + option_error_on_warning = value; +} + +// Turn on/off banners +void set_option_hide_banners(bool value) { + option_hide_banners = value; +} + +// Input source file format +void set_option_input_source(int value) { + option_input_source = value; +} + +// Area output sort order +void set_option_area_sort(int value) { + option_area_sort = value; +} + +// Color output mode +void set_option_color_mode(int value) { + option_color_mode = value; +} + +// Use Percentage based color +// Turns on color mode to default if not enabled +void set_option_percentage_based_color(bool value) { + option_percentage_based_color = value; + + if (get_option_color_mode() == OPT_PRINT_COLOR_OFF) + set_option_color_mode(OPT_PRINT_COLOR_DEFAULT); +} + +// Hide areas smaller than size +void set_option_area_hide_size(uint32_t value) { + option_area_hide_size = value; +} + + +// -sP : Custom Color Palette. Each colon separated entry is decimal VT100 color code +// -sP:DEFAULT:ROM:VRAM:SRAM:WRAM:HRAM +// +// Custom color scheme for output +bool option_set_displayed_bank_range(char * arg_str) { + + #define MAX_SPLIT_WORDS 4 + #define EXPECTED_COLS 3 + + char cols; + char * p_str; + char * p_words[MAX_SPLIT_WORDS]; + + // Split string into words separated by - and : chars + cols = 0; + p_str = strtok(arg_str,"-:"); + while (p_str != NULL) + { + p_words[cols++] = p_str; + p_str = strtok(NULL, "-:"); + if (cols >= MAX_SPLIT_WORDS) break; + } + + if (cols == EXPECTED_COLS) { + option_forced_display_max_bank_ROM = strtol(p_words[1], NULL, 10); + option_forced_display_max_bank_SRAM = strtol(p_words[2], NULL, 10); + // printf("2:%s\n", p_words[2]); + return true; + } else + return false; // Signal failure +} + + + +// Input source file format +int get_option_input_source(void) { + return option_input_source; +} + +// Area output sort order +int get_option_area_sort(void) { + return option_area_sort; +} + +// Color output mode +int get_option_color_mode(void) { + return option_color_mode; +} + +// Use Percentage based color +bool get_option_percentage_based_color(void) { + return option_percentage_based_color; +} + +// Turn on/off banners +bool get_option_hide_banners(void) { + return option_hide_banners; +} + +// Hide areas smaller than size +uint32_t get_option_area_hide_size(void) { + return option_area_hide_size; +} + +// Current platform (used for changing memory map templates) +unsigned int get_option_platform(void) { + return option_platform; +} + +// Turn on/off whether to use ascii style +// block characters for graphs +bool get_option_display_asciistyle(void) { + return option_display_asciistyle; +} + + + + +void set_exit_error(void) { + exit_error = true; +} + +bool get_exit_error(void) { + return exit_error; +} + + +uint32_t round_up_power_of_2(uint32_t val) { + + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} + + +uint32_t min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + + +uint32_t max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} diff --git a/gbdk-support/romusage/src/common.h b/gbdk-support/romusage/src/common.h @@ -0,0 +1,102 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _COMMON_H +#define _COMMON_H + +// #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +#define DEFAULT_STR_LEN 100 + +#define OPT_AREA_SORT_DEFAULT 0 +#define OPT_AREA_SORT_SIZE_DESC 1 +#define OPT_AREA_SORT_ADDR_ASC 2 +#define OPT_AREA_SORT_HIDE 3 + +#define OPT_INPUT_SRC_NONE 0 +#define OPT_INPUT_SRC_CDB 1 +#define OPT_INPUT_SRC_NOI 2 +#define OPT_INPUT_SRC_MAP 3 +#define OPT_INPUT_SRC_IHX 4 +#define OPT_INPUT_SRC_ROM 5 + +#define OPT_AREA_HIDE_SIZE_DEFAULT 0 + +#define OPT_PRINT_COLOR_OFF 0 +#define OPT_PRINT_COLOR_WHOLE_ROW 1 +#define OPT_PRINT_COLOR_WHOLE_ROW_DIMMED 2 +#define OPT_PRINT_COLOR_ROW_ENDS 3 +#define OPT_PRINT_COLOR_DEFAULT (OPT_PRINT_COLOR_WHOLE_ROW) + + +// Each flags should be a unique power of 2 +#define OPT_MERGED_BANKS_NONE 0u +#define OPT_MERGED_BANKS_WRAM 1u +#define OPT_MERGED_BANKS_ROM 2u + +#define OPT_PLAT_GAMEBOY 0u +#define OPT_PLAT_SMS_GG_GBDK 1u // GBDK specific layout of sms/gg + + +extern bool banks_display_areas; +extern bool banks_display_headers; +extern bool banks_display_minigraph; +extern bool banks_display_largegraph; +extern bool option_compact_mode; +extern bool option_json_output; +extern bool option_summarized_mode; + +extern unsigned int option_merged_banks; +extern unsigned int option_forced_display_max_bank_ROM; +extern unsigned int option_forced_display_max_bank_SRAM; + +extern bool option_all_areas_exclusive; +extern bool option_quiet_mode; +extern bool option_suppress_duplicates; +extern bool option_error_on_warning; +extern unsigned int option_merged_banks; +// Use get_/set_() for these +// extern bool option_hide_banners; +// extern int option_input_source; +// extern int option_area_sort; +// extern int option_color_mode; +extern uint32_t option_area_hide_size; +extern bool exit_error; + + +void set_option_all_areas_exclusive(bool value); +void set_option_quiet_mode(bool value); +void set_option_suppress_duplicates(bool value); +void set_option_error_on_warning(bool value); +void set_option_hide_banners(bool value); +void set_option_input_source(int value); +void set_option_area_sort(int value); +void set_option_color_mode(int value); +void set_option_percentage_based_color(bool value); +void set_option_area_hide_size(uint32_t value); +void set_option_platform(unsigned int); +void set_option_display_asciistyle(bool value); +void set_option_show_compact(bool value); +void set_option_show_json(bool value); +void set_option_summarized(bool value); + +bool option_set_displayed_bank_range(char * arg_str); + +void set_option_merged_banks(unsigned int value); + +int get_option_input_source(void); +int get_option_area_sort(void); +int get_option_color_mode(void); +bool get_option_percentage_based_color(void); +bool get_option_hide_banners(void); +unsigned int get_option_platform(void); +bool get_option_display_asciistyle(void); +uint32_t get_option_area_hide_size(void); + +uint32_t round_up_power_of_2(uint32_t val); + +uint32_t min(uint32_t a, uint32_t b); +uint32_t max(uint32_t a, uint32_t b); + +#endif // _COMMON_H + diff --git a/gbdk-support/romusage/src/ihx_file.c b/gbdk-support/romusage/src/ihx_file.c @@ -0,0 +1,309 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "banks.h" +#include "ihx_file.h" + +// Example data to parse from a .ihx file +// No area names +// : 01 0020 00 E9 F6 +// S BB AAAA RR DD CC +// +// S: Start (":") (1 char) +// BB: ByteCount (2 chars, one byte) +// AAAA: Address (4 chars, two bytes) +// RR: Record Type (2 chars, one byte. 00=data, 01=EOF, Others) +// DD: Data (<ByteCount> bytes) +// CC: Checksum (2 chars, one byte. Sum record bytes, 2's complement of LSByte) +/* +:01002000E9F6 +:05002800220D20FCC9BF +:070030001A22130D20FAC98A +. +. +. +:00000001FF (EOF indicator) +*/ + +/* TODO/WARNING/BUG: 100% full banks +It may not be possible to easily tell the difference between a perfectly filled +bank (100% and no more) and an adjacent partially filled bank that starts at +zero - versus - the first bank overflowed into the second that is empty. + +Currently the 100% bank will get merged into the next one and present as overflow +*/ + + +#define IS_NOT_BANK_START(addr) (!((addr & 0x00003FFFU) == 0x00000000U)) +#define IS_NOT_BANK_END(addr) (!((addr & 0x00003FFFU) == 0x00003FFFU)) +#define BANK_NUM(addr) ((addr & 0xFFFFC000U) >> 14) +#define ADDR_UNSET 0xFFFFFFFEU + +#define MAX_STR_LEN 4096 +#define IHX_DATA_LEN_MAX 255 +#define IHX_REC_LEN_MIN (1 + 2 + 4 + 2 + 0 + 2) // Start(1), ByteCount(2), Addr(4), Rec(2), Data(0..255x2), Checksum(2) + +// IHX record types +#define IHX_REC_DATA 0x00U +#define IHX_REC_EOF 0x01U +#define IHX_REC_EXTSEG 0x02U +#define IHX_REC_STARTSEG 0x03U +#define IHX_REC_EXTLIN 0x04U +#define IHX_REC_STARTLIN 0x05U + +typedef struct ihx_record { + uint16_t length; + uint32_t byte_count; + uint32_t address; + uint32_t address_end; + uint32_t type; + uint32_t checksum; // Would prefer this be a uint8_t, but mingw sscanf("%2hhx") has a buffer overflow that corrupts adjacent data +} ihx_record; + +uint32_t g_address_upper; + +// Converts sequentally stored ihx bank style (ROM0=0x0000, ROM1=0x4000, ROM2=0x8000) +// into map/noi banked style (ROM0=0x0000, ROM1=0x04000, ROM2=0x14000) +uint32_t ihx_bank_2_mapnoi_bank(uint32_t addr) { + + // If above 0x4000, upshift any address bits beyond 0x3FFF + // into upper 16 bits and force 0x4000 as base address + if (addr >= 0x4000) + addr = (addr & 0x3FFF) | ((addr & 0xFFFFC000) << 2) + 0x4000; + return addr; +} + + +// Return false if any character isn't a valid hex digit +int check_hex(char * c) { + while (*c != '\0') { + if ((*c >= '0') && (*c <= '9')) + c++; + if ((*c >= 'A') && (*c <= 'F')) + c++; + if ((*c >= 'a') && (*c <= 'f')) + c++; + else + return false; + } + + return true; +} + + +// Parse and validate an IHX record +int ihx_parse_and_validate_record(char * p_str, ihx_record * p_rec) { + + int calc_length = 0; + int c; + uint32_t ctemp, checksum_calc = 0; // Avoid mingw sscanf("%2hhx") buffer overflow with uint8_t + + // Remove trailing CR and LF + p_rec->length = strlen(p_str); + for (c = 0;c < p_rec->length;c++) { + if (p_str[c] == '\n' || p_str[c] == '\r') { + p_str[c] = '\0'; // Replace char with string terminator + p_rec->length = c; // Shrink length to truncated size + break; // Exit loop after finding first CR or LF + } + } + + // Only parse lines that start with ':' character (Start token for IHX record) + if (p_str[0] != ':') { + log_warning("Warning: IHX: Invalid start of line token for line: %s \n", p_str); + return false; + } + + // Require minimum length + if (p_rec->length < IHX_REC_LEN_MIN) { + log_warning("Warning: IHX: Invalid line, too few characters: %s. Is %d, needs at least %d \n", p_str, p_rec->length, IHX_REC_LEN_MIN); + return false; + } + + // Only hex characters are allowed after start token + p_str++; // Advance past Start code + if (check_hex(p_str)) { + log_warning("Warning: IHX: Invalid line, non-hex characters present: %s\n", p_str); + return false; + } + + // Read record header: byte count, start address, type + sscanf(p_str, "%2x%4x%2x", &p_rec->byte_count, &p_rec->address, &p_rec->type); + p_str += (2 + 4 + 2); + + // Require expected data byte count to fit within record length (at 2 chars per hex byte) + calc_length = IHX_REC_LEN_MIN + (p_rec->byte_count * 2); + if (p_rec->length != calc_length) { + log_warning("Warning: IHX: byte count doesn't match length available in record! Record length = %d, Calc length = %d, bytecount = %d \n", p_rec->length, calc_length, p_rec->byte_count); + return false; + } + + // Is this an extended linear address record? Read in offset address if so + if (p_rec->type == IHX_REC_EXTLIN) { + sscanf(p_str, "%4x", &g_address_upper); + g_address_upper <<= 16; // Shift into upper 16 bits of address space + } + else if (p_rec->type == IHX_REC_DATA) { + + // Don't process records with zero bytes of length + if (p_rec->byte_count == 0) { + log_warning("Warning: IHX: Zero length record starting at %x\n", p_rec->address); + return false; + } + + // Apply extended linear address (upper 16 bits of address space) + // Calculate end address + p_rec->address |= g_address_upper; + p_rec->address_end = p_rec->address + p_rec->byte_count - 1; + } + + // Read data segment and calculate checsum of data + headers + checksum_calc = p_rec->byte_count + (p_rec->address & 0xFF) + ((p_rec->address >> 8) & 0xFF) + p_rec->type; + for (c = 0;c < p_rec->byte_count;c++) { + sscanf(p_str, "%2x", &ctemp); + p_str += 2; + checksum_calc += ctemp; + } + + // Final calculated checeksum is 2's complement of LSByte + checksum_calc = (((checksum_calc & 0xFF) ^ 0xFF) + 1) & 0xFF; + + // Read checksum from data + sscanf(p_str, "%2x", &p_rec->checksum); + p_str += 2; + + if (p_rec->checksum != checksum_calc) { + log_warning("Warning: IHX: record checksum %x didn't match calculated checksum %x\n", p_rec->checksum, checksum_calc); + return false; + } + + // For records that start in banks above the unbanked region (0x000 - 0x3FFF) + // Warn if they cross the boundary between different banks + if ((p_rec->address >= 0x00004000U) && + ((p_rec->address & 0xFFFFC000U) != (p_rec->address_end & 0xFFFFC000U))) { + log_warning("Warning: IHX: Write from one bank spans into the next. Bank overflow? %x -> %x (bank %d -> %d)\n", + p_rec->address, p_rec->address_end, BANK_NUM(p_rec->address), BANK_NUM(p_rec->address_end)); + } + + return true; +} + +void area_convert_and_add(area_item area) { + + area_item t_area; + + // Convert ihx banked addresses to map/noi style + // End should be calculated relative to start + + // Records which cross from (< 0x4000) into (>= 0x4000) + // could produce ROM_0 entries that should be in ROM_1. + // These are not warned about earlier since it could be + // legit behavior for a 32K unbanked ROM. + // + // Split them on the bank boundary to avoid that. + if ((area.start < 0x00004000U) && (area.end >= 0x00004000U)) + { + // Copy area and drop part in the lower unbanked bank area + t_area = area; + t_area.start = 0x00004000U; + area_convert_and_add(t_area); + // Truncate original area at unbanked ROM boundary + area.end = 0x00003FFFU; + } + + area.end = (area.end - area.start) + ihx_bank_2_mapnoi_bank(area.start); + area.start = ihx_bank_2_mapnoi_bank(area.start); + banks_check(area); +} + + +int ihx_file_process_areas(char * filename_in) { + + int ret = true; // default to success + char cols; + char strline_in[MAX_STR_LEN] = ""; + FILE * ihx_file = fopen(filename_in, "r"); + area_item area; + ihx_record ihx_rec; + + set_option_input_source(OPT_INPUT_SRC_IHX); + + // IHX record areas don't have distinct names, so turn + // off duplicate suppression to avoid any being dropped. + // They are also never allowed to overlap + set_option_suppress_duplicates(false); + set_option_all_areas_exclusive(true); + + // Initialize global upper address modifier + g_address_upper = 0x0000; + + // Initialize area record + snprintf(area.name, sizeof(area.name), "ihx record"); + area.exclusive = option_all_areas_exclusive; // Default is false + area.start = ADDR_UNSET; + area.end = ADDR_UNSET; + + if (ihx_file) { + + // Read one line at a time into \0 terminated string + while (fgets(strline_in, sizeof(strline_in), ihx_file) != NULL) { + + // Parse record, skip if fails validation + if (!ihx_parse_and_validate_record(strline_in, &ihx_rec)) + continue; + + // Process the pending record and exit if last record (EOF) + // Also ignore non-default data records (don't seem to occur for gbz80) + if (ihx_rec.type == IHX_REC_EOF) { + area_convert_and_add(area); + continue; + } else if (ihx_rec.type == IHX_REC_EXTLIN) { + // printf("Extended linear address changed to %08x %s\n\n\n", g_address_upper, strline_in); + continue; + } else if (ihx_rec.type != IHX_REC_DATA) { + log_warning("Warning: IHX: dropped record %s of type %d\n", strline_in, ihx_rec.type); + continue; + } + + // Records are left pending (non-processed) until they don't merge + // with the current incoming record *or* the final (EOF) record is found. + + // Try to merge with (pending) previous record if it's address-adjacent, + // except when the new record starts or ends on a bank boundary + // (this reduces count from 1000's since most are only 32 bytes long) + if ((ihx_rec.address == area.end + 1) && IS_NOT_BANK_START(ihx_rec.address)) { + area.end = ihx_rec.address_end; // append to previous area + } else if ((ihx_rec.address_end == area.start + 1) && IS_NOT_BANK_END(ihx_rec.address_end)) { + area.start = ihx_rec.address; // pre-pend to previous area + } else { + // New record was *not* adjacent to last, + // so process the last/pending record + if (area.start != ADDR_UNSET) { + area_convert_and_add(area); + } + // Now queue current record as pending for next loop + area.start = ihx_rec.address; + area.end = ihx_rec.address + ihx_rec.byte_count - 1; + } + + } // end: while still lines to process + + fclose(ihx_file); + + } // end: if valid file + else + ret = false; + + return ret; +} + diff --git a/gbdk-support/romusage/src/ihx_file.h b/gbdk-support/romusage/src/ihx_file.h @@ -0,0 +1,5 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +int ihx_file_process_areas(char * filename_in); diff --git a/gbdk-support/romusage/src/list.c b/gbdk-support/romusage/src/list.c @@ -0,0 +1,71 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +#include "logging.h" +#include "list.h" + +#define LIST_GROW_SIZE 100 // 50 // grow array by N entries at a time + +// Initialize the list and it's array +// typesize *must* match the type that will be used with the array +void list_init(list_type * p_list, size_t array_typesize) { + p_list->typesize = array_typesize; + p_list->count = 0; + p_list->size = LIST_GROW_SIZE; + p_list->p_array = (void *)malloc(p_list->size * p_list->typesize); + + if (!p_list->p_array) { + log_error("ERROR: Failed to allocate memory for list!\n"); + exit(EXIT_FAILURE); + } +} + + +// Free the array memory allocated for the list +void list_cleanup(list_type * p_list) { + if (p_list->p_array) { + free (p_list->p_array); + p_list->p_array = NULL; + } +} + + +// Add a new item to the lists array, resize if needed +// p_newitem *must* be the same type the list was initialized with +// New item gets copied, so ok if it's a local var with limited lifetime +void list_additem(list_type * p_list, void * p_newitem) { + + void * tmp_list; + + p_list->count++; + + // Grow array if needed + if (p_list->count == p_list->size) { + // Save a copy in case reallocation fails + tmp_list = p_list->p_array; + + p_list->size += p_list->typesize * LIST_GROW_SIZE; + p_list->p_array = (void *)realloc(p_list->p_array, p_list->size * p_list->typesize); + // If realloc failed, free original buffer before quitting + if (!p_list->p_array) { + log_error("ERROR: Failed to reallocate memory for list!\n"); + if (tmp_list) { + free(tmp_list); + tmp_list = NULL; + } + exit(EXIT_FAILURE); + } + } + + // Copy new entry + memcpy((uint_least8_t *)p_list->p_array + ((p_list->count - 1) * p_list->typesize), + p_newitem, + p_list->typesize); +} + diff --git a/gbdk-support/romusage/src/list.h b/gbdk-support/romusage/src/list.h @@ -0,0 +1,20 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _LIST_H +#define _LIST_H + + +typedef struct list_type { + void * p_array; + uint32_t size; + uint32_t count; + size_t typesize; +} list_type; + +void list_init(list_type *, size_t); +void list_cleanup(list_type *); +void list_additem(list_type *, void *); + +#endif // _LIST_H + diff --git a/gbdk-support/romusage/src/logging.c b/gbdk-support/romusage/src/logging.c @@ -0,0 +1,55 @@ +// logging.c + +#include <stdarg.h> +#include <stdio.h> + +#include "logging.h" + +int output_level = OUTPUT_LEVEL_DEFAULT; + + +#define VA_LIST_PRINT() \ + va_list args; \ + va_start (args, format); \ + vfprintf (stderr, format, args); \ + va_end (args); + + +void log_set_level(int new_output_level) { + output_level = new_output_level; +} + +void log_debug(const char * format, ...){ + + if (output_level > OUTPUT_LEVEL_DEBUG) return; + VA_LIST_PRINT(); +} + +void log_verbose(const char * format, ...){ + + if (output_level > OUTPUT_LEVEL_VERBOSE) return; + VA_LIST_PRINT(); +} + +void log_standard(const char * format, ...){ + + // Only print if quiet mode and error_only are NOT enabled + if ((output_level == OUTPUT_LEVEL_QUIET) || + (output_level == OUTPUT_LEVEL_ONLY_ERRORS)) return; + VA_LIST_PRINT(); +} + +void log_warning(const char * format, ...){ + + // Only print if quiet mode and error_only are NOT enabled + if ((output_level == OUTPUT_LEVEL_QUIET) || + (output_level == OUTPUT_LEVEL_ONLY_ERRORS)) return; + VA_LIST_PRINT(); +} + +void log_error(const char * format, ...){ + + // Only print if quiet mode is NOT enabled + if (output_level == OUTPUT_LEVEL_QUIET) return; + VA_LIST_PRINT(); +} + diff --git a/gbdk-support/romusage/src/logging.h b/gbdk-support/romusage/src/logging.h @@ -0,0 +1,23 @@ +// logging.h + +#ifndef _LOGGING_H +#define _LOGGING_H + +enum output_levels { + OUTPUT_LEVEL_DEBUG, + OUTPUT_LEVEL_VERBOSE, + OUTPUT_LEVEL_DEFAULT, + OUTPUT_LEVEL_ONLY_ERRORS, + OUTPUT_LEVEL_QUIET +}; + +#define log_progress log_verbose + +void log_set_level(int new_output_level); +void log_debug(const char * format, ...); +void log_verbose(const char * format, ...); +void log_standard(const char * format, ...); +void log_warning(const char * format, ...); +void log_error(const char * format, ...); + +#endif + diff --git a/gbdk-support/romusage/src/map_file.c b/gbdk-support/romusage/src/map_file.c @@ -0,0 +1,148 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "banks.h" +#include "map_file.h" + +// Example data to parse from a .map file (excluding unwanted lines): +/* +_CODE 00000200 00006A62 = 27234. bytes (REL,CON) +_CODE 00000200 00006A62 = 27234. bytes (REL,CON) +_HOME 00006C62 000000ED = 237. bytes (REL,CON) +_BASE 00006D4F 000002A3 = 675. bytes (REL,CON) +_GSINIT 00006FF2 000001F9 = 505. bytes (REL,CON) +_GSINITTAIL 000071EB 00000001 = 1. bytes (REL,CON) +_DATA 0000C0A0 00001684 = 5764. bytes (REL,CON) +_BSS 0000D724 00000041 = 65. bytes (REL,CON) +_HEAP 0000D765 00000000 = 0. bytes (REL,CON) +_HRAM10 00000000 00000001 = 1. bytes (ABS,CON) +*/ + +#define MAX_SPLIT_WORDS 6 +#define RGBDS_BANK_SPLIT_WORDS 3 +#define RGBDS_SECT_NAME_SPLIT_WORDS 3 +#define RGBDS_SECT_INFO_SPLIT_WORDS 5 +#define GBDK_AREA_SPLIT_WORDS 6 +#define BANK_NUM_UNSET 0xFFFFFFFF + + + +static int str_split(char * str_check, char * p_words[], const char * split_criteria) { + + char cols; + char * p_str; + + cols = 0; + p_str = strtok (str_check,split_criteria); + while (p_str != NULL) + { + p_words[cols++] = p_str; + p_str = strtok(NULL, split_criteria); + if (cols >= MAX_SPLIT_WORDS) break; + } + + return (cols); +} + + +static void add_area_gbdk(char * p_words[]) { + + area_item area; + + if ((strtol(p_words[2], NULL, 16) > 0) && // Exclude empty areas + !(strstr(p_words[0], "SFR")) && // Exclude SFR areas (not actually located at addresses in area listing) + !(strstr(p_words[0], "HRAM")) // Exclude HRAM area + ) + { + snprintf(area.name, sizeof(area.name), "%s", p_words[0]); // [0] Area Name + area.start = strtol(p_words[1], NULL, 16); // [1] Area Hex Address Start + area.end = area.start + strtol(p_words[2], NULL, 16) - 1; // Start + [3] Hex Size - 1 = Area End + if (strstr(area.name,"HEADER")) + area.exclusive = false; // HEADER areas almost always overlap, ignore them + else + area.exclusive = option_all_areas_exclusive; // Default is false + banks_check(area); + } +} + + +static void add_area_rgbds(char * p_words[], int current_bank, const char * str_area_name) { + + area_item area; + + snprintf(area.name, sizeof(area.name), "%s", str_area_name); // Area Name + area.start = strtol(p_words[1], NULL, 16) | (current_bank << 16); // [1] Area Hex Address Start + area.end = strtol(p_words[2], NULL, 16) | (current_bank << 16); // [2] Area Hex Address End + area.exclusive = option_all_areas_exclusive; // Default is false + banks_check(area); +} + + +static int get_bank_num_rgbds(char * p_words[]) { + + return strtol(p_words[2], NULL, 10); +} + + +int map_file_process_areas(char * filename_in) { + + uint32_t cur_bank_rgbds; + char * p_words[MAX_SPLIT_WORDS]; + char strline_in[MAX_STR_LEN] = ""; + FILE * map_file = fopen(filename_in, "r"); + + set_option_input_source(OPT_INPUT_SRC_MAP); + + cur_bank_rgbds = BANK_NUM_UNSET; + + if (map_file) { + + // Read one line at a time into \0 terminated string + while ( fgets(strline_in, sizeof(strline_in), map_file) != NULL) { + + // RGBDS Bank Numbers: Bank lines precede Section lines, use them to set bank num + if (strstr(strline_in, " bank #")) { + if (str_split(strline_in,p_words," #:\r\n") == RGBDS_BANK_SPLIT_WORDS) + cur_bank_rgbds = get_bank_num_rgbds(p_words); + } + + // RGBDS Sections: Only parse lines that have Section (Area) summary info + else if (strstr(strline_in, " SECTION: ") || strstr(strline_in, "\tSECTION: ")) { + if (cur_bank_rgbds != BANK_NUM_UNSET) { + // Try to strip quote bracketed name out first + int name_split_count = str_split(strline_in, p_words,"\""); + if (name_split_count > 0) { + // Save section name from array before splitting again (if not blank) + const char * str_area_name = (name_split_count == RGBDS_SECT_NAME_SPLIT_WORDS) ? p_words[1] : ""; + // Then split up the remaining section info from first string in split array + if (str_split(p_words[0], p_words," :$()[]\n\t\"") == RGBDS_SECT_INFO_SPLIT_WORDS) + add_area_rgbds(p_words, cur_bank_rgbds, str_area_name); + } + } + } + + // GBDK Areas: Only parse lines that start with '_' character (Area summary lines) + else if (strline_in[0] == '_') { + if (str_split(strline_in, p_words, " =.") == GBDK_AREA_SPLIT_WORDS) + add_area_gbdk(p_words); + } + + } // end: while still lines to process + + fclose(map_file); + + } // end: if valid file + else return (false); + + return true; +} + diff --git a/gbdk-support/romusage/src/map_file.h b/gbdk-support/romusage/src/map_file.h @@ -0,0 +1,8 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#define MAX_STR_LEN 4096 +// #define MAX_STR_AREALEN 100 + +int map_file_process_areas(char * filename_in); diff --git a/gbdk-support/romusage/src/noi_file.c b/gbdk-support/romusage/src/noi_file.c @@ -0,0 +1,178 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "list.h" +#include "banks.h" +#include "noi_file.h" + + +list_type area_list; + +// Initialize the symbol list +void noi_init(void) { + + list_init(&area_list, sizeof(area_item)); +} + + +// Free the symbol list +void noi_cleanup(void) { + + list_cleanup(&area_list); +} + +// Example data to parse from a .map file (excluding unwanted lines): +/* +DEF l__BSS 0x41 +DEF l__HEADERe 0x75 +DEF l__HOME 0xED +DEF l__GSINIT 0x1F9 +DEF s__CODE 0x200 +DEF l__BASE 0x2A3 +DEF l__DATA 0x1F20 +DEF l__CODE 0x6A62 +DEF s__HOME 0x6C62 +DEF s__BASE 0x6D4F +*/ + + + +// Find a matching area, if none matches a new one is added and returned +static int arealist_get_id_by_name(char * area_name) { + + area_item * areas = (area_item *)area_list.p_array; + area_item new_area; + int c; + + // Check for matching area name + for(c=0;c < area_list.count; c++) { + // Return matching area index if present + if (strncmp(area_name, areas[c].name, AREA_MAX_STR) == 0) { + return c; + } + } + + // no match was found, add area + snprintf(new_area.name, sizeof(new_area.name), "%s", area_name); + new_area.start = AREA_VAL_UNSET; + new_area.end = AREA_VAL_UNSET; + new_area.length = AREA_VAL_UNSET; + if (strstr(area_name,"HEADER")) + new_area.exclusive = false; // HEADER areas almost always overlap, ignore them + else + new_area.exclusive = option_all_areas_exclusive; // Default is false + + list_additem(&area_list, &new_area); + + return (area_list.count - 1); +} + + +// Process list of areas and add them to banks +static void noi_arealist_add_all_to_banks() { + + area_item * areas = (area_item *)area_list.p_array; + int c; + + // Only process completed areas (start and length both set) + for(c=0;c < area_list.count; c++) { + + if ((areas[c].start != AREA_VAL_UNSET) && + (areas[c].length != AREA_VAL_UNSET)) { + areas[c].end = areas[c].start + areas[c].length - 1; + banks_check(areas[c]); + } + } +} + + +static void noi_arealist_add(char * rec_type, char * name, char * value) { + + area_item * areas = (area_item *)area_list.p_array; + + int area_id = arealist_get_id_by_name(name); // [2] Area Name1 + if (area_id != ERR_NO_AREAS_LEFT) { + + // Handle whether it's a start-of-address or a length record for the given area + if (rec_type[0] == NOI_REC_START) + areas[area_id].start = strtol(value, NULL, 16); // [2] Area Hex Address Start + else if (rec_type[0] == NOI_REC_LENGTH) { + // Don't add lengths of zero + if (strtol(value, NULL, 16) > 0) + areas[area_id].length = strtol(value, NULL, 16); // [2] Area Hex Length + } + } + +} + + +int noi_file_process_areas(char * filename_in) { + + char cols; + char * p_str; + char * p_words[MAX_SPLIT_WORDS]; + char strline_in[MAX_STR_LEN] = ""; + FILE * noi_file = fopen(filename_in, "r"); + area_item area; + int area_id; + + set_option_input_source(OPT_INPUT_SRC_NOI); + + if (noi_file) { + + // Read one line at a time into \0 terminated string + while ( fgets(strline_in, sizeof(strline_in), noi_file) != NULL) { + + // Require minimum length to match + if (strlen(strline_in) >= NOI_REC_START_LEN) { + + // Match either _S_egment or _L_ength records + if ( (strncmp(strline_in, "DEF l__", NOI_REC_START_LEN) == 0) || + (strncmp(strline_in, "DEF s__", NOI_REC_START_LEN) == 0)) { + + + // Split string into words separated by spaces + cols = 0; + p_str = strtok(strline_in," _"); + while (p_str != NULL) + { + p_words[cols++] = p_str; + // Only split on underscore for the second match + if (cols == 1) + p_str = strtok(NULL, " _"); + else + p_str = strtok(NULL, " "); + if (cols >= MAX_SPLIT_WORDS) break; + } + + if (cols == NOI_REC_COUNT_MATCH) { + if ( !(strstr(p_words[2], "SFR")) && // Exclude SFR areas (not actually located at addresses in area listing) + !(strstr(p_words[2], "HRAM")) ) { // Exclude HRAM area + + noi_arealist_add(p_words[1], p_words[2], p_words[3]); + } + } + } // end: if valid start of line + } // end: valid min chars to process line + + } // end: while still lines to process + + fclose(noi_file); + + // Process all the areas + noi_arealist_add_all_to_banks(); + + } // end: if valid file + else return (false); + + return true; +} diff --git a/gbdk-support/romusage/src/noi_file.h b/gbdk-support/romusage/src/noi_file.h @@ -0,0 +1,22 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#define MAX_STR_LEN 4096 +// #define MAX_STR_AREALEN 100 +#define MAX_SPLIT_WORDS 6 + +#define NOI_REC_COUNT_MATCH 4 + +#define ERR_NO_AREAS_LEFT -1 +#define AREA_VAL_UNSET 0xFFFFFFFF + +#define NOI_REC_START_LEN 7 // length of "DEF l__" +#define NOI_REC_START 's' +#define NOI_REC_LENGTH 'l' + + +int noi_file_process_areas(char * filename_in); + +void noi_init(void); +void noi_cleanup(void); + diff --git a/gbdk-support/romusage/src/rom_file.c b/gbdk-support/romusage/src/rom_file.c @@ -0,0 +1,171 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "list.h" +#include "banks.h" +#include "rom_file.h" + + +#define ADDR_UNSET 0xFFFFFFFF +#define EMPTY_CONSECUTIVE_THRESHOLD 16 +#define EMPTY_VAL 0xFF +#define BANK_SIZE 0x4000 +#define BANK_ADDR_MASK 0x00003FFF +#define BANK_NUM_MASK 0xFFFFC000 +#define BANK_NUM_UPSHIFT 2 + +#define ROM_ADDR_TO_BANKED(addr) ((addr & BANK_ADDR_MASK) | ((addr & BANK_NUM_MASK) << BANK_NUM_UPSHIFT)) + + +// Read from a file into a buffer (will allocate needed memory) +// Returns NULL if reading file didn't succeed +uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { + + long fsize; + FILE * file_in = fopen(filename, "rb"); + uint8_t * filedata = NULL; + + if (file_in) { + // Get file size + fseek(file_in, 0, SEEK_END); + fsize = ftell(file_in); + if (fsize != -1L) { + fseek(file_in, 0, SEEK_SET); + + filedata = (uint8_t *)malloc(fsize); + if (filedata) { + if (fsize != fread(filedata, 1, fsize, file_in)) { + log_warning("Warning: File read size didn't match expected for %s\n", filename); + filedata = NULL; + } + // Read was successful, set return size + *ret_size = fsize; + } else log_error("ERROR: Failed to allocate memory to read file %s\n", filename); + + } else log_error("ERROR: Failed to read size of file %s\n", filename); + + fclose(file_in); + } else log_error("ERROR: Failed to open input file %s\n", filename); + + return filedata; +} + + +// Calculate a range, adjust it's bank num and add try adding to banks if valid +static void rom_add_range(area_item range, uint32_t cur_idx, uint32_t empty_run, bool romsize_32K_or_less) { + + if (range.start != ADDR_UNSET) { + + // Range ended, add to areas + range.end = cur_idx - empty_run; + + // Don't try to virtual translate address for binary ROM + // files 32K or smaller, most likely they're unbanked. + if (!romsize_32K_or_less) { + // convert from ROM banked format (linear, bits in .14+) + // to expected banked format (bank in bits .16+) + range.start = ROM_ADDR_TO_BANKED(range.start); + range.end = ROM_ADDR_TO_BANKED(range.end); + + // Banks 1+ all start at 0x4000 + if (BANK_GET_NUM(range.start) >= 1) { + range.start += BANK_SIZE; + range.end += BANK_SIZE; + } + } + + // Calc length and add to banks + if (range.end >= range.start) { + range.length = range.end - range.start + 1; + // printf("ROM ADD Range: %8x -> %8x, %d (at %8x)\n", range.start, range.end, range.length, cur_idx); + banks_check(range); + } + } +} + + + +int rom_file_process(char * filename_in) { + + char cols; + uint8_t * p_buf = NULL; + uint32_t buf_idx = 0; + uint32_t buf_length = 0; + uint32_t empty_run = 0; + uint32_t bank_bytes = 0; + area_item rom_range; + bool romsize_32K_or_less; + + set_option_input_source(OPT_INPUT_SRC_ROM); + + // Read in ROM image + p_buf = file_read_into_buffer(filename_in, &buf_length); + romsize_32K_or_less = (buf_length <= 0x8000); + + rom_range.name[0] = '\0'; // Rom file ranges don't have names, set string to empty + rom_range.start = ADDR_UNSET; + + if (p_buf) { + + // Loop through all ROM bytes + while (buf_idx < buf_length) { + + // Process each bank (0x4000 bytes in a row) separately + // and close out any ranges that might span between them + + if (buf_length > BANK_SIZE) bank_bytes = BANK_SIZE; + else bank_bytes = buf_length; + + while (bank_bytes) { + + // Split buffer up into runs of non-zero bytes + // with a threshold of N zero bytes in a row to split them + if (p_buf[buf_idx] != EMPTY_VAL) { + if (rom_range.start == ADDR_UNSET) + rom_range.start = buf_idx; + empty_run = 0; + } else { + empty_run++; + + // Handle current run/range if present and over threshold. + // Otherwise ignore the "empty" vals since it may be data of that value + if ((empty_run > EMPTY_CONSECUTIVE_THRESHOLD) && + (rom_range.start != ADDR_UNSET)) { + + // Add range then flag as cleared and prep for a new range + rom_add_range(rom_range, buf_idx, empty_run, romsize_32K_or_less); + rom_range.start = ADDR_UNSET; + } + } + + buf_idx++; + bank_bytes--; + } // end: while still _bank_ bytes to process + + // Close out a remaining run if one exists (at last byte which is -1 of current) + // Flag as cleared and prep for a new range + rom_add_range(rom_range, buf_idx - 1, empty_run, romsize_32K_or_less); + rom_range.start = ADDR_UNSET; + + } // End main buffer loop + + if (p_buf) { + free(p_buf); + p_buf = NULL; + } + + } // end: if valid file + else return (false); + + return true; +} diff --git a/gbdk-support/romusage/src/rom_file.h b/gbdk-support/romusage/src/rom_file.h @@ -0,0 +1,5 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +int rom_file_process(char * filename_in); diff --git a/gbdk-support/romusage/src/romusage.c b/gbdk-support/romusage/src/romusage.c @@ -0,0 +1,327 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "logging.h" +#include "banks.h" +#include "banks_color.h" +#include "map_file.h" +#include "noi_file.h" +#include "ihx_file.h" +#include "cdb_file.h" +#include "rom_file.h" + +#define VERSION "version 1.2.8" + +void static display_cdb_warning(void); +void static display_help(void); +int handle_args(int argc, char * argv[]); +static bool matches_extension(char *, char *); +static void init(void); +void cleanup(void); + + +char filename_in[MAX_STR_LEN] = {'\0'}; +int show_help_and_exit = false; + + +static void display_cdb_warning() { + printf("\n" + " ************************ NOTICE ************************ \n" + " .cdb output ONLY counts (most) data from C sources. \n" + " It cannot count functions and data from ASM and LIBs. \n" + " Bank totals may be incorrect/missing. (-nB to hide this) \n" + " ************************ NOTICE ************************ \n"); +} + +static void display_help(void) { + fprintf(stdout, + "romusage input_file.[map|noi|ihx|cdb|.gb[c]|.pocket|.duck|.gg|.sms] [options]\n" + VERSION", by bbbbbr\n" + "\n" + "Options\n" + "-h : Show this help\n" + "-p:SMS_GG : Set platform to GBDK SMS/Game Gear (changes memory map templates)\n" + "\n" + "-a : Show Areas in each Bank. Optional sort by, address:\"-aA\" or size:\"-aS\" \n" + "-g : Show a small usage graph per bank (-gA for ascii style)\n" + "-G : Show a large usage graph per bank (-GA for ascii style)\n" + "-B : Brief (summarized) output for banked regions. Auto scales max bank\n" + " shows [Region]_[Max Used Bank] / [auto-sized Max Bank Num]\n" + "-F : Force Max ROM and SRAM bank num for -B. (0 based) -F:ROM:SRAM (ex: -F:255:15)\n" + "\n" + "-m : Manually specify an Area -m:NAME:HEXADDR:HEXLENGTH\n" + "-e : Manually specify an Area that should not overlap -e:NAME:HEXADDR:HEXLENGTH\n" + "-E : All areas are exclusive (except HEADERs), warn for any overlaps\n" + "-q : Quiet, no output except warnings and errors\n" + "-Q : Suppress output of warnings and errors\n" + "-R : Return error code for Area warnings and errors\n" + "\n" + "-sR : [Rainbow] Color output (-sRe for Row Ends, -sRd for Center Dimmed, -sRp %% based)\n" + "-sP : Custom Color Palette. Colon separated entries are decimal VT100 color codes\n" + " -sP:DEFAULT:ROM:VRAM:SRAM:WRAM:HRAM (section based color only)\n" + "-sC : Show Compact Output, hide non-essential columns\n" + "-sH : Show HEADER Areas (normally hidden)\n" + "-smROM : Show Merged ROM_0 and ROM_1 output (i.e. bare 32K ROM)\n" + "-smWRAM : Show Merged WRAM_0 and WRAM_1 output (i.e DMG/MGB not CGB)\n" + " -sm* compatible with banked ROM_x or WRAM_x when used with -B\n" + "-sJ : Show JSON output. Some options not applicable. When used, -Q recommended\n" + "-nB : Hide warning banner (for .cdb output)\n" + "-nA : Hide areas (shown by default in .cdb output)\n" + "-z : Hide areas smaller than SIZE -z:DECSIZE\n" + "\n" + "Use: Read a .map, .noi, .cdb or .ihx file to display area sizes\n" + "Example 1: \"romusage build/MyProject.map\"\n" + "Example 2: \"romusage build/MyProject.noi -a -e:STACK:DEFF:100 -e:SHADOW_OAM:C000:A0\"\n" + "Example 3: \"romusage build/MyProject.ihx -g\"\n" + "Example 4: \"romusage build/MyProject.map -q -R\"\n" + "Example 5: \"romusage build/MyProject.noi -sR -sP:90:32:90:35:33:36\"\n" + "Example 6: \"romusage build/MyProject.map -sRp -g -B -F:255:15 -smROM -smWRAM\"\n" + "\n" + "Notes:\n" + " * GBDK / RGBDS map file format detection is automatic.\n" + " * Estimates are as close as possible, but may not be complete.\n" + " Unless specified with -m/-e they *do not* factor regions lacking\n" + " complete ranges in the Map/Noi/Ihx file, for example Shadow OAM and Stack.\n" + " * IHX files can only detect overlaps, not detect memory region overflows.\n" + " * CDB file output ONLY counts (most) data from C sources.\n" + " It cannot count functions and data from ASM and LIBs,\n" + " so bank totals may be incorrect/missing.\n" + " * GB/GBC/ROM files are just guessing, no promises.\n" + ); +} + + +// Default options for Windows Drag and Drop recipient mode +void set_drag_and_drop_mode_defaults(void) { + + set_option_color_mode(OPT_PRINT_COLOR_ROW_ENDS); + banks_output_show_minigraph(true); + set_option_summarized(true); +} + + +int handle_args(int argc, char * argv[]) { + + int i; + + if( argc < 2 ) { + display_help(); + return false; + } + + // Copy input filename (if not preceded with option dash) + if (argv[1][0] != '-') + snprintf(filename_in, sizeof(filename_in), "%s", argv[1]); + + // Start at first optional argument, argc is zero based + for (i = 1; i <= (argc -1); i++ ) { + + if (strstr(argv[i], "-h") == argv[i]) { + display_help(); + show_help_and_exit = true; + return true; // Don't parse further input when -h is used + } else if (strstr(argv[i], "-a") == argv[i]) { + banks_output_show_areas(true); + if (argv[i][2] == 'S') set_option_area_sort(OPT_AREA_SORT_SIZE_DESC); + else if (argv[i][2] == 'A') set_option_area_sort(OPT_AREA_SORT_ADDR_ASC); + + } else if (strstr(argv[i], "-sR") == argv[i]) { + switch (argv[i][ + strlen("-sR")]) { + case 'p': set_option_percentage_based_color(true); break; // Turns on default color mode if not set + case 'e': set_option_color_mode(OPT_PRINT_COLOR_ROW_ENDS); break; + case 'd': set_option_color_mode(OPT_PRINT_COLOR_WHOLE_ROW_DIMMED); break; + case 'w': set_option_color_mode(OPT_PRINT_COLOR_WHOLE_ROW); break; + default: set_option_color_mode(OPT_PRINT_COLOR_DEFAULT); break; + } + } else if (strstr(argv[i], "-sP") == argv[i]) { + if (!set_option_custom_bank_colors(argv[i])) { + fprintf(stdout,"malformed custom color palette: %s\n\n", argv[i]); + display_help(); + return false; + } + } else if (strstr(argv[i], "-sH") == argv[i]) { + banks_output_show_headers(true); + + } else if (strstr(argv[i], "-sC") == argv[i]) { + set_option_show_compact(true); + + } else if (strstr(argv[i], "-sJ") == argv[i]) { + set_option_show_json(true); + + } else if (strstr(argv[i], "-nB") == argv[i]) { + set_option_hide_banners(true); + + } else if (strstr(argv[i], "-nA")) { + set_option_area_sort(OPT_AREA_SORT_HIDE); + + } else if (strstr(argv[i], "-p:SMS_GG") == argv[i]) { + set_option_platform(OPT_PLAT_SMS_GG_GBDK); + + } else if (strstr(argv[i], "-g") == argv[i]) { + banks_output_show_minigraph(true); + if (argv[i][2] == 'A') set_option_display_asciistyle(true); + } else if (strstr(argv[i], "-G") == argv[i]) { + banks_output_show_largegraph(true); + if (argv[i][2] == 'A') set_option_display_asciistyle(true); + } else if (strstr(argv[i], "-E") == argv[i]) { + set_option_all_areas_exclusive(true); + + } else if (strstr(argv[i], "-B") == argv[i]) { + set_option_summarized(true); + } else if (strstr(argv[i], "-F") == argv[i]) { + if (!option_set_displayed_bank_range(argv[i])) { + fprintf(stdout,"Malformed -F forced display max bank range\n\n"); + display_help(); + return false; + } + + } else if (strstr(argv[i], "-smWRAM") == argv[i]) { + set_option_merged_banks(OPT_MERGED_BANKS_WRAM); + } else if (strstr(argv[i], "-smROM") == argv[i]) { + set_option_merged_banks(OPT_MERGED_BANKS_ROM); + + } else if (strstr(argv[i], "-q") == argv[i]) { + set_option_quiet_mode(true); + } else if (strstr(argv[i], "-Q") == argv[i]) { + log_set_level(OUTPUT_LEVEL_QUIET); + } else if (strstr(argv[i], "-R") == argv[i]) { + set_option_error_on_warning(true); + + } else if (strstr(argv[i], "-z:") == argv[i]) { + set_option_area_hide_size( strtol(argv[i] + 3, NULL, 10)); + + } else if ((strstr(argv[i], "-m") == argv[i]) || + (strstr(argv[i], "-e") == argv[i])) { + if (!area_manual_queue(argv[i])) { + fprintf(stdout,"Malformed manual area argument: %s\n\n", argv[i]); + display_help(); + return false; + } + } else if (argv[i][0] == '-') { + fprintf(stdout,"Unknown argument: %s\n\n", argv[i]); + display_help(); + return false; + } + + } + + return true; +} + + +// Case insensitive +static bool matches_extension(char * filename, char * extension) { + + if (strlen(filename) >= strlen(extension)) { + char * str_ext = filename + (strlen(filename) - strlen(extension)); + + return (strncasecmp(str_ext, extension, strlen(extension)) == 0); + } + else + return false; +} + + +static void init(void) { + cdb_init(); + noi_init(); + banks_init(); +} + + +// Register as an exit handler +void cleanup(void) { + cdb_cleanup(); + noi_cleanup(); + banks_cleanup(); +} + + +int main( int argc, char *argv[] ) { + + int ret = EXIT_FAILURE; // Default to failure on exit + + // Register cleanup with exit handler + atexit(cleanup); + + init(); + + #ifdef DRAG_AND_DROP_MODE + // Non-interactive mode, set some reasonable default + set_drag_and_drop_mode_defaults(); + #endif + + if (handle_args(argc, argv)) { + + banks_init_templates(); + area_manual_apply_queued(); + + if (show_help_and_exit) { + ret = EXIT_SUCCESS; + } + else { + // detect file extension + if (matches_extension(filename_in, (char *)".noi")) { + if (noi_file_process_areas(filename_in)) { + banklist_finalize_and_show(); + ret = EXIT_SUCCESS; // Exit with success + } + } else if (matches_extension(filename_in, (char *)".map")) { + if (map_file_process_areas(filename_in)) { + banklist_finalize_and_show(); + ret = EXIT_SUCCESS; // Exit with success + } + } else if (matches_extension(filename_in, (char *)".ihx")) { + if (ihx_file_process_areas(filename_in)) { + banklist_finalize_and_show(); + ret = EXIT_SUCCESS; // Exit with success + } + } else if (matches_extension(filename_in, (char *)".gb" ) || + matches_extension(filename_in, (char *)".gbc" ) || + matches_extension(filename_in, (char *)".sms" ) || + matches_extension(filename_in, (char *)".gg" ) || + matches_extension(filename_in, (char *)".pocket") || + matches_extension(filename_in, (char *)".duck") ) { + // printf("ROM FILE\n"); + if (rom_file_process(filename_in)) { + banklist_finalize_and_show(); + ret = EXIT_SUCCESS; // Exit with success + } + } else if (matches_extension(filename_in, (char *)".cdb")) { + if (cdb_file_process_symbols(filename_in)) { + if (!get_option_hide_banners()) display_cdb_warning(); + + banklist_finalize_and_show(); + + if (!get_option_hide_banners()) display_cdb_warning(); + ret = EXIT_SUCCESS; // Exit with success + } + } + + } + } + + if (ret == EXIT_FAILURE) + printf("Problem with filename or unable to open file! %s\n", filename_in); + + // Override exit code if was set during processing + if (get_exit_error()) + ret = EXIT_FAILURE; + + #ifdef DRAG_AND_DROP_MODE + // Wait for input to keep the console window open after processing + printf("\n\nPress Any Key to Continue\n"); + getchar(); + #endif + + return ret; +} diff --git a/licenses/LICENSE_romusage b/licenses/LICENSE_romusage @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org>
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.