Add script to build gecko code of GCI Loader

- GCI Loader: gecko-gosub + 06 bin + 04 bl to onReadOptionBlock
- place GCI Loader code at 817FE800 since [817FEEA0, 81800000) is used
  by system
- place buffer at 817F1800 (instead of 817F1000+4) since there seems to
  be some alignment issue
- Assume file size == 0x2000*6 since fileInfo->length seems to be 0
This commit is contained in:
sup39 2023-02-10 23:20:40 +09:00
parent 638d27b425
commit 23e5c658ad
21 changed files with 322 additions and 77 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
*.o
out
__pycache__

View file

@ -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 $@
@ -69,3 +86,4 @@ check-set-%:
.PHONY: clean
clean:
$(RM) -rv $(OUT_DIR) $(OBJ_FILES)
$(MAKE) -C$(GECKO_DIR) $@

View file

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

27
gecko/GeckoFactory.py Normal file
View file

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

40
gecko/Makefile Normal file
View file

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

101
gecko/bin2gct.py Normal file
View file

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

15
gecko/code/gecko-gosub.s Normal file
View file

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

View file

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

2
gecko/hook.csv Normal file
View file

@ -0,0 +1,2 @@
symbol,GMSE01,GMSJ01,GMSP01,GMSJ0A
onReadOptionBlock,802A96E0,801069F4,802A96E0,80291538
1 symbol GMSE01 GMSJ01 GMSP01 GMSJ0A
2 onReadOptionBlock 802A96E0 801069F4 802A96E0 80291538

View file

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

View file

@ -8,8 +8,12 @@
#define CARD_SLOT_A 0
#define CARD_SLOT_B 1
#define MEM1_START 0x8000000
#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
{

View file

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

View file

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

5
ldscript/GMSE01.ld Normal file
View file

@ -0,0 +1,5 @@
mount_ = 0x802b2914;
open_ = 0x802b20d0;
CARDOpen = 0x8035938c;
CARDRead = 0x80359d20;
CARDClose = 0x80359504;

5
ldscript/GMSJ01.ld Normal file
View file

@ -0,0 +1,5 @@
mount_ = 0x80107b50;
open_ = 0x801072f4;
CARDOpen = 0x800a3cac;
CARDRead = 0x800a4640;
CARDClose = 0x800a3e24;

5
ldscript/GMSJ0A.ld Normal file
View file

@ -0,0 +1,5 @@
mount_ = 0x80292634;
open_ = 0x80291e38;
CARDOpen = 0x80338c8c;
CARDRead = 0x80339620;
CARDClose = 0x80338e04;

5
ldscript/GMSP01.ld Normal file
View file

@ -0,0 +1,5 @@
mount_ = 0x802aa824;
open_ = 0x802a9fe0;
CARDOpen = 0x803515ac;
CARDRead = 0x80351f40;
CARDClose = 0x80351724;

14
ldscript/sms.ld Normal file
View file

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

14
sms.ld
View file

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

View file

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

View file

@ -18,22 +18,33 @@
#define ADDR_GECKO_HEAP_START 0x817F0000
#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);
}