diff --git a/.gitignore b/.gitignore index 80f93b0..6ba2d96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.o -out \ No newline at end of file +out +__pycache__ diff --git a/Makefile b/Makefile index da996ac..d017fd5 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +MAKE := make + CC := powerpc-eabi-gcc CFLAGS := -O3 -I. -Wa,-mregnames,-mgekko -Wall -fno-asynchronous-unwind-tables -fno-unwind-tables -fverbose-asm -gdwarf @@ -12,20 +14,28 @@ LD := powerpc-eabi-ld LDFLAGS := --unresolved-symbols=ignore-in-object-files --enable-non-contiguous-regions -EB --nmagic OBJCOPY := powerpc-eabi-objcopy -OBJCOPYFLAGS := -O binary --add-gnu-debuglink=sms.ld --gap-fill 0x00 --pad-to 0x81800000 +#OBJCOPYFLAGS = -O binary $(foreach e,$(LD_FILES),--add-gnu-debuglink=$e) --gap-fill 0x00 --pad-to 0x81800000 +OBJCOPYFLAGS = -O binary $(foreach e,$(LD_FILES),--add-gnu-debuglink=$e) + +PY := python3 +BIN2GCT := $(PY) gecko/bin2gct.py SRC_DIR := source INC_DIR := include +LD_DIR := ldscript OUT_DIR := out OBJ_DIR := obj -LD_FILE := sms.ld -OUT_MAP := main.map +GECKO_DIR := gecko +GECKO_OUT_DIR := out +OUT_MAP := main.map OUT_MAIN := main.out OUT_BIN := main.bin OUT_LST := main.lst OUT_ASM := main.asm +OUT_LOADER_TXT = loader.txt +OUT_LOADER_GCT = loader.gct GMSE01 := 1 GMSJ01 := 2 @@ -36,11 +46,13 @@ TARGET_VERSION = $($(GAME_VERSION)) SRC_FILES := $(wildcard source/*.c) OBJ_FILES := $(SRC_FILES:.c=.o) +LD_FILES := $(LD_DIR)/sms.ld $(LD_DIR)/$(GAME_VERSION).ld +GECKO_FILE_LOADER := $(GECKO_DIR)/$(GECKO_OUT_DIR)/gecko-gosub.bin +GECKO_FILES := $(GECKO_FILE_LOADER) $(GECKO_DIR)/$(GECKO_OUT_DIR)/gecko-return.bin OBJS := main.o card.o -blockCount := 7 -all: $(OUT_DIR)/$(OUT_BIN) $(OUT_DIR)/$(OUT_LST) $(OUT_DIR)/$(OUT_ASM) +all: $(OUT_DIR)/$(OUT_BIN) $(OUT_DIR)/$(OUT_LST) $(OUT_DIR)/$(OUT_ASM) $(OUT_DIR)/$(OUT_LOADER_TXT) $(OUT_DIR)/$(OUT_ASM): $(OUT_DIR)/$(OUT_BIN) $(OD) $(ODASMFLAGS) $(OUT_DIR)/$(OUT_BIN) | grep -Pe "^\t(?!Address)" | sed 's/^\t//' > $(OUT_DIR)/$(OUT_ASM) @@ -51,12 +63,17 @@ $(OUT_DIR)/$(OUT_LST): $(OUT_DIR)/$(OUT_BIN) $(OUT_DIR)/$(OUT_BIN): $(OUT_DIR)/$(OUT_MAIN) $(OBJCOPY) $(OBJCOPYFLAGS) $< $@ -$(OUT_DIR)/$(OUT_MAIN): $(OBJ_FILES) $(LD_FILE) $(OUT_DIR) - $(LD) $(LDFLAGS) -o $@ -T $(LD_FILE) -Map $(OUT_DIR)/$(OUT_MAP) $(OBJ_FILES) +$(OUT_DIR)/$(OUT_MAIN): check-set-GAME_VERSION $(OBJ_FILES) $(LD_FILES) $(OUT_DIR) + $(LD) $(LDFLAGS) -o $@ -T $(LD_FILES) -Map $(OUT_DIR)/$(OUT_MAP) $(OBJ_FILES) -$(OBJ_FILES): $(SRC_FILES) check-set-TARGET_VERSION +$(OBJ_FILES): $(SRC_FILES) check-set-GAME_VERSION #check-set-TARGET_VERSION $(CC) $(CFLAGS) -D VERSION=$(TARGET_VERSION) -I $(INC_DIR) -o $@ -c $(@:.o=.c) +$(OUT_DIR)/$(OUT_LOADER_TXT): $(OUT_DIR)/$(OUT_BIN) $(OUT_DIR)/$(OUT_MAP) $(GECKO_FILE_LOADER) | $(OUT_DIR) + $(BIN2GCT) hook:$(OUT_DIR)/$(OUT_MAP):$(OUT_DIR)/$(OUT_BIN):onReadOptionBlock C0:$(GECKO_FILE_LOADER) > $@ +$(GECKO_FILES): + $(MAKE) -C$(GECKO_DIR) OUT_DIR=$(GECKO_OUT_DIR) $(patsubst $(GECKO_DIR)/%,%,$@) + $(OUT_DIR): mkdir -p $@ @@ -68,4 +85,5 @@ check-set-%: .PHONY: clean clean: - $(RM) -rv $(OUT_DIR) $(OBJ_FILES) \ No newline at end of file + $(RM) -rv $(OUT_DIR) $(OBJ_FILES) + $(MAKE) -C$(GECKO_DIR) $@ diff --git a/build-all.sh b/build-all.sh index ab7f4b2..0c6ab66 100755 --- a/build-all.sh +++ b/build-all.sh @@ -1,6 +1,6 @@ #!/bin/bash -make GAME_VERSION=GMSE01 OUT_DIR=out/gmse01 -make GAME_VERSION=GMSJ01 OUT_DIR=out/gmsj01 -make GAME_VERSION=GMSP01 OUT_DIR=out/gmsp01 -make GAME_VERSION=GMSJ0A OUT_DIR=out/gmsj0a \ No newline at end of file +make GAME_VERSION=GMSE01 OUT_DIR=out/GMSE01 +make GAME_VERSION=GMSJ01 OUT_DIR=out/GMSJ01 +make GAME_VERSION=GMSP01 OUT_DIR=out/GMSP01 +make GAME_VERSION=GMSJ0A OUT_DIR=out/GMSJ0A diff --git a/gecko/GeckoFactory.py b/gecko/GeckoFactory.py new file mode 100644 index 0000000..3782a69 --- /dev/null +++ b/gecko/GeckoFactory.py @@ -0,0 +1,27 @@ +def int2bytes(x): + return x.to_bytes(4, 'big') + +def makeC0(arg): + ## read bin file + with open(arg, 'rb') as f: + raw = f.read() + ## .align 2 + if len(raw) % 4: + raw += b'\x00'*(4 - len(raw)%4) + ## add `blr` to align 3 + if len(raw) % 8: + raw += b'\x4E\x80\x00\x20' + ## add C0 header + raw = b'\xC0\x00\x00\x00' + int2bytes(len(raw) >> 3) + raw + return raw + +def make06(dst, fn): + ## read bin file + with open(fn, 'rb') as f: + raw = f.read() + size06 = len(raw) + ## .align 3 + if len(raw) % 8: + raw += b'\x00'*(8 - len(raw)%8) + ## header + return int2bytes(0x06<<24 | dst & 0x1ffffff) + int2bytes(size06) + raw diff --git a/gecko/Makefile b/gecko/Makefile new file mode 100644 index 0000000..595eca2 --- /dev/null +++ b/gecko/Makefile @@ -0,0 +1,40 @@ +AS := powerpc-eabi-as +ASFLAGS := -mregnames -mgekko + +LD := powerpc-eabi-ld +LDFLAGS := --unresolved-symbols=ignore-in-object-files --enable-non-contiguous-regions -EB --nmagic + +OBJCOPY := powerpc-eabi-objcopy +OBJCOPYFLAGS = -O binary + +PY := python3 +BIN2GCT := $(PY) bin2gct.py + +LD_DIR := ../ldscript +LD_FILES := $(LD_DIR)/sms.ld + +SRC_DIR := code +OUT_DIR := out + +SRC_FILES := $(wildcard $(SRC_DIR)/*.s) +OBJ_FILES := $(SRC_FILES:.s=.o) +LDO_FILES := $(OBJ_FILES:.o=.ld.o) +OUT_TXT := $(patsubst $(SRC_DIR)/%.s,$(OUT_DIR)/%.txt,$(SRC_FILES)) + +all: $(OUT_TXT) + +%.o: %.s + $(AS) $(ASFLAGS) -o $@ -c $^ +%.ld.o: %.o + $(LD) $(LDFLAGS) -o $@ -T $(LD_FILES) $^ +$(OUT_DIR)/%.bin: $(SRC_DIR)/%.ld.o | $(OUT_DIR) + $(OBJCOPY) $(OBJCOPYFLAGS) $< $@ +$(OUT_DIR)/%.txt: $(OUT_DIR)/%.bin | $(OUT_DIR) + $(BIN2GCT) C0:$^ > $@ + +$(OUT_DIR): + mkdir -p $@ + +.PHONY: clean +clean: + $(RM) -rv $(OUT_DIR) $(OBJ_FILES) $(LDO_FILES) diff --git a/gecko/bin2gct.py b/gecko/bin2gct.py new file mode 100644 index 0000000..c3ffd01 --- /dev/null +++ b/gecko/bin2gct.py @@ -0,0 +1,101 @@ +import sys +import os +from GeckoFactory import int2bytes, make06, makeC0 + +validArguments = ''' + C0:{input.bin} + hook:{input.map}:{input.bin}:{symbol=fromAddr}... +'''[1:-1] + +def exitInvalidSyntax(arg): + print('Invalid argument `%s`\nValid arguments:\n%s'%(arg, validArguments)) + sys.exit(1) +def exitMessage(msg): + print(msg) + sys.exit(1) + +def printCode(raw, file=sys.stdout): + assert len(raw)%8 == 0, \ + f'Expect code with length of multiple of 8, got {len(raw)}' + for i in range(0, len(raw), 8): + print(raw[i:i+4].hex().upper(), raw[i+4:i+8].hex().upper(), file=file) + +def loadHookDB(fn): + import csv + with open(fn) as f: + reader = csv.DictReader(f) + return { + row['symbol']: row + for row in reader + } + +def parseMap(fn): + with open(fn) as f: + sections = {} + symbols = {} + for line in f: + args = line.split() + try: + if line.startswith('.') and len(args) == 3: + # .text 0x00000000817fe800 0x1dc + sections[args[0]] = int(args[1], 16) + elif len(args) == 2: + # 0x00000000817fe800 loadCard + symbols[args[1]] = int(args[0], 16) + except: pass + return sections, symbols + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Usage: %s {ARG}...\nARG:\n%s'%(sys.argv[0], validArguments)) + sys.exit(1) + + __dirname = os.path.dirname(__file__) + hookDB = loadHookDB(os.path.join(__dirname, './hook.csv')) + + code = b'' + for arg0 in sys.argv[1:]: + toks = arg0.split(':', 1) + if len(toks) != 2: exitInvalidSyntax(arg) + ct, arg = toks + if ct == 'C0': + code += makeC0(arg) + elif ct == 'hook': + args = arg.split(':') + if len(args) < 3: exitInvalidSyntax(arg) + fnMap, fnBin, *entries = args + ## parse map file + sections, symbols = parseMap(fnMap) + assert '.text' in sections, f'.text section does not exists in file {fnMap}' + dst = sections['.text'] + ## 06 + code += make06(dst, fnBin) + ## 04 entries + for entry in entries: + toks = entry.split('=') + name = toks[0] + if len(toks) == 1: + version = os.environ.get('GAME_VERSION') + if not version: exitMessage('Environment variable `GAME_VERSION` is not set. Please set the GAME_VERSION environment variable or specify the address manually') + if name not in hookDB: exitMessage('The address for symbol `%s` is unknown. Please specify it manually\nSyntax: {name}={addr}'%name) + hook = hookDB[name] + if version not in hook: exitMessage(f'Invalid GAME_VERSION: {version}') + addr0 = int(hook[version], 16) + elif len(toks) == 2: + try: + addr0 = int(toks[1], 16) + except ValueError: + exitInvalidSyntax(arg) + else: + exitInvalidSyntax(arg) + assert name in symbols, f'symbol `{name}` does not exists in file {fnMap}' + addr1 = symbols[name] + dis = addr1 - addr0 + assert -0x200_0000 <= dis < 0x200_0000, 'too far to branch from %s(%08X) to %08X'%(name, addr0, addr1) + ## make 04 + code += int2bytes(0x0400_0000 | addr0 & 0x1ff_ffff) + code += int2bytes(0x4800_0001 | dis & 0x3ff_ffff) + else: + exitInvalidSyntax(arg) + + printCode(code) diff --git a/gecko/code/gecko-gosub.s b/gecko/code/gecko-gosub.s new file mode 100644 index 0000000..e095477 --- /dev/null +++ b/gecko/code/gecko-gosub.s @@ -0,0 +1,15 @@ +## check status +### r3 = GCTDST + lis r3, GCTDST@ha + lwzu r0, GCTDST@l(r3) +### return if not DONE(2) + cmpwi r0, 2 + bnelr + +## backup r15 (pointer to current gecko instruction) + add r0, r15, r4 + stw r0, 4(r3) +## update r15 = dst + 8(GCTDST.code-r3) - r4(this C0 code size) + addi r15, r3, 8 + sub r15, r15, r4 + blr diff --git a/gecko/code/gecko-return.s b/gecko/code/gecko-return.s new file mode 100644 index 0000000..ad3f3c1 --- /dev/null +++ b/gecko/code/gecko-return.s @@ -0,0 +1,6 @@ +## restore r15 (pointer to current gecko instruction) + lis r3, GCTDST+4@ha + lwz r15, GCTDST+4@l(r3) +## update r15 = restored r15 - r4(this C0 code size) + sub r15, r15, r4 + blr diff --git a/gecko/hook.csv b/gecko/hook.csv new file mode 100644 index 0000000..ca5c713 --- /dev/null +++ b/gecko/hook.csv @@ -0,0 +1,2 @@ +symbol,GMSE01,GMSJ01,GMSP01,GMSJ0A +onReadOptionBlock,802A96E0,801069F4,802A96E0,80291538 diff --git a/include/card.h b/include/card.h index b34ce1d..4eccc44 100644 --- a/include/card.h +++ b/include/card.h @@ -1,11 +1,14 @@ - - #include "gcn.h" #include "sms.h" #ifndef __CARD_H__ #define __CARD_H__ -bool loadCard(TCardManager *cm, CARDFileInfo *fileInfo, char *fileName, u32 *dst); +#define LOAD_ERR_MOUNT -1 +#define LOAD_ERR_OPEN -2 +#define LOAD_ERR_SIZE -3 +#define LOAD_ERR_FILE_NOT_EXIST -4 +#define LOAD_ERR_READ -5 +int loadCard(TCardManager *cm, CARDFileInfo *fileInfo, char *fileName, u32 *dst); -#endif \ No newline at end of file +#endif diff --git a/include/gcn.h b/include/gcn.h index e7c226b..c85e055 100644 --- a/include/gcn.h +++ b/include/gcn.h @@ -8,8 +8,12 @@ #define CARD_SLOT_A 0 #define CARD_SLOT_B 1 -#define MEM1_START 0x8000000 -#define MEM1_END 0x81800000 +#define MEM1_START 0x80000000 +#define MEM1_END 0x81800000 +// The area beyond ArenaHi is used by system +// and should not be modified +// TODO version? +#define ArenaHi 0x817FEEA0 typedef struct CARDFileInfo { @@ -30,4 +34,4 @@ s32 CARDUnmount(s32 chan); // s32 CARDProbeEx(s32 chan, s32 *memSize, s32 *sectorSize); // s32 CARDMount(s32 chan, void *workArea, CARDCallback detachCallback); -#endif \ No newline at end of file +#endif diff --git a/include/sms.h b/include/sms.h index e42aef7..e8f88a9 100644 --- a/include/sms.h +++ b/include/sms.h @@ -1,20 +1,6 @@ #ifndef __SMS_H__ #define __SMS_H__ -/** For NTSC-J 1.0: - * - * mount_ 80107b50 - * open_ 801072f4 - * CARDOpen 800a3cac - * CARDRead 800a4640 - * CARDClose 800a3e24 - * - * onReadOptionBlock 801069f4 - */ - -// static void *gpApplication = (void *)0x803e9700; -// static void *gpCardManager = (void *)0x8040a2b4; - typedef struct { int slot; @@ -23,4 +9,4 @@ typedef struct int mount_(TCardManager *this, bool); int open_(TCardManager *this, CARDFileInfo *fileInfo); -#endif \ No newline at end of file +#endif diff --git a/include/typedefs.h b/include/typedefs.h index 1df7887..13babcb 100644 --- a/include/typedefs.h +++ b/include/typedefs.h @@ -2,6 +2,8 @@ #define __TYPEDEF_H__ typedef int bool; +#define true 1 +#define false 0 typedef unsigned long long u64; typedef signed long long s64; @@ -12,4 +14,4 @@ typedef signed short s16; typedef unsigned char u8; typedef signed char s8; -#endif \ No newline at end of file +#endif diff --git a/ldscript/GMSE01.ld b/ldscript/GMSE01.ld new file mode 100644 index 0000000..c801869 --- /dev/null +++ b/ldscript/GMSE01.ld @@ -0,0 +1,5 @@ +mount_ = 0x802b2914; +open_ = 0x802b20d0; +CARDOpen = 0x8035938c; +CARDRead = 0x80359d20; +CARDClose = 0x80359504; diff --git a/ldscript/GMSJ01.ld b/ldscript/GMSJ01.ld new file mode 100644 index 0000000..b4953a6 --- /dev/null +++ b/ldscript/GMSJ01.ld @@ -0,0 +1,5 @@ +mount_ = 0x80107b50; +open_ = 0x801072f4; +CARDOpen = 0x800a3cac; +CARDRead = 0x800a4640; +CARDClose = 0x800a3e24; diff --git a/ldscript/GMSJ0A.ld b/ldscript/GMSJ0A.ld new file mode 100644 index 0000000..8202a66 --- /dev/null +++ b/ldscript/GMSJ0A.ld @@ -0,0 +1,5 @@ +mount_ = 0x80292634; +open_ = 0x80291e38; +CARDOpen = 0x80338c8c; +CARDRead = 0x80339620; +CARDClose = 0x80338e04; diff --git a/ldscript/GMSP01.ld b/ldscript/GMSP01.ld new file mode 100644 index 0000000..dfc9cd9 --- /dev/null +++ b/ldscript/GMSP01.ld @@ -0,0 +1,5 @@ +mount_ = 0x802aa824; +open_ = 0x802a9fe0; +CARDOpen = 0x803515ac; +CARDRead = 0x80351f40; +CARDClose = 0x80351724; diff --git a/ldscript/sms.ld b/ldscript/sms.ld new file mode 100644 index 0000000..e16880d --- /dev/null +++ b/ldscript/sms.ld @@ -0,0 +1,14 @@ +/** + * There seems to be an alignment issue + * addr of GCTDST.code may need to be a multiple of 0x200 or something? + */ +GCTDST = 0x817F1800 - 8; + +SECTIONS { + . = 0x817FE800; /* TODO: Determine Best Address */ + .text : { *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { *(.bss) } + .sdata : { *(.sdata) } +} diff --git a/sms.ld b/sms.ld deleted file mode 100644 index 5a75926..0000000 --- a/sms.ld +++ /dev/null @@ -1,14 +0,0 @@ -SECTIONS { - . = 0x817FF000; /* TODO: Determine Best Address */ - .text : { *(.text) } - .rodata : { *(.rodata) } - .data : { *(.data) } - .bss : { *(.bss) } - .sdata : { *(.sdata) } -} - -mount_ = 0x80107b50; -open_ = 0x801072f4; -CARDOpen = 0x800a3cac; -CARDRead = 0x800a4640; -CARDClose = 0x800a3e24; diff --git a/source/card.c b/source/card.c index 1234db1..1325f8c 100644 --- a/source/card.c +++ b/source/card.c @@ -1,23 +1,32 @@ -#include "gcn.h" -#include "sms.h" +#include "card.h" -bool loadCard(TCardManager *cm, CARDFileInfo *fileInfo, char *fileName, u32 *dst) +// TODO: we need to keep some space to store this loader code +// #define DST_END ArenaHi +#define DST_END 0x817FE800 + +int loadCard(TCardManager *cm, CARDFileInfo *fileInfo, char *fileName, u32 *dst) { + int rc; + // Mount the card - if (!mount_(cm, 1)) - return -1; + if (mount_(cm, 1)) + return LOAD_ERR_MOUNT; // Open the card - if (!CARDOpen(cm->slot, fileName, fileInfo)) - return -1; + if ((rc = CARDOpen(cm->slot, fileName, fileInfo))) { + return rc == -4 ? LOAD_ERR_FILE_NOT_EXIST : LOAD_ERR_OPEN; + } // Too many codes - if (fileInfo->length > (MEM1_END - (u32)dst)) - return -1; + // TODO fileInfo->length seems to be 0? + // int fileSize = fileInfo->length; + int fileSize = 0x2000 * 6; + if (fileSize > (DST_END - (u32)dst)) + return LOAD_ERR_SIZE; // Read codes into destination address - if (!CARDRead(fileInfo, dst, fileInfo->length, 0)) - return -1; + if (CARDRead(fileInfo, dst, fileSize, 0)) + return LOAD_ERR_READ; // Done CARDClose(fileInfo); diff --git a/source/main.c b/source/main.c index c4e2700..899b1dc 100644 --- a/source/main.c +++ b/source/main.c @@ -16,24 +16,35 @@ // See: https://github.com/BitPatty/gctGenerator/blob/375ba5eb73e50894d4e765fdfddbe004e3b4949d/Readme.md#reserved-memory // Range: 0x817F0000 - 0x817F1000 #define ADDR_GECKO_HEAP_START 0x817F0000 -#define ADDR_GECKO_HEAP_SIZE 0x00001000 +#define ADDR_GECKO_HEAP_SIZE 0x00001000 #define ADDR_GECKO_HEAP_END (ADDR_GECKO_HEAP_START + ADDR_GECKO_HEAP_SIZE) -#define PTR_FLG_LOADED (bool *)ADDR_GECKO_HEAP_END -#define GECKO_CODE_START (ADDR_GECKO_HEAP_END + sizeof(*PTR_FLG_LOADED)) +// GCTDST = 0x817F1000; // in ldscripts/sms.ld + +extern struct GCTBuffer { + // =0: initialized + // >0: Loading(1), Done(2) + // <0: error + int status; + // return address to the original Gecko handler + void *returnAddr; + // actual Gecko code + u32 code[0]; +} GCTDST; + +#define GCT_LOAD_LOADING 1 +#define GCT_LOAD_DONE 2 void loadCodes(TCardManager *cm, CARDFileInfo *fileInfo) { - *PTR_FLG_LOADED = -1; - - if (loadCard(cm, fileInfo, FILENAME, (void *)GECKO_CODE_START)) - *PTR_FLG_LOADED = 0; + GCTDST.status = GCT_LOAD_LOADING; + // load card + int rc = loadCard(cm, fileInfo, FILENAME, GCTDST.code); + GCTDST.status = rc == 0 ? GCT_LOAD_DONE : rc; // DONE or Error code } -// int onReadOptionBlock(TCardManager *this, CARDFileInfo *fileInfo) -// { -// if (*PTR_FLG_LOADED) -// return open_(this, fileInfo); - -// loadCodes(this, fileInfo); -// return open_(this, fileInfo); -// } +int onReadOptionBlock(TCardManager *this, CARDFileInfo *fileInfo) +{ + if (GCTDST.status <= 0) // Initialized(=0) or Error(<0) + loadCodes(this, fileInfo); + return open_(this, fileInfo); +}