mirror of
https://github.com/gbdk-2020/gbdk-2020.git
synced 2026-02-20 00:32:21 +01:00
tools: add romusage
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
15
Makefile
15
Makefile
@@ -153,6 +153,8 @@ endif
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/makebin TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory
|
||||
@echo Building png2hicolorgb
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/png2hicolorgb TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory
|
||||
@echo Building romusage
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/romusage TOOLSPREFIX=$(TOOLSPREFIX) TARGETDIR=$(TARGETDIR)/ --no-print-directory
|
||||
@echo
|
||||
|
||||
gbdk-support-install: gbdk-support-build $(BUILDDIR)/bin
|
||||
@@ -189,6 +191,9 @@ gbdk-support-install: gbdk-support-build $(BUILDDIR)/bin
|
||||
@echo Installing png2hicolorgb
|
||||
@cp $(GBDKSUPPORTDIR)/png2hicolorgb/png2hicolorgb$(EXEEXTENSION) $(BUILDDIR)/bin/png2hicolorgb$(EXEEXTENSION)
|
||||
@$(TARGETSTRIP) $(BUILDDIR)/bin/png2hicolorgb$(EXEEXTENSION)
|
||||
@echo Installing romusage
|
||||
@cp $(GBDKSUPPORTDIR)/romusage/romusage$(EXEEXTENSION) $(BUILDDIR)/bin/romusage$(EXEEXTENSION)
|
||||
@$(TARGETSTRIP) $(BUILDDIR)/bin/romusage$(EXEEXTENSION)
|
||||
@echo
|
||||
|
||||
gbdk-support-clean:
|
||||
@@ -209,6 +214,8 @@ gbdk-support-clean:
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/makebin clean
|
||||
@echo Cleaning png2hicolorgb
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/png2hicolorgb clean
|
||||
@echo Cleaning romusage
|
||||
@$(MAKE) -C $(GBDKSUPPORTDIR)/romusage clean
|
||||
@echo
|
||||
|
||||
# Rules for gbdk-lib
|
||||
@@ -473,7 +480,7 @@ ifneq (,$(wildcard $(BUILDDIR)/bin/))
|
||||
echo \@anchor png2asset-settings >> $(TOOLCHAIN_DOCS_FILE);
|
||||
echo \# png2asset settings >> $(TOOLCHAIN_DOCS_FILE);
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE);
|
||||
$(BUILDDIR)/bin/png2asset >> $(TOOLCHAIN_DOCS_FILE) 2>&1
|
||||
$(BUILDDIR)/bin/png2asset >> $(TOOLCHAIN_DOCS_FILE) 2>&1 || true
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE)
|
||||
# png2hicolorgb
|
||||
echo \@anchor png2hicolorgb-settings >> $(TOOLCHAIN_DOCS_FILE);
|
||||
@@ -481,6 +488,12 @@ ifneq (,$(wildcard $(BUILDDIR)/bin/))
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE);
|
||||
$(BUILDDIR)/bin/png2hicolorgb -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE)
|
||||
# romusage
|
||||
echo \@anchor romusage-settings >> $(TOOLCHAIN_DOCS_FILE);
|
||||
echo \# romusage settings >> $(TOOLCHAIN_DOCS_FILE);
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE);
|
||||
$(BUILDDIR)/bin/romusage -h >> $(TOOLCHAIN_DOCS_FILE) 2>&1
|
||||
echo \`\`\` >> $(TOOLCHAIN_DOCS_FILE)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
47
gbdk-support/romusage/Makefile
Normal file
47
gbdk-support/romusage/Makefile
Normal 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
|
||||
|
||||
2
gbdk-support/romusage/src/bank_list.h
Normal file
2
gbdk-support/romusage/src/bank_list.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "banks.h"
|
||||
void banklist_printall(bank_item []);
|
||||
135
gbdk-support/romusage/src/bank_templates.c
Normal file
135
gbdk-support/romusage/src/bank_templates.c
Normal 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;
|
||||
}
|
||||
12
gbdk-support/romusage/src/bank_templates.h
Normal file
12
gbdk-support/romusage/src/bank_templates.h
Normal 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
|
||||
790
gbdk-support/romusage/src/banks.c
Normal file
790
gbdk-support/romusage/src/banks.c
Normal 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
|
||||
}
|
||||
}
|
||||
113
gbdk-support/romusage/src/banks.h
Normal file
113
gbdk-support/romusage/src/banks.h
Normal 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
|
||||
179
gbdk-support/romusage/src/banks_color.c
Normal file
179
gbdk-support/romusage/src/banks_color.c
Normal 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
|
||||
}
|
||||
77
gbdk-support/romusage/src/banks_color.h
Normal file
77
gbdk-support/romusage/src/banks_color.h
Normal 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
|
||||
345
gbdk-support/romusage/src/banks_print.c
Normal file
345
gbdk-support/romusage/src/banks_print.c
Normal 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");
|
||||
}
|
||||
14
gbdk-support/romusage/src/banks_print.h
Normal file
14
gbdk-support/romusage/src/banks_print.h
Normal 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
|
||||
209
gbdk-support/romusage/src/banks_summarized.c
Normal file
209
gbdk-support/romusage/src/banks_summarized.c
Normal 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);
|
||||
}
|
||||
13
gbdk-support/romusage/src/banks_summarized.h
Normal file
13
gbdk-support/romusage/src/banks_summarized.h
Normal 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
|
||||
294
gbdk-support/romusage/src/cdb_file.c
Normal file
294
gbdk-support/romusage/src/cdb_file.c
Normal 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;
|
||||
}
|
||||
23
gbdk-support/romusage/src/cdb_file.h
Normal file
23
gbdk-support/romusage/src/cdb_file.h
Normal 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);
|
||||
258
gbdk-support/romusage/src/common.c
Normal file
258
gbdk-support/romusage/src/common.c
Normal 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;
|
||||
}
|
||||
102
gbdk-support/romusage/src/common.h
Normal file
102
gbdk-support/romusage/src/common.h
Normal 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
|
||||
309
gbdk-support/romusage/src/ihx_file.c
Normal file
309
gbdk-support/romusage/src/ihx_file.c
Normal 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;
|
||||
}
|
||||
|
||||
5
gbdk-support/romusage/src/ihx_file.h
Normal file
5
gbdk-support/romusage/src/ihx_file.h
Normal 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);
|
||||
71
gbdk-support/romusage/src/list.c
Normal file
71
gbdk-support/romusage/src/list.c
Normal 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);
|
||||
}
|
||||
|
||||
20
gbdk-support/romusage/src/list.h
Normal file
20
gbdk-support/romusage/src/list.h
Normal 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
|
||||
55
gbdk-support/romusage/src/logging.c
Normal file
55
gbdk-support/romusage/src/logging.c
Normal 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();
|
||||
}
|
||||
23
gbdk-support/romusage/src/logging.h
Normal file
23
gbdk-support/romusage/src/logging.h
Normal 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
|
||||
148
gbdk-support/romusage/src/map_file.c
Normal file
148
gbdk-support/romusage/src/map_file.c
Normal 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;
|
||||
}
|
||||
|
||||
8
gbdk-support/romusage/src/map_file.h
Normal file
8
gbdk-support/romusage/src/map_file.h
Normal 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);
|
||||
178
gbdk-support/romusage/src/noi_file.c
Normal file
178
gbdk-support/romusage/src/noi_file.c
Normal 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;
|
||||
}
|
||||
22
gbdk-support/romusage/src/noi_file.h
Normal file
22
gbdk-support/romusage/src/noi_file.h
Normal 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);
|
||||
171
gbdk-support/romusage/src/rom_file.c
Normal file
171
gbdk-support/romusage/src/rom_file.c
Normal 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;
|
||||
}
|
||||
5
gbdk-support/romusage/src/rom_file.h
Normal file
5
gbdk-support/romusage/src/rom_file.h
Normal 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);
|
||||
327
gbdk-support/romusage/src/romusage.c
Normal file
327
gbdk-support/romusage/src/romusage.c
Normal 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
24
licenses/LICENSE_romusage
Normal 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>
|
||||
Reference in New Issue
Block a user