tools: add romusage

This commit is contained in:
bbbbbr
2024-01-02 03:19:57 -08:00
parent 623f0518eb
commit c5ffc33c14
35 changed files with 4055 additions and 5 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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__

View File

@@ -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

View File

@@ -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.
```

View File

@@ -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

View File

@@ -0,0 +1,2 @@
#include "banks.h"
void banklist_printall(bank_item []);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

24
licenses/LICENSE_romusage Normal file
View File

@@ -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>