diff --git a/changelog.md b/changelog.md index dabe603..dea1171 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # [Tonyhax International](readme.md) -> Changelog +## Version 1.5.7 (10/7/2024) + +* [tonyhax-international-v1.5.7](https://github.com/alex-free/tonyhax/releases/download/v1.5.7i/tonyhax-international-v1.5.7.zip) + +---------------------------------- + +Changes: + +* Bug fix: v1.5.6 introduced a regression that caused some games to send an empty GameID string. This has been addressed with better boot file parsing for GameID. + ## Version 1.5.6 (10/6/2024) * [tonyhax-international-v1.5.6](https://github.com/alex-free/tonyhax/releases/download/v1.5.6i/tonyhax-international-v1.5.6.zip) diff --git a/docs/tonyhax-vs-tonyhax-international-diffs/tonyhax-international-v1.5.8.diff b/docs/tonyhax-vs-tonyhax-international-diffs/tonyhax-international-v1.5.8.diff new file mode 100644 index 0000000..bb50914 --- /dev/null +++ b/docs/tonyhax-vs-tonyhax-international-diffs/tonyhax-international-v1.5.8.diff @@ -0,0 +1,7739 @@ +diff --git a/tmp/og-tonyhax.O0E/loader/Makefile b/loader/Makefile +index f121ebf..0f5e6ff 100644 +--- a/tmp/og-tonyhax.O0E/loader/Makefile ++++ b/loader/Makefile +@@ -1,7 +1,7 @@ + + # Thanks to whoever made https://devhints.io/makefile! + +-include ../variables.mk ++include ../variables-shared.mk + + LOADER_AUTOGEN := orca.inc + LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) +@@ -10,7 +10,7 @@ LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S)) + all: $(LOADER_FILES) + + clean: +- $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) ++ $(RM) $(LOADER_FILES) *.o *.elf *.raw $(LOADER_AUTOGEN) + + # Intermediate objects + +@@ -29,11 +29,12 @@ secondary.elf: secondary.ld $(LOADER_OBJECTS) + + # Results + +-tonyhax.mcs: tonyhax-tpl.mcs secondary.elf +- bash generate-tonyhax-mcs.sh secondary.elf tonyhax-tpl.mcs tonyhax.mcs $(TONYHAX_VERSION) ++tonyhax.mcs: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801F2300 -raw -o secondary.raw ++ bash generate-tonyhax-mcs.sh secondary.raw tonyhax-tpl-16kb.mcs tonyhax.mcs $(TONYHAX_VERSION) + +-BESLEM-99999TONYHAX: tonyhax.mcs ++HAX: tonyhax.mcs + bash ../util/mcs2raw.sh tonyhax.mcs + + tonyhax.exe: secondary.elf +- bash generate-tonyhax-exe.sh secondary.elf tonyhax.exe ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801F2300 -o tonyhax.exe +diff --git a/loader/Makefile.ff9 b/loader/Makefile.ff9 +new file mode 100644 +index 0000000..348d1c5 +--- /dev/null ++++ b/loader/Makefile.ff9 +@@ -0,0 +1,40 @@ ++ ++# Thanks to whoever made https://devhints.io/makefile! ++ ++include ../variables-shared.mk ++ ++LOADER_AUTOGEN := orca.inc ++LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) ++LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) ++ ++all: $(LOADER_FILES) ++ ++clean: ++ $(RM) $(LOADER_FILES) FF9 tonyhax-ff9.mcs *.o *.elf *.raw $(LOADER_AUTOGEN) ++ ++# Intermediate objects ++ ++%.o: %.c $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++%.o: %.S $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++orca.inc: orca.img ++ bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc ++ ++secondary.elf: secondary-ff9.ld $(LOADER_OBJECTS) ++ $(LD) $(LDFLAGS) -T secondary-ff9.ld $(LOADER_OBJECTS) -o $@ ++ bash insert-tonyhax-crc.sh secondary.elf ++ ++# Results ++ ++tonyhax.mcs: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801F0480 -raw -o secondary.raw ++ bash generate-tonyhax-mcs.sh secondary.raw tonyhax-tpl-ff9-16kb.mcs tonyhax-ff9.mcs $(TONYHAX_VERSION) ++ ++HAX: tonyhax.mcs ++ bash ../util/mcs2raw.sh tonyhax-ff9.mcs ++ ++tonyhax.exe: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801F0480 -o tonyhax.exe +diff --git a/loader/Makefile.freepsxboot b/loader/Makefile.freepsxboot +new file mode 100644 +index 0000000..2ba06fd +--- /dev/null ++++ b/loader/Makefile.freepsxboot +@@ -0,0 +1,33 @@ ++ ++# Thanks to whoever made https://devhints.io/makefile! ++ ++include ../variables-shared.mk ++include ../variables.mk.freepsxboot ++ ++LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) ++LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) ++ ++all: $(LOADER_FILES_NO_MCS) ++ ++clean: ++ $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) ++ ++# Intermediate objects ++ ++%.o: %.c $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++%.o: %.S $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++orca.inc: orca.img ++ bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc ++ ++secondary.elf: secondary.ld $(LOADER_OBJECTS) ++ $(LD) $(LDFLAGS) -T secondary.ld $(LOADER_OBJECTS) -o $@ ++ bash insert-tonyhax-crc.sh secondary.elf ++ ++# Results ++ ++tonyhax.exe: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801f6200 -o tonyhax.exe +diff --git a/loader/Makefile.rom b/loader/Makefile.rom +new file mode 100644 +index 0000000..d075686 +--- /dev/null ++++ b/loader/Makefile.rom +@@ -0,0 +1,33 @@ ++ ++# Thanks to whoever made https://devhints.io/makefile! ++ ++include ../variables-shared.mk ++include ../variables.mk.rom ++ ++LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) ++LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) ++ ++all: $(LOADER_FILES_NO_MCS) ++ ++clean: ++ $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) ++ ++# Intermediate objects ++ ++%.o: %.c $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++%.o: %.S $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++orca.inc: orca.img ++ bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc ++ ++secondary.elf: secondary.ld $(LOADER_OBJECTS) ++ $(LD) $(LDFLAGS) -T secondary.ld $(LOADER_OBJECTS) -o $@ ++ bash insert-tonyhax-crc.sh secondary.elf ++ ++# Results ++ ++tonyhax.exe: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801f6200 -o tonyhax.exe +diff --git a/loader/Makefile.tocperfect b/loader/Makefile.tocperfect +new file mode 100644 +index 0000000..d73b172 +--- /dev/null ++++ b/loader/Makefile.tocperfect +@@ -0,0 +1,33 @@ ++ ++# Thanks to whoever made https://devhints.io/makefile! ++ ++include ../variables-shared.mk ++include ../variables.mk.tocperfect ++ ++LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) ++LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) ++ ++all: $(LOADER_FILES_NO_MCS) ++ ++clean: ++ $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) ++ ++# Intermediate objects ++ ++%.o: %.c $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++%.o: %.S $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++orca.inc: orca.img ++ bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc ++ ++secondary.elf: secondary.ld $(LOADER_OBJECTS) ++ $(LD) $(LDFLAGS) -T secondary.ld $(LOADER_OBJECTS) -o $@ ++ bash insert-tonyhax-crc.sh secondary.elf ++ ++# Results ++ ++tonyhax.exe: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801f6200 -o tonyhax.exe +diff --git a/loader/Makefile.xstation b/loader/Makefile.xstation +new file mode 100644 +index 0000000..0154130 +--- /dev/null ++++ b/loader/Makefile.xstation +@@ -0,0 +1,33 @@ ++ ++# Thanks to whoever made https://devhints.io/makefile! ++ ++include ../variables-shared.mk ++include ../variables.mk.xstation ++ ++LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) ++LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) ++ ++all: $(LOADER_FILES_NO_MCS) ++ ++clean: ++ $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) ++ ++# Intermediate objects ++ ++%.o: %.c $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++%.o: %.S $(LOADER_HEADERS) ++ $(CC) $(CFLAGS) -c $< ++ ++orca.inc: orca.img ++ bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc ++ ++secondary.elf: secondary.ld $(LOADER_OBJECTS) ++ $(LD) $(LDFLAGS) -T secondary.ld $(LOADER_OBJECTS) -o $@ ++ bash insert-tonyhax-crc.sh secondary.elf ++ ++# Results ++ ++tonyhax.exe: secondary.elf ++ ../pcsx-redux-support/ps1-packer secondary.elf -tload 0x801f6200 -o tonyhax.exe +diff --git a/loader/ap-bypass.c b/loader/ap-bypass.c +new file mode 100644 +index 0000000..0cbe4e9 +--- /dev/null ++++ b/loader/ap-bypass.c +@@ -0,0 +1,2777 @@ ++ ++#include "bios.h" ++#include "str.h" ++#include "ap-bypass.h" ++//#include "debugscreen.h" ++ ++bool cheat_engine_installed; ++ ++uint8_t gs_code_type; ++ ++int32_t code_address_ram_location = 0xD000; ++int32_t code_compare_ram_location = 0xD004; ++int32_t code_enable_ram_location = 0xD00C; ++ ++void clear_gs_code_line_ram () { ++ bzero((void*)0xD000, 0x600); ++// 255 code line limit * 6 bytes per code + 6 bytes of padding = 1536/0x600. 0xD000-0xD600 are to be zeroed out to ensure correct parsing by the cheat engine (used for gs codes loaded via memory card AND for APv2 bypasses). Every BIOS besides v3.0 has enough garbage? in this 'reserved' area to break the cheat engine parsing if we don't do this. Previously this zero-out was always done regardless of if the GameShark feature was actually being used. Now this is only done if the GameShark engine is active to fix issue 39: https://github.com/alex-free/tonyhax/issues/39 . ++} ++ ++void add_8bit_code(const uint32_t gs1, const uint8_t gs2, const uint8_t gs_code_type) { ++ if(code_address_ram_location == 0xD000) ++ clear_gs_code_line_ram(); // before writing first code line clear RAM ++ ++ memcpy((void*)code_address_ram_location, &gs1, 4); ++ memcpy((void*)code_compare_ram_location, &gs2, 1); ++ memcpy((void*)code_enable_ram_location, &gs_code_type, 1); ++// Update addresses to write to for an additional code ++ code_address_ram_location = (code_address_ram_location + 0x010); ++ code_compare_ram_location = (code_compare_ram_location + 0x010); ++ code_enable_ram_location = (code_enable_ram_location + 0x010); ++} ++ ++void add_16bit_code(const uint32_t gs1, const uint16_t gs2, const uint8_t gs_code_type) { ++ ++ if(code_address_ram_location == 0xD000) ++ clear_gs_code_line_ram(); // before writing first code line clear RAM ++ ++ memcpy((void*)code_address_ram_location, &gs1, 4); ++ memcpy((void*)code_compare_ram_location, &gs2, 2); ++ memcpy((void*)code_enable_ram_location, &gs_code_type, 1); ++// Update addresses to write to for an additional code ++ code_address_ram_location = (code_address_ram_location + 0x010); ++ code_compare_ram_location = (code_compare_ram_location + 0x010); ++ code_enable_ram_location = (code_enable_ram_location + 0x010); ++} ++ ++// size optimization wrapper, instead of specifying 0xD0 or 0x80 each time we add a code via built-in ap bypass system ++void add_80_code(const uint32_t gs1, const uint16_t gs2) { ++ add_16bit_code(gs1, gs2, 0x80); ++} ++ ++void add_D0_code(const uint32_t gs1, const uint16_t gs2) { ++ add_16bit_code(gs1, gs2, 0xD0); ++} ++ ++void install_cheat_engine() { ++// generate with `scripts/stealth-engine-xxd.sh` after compiling .EXE file with No $ PSX Emu Assembler ++const unsigned char cheat_engine_v1_0_4[] = { ++0xe0, 0xff, 0xbd, 0x27, 0x00, 0x00, 0xa4, 0xaf, ++ 0x04, 0x00, 0xa5, 0xaf, 0x08, 0x00, 0xa6, 0xaf, 0x0c, 0x00, 0xa7, 0xaf, ++ 0x00, 0x00, 0x04, 0x3c, 0x00, 0xd0, 0x84, 0x34, 0x00, 0x00, 0x00, 0x00, ++ 0x0c, 0x00, 0x86, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0xc0, 0x10, ++ 0xd0, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0xc7, 0x10, ++ 0xd1, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x00, 0xc7, 0x10, ++ 0xd2, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0xc7, 0x10, ++ 0xd3, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0xc7, 0x10, ++ 0xe0, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0xc7, 0x10, ++ 0xe1, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0xc7, 0x10, ++ 0xe2, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0xc7, 0x10, ++ 0xe3, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0xc7, 0x10, ++ 0x80, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0xc7, 0x10, ++ 0x30, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xc7, 0x10, ++ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x07, 0x34, 0x00, 0x00, 0x00, 0x00, ++ 0x73, 0x00, 0xc7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x07, 0x34, ++ 0x1c, 0x00, 0x86, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0xc7, 0x10, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x90, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xa0, 0x10, 0x00, 0x84, 0x24, ++ 0xcf, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x94, ++ 0x00, 0x00, 0x87, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xa4, ++ 0x10, 0x00, 0x84, 0x24, 0xc8, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x85, 0x90, 0x00, 0x00, 0x87, 0x8c, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0xe6, 0x90, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xff, 0xc5, 0x10, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0xbe, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x94, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x94, 0x00, 0x00, 0x00, 0x00, ++ 0xd9, 0xff, 0xc5, 0x10, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, ++ 0xb4, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x90, ++ 0x00, 0x00, 0x87, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x90, ++ 0x00, 0x00, 0x00, 0x00, 0xcf, 0xff, 0xc5, 0x14, 0x00, 0x00, 0x00, 0x00, ++ 0x20, 0x00, 0x84, 0x24, 0xaa, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x85, 0x94, 0x00, 0x00, 0x87, 0x8c, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0xe6, 0x94, 0x00, 0x00, 0x00, 0x00, 0xc5, 0xff, 0xc5, 0x14, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0xa0, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x90, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x90, 0x00, 0x00, 0x00, 0x00, ++ 0x2b, 0x38, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0xff, 0xe0, 0x14, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0x94, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x94, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x94, 0x00, 0x00, 0x00, 0x00, ++ 0x2b, 0x38, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0xff, 0xe0, 0x14, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0x88, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x90, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x90, 0x00, 0x00, 0x00, 0x00, ++ 0x2b, 0x38, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0xff, 0xe0, 0x14, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0x7c, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x85, 0x94, 0x00, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x94, 0x00, 0x00, 0x00, 0x00, ++ 0x2b, 0x38, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xff, 0xe0, 0x14, ++ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x84, 0x24, 0x70, 0xff, 0x00, 0x18, ++ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x85, 0x90, 0x10, 0x00, 0x87, 0x8c, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xa0, 0x20, 0x00, 0x84, 0x24, ++ 0x69, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x85, 0x94, ++ 0x10, 0x00, 0x87, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xa4, ++ 0x20, 0x00, 0x84, 0x24, 0x62, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0xa4, 0x8f, 0x04, 0x00, 0xa5, 0x8f, 0x08, 0x00, 0xa6, 0x8f, ++ 0x0c, 0x00, 0xa7, 0x8f, 0x20, 0x00, 0xbd, 0x27, 0x01, 0x00, 0x1a, 0x3c, ++ 0xfc, 0xcf, 0x5a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x40, 0x03, ++ 0x00, 0x00, 0x00, 0x00 ++}; ++ ++ //debug_write("Installing MottZilla AP Engine"); ++ memcpy((void*)0xC000, (void*)cheat_engine_v1_0_4, sizeof(cheat_engine_v1_0_4)); // Copy MottZilla's cheat engine assembly binary to 0xC000 ++ const uint32_t b0_jump = (*(uint32_t*)0xB4) & 0xFFFF; ++ //debug_write("Got jump address for B0 functions : %x", (uint32_t) b0_jump); ++ const uint32_t b0_base = (*(uint32_t*)(b0_jump + 4)) & 0xFFFF; ++ //debug_write("Got base address for B0 table : %x", (uint32_t) b0_base); ++ const uint32_t b0_entry = b0_base + (0x17 * 4); ++ //debug_write("Got B0 table entry address : %x", (uint32_t) b0_entry); ++ const uint32_t old_table_val = *(uint32_t*) b0_entry; ++ //debug_write("B0 table entry to modify has the original contents : %x", (uint32_t) old_table_val); ++ memcpy((void*)0xCFFC, (void*)&old_table_val, sizeof(old_table_val)); // Copy the original 32 bit number of the B table entry we want to modify to 0xCFFC ++ const uint16_t redirect = 0xC000; ++ memcpy((void*)b0_entry, &redirect, 2); // Write the value 0xC000 to table entry we want to modify ++ //for(volatile int i = 0; i < 0x100000; i++); // won't be optimized out by -Os, pause ++ cheat_engine_installed = true; ++} ++ ++void activate_anti_anti_piracy(char * bootfile, const int32_t load_addr) ++{ ++ int32_t ver_check; ++ uint8_t ver_check_val; ++ int8_t bootfile_len = strlen(bootfile); ++ /* ++ lowest possible ap bootfile len is 19 ++ cdrom:\\XXXX_XXX.XX;1 = 21 len ++ cdrom:\XXXX_XXX.XX;1 = 20 len ++ cdrom:XXXX_XXX.XX;1 = 19 len ++ */ ++ //debug_write("Bootfile len: %d", bootfile_len); ++ ++ // Aprip fake VC0 bypass code type constants ++ const uint16_t fake_vc0_bypass_compare_val = 0x001E; ++ const uint16_t fake_vc0_bypass_patch_val = 0x0000; ++ ++ // Aprip fake PAL BIOS bypass code type constants ++ const uint16_t fake_pal_bios_bypass_compare_val = 0x1062; ++ const uint16_t fake_pal_bios_bypass_patch_val = 0x1800; ++ ++ // Many pre-existing 'skip mod check' or 'force routine ok' type codes from back in the day seem to share these values, so they are a const ++ const uint16_t common_routine_return_compare_val = 0x1040; ++ const uint16_t common_routine_return_patch_val = 0x1000; ++ ++ if(is_beat_mania_append_gottamix) // uses Append No Swap Bypass (By mdmdj) method so bootfile detection at this point in execution is not possible ++ { ++ /* ++ D01C0838 2021 ++ 801C0834 FFF6 ++ code 1/2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x801C0838, 0x2021); ++ add_80_code(0x801C0834, 0xFFF6); ++ /* ++ D01C0838 2021 ++ 801C0836 1000 ++ code 2/2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x801C0838, 0x2021); ++ add_80_code(0x801C0836, 0x1000); ++ install_cheat_engine(); ++ return; ++ } ++ ++ // this is less expensive space-wise then using strncmp ++ if(bootfile_len >= 19) { ++ bootfile = &bootfile[bootfile_len-13]; // strip bootfile to last 13 bytes, XXXX_XXX.XX;1 ++ //debug_write("Bootfile stripped 1: %s", bootfile); ++ bootfile[11] = 0; // replace ; with termination. So XXXX_XXX.XX;1 becomes XXXX_XXX.XX for less expensive (space-wise) strcmps ++ //debug_write("Bootfile stripped 2: %s", bootfile); ++ //for(volatile int i = 0; i < 0x100000; i++); // won't be optimized out by -Os, pause ++ } else { ++ return; ++ // Speed optimization. All anti-piracy games currently have an pre-stripped bootfile name that is at least 19 bytes long. So if the bootfile happens to have something like 'cdrom://MAIN.EXE;1' (which is 18 in length) , we already know not to bother even checking for an anti-piracy bootfile match to apply codes for (which takes time in itself to do as well). ++ } ++ ++// Animetic Story Game 1: Card Captor Sakura ++ if( ++ ((strcmp("SLPS_018.30", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SLPS_018.31", bootfile)) == 0) // Japan Disc 2 ++ ) { // 2 disc game ++ /* ++ D001516A 1040 ++ 8001516A 1000 ++ code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_c.shtml ++ */ ++ add_D0_code(0x8001516A, common_routine_return_compare_val); ++ add_80_code(0x8001516A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Alundra 2 ++ ((strcmp("SCPS_101.15", bootfile)) == 0) { // Japan ++ /* ++ D004E91A 1040 ++ 8004E91A 1000 ++ 'skip mod check' code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_a.shtml ++ */ ++ add_D0_code(0x8004E91A, common_routine_return_compare_val); ++ add_80_code(0x8004E91A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Arc The Lad III ++ ( ++ ((strcmp("SCPS_101.06", bootfile)) == 0) // Japan Rev 0/Rev 1 Disc 1 ++ || ((strcmp("SCPS_101.07", bootfile)) == 0) // Japan Rev 0/Rev 1 Disc 2 ++ ) { ++ ver_check = (load_addr + 0x20); // First different byte between revisions ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0x8C) // Rev 0 ++ { ++ /* ++ D0021DF6 1040 ++ 80021DF6 1000 ++ code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_a.shtml ++ */ ++ add_D0_code(0x80021DF6, common_routine_return_compare_val); ++ add_80_code(0x80021DF6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else { // Rev 1 ++ /* ++ D0022206 1040 ++ 80022206 1000 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x80022206, common_routine_return_compare_val); ++ add_80_code(0x80022206, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } ++ } else if ++ ++// Beatmania featuring Dreams Come True ++ ((strcmp("SLPM_865.97", bootfile)) == 0) { // Japan ++ /* ++ D012129A 1040 ++ 8012129A 1000 ++ skip routine code ++ code from https://gamehacking.org/game/93343 by davintheravin ++ did not need C1 code: "Code Execution Delay - Delays activation of codes by &value." because "Line starting w/ 'C1' needed for GS version 2.41 and up." and this tested okay. Info from "Info/Note" on that link ++ */ ++ add_D0_code(0x8012129A, common_routine_return_compare_val); ++ add_80_code(0x8012129A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Beatmania Best Hits ++ ((strcmp("Slpm_865.96", bootfile)) == 0) { // Japan Rev 0/Rev 1 ++ // not a typo, weird asf filename ++ /* ++ D01500FE 1062 ++ 801500FE 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x801500FE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801500FE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Beat Mania: The Sound of Tokyo ++ ((strcmp("SLPM_867.69", bootfile)) == 0) { // Japan ++ /* ++ D01338FE 1062 ++ 801338FE 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x801338FE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801338FE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Beat Mania 6thMix + Core Remix ++ ((strcmp("SLPM_870.12", bootfile)) == 0) { // Japan ++ /* ++ D0130822 1062 ++ 80130822 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x80130822, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80130822, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Bishi Bashi Special 2 ++ ((strcmp("SLPM_862.67", bootfile)) == 0) { // Japan ++ /* ++ D009818A 1040 ++ 8009818A 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x8009818A, common_routine_return_compare_val); ++ add_80_code(0x8009818A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Boku no Natsuyasumi: Summer Holiday 20th Century ++ ((strcmp("SCPS_100.88", bootfile)) == 0) { // Japan ++ /* ++ D004921E 1062 ++ 8004921E 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8004921E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8004921E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Breath of Fire IV ++ ((strcmp("SLPS_027.28", bootfile)) == 0) { // Japan / USA E3 2000 Beta Build ++ /* ++ D01CE39A 1062 ++ 801CE39A 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x801CE39A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801CE39A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Bust A Move 2: Dance Tengoku Mix ++ ((strcmp("SLPM_862.19", bootfile)) == 0) { // Japan ++ /* ++ D008FB02 1040 ++ 8008FB02 1000 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8008FB02, common_routine_return_compare_val); ++ add_80_code(0x8008FB02, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Capcom vs. SNK: Millennium Fight 2000 Pro ++ ((strcmp("SLPM_870.53", bootfile)) == 0) { // Japan ++ /* ++ D0033BCE 1062 ++ 80033BCE 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x80033BCE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80033BCE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Chase The Express ++ ( ++ ((strcmp("SCPS_101.09", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SCPS_101.10", bootfile)) == 0) // Japan Disc 2 ++ || ((strcmp("PAPX_901.06", bootfile)) == 0) // Japan Demo 1 ++ || ((strcmp("PCPX_961.89", bootfile)) == 0) // Japan Demo 2 ++ ) { ++ /* ++ D00EA6DE 1040 ++ 800EA6DE 1000 ++ 'skip mod check' code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_c.shtml ++ */ ++ add_D0_code(0x800EA6DE, common_routine_return_compare_val); ++ add_80_code(0x800EA6DE, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Chocobo Racing: Genkai e no Road ++ ((strcmp("SLPS_019.51", bootfile)) == 0) { // Japan ++ /* ++ D00AB72A 1040 ++ 800AB72A 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_c.shtml (Bung Japan) ++ */ ++ add_D0_code(0x800AB72A, common_routine_return_compare_val); ++ add_80_code(0x800AB72A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Crash Bash ++ ((strcmp("SCUS_945.70", bootfile)) == 0) { // USA ++ /* ++ D002D51E 1040 ++ 8002D51E 1000 ++ code from GameHacking: https://gamehacking.org/game/88640, skips mod check ++ */ ++ add_D0_code(0x8002D51E, common_routine_return_compare_val); ++ add_80_code(0x8002D51E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCPS_101.40", bootfile)) == 0) { // Japan ++ /* ++ D002D7EE 1040 ++ 8002D7EE 1000 ++ code from GameHacking: https://gamehacking.org/game/93827, skips mod check ++ */ ++ add_D0_code(0x8002D7EE, common_routine_return_compare_val); ++ add_80_code(0x8002D7EE, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Crash Bandicoot Racing ++ ((strcmp("SCPS_101.18", bootfile)) == 0) { // Japan ++ /* ++ D001259A 1040 ++ 8001259A 1000 ++ 'skip mod check' code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_b.shtml (yes it's on the wrong lettered page) ++ */ ++ add_D0_code(0x8001259A, common_routine_return_compare_val); ++ add_80_code(0x8001259A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PCPX_961.83", bootfile)) == 0) { // Japan Demo ++ /* ++ D001255E 1040 ++ 8001255E 1040 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x8001255E, common_routine_return_compare_val); ++ add_80_code(0x8001255E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Cool Boarders 2001 ++ ( ++ ((strcmp("SCUS_946.25", bootfile)) == 0) // USA Demo ++ || ((strcmp("SCUS_945.97", bootfile)) == 0) // USA ++ ) ++ { ++ /* ++ D00B1146 1062 ++ 800B1146 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x800B1146, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800B1146, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dancing Blade Katte ni Momotenshi II: Tears of Eden ++ ( ++ ((strcmp("SLPM_862.10", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SLPM_862.11", bootfile)) == 0) // Japan Disc 2 ++ || ((strcmp("SLPM_862.12", bootfile)) == 0) // Japan Disc 3 ++ ) ++ { ++ /* ++ D019E272 1040 ++ 8019E272 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x8019E272, common_routine_return_compare_val); ++ add_80_code(0x8019E272, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution ++ ((strcmp("SLPM_862.22", bootfile)) == 0) { // Japan ++ /* ++ D001E160 FFF2 ++ 8001E160 0001 ++ bypass checksum ++ code from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x8001E160, 0xFFF2); ++ add_80_code(0x8001E160, 0x0001); ++ /* ++ D01B6F20 0003 ++ 801B6F20 0001 ++ pro action replay bypass ++ code from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x801B6F20, 0x0003); ++ add_80_code(0x801B6F20, 0x0001); ++ /* ++ D01B76A8 DE07 ++ 801B76A8 DDFE ++ mod-chip bypass ++ code from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x801B76A8, 0xDE07); ++ add_80_code(0x801B76A8, 0xDDFE); ++ /* ++ EDC code 1/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ D004B4C0 0920 ++ 8004B4C0 0900 ++ */ ++ add_D0_code(0x8004B4C0, 0x0920); ++ add_80_code(0x8004B4C0, 0x0900); ++ /* ++ EDC code 2/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ D01B6A68 0920 ++ 801B6A68 0900 ++ */ ++ add_D0_code(0x801B6A68, 0x0920); ++ add_80_code(0x801B6A68, 0x0900); ++ /* ++ EDC code 3/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ D01B6BF0 0920 ++ 801B6BF0 0900 ++ */ ++ add_D0_code(0x801B6BF0, 0x0920); ++ add_80_code(0x801B6BF0, 0x0900); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLUS_012.80", bootfile)) == 0) { // USA ++ /* ++ D010024E 1062 ++ 8010024E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8010024E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8010024E, fake_pal_bios_bypass_patch_val); ++ // todo: EDC bypass ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: Best Hits ++ ((strcmp("SLPM_866.93", bootfile)) == 0) { // Japan ++ /* ++ D010024E 1062 ++ 8010024E 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8010024E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8010024E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: Disney's Rave ++ ((strcmp("SLPM_866.67", bootfile)) == 0) { // Japan ++ /* ++ D0190182 1062 ++ 80190182 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x80190182, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80190182, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: Extra Mix ++ ((strcmp("SLPM_868.31", bootfile)) == 0) { // Japan ++ /* ++ D00E8266 1062 ++ 800E8266 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x800E8266, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800E8266, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution 2nd Remix ++ ((strcmp("SLPM_862.52", bootfile)) == 0) { // Dance Dance Revolution 2nd Remix Append Club Vol 1 and Dance Dance Revolution 2nd Remix Append Club Vol 2 discs are booted with a 'disc change' feature within Dance Dance Revolution 2nd Remix (which is a 'base' disc when booting the others, kind of like DLC). ++ /* ++ Dance Dance Revolution 2nd Remix: ++ D00200A6 1040 ++ 800200A6 1000 ++ */ ++ add_D0_code(0x800200A6, common_routine_return_compare_val); ++ add_80_code(0x800200A6, common_routine_return_patch_val); ++ /* ++ D0020D64 FFF2 ++ 80020D64 0001 ++ */ ++ add_D0_code(0x80020D64, 0xFFF2); ++ add_80_code(0x80020D64, 0x0001); ++ /* ++ D01C1BE4 FFF2 ++ 801C1BE4 0001 ++ */ ++ add_D0_code(0x801C1BE4, 0xFFF2); ++ add_80_code(0x801C1BE4, 0x0001); ++ ++ /* ++ D01C1C7A 0C07 ++ 801C1C7A 3002 ++ */ ++ add_D0_code(0x801C1C7A, 0x0C07); ++ add_80_code(0x801C1C7A, 0x3002); ++ /* ++ D01C2936 1040 ++ 801C2936 1000 ++ */ ++ add_D0_code(0x801C2936, common_routine_return_compare_val); ++ add_80_code(0x801C2936, common_routine_return_patch_val); ++ /* ++ Dance Dance Revolution 2nd Remix Append Club Vol 1 ++ D01C2A18 0C92 ++ 801C2A18 0AA7 ++ */ ++ add_D0_code(0x801C2A18, 0x0C92); ++ add_80_code(0x801C2A18, 0x0AA7); ++ /* ++ D01C2EA2 1040 ++ 801C2EA2 1000 ++ */ ++ add_D0_code(0x801C2EA2,common_routine_return_compare_val); ++ add_80_code(0x801C2EA2, common_routine_return_patch_val); ++ /* ++ D01C26DE 1062 ++ 801C26DE 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x801C26DE,fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801C26DE, fake_pal_bios_bypass_patch_val); ++ /* ++ Dance Dance Revolution 2nd Remix Append Club Vol 2 ++ D01C2F32 1040 ++ 801C2F32 1000 ++ */ ++ add_D0_code(0x801C2F32, common_routine_return_compare_val); ++ add_80_code(0x801C2F32, common_routine_return_patch_val); ++ /* ++ D01C2AA8 0CB6 ++ 801C2AA8 0ACB ++ */ ++ add_D0_code(0x801C2AA8, 0x0CB6); ++ add_80_code(0x801C2AA8, 0x0ACB); ++ /* ++ D01C276E 1062 ++ 801C276E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x801C276E,fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801C276E, fake_pal_bios_bypass_patch_val); ++ // non-aprip generated codes are from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: 3rd Mix ++ ((strcmp("SLPM_865.03", bootfile)) == 0) { // Japan ++ /* ++ D00C19A2 1062 ++ 800C19A2 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x800C19A2, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800C19A2, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: 4th Mix ++ ((strcmp("SLPM_867.52", bootfile)) == 0) { // Japan ++ /* ++ D00E824E 1062 ++ 800E824E 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x800E824E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800E824E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dance Dance Revolution: 5th Mix ++ ((strcmp("SLPM_868.97", bootfile)) == 0) { // Japan ++ /* ++ D0174306 1062 ++ 80174306 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x80174306, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80174306, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dancing Stage featuring Dreams Come True ++ ((strcmp("SLPM_865.05", bootfile)) == 0) { // Japan ++ /* ++ D0190182 1062 ++ 80190182 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x80190182, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80190182, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dancing Stage featuring True Kiss Destination ++ ((strcmp("SLPM_864.11", bootfile)) == 0) { // Japan ++ /* ++ D019117A 1040 ++ 8019117A 1000 ++ skip mod check ++ code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x8019117A, common_routine_return_compare_val); ++ add_80_code(0x8019117A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Dino Crisis ++ ((strcmp("SLUS_009.22", bootfile)) == 0) { // USA Rev 0/USA Rev 1 ++ ver_check = (load_addr + 0x61); // First different byte between revisions ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0xD0) { // Rev 0 ++ /* ++ D0149004 959C ++ 80149004 9E64 ++ Found independently by MottZilla, but actually turns out to be the same code by Epson found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x80149004, 0x959C); ++ add_80_code(0x80149004, 0x9E64); ++ install_cheat_engine(); ++ } else { // Rev 1 ++ /* ++ D0148004 8658 ++ 80148004 8F20 ++ my code, the anti-piracy table just moved memory addresses between versions :) ++ */ ++ add_D0_code(0x80148004, 0x8658); ++ add_80_code(0x80148004, 0x8F20); ++ install_cheat_engine(); ++ } ++ } else if ++ ++ ((strcmp("SLPS_021.80", bootfile)) == 0) { // Japan ++ /* ++ D0149004 959C ++ 80149004 9E64 ++ Found independently by MottZilla, but actually turns out to be the same code by Epson found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_d.shtml . Yes this is the same code as the USA rev 0 one. ++ */ ++ add_D0_code(0x80149004, 0x959C); ++ add_80_code(0x80149004, 0x9E64); ++ install_cheat_engine(); ++ } else if ++ ++// Dino Crisis 2 ++ ((strcmp("SLPM_866.27", bootfile)) == 0) { // Japan ++ /* ++ D00D639E 1062 ++ 800D639E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x800D639E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800D639E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLPM_805.73", bootfile)) == 0) { // Japan Demo ++ /* ++ D00C9DA6 1062 ++ 800C9DA6 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x800C9DA6, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800C9DA6, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Disney's The Emperor's New Groove ++ ((strcmp("SCUS_945.71", bootfile)) == 0) { // USA ++ /* ++ D004C6E2 1062 ++ 8004C6E2 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8004C6E2, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8004C6E2, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCES_030.10", bootfile)) == 0) { // Europe ++ /* ++ D004CBDA 1062 ++ 8004CBDA 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8004CBDA, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8004CBDA, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Doko Demo Issho ++ ( ++ ((strcmp("SCPS_100.92", bootfile)) == 0) // Japan Rev 0/Japan Rev 1 ++ || ((strcmp("SCPM_850.06", bootfile)) == 0) // Japan Calpis Water Version ++ ) { ++ /* ++ these 3 codes work for Japan Rev 0, Japan Rev 1, and Japan Calpis Water Version. ++ D01207E8 2021 ++ 801207E4 FFF6 ++ code 1/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x801207E8, 0x2021); ++ add_80_code(0x801207E4, 0xFFF6); ++ /* ++ D01207E8 2021 ++ 801207E6 1000 ++ code 2/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x801207E8, 0x2021); ++ add_80_code(0x801207E6, common_routine_return_patch_val); ++ /* ++ D0151AE2 1040 ++ 80151AE2 1000 ++ code 3/3 from https://consolecopyworld.com/psx/psx_game_codes_d.shtml ++ */ ++ add_D0_code(0x80151AE2, common_routine_return_compare_val); ++ add_80_code(0x80151AE2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PCPX_961.52", bootfile)) == 0) { // Japan Demo 1 ++ /* ++ D0151D92 9222 ++ 80151D92 A222 ++ force ok type B modpar generated code ++ */ ++ add_D0_code(0x80151D92, 0x9222); ++ add_80_code(0x80151D92, 0xA222); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PAPX_900.86", bootfile)) == 0) { // Japan Demo 2 ++ /* ++ D0151D8A 9222 ++ 80151D8A A222 ++ force ok type B modpar generated code ++ */ ++ add_D0_code(0x80151D8A, 0x9222); ++ add_80_code(0x80151D8A, 0xA222); ++ install_cheat_engine(); ++ } else if ++ ++// Exciting Bass 2 ++ ((strcmp("SLPM_862.95", bootfile)) == 0) { // Japan ++ /* ++ D00177BA 1040 ++ 800177BA 1000 ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_e.shtml ++ */ ++ add_D0_code(0x800177BA, common_routine_return_compare_val); ++ add_80_code(0x800177BA, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Exciting Bass 3 ++ ((strcmp("SLPM_867.29", bootfile)) == 0) { // Japan ++ /* ++ D00225E2 1062 ++ 800225E2 1800 ++ my code to patch out readtoc via aprip ++ */ ++ add_D0_code(0x800225E2, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800225E2, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Final Fantasy VIII ++ ( ++ ((strcmp("SLPS_018.80", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SLPS_018.81", bootfile)) == 0) // Japan Disc 2 ++ || ((strcmp("SLPS_018.82", bootfile)) == 0) // Japan Disc 3 ++ || ((strcmp("SLPS_018.83", bootfile)) == 0) // Japan Disc 3 ++ ) { ++ /* ++ D009B182 0000 ++ 8009B182 2402 ++ code from https://consolecopyworld.com/psx/psx_game_codes_f.shtml by Asian Game Shark Code Creator (NOTE: CODE has typo on consolecopyworld page) ++ */ ++ add_D0_code(0x8009B182, 0x0000); ++ add_80_code(0x8009B182, 0x2402); ++ install_cheat_engine(); ++ } else if ++ ++// Gekitotsu Toma L'Arc - L'Arc en Ciel vs Tomarunner ++ ((strcmp("SCPS_101.34", bootfile)) == 0) { // Japan ++ /* ++ D016385E 1062 ++ 8016385E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8016385E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8016385E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Glint Glitters ++ ((strcmp("SLPM_862.00", bootfile)) == 0) { // Japan ++ /* ++ D01B2816 1040 ++ 801B2816 1000 ++ code from https://gamehacking.org/game/94731 ++ this code is a 'force ok' type code. the whole routine still runs the test commands and readtoc, but it forces ok for the result of the test commands ++ */ ++ add_D0_code(0x801B2816, common_routine_return_compare_val); ++ add_80_code(0x801B2816, common_routine_return_patch_val); ++ /* ++ D01B3188 001E ++ 801B3188 0000 ++ my code via aprip. this disables readtoc so in combination with the code above this does allow a non-stealth mod-chip console to work as well as a stock console. Verified with SCPH-5501 non-stealth mod-chipped, and SCPH-101 stock. ++ */ ++ add_D0_code(0x801B3188, fake_vc0_bypass_compare_val); ++ add_80_code(0x801B3188, fake_vc0_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Global Force: Shin Sentou Kokka ++ ((strcmp("SCPS_100.82", bootfile)) == 0) { // Japan Rev 0/Japan Rev 1 ++ // same codes works for both Japan Rev 0 and Japan Rev 1 ++ /* ++ D01E8CC4 2021 ++ 801E8CC0 FFF6 ++ code 1/2 from https://consolecopyworld.com/psx/psx_game_codes_g.shtml ++ skips mod-check ++ */ ++ add_D0_code(0x801E8CC4, 0x2021); ++ add_80_code(0x801E8CC0, 0xFFF6); ++ /* ++ D01E8CC4 2021 ++ 801E8CC2 1000 ++ code 2/2 from https://consolecopyworld.com/psx/psx_game_codes_g.shtml ++ skips mod-check ++ */ ++ add_D0_code(0x801E8CC4, 0x2021); ++ add_80_code(0x801E8CC2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PCPX_961.46", bootfile)) == 0) { // Japan Demo ++ /* ++ D01E91EC 2021 ++ 801E91E8 FFF6 ++ code 1/2 via aprip conversion ++ skips mod-check ++ */ ++ add_D0_code(0x801E91EC, 0x2021); ++ add_80_code(0x801E91E8, 0xFFF6); ++ /* ++ D01E91EC 2021 ++ 801E91EA 1000 ++ code 2/2 via aprip conversion ++ skips mod-check ++ */ ++ add_D0_code(0x801E91EC, 0x2021); ++ add_80_code(0x801E91EA, 0x1000); ++ install_cheat_engine(); ++ } else if ++ ++// Goo! Goo! Soundry ++ ((strcmp("SLPM_862.50", bootfile)) == 0) { // Japan ++ /* ++ D0012B66 1040 ++ 80012B66 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_g.shtml ++ skips mod-check ++ */ ++ add_D0_code(0x80012B66, common_routine_return_compare_val); ++ add_80_code(0x80012B66, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Grind Session ++ ((strcmp("SCUS_945.68", bootfile)) == 0) { // USA ++ /* ++ D0013F3A 1062 ++ 80013F3A 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x80013F3A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80013F3A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Guitar Freaks ++ ((strcmp("SLPM_862.65", bootfile)) == 0) { // Japan ++ /* ++ D001654E 1040 ++ 8001654E 1000 ++ skip mod check ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_g.shtml ++ */ ++ add_D0_code(0x8001654E, common_routine_return_compare_val); ++ add_80_code(0x8001654E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Gungauge ++ ((strcmp("SLPM_862.33", bootfile)) == 0) { // Japan ++ /* ++ D00E0206 1040 ++ 800E0206 1000 ++ force ok ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_g.shtml ++ */ ++ add_D0_code(0x800E0206, common_routine_return_compare_val); ++ add_80_code(0x800E0206, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Harlem Beat: You're The One ++ ((strcmp("SLPM_863.29", bootfile)) == 0) { // Japan ++ /* ++ D01A411E 1040 ++ 801A411E 1000 ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_h.shtml ++ */ ++ add_D0_code(0x801A411E, common_routine_return_compare_val); ++ add_80_code(0x801A411E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Hyper Value 2800: Hanafuda ++ ((strcmp("SLPM_864.17", bootfile)) == 0) { // Japan ++ /* ++ D001844E 1440 ++ 8001844E 1040 ++ my very first custom bypass developed in no $ psx emu! ++ skips mod-check completely ++ */ ++ add_D0_code(0x8001844E, 0x1440); ++ add_80_code(0x8001844E, common_routine_return_compare_val); ++ install_cheat_engine(); ++ } else if ++ ++// Hyper Value 2800: Mahjong ++ ((strcmp("SLPM_862.92", bootfile)) == 0) { // Japan ++ /* ++ D00A0186 1040 ++ 800A0186 1000 ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_h.shtml ++ */ ++ add_D0_code(0x800A0186, common_routine_return_compare_val); ++ add_80_code(0x800A0186, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Hyper Value 2800: Hyper Pachinko ++ ((strcmp("SLPM_864.18", bootfile)) == 0) { // Japan ++ /* ++ D01380FE 1062 ++ 801380FE 1800 ++ first match for PAL BIOS (there are 2 matches) ++ code generated via aprip ++ */ ++ add_D0_code(0x801380FE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801380FE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// i-mode mo Issho: Doko Demo Issho Tsuika Disc ++ ((strcmp("SCPS_101.41", bootfile)) == 0) { // Japan ++ /* ++ D015205C 0062 ++ 8015205C 0014 ++ D015205E 1202 ++ 8015205E 1800 ++ aprip conversion from MottZilla and I's koneko mo isso bypass ++ force ok for test commands ++ */ ++ add_D0_code(0x8015205C, 0x0062); ++ add_80_code(0x8015205C, 0x0014); ++ add_D0_code(0x8015205E, 0x1202); ++ add_80_code(0x8015205E, 0x1800); ++ /* ++ D01698B4 001E ++ 801698B4 0000 ++ code generated via aprip by M4x1mumReZ: https://gbatemp.net/members/m4x1mumrez.610331/ ++ */ ++ add_D0_code(0x801698B4, fake_vc0_bypass_compare_val); ++ add_80_code(0x801698B4, fake_vc0_bypass_patch_val); ++ /* ++ D01518D4 000A ++ 801518D4 0000 ++ D01518D6 1062 ++ 801518D6 0000 ++ Fake a Non-PAL BIOS ++ aprip conversion from MottZilla and I's koneko mo isso bypass ++ */ ++ add_D0_code(0x801518D4, 0x000A); ++ add_80_code(0x801518D4, 0x0000); ++ add_D0_code(0x801518D6, 0x1062); ++ add_80_code(0x801518D6, 0x0000); ++ install_cheat_engine(); ++ } else if ++ ++// I.Q Final ++ ( ++ ((strcmp("PCPX_961.37", bootfile)) == 0) // Japan Demo 1 ++ || ((strcmp("PAPX_900.63", bootfile)) == 0) // Japan Demo 2 ++ ) { ++ // Notice how the protection was not in the released retail version (this was supposed to be the second protected game after PoPoRoGue but the protection for PoPoRoGue Rev 0, the only protected game at the time, was broken for SCPH-1000 unmodified consoles): https://geocities.restorativland.org/SiliconValley/Station/8269/ ++ /* ++ D00824CA 1040 ++ 800824CA 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_i.shtml ++ */ ++ add_D0_code(0x800824CA, common_routine_return_compare_val); ++ add_80_code(0x800824CA, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Powerful Pro Yakyuu: Premium-ban ++ ((strcmp("SLPM_872.16", bootfile)) == 0) { // Japan ++ /* ++ D016641A 1040 ++ 8016641A 1000 ++ my code via aprip conversion, I dumped the RAM from the Jikkyou Powerful Pro Yakyuu 2000: Ketteiban game and used the GameShark conversion feature with the dumped ram from Jikkyou Powerful Pro Yakyuu: Premium-ban and it actually was similar enough to work ++ */ ++ add_D0_code(0x8016641A, common_routine_return_compare_val); ++ add_80_code(0x8016641A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Powerful Pro Yakyuu '99 Ketteiban ++ ((strcmp("SLPM_864.33", bootfile)) == 0) { // Japan ++ /* ++ D016A04E 1062 ++ 8016A04E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8016A04E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8016A04E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Powerful Pro Yakyuu '99: Kaimakuban ++ ((strcmp("SLPM_862.53", bootfile)) == 0) { // Japan ++ /* ++ D016A07A 1040 ++ 8016A07A 1000 ++ code from copy console world: https://consolecopyworld.com/psx/psx_game_codes_j.shtml ++ */ ++ add_D0_code(0x8016A07A, common_routine_return_compare_val); ++ add_80_code(0x8016A07A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Powerful Pro Yakyuu 2000: Kaimakuban / Jikkyou Powerful Pro Yakyuu 2000: Ketteiban ++ ( ++ ((strcmp("SLPM_865.78", bootfile)) == 0) // Jikkyou Powerful Pro Yakyuu 2000: Kaimakuban ++ || ((strcmp("SLPM_866.94", bootfile)) == 0) // Jikkyou Powerful Pro Yakyuu 2000: Ketteiban ++ ) { ++ /* ++ D016807A 1040 ++ 8016807A 1000 ++ my code via aprip conversion, I dumped the RAM from the Jikkyou Powerful Pro Yakyuu '99: Kaimakuban game and used the GameShark conversion feature with the dumped ram from Jikkyou Powerful Pro Yakyuu 2000: Ketteiban and it actually was similar enough to work ++ */ ++ add_D0_code(0x8016807A, common_routine_return_compare_val); ++ add_80_code(0x8016807A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Powerful Pro Yakyuu 2001: Ketteiban / Jikkyou Powerful Pro Yakyuu 2001 / Jikkyou Powerful Pro Yakyuu 2002: Haru ++ ( ++ ((strcmp("SLPM_868.07", bootfile)) == 0) // Jikkyou Powerful Pro Yakyuu 2001 ++ || ((strcmp("SLPM_869.90", bootfile)) == 0) // Jikkyou Powerful Pro Yakyuu 2001: Ketteiban ++ || ((strcmp("SLPM_870.33", bootfile)) == 0) // Jikkyou Powerful Pro Yakyuu 2002: Haru ++ ) { ++ /* ++ D01D9646 1040 ++ 801D9646 1000 ++ my code via aprip conversion, I dumped the RAM from the Jikkyou Powerful Pro Yakyuu 2000: Ketteiban game and used the GameShark conversion feature with the dumped ram from Jikkyou Powerful Pro Yakyuu 2001: Ketteiban and it actually was similar enough to work. Code conversion got 2 matches, first match worked. ++ */ ++ add_D0_code(0x8016643A, common_routine_return_compare_val); ++ add_80_code(0x8016643A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Jikkyou Kyousouba Ikusei Simulation Game: Breeding Stud '99 ++ ((strcmp("SLPM_863.16", bootfile)) == 0) { // Japan ++ /* ++ D00A54D2 1040 ++ 800A54D2 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x800A54D2, common_routine_return_compare_val); ++ add_80_code(0x800A54D2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// JoJo's Bizarre Adventure ++ ((strcmp("SLPS_022.36", bootfile)) == 0) { // Japan ++ /* ++ D0035C48 D91E ++ 80035C48 D733 ++ code 1 of 2 found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_j.shtml ++ */ ++ add_D0_code(0x80035C48, 0xD91E); ++ add_80_code(0x80035C48, 0xD733); ++ /* ++ D00360D2 1040 ++ 800360D2 1000 ++ code 2 of 2 found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_j.shtml ++ */ ++ add_D0_code(0x800360D2, common_routine_return_compare_val); ++ add_80_code(0x800360D2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Shiritsu Justice Gakuen: Nekketsu Seishun Nikki 2 ++ ((strcmp("SLPS_021.20", bootfile)) == 0) { // Japan ++ /* ++ D00A6CFA 1040 ++ 800A6CFA 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_j.shtml ++ */ ++ add_D0_code(0x800A6CFA, common_routine_return_compare_val); ++ add_80_code(0x800A6CFA, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Koko Hore! Pukka ++ ((strcmp("SCPS_101.33", bootfile)) == 0) { // Japan ++ /* ++ D008694A 1062 ++ 8008694A 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x8008694A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8008694A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Koneko mo Issho ++ ((strcmp("SCPS_101.27", bootfile)) == 0) { // Japan ++ /* ++ D01520D8 0062 ++ 801520D8 0014 ++ D01520DA 1202 ++ 801520DA 1800 ++ bypass co-developed by me and MottZilla ++ force ok for test commands ++ */ ++ add_D0_code(0x801520D8, 0x0062); ++ add_80_code(0x801520D8, 0x0014); ++ add_D0_code(0x801520DA, 0x1202); ++ add_80_code(0x801520DA, 0x1800); ++ /* ++ D016957C 001E ++ 8016957C 0000 ++ code generated via aprip to patch out readtoc ++ */ ++ add_D0_code(0x8016957C, fake_vc0_bypass_compare_val); ++ add_80_code(0x8016957C, fake_vc0_bypass_patch_val); ++ /* ++ D0151950 000A ++ 80151950 0000 ++ D0151952 1062 ++ 80151952 0000 ++ never lock up on PAL BIOS ++ */ ++ add_D0_code(0x80151950, 0x000A); ++ add_80_code(0x80151950, 0x0000); ++ add_D0_code(0x80151952, 0x1062); ++ add_80_code(0x80151952, 0x0000); ++ install_cheat_engine(); ++ } else if ++ ++// Konami 80's Arcade Gallery ++ ((strcmp("SLPM_862.28", bootfile)) == 0) { // Japan ++ /* ++ D0013FD2 1440 ++ 80013FD2 1000 ++ skip mod check ++ code from https://consolecopyworld.com/psx/psx_game_codes_k.shtml ++ */ ++ add_D0_code(0x80013FD2, common_routine_return_compare_val); ++ add_80_code(0x80013FD2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Legend Of Dragoon ++ ( ++ ((strcmp("SCPS_101.19", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("scps_101.20", bootfile)) == 0) // Japan Disc 2 (weird ass filename not a typo) ++ || ((strcmp("scps_101.21", bootfile)) == 0) // Japan Disc 3 (weird ass filename not a typo) ++ || ((strcmp("scps_101.22", bootfile)) == 0) // Japan Disc 4 (weird ass filename not a typo) ++ ) { ++ /* ++ D01BF172 1040 ++ 801BF172 1000 ++ code found on consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_l.shtml ++ */ ++ add_D0_code(0x801BF172, common_routine_return_compare_val); ++ add_80_code(0x801BF172, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ((strcmp("SCUS_944.91", bootfile)) == 0) // USA Disc 1 ++ || ((strcmp("SCUS_945.84", bootfile)) == 0) // USA Disc 2 ++ || ((strcmp("SCUS_945.85", bootfile)) == 0) // USA Disc 3 ++ || ((strcmp("SCUS_945.86", bootfile)) == 0) // USA Disc 4 ++ ) { ++ /* ++ 801BF6F6 1040 ++ 801BF6F6 1000 ++ my code via aprip's gameshark conversion ++ */ ++ add_D0_code(0x801BF6F6, common_routine_return_compare_val); ++ add_80_code(0x801BF6F6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ( ++ (strcmp("SCES_030.43", bootfile)) == 0) // Europe Disc 1 ++ || ((strcmp("SCES_130.43", bootfile)) == 0) // Europe Disc 2 ++ || ((strcmp("SCES_230.43", bootfile)) == 0) // Europe Disc 3 ++ || ((strcmp("SCES_330.43", bootfile)) == 0) // Europe Disc 4 ++ ) { ++ /* ++ D01C0892 1040 ++ 801C0892 1000 ++ my code via aprip's gameshark conversion ++ */ ++ add_D0_code(0x801C0892, common_routine_return_compare_val); ++ add_80_code(0x801C0892, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ((strcmp("SCES_030.44", bootfile)) == 0) // France Disc 1 ++ || ((strcmp("SCES_130.44", bootfile)) == 0) // France Disc 2 ++ || ((strcmp("SCES_230.44", bootfile)) == 0) // France Disc 3 ++ || ((strcmp("SCES_330.44", bootfile)) == 0) // France Disc 4 ++ ) { ++ /* ++ D01C0872 1040 ++ 801C0872 1040 ++ my code via aprip's gameshark conversion ++ */ ++ add_D0_code(0x801C0872, common_routine_return_compare_val); ++ add_80_code(0x801C0872, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ((strcmp("SCES_030.45", bootfile)) == 0) // Germany Disc 1 ++ || ((strcmp("SCES_130.45", bootfile)) == 0) // Germany Disc 2 ++ || ((strcmp("SCES_230.45", bootfile)) == 0) // Germany Disc 3 ++ || ((strcmp("SCES_330.45", bootfile)) == 0)// Germany Disc 4 ++ ) { ++ /* ++ D01C082E 1040 ++ 801C082E 1000 ++ my code via aprip's gameshark conversion ++ */ ++ add_D0_code(0x801C082E, common_routine_return_compare_val); ++ add_80_code(0x801C082E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ((strcmp("SCES_030.46", bootfile)) == 0) // Italian Disc 1 ++ || ((strcmp("SCES_130.46", bootfile)) == 0) // Italian Disc 2 ++ || ((strcmp("SCES_230.46", bootfile)) == 0) // Italian Disc 3 ++ || ((strcmp("SCES_330.46", bootfile)) == 0)// Italian Disc 4 ++ ) { ++ /* ++ D01C0826 1040 ++ 801C0826 1000 ++ my code via aprip's gameshark conversion ++ */ ++ add_D0_code(0x801C0826, common_routine_return_compare_val); ++ add_80_code(0x801C0826, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Legend Of Mana ++ ((strcmp("SLPS_021.70", bootfile)) == 0) { // Japan ++ /* ++ D0050ECA 1040 ++ 80050ECA 1000 ++ code 1 of 2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_l.shtml ++ */ ++ add_D0_code(0x80050ECA, common_routine_return_compare_val); ++ add_80_code(0x80050ECA, common_routine_return_patch_val); ++ /* ++ D00360D2 1040 ++ 800360D2 1000 ++ code 2 of 2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_l.shtml ++ */ ++ add_D0_code(0x800360D2, common_routine_return_compare_val); ++ add_80_code(0x800360D2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Lord Of Monsters ++ ((strcmp("SCPS_100.86", bootfile)) == 0) { // Japan ++ /* ++ D0015F4A 1040 ++ 80015F4A 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_l.shtml ++ */ ++ add_D0_code(0x80015F4A, common_routine_return_compare_val); ++ add_80_code(0x80015F4A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PAPX_900.81", bootfile)) == 0) { // Japan Demo ++ /* ++ D005E7EA 1040 ++ 8005E7EA 1000 ++ force ok ++ code from aprip conversion ++ */ ++ add_D0_code(0x8005E7EA, common_routine_return_compare_val); ++ add_80_code(0x8005E7EA, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Love Hina: Ai wa Kotoba no Naka ni / Love Hina 2: Kotoba wa Konayuki no You ni ++ ( ++ ((strcmp("SLPM_866.52", bootfile)) == 0) // Love Hina: Ai wa Kotoba no Naka ni ++ || ((strcmp("SLPM_866.77", bootfile)) == 0) // Love Hina 2: Kotoba wa Konayuki no You ni ++ ) { ++ /* ++ D01009FE 1620 ++ 801009FE 1220 ++ custom bypass developed by myself with no $ psx emu ++ */ ++ add_D0_code(0x801009FE, 0x1620); ++ add_80_code(0x801009FE, 0x1220); ++ install_cheat_engine(); ++ } else if ++ ++// Magical Tetris Challenge featuring Mickey ++ ((strcmp("SLPS_017.86", bootfile)) == 0) { ++ /* ++ D014C072 1040 ++ 8014C072 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x8014C072, common_routine_return_compare_val); ++ add_80_code(0x8014C072, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Marvel vs. Capcom: Clash of Super Heroes ++ ( ++ ((strcmp("SLPS_023.68", bootfile)) == 0) // Japan ++ || ((strcmp("SLPM_805.08", bootfile)) == 0) // Japan Demo ++ ) { ++ /* ++ D00F0C3A 1040 ++ 800F0C3A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x800F0C3A, common_routine_return_compare_val); ++ add_80_code(0x800F0C3A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Medievil II ++ ((strcmp("SCUS_945.64", bootfile)) == 0) { ++ /* ++ D009877E 1062 ++ 8009877E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8009877E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8009877E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Minna no Golf 2 ++ ((strcmp("SCPS_100.93", bootfile)) == 0) { // Japan Rev 0 / Japan Rev 1 ++ /* ++ D0050E3A 1040 ++ 80050E3A 1000 ++ works on both Japan Rev 0 and Japan Rev 1 ++ code from https://consolecopyworld.com/psx/psx_game_codes_e.shtml ++ */ ++ add_D0_code(0x80050E3A, common_routine_return_compare_val); ++ add_80_code(0x80050E3A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// MLB 2002 Demo, MLB 2002, MLB 2003 Demo, MLB 2003, MLB 2004, MLB 2005 ++ ( ++ ((strcmp("SCUS_946.48", bootfile)) == 0) // MLB 2002 USA Demo ++ || ((strcmp("SCUS_946.38", bootfile)) == 0) // MLB 2002 USA ++ || ((strcmp("SCUS_946.72", bootfile)) == 0) // MLB 2003 USA Demo ++ || ((strcmp("SCUS_946.53", bootfile)) == 0) // MLB 2003 USA ++ || ((strcmp("SCUS_946.89", bootfile)) == 0) // MLB 2004 USA ++ || ((strcmp("SCUS_946.92", bootfile)) == 0) // MLB 2005 USA ++ ) { ++ /* ++ D002024A 1062 ++ 8002024A 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8002024A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8002024A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Metal Gear Solid: Integral ++ ( ++ ((strcmp("SLPM_862.47", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SLPM_862.48", bootfile)) == 0) // Japan Disc 2 ++ ) { ++ /* ++ D00C3AB6 1040 ++ 800C3AB6 1000 ++ skip mod check ++ code 1/2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x800C3AB6, common_routine_return_compare_val); ++ add_80_code(0x800C3AB6, common_routine_return_patch_val); ++ /* ++ D00C492A 1040 ++ 800C492A 1000 ++ skip mod check ++ code 2/2 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x800C492A, common_routine_return_compare_val); ++ add_80_code(0x800C492A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLPM_862.49", bootfile)) == 0) { // Japan Disc 3 ++ /* ++ D00C209A 1040 ++ 800C209A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x800C209A, common_routine_return_compare_val); ++ add_80_code(0x800C209A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// My Garden ++ ((strcmp("SLPS_022.13", bootfile)) == 0) { // Japan ++ /* ++ D009E212 1040 ++ 8009E212 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x8009E212, common_routine_return_compare_val); ++ add_80_code(0x8009E212, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NBA Shootout 2001 and NBA Shootout 2001 Demo ++ ((strcmp("SCUS_945.81", bootfile)) == 0) { // this game is nuts. Seriously what the fuck is this. The demo and retail versions share not only the same executable filename, the demo executable file is the exact same with 0 diffs compared to the retail version. I guess retail functionallity is being enabled by some other file in track 01 (which do in fact differ between the demo and retail versions). EVEN FUCKING WORSE is that the demo version has serial number SCUS_945.82, but the boot file is SCUS_945.81! For now we enable both demo and retail codes for both versions to ensure it boots, since we can't detect this off of bootfile alone. To do this in a less ugly way we would need to find the file in track 01 that differs (to enable retail functionallity) and then diff that in memory. ++ /* ++ D01AD562 1062 ++ 801AD562 1800 ++ my code generated via aprip (USA) ++ */ ++ add_D0_code(0x801AD562, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801AD562, fake_pal_bios_bypass_patch_val); ++ /* ++ D01ABF9A 1062 ++ 801ABF9A 1800 ++ my code generated via aprip (USA Demo) ++ */ ++ add_D0_code(0x801ABF9A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801ABF9A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NBA Shootout 2002 ++ ((strcmp("SCUS_946.60", bootfile)) == 0) { // USA Demo ++ /* ++ D01AC5E6 1062 ++ 801AC5E6 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x801AC5E6, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801AC5E6, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCUS_946.41", bootfile)) == 0) { // USA ++ /* ++ D01ADD06 1062 ++ 801ADD06 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x801ADD06, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801ADD06, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NBA Shootout 2003 ++ ((strcmp("SCUS_946.73", bootfile)) == 0) { // USA ++ /* ++ D01ADE42 1062 ++ 801ADE42 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x801ADE42, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801ADE42, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NBA Shootout 2004 ++ ((strcmp("SCUS_946.91", bootfile)) == 0) { // USA ++ /* ++ D01ADF0A 1062 ++ 801ADF0A 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x801ADF0A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801ADF0A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NCAA FinalFour 2001 ++ ((strcmp("SCUS_945.79", bootfile)) == 0) { // USA ++ /* ++ D001DE06 1062 ++ 8001DE06 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8001DE06, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8001DE06, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NCAA GameBreaker 2001 ++ ( ++ ((strcmp("SCUS_945.74", bootfile)) == 0) // USA ++ || ((strcmp("SCUS_945.73", bootfile)) == 0) // USA Demo ++ ) { ++ /* ++ D0100A5A 1062 ++ 80100A5A 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x80100A5A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80100A5A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NFL GameDay 2001 ++ ( ++ ((strcmp("SCUS_945.75", bootfile)) == 0) // USA ++ || ((strcmp("SCUS_945.76", bootfile)) == 0) // USA Demo ++ ) ++ { ++ /* ++ D010000E 1062 ++ 8010000E 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8010000E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8010000E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NFL GameDay 2002, NFL GameDay 2003, NFL GameDay 2004, and NFL GameDay 2005 ++ ( ++ ((strcmp("SCUS_946.39", bootfile)) == 0) // NFL GameDay 2002 USA ++ || ((strcmp("SCUS_946.65", bootfile)) == 0) // NFL GameDay 2003 USA ++ || ((strcmp("SCUS_946.90", bootfile)) == 0) // NFL GameDay 2004 USA ++ || ((strcmp("SCUS_946.95", bootfile)) == 0) // NFL GameDay 2005 USA ++ ) { ++ /* ++ D002000E 1062 ++ 8002000E 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x8002000E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8002000E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// NHL FaceOff 2001 ++ ((strcmp("SCUS_945.77", bootfile)) == 0) { // USA ++ /* ++ D00F1126 1062 ++ 800F1126 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x800F1126, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800F1126, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCUS_945.78", bootfile)) == 0) { // USA Demo ++ /* ++ D00F05EE 1062 ++ 800F05EE 1800 ++ my code generated via aprip ++ */ ++ add_D0_code(0x800F05EE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800F05EE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Oha-Studio Dance Dance Revolution ++ ((strcmp("SLPM_866.03", bootfile)) == 0) { // Japan ++ /* ++ D00F8236 1062 ++ 800F8236 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x800F8236, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800F8236, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Omega Boost ++ ((strcmp("SCPS_100.20", bootfile)) == 0) { // Japan ++ /* ++ D0120876 1040 ++ 80120876 1000 ++ force ok ++ code from https://consolecopyworld.com/psx/psx_game_codes_o.shtml ++ */ ++ add_D0_code(0x80120876, common_routine_return_compare_val); ++ add_80_code(0x80120876, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PAPX_900.80", bootfile)) == 0) { // Japan ++ /* ++ D0120876 1040 ++ 80120876 1000 ++ force ok ++ code via aprip conversion (pattern length set to 10) ++ */ ++ add_D0_code(0x80120962, common_routine_return_compare_val); ++ add_80_code(0x80120962, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Ore no Ryouri / My Cooking ++ ((strcmp("SCPS_100.99", bootfile)) == 0) { // Japan ++ /* ++ D0031C1A 1040 ++ 80031C1A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_m.shtml ++ */ ++ add_D0_code(0x80031C1A, common_routine_return_compare_val); ++ add_80_code(0x80031C1A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Ore no Shikabane o Koete Yuke ++ ((strcmp("SCPS_100.74", bootfile)) == 0) { // Japan ++ /* ++ D00C7DBE 1040 ++ 800C7DBE 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_o.shtml ++ */ ++ add_D0_code(0x800C7DBE, common_routine_return_compare_val); ++ add_80_code(0x800C7DBE, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Panekit: Infinitive Crafting Toy Case ++ ((strcmp("SCPS_100.96", bootfile)) == 0) { // Japan Rev 0 / Japan Rev 1 ++ ver_check = (load_addr + 0x3E52C); // First different byte between rev 0 and rev 1 ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0x40) { // Japan Rev 0 ++ /* ++ skip mod check (3 codes) ++ D00F290C 800B ++ 800F290E 1000 ++ code 1/3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x800F290C, 0x800B); ++ add_80_code(0x800F290E, common_routine_return_patch_val); ++ /* ++ D00F290C 800B ++ 800F290C 000E ++ code 2/3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x800F290C, 0x800B); ++ add_80_code(0x800F290C, 0x000E); ++ /* ++ D0052710 FFF8 ++ 80052710 0001 ++ code 3/3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x80052710, 0xFFF8); ++ add_80_code(0x80052710, 0x0001); ++ install_cheat_engine(); ++ } else { // 0x00 Japan Rev 1 ++ //debug_write("YES"); ++ /* ++ skip mod check (3 codes) ++ D0168AAC 800B ++ 80168AAE 1000 ++ code 1/3 converted via aprip ++ */ ++ add_D0_code(0x80168AAC, 0x800B); ++ add_80_code(0x80168AAE, common_routine_return_patch_val); ++ /* ++ D0168AAC 800B ++ 80168AAC 000E ++ code 2/3 converted via aprip ++ */ ++ add_D0_code(0x80168AAC, 0x800B); ++ add_80_code(0x80168AAC, 0x000E); ++ /* ++ D0052710 FFF8 ++ 80052710 0001 ++ code 3/3 needed no conversion for rev 1: ./aprip D0052710 FFF8 paner0 paner1 4 shows this same address with a match ++ */ ++ add_D0_code(0x80052710, 0xFFF8); ++ add_80_code(0x80052710, 0x0001); ++ install_cheat_engine(); ++ } ++ } else if ++ ++// Planet Lakia ++ ((strcmp("SLPM_862.64", bootfile)) == 0) { // Japan / English Translation ++ ver_check = (load_addr + 0xC130); // First different byte between original and translation. Translation has it's own bypass implemented which causes the game to lock up if ours is also applied. ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0x00) { // Japan, English Translation has 0xC4 here ++ /* ++ D001F1C6 1402 ++ 8001F1C6 1000 ++ force ok ++ code 1/2 from https://gamehacking.org/game/107982 ++ */ ++ add_D0_code(0x8001F1C6, 0x1402); ++ add_80_code(0x8001F1C6, common_routine_return_patch_val); ++ /* ++ D002DBA6 1040 ++ 8002DBA6 1000 ++ code 2/2 from https://gamehacking.org/game/107982 ++ */ ++ add_D0_code(0x8002DBA6, common_routine_return_compare_val); ++ add_80_code(0x8002DBA6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } ++ } else if ++ ++// Pocket Jiman ++ ((strcmp("SCPS_101.04", bootfile)) == 0) { // Japan ++ /* ++ D01054D2 1062 ++ 801054D2 1800 ++ code by https://gbatemp.net/members/m4x1mumrez.610331/ generated with APrip ++ */ ++ add_D0_code(0x801054D2, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801054D2, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Pocket MuuMuu ++ ((strcmp("SCPS_100.76", bootfile)) == 0) { // Japan ++ /* ++ skip mod check ++ D001F7F8 2021 ++ 8001F7F4 FFF6 ++ code 1/2 from https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x8001F7F8, 0x2021); ++ add_80_code(0x8001F7F4, 0xFFF6); ++ /* ++ D001F7F8 2021 ++ 8001F7F6 1000 ++ code 2/2 from https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x8001F7F8, 0x2021); ++ add_80_code(0x8001F7F6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// PoPoLoCrois Monogatari II ++ ( ++ ((strcmp("SCPS_101.12", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SCPS_101.13", bootfile)) == 0) // Japan Disc 2 ++ || ((strcmp("SCPS_101.14", bootfile)) == 0) // Japan Disc 3 ++ ) { ++ /* ++ D00B2612 1040 ++ 800B2612 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x800B2612, common_routine_return_compare_val); ++ add_80_code(0x800B2612, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// PoPoRoGue ++ ((strcmp("SCPS_100.50", bootfile)) == 0) { // Japan Rev 0 / Japan Rev 1 ++ ver_check = (load_addr + 0x8FC); // First different byte between rev 0 and rev 1. ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0x64) { // Japan Rev 0 ++ /* ++ D008EF4E 1040 ++ 8008EF4E 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x8008EF4E, common_routine_return_compare_val); ++ add_80_code(0x8008EF4E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else { // 0x7C Rev 1 ++ /* ++ D012E96C 0119 ++ 8012E96C 0000 ++ code 1/4 generated via aprip ++ */ ++ add_D0_code(0x8012E96C, 0x0119); ++ add_80_code(0x8012E96C, 0x0000); ++ /* ++ D012E96E 0304 ++ 8012E96E 0000 ++ code 2/4 generated via aprip ++ */ ++ add_D0_code(0x8012E96E, 0x0304); ++ add_80_code(0x8012E96E, 0x0000); ++ /* ++ D012E978 0119 ++ 8012E978 0000 ++ code 3/4 generated via aprip ++ */ ++ add_D0_code(0x8012E978, 0x0119); ++ add_80_code(0x8012E978, 0x0000); ++ /* ++ D012E97A 0302 ++ 8012E97A 0000 ++ code 4/4 generated via aprip ++ */ ++ add_D0_code(0x8012E97A, 0x302); ++ add_80_code(0x8012E97A, 0x0000); ++ install_cheat_engine(); ++ } ++ } else if ++ ++// Pop'n Music: Animation Melody ++ ((strcmp("SLPM_865.92", bootfile)) == 0) { // Japan ++ /* ++ D0016112 1062 ++ 80016112 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x80016112, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80016112, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Pop'n Music: Disney Tunes ++ ((strcmp("SLPM_866.70", bootfile)) == 0) { // Japan ++ /* ++ D0013332 1062 ++ 80013332 1800 ++ code generated via aprip ++ */ ++ add_D0_code(0x80013332, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80013332, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Pop'n Music 2 ++ ((strcmp("SLPM_862.94", bootfile)) == 0) { // Japan ++ /* ++ D0015342 1040 ++ 80015342 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_p.shtml ++ */ ++ add_D0_code(0x80015342, common_routine_return_compare_val); ++ add_80_code(0x80015342, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Pop'n Music 5 ++ ((strcmp("SLPM_869.37", bootfile)) == 0) { // Japan ++ /* ++ D0030E0E 1062 ++ 80030E0E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x80030E0E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80030E0E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Pop'n Music 6 ++ ((strcmp("SLPM_870.89", bootfile)) == 0) { // Japan ++ /* ++ D0030F06 1062 ++ 80030F06 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x80030F06, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80030F06, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Resident Evil 3: The Last Escape ++ ((strcmp("SLPS_023.00", bootfile)) == 0) { // Japan Rev 0/ Japan Rev 1 ++ ver_check = (load_addr + 0x1B0); // First different byte between revisions ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0xF4) // Rev 0 ++ { ++ /* ++ D01840E2 1040 ++ 801840E2 1000 ++ 'skip check' code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x801840E2, common_routine_return_compare_val); ++ add_80_code(0x801840E2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else { // Rev 1 ++ /* ++ D018418E 1040 ++ 8018418E 1000 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x8018418E, common_routine_return_compare_val); ++ add_80_code(0x8018418E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } ++ } else if ++ ++ ((strcmp("SLPM_804.85", bootfile)) == 0) { // Japan Demo ++ /* ++ D01800E2 1040 ++ 801800E2 1000 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x801800E2, common_routine_return_compare_val); ++ add_80_code(0x801800E2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLUS_900.64", bootfile)) == 0) { // USA Demo ++ /* ++ D01840E2 1040 ++ 801840E2 1000 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x801840E2, common_routine_return_compare_val); ++ add_80_code(0x801840E2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Resident Evil Survivor ++ ((strcmp("SLPS_025.53", bootfile)) == 0) { // Japan ++ /* ++ D0017962 1040 ++ 80017962 1000 ++ 'skip mod check' code is from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_b.shtml ++ */ ++ add_D0_code(0x80017962, common_routine_return_compare_val); ++ add_80_code(0x80017962, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLUS_010.87", bootfile)) == 0) { // USA ++ /* ++ D001714E 1040 ++ 8001714E 1000 ++ converted code via aprip ++ */ ++ add_D0_code(0x8001714E, common_routine_return_compare_val); ++ add_80_code(0x8001714E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ( ++ ((strcmp("SLES_027.32", bootfile)) == 0) // Europe ++ || ((strcmp("SLES_027.44", bootfile)) == 0) // France ++ ) { ++ /* ++ D001713E 1040 ++ 8001713E 1000 ++ converted code via aprip ++ */ ++ add_D0_code(0x8001713E, common_routine_return_compare_val); ++ add_80_code(0x8001713E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Robbit mon Dieu ++ ((strcmp("SCPS_101.03", bootfile)) == 0) { // Japan ++ /* ++ D001C646 1040 ++ 8001C646 1000 ++ skip mod check ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_r.shtml ++ */ ++ add_D0_code(0x8001C646, common_routine_return_compare_val); ++ add_80_code(0x8001C646, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman ++ ((strcmp("SLPS_022.20", bootfile)) == 0) { // Japan ++ /* ++ D006C92A 1040 ++ 8006C92A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_r.shtml ++ */ ++ add_D0_code(0x8006C92A, common_routine_return_compare_val); ++ add_80_code(0x8006C92A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman 2: Dr. Wily no Nazo ++ ((strcmp("SLPS_022.55", bootfile)) == 0) { // Japan ++ /* ++ D006CA82 1040 ++ 8006CA82 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_r.shtml ++ */ ++ add_D0_code(0x8006CA82, common_routine_return_compare_val); ++ add_80_code(0x8006CA82, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman 3: Dr. Wily no Saigo!? ++ ((strcmp("SLPS_022.62", bootfile)) == 0) { // Japan ++ /* ++ D006CA1A 1040 ++ 8006CA1A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_r.shtml ++ */ ++ add_D0_code(0x8006CA1A, common_routine_return_compare_val); ++ add_80_code(0x8006CA1A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman 4: Aratanaru Yabou!! ++ ((strcmp("SLPS_023.24", bootfile)) == 0) { // Japan ++ /* ++ D01D9E78 4042 ++ 801D9E78 0042 ++ force ok code ++ */ ++ add_D0_code(0x801D9E78, 0x4042); ++ add_80_code(0x801D9E78, 0x0042); ++ /* ++ D007009C 001E ++ 8007009C 0000 ++ my code via aprip to patch out readtoc ++ */ ++ add_D0_code(0x8007009C, fake_vc0_bypass_compare_val); ++ add_80_code(0x8007009C, fake_vc0_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman 5: Blues no Wana!? ++ ((strcmp("SLPS_023.38", bootfile)) == 0) { // Japan ++ /* ++ D006E0D6 1062 ++ 8006E0D6 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8006E0D6, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8006E0D6, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman 6 ++ ((strcmp("SLPS_023.79", bootfile)) == 0) { // Japan ++ /* ++ D006DA7A 1040 ++ 8006DA7A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_r.shtml ++ */ ++ add_D0_code(0x8006DA7A, common_routine_return_compare_val); ++ add_80_code(0x8006DA7A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman X5 ++ ((strcmp("SLPM_866.66", bootfile)) == 0) { // Japan ++ /* ++ D0011EC6 1062 ++ 80011EC6 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x80011EC6, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80011EC6, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Rockman X6 ++ ((strcmp("SLPM_869.59", bootfile)) == 0) { // Japan ++ /* ++ D001219E 1062 ++ 8001219E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x8001219E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x8001219E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// SaGa Frontier II ++ ((strcmp("SLPS_019.90", bootfile)) == 0) { // Japan ++ /* ++ D00DC7D6 1040 ++ 800DC7D6 1000 ++ force ok test commands ++ code from https://consolecopyworld.com/psx/psx_game_codes_s.shtml ++ */ ++ add_D0_code(0x800DC7D6, common_routine_return_compare_val); ++ add_80_code(0x800DC7D6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Saru! Get You! / Ape Escape ++ ((strcmp("SCPS_100.91", bootfile)) == 0) { // Japan Rev 0 / Japan Rev 1 ++ ver_check = (load_addr + 0x68); // First different byte between revisions is 0x68 ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0xA0) { // Japan Rev 0 ++ /* ++ D0136A8A 1040 ++ 80136A8A 1000 ++ force ok test commands ++ code from https://consolecopyworld.com/psx/psx_game_codes_a.shtml ++ */ ++ add_D0_code(0x80136A8A, common_routine_return_compare_val); ++ add_80_code(0x80136A8A, common_routine_return_patch_val); ++ } else { // 0xF0 Japan Rev 1 ++ /* ++ D0136AAA 1040 ++ 80136AAA 1000 ++ force ok test commands ++ my code via aprip conversion ++ */ ++ add_D0_code(0x80136AAA, common_routine_return_compare_val); ++ add_80_code(0x80136AAA, common_routine_return_patch_val); ++ } ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PAPX_900.79", bootfile)) == 0) { // Japan demo ++ /* ++ D0137A2A 1040 ++ 80137A2A 1000 ++ force ok test commands ++ my code via aprip conversion ++ */ ++ add_D0_code(0x80137A2A, common_routine_return_compare_val); ++ add_80_code(0x80137A2A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Silent Hill ++ ((strcmp("SLPM_861.92", bootfile)) == 0) { // Japan Rev 0/Japan Rev 1/Japan Rev 2 ++ // same code works for Japan Rev 0, Japan Rev 1, and Japan Rev 2 ++ /* ++ D01E778E 1040 ++ 801E778E 1000 ++ code from https://gamehacking.org/game/108601 ++ */ ++ add_D0_code(0x801E778E, common_routine_return_compare_val); ++ add_80_code(0x801E778E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Spyro The Dragon ++ ( ++ ((strcmp("SCPS_100.85", bootfile)) == 0) // Japan ++ || ((strcmp("SCPS_100.83", bootfile)) == 0) ++ ) { ++ /* ++ D006003E 9222 ++ 8006003E A222 ++ skip mod check ++ codes from https://consolecopyworld.com/psx/psx_game_codes_s.shtml ++ */ ++ add_D0_code(0x8006003E, 0x9222); ++ add_80_code(0x8006003E, 0xA222); ++ install_cheat_engine(); ++ } else if ++ ++// Spyro: Year Of The Dragon ++ ((strcmp("SCUS_944.67", bootfile)) == 0) { // has 2 versions, rev 0 and rev 1 ++ ver_check = (load_addr); // First different byte between revisions is well the first byte :) ++ //debug_write("Got address for version check: %x", (uint32_t) ver_check); ++ ver_check_val = *(uint8_t*) ver_check; ++ //debug_write("Ver check address has the contents : %x", (uint8_t) ver_check_val); ++ if(ver_check_val == 0xF4) { // Rev 0 ++ /* ++ D007F08C 0001 ++ 8007F08C 0000 ++ D007F08C 0002 ++ 8007F08C 0000 ++ D007F08C 0003 ++ 8007F08C 0000 ++ D007F08C 0004 ++ 8007F08C 0000 ++ D007F08C 0005 ++ 8007F08C 0000 ++ D007F08C 0006 ++ 8007F08C 0000 ++ D007F08C 0007 ++ 8007F08C 0000 ++ D007F08C 0008 ++ 8007F08C 0000 ++ D007F08C 0009 ++ 8007F08C 0000 ++ D007F08C 000A ++ 8007F08C 0000 ++ D007F08C 000B ++ 8007F08C 0000 ++ D007F08C 000C ++ 8007F08C 0000 ++ D007F08C 000D ++ 8007F08C 0000 ++ D007F08C 000E ++ 8007F08C 0000 ++ codes co-developed by MottZilla (mostly) and myself. ++ */ ++ add_D0_code(0x8007F08C, 0x0001); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0002); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0003); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0004); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0005); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0006); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0007); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0008); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x0009); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x000A); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x000B); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x000C); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x000D); ++ add_80_code(0x8007F08C, 0x0000); ++ add_D0_code(0x8007F08C, 0x000E); ++ add_80_code(0x8007F08C, 0x0000); ++ install_cheat_engine(); ++ } else { // 0x08 = Rev 1 ++ /* ++ D007F23C 0001 ++ 8007F23C 0000 ++ D007F23C 0002 ++ 8007F23C 0000 ++ D007F23C 0003 ++ 8007F23C 0000 ++ D007F23C 0004 ++ 8007F23C 0000 ++ D007F23C 0005 ++ 8007F23C 0000 ++ D007F23C 0006 ++ 8007F23C 0000 ++ D007F23C 0007 ++ 8007F23C 0000 ++ D007F23C 0008 ++ 8007F23C 0000 ++ D007F23C 0009 ++ 8007F23C 0000 ++ D007F23C 000A ++ 8007F23C 0000 ++ D007F23C 000B ++ 8007F23C 0000 ++ D007F23C 000C ++ 8007F23C 0000 ++ D007F23C 000D ++ 8007F23C 0000 ++ D007F23C 000E ++ 8007F23C 0000 ++ Codes manually converted to Rev 1 by myself ++ */ ++ add_D0_code(0x8007F23C, 0x0001); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0002); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0003); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0004); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0005); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0006); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0007); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0008); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x0009); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x000A); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x000B); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x000C); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x000D); ++ add_80_code(0x8007F23C, 0x0000); ++ add_D0_code(0x8007F23C, 0x000E); ++ add_80_code(0x8007F23C, 0x0000); ++ install_cheat_engine(); ++ } ++ } else if ++ ++// Street Fighter EX2 Plus ++ ((strcmp("SLPS_025.08", bootfile)) == 0) { // Japan ++ /* ++ D01BD48A 1040 ++ 801BD48A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_s.shtml ++ */ ++ add_D0_code(0x801BD48A, common_routine_return_compare_val); ++ add_80_code(0x801BD48A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLPM_805.17", bootfile)) == 0) { // Japan Demo ++ /* ++ D01BD13E 1040 ++ 801BD13E 1000 ++ my code via aprip gameshark code conversion ++ */ ++ add_D0_code(0x801BD48A, common_routine_return_compare_val); ++ add_80_code(0x801BD13E, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Strider 2 ++ ((strcmp("SLUS_011.63", bootfile)) == 0) { // USA ++ /* ++ D01F4D5E 1062 ++ 801F4D5E 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x801F4D5E, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x801F4D5E, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Tokimeki Memorial 2 (soo many god damn discs/versions/bootfiles) ++ ( ++ // main game (5 discs) ++ ((strcmp("SLPM_863.55", bootfile)) == 0) // Tokimeki Memorial 2 Rev 0/Rev 1 Disc 1 ++ || ((strcmp("SLPM_863.50", bootfile)) == 0) // Tokimeki Memorial 2 Limited Box Disc 1 ++ || ((strcmp("SLPM_863.56", bootfile)) == 0) // Tokimeki Memorial 2 Rev 0/Rev 1 Disc 2 ++ || ((strcmp("SLPM_863.51", bootfile)) == 0) // Tokimeki Memorial 2 Limited Box Disc 2 ++ || ((strcmp("SLPM_863.57", bootfile)) == 0) // Tokimeki Memorial 2 Rev 0/Rev 1 Disc 3 ++ || ((strcmp("SLPM_863.52", bootfile)) == 0) // Tokimeki Memorial 2 Limited Box Disc 3 ++ || ((strcmp("SLPM_863.58", bootfile)) == 0) // Tokimeki Memorial 2 Rev 0/Rev 1 Disc 4 ++ || ((strcmp("SLPM_863.53", bootfile)) == 0) // Tokimeki Memorial 2 Limited Box Disc 4 ++ || ((strcmp("SLPM_863.59", bootfile)) == 0) // Tokimeki Memorial 2 Rev 0/Rev 1 Disc 5 ++ || ((strcmp("SLPM_863.54", bootfile)) == 0) // Tokimeki Memorial 2 Limited Box Disc 5 ++ // dlc evs (3 discs) ++ || ((strcmp("SLPM_805.27", bootfile)) == 0) // Tokimeki Memorial 2 Emotional Voice System Append Disc 1 ++ || ((strcmp("SLPM_805.44", bootfile)) == 0) // Tokimeki Memorial 2 Emotional Voice System Append Disc 2 ++ || ((strcmp("SLPM_805.50", bootfile)) == 0) // Tokimeki Memorial 2 Emotional Voice System Append Disc 3 ++ ) { ++ /* ++ D00108C6 1040 ++ 800108C6 1000 ++ code from https://gamehacking.org/game/109315 ++ force ok result of test commands ++ */ ++ add_D0_code(0x800108C6, common_routine_return_compare_val); ++ add_80_code(0x800108C6, common_routine_return_patch_val); ++ /* ++ D0011514 001E ++ 80011514 0000 ++ my code via aprip to disable readtoc ++ */ ++ add_D0_code(0x80011514, fake_vc0_bypass_compare_val); ++ add_80_code(0x80011514, fake_vc0_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Tokimeki Memorial Drama Series Vol. 3: Tabidachi no Uta ++ ( ++ ((strcmp("SLPM_862.24", bootfile)) == 0) // Japan Disc 1 ++ || ((strcmp("SLPM_862.25", bootfile)) == 0) // Japan Disc 2 ++ ){ // Japan Disc 1 ++ /* ++ D005C290 2021 ++ 8005C28C FFF6 ++ code 1/3 from https://consolecopyworld.com/psx/psx_game_codes_t.shtml ++ */ ++ add_D0_code(0x8005C290, 0x2021); ++ add_80_code(0x8005C28C, 0xFFF6); ++ /* ++ D005C290 2021 ++ 8005C28E 1000 ++ code 2/3 from https://consolecopyworld.com/psx/psx_game_codes_t.shtml ++ */ ++ add_D0_code(0x8005C290, 0x2021); ++ add_80_code(0x8005C28E, common_routine_return_patch_val); ++ /* ++ D001B3A6 1060 ++ 8001B3A6 1000 ++ code 3/3 from https://consolecopyworld.com/psx/psx_game_codes_t.shtml ++ */ ++ add_D0_code(0x8001B3A6, 0x1060); ++ add_80_code(0x8001B3A6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Tomba! 2: The Evil Swine Return ++ ((strcmp("SCUS_944.54", bootfile)) == 0) { // USA ++ /* ++ D0011A1A 1062 ++ 80011A1A 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x80011A1A, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x80011A1A, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Tron ni Kobun ++ ((strcmp("SLPS_021.08", bootfile)) == 0) { // Japan ++ /* ++ D001277A 1040 ++ 8001277A 1000 ++ code from https://gamehacking.org/game/109452 ++ force ok test command results ++ */ ++ add_D0_code(0x8001277A, common_routine_return_compare_val); ++ add_80_code(0x8001277A, common_routine_return_patch_val); ++ /* ++ D004E168 001E ++ 8004E168 0000 ++ my code via aprip to patch out readtoc ++ */ ++ add_D0_code(0x8004E168, fake_vc0_bypass_compare_val); ++ add_80_code(0x8004E168, fake_vc0_bypass_patch_val); ++ // together these commands disable the routine effectively ++ install_cheat_engine(); ++ } else if ++ ++// Um Jammer Lammy ++// TODO: FIND Um Jammer Lammy Japan Tentou Houei-you and add support if needed ++ ((strcmp("SCPS_180.11", bootfile)) == 0) { // Japan ++ /* ++ D01DA7D8 2021 ++ 801DA7D4 FFF6 ++ code 1/2 from https://consolecopyworld.com/psx/psx_game_codes_u.shtml ++ */ ++ add_D0_code(0x801DA7D8, 0x2021); ++ add_80_code(0x801DA7D4, 0xFFF6); ++ /* ++ D01DA7D8 2021 ++ 801DA7D6 1000 ++ code 2/2 from https://consolecopyworld.com/psx/psx_game_codes_u.shtml ++ */ ++ add_D0_code(0x801DA7D8, 0x2021); ++ add_80_code(0x801DA7D6, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCUS_943.98", bootfile)) == 0) { // USA Demo ++ /* ++ D01DAB44 2021 ++ 801DAB40 FFF6 ++ mod-check skip code via modpar https://consolecopyworld.com/psx/psx_utils_pn_cnv.shtml ++ */ ++ add_D0_code(0x801DAB44, 0x2021); ++ add_80_code(0x801DAB40, 0xFFF6); ++ /* ++ D01DAB44 2021 ++ 801DAB42 1000 ++ mod-check skip code via modpar https://consolecopyworld.com/psx/psx_utils_pn_cnv.shtml ++ */ ++ add_D0_code(0x801DAB44, 0x2021); ++ add_80_code(0x801DAB42, 0x1000); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCES_017.53", bootfile)) == 0) { // Europe ++ /* ++ D01D81B8 2021 ++ 801D81B4 FFF6 ++ mod-check skip code via modpar https://consolecopyworld.com/psx/psx_utils_pn_cnv.shtml ++ */ ++ add_D0_code(0x801D81B8, 0x2021); ++ add_80_code(0x801D81B4, 0xFFF6); ++ /* ++ D01D81B8 2021 ++ 801D81B6 1000 ++ mod-check skip code via modpar https://consolecopyworld.com/psx/psx_utils_pn_cnv.shtml ++ */ ++ add_D0_code(0x801D81B8, 0x2021); ++ add_80_code(0x801D81B6, 0x1000); ++ install_cheat_engine(); ++ } else if ++ ++// Vandal Hearts II ++ ((strcmp("SLUS_009.40", bootfile)) == 0) { // USA ++ /* ++ D0040C90 000B ++ 80040C92 1000 ++ code from http://archive.thegia.com/news/9912/n09a.html ++ */ ++ add_D0_code(0x80040C90, 0x000B); ++ add_80_code(0x80040C92, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SLPM_862.51", bootfile)) == 0) { // Japan ++ /* ++ D00C49A2 1040 ++ 800C49A2 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_v.shtml ++ */ ++ add_D0_code(0x800C49A2, common_routine_return_compare_val); ++ add_80_code(0x800C49A2, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Wild Arms 2 ++ ( ++ ((strcmp("SCPS_100.89", bootfile)) == 0) // Japan Rev 0 Disc 1 / Japan Rev 1 Disc 1 ++ || ((strcmp("SCPS_100.90", bootfile)) == 0) // Japan Rev 0 Disc 2 / Japan Rev 1 Disc 2 ++ ) { ++ /* ++ D002A51A 1040 ++ 8002A51A 1000 ++ code from https://consolecopyworld.com/psx/psx_game_codes_w.shtml ++ */ ++ add_D0_code(0x8002A51A, common_routine_return_compare_val); ++ add_80_code(0x8002A51A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("PCPX_961.61", bootfile)) == 0) // Japan Demo 1 ++ { ++ /* ++ D002A594 2021 ++ 8002A590 FFF6 ++ code 1/2 from https://consolecopyworld.com/psx/psx_game_codes_w.shtml ++ */ ++ add_D0_code(0x8002A594, 0x2021); ++ add_80_code(0x8002A590, 0xFFF6); ++ /* ++ D002A594 2021 ++ 8002A592 1000 ++ code 2/2 from https://consolecopyworld.com/psx/psx_game_codes_w.shtml ++ */ ++ add_D0_code(0x8002A594, 0x2021); ++ add_80_code(0x8002A592, 0x1000); ++ install_cheat_engine(); ++ ++ } else if ++ ++ ((strcmp("PCPX_961.71", bootfile)) == 0) // Japan Demo 2 ++ { ++ /* ++ D002A590 2021 ++ 8002A58C FFF6 ++ code 1/2 converted via aprip ++ */ ++ add_D0_code(0x8002A590, 0x2021); ++ add_80_code(0x8002A58C, 0xFFF6); ++ /* ++ D002A590 2021 ++ 8002A58E 1000 ++ code 2/2 converted via aprip ++ */ ++ add_D0_code(0x8002A590, 0x2021); ++ add_80_code(0x8002A58E, 0x1000); ++ install_cheat_engine(); ++ ++ } else if ++ ( ++ ((strcmp("SCUS_944.84", bootfile)) == 0) // USA Disc 1 ++ || ((strcmp("SCUS_944.98", bootfile)) == 0) // USA Disc 2 ++ ) { ++ /* ++ D00282CE 1062 ++ 800282CE 1800 ++ my code via aprip ++ */ ++ add_D0_code(0x800282CE, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800282CE, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++ ((strcmp("SCUS_945.92", bootfile)) == 0) { // USA Demo ++ /* ++ D00283C6 1062 ++ 800283C6 1800 ++ my code via aprip gameshark code conversion on my own code (code-ception) ++ */ ++ add_D0_code(0x800283C6, fake_pal_bios_bypass_compare_val); ++ add_80_code(0x800283C6, fake_pal_bios_bypass_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// World Soccer Jikkyou Winning Eleven 4 ++ ((strcmp("SLPM_862.91", bootfile)) == 0) { // Japan Rev 0/Japan Rev 1 ++ /* ++ D01030CA 1040 ++ 801030CA 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_w.shtml ++ */ ++ add_D0_code(0x801030CA, common_routine_return_compare_val); ++ add_80_code(0x801030CA, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// XI[SAI] Jumbo ++ ((strcmp("SCPS_101.23", bootfile)) == 0) { // Japan ++ /* ++ D010178A 1040 ++ 8010178A 1000 ++ code 1 of 3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_x.shtml ++ */ ++ add_D0_code(0x8010178A, common_routine_return_compare_val); ++ add_80_code(0x8010178A, common_routine_return_patch_val); ++ /* ++ D00A370A 1040 ++ 800A370A 1000 ++ code 2 of 3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_x.shtml ++ */ ++ add_D0_code(0x800A370A, common_routine_return_compare_val); ++ add_80_code(0x800A370A, common_routine_return_patch_val); ++ /* ++ D0113C1A 1040 ++ 80113C1A 1000 ++ code 3 of 3 from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_x.shtml ++ */ ++ add_D0_code(0x80113C1A, common_routine_return_compare_val); ++ add_80_code(0x80113C1A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } else if ++ ++// Yu-Gi-Oh! Forbidden Memories ++ ( ++ ((strcmp("SLPM_863.96", bootfile)) == 0) // Japan ++ || ((strcmp("SLPM_863.98", bootfile)) == 0) // Japan Demo ++ || ((strcmp("SLUS_014.11", bootfile)) == 0) // USA ++ || ((strcmp("SLES_039.47", bootfile)) == 0) // Europe ++ || ((strcmp("SLES_039.48", bootfile)) == 0) // France ++ || ((strcmp("SLES_039.49", bootfile)) == 0) // Germany ++ || ((strcmp("SLES_039.50", bootfile)) == 0) // Spain ++ || ((strcmp("SLES_039.51", bootfile)) == 0) // Italy ++ ) { ++ /* ++ D016818A 1040 ++ 8016818A 1000 ++ code from consolecopyworld: https://consolecopyworld.com/psx/psx_game_codes_y.shtml ++ */ ++ add_D0_code(0x8016818A, common_routine_return_compare_val); ++ add_80_code(0x8016818A, common_routine_return_patch_val); ++ install_cheat_engine(); ++ } ++} +\ No newline at end of file +diff --git a/loader/ap-bypass.h b/loader/ap-bypass.h +new file mode 100644 +index 0000000..d9379b6 +--- /dev/null ++++ b/loader/ap-bypass.h +@@ -0,0 +1,10 @@ ++ ++#pragma once ++ ++void activate_anti_anti_piracy(char * bootfile, const int32_t load_addr); ++void add_8bit_code(const uint32_t gs1, const uint8_t gs2, const uint8_t gs_code_type); ++void add_16bit_code(const uint32_t gs1, const uint16_t gs2, const uint8_t gs_code_type); ++void install_cheat_engine(); ++ ++extern bool cheat_engine_installed; ++extern bool is_beat_mania_append_gottamix; +diff --git a/tmp/og-tonyhax.O0E/loader/bios-asm.S b/loader/bios-asm.S +index f181105..d81b6bb 100644 +--- a/tmp/og-tonyhax.O0E/loader/bios-asm.S ++++ b/loader/bios-asm.S +@@ -1,6 +1,4 @@ + +-#include +- + .text + .align 4 + +@@ -10,9 +8,9 @@ + + .global FakeEnqueueCdIntr + FakeEnqueueCdIntr: +- lw ra, 0x14(sp) +- addi sp, 0x18 +- jr ra ++ lw $31, 0x14($29) ++ addi $29, 0x18 ++ jr $31 + + ############ + # SYSCALLS # +@@ -20,15 +18,15 @@ FakeEnqueueCdIntr: + + .global EnterCriticalSection + EnterCriticalSection: +- li a0, 0x01 ++ li $4, 0x01 + syscall +- jr ra ++ jr $31 + + .global ExitCriticalSection + ExitCriticalSection: +- li a0, 0x02 ++ li $4, 0x02 + syscall +- jr ra ++ jr $31 + + ############### + # A-FUNCTIONS # +@@ -36,110 +34,110 @@ ExitCriticalSection: + + .global todigit + todigit: +- li t1, 0x0A ++ li $9, 0x0A + j 0xA0 + + .global strcmp + strcmp: +- li t1, 0x17 ++ li $9, 0x17 + j 0xA0 + + .global strncmp + strncmp: +- li t1, 0x18 ++ li $9, 0x18 + j 0xA0 + + .global strcpy + strcpy: +- li t1, 0x19 ++ li $9, 0x19 + j 0xA0 + + .global strlen + strlen: +- li t1, 0x1B ++ li $9, 0x1B + j 0xA0 + + .global strchr + strchr: +- li t1, 0x1E ++ li $9, 0x1E + j 0xA0 + + .global memcpy + memcpy: +- li t1, 0x2A +- j 0xA0 +- +-.global std_out_puts +-std_out_puts: +- li t1, 0x3E ++ li $9, 0x2A + j 0xA0 + + .global DoExecute + DoExecute: +- # Pepsiman (J) crashes if s5 is not zero +- # The BIOS leaves them s1-s6 zeroed, so we'll do the same +- li s1, 0 +- li s2, 0 +- li s3, 0 +- li s4, 0 +- li s5, 0 +- li s6, 0 +- li t1, 0x43 ++ # Pepsiman (J) crashes if $21 is not zero ++ # The BIOS leaves them $17-s6 zeroed, so we'll do the same ++ li $17, 0 ++ li $18, 0 ++ li $19, 0 ++ li $20, 0 ++ li $21, 0 ++ li $22, 0 ++ li $9, 0x43 + j 0xA0 + + .global FlushCache + FlushCache: +- li t1, 0x44 ++ li $9, 0x44 + j 0xA0 + + .global init_a0_b0_c0_vectors + init_a0_b0_c0_vectors: +- li t1, 0x45 ++ li $9, 0x45 + j 0xA0 + + .global GPU_dw + GPU_dw: +- li t1, 0x46 ++ li $9, 0x46 + j 0xA0 + + .global SendGP1Command + SendGP1Command: +- li t1, 0x48 ++ li $9, 0x48 + j 0xA0 + + .global GPU_cw + GPU_cw: +- li t1, 0x49 ++ li $9, 0x49 + j 0xA0 + + .global GPU_cwp + GPU_cwp: +- li t1, 0x4A ++ li $9, 0x4A + j 0xA0 + + .global LoadAndExecute + LoadAndExecute: +- li t1, 0x51 ++ li $9, 0x51 + j 0xA0 + + .global CdInit + CdInit: +- li t1, 0x54 ++ li $9, 0x54 + j 0xA0 + + .global SetConf + SetConf: +- li t1, 0x9C +- j 0xA0 +- +-.global SetMemSize +-SetMemSize: +- li t1, 0x9F ++ li $9, 0x9C + j 0xA0 + + .global CdReadSector + CdReadSector: +- li t1, 0xA5 ++ li $9, 0xA5 ++ j 0xA0 ++ ++.global CdGetLbn ++CdGetLbn: ++ li $9, 0xA4 ++ j 0xA0 ++ ++.global SetMemSize ++SetMemSize: ++ li $9, 0x9F + j 0xA0 + + ############### +@@ -148,37 +146,42 @@ CdReadSector: + + .global SetDefaultExitFromException + SetDefaultExitFromException: +- li t1, 0x18 ++ li $9, 0x18 + j 0xB0 + + .global FileOpen + FileOpen: +- li t1, 0x32 ++ li $9, 0x32 + j 0xB0 + + .global FileRead + FileRead: +- li t1, 0x34 ++ li $9, 0x34 + j 0xB0 + + .global FileClose + FileClose: +- li t1, 0x36 ++ li $9, 0x36 ++ j 0xB0 ++ ++.global FormatDevice ++FormatDevice: ++ li $9, 0x41 + j 0xB0 + + .global GetLastError + GetLastError: +- li t1, 0x54 ++ li $9, 0x54 + j 0xB0 + + .global GetC0Table + GetC0Table: +- li t1, 0x56 ++ li $9, 0x56 + j 0xB0 + + .global GetB0Table + GetB0Table: +- li t1, 0x57 ++ li $9, 0x57 + j 0xB0 + + ############### +@@ -187,15 +190,15 @@ GetB0Table: + + .global InstallExceptionHandlers + InstallExceptionHandlers: +- li t1, 0x07 ++ li $9, 0x07 + j 0xC0 + + .global InstallDevices + InstallDevices: +- li t1, 0x12 ++ li $9, 0x12 + j 0xC0 + + .global AdjustA0Table + AdjustA0Table: +- li t1, 0x1C ++ li $9, 0x1C + j 0xC0 +diff --git a/tmp/og-tonyhax.O0E/loader/bios.c b/loader/bios.c +index 92edfab..be740e7 100644 +--- a/tmp/og-tonyhax.O0E/loader/bios.c ++++ b/loader/bios.c +@@ -5,23 +5,13 @@ + #include "debugscreen.h" + #include "str.h" + +-void * original_disc_error; +- +-bool console_has_tty() { +- /* +- * Check if the console has a SCN2681 TTY used for debug by writing data to the control +- * registers and reading it back. +- * +- * The control is 16 bit wide, and is accessed by writing or reading twice the same register. +- */ +- volatile uint8_t * scn2681modereg = (uint8_t *) 0x1F802020; ++// Set to zero unless you are using an emulator or have a physical UART on the PS1, else it'll freeze ++const uint32_t tty_enabled = 0; + +- *scn2681modereg = 0x55; +- *scn2681modereg = 0xAA; +- return *scn2681modereg == 0x55 && *scn2681modereg == 0xAA; +-} ++void * original_disc_error; + + void bios_reinitialize() { ++ // See https://github.com/ogamespec/psxdev/blob/master/reverse/Main.c for reference to some of this... + // Disable interrupts + EnterCriticalSection(); + +@@ -53,7 +43,7 @@ void bios_reinitialize() { + I_MASK = 0; + + // Setup devices. +- InstallDevices(console_has_tty()); ++ InstallDevices(tty_enabled); + + /* + * Configure with default values +@@ -94,12 +84,12 @@ void bios_reinitialize() { + /* + * Set RAM size to 8MB, which is incorrect but it's what the BIOS sets. + * +- * This is required because the entrypoint game might've set it to 2MB, and a bugged target +- * game might accidentally access an address in the mirror region, causing a fault to be caused ++ * This is required because the entrypoint game might've set it to 2MB, and a target ++ * game might accidentally access an address in the mirror region (expecting the default 8MB setting from BIOS boot), causing a fault to be caused + * in real hardware. + */ + SetMemSize(8); +- ++ + // Re-enable interrupts + ExitCriticalSection(); + +diff --git a/tmp/og-tonyhax.O0E/loader/bios.h b/loader/bios.h +index 2a2db60..91b0171 100644 +--- a/tmp/og-tonyhax.O0E/loader/bios.h ++++ b/loader/bios.h +@@ -1,7 +1,7 @@ + + #pragma once + #include +-#include ++#include + + typedef struct exe_header exe_header_t; + typedef struct exe_offsets exe_offsets_t; +@@ -21,7 +21,7 @@ struct exe_offsets { + }; + + struct exe_header { +- char signature[8]; // 0x00 ++ char signature[8]; // 0x00 + uint8_t _reserved0[8]; // 0x08 + exe_offsets_t offsets; // 0x10 + }; +@@ -245,13 +245,6 @@ void ExitCriticalSection(); + */ + uint32_t todigit(char c); + +-/** +- * Prints a text through the TTY. +- * +- * @param text text to print +- */ +-void std_out_puts(const char * text); +- + /** + * Starts a previously loaded executable. + * +@@ -373,6 +366,15 @@ void SetMemSize(uint32_t size); + */ + int32_t CdReadSector(uint32_t sector_count, uint32_t start_sector, void * buffer); + ++/** ++ * Gets LBA of file on disc ++ * ++ * @param filename filename without cdrom:\\, i.e. cdrom:\\tonyhax.exe needs to be tonyhax.exe:1 ++ * ++ * Table A, call 0x9C. ++ */ ++uint32_t CdGetLbn(const char * filename); ++ + /* + * B-FUNCTIONS + */ +@@ -418,6 +420,15 @@ int32_t FileRead(int32_t fd, void * dst, uint32_t length); + */ + void FileClose(int32_t fd); + ++/** ++ * Formats the specified device. ++ * ++ * Table B, call 0x41. ++ * ++ * @param devname memory card device (bu00: or bu10:) ++ */ ++uint32_t FormatDevice(char * devicename); ++ + /** + * Returns the error code for the last failed file operation. + * +diff --git a/tmp/og-tonyhax.O0E/loader/cdrom.c b/loader/cdrom.c +index dddc533..4f754db 100644 +--- a/tmp/og-tonyhax.O0E/loader/cdrom.c ++++ b/loader/cdrom.c +@@ -3,7 +3,8 @@ + #include "bios.h" + #include + +-volatile uint8_t * const CD_REGS = (volatile uint8_t *) 0x1F801800; ++// Changed from 0x1F801800 to correct memory region (thanks Nicholas Noble), see https://psx-spx.consoledev.net/memorymap/#write-queue ++volatile uint8_t * const CD_REGS = (volatile uint8_t *) 0xBF801800; + + inline void cd_set_page(uint8_t page) { + CD_REGS[0] = page; +@@ -43,6 +44,40 @@ void cd_command(uint_fast8_t cmd, const uint8_t * params, uint_fast8_t params_le + CD_REGS[1] = cmd; + } + ++void cd_command_race(uint_fast8_t cmd, const uint8_t * params, uint_fast8_t params_len) { ++ ++ // Wait for previous command to finish, if any ++ //while (CD_REGS[0] & 0x80); ++ ++ // Switch to page 0 ++ cd_set_page(0); ++ ++ // Clear read and write FIFOs ++ CD_REGS[3] = 0xC0; ++ ++ // Copy request ++ while (params_len != 0) { ++ CD_REGS[2] = *params; ++ params++; ++ params_len--; ++ } ++ ++ // Switch to page 1 ++ cd_set_page(1); ++ ++ // Disable interrupts as we'll poll ++ CD_REGS[2] = 0x00; ++ ++ // Acknowledge interrupts, if there were any ++ CD_REGS[3] = 0x07; ++ ++ // Switch to page 0 ++ cd_set_page(0); ++ ++ // Finally write command to start ++ CD_REGS[1] = cmd; ++} ++ + uint_fast8_t cd_wait_int(void) { + + // Wait for command to finish, if any +@@ -64,6 +99,28 @@ uint_fast8_t cd_wait_int(void) { + return interrupt; + } + ++uint_fast8_t cd_wait_int_race(void) { ++ ++ // Wait for command to finish, if any ++ //while (CD_REGS[0] & 0x80); ++ ++ // Switch to page 1 ++ cd_set_page(1); ++ ++ // Wait until an interrupt happens (int != 0) ++ uint_fast8_t interrupt; ++ do { ++ interrupt = CD_REGS[3] & 0x07; ++ } while (interrupt == 0); ++ ++ // Acknowledge it ++ CD_REGS[3] = 0x07; ++ ++ // Return it ++ return interrupt; ++} ++ ++ + uint_fast8_t cd_read_reply(uint8_t * reply_buffer) { + + // Switch to page 1 +@@ -81,6 +138,9 @@ uint_fast8_t cd_read_reply(uint8_t * reply_buffer) { + return len; + } + ++//we can't trust this due to a race condition. If the actual BIOS executes a CD command (affects the Tonyhax International ROM), or a game is in the middle of executing a CD command when Tonyhax International starts (affects GSHAX), and this is executed close enough in the Tonyhax International loader code itself, it will get stuck if done like below: ++ ++/* + bool cd_drive_init() { + cd_command(CD_CMD_INIT, NULL, 0); + +@@ -96,18 +156,22 @@ bool cd_drive_init() { + + return true; + } ++*/ + +-bool cd_drive_reset() { +- // Issue a reset ++void cd_drive_init() { ++ cd_command_race(CD_CMD_INIT, NULL, 0); ++ ++ cd_wait_int_race(); ++ cd_wait_int_race(); ++} ++ ++void cd_drive_reset() { ++ // Issue a reset (looses authentication and or unlock when completed, so do an unlock after this) + cd_command(CD_CMD_RESET, NULL, 0); + +- // Should succeed with 3 +- if (cd_wait_int() != 3) { +- return false; +- } ++ // Should succeed with 3 but doesn't sometimes so we can't check the return value ++ cd_wait_int(); + + // Need to wait for some cycles before it springs back to life +- for (int i = 0; i < 0x400000; i++); +- +- return true; ++ for (volatile int i = 0; i < 0x400000; i++); // unmodifiable compiler code when using -Os optimizations + } +diff --git a/tmp/og-tonyhax.O0E/loader/cdrom.h b/loader/cdrom.h +index 438e7cd..653b02c 100644 +--- a/tmp/og-tonyhax.O0E/loader/cdrom.h ++++ b/loader/cdrom.h +@@ -1,6 +1,6 @@ + + #pragma once +-#include ++#include + #include + + #define CD_CMD_GETSTAT 0x01 +@@ -8,6 +8,15 @@ + #define CD_CMD_TEST 0x19 + #define CD_CMD_RESET 0x1C + #define CD_TEST_REGION 0x22 ++#define CD_TEST_VERSION 0x20 ++#define CD_CMD_SET_SESSION 0x12 ++#define CD_SET_SESSION_1 0x01 ++#define CD_SET_SESSION_2 0x02 ++#define CD_CMD_STOP 0x08 ++#define CD_CMD_GETID 0x1A ++#define CD_CMD_SETMODE 0x0E ++#define CD_CMD_GETTN 0x13 ++#define CD_CMD_GETTD 0x14 + + /** + * Starts executing a CD command. +@@ -38,11 +47,9 @@ uint_fast8_t cd_read_reply(uint8_t * reply_buffer); + * + * @returns true if succeded, or false otherwise. + */ +-bool cd_drive_init(void); ++//bool cd_drive_init(void); ++// we can't trust that: ++void cd_drive_init(); + +-/** +- * Resets the drive. +- * +- * @returns true if succeded, or false otherwise. +- */ +-bool cd_drive_reset(void); ++// Resets the drive. ++void cd_drive_reset(); +diff --git a/tmp/og-tonyhax.O0E/loader/cfgparse.h b/loader/cfgparse.h +index 00e9464..7935129 100644 +--- a/tmp/og-tonyhax.O0E/loader/cfgparse.h ++++ b/loader/cfgparse.h +@@ -1,6 +1,6 @@ + + #pragma once +-#include ++#include + #include + + bool config_get_hex(const char * config, const char * wanted, uint32_t * value); +diff --git a/tmp/og-tonyhax.O0E/loader/crc.h b/loader/crc.h +index cf3305b..4e1e512 100644 +--- a/tmp/og-tonyhax.O0E/loader/crc.h ++++ b/loader/crc.h +@@ -1,6 +1,6 @@ + + #pragma once + +-#include ++#include + + uint32_t crc32(const void * data, uint32_t len); +diff --git a/tmp/og-tonyhax.O0E/loader/debugscreen.c b/loader/debugscreen.c +index cb8c32e..0accfe8 100644 +--- a/tmp/og-tonyhax.O0E/loader/debugscreen.c ++++ b/loader/debugscreen.c +@@ -113,7 +113,7 @@ void debug_init() { + gpu_flush_cache(); + + // Draw border +- debug_text_at(TH_MARGIN, 40, "tonyhax " STRINGIFY(TONYHAX_VERSION)); ++ debug_text_at(TH_MARGIN, 40, "tonyhax international " STRINGIFY(TONYHAX_VERSION)); + struct gpu_solid_rect band = { + .pos = { + .x = 0, +@@ -210,8 +210,33 @@ void debug_write(const char * str, ...) { + + // Flush old textures + gpu_flush_cache(); ++ bool do_repeat = false; + +- if (strcmp(last_printed_line, formatted) != 0) { ++ if((strcmp(last_printed_line, formatted) != 0) && (dont_scroll) && (!do_repeat)) { ++ do_repeat = true; // display text repeated after inital change ++ // Line's text is different, so scroll up ++ gpu_size_t line_size = { ++ .width = SCREEN_WIDTH - LOG_MARGIN, ++ .height = LOG_LINE_HEIGHT, ++ }; ++ for (int line = 1; line < LOG_LINES; line++) { ++ gpu_point_t source_line = { ++ .x = LOG_MARGIN, ++ .y = LOG_START_Y + LOG_LINE_HEIGHT * line ++ }; ++ gpu_point_t dest_line = { ++ .x = LOG_MARGIN, ++ .y = LOG_START_Y + LOG_LINE_HEIGHT * line ++ }; ++ gpu_copy_rectangle(&source_line, &dest_line, &line_size); ++ } ++ ++ // Copy to last printed buffer and reset counter ++ strcpy(last_printed_line, formatted); ++ last_printed_count = 1; ++ ++ to_print = formatted; ++ } else if ((strcmp(last_printed_line, formatted) != 0) && (!do_repeat)) { + // Line's text is different, so scroll up + gpu_size_t line_size = { + .width = SCREEN_WIDTH - LOG_MARGIN, +@@ -234,12 +259,16 @@ void debug_write(const char * str, ...) { + last_printed_count = 1; + + to_print = formatted; ++ + } else { + last_printed_count++; + +- // Same line, so print with a repeat counter +- mini_sprintf(formatted_repeated, "%s (x%d)", last_printed_line, last_printed_count); +- ++ // Same line, so print with a repeat counter unless we are waiting for controller input. ++ if(!controller_input) { ++ mini_sprintf(formatted_repeated, "%s (x%d)", last_printed_line, last_printed_count); ++ } else { ++ mini_sprintf(formatted_repeated, "%s", last_printed_line); ++ } + to_print = formatted_repeated; + } + +diff --git a/tmp/og-tonyhax.O0E/loader/debugscreen.h b/loader/debugscreen.h +index 6ae71a5..1647381 100644 +--- a/tmp/og-tonyhax.O0E/loader/debugscreen.h ++++ b/loader/debugscreen.h +@@ -1,6 +1,6 @@ + + #pragma once +-#include ++#include + #include + + void debug_init(); +@@ -10,3 +10,5 @@ void debug_write(const char * str, ...); + void debug_text_at(uint_fast16_t x, uint_fast16_t y, const char * str); + + void debug_switch_standard(bool pal); ++ ++extern bool controller_input, dont_scroll; +\ No newline at end of file +diff --git a/loader/gameid-psx-exe.c b/loader/gameid-psx-exe.c +new file mode 100644 +index 0000000..e249dfb +--- /dev/null ++++ b/loader/gameid-psx-exe.c +@@ -0,0 +1,651 @@ ++#include "str.h" ++#include ++ ++bool is_scps = false; ++ ++const char * get_psx_exe_gameid(const unsigned char volume_creation_timestamp[17]) ++ { ++ /* ++ Special handling for PSX.EXE games with memcardpro. Currently this library support games that meet the below crtera: ++ * An officially licensed discs with a PSX.EXE bootfile (or some variant of it, i.e. psx.exe). Betas, Alphas, or unreleased games usually don't have a common sense product code/serial to send to the memcardpro and are currently not implemented. ++ * The game Has features that actively utilize a memory card. If there is no saving functionality in the game, then why waste executable space for it? ++ ++ Japanese PSX games from launch day (12/3/1994) to 7/12/1995 (the date of the below memo found in the Sony BBS archive: http://psx.arthus.net/sdk/Psy-Q/DOCS/BBS/scea_bbs.pdf) did not consistently follow the bootfile name format: ++ ====================================================================== ++ 7/12/95 10:43 AM ++ URGENT!! CD mastering information ++ Thomas Boyd ++ News ++ Late Late Breaking News from Japan: ---------------------------------------------------- Do not name your ++ executable PSX.EXE Name your executable after the following convention: ++ Use your product code (AAAA-XXXXX) and turn it into a file name by inserting a period after ++ the eighth character. ++ Example: AAAA-XXXXX = SLUS-12345 ++ Product code: SLUS-12345 Executable name on CD: SLUS-123.45;1 ++ To run the main file, build SYSTEM.CNF and put it in your root directory. SYSTEM.CNF should ++ look like this: ++ BOOT=cdrom:\AAAA-XXX.XX;1 (in the example this TCB=4 would ++ be SLUS-123.45;1) EVENT=10 STACK=801fff00 ++ ++ ====================================================================== ++ ++ Some Japanese PSX games were released not following the bootfile name format even after the memo. It appears to myself that by October 1995, it got a lot more consistant with following the bootfile name format. ++ ++ All PSX games have an serial product code, even the ones that don't make the product code the bootfile name. This is what needs to be sent as the game id to the memcardpro for games that don't follow the bootfile name format. ++ ++ In order to find such games, I used the redump,org search functionality to search for the "PSX.EXE" and "PSXEXE" comments on the redump pages of all PlayStation games. This doesn't get you all such games as leaving a comment on a redump upload mentioning the PSX.EXE boot file are left inconstantly by different up-loaders. There were also some false positives with these searches: ++ ++ * http://redump.org/discs/quicksearch/PSX.EXE/comments/only ++ * http://redump.org/discs/quicksearch/PSXEXE/comments/only ++ ++ ++ In order to find the remaining games, I decided to search through all disc images from SLPS_00001 to SLPS_00152. While the product serial codes are not perfectly sequential in regards to release date, by time you get to SLPS_00152 your in at least late 1995 for most release dates of games. At that point most games are following the correct bootfile format. ++ ++ ====================================================================== ++ Implementation Notes: ++ ++ 1) Read Sector 16. From NO$PSX SPX (https://problemkaputt.de/psx-spx.htm#cdromisovolumedescriptors), sector 16 contains: ++ ++ ====================================================================== ++ Primary Volume Descriptor (sector 16 on PSX disks) ++ ++ 000h 1 Volume Descriptor Type (01h=Primary Volume Descriptor) ++ 001h 5 Standard Identifier ("CD001") ++ 006h 1 Volume Descriptor Version (01h=Standard) ++ 007h 1 Reserved (00h) ++ 008h 32 System Identifier (a-characters) ("PLAYSTATION") ++ 028h 32 Volume Identifier (d-characters) (max 8 chars for PSX?) ++ 048h 8 Reserved (00h) ++ 050h 8 Volume Space Size (2x32bit, number of logical blocks) ++ 058h 32 Reserved (00h) ++ 078h 4 Volume Set Size (2x16bit) (usually 0001h) ++ 07Ch 4 Volume Sequence Number (2x16bit) (usually 0001h) ++ 080h 4 Logical Block Size in Bytes (2x16bit) (usually 0800h) (1 sector) ++ 084h 8 Path Table Size in Bytes (2x32bit) (max 800h for PSX) ++ 08Ch 4 Path Table 1 Block Number (32bit little-endian) ++ 090h 4 Path Table 2 Block Number (32bit little-endian) (or 0=None) ++ 094h 4 Path Table 3 Block Number (32bit big-endian) ++ 098h 4 Path Table 4 Block Number (32bit big-endian) (or 0=None) ++ 09Ch 34 Root Directory Record (see next chapter) ++ 0BEh 128 Volume Set Identifier (d-characters) (usually empty) ++ 13Eh 128 Publisher Identifier (a-characters) (company name) ++ 1BEh 128 Data Preparer Identifier (a-characters) (empty or other) ++ 23Eh 128 Application Identifier (a-characters) ("PLAYSTATION") ++ 2BEh 37 Copyright Filename ("FILENAME.EXT;VER") (empty or text) ++ 2E3h 37 Abstract Filename ("FILENAME.EXT;VER") (empty) ++ 308h 37 Bibliographic Filename ("FILENAME.EXT;VER") (empty) ++ 32Dh 17 Volume Creation Timestamp ("YYYYMMDDHHMMSSFF",timezone) ++ 33Eh 17 Volume Modification Timestamp ("0000000000000000",00h) ++ 34Fh 17 Volume Expiration Timestamp ("0000000000000000",00h) ++ 360h 17 Volume Effective Timestamp ("0000000000000000",00h) ++ 371h 1 File Structure Version (01h=Standard) ++ 372h 1 Reserved for future (00h-filled) ++ 373h 141 Application Use Area (00h-filled for PSX and VCD) ++ 400h 8 CD-XA Identifying Signature ("CD-XA001" for PSX and VCD) ++ 408h 2 CD-XA Flags (unknown purpose) (00h-filled for PSX and VCD) ++ 40Ah 8 CD-XA Startup Directory (00h-filled for PSX and VCD) ++ 412h 8 CD-XA Reserved (00h-filled for PSX and VCD) ++ 41Ah 345 Application Use Area (00h-filled for PSX and VCD) ++ 573h 653 Reserved for future (00h-filled) ++ ====================================================================== ++ Volume Descriptor Timestamps ++ The various timestamps occupy 17 bytes each, in form of ++ ++ "YYYYMMDDHHMMSSFF",timezone ++ "0000000000000000",00h ;empty timestamp ++ ++ The first 16 bytes are ASCII Date and Time digits (Year, Month, Day, Hour, Minute, Second, and 1/100 Seconds. The last byte is Offset from Greenwich Mean Time in number of 15-minute steps from -48 (West) to +52 (East); or actually: to +56 when recursing Kiribati's new timezone. ++ Note: PSX games manufactured in year 2000 were accidentally marked to be created in year 0000. ++ ====================================================================== ++ ++ 2) We use the Volume Creation Timestamp at offset 0x32D to identify what game we have. So far there have been no conflicts between games, this value is unique enough. ++ ++ 3) We send back the serial as SLPS by default. In order to signify it is SCPS we set a boolean to true. ++ ++ ====================================================================== ++ ++ 4) You need to rebuild the full serial number (this is because we only send back unique parts of the string that will be sent as the final GameID to save executable space). See 'secondary.c' for the implementation of this. ++ */ ++ ++ // Ordered by serial number. ++ ++ // Begin SLPS. ++ ++ if(strcmp( (char *)volume_creation_timestamp, "1994111009000000") == 0) { // Ridge Racer (Japan) - http://redump.org/disc/2679/. ++ return "000.01"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/R/SLPS-00001.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994110702000000") == 0) { // Gokujou Parodius Da! Deluxe Pack (Japan) - http://redump.org/disc/5337/. ++ return "000.02"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00002.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994102615231700") == 0) { // Tama: Adventurous Ball in Giddy Labyrinth (Japan) - http://redump.org/disc/6980/. ++ return "000.03"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00003.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1994110218594700") == 0) || // A Ressha de Ikou 4: Evolution (Japan) (Rev 0) - http://redump.org/disc/21858/. ++ (strcmp( (char *)volume_creation_timestamp, "1995030218052000") == 0) // A Ressha de Ikou 4: Evolution (Japan) (Rev 1) - http://redump.org/disc/21858/. ++ ) { ++ return "000.04"; // Serial from CD case. Uses 15 MC blocks - https://psxdatacenter.com/games/J/A/SLPS-00004.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1994110722360400") == 0) || // Mahjong Station Mazin (Japan) (Rev 0) - http://redump.org/disc/63533/. ++ (strcmp( (char *)volume_creation_timestamp, "1994120610494900") == 0) // Mahjong Station Mazin (Japan) (Rev 1) - http://redump.org/disc/10881/. ++ ) { ++ return "000.05"; // Serial from CD case. Uses 1 MC blocks - https://psxdatacenter.com/games/J/M/SLPS-00005.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994110407000000") == 0) { // Nekketsu Oyako (Japan) - http://redump.org/disc/10088/. ++ return "000.06"; // Serial from CD case. Uses 1 MC block -https://psxdatacenter.com/games/J/N/SLPS-00006.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994111419300000") == 0) { // Geom Cube (Japan) - http://redump.org/disc/14660/. ++ return "000.07"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00007.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994121808190700") == 0) { // Metal Jacket (Japan) - http://redump.org/disc/5927/. ++ return "000.08"; // Serial from CD case. Uses 2 MC blocks - https://psxdatacenter.com/games/J/M/SLPS-00008.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994121917000000") == 0) { // Cosmic Race (Japan) - http://redump.org/disc/16058/. ++ return "000.09"; // Serial from CD case. Uses 7 MC blocks - https://psxdatacenter.com/games/J/C/SLPS-00009.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995052918000000") == 0) { // Falcata: Astran Pardma no Monshou (Japan) - http://redump.org/disc/1682/. ++ return "000.10"; // Serial from CD case. Uses 4 MC blocks - https://psxdatacenter.com/games/J/F/SLPS-00010.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994110220020600") == 0) { // A Ressha de Ikou 4: Evolution (Japan) (Hatsubai Kinen Gentei Set) - http://redump.org/disc/70160/. ++ return "000.11"; // Serial from CD case. Uses 15 MC blocks - https://psxdatacenter.com/games/J/A/SLPS-00011.html. ++ } else if ++ ++ // SLPS_00012 (https://psxdatacenter.com/games/J/S/SLPS-00012.html) bootfile is 'START.EXE' with a SYSTEM.CNF. This is the discs Space Griffon VF-9 (Japan) (Rev 0) - http://redump.org/disc/5914/ and Space Griffon VF-9 (Japan) (Rev 1) - http://redump.org/disc/45998/. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994121518000000") == 0) { // Raiden Project (Japan) - http://redump.org/disc/3774/. ++ return "000.13"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/R/SLPS-00013.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994103000000000") == 0) { // Mahjong Gokuu Tenjiku (Japan) - http://redump.org/disc/17392/. ++ return "000.14"; // Serial from CD case. Uses 4 MC blocks - https://psxdatacenter.com/games/J/M/SLPS-00014.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994101813262400") == 0) { // TwinBee Taisen Puzzle-dama (Japan) - http://redump.org/disc/22905/. ++ return "000.15"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00015.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1994112617300000") == 0) || // Jikkyou Powerful Pro Yakyuu '95 (Japan) (Rev 0) - http://redump.org/disc/9552/. ++ (strcmp( (char *)volume_creation_timestamp, "1994121517300000") == 0) // // Jikkyou Powerful Pro Yakyuu '95 (Japan) (Rev 1) - http://redump.org/disc/27931/. ++ ) { ++ return "000.16"; // Serial from CD case. 3 MC blocks - https://psxdatacenter.com/games/J/J/SLPS-00016.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994111013000000") == 0) { // King's Field (Japan) - http://redump.org/disc/7072/. ++ return "000.17"; // Serial from CD case. Uses 5 MC blocks - https://psxdatacenter.com/games/J/K/SLPS-00017.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994111522183200") == 0) { // Twin Goddesses (Japan) - http://redump.org/disc/7885/. ++ return "000.18"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00018.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994112918000000") == 0) { // Kakinoki Shougi (Japan) - http://redump.org/disc/22869/. ++ return "000.19"; // Serial from CD case. Uses 4 MC blocks - https://psxdatacenter.com/games/J/K/SLPS-00019.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994111721302100") == 0) { // Houma Hunter Lime: Special Collection Vol. 1 (Japan) - http://redump.org/disc/18606/. ++ return "000.20"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/H/SLPS-00020.html ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994100617242100") == 0) { // Kikuni Masahiko Jirushi: Warau Fukei-san Pachi-Slot Hunter (Japan) - http://redump.org/disc/33816/. ++ return "000.21"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00021.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995030215000000") == 0) { //Starblade Alpha (Japan) - http://redump.org/disc/4664/. ++ return "000.22"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/S/SLPS-00022.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994122718351900") == 0) { //CyberSled (Japan) - http://redump.org/disc/7879/. ++ return "000.23"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/C/SLPS-00023.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994092920284600") == 0) { // Myst (Japan) (Rev 0) - http://redump.org/disc/4786/ / Myst (Japan) (Rev 1) - http://redump.org/disc/33887/ / Myst (Japan) (Rev 2) - http://redump.org/disc/1488/. ++ return "000.24"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/M/SLPS-00024.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1994113012000000") == 0) || // Toushinden (Japan) (Rev 0) - http://redump.org/disc/1560/. ++ (strcmp( (char *)volume_creation_timestamp, "1995012512000000") == 0) // Toushinden (Japan) (Rev 1) - http://redump.org/disc/23826/. ++ ) { // We don't care about revision differences! ++ return "000.25"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/B/SLPS-00025.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995041921063500") == 0) { // Rayman (Japan) - http://redump.org/disc/33719/. ++ return "000.26"; // CD case serial. Uses 3 MC blocks - https://psxdatacenter.com/games/J/R/SLPS-00026.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994121500000000") == 0) { // Kileak, The Blood (Japan) - http://redump.org/disc/14371/. ++ return "000.27"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00027.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1994121017582300") == 0) { // Jigsaw World (Japan) - http://redump.org/disc/14455/. ++ return "000.28"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/J/SLPS-00028.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995022623000000") == 0) { // Idol Janshi Suchie-Pai Limited (Japan) - http://redump.org/disc/33789/. ++ return "000.29"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/I/SLPS-00029.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995050116000000") == 0) || // Game no Tatsujin (Japan) (Rev 0) -http://redump.org/disc/36035/. ++ (strcmp( (char *)volume_creation_timestamp, "1995060613000000") == 0) // Game no Tatsujin (Japan) (Rev 1) - http://redump.org/disc/37866/. ++ ) { ++ return "000.30"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00030.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995021802000000") == 0) { // Kyuutenkai (Japan) - http://redump.org/disc/37548/. ++ return "000.31"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00031.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995021615022900") == 0) { // Uchuu Seibutsu Flopon-kun P! (Japan) - http://redump.org/disc/18814/. ++ return "000.32"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/U/SLPS-00032.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995100209000000") == 0) || // Boxer's Road (Japan) (Rev 1) - http://redump.org/disc/6537/. ++ (strcmp( (char *)volume_creation_timestamp, "1995080809000000") == 0) // Boxer's Road (Japan) (Rev 0) - http://redump.org/disc/2765/. ++ ) { ++ return "000.33"; // Serial from Rev 0 CD case. Rev 1 serial is SLPS-91007. Uses 7 MC blocks - https://psxdatacenter.com/games/J/B/SLPS-00033.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995071821394900") == 0) { // Zeitgeist (Japan) - http://redump.org/disc/16333/. ++ return "000.34"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/Z/SLPS-00034.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995042506300000") == 0) { // Mobile Suit Gundam (Japan) - http://redump.org/disc/3080/. ++ return "000.35"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/M/SLPS-00035.html. ++ } else if ++ ++ // SLPS_000.36 has a proper bootfile: http://redump.org/disc/6599/ ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995011411551700") == 0) { // Pachio-kun: Pachinko Land Daibouken (Japan) - http://redump.org/disc/36504/. ++ return "000.37"; // CD case serial. ++ //debug_write("Pachio-kun: Pachinko Land Daibouken (Japan)"); // 2 MC block - https://psxdatacenter.com/games/J/P/SLPS-00037.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995041311392800") == 0) { // Nichibutsu Mahjong: Joshikou Meijinsen (Japan) - http://redump.org/disc/35101/. ++ return "000.38"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/N/SLPS-00038.html. ++ } else if ++ ++ // SLPS_00039 Metamor Panic: Doki Doki Youma Busters!! (Japan) - http://redump.org/disc/34086/ has a proper bootfile name 'SLPS_000.39'. ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995031205000000") == 0) || // Tekken (Japan) (Rev 0) - http://redump.org/disc/671/. ++ (strcmp( (char *)volume_creation_timestamp, "1995061612000000") == 0) // Tekken (Japan) (Rev 1) - http://redump.org/disc/1807/. ++ ) { // We don't care about revision differences! ++ return "000.40"; // Using CD case serial. ++ //debug_write("Tekken (Japan) (Rev 0) / (Rev 1)"); // 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00040.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995040509000000") == 0) { // Gussun Oyoyo (Japan) - http://redump.org/disc/11336/. ++ return "000.41"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00041.html. ++ } else if ++ ++ // SLPS_00042 DOESN't EXIST??!! - https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995052612000000") == 0) { // Mahjong Ganryuu-jima (Japan) - http://redump.org/disc/33772/ ++ return "000.43"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/M/SLPS-00043.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995042500000000") == 0) { // Hebereke Station Popoitto (Japan) - http://redump.org/disc/36164/ ++ return "000.44"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/H/SLPS-00044.html. ++ } else if ++ ++ // SLPS_00045 DOESN't EXIST??!! - https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt. ++ ++ // SLPS_00046 Universal Virtua Pachi-Slot: Hisshou Kouryakuhou (Japan) - http://redump.org/disc/36344/ has a proper bootfile 'PACH.EXE'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995033100003000") == 0) { // Missland (Japan) - http://redump.org/disc/10869/. ++ return "000.47"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/M/SLPS-00047.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995041400000000") == 0) { // Gokuu Densetsu: Magic Beast Warriors (Japan) - http://redump.org/disc/24258/. ++ return "000.48"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00048.html. ++ } else if ++ ++ // SLPS_00049 DOESN't EXIST??!! - https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995050413421800") == 0) { // Night Striker (Japan) - http://redump.org/disc/10931/. ++ return "000.50"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/N/SLPS-00050.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995040509595900") == 0) { // Entertainment Jansou: That's Pon! (Japan) - http://redump.org/disc/34808/. ++ return "000.51"; // Serial from CD case. Uses 2 MC blocks - https://psxdatacenter.com/games/J/T/SLPS-00051.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995030103150000") == 0) { //Kanazawa Shougi '95 (Japan) - http://redump.org/disc/34246/. ++ return "000.52"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00052.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995100409235300") == 0) { //Thoroughbred Breeder II Plus (Japan) - http://redump.org/disc/33282/. ++ return "000.53"; // Serial from CD case. Uses 5 MC block - https://psxdatacenter.com/games/J/T/SLPS-00053.html. ++ } else if ++ ++ // SLPS_00054 Yaoi Jun'ichi Gokuhi Project: UFO o Oe!! (Japan) - http://redump.org/disc/28631/ has proper bootfile 'SLPS_000.54'. ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995060504013600") == 0) || // Cyberwar (Japan) (Disc 1) - http://redump.org/disc/30637/. ++ (strcmp( (char *)volume_creation_timestamp, "1995060319142200") == 0) || // Cyberwar (Japan) (Disc 2) - http://redump.org/disc/30638/. ++ (strcmp( (char *)volume_creation_timestamp, "1995060402110800") == 0) // Cyberwar (Japan) (Disc 3) - http://redump.org/disc/30639/. ++ ) { ++ return "000.55"; // Using Disc 1 CD case serial: http://redump.org/disc/30637/. All CDs assigned to same memory card. SLPS_00056 is Disc 2, and SLPS_00057 is Disc 3. Uses 1 MC block - https://psxdatacenter.com/games/J/C/SLPS-00055.html. ++ } else if ++ ++ // SLPS_0058 Ide Yousuke no Mahjong Kazoku (Japan) (Rev 0) - http://redump.org/disc/34169/ and Ide Yousuke no Mahjong Kazoku (Japan) (Rev 1) - http://redump.org/disc/33562/ have a proper bootfile 'SLPS_000.58'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995081612000000") == 0) { // Taikyoku Shougi: Kiwame (Japan) - http://redump.org/disc/35288/. ++ return "000.59"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00059.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995051201000000") == 0) { // Aquanaut no Kyuujitsu (Japan) - http://redump.org/disc/16984/. ++ return "000.60"; // Serial from CD case. Uses 4 MC block - https://psxdatacenter.com/games/J/A/SLPS-00060.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995051700000000") == 0) { // Ace Combat (Japan) - http://redump.org/disc/1691/. ++ return "000.61"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/A/SLPS-00061.html. ++ } else if ++ ++ // SLPS_00062 Hyper Formation Soccer (Japan) - http://redump.org/disc/2602/ has a proper bootfile 'SLPS_00062'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995051002471900") == 0) { // Keiba Saishou no Housoku '95 (Japan) - http://redump.org/disc/22944/ ++ return "000.63"; // Serial from CD case. Uses 15 MC blocks - https://psxdatacenter.com/games/J/K/SLPS-00063.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995083112000000") == 0) || // Tokimeki Memorial: Forever with You (Japan) (Rev 1) - http://redump.org/disc/6789/ / Tokimeki Memorial: Forever with You (Japan) (Shokai Genteiban) (Rev 1) - http://redump.org/disc/6788/. ++ (strcmp( (char *)volume_creation_timestamp, "1995111700000000") == 0) // Tokimeki Memorial: Forever with You (Japan) (Rev 2) - http://redump.org/disc/33338/. ++ // Tokimeki Memorial: Forever with You (Japan) (PlayStation The Best) - http://redump.org/disc/8876/ has it's own different serial and proper bootfile name, so already works with mcpro! ++ ) { ++ return "000.64"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00064.html ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1996033100000000") == 0) { // Tokimeki Memorial: Forever with You (Japan) (Rev 4) - http://redump.org/disc/6764/. ++ return "000.65"; // Using CD case serial. Why it is different from the above? No clue. Not a typo though. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00065.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995051816000000") == 0) { // Kururin Pa! (Japan) - http://redump.org/disc/33413/. ++ return "000.66"; // Serial from CD case. ++ //debug_write("Kururin Pa! (Japan)"); // 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00066.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995061418000000") == 0) { // Jikkyou Powerful Pro Yakyuu '95: Kaimakuban (Japan) - http://redump.org/disc/14367/. ++ return "000.67"; // Serial from CD case. Uses 3 MC blocks - https://psxdatacenter.com/games/J/J/SLPS-00067.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995061911303400") == 0) || // J.League Jikkyou Winning Eleven (Japan) (Rev 0) - http://redump.org/disc/6740/. ++ (strcmp( (char *)volume_creation_timestamp, "1995072800300000") == 0) // // J.League Jikkyou Winning Eleven (Japan) (Rev 1) - http://redump.org/disc/2848/. ++ ) { ++ return "000.68"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/J/SLPS-00068.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995061806364400") == 0) { // King's Field II (Japan) - http://redump.org/disc/5892/. ++ return "000.69"; // Serial from CD case. Uses 2-15 MC blocks - https://psxdatacenter.com/games/J/K/SLPS-00069.html. ++ } else if ++ ++ // King's Field II (Japan) (PlayStation the Best) (Rev 0) - http://redump.org/disc/7073/ and King's Field II (Japan) (PlayStation the Best) (Rev 1) - http://redump.org/disc/13338/ have a proper bootfile 'SLPS_910.03'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995062922000000") == 0) { //Street Fighter: Real Battle on Film (Japan) - http://redump.org/disc/26158/ ++ return "000.70"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/S/SLPS-00070.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995040719355400") == 0) { // 3x3 Eyes: Kyuusei Koushu (Disc 1) (Japan) - http://redump.org/disc/7881/ / 3x3 Eyes: Kyuusei Koushu (Disc 2) (Japan) http://redump.org/disc/7880/. ++ return "000.71"; // Using disc 1 CD case serial: http://redump.org/disc/7881/. Disc 2 serial is SLPS_00072. All CDs assigned to same memory card. ++ //debug_write("3x3 Eyes: Kyuusei Koushu (Disc 1) (Japan) / (Disc 2)"); // 1 MC block - https://psxdatacenter.com/games/J/0-9/SLPS-00071.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995061806364400") == 0) { // Dragon Ball Z: Ultimate Battle 22 (Japan) - http://redump.org/disc/10992/. ++ return "000.73"; // Serial from CD case. ++ //debug_write("Dragon Ball Z: Ultimate Battle 22 (Japan)"); // 1 MC block - https://psxdatacenter.com/games/J/D/SLPS-00073.html. ++ } else if ++ ++ // SLPS_00074 Sparrow Garden: Namco Mahjong (Japan) - http://redump.org/disc/35289/ has a proper bootfile 'SLPS_000.74" ++ ++ // SLPS_00075 World Stadium EX (Japan) (Rev 0) - http://redump.org/disc/10903/ and SLPS_00075 World Stadium EX (Japan) (Rev 1) - http://redump.org/disc/10904/ have a proper bootfile 'SLPS_000.75". ++ ++ // SLPS_00076 doesn't exist - https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995051015300000") == 0) { // Douga de Puzzle da! Puppukupuu (Japan) - http://redump.org/disc/11935/ ++ return "000.77"; // Serial from CD case. Uses 13 MC blocks - https://psxdatacenter.com/games/J/D/SLPS-00077.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995070302000000") == 0) { // Gakkou no Kowai Uwasa: Hanako-san ga Kita!! (Japan) - http://redump.org/disc/11935/ ++ return "000.78"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00078.html. ++ } else if ++ ++ // SLPS_00079E J.League Soccer: Prime Goal EX (Japan) (Lawson Station) - http://redump.org/disc/67854/ and SLPS_00079 J.League Soccer: Prime Goal EX (Japan) - http://redump.org/disc/67854/ have a proper bootfile 'SLPS_000.79". ++ ++ // SLPS_00080 Street Fighter II Movie (Disc 1) (Japan) - http://redump.org/disc/18429/ has a proper bootfile 'SLPS_000.80". ++ ++ // SLPS_00081 Street Fighter II Movie (Disc 2) (Japan) - http://redump.org/disc/18430/ has a proper bootfile 'SLPS_000.81". ++ ++ // SLPS_00082 Shichuu Suimei Pitagraph (Japan) - http://redump.org/disc/33850/ has a proper bootfile 'SLPS_000.82". ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995070523450000") == 0) { // Zero Divide (Japan) - http://redump.org/disc/99925/. ++ return "000.83"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/Z/SLPS-00083.html. ++ } else if ++ ++ // SLPS_00084 Shin Nihon Pro Wrestling: Toukon Retsuden (Japan) - http://redump.org/disc/12635/ has a proper bootfile 'SLPS_000.84". ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995072522004900") == 0) { // Houma Hunter Lime: Special Collection Vol. 2 (Japan) - http://redump.org/disc/18607/. ++ return "000.85"; // Serial from CD case. ++ //debug_write("Houma Hunter Lime: Special Collection Vol. 2 (Japan)"); // 1 MC block - https://psxdatacenter.com/games/J/I/SLPS-00029.html. ++ } else if ++ ++ // SLPS_00086 Kaitei Daisensou: In the Hunt (Japan) - http://redump.org/disc/9478/ has a proper bootfile 'SLPS_000.86". ++ ++ // SLPS_00087 The Perfect Golf (Japan) - http://redump.org/disc/11332/ has a proper bootfile 'SLPS_000.87". ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995070613170000") == 0) { // Ground Stroke: Advanced Tennis Game (Japan) - http://redump.org/disc/33778/ ++ return "000.88"; // Serial from CD case. ++ //debug_write("Ground Stroke: Advanced Tennis Game (Japan)"); // 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00088.html ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995082517551900") == 0) { // The Oni Taiji!!: Mezase! Nidaime Momotarou (Japan) - http://redump.org/disc/33948/ ++ return "000.89"; // CD case serial. ++ // debug_write("The Oni Taiji!!: Mezase! Nidaime Momotarou (Japan)"); // 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00089.html ++ } else if ++ ++ // The Oni Taiji!!: Mezase! Nidaime Momotarou (Japan) (Major Wave) - http://redump.org/disc/70837/ has a proper bootfile 'SLPM_869.89'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995082109402500") == 0) { // Eisei Meijin (Japan) (Rev 1) - http://redump.org/disc/37494/ ++ return "000.90"; // CD case serial. Uses 3 MC blocks - https://psxdatacenter.com/games/J/E/SLPS-00090.html. ++ } else if ++ ++ // Eisei Meijin (Japan) (Rev 0) has never been dumped because it may not exist: http://wiki.redump.org/index.php?title=Sony_PlayStation_-_Asia_Undumped_Discs#Undumped_Revisions. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995053117000000") == 0) { // Exector (Japan) - http://redump.org/disc/2814/. ++ return "000.91"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/E/SLPS-00091.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995081100000000") == 0) { // King of Bowling (Japan) - http://redump.org/disc/34727/. ++ return "000.92"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/K/SLPS-00092.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995071011035200") == 0) { // Oh-chan no Oekaki Logic (Japan) - http://redump.org/disc/7882/. ++ return "000.93"; // CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/O/SLPS-00093.html. ++ } else if ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995090510000000") == 0) || // Thunder Storm & Road Blaster (Disc 1) (Thunder Storm) (Japan) - http://redump.org/disc/6740/. ++ (strcmp( (char *)volume_creation_timestamp, "1995083123000000") == 0) // Thunder Storm & Road Blaster (Disc 2) (Road Blaster) (Japan) - http://redump.org/disc/8551/. ++ ) { ++ return "000.94"; // Serial from Disc 1 CD case. Disc 2 serial is SLPS_00095. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00094.html. ++ } else if ++ ++ // SLPS_00096 Game no Tetsujin: The Shanghai (Japan) - http://redump.org/disc/36470/ has a proper bootfile 'SLPS_000.96". ++ ++ // SLPS_00097 Gensou Suikoden (Japan) - http://redump.org/disc/7461/ has a proper bootfile 'SLPS_000.97". ++ ++ // SLPS_00098 Ray Tracers (Japan) - http://redump.org/disc/34232/ has a proper bootfile 'SLPS_000.98". ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995100601300000") == 0) { // Moero!! Pro Yakyuu '95: Double Header (Japan) - http://redump.org/disc/34818/. ++ return "000.99"; // Using CD case serial. Uses 6 MC blocks - https://psxdatacenter.com/games/J/M/SLPS-00099.html. ++ } else if ++ ++ // SLPS_00100 Detana Twinbee Yahoo! Deluxe Pack (Japan) - http://redump.org/disc/5336/ has a proper bootfile 'SLPS_001.00'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995081001450000") == 0) { // Universal-ki Kanzen Kaiseki: Pachi-Slot Simulator (Japan) - http://redump.org/disc/36304/. ++ return "001.01"; // Using CD case serial. Uses 2 MC block - https://psxdatacenter.com/games/J/U/SLPS-00101.html. ++ } else if ++ ++ // SLPS_00102 Twilight Syndrome: Tansaku-hen (Japan) - http://redump.org/disc/6840/ has a proper bootfile 'SLPS_001.02'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995080316000000") == 0) { // V-Tennis (Japan) - http://redump.org/disc/22684/. ++ return "001.03"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/V/SLPS-00103.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995081020000000") == 0) { // Gouketsuji Ichizoku 2: Chotto dake Saikyou Densetsu (Japan) - http://redump.org/disc/12680/. ++ return "001.04"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/G/SLPS-00104.html. ++ } else if ++ ++ // SLPS_00105 Total Eclipse Turbo (Japan) - http://redump.org/disc/27059/ has a proper bootfile 'SLPS_00105'. ++ ++ // SLPS_00106 Nobunaga no Yabou: Haouden (Japan) - http://redump.org/disc/8443/ has a proper bootfile 'SLPS_00106'. ++ ++ // SLPS_00107 Namco Museum Vol. 1 (Japan) - http://redump.org/disc/8443/ has a proper bootfile 'SLPS_00107'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995090722000000") == 0) { // Darkseed (Japan) - http://redump.org/disc/1640/. ++ return "001.08"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/D/SLPS-00108.html. ++ } else if ++ ++ // SLPS_00109 AI Shougi (Japan) - http://redump.org/disc/62297/ has a proper bootfile 'SLPS_001.09'. ++ ++ // SLPS_00110 Miracle World: Fushigi no Kuni no IQ Meiro (Japan) - http://redump.org/disc/36181/ has a proper bootfile 'SLPS_001.10'. ++ ++ // SLPS_00111 Night Head: The Labyrinth (Japan) - http://redump.org/disc/39266/ has a proper bootfile 'SLPS_00111'. ++ ++ // SLPS_00112 Hissatsu Pachinko Station (Japan) -http://redump.org/disc/42584/ has a proper bootfile 'SLPS_00112'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995090516062841") == 0) { // Sotsugyou II: Neo Generation (Japan) - http://redump.org/disc/7885/. ++ return "001.13"; // Using CD case serial. Uses 1 MC block - https://psxdatacenter.com/games/J/T/SLPS-00040.html. ++ } else if ++ ++ // SLPS_00114 Sangokushi IV (Japan) (Rev 0) - http://redump.org/disc/24772/ and Sangokushi IV (Japan) (Rev 1) - http://redump.org/disc/24772/have a proper bootfile 'SLPS_00114'. ++ ++ // SLPS_00115 Kuma no Pooh-tarou: Sora wa Pink da! Zen'in Shuugou!! (Japan) - http://redump.org/disc/62666/ has a proper bootfile 'SLPS_001.15'. ++ ++ // SLPS_00117 Emit Value Pack (Disc 1) (Vol. 1: Toki no Maigo) (Japan) - http://redump.org/disc/22411/ has a proper bootfile 'SLPS_001.17'. ++ ++ // SLPS_00118 Emit Value Pack (Disc 2) (Vol. 2: Inochigake no Tabi) (Japan) - http://redump.org/disc/22412/ has a proper bootfile 'SLPS_001.18'. ++ ++ // SLPS_00119 Emit Value Pack (Disc 3) (Vol. 3: Watashi ni Sayonara wo) (Japan) - http://redump.org/disc/22413/ has a proper bootfile 'SLPS_001.19'. ++ ++ // SLPS_00120 Creature Shock (Disc 1) (Japan) - http://redump.org/disc/38429/ has a proper bootfile 'slps_001.20'. ++ ++ // SLPS_00121 Creature Shock (Disc 2) (Japan) - http://redump.org/disc/38430/ has a proper bootfile 'slps_001.21'. ++ ++ // SLPS_00122 Off-World Interceptor Extreme (Japan) - http://redump.org/disc/15373/ has a proper bootfile 'SLPS_001.22'. ++ ++ // SLPS_00123 TIZ: Tokyo Insect Zoo (Japan) - http://redump.org/disc/7886/ has a proper bootfile 'SLPS_001.23'. ++ ++ // SLPS_00124 Sento Monogatari: Sono I (Japan) - http://redump.org/disc/39503/ has a proper bootfile 'SLPS_001.24'. ++ ++ // SLPS_00125 A Ressha de Ikou 4: Evolution Global (Japan) (Rev 0) - http://redump.org/disc/8493/ has a proper bootfile 'SLPS_001.25'. A Ressha de Ikou 4: Evolution Global (Japan) (Rev 1) - http://redump.org/disc/8444/ has a proper bootfile 'SLPS_910.19'. ++ ++ // SLPS_000126 is apparently FEDA - https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt but http://wiki.redump.org/index.php?title=Sony_PlayStation_-_Asia_Undumped_Discs argues it doesn't exist and or wasn't released. ++ ++ // SLPS_00127 Striker: World Cup Premiere Stage (Japan) - http://redump.org/disc/34589/ has a proper bootfile 'SLPS_001.27'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995082016003000") == 0) { // Makeruna! Makendou 2 (Japan) - http://redump.org/disc/37537/ ++ return "001.28"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/M/SLPS-00128.html ++ } else if ++ ++ // SLPS_0129 Break Thru! (Japan) - http://redump.org/disc/29931/ has a proper bootfile 'SLPS_001.29'. ++ ++ // SLPS_0130 Dokiou-ki (Japan) - http://redump.org/disc/3982/ has a proper bootfile 'SLPS_001.30'. ++ ++ // SLPS_0131 Two-Ten Kaku (Japan) - http://redump.org/disc/10238/ has a proper bootfile 'SLPS_001.31'. ++ ++ // SLPS_0132 Gionbana (Japan) - http://redump.org/disc/33539/ has a proper bootfile 'SLPS_001.32'. ++ ++ ( ++ (strcmp( (char *)volume_creation_timestamp, "1995102101350000") == 0) || // D no Shokutaku: Complete Graphics (Japan) (Disc 1) - http://redump.org/disc/763/. ++ (strcmp( (char *)volume_creation_timestamp, "1995102102521200") == 0) || // D no Shokutaku: Complete Graphics (Japan) (Disc 2) - http://redump.org/disc/764/. ++ (strcmp( (char *)volume_creation_timestamp, "1995102105003200") == 0) // D no Shokutaku: Complete Graphics (Japan) (Disc 3) - http://redump.org/disc/765/. ++ ) { ++ return "001.33"; // Using disc 1 CD case serial: http://redump.org/disc/763/. Disc 2 serial is SLPS_00134. Disc 3 serial is SLPS_0135 All CDs assigned to same memory card. Use 13 MC blocks - https://psxdatacenter.com/games/J/D/SLPS-00133.html. ++ } else if ++ ++ // SLPS_00136 Othello World II: Yume to Michi e no Chousen (Japan) - http://redump.org/disc/41764/ has a proper bootfile 'SLPS_001.36'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995100910002200") == 0) { // Keiba Saishou no Housoku '96 Vol. 1 (Japan) - http://redump.org/disc/22945/. ++ return "001.37"; // Serial from CD case. Uses 1 MC blocks- https://psxdatacenter.com/games/J/S/SLPS-00137.html. ++ } else if ++ ++ // SLPS_00138 Galaxy Fight: Universal Warriors (Japan) - http://redump.org/disc/21626/ has a proper bootfile 'SLPS_001.38'. ++ ++ // SLPS_00139 Shougi Joryuu Meijin'isen (Japan) - http://redump.org/disc/46378/ has a proper bootfile 'SLPS_001.39'. ++ ++ // SLPS_00140 The Snowman (Japan) - http://redump.org/disc/36568/has a proper bootfile 'SLPS_001.40'. ++ ++ // SLPS_00141 Alone in the Dark 2 (Japan) - http://redump.org/disc/13174/ has a proper bootfile 'SLPS_001.41'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995101801325900") == 0) { // Senryaku Shougi (Japan) - http://redump.org/disc/61170/. ++ return "001.42"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/S/SLPS-00142.html. ++ } else if ++ ++ // SLPS_00143 Reverthion (Japan) - http://redump.org/disc/7887/ has a proper bootfile 'SLPS_001.43'. ++ ++ // SLPS_00144 J. B. Harold: Blue Chicago Blues (Disc 1) (Japan) - http://redump.org/disc/18647/ has a proper bootfile 'SLPS_001.44'. ++ ++ // SLPS_00145 J. B. Harold: Blue Chicago Blues (Disc 2) (Japan) - http://redump.org/disc/18646/ has a proper bootfile 'SLPS_001.45'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995113010450000") == 0) { // Keiba Saishou no Housoku '96 Vol. 1 (Japan) - http://redump.org/disc/22945/. ++ return "001.46"; // Serial from CD case. Uses 15 MC blocks - https://psxdatacenter.com/games/J/K/SLPS-00146.html. ++ } else if ++ ++ // SLPS_00147 Floating Runner: 7-tsu no Suishou no Monogatari (Japan) - http://redump.org/disc/8582/ has a proper bootfile 'SLPS_001.47'. ++ ++ // SLPS_00148 The Firemen 2: Pete & Danny (Japan) - http://redump.org/disc/18950/ has a proper bootfile 'SLPS_001.48'. ++ ++ // SLPS_00149 Puppet Zoo Pilomy (Japan) - http://redump.org/disc/37618/ has a proper bootfile 'SLPS_001.49'. ++ ++ // SLPS_00150 Ridge Racer Revolution (Japan) (Rev 0) - http://redump.org/disc/2733/ and Ridge Racer Revolution (Japan) (Rev 1) - http://redump.org/disc/6209 have a proper bootfile 'SLPS_001.50'. ++ ++ // SLPS_00151 Carnage Heart (Japan) - http://redump.org/disc/1599/ has a proper bootfile 'SLPS_001.51'. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995092205430500") == 0) { // Yaku: Yuujou Dangi (Japan) - http://redump.org/disc/4668/ ++ return "001.52"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/Y/SLPS-00152.html. ++ } else if ++ ++ // TODO, check until at least SLPS_00173 for any more stragglers. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995121620000000") == 0) { // Alnam no Kiba: Juuzoku Juuni Shinto Densetsu (Japan) - http://redump.org/disc/11199/ ++ return "001.73"; // Serial from CD case. Uses 1 MC block - https://psxdatacenter.com/games/J/A/SLPS-00173.html. ++ } else if ++ ++ // SLPS_00719 The Great Battle VI (Japan) - http://redump.org/disc/37406/ has PSX.EXE. But it does not use memory cards (no save functionallity) https://psxdatacenter.com/games/J/T/SLPS-00719.html. ++ ++ // Begin SCPS. ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995030717020700") == 0) { // Victory Zone (Japan) - http://redump.org/disc/37010/. ++ is_scps = true; ++ return "100.02"; // Using CD case serial. Uses 2 MC blocks - https://psxdatacenter.com/games/J/V/SCPS-10002.html. ++ // TESTING NOTE: Latest DuckStation as of 9/11/2024 doesn't work with this game, but v0.1-6292 does just fine. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995032400000000") == 0) { // Jumping Flash! Aloha Danshaku Funky Daisakusen no Maki (Japan) - http://redump.org/disc/4051/. ++ is_scps = true; ++ return "100.07"; // Using CD case serial. Uses 1-3 MC blocks - https://psxdatacenter.com/games/J/J/SCPS-10007.html. ++ } else if ++ ++ (strcmp( (char *)volume_creation_timestamp, "1995052420065100") == 0) { // Arc the Lad (Japan) (Rev 0) - http://redump.org/disc/67966/ / Arc the Lad (Japan) (Rev 1) - http://redump.org/disc/1472/ ++ is_scps = true; ++ return "100.08"; // Serial from Rev 0 CD case. Rev 1 serial is SLPS_00095. Uses 1 MC block - https://psxdatacenter.com/games/J/A/SCPS-10008.html. ++ ++ } else { ++ return "0"; // If not known say its "0". ++ } ++ } +diff --git a/loader/gameid-psx-exe.h b/loader/gameid-psx-exe.h +new file mode 100644 +index 0000000..5bffaf6 +--- /dev/null ++++ b/loader/gameid-psx-exe.h +@@ -0,0 +1,3 @@ ++ ++const char * get_psx_exe_gameid(const unsigned char volume_creation_timestamp[17]); ++extern bool is_scps; +diff --git a/tmp/og-tonyhax.O0E/loader/generate-tonyhax-exe.sh b/tmp/og-tonyhax.O0E/loader/generate-tonyhax-exe.sh +deleted file mode 100644 +index 6bd43aa..0000000 +--- a/tmp/og-tonyhax.O0E/loader/generate-tonyhax-exe.sh ++++ /dev/null +@@ -1,49 +0,0 @@ +-#!/bin/bash +- +-set -euo pipefail +- +-if [ $# -ne 2 ]; then +- echo "Usage: $0 elf-file exe-file" +- exit 1 +-fi +- +-elf_file="$1" +-exe_file="$2" +- +-# Extract addresses +-ro_start=$(objdump -x "$elf_file" | grep __RO_START__ | cut -d ' ' -f 1) +- +-# Create temporary file for the binary +-bin_file=$(mktemp) +-mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data -j .crc "$elf_file" $bin_file +- +-# Round filesize to nearest 2048-byte block +-truncate --size=%2048 $bin_file +-bin_size=$(printf "%08X" $(stat -c %s $bin_file)) +- +-# Create executable now +-echo "Creating executable" +-echo -ne "PS-X EXE" >"$exe_file" +-head -c 8 /dev/zero >>"$exe_file" +-# Initial PC +-echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}" >>"$exe_file" +-head -c 4 /dev/zero >>"$exe_file" +-# Load address +-echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}" >>"$exe_file" +-# Load size +-echo -ne "\x${bin_size:6:2}\x${bin_size:4:2}\x${bin_size:2:2}\x${bin_size:0:2}" >>"$exe_file" +-head -c 16 /dev/zero >>"$exe_file" +-# Initial SP +-echo -ne "\x00\xFF\x1F\x80" >>"$exe_file" +-head -c 24 /dev/zero >>"$exe_file" +-# Magic string +-echo -n "Sony Computer Entertainment Inc. for Europe area" >>"$exe_file" +- +-# Pad to 2048 +-truncate --size=%2048 "$exe_file" +- +-# Insert binary +-cat $bin_file >> "$exe_file" +- +-# Cleanup +-rm $bin_file +diff --git a/tmp/og-tonyhax.O0E/loader/generate-tonyhax-mcs.sh b/loader/generate-tonyhax-mcs.sh +index 33116fa..2fb1ee5 100644 +--- a/tmp/og-tonyhax.O0E/loader/generate-tonyhax-mcs.sh ++++ b/loader/generate-tonyhax-mcs.sh +@@ -3,21 +3,29 @@ + set -euo pipefail + + if [ $# -ne 4 ]; then +- echo "Usage: $0 elf-file tpl-file mcs-file version" ++ echo "Usage: $0 raw-binary tpl-file mcs-file version" + exit 1 + fi + +-elf_file="$1" ++raw_binary="$1" + tpl_file="$2" + mcs_file="$3" + version="$4" + +-# Extract addresses +-ro_start=$(objdump -x "$elf_file" | grep __RO_START__ | cut -d ' ' -f 1) ++# Set addresses ++if [ "$2" = "tonyhax-tpl-ff9-16kb.mcs" ]; then ++ ro_start="801F0480" ++elif [ "$2" = "tonyhax-tpl-16kb.mcs" ]; then ++ ro_start="801F2300" ++else ++ echo "Error: Unknown what to specify ro_start for $2" ++ exit 1 ++fi ++echo "$ro_start" + + # Create temporary file for the binary + bin_file=$(mktemp) +-mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data -j .crc "$elf_file" $bin_file ++mv secondary.raw $bin_file + + # Round filesize to nearest 128-byte block + truncate --size=%128 $bin_file +@@ -25,11 +33,11 @@ load_len=$(printf "%08X" $(stat -c %s $bin_file)) + + # Create file + cp "$tpl_file" "$mcs_file" +-echo -n "tonyhax ${version}" | dd status=none conv=notrunc bs=1 seek=132 of="$mcs_file" ++echo -n "tonyhax-i ${version}" | dd status=none conv=notrunc bs=1 seek=132 of="$mcs_file" + dd status=none conv=notrunc bs=1 seek=384 if=$bin_file of="$mcs_file" + + # Insert address at 0xC0 and length at 0xC4, which is 0x40 and 0x44 inside the save file header +-echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}\x${load_len:6:2}\x${load_len:4:2}\x${load_len:2:2}\x${load_len:0:2}" | dd status=none conv=notrunc of=tonyhax.mcs bs=1 seek=192 ++echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}\x${load_len:6:2}\x${load_len:4:2}\x${load_len:2:2}\x${load_len:0:2}" | dd status=none conv=notrunc of="$mcs_file" bs=1 seek=192 + + # Cleanup + rm $bin_file +diff --git a/tmp/og-tonyhax.O0E/loader/gpu.h b/loader/gpu.h +index c7ac757..de5fa81 100644 +--- a/tmp/og-tonyhax.O0E/loader/gpu.h ++++ b/loader/gpu.h +@@ -1,7 +1,7 @@ + + #pragma once + #include +-#include ++#include + + #define GPU_DISPLAY_H256 0 + #define GPU_DISPLAY_H320 1 +diff --git a/tmp/og-tonyhax.O0E/loader/insert-tonyhax-crc.sh b/loader/insert-tonyhax-crc.sh +index ce49e5f..14d3582 100644 +--- a/tmp/og-tonyhax.O0E/loader/insert-tonyhax-crc.sh ++++ b/loader/insert-tonyhax-crc.sh +@@ -16,7 +16,7 @@ fi + elf_file="$1" + + # Calculate CRC +-crc=$(mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data "$elf_file" /dev/stdout | crc32) ++crc=$(mipsel-none-elf-objcopy -O binary -j .text -j .rodata -j .data "$elf_file" /dev/stdout | crc32) + echo "CRC32: 0x${crc}" + + # Create temporary file +@@ -24,7 +24,7 @@ tmpsection=$(mktemp) + echo -ne "\x${crc:6:2}\x${crc:4:2}\x${crc:2:2}\x${crc:0:2}" >$tmpsection + + # Insert it +-mips-linux-gnu-objcopy --update-section .crc=$tmpsection "$elf_file" ++mipsel-none-elf-objcopy --update-section .crc=$tmpsection "$elf_file" + + # Cleanup + rm $tmpsection +diff --git a/tmp/og-tonyhax.O0E/loader/integrity.c b/loader/integrity.c +index e7c4e3d..f3134f2 100644 +--- a/tmp/og-tonyhax.O0E/loader/integrity.c ++++ b/loader/integrity.c +@@ -1,7 +1,7 @@ + + #include "integrity.h" + #include "crc.h" +-#include ++#include + + // Loading address of tonyhax, provided by the secondary.ld linker script + extern uint8_t __RO_START__, __CRC_START__; +diff --git a/tmp/og-tonyhax.O0E/loader/io.h b/loader/io.h +index b7012b5..1164b7b 100644 +--- a/tmp/og-tonyhax.O0E/loader/io.h ++++ b/loader/io.h +@@ -1,6 +1,6 @@ + + #pragma once +-#include ++#include + + #define _REG32(x) (*((volatile uint32_t *) x)) + #define _REG16(x) (*((volatile uint16_t *) x)) +diff --git a/loader/memcardpro.S b/loader/memcardpro.S +new file mode 100644 +index 0000000..898295d +--- /dev/null ++++ b/loader/memcardpro.S +@@ -0,0 +1,579 @@ ++#======================================================================================= ++# mipsel-none-elf-as GNU Assembler (GNU Binutils) 2.38 ++# Copyright (C) 2022 Free Software Foundation, Inc. ++#--------------------------------------------------------------------------------------- ++# Copyright (C) 2021-2024 Cybdyn Systems. All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++#======================================================================================= ++ ++# no opcode reordering ++.set noreorder ++ ++# link this in the 'text' section ++.section .text ++ ++# function prototypes ++.global MemCardPro_Ping ++.global MemCardPro_SendGameID ++.global MemCardPro_PrevCH ++.global MemCardPro_NextCH ++.global MemCardPro_PrevDIR ++.global MemCardPro_NextDIR ++.global MemCardPro_Wait ++.global MemCardPro_Exchng ++.global MemcardPro_SimpleCommand ++.global MemcardPro_InterCommandDelay ++ ++.equ JOY_DELAY_COUNTER, 170 # 170 * 3 instructions = 510 cycles ++ ++ ++# arguments: ++# $a0 (port number) ++# $a1 (string length) ++# $a2 (string data (251 bytes maximum)) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = game ID command start (failed to send the game ID command) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = string length fail (failed to send the string length) ++# 5 = data transmission fail (failed to get the previous byte) ++MemCardPro_SendGameID: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ # Add 1 to the string length to include a trailing 0 ++ # without passing that burdern to the caller ++ # i.e. allow the developer to pass strlen(gameid) ++ # (MCPro still accepts the unterminated packet but this is cleaner) ++ addiu $a1, 1 ++ ++ lui $t0, IOBASE ++ ++ li $v0, 0x1003 # TX Enable, joypad port select ++ andi $a0, 1 ++ sll $a0, 13 ++ or $v0, $a0 # Select port 2 if $a0 is 1 ++ ++ sh $v0, JOY_CTRL($t0) # Set to Joypad control interface ++ ++ jal MemCardPro_Wait ++ li $a0, JOY_DELAY_COUNTER ++.Lread_empty_fifo_writeA: # Flush the RX FIFO just in case ++ lbu $v1, JOY_TXRX($t0) ++ lhu $v0, JOY_STAT($t0) ++ nop ++ andi $v0, 0x2 ++ bnez $v0, .Lread_empty_fifo_writeA ++ nop ++ ++ lhu $v1, JOY_CTRL($t0) ++ nop ++ or $v1, 0x10 ++ sh $v1, JOY_CTRL($t0) ++ ++ # BYTE 0 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'bus select' byte ++ li $a0, 0x81 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lgameid_error ++ #nop ++ ++ bne $v0, 0xFF, .Lgameid_error # If we do not get 0xFF (255d) back, branch ++ addiu $v0, $zero, 1 # Return a value of 1 [DELAY SLOT] ++ ++ # BYTE 1 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'game ID start command' byte ++ li $a0, 0x21 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lgameid_error ++ #nop ++ ++ # This response may be 0x00 or 0x08 (MCPro and regular cards) ++ # Depending on when you do the ping, so we can accept both ++ ++ # We got the 0x00 ping response ++ beq $v0, $zero, .LvalidresponseA ++ nop ++ ++ addiu $v0, $v0, -0x08 # We got the 0x08 ping response ++ beq $v0, $zero, .LvalidresponseA ++ nop ++ ++ b .Lgameid_error # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 2 # Return a value of 2 [DELAY SLOT] ++.LvalidresponseA: ++ # BYTE 2 ----------------------------------------------------------------- ++ #jal MemCardPro_Exchng # Send 'reserved' ++ #li $a0, 0x00 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lgameid_error ++ #nop ++ ++ #bnez $v0, .Lgameid_error # If we do not get 0x00 back, branch ++ #addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ # BYTE 3 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send 'reserved' ++ li $a0, 0x00 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lgameid_error ++ #nop ++ ++ bnez $v0, .Lgameid_error # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ # BYTE 4 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'string length' ++ move $a0, $a1 # copy $a1 to $a0 [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lgameid_error ++ #nop ++ ++ bnez $v0, .Lgameid_error # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 4 # Return a value of 4 [DELAY SLOT] ++ ++ # BYTE 5 ----------------------------------------------------------------- ++.strloop: ++ lbu $a0, 0($a2) # get the byte from the string ++ jal MemCardPro_Exchng # send the 'string data' itself ++ addiu $a1, $a1, -1 # decrement remaining bytes [DELAY SLOT] ++ ++ bnez $a1, .strloop # repeat the loop until all the bytes are sent ++ addiu $a2, 1 # advance the string address [DELAY SLOT] ++ # ------------------------------------------------------------------------ ++ ++ addiu $v0, $zero, 0 # No errors (return a value of 0) ++ ++# "error handler ++.Lgameid_error: ++ sb $zero, JOY_TXRX($t0) # Get the end byte ++ nop ++ ++ sh $zero, JOY_CTRL($t0) # Apparently required ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# $a0 = port number ++# $a1 = 0x22, 0x23, 0x24, or 0x24 to increment or decrement channels. ++# $a2 = expected response (0x27 for ping, 0x20 for channel/dir switches) ++# $a2 & 0x100 = response is actually required ++MemcardPro_SimpleCommand: ++ ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ lui $t0, IOBASE ++ ++ li $v0, 0x1003 # TX Enable, joypad port select ++ andi $a0, 1 ++ sll $a0, 13 ++ or $v0, $a0 # Select port 2 if $a0 is 1 ++ ++ sh $v0, JOY_CTRL($t0) # Set to Joypad control interface ++ ++ jal MemCardPro_Wait ++ li $a0, JOY_DELAY_COUNTER ++.Lread_empty_fifo_writeB: # Flush the RX FIFO just in case ++ lbu $v1, JOY_TXRX($t0) ++ lhu $v0, JOY_STAT($t0) ++ nop ++ andi $v0, 0x2 ++ bnez $v0, .Lread_empty_fifo_writeB ++ nop ++ ++ lhu $v1, JOY_CTRL($t0) ++ nop ++ or $v1, 0x10 ++ sh $v1, JOY_CTRL($t0) ++ ++ # BYTE 0 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'bus select' byte ++ li $a0, 0x81 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ bne $v0, 0xFF, .Lerror # If we do not get 0xFF (255d) back, branch ++ addiu $v0, $zero, 1 # Return a value of 1 [DELAY SLOT] ++ ++ # BYTE 1 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'next channel' byte ++ move $a0, $a1 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ # This response may be 0x00 or 0x08 (MCPro and regular cards) ++ # Depending on when you do the ping, so we can accept both ++ ++ # We got the 0x00 ping response ++ beq $v0, $zero, .LvalidresponseB ++ nop ++ ++ addiu $v0, $v0, -0x08 # We got the 0x08 ping response ++ beq $v0, $zero, .LvalidresponseB ++ nop ++ ++ b .Lerror # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 2 # Return a value of 2 [DELAY SLOT] ++.LvalidresponseB: ++ # BYTE 2 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send 'reserved' ++ li $a0, 0x00 # [DELAY SLOT] ++ ++ bnez $v0, .Lerror # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ #bne $v0, 0x00, .Lerror # If we do not get 0x00 back, branch ++ #addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ # BYTE 3 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send 'reserved' ++ li $a0, 0x00 # [DELAY SLOT] ++ ++ bnez $v0, .Lerror # If we do not get 0x00 back, branch ++ addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ #bne $v0, 0x00, .Lerror # If we do not get 0x00 back, branch ++ #addiu $v0, $zero, 3 # Return a value of 3 [DELAY SLOT] ++ ++ # BYTE 4 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'success' check ++ li $a0, 0x00 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ # Do we need a response for this packet type? ++ # Ping should always return 0x27 ++ # But older/bugged firmwares may not return 0x20 ++ # for directory switching, let's cover those cases ++ andi $a0, $a2, 0x100 ++ beq $a0, $zero, .Lresponse_not_required ++ nop ++ ++ andi $a2, $a2, 0xFF ++ bne $v0, $a2, .Lerror # If we do not get 0x20/0x27 back, branch ++ addiu $v0, $zero, 4 # Return a value of 4 [DELAY SLOT] ++.Lresponse_not_required: ++ # BYTE 5 ----------------------------------------------------------------- ++ jal MemCardPro_Exchng # Send the 'termination signal' ++ # Load 0x01 into the high word, since it's the last byte in the sequence ++ lui $a0, 0x01 # [DELAY SLOT] ++ ++ #jal MemCardPro_Wait ++ #li $a0, JOY_DELAY_COUNTER ++ ++ # return the result now (debugging) ++ #b .Lerror ++ #nop ++ ++ bne $v0, 0xFF, .Lerror # If we do not get 0xFF back, branch ++ addiu $v0, $zero, 5 # Return a value of 5 [DELAY SLOT] ++ # ------------------------------------------------------------------------ ++ ++ addiu $v0, $zero, 0 # No errors (return a value of 0) ++ ++ ++# !error handler ++.Lerror: ++ sb $zero, JOY_TXRX($t0) # Get the end byte ++ nop ++ ++ sh $zero, JOY_CTRL($t0) # Apparently required ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# arguments: ++# $a0 (port number) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = ping fail (no response from the MemCard Pro) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = card present fail (no card is present) ++# 5 = termination signal fail (no termination signal detected) ++MemCardPro_Ping: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a2, 0x127 ++ jal MemcardPro_SimpleCommand ++ li $a1, 0x20 ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++# arguments: ++# $a0 (port number) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = next channel (failed to select the next channel) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = success fail (device success flag check failed) ++# 5 = termination signal fail (no termination signal detected) ++MemCardPro_NextCH: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a2, 0x20 ++ jal MemcardPro_SimpleCommand ++ li $a1, 0x23 ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# arguments: ++# $a0 (port number) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = previous channel (failed to select the previous channel) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = success fail (device success flag check failed) ++# 5 = termination signal fail (no termination signal detected) ++MemCardPro_PrevCH: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a2, 0x20 ++ jal MemcardPro_SimpleCommand ++ li $a1, 0x22 ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# arguments: ++# $a0 (port number) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = next channel (failed to select the next card) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = success fail (device success flag check failed) ++# 5 = termination signal fail (no termination signal detected) ++MemCardPro_NextDIR: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a2, 0x20 ++ jal MemcardPro_SimpleCommand ++ li $a1, 0x25 ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# arguments: ++# $a0 (port number) ++# ++# return values: ++# 0 = no error (MemCard Pro detected) ++# 1 = bus select fail (no memory card device detected) ++# 2 = previous channel (failed to select the previous card) ++# 3 = reserve fail (reserved for future use (ignore this)) ++# 4 = success fail (device success flag check failed) ++# 5 = termination signal fail (no termination signal detected) ++MemCardPro_PrevDIR: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a2, 0x20 ++ jal MemcardPro_SimpleCommand ++ li $a1, 0x24 ++ ++ lw $ra, 0($sp) ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# handler/processor ++# $a0 = byte to send ++# $a0 & 0x000010000 == final byte in the sequnce (do not wait for /ACK) ++MemCardPro_Exchng: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ # We don't need to /ACK the final byte in a sequence ++ # so we can return early if this bit is set. ++ lui $t0, 0x01 ++ li $t1, 0x00 ++ and $t0, $a0, $t0 ++ beq $t0, $zero, .Lno_final_byte ++ nop ++ ++ # It's the last byte in the sequence, so ++ # set $t1 for an early return ++ li $t1,0x01 ++.Lno_final_byte: ++ lui $t0, IOBASE ++ ++ sb $a0, JOY_TXRX($t0) ++ nop ++ ++.Lsend_wait_exchg: ++ lhu $v0, JOY_STAT($t0) ++ nop ++ andi $v0, 0x2 # JOY_STAT value ++ beq $v0, $zero, .Lsend_wait_exchg ++ nop ++ ++ # We've got a byte waiting in the RX buffer ++ # But it's the last in the sequence, so ++ # We don't need to wait for an /ACK ++ bnez $t1, .Ldone ++ nop ++ ++ lui $a0, 0xBFC0 ++ move $v1, $zero # set $v1 to zero ++.Lwait_ack_exchg: ++ bgt $v1, 4500, .Ltimeout # ACK delay (4500 * 7 instructions = 31,500 cycles) ++ nop ++ lhu $v0, JOY_STAT($t0) ++ lw $zero, 4($a0) ++ lw $zero, 0($a0) ++ andi $v0, 0x202 ++ bne $v0, 0x202, .Lwait_ack_exchg ++ addiu $v1, 1 ++ ++ b .Ldone ++ nop ++.Ltimeout: ++ lbu $v0, JOY_TXRX($t0) ++ nop ++ b .Lexit_exchg ++ #ori $v0, 0x100 # Add 0x100 (used as a timeout flag on bit 8 of the $v0 register. we could use andi and mask it, but we'll just nop it) ++ nop ++.Ldone: ++ lhu $v1, JOY_CTRL($t0) ++ lbu $v0, JOY_TXRX($t0) ++ or $v1, 0x10 ++ sh $v1, JOY_CTRL($t0) ++.Lexit_exchg: ++ # Add a standard delay to pad us out to about 18us ++ # Not strictly necessary, but it's not a noticable delay ++ # and falls roughly in line with other implementations ++ jal MemCardPro_Wait ++ li $a0,0x70 ++ ++ lw $ra, 0($sp) ++ nop ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# simple delay loop (each instruction adds 1 cycle so a wait of 150 in $t1 results in 500 cycles) ++# $a0 = delay ++MemCardPro_Wait: ++ addiu $a0, -1 ++ bgtz $a0, MemCardPro_Wait ++ nop ++ ++ jr $ra ++ nop ++ ++ ++# No pa$rams, will wait long enough to do a ping followed by a ++# SendGameID on the same f$rame ++# (with a lot of wiggle room# 2.5ms in this case) ++# Where possible, just wait a f$rame instead. ++MemcardPro_InterCommandDelay: ++ addiu $sp, -4 ++ sw $ra, 0($sp) ++ ++ li $a0, 0x7000 ++ jal MemCardPro_Wait ++ ++ lw $ra, 0($sp) ++ nop ++ addiu $sp, 4 ++ jr $ra ++ nop ++ ++ ++# includes ++.include "memcardpro_regs.asm" +diff --git a/loader/memcardpro.h b/loader/memcardpro.h +new file mode 100644 +index 0000000..a66f607 +--- /dev/null ++++ b/loader/memcardpro.h +@@ -0,0 +1,40 @@ ++// Apache V2 License ++ ++ ++#ifndef MCPRO_H ++#define MCPRO_H ++ ++// ++// Asm functions ++// ++ ++extern int MemCardPro_Ping(int port01); ++extern int MemCardPro_SendGameID(int port01, int maxLen255, char *gameID); ++ ++extern int MemCardPro_NextCH(int port01); ++extern int MemCardPro_PrevCH(int port01); ++extern int MemCardPro_NextDIR(int port01); ++extern int MemCardPro_PrevDIR(int port01); ++ ++// Not a hard and fast rule, but here as a reminder that ++// there should be a decent delay between ++// e.g. back to back Ping() and SendGameID() commands ++// Ideally a frame, but at least a few ms. ++extern int MemcardPro_InterCommandDelay(); ++ ++// ++// Errors: ++// ++ ++#define MCPRO_0_NO_ERROR ++#define MCPRO_1_BUS_SELECT_FAIL ++// e.g no ping response, failed to change channel, etc. ++#define MCPRO_2_OPERATION_FAILED ++#define MCPRO_3_RESERVE_FAIL ++#define MCPRO_4_STRING_LENGTH_OR_SUCCESS_FAIL ++#define MCPRO_5_DATA_TRANSMISSION_FAIL ++ ++#define MCPRO_PORT_0 0 ++#define MCPRO_PORT_1 1 ++ ++#endif /* MCPRO_H */ +\ No newline at end of file +diff --git a/loader/memcardpro_regs.asm b/loader/memcardpro_regs.asm +new file mode 100644 +index 0000000..7a3dfa7 +--- /dev/null ++++ b/loader/memcardpro_regs.asm +@@ -0,0 +1,117 @@ ++# hardware register definitions ++ ++.equ IOBASE, 0x1F80 # IO segment base ++ ++# GPU ++.equ GP0, 0x1810 # also GPUREAD ++.equ GP1, 0x1814 # also GPUSTAT ++ ++# CD ++.equ CD_STAT, 0x1800 ++.equ CD_CMD, 0x1801 # also response FIFO ++.equ CD_DATA, 0x1802 # also parameters ++.equ CD_IRQ, 0x1803 ++ ++.equ CD_REG0, 0x1800 ++.equ CD_REG1, 0x1801 ++.equ CD_REG2, 0x1802 ++.equ CD_REG3, 0x1803 ++ ++.equ SBUS_5, 0x1018 ++.equ COM_DELAY, 0x1020 ++ ++# SPU (must be used with 16-bit load/store instructions) ++.equ SPU_VOICE_BASE, 0x1C00 ++ ++.equ SPU_MASTER_VOL, 0x1D80 ++.equ SPU_REVERB_VOL, 0x1D84 ++.equ SPU_KEY_ON, 0x1D88 ++.equ SPU_KEY_OFF, 0x1D8C ++.equ SPU_FM_MODE, 0x1D90 ++.equ SPU_NOISE_MODE, 0x1D94 ++.equ SPU_REVERB_ON, 0x1D98 ++.equ SPU_CHAN_STATUS, 0x1D9C ++ ++.equ SPU_REVERB_ADDR, 0x1DA2 ++.equ SPU_IRQ_ADDR, 0x1DA4 ++.equ SPU_ADDR, 0x1DA6 ++.equ SPU_DATA, 0x1DA8 ++ ++.equ SPUCNT, 0x1DAA ++.equ SPUDTCNT, 0x1DAC ++.equ SPUSTAT, 0x1DAE ++ ++.equ SPU_CD_VOL, 0x1DB0 ++.equ SPU_EXT_VOL, 0x1DB4 ++.equ SPU_CURRENT_VOL, 0x1DB8 ++ ++.equ SPU_VOICE_VOL_L, 0x00 ++.equ SPU_VOICE_VOL_R, 0x02 ++.equ SPU_VOICE_FREQ, 0x04 ++.equ SPU_VOICE_ADDR, 0x06 ++.equ SPU_VOICE_ADSR_L, 0x08 ++.equ SPU_VOICE_ADSR_H, 0x0A ++.equ SPU_VOICE_LOOP, 0x0E ++ ++# MDEC ++.equ MDEC0, 0x1820 ++.equ MDEC1, 0x1824 ++ ++# Pads ++.equ JOY_TXRX, 0x1040 ++.equ JOY_STAT, 0x1044 ++.equ JOY_MODE, 0x1048 ++.equ JOY_CTRL, 0x104A ++.equ JOY_BAUD, 0x104E ++ ++# Serial ++.equ SIO_TXRX, 0x1050 ++.equ SIO_STAT, 0x1054 ++.equ SIO_MODE, 0x1058 ++.equ SIO_CTRL, 0x105A ++.equ SIO_BAUD, 0x105E ++ ++# IRQ ++.equ ISTAT, 0x1070 ++.equ IMASK, 0x1074 ++ ++# DMA ++.equ DPCR, 0x10F0 ++.equ DICR, 0x10F4 ++ ++.equ D0_MADR, 0x1080 ++.equ D0_BCR, 0x1084 ++.equ D0_CHCR, 0x1088 ++ ++.equ D1_MADR, 0x1090 ++.equ D1_BCR, 0x1094 ++.equ D1_CHCR, 0x1098 ++ ++.equ D2_MADR, 0x10A0 ++.equ D2_BCR, 0x10A4 ++.equ D2_CHCR, 0x10A8 ++ ++.equ D3_MADR, 0x10B0 ++.equ D3_BCR, 0x10B4 ++.equ D3_CHCR, 0x10B8 ++ ++.equ D4_MADR, 0x10C0 ++.equ D4_BCR, 0x10C4 ++.equ D4_CHCR, 0x10C8 ++ ++.equ D6_MADR, 0x10E0 ++.equ D6_BCR, 0x10E4 ++.equ D6_CHCR, 0x10E8 ++ ++# Timers ++.equ T0_CNT, 0x1100 ++.equ T0_MODE, 0x1104 ++.equ T0_TGT, 0x1108 ++ ++.equ T1_CNT, 0x1110 ++.equ T1_MODE, 0x1114 ++.equ T1_TGT, 0x1118 ++ ++.equ T2_CNT, 0x1120 ++.equ T2_MODE, 0x1124 ++.equ T2_TGT, 0x1128 +diff --git a/tmp/og-tonyhax.O0E/loader/patch-ap.S b/tmp/og-tonyhax.O0E/loader/patch-ap.S +deleted file mode 100644 +index 85511eb..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patch-ap.S ++++ /dev/null +@@ -1,196 +0,0 @@ +- +-#include +- +-.text +- +-# +-# Intercepts the syscall(1) (aka EnterCriticalSection). +-# +-# When this code is executed, the registers are as follows: +-# - v0: saved thread registers, must NOT be modified. +-# The rest of the registers are not critical and can be used freely. +-# +-.globl patch_ap_start +-patch_ap_start: +- # Load the SP value +- lw t0, 0x7C(v0) +- +- # +- # If we are being called from an antimodchip module, the call stack will look like this: +- # - (game code) +- # - ap_check +- # - ap_failed +- # - StopCallback +- # - disable_ints +- # - EnterCriticalSection +- # +- # For all known modules, the return address from StopCallback to ap_failed sits at SP+0x28. +- # +- # Before reading from this address, we will check that after adding that offset, we do not +- # cross a 2MB boundary, which could cause an illegal memory read. +- # +- # Without this check the following games crash: +- # - Elemental Gearbolt (U) (SLUS-00654), calling with a stack at 0x807FFFE0, as we attempt +- # to read from 0x80800008. +- # - Rival Schools (U) (SLUS-00681), calling with a stack of 0x801FFFD8, as we attempt to +- # read from 0x80200000. A mirror would be generally present here, but not for this game +- # as it calls SetMemSize(2) to intentionally block this. +- # +- # Some games, like Grind Session (U) (SCUS-94568) use the scratchpad during gameplay for the +- # stack, which is only 1KB. For now, we will not add a safety check for this case since the +- # vast majority of games store the stack in RAM and no game is known to crash without it. +- # +- addi t1, t0, 0x28 +- xor t1, t0 +- srl t1, 21 +- bne t1, zero, patch_ap_skip +- +- # Load alledged return address +- lw t1, 0x28(t0) +- +- # +- # Check now if the loaded value could be a word-aligned address in either the KUSEG +- # (0x00000000-0x007FFFFF) or the KSEG0 (0x80000000-0x807FFFFF) regions, which is were user +- # code is executed. +- # +- # Most games use the KSEG0, except for Emperors New Groove (U) (SCUS-94571) whose programmers +- # seemed to prefer the KUSEG region. +- # +- # We cannot limit ourselves to checking the first 2MB of RAM, because some games, like +- # Robbit Mon Dieu (J) (SCPS-10103) use a mirror (0x80600000-0x807FFFFF). +- # +- li t2, 0x7F800003 +- and t2, t1 +- bne t2, zero, patch_ap_skip +- +- # +- # First, we will attempt to handle a version 1 antimodchip module. +- # +- # This checks only for the presence of a dumb modchip, by checking if the SCEx counter +- # increments when it should not. It is also only capable of displaying the stop screen +- # in Japanese. +- # +- # The offsets for some of the checked games are: +- # +- # Um Jammer Lammy (PAL-E): +- # - ap_check (0x801D8008) +- # - ap_failed (0x801D83E0, called from 0x801D8174) +- # - StopCallback (0x800356C4, called from 0x801D8400) +- # - disable_ints (0x80035B54, called from 0x800356E0) +- # - EnterCriticalSection +- # +- # For Saru! Get You (NTSC-J): +- # - ap_check (0x80136950) +- # - ap_failed (0x80136D28, called from 0x80136ABC) +- # - StopCallback (0x8002E814, called from 0x80136D48) +- # - disable_ints (0x8002ECA4, called from 0x8002E82C) +- # - EnterCriticalSection +- # +- # The return call from StopCallback to ap_failed is located at SP+0x28. We will check if +- # at this address +0x74 exists a "li v0, 0xE6000002", which is a black rentangle passed to +- # the DrawPrim function to clear the screen. +- # +- # If it exists, we will patch the thread state to return back to ap_check, as if the +- # ap_failed function had returned. +- # +- +- # Compare signature, and test for v2 if does not match +- lw t2, 0x74(t1) +- li t3, 0x3C02E600 +- bne t2, t3, patch_ap_v15 +- +- lw t2, 0x78(t1) +- li t3, 0x34420002 +- bne t2, t3, patch_ap_v15 +- +- # Load return address from ap_failed to ap_check +- lw t1, 0xE8(t0) +- +- # Adjust stack pointer +- addi t0, 0xF0 +- +- # Save and return +- b patch_ap_save +- +- # +- # Handle another variant of the v1, used by Vandal Hearts II - Tenjou no Mon (J) (SLPM-86251) +- # - ap_check (0x800C4868) +- # - ap_failed (0x800C4C40, called from 0x800C49D4) +- # - StopCallback (0x800D2700, called from 0x800C4C58) +- # - disable_ints (0x800D2B90, called from 0x800D2718) +- # - EnterCriticalSection +- # +- # Same idea, except the load is now a "li v1, 0xE6000002" at +0x64 bytes after ap_failed +- # returns to ap_check. +- # +- # The offsets are the same as for v2, so we will reuse those adjusts. +- # +-patch_ap_v15: +- lw t2, 0x64(t1) +- li t3, 0x3C03E600 +- bne t2, t3, patch_ap_v2 +- +- lw t2, 0x68(t1) +- li t3, 0x34630002 +- beq t2, t3, patch_ap_adjust_v2 +- +- # +- # We will now attempt to patch an antimodchip v2 module. +- # +- # This one is smarter and checks that the SCEx wobble is present in the inner tracks, +- # to detect CD swapping; and for dumb modchips by checking for absence of the wobble +- # in the outer tracks. +- # +- # The offsets for some of the checked games are: +- # +- # Rockman 2 - Dr. Wily no Nazo (J) (SLPS-02255): +- # - ap_check (0x8006CA58) +- # - ap_failed (0x8006D654, called from 0x8006CE5C and 0x8006D238) +- # - StopCallback (0x80024524, called from 0x8006D66C) +- # - disable_ints (0x800249B4, called from 0x8002453C) +- # - EnterCriticalSection +- # +- # The return address from StopCallback to ap_failed is located at SP+0x28, exactly as above +- # so we will not load it again. +- # +- # For this other version, we will check if at this return address +0x10 bytes exists a +- # "sh zero, 0x1F801DAA", which is used to mute the audio. +- # +- # If that exists, we will patch the thread state to return back to ap_check. +- # +-patch_ap_v2: +- # Compare signature +- lw t2, 0x18(t1) +- li t3, 0x3C011F80 +- bne t2, t3, patch_ap_skip +- +- lw t2, 0x1C(t1) +- li t3, 0xA4201DAA +- bne t2, t3, patch_ap_skip +- +-patch_ap_adjust_v2: +- # Load return address to from ap_failed to ap_check +- lw t1, 0x120(t0) +- +- # Adjust stack pointer +- addi t0, 0x128 +- +-patch_ap_save: +- # Zero the s0 and s1 stored in the thread state, so the state machine used by ap_check exits +- sw zero, 0x48(v0) +- sw zero, 0x4C(v0) +- +- # Save adjusted stack pointer and return address +- sw t0, 0x7C(v0) +- sw t1, 0x88(v0) +- +-.globl patch_ap_success +-patch_ap_success: +- j 0x12341234 +- +-.globl patch_ap_skip +-patch_ap_skip: +- j 0x12341234 +- +-.globl patch_ap_end +-patch_ap_end: +diff --git a/tmp/og-tonyhax.O0E/loader/patch-fpb.S b/tmp/og-tonyhax.O0E/loader/patch-fpb.S +deleted file mode 100644 +index e26fb72..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patch-fpb.S ++++ /dev/null +@@ -1,35 +0,0 @@ +- +-#include +- +-.text +- +-# +-# The anti-FreePSXBoot patch. +-# +-# This patch is called right at the very end of the last step in the read sector finite state +-# machine: +-# https://github.com/grumpycoders/pcsx-redux/blob/f6484e8010a40a81e4019d9bfa1a9d408637b614/src/mips/openbios/sio0/card.c#L194 +-# +-# When this code is executed, the registers are as follows: +-# - v0 contains 1, or "success". +-# - a1 contains the read buffer +-# - a2 contains the current sector number +-# +-# If the sector being read is sector 0 and it contains "FPBZ" at +0x7C, we modify the read data +-# so it is detected as corrupted and the game skips reading from it +-# +-# The offsets have been checked against BIOSes 2.2, 3.0, 4.1 and 4.4 +-# +-.globl patch_fpb_start +-patch_fpb_start: +- lw t0, 0x7C(a1) +- li t1, 0x5A425046 +- bne a2, 0, patch_fpb_ret +- bne t0, t1, patch_fpb_ret +- +- sw zero, 0(a1) +-patch_fpb_ret: +- j 0x5B54 +- +-.globl patch_fpb_end +-patch_fpb_end: +diff --git a/tmp/og-tonyhax.O0E/loader/patch-uart.S b/tmp/og-tonyhax.O0E/loader/patch-uart.S +deleted file mode 100644 +index da95257..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patch-uart.S ++++ /dev/null +@@ -1,121 +0,0 @@ +- +-#include +- +-.text +- +-# +-# This is a complete replacement for the original std_out_putc the BIOS has. +-# +-# In this function: +-# - a0: data to bitbang +-# - t0: working registers +-# - t1: bits to send +-# - t2: last IRQ flag value +-# - t3: 0x1F800000 (I/O start address) +-# - t4: COP0 SR value +-# +-.globl patch_uartputc_start +-patch_uartputc_start: +- # Add start (0) and stop (1) bits to byte +- andi a0, 0xFF +- sll a0, 1 +- ori a0, 0x200 +- +- # Newline (after processing) +- li t0, (0x0A << 1 | 0x200) +- +- # Bits to send (1 start + 8 data + 1 stop) +- li t1, 10 +- +- # Compare against newline (0x0A) +- bne a0, t0, notnl +- +- # If newline, prepend a 0x0D, like the original function did and increment bit count +- sll a0, 10 +- ori a0, (0x0D << 1 | 0x200) +- addi t1, 10 +- +-notnl: +- # We will directly manipulate the COP0 status registers instead of using EnterCriticalSection +- # to avoid other threads/interrupts from fucking up the timing. +- # +- # The reason is two-fold: +- # - The kernel does not support reentrant calls - if something calls us while we are +- # executing kernel code and we generate a syscall, we'd nuke the current thread state. +- # +- # - SetConf calls printf while re-configuring the TCBs (thread control blocks). Executing +- # *any* interrupt at that point (which includes syscalls) will cause the interrupt +- # handler to write the current thread state to the zero address, wiping the interrupt +- # trampoline at 0x80. +- # +- # By directly manipulating this register we're opening ourselves to all kinds of race +- # conditions, but since this is just for debugging tonyhax, that's good enough for me. +- +- # Load current SR state in t4 +- mfc0 t4, $12 +- +- # Clear bits 10 and 0, the same flags WarmBoot clears +- li t0, 0xFFFFFBFE +- and t0, t4 +- mtc0 t0, $12 +- +- # Load I/O start +- lui t3, 0x1F80 +- +- # Set timer 0 target to 293 cycles (33868800Hz/115200bps-1) +- li t0, 293 +- sw t0, 0x1108(t3) +- +- # Start timer 0 in: +- # - Source clock to SysClk (33868800Hz) +- # - Free-running mode +- # - Reset on reaching target value +- # - IRQ on repeat mode (can be fired multiple times) +- # - Toggle IRQ flag (bit 10) on every IRQ +- # +- # We must not use the "reached target value" flag because that seems to be affected by some +- # kind of undocumented hardware errata. In real hardware, that flag can read zero if the +- # elapsed cycles between timer start and read and target values are both even or odd. +- # +- # Also note that although we are using the IRQ bits, interrupts are actually disabled so +- # we will busy poll the corresponding bits. +- li t0, 0x04D8 +- sw t0, 0x1104(t3) +- +- # Current timer IRQ flag status +- li t2, 0x0400 +- +-writebit: +- # Emit bit via /JOY pin of port 2. +- # We need to invert it, then put it into JOY_CTRL.13. +- # The XOR also sets the bit JOY_CTRL.2 which enables outputing the /JOY signal +- andi t0, a0, 1 +- sll t0, 13 +- xori t0, 0x2002 +- sh t0, 0x104A(t3) +- +- # Shift right current buffer +- srl a0, 1 +- +- # Decrement count while we're waiting +- addi t1, -1 +- +- # Wait until the interrupt flag toggles +-writewait: +- lw t0, 0x1104(t3) +- andi t0, 0x0400 +- beq t0, t2, writewait +- +- # Save current IRQ flag status +- move t2, t0 +- +- # If not done, keep going +- bne t1, zero, writebit +- +- # Restore coprocessor flags +- mtc0 t4, $12 +- +- jr ra +- +-.global patch_uartputc_end +-patch_uartputc_end: +diff --git a/tmp/og-tonyhax.O0E/loader/patch-vandal-hearths-2.S b/tmp/og-tonyhax.O0E/loader/patch-vandal-hearths-2.S +deleted file mode 100644 +index 67f389a..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patch-vandal-hearths-2.S ++++ /dev/null +@@ -1,35 +0,0 @@ +- +-#include +- +-.text +- +-# +-# This game needs a special treatment, as it has the regular antipiracy, but also an extra check +-# by calling CdGetDiskType. If it detects the disc is a burned one, it aborts. +-# +-# Furthermore, if the BIOS is an European one, it gets stuck on a loop, calling the AP module +-# forever. +-# +-# So we will just nuke the antipiracy call. This function is supposed to return a nonzero, but +-# we do not need to patch v0 as there is a load constant into v0 right before the call. +-# +-.globl patch_vandal_start +-patch_vandal_start: +- # Load address where the call to antipiracy sits +- la t0, 0x80040C98 +- +- # Check if it matches "jal 0x80042854" +- lw t1, 0(t0) +- li t2, 0x0C010A15 +- bne t1, t2, patch_vandal_return +- +- # If it does, NOP the opcode +- sw zero, 0(t0) +- +-.globl patch_vandal_return +-patch_vandal_return: +- # This will be replaced with the real address +- j 0x12341234 +- +-.globl patch_vandal_end +-patch_vandal_end: +diff --git a/tmp/og-tonyhax.O0E/loader/patcher.c b/tmp/og-tonyhax.O0E/loader/patcher.c +deleted file mode 100644 +index 27c1c49..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patcher.c ++++ /dev/null +@@ -1,147 +0,0 @@ +- +-#include "bios.h" +-#include "debugscreen.h" +-#include "str.h" +-#include "patcher.h" +- +-inline void encode_j(void * jump_location, const void * jump_dest) { +- uint32_t * words = (uint32_t *) jump_location; +- words[0] = 0x08000000 | (((uint32_t) jump_dest >> 2) & 0x3FFFFFF); +-} +- +-inline void encode_jal(void * jump_location, const void * jump_dest) { +- uint32_t * words = (uint32_t *) jump_location; +- words[0] = 0x0C000000 | (((uint32_t) jump_dest >> 2) & 0x3FFFFFF); +-} +- +-inline void encode_li(void * load_location, int regnum, uint32_t value) { +- uint32_t * words = (uint32_t *) load_location; +- +- // LUI - Load Upper Immediate +- words[0] = 0x3C000000 | (regnum << 16) | (value >> 16); +- +- // ORI - OR Immediate +- words[1] = 0x34000000 | (regnum << 21) | (regnum << 16) | (value & 0xFFFF); +-} +- +-uint8_t * install_generic_antipiracy_patch(uint8_t * install_addr) { +- // Exports defined by the patch +- extern uint8_t patch_ap_start; +- extern uint8_t patch_ap_end; +- extern uint8_t patch_ap_skip; +- extern uint8_t patch_ap_success; +- +- debug_write(" * Generic antipiracy"); +- +- // Get the handler info structure +- handler_info_t * syscall_handler = bios_get_syscall_handler(); +- +- // Get the start of the verifier function (the only one set) +- uint32_t * verifier = (uint32_t *) syscall_handler->verifier; +- +- /* +- * At opcode 20 it accesses an 4-word array which contain where to jump depending on the +- * syscall performed. We're interested in modifying the value for 1 (EnterCriticalSection) +- * so we can intercept it and defuse the antimodchip. +- */ +- uint32_t lw_op = verifier[20]; +- if ((lw_op >> 16) != 0x8C39) { +- debug_write("Aborted! Please report this!"); +- return install_addr; +- } +- +- // Extract location of cases array +- void ** cases_array = (void **) (lw_op & 0xFFFF); +- +- // Copy blob +- memcpy(install_addr, &patch_ap_start, &patch_ap_end - &patch_ap_start); +- +- /* +- * Insert the jump to the original code, which we'll use if the call was not originated from +- * an antipiracy module. +- */ +- encode_j(install_addr + (&patch_ap_skip - &patch_ap_start), cases_array[1]); +- +- /* +- * Insert the jump we'll use to exit the exception handler once we have finished patching up +- * the thread state if the call was indeed originated from an antipiracy module. +- * +- * We'll use the address of syscall(0) which behaves as a nop to exit the exception. +- */ +- encode_j(install_addr + (&patch_ap_success - &patch_ap_start), cases_array[0]); +- +- // Finally replace +- cases_array[1] = install_addr; +- +- return install_addr + (&patch_ap_end - &patch_ap_start); +-} +- +-uint8_t * install_vandal_patch(uint8_t * install_addr) { +- // Exports defined by the patch +- extern uint8_t patch_vandal_start; +- extern uint8_t patch_vandal_return; +- extern uint8_t patch_vandal_end; +- +- debug_write(" * Vandal Hearths 2 AP"); +- +- // Copy blob +- memcpy(install_addr, &patch_vandal_start, &patch_vandal_end - &patch_vandal_start); +- +- // Hook into call 16 of table B (OutdatedPadGetButtons), which is called once per frame +- void ** b0_tbl = GetB0Table(); +- +- // Insert call to real function +- encode_j(install_addr + (&patch_vandal_return - &patch_vandal_start), b0_tbl[0x16]); +- +- // Replace it now +- b0_tbl[0x16] = install_addr; +- +- // Advance installation address +- return install_addr + (&patch_vandal_end - &patch_vandal_start); +-} +- +-uint8_t * install_fpb_patch(uint8_t * install_addr) { +- // Exports defined by the patch +- extern uint8_t patch_fpb_start; +- extern uint8_t patch_fpb_end; +- +- debug_write(" * FreePSXBoot"); +- +- // Copy blob +- memcpy(install_addr, &patch_fpb_start, &patch_fpb_end - &patch_fpb_start); +- +- // Install it +- encode_jal((void *) 0x5B40, install_addr); +- +- // Advance installation address +- return install_addr + (&patch_fpb_end - &patch_fpb_start); +-} +- +-void patcher_apply(const char * boot_file) { +- // We have plenty of space at the end of table B +- uint8_t * install_addr = (uint8_t *) (GetB0Table() + 0x5E); +- +- // Install patches +- debug_write("Installing patches:"); +- +- // Install a suitable antimodchip patch +- if (strcmp(boot_file, "cdrom:\\SLUS_009.40;1") == 0) { +- install_addr = install_vandal_patch(install_addr); +- } else { +- install_addr = install_generic_antipiracy_patch(install_addr); +- } +- +- // FreePSXBoot does not work on PS2 so skip its installation +- if (bios_is_ps1()) { +- install_addr = install_fpb_patch(install_addr); +- } +-} +- +-void patcher_apply_softuart() { +- // Exports defined by the patch +- extern uint8_t patch_uartputc_start; +- extern uint8_t patch_uartputc_end; +- +- // Overwrite BIOS' std_out_putchar function +- memcpy(BIOS_A0_TABLE[0x3C], &patch_uartputc_start, &patch_uartputc_end - &patch_uartputc_start); +-} +diff --git a/tmp/og-tonyhax.O0E/loader/patcher.h b/tmp/og-tonyhax.O0E/loader/patcher.h +deleted file mode 100644 +index 5b24f53..0000000 +--- a/tmp/og-tonyhax.O0E/loader/patcher.h ++++ /dev/null +@@ -1,12 +0,0 @@ +- +-#pragma once +- +-/** +- * Install and apply suitable BIOS patches. +- */ +-void patcher_apply(const char * boot_file); +- +-/** +- * Installs the softUART patch. +- */ +-void patcher_apply_softuart(); +diff --git a/loader/secondary-ff9.ld b/loader/secondary-ff9.ld +new file mode 100644 +index 0000000..178d83d +--- /dev/null ++++ b/loader/secondary-ff9.ld +@@ -0,0 +1,36 @@ ++MEMORY { ++ ram(wrx) :ORIGIN = 0x801E8480, LENGTH = 0xBD00 ++} ++SECTIONS { ++ . = 0x801E8480; ++ PROVIDE(__RO_START__ = .); ++ .text : ++ { ++ *(.start) ++ *(.text .text.*) ++ } >ram ++ .rodata : ++ { ++ *(.rodata .rodata.*) ++ } >ram ++ .data : ++ { ++ *(.data .data.*) ++ } >ram ++ PROVIDE(__CRC_START__ = .); ++ .crc : ++ { ++ *(.crc .crc.*) ++ } >ram ++ PROVIDE(__BSS_START__ = .); ++ .bss : ++ { ++ *(.bss .bss.*) ++ } >ram ++ PROVIDE(__BSS_END__ = .); ++ /DISCARD/ : ++ { ++ *(*) ++ } ++} ++ENTRY (start) +diff --git a/tmp/og-tonyhax.O0E/loader/secondary.c b/loader/secondary.c +index 340a79a..3789e0c 100644 +--- a/tmp/og-tonyhax.O0E/loader/secondary.c ++++ b/loader/secondary.c +@@ -1,6 +1,6 @@ + + #include +-#include ++#include + #include + #include "str.h" + #include "audio.h" +@@ -10,13 +10,529 @@ + #include "crc.h" + #include "debugscreen.h" + #include "gpu.h" +-#include "patcher.h" ++#include "ap-bypass.h" + #include "integrity.h" + #include "io.h" ++#include "memcardpro.h" ++#include "gameid-psx-exe.h" ++ ++// To test ROM functionality in emulation via boot CD, uncomment the following 2 lines: ++//#undef ROM ++//#define ROM ++ ++// To test XSTATION ROM functionality in emulation via boot CD, uncomment the following 4 lines: ++//#undef XSTATION ++//#define XSTATION ++//#undef ROM ++//#define ROM ++ ++//To test behavior without any anti-piracy bypasses enabled (useful for testing D0 AP bypass codes via save game file gameshark functionality rather then internal activate_anti_piracy() function), uncomment: ++//#define AP_BYPASS_DISABLE ++ ++// To test MCPro code path on an emulator, uncomment the line below. This must be commented out for releases. ++//#define FAKE_MCPRO ++ ++// To test PS2 code path on an emulator, uncomment the line below. This must be commented out for releases. ++//#define FAKE_PS2 ++ ++/* ++MCP/SD2PSX support added using danhans42's code in his tonyhax_mcp fork: https://github.com/danhans42/tonyhax_mcp/ as a base. ++ ++Implements the protocol which can be found here https://gitlab.com/chriz2600/ps1-game-id-transmission utilising the library/code found here https://github.com/Cybdyn-Systems/MemCardPro-ASM . ++ ++--Memcard Pro Library Licence Notice-- ++MemCard Pro Library ++Copyright (C) 2021-2024 Cybdyn Systems. All Rights Reserved. ++Licensed under the Apache License, Version 2.0 (the "License"); ++you may not use this file except in compliance with the License. ++You may obtain a copy of the License at ++http://www.apache.org/licenses/LICENSE-2.0 ++Unless required by applicable law or agreed to in writing, software ++distributed under the License is distributed on an "AS IS" BASIS, ++WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++See the License for the specific language governing permissions and ++limitations under the License. ++*/ ++ ++const char * p5_localized; ++const char * region_name; ++ ++bool is_beat_mania_append_gottamix = false; // Special handling for no-swap bypass needed for anti-piracy bypass system ++bool calibrate_laser = false; // Only Japanese VC2 and VC3 consoles need this so it is off by default ++bool bugged_setsession = false; // VC0 A, VC0 B, and VC1 A CDROM Controller BIOS versions all have a buggy SetSession command that requires a special work around to use ++bool enable_unlock = true; // Disabled on VC0A and VC0B Japanese CDROM Controller BIOS versions automatically. On VC1A+ the testregion command is run and if the region is Japan it is also disabled. ++bool controller_input = false; // When enabled, debug_write does not display the repeat messages counter. This is so we can draw a blank line and then wait for controller input using vsync in debug_write. ++bool dont_scroll = false; // When enabled, the screen doesn't scroll (for memory card save file selection) ++bool installed_cheat_engine = false; // The cheat engine is installed when parse_memcard_save_gameshark_codes() completes. Some games may go on to set explicit anti-piracy bypass GameShark codes however, so to prevent the cheat engine from being installed twice (which is wasteful) we set a flag here. ++bool did_read_mc = false; // We need to set the GameShark codes AFTER the last bios_reintialize(). I want to call bios_reinitialize() after reading the memory card data to prevent anything screwy in booting games, so we can just parse the data later after the final bios_reinitialize since it's still in RAM. ++bool gameid_device = false; // We don't need to remove a mcpro with gameid, so we don't display the remove the freepsxboot card now message when it's detected. This also tells the code to handle gameid when true. ++ ++uint16_t mc_base = 0x102; // start of gs code data in memory card buffer ++ ++uint8_t number_of_gameshark_code_lines; // part of my basic format to store codes, this tells us how many we will activate ++uint8_t * user_start = (uint8_t *) 0x80010000; + + // Loading address of tonyhax, provided by the secondary.ld linker script + extern uint8_t __RO_START__, __BSS_START__, __BSS_END__; + ++// for controller input functions ++void * address; // For Calculating BIOS Functions ++uint8_t j; // Joypad ++uint8_t padbuf[2][0x22]; // Joypad Buffers ++ ++void try_boot_cd(); // for access in memory card features ++ ++// re-usable strings for debug_write(). Due to compiler optimizations for size already being enabled, this isn't as black and white as just put any repeated strings here. It has to make sense (and visibly reduce the size of secondary.elf) which it does in the below cases. ++#define memory_card_in_slot "memory card in slot" ++#define press_triangle "Press TRIANGLE to" ++#define press_circle "Press CIRCLE to" ++#define press_cross "Press CROSS to" ++ ++ ++#if !defined TOCPERFECT ++#if defined ROM ++void run_shell() { ++ // runs Sony BIOS. Can access CD Player/Memory Card Manager. Can not boot any discs, even ones that normally work without the flash cart inserted in the console. This has been adapted code from the SCPH-1001 decomp: https://github.com/ogamespec/psxdev/blob/97fbb2d03e5aff4449097afd2b59690002cb2341/reverse/Main.c#L395 ++ ++ debug_write("Starting Sony BIOS..."); ++ memcpy((void*)0x80030000, (void*)0xBFC18000, 0x67FF0); // uses 0x80030000-0x80077FF0 ++ FlushCache(); ++ ((void (*)(void))0x80030000)(); ++} ++#endif ++ ++void controller_input_start() { ++ controller_input = 1; // disable the repeat counter used in debug_write until controller input is done, see debugscreen.c ++ // BIOS Function InitPAD(buf1,sz1,buf2,sz2) ++ address = (uint32_t *) GetB0Table()[0x12]; ++ ((void (*)(uint8_t*,uint32_t,uint8_t*,uint32_t)) address)(padbuf[0],0x22,padbuf[1],0x22); // BIOS InitPAD(buf1,sz1,buf2,sz2) exec ++ // BIOS Function StartPAD() ++ address = (void *) (GetB0Table()[0x13]); ++ ((void (*)(void)) address)(); // BIOS StartPad exec ++} ++ ++void controller_input_stop() { // this doubles as 'closing' the memory card functions ++ controller_input = 0; // Set debug_write back to normal (enable repeat counter) as controller input is done ++ // BIOS Function StopPAD() ++ address = (void *) (GetB0Table()[0x14]); ++ // StopPAD() as we are done using Joypad input ++ ((void (*)(void)) address)(); // BIOS StopPad exec ++} ++ ++void memory_card_and_controller_wait() { ++ for(volatile int i = 0; i < 0x100000; i++); // Pause to not spam a memory card read error message if a button is pressed down, and to ensure successful open/read of memory card which requires a waiting period in between calls ++} ++ ++uint8_t mcpro_check(uint8_t port){ ++ controller_input_start(); // For at least the ROM entrypoint, the BIOS is calling StartPad() (which also initializes memory cards and controllers) at some point before mottzilla's PS-EXE cartridge loader is taking over control and giving it to Tonyhax International. We need to call our own StartPad()and then stoppad() to close it correctly BEFORE we pull for a game id device. See https://github.com/alex-free/tonyhax/issues/69 . ++ controller_input_stop(); ++ uint8_t mcpro_check_ret = MemCardPro_Ping(port); ++ memory_card_and_controller_wait(); // docs say to wait ++ return mcpro_check_ret; ++} ++ ++uint8_t mcpro_sendid(char *gameid) { ++ memory_card_and_controller_wait(); // docs say to wait ++ uint8_t mcpro_sendid_ret = MemCardPro_SendGameID(MCPRO_PORT_0, strlen(gameid), gameid); ++ memory_card_and_controller_wait(); // docs say to wait ++ return mcpro_sendid_ret; ++} ++ ++void save_file_number_select_wait() // 1/2 as responsive for quicker save file selection ui ++{ ++ for(volatile int i = 0; i < 0x50000; i++); // Pause ++} ++ ++void memory_card_init() { ++ debug_write("Memory card init..."); ++ // BIOS Function InitCard(pad_enable) ++ int32_t pad_enable = 1; ++ address = (uint32_t *) GetB0Table()[0x4A]; ++ ((void (*)(int32_t*)) address)(&pad_enable); ++ // BIOS Function StartCard() ++ address = (void *) (GetB0Table()[0x4B]); ++ ((void (*)(void)) address)(); ++ // BIOS Function _bu_init() ++ address = (void *) (GetB0Table()[0x55]); ++ ((void (*)(void)) address)(); ++ ++ memory_card_and_controller_wait(); // all current functions calling this do it next so just do it once here ++} ++ ++void read_memory_card_controls() { // we call this more then once ++ debug_write(""); ++ debug_write("%s exit without enabling codes", press_triangle); ++ debug_write("%s load codes from %s 1", press_cross, memory_card_in_slot); ++ debug_write("%s load codes from %s 2", press_circle, memory_card_in_slot); ++} ++ ++ ++void read_memory_card_for_gameshark_codes() { ++/* ++PSX Official Docs ++ ++InitCARD ++Initialize Memory Card BIOS. ++Library Header File Introduced Documentation Date ++libcard.lib libapi.h 3.0 12/14/98 ++Syntax ++void InitCARD( ++long val) Specify sharing with controller ++Explanation ++Initializes the Memory Card BIOS and enters an idle state. val specifies whether or not there is sharing with ++the controller. (0: not shared; 1: shared.) ++When the BIOS is subsequently put into operation by StartCARD(), the low-level interface functions that ++begin with “ _card” can be used directly. ++The Memory Card file system uses these interfaces internally, so InitCARD() needs to be executed before ++_bu_init(). ++There is no effect on the controller. ++*/ ++ ++/* ++No $ PSX SPX File Error info ++ ++File Error Numbers for B(54h) and B(55h) ++ 00h okay (though many successful functions leave old error code unchanged) ++ 02h file not found ++ 06h bad device port number (tty2 and up) ++ 09h invalid or unused file handle ++ 10h general error (physical I/O error, unformatted, disk changed for old fcb) ++ 11h file already exists error (create/undelete/rename) ++ 12h tried to rename a file from one device to another device ++ 13h unknown device name ++ 16h sector alignment error, or fpos>=filesize, unknown seektype or ioctl cmd ++ 18h not enough free file handles ++ 1Ch not enough free memory card blocks ++ FFFFFFFFh invalid or unused file handle passed to B(55h) function ++*/ ++ ++ int32_t mc_fd; // assign to slot 1 or slot 2 filepath ++ char slot; ++ char * save_file; ++ bool did_select_card = false; // can exit out before selecting ++ bool card_select_unsuccessful = false; // detect errors in reading save file ++ ++ memory_card_init(); ++ read_memory_card_controls(); ++ ++ while(!did_select_card) { ++ j = padbuf[0][3] ^ 0xFF; ++ if(j == 0x40) { // X button ++ memory_card_and_controller_wait(); ++ save_file = "bu00:TONYHAXINTGS"; ++ did_select_card = true; ++ slot = 1; ++ } else if(j == 0x20) { // Circle button ++ memory_card_and_controller_wait(); ++ save_file = "bu10:TONYHAXINTGS"; ++ did_select_card = true; ++ slot = 2; ++ } else if(j == 0x10) { // Triangle button ++ memory_card_and_controller_wait(); ++ break; ++ } ++ ++ if(did_select_card) { ++ debug_write("Select save file name for %s %d", memory_card_in_slot, slot); ++ debug_write(""); ++ debug_write("%s increase the save file name's number", press_triangle); ++ debug_write("%s decrease the save file name's number", press_cross); ++ debug_write("%s confirm save file name selection", press_circle); ++ debug_write(""); ++ ++ char save_file_number = 0; ++ char previous_save_file_number = 0; ++ char selected_save_file_full_path[22]; // +2 for the number and the null terminator ++ bool first_run = true; ++ memory_card_and_controller_wait(); ++ ++ dont_scroll = true; // beautiful! ++ ++ while(1) ++ { ++ j = padbuf[0][3] ^ 0xFF; ++ ++ if(j == 0x40) { // Cross button ++ if(save_file_number > 0) ++ { ++ save_file_number--; ++ } ++ } else if(j == 0x10) { // Triangle button ++ if(save_file_number < 14) // max number of save files per memory card is 15 (0-14) ++ { ++ save_file_number++; ++ } ++ } else if(j == 0x20) { // Circle button ++ break; ++ } ++ ++ if((save_file_number != previous_save_file_number) || (first_run)) { ++ debug_write("%s%d", save_file, save_file_number); ++ save_file_number_select_wait(); // slow down ++ ++ if(first_run) { ++ first_run = false; ++ } ++ } ++ ++ previous_save_file_number = save_file_number; // only update display on new selection or first run for starting reference ++ } ++ dont_scroll = false; ++ ++ // where we're going, we don't need sdks ++ ++ for (int i = 0; i < 17; i++) { ++ selected_save_file_full_path[i] = save_file[i]; ++ } ++ ++ // get ascii for string append ++ if(save_file_number < 10) // single digit ++ { ++ save_file_number += 0x30; // add 0x30 for 0-9 ++ selected_save_file_full_path[17] = save_file_number; ++ selected_save_file_full_path[18] = '\0'; // null terminate ++ ++ /* debug ++ for(char i = 0; i < 19; i++) ++ debug_write("%x", selected_save_file_full_path[i]); ++ */ ++ } else { ++ selected_save_file_full_path[17] = 0x31; // 1 ++ ++ if(save_file_number == 10) { // double digit values ++ selected_save_file_full_path[18] = 0x30; // 0 ++ } else if(save_file_number == 11) { ++ selected_save_file_full_path[18] = 0x31; // 1 ++ } else if(save_file_number == 12) { ++ selected_save_file_full_path[18] = 0x32; // 2 ++ } else if(save_file_number == 13) { ++ selected_save_file_full_path[18] = 0x33; // 3 ++ } else if(save_file_number == 14) { ++ selected_save_file_full_path[18] = 0x34; // 4 ++ } ++ ++ selected_save_file_full_path[19] = '\0'; // null terminate ++ /* debug ++ for(char i = 0; i < 20; i++) ++ debug_write("%x", selected_save_file_full_path[i]); ++ */ ++ } ++ ++ mc_fd = FileOpen(selected_save_file_full_path, FILE_READ); ++ ++ if(mc_fd == -1) { ++ memory_card_and_controller_wait(); ++ debug_write("Can not open %s, read error %d", selected_save_file_full_path, GetLastError()); ++ #if defined FREEPSXBOOT ++ debug_write("Verify that the FreePSXBoot memory card has already been removed"); ++ #endif ++ did_select_card = false; ++ } else if (mc_fd > 0) { ++ int32_t read; ++ ++ read = FileRead(mc_fd, user_start, 0x2000); // read the entire file "TONYHAXINTGS" to the start of 'user RAM' (which will be cleared later before booting an executable). So 0x80010000-0x80012000 in RAM contains the contents of "TONYHAXINTGS". ++ // 8192 % 64 = 128 ++ FileClose(mc_fd); ++ ++ if (read == -1) { ++ memory_card_and_controller_wait(); ++ debug_write("Can not read %s, read error %d", selected_save_file_full_path, GetLastError()); ++ did_select_card = false; ++ } else { ++ number_of_gameshark_code_lines = user_start[mc_base + 1]; ++ debug_write("%d code lines detected", number_of_gameshark_code_lines); ++ ++ uint8_t sum; ++ uint8_t prev = 0; ++ uint8_t next; ++ uint8_t checksum_in_save_file = user_start[mc_base]; ++ ++ for (int i = 0x103; i < 0x2000; i++) ++ { ++ //debug_write("%x: @ %x", &user_start[i], user_start[i]); ++ next = user_start[i]; ++ sum = prev + next; ++ sum &= 0xFF; ++ prev = sum; ++ } ++ ++ if(checksum_in_save_file == sum) { ++ debug_write("%s checksum: %x verified", selected_save_file_full_path, sum); ++ did_read_mc = 1; // set flag to parse codes uploaded to RAM, right before clearing RAM itself and booting the game ++ } else { ++ debug_write("%s checksum: %x did not match the expected checksum: %x!", selected_save_file_full_path, sum, checksum_in_save_file); ++ debug_write("No codes enabled, %s may be corrupted", selected_save_file_full_path); ++ did_select_card = false; ++ } ++ } ++ } ++ ++ if(!did_select_card) { // if this is untrue here we got an error, so display the controls again to user (we are still in gameshark code load loop pulling for input on if we should select the memory card in slot 1 or slot 2 to use) ++ card_select_unsuccessful = true; ++ } ++ } ++ ++ if(card_select_unsuccessful) { ++ read_memory_card_controls(); // tell user the updated controls since we are out of the previously ran loop ++ card_select_unsuccessful = false; // only print to screen once ++ } else { ++ debug_write(""); // Vblank wait for controller input ++ } ++ } ++ ++ try_boot_cd(); // simply the easiest way to return ++} ++ ++void format_memcard() { ++ /* ++ B(41h) - FormatDevice(devicename) ++ Erases all files on the device (ie. for formatting memory cards). ++ Returns 1=okay, or 0=failed. ++ */ ++ ++ bool did_format = false; // can exit out before formatting (also checks status of being successful) ++ char slot = 0; ++ ++ memory_card_init(); ++ ++ debug_write("%s exit without formatting", press_triangle); ++ debug_write("%s FORMAT %s 1", press_cross, memory_card_in_slot); ++ debug_write("%s FORMAT %s 2", press_circle, memory_card_in_slot); ++ ++ while(1) { ++ j = padbuf[0][3] ^ 0xFF; ++ if(j == 0x40) { // Cross button ++ memory_card_and_controller_wait(); ++ ++ if(FormatDevice("bu00:") == 1) { ++ did_format = true; ++ } ++ ++ slot = 1; ++ } else if(j == 0x20) { // Circle button ++ memory_card_and_controller_wait(); ++ ++ if(FormatDevice("bu10:") == 1) ++ { ++ did_format = true; ++ } ++ ++ slot = 2; ++ } else if(j == 0x10) { // Triangle button ++ memory_card_and_controller_wait(); ++ break; // bail ++ } ++ ++ if(did_format) { ++ break; ++ } ++ ++ if(slot > 0) ++ { ++ debug_write("Format error %d for %s %d", GetLastError(), memory_card_in_slot, slot); ++ slot = 0; ++ } ++ ++ debug_write(""); // Vblank wait for controller input ++ } ++ ++ if(did_format) { ++ debug_write("Format successful for %s %d", memory_card_in_slot, slot); ++ } ++ ++ try_boot_cd(); // simply the easiest way to return ++} ++ ++void parse_memcard_save_gameshark_codes() { ++ /* ++ TONYHAXINTGS format: ++ ++ 0x00-0x101 = static save file data ++ ++ 0x102 = checksum generated by thigsgen ++ ++ 0x103 = number of code lines in hex ++ ++ 0x104-0x107 = gameshark code address (may or may not include code prefix i.e. D0, E0, 30, or 80 could be 0x104) ++ ++ 0x108-0x109 = gameshark code mod value ++ ++ 0x110-0x103 = 2nd gameshark code address ++ ++ 0x114-0x115 = 2nd gameshark code mod value (and then so on and so on for number of code lines in hex number) ++ */ ++ ++ uint32_t gameshark_code_address; ++ uint8_t gs_code_type; ++ ++ /* ++ uint32_t sum; ++ uint8_t prev = 0; ++ uint8_t next; ++ for (int i = 0; i < 0x2000; i++) ++ { ++ debug_write("%x: @ %x", &user_start[i], user_start[i]); ++ next = user_start[i]; ++ sum = prev + next; ++ sum &= 0xFF; ++ prev = sum; ++ } ++ debug_write("Memory card buffer checksum: %x", sum); ++ for (volatile int i = 0; i < 0x100000; i++); // won't be optimized out by -Os, pause ++ */ ++ ++ for(int i = 0; i < number_of_gameshark_code_lines; i++) { ++ ++ gs_code_type = user_start[mc_base + 5]; ++ ++ if( ++ (gs_code_type == 0xD0) || ++ (gs_code_type == 0xD1) || ++ (gs_code_type == 0xD2) || ++ (gs_code_type == 0xD3) || ++ (gs_code_type == 0xE0) || ++ (gs_code_type == 0xE1) || ++ (gs_code_type == 0xE2) || ++ (gs_code_type == 0xE3) || ++ (gs_code_type == 0x30) ++ ) { ++ user_start[mc_base + 5] = 0x80; // we need to convert the prefix to the real address first byte of 0x80 for the cheat engine ++ } ++ ++ gameshark_code_address = user_start[mc_base + 2] + (user_start[mc_base + 3] << 8) + (user_start[mc_base + 4] << 16) + (user_start[mc_base + 5] << 24); ++ //debug_write("GS Code Addr: %x", gameshark_code_address); ++ ++ if( ++ (gs_code_type == 0x80) || ++ (gs_code_type == 0xD0) || ++ (gs_code_type == 0xD1) || ++ (gs_code_type == 0xD2) || ++ (gs_code_type == 0xD3) ++ ++ ) { ++ uint16_t gameshark_code_mod_val = user_start[mc_base + 6] + (user_start[mc_base + 7] << 8); ++ //debug_write("GS Code Mod Val: %x", gameshark_code_mod_val); ++ add_16bit_code(gameshark_code_address, gameshark_code_mod_val, gs_code_type); ++ } ++ ++ if( ++ (gs_code_type == 0x30) || ++ (gs_code_type == 0xE0) || ++ (gs_code_type == 0xE1) || ++ (gs_code_type == 0xE2) || ++ (gs_code_type == 0xE3) ++ ++ ) { ++ uint8_t gameshark_code_mod_val = user_start[mc_base + 6]; ++ //debug_write("GS Code Mod Val: %x", gameshark_code_mod_val); ++ add_8bit_code(gameshark_code_address, gameshark_code_mod_val, gs_code_type); ++ } ++ ++ mc_base = (mc_base + 6); // advance 6 bytes from current val ++ } ++} ++#endif // TOCPERFECT ++ + void log_bios_version() { + /* + * "System ROM Version 4.5 05/25/00 A" +@@ -65,44 +581,7 @@ bool backdoor_cmd(uint_fast8_t cmd, const char * string) { + } + + bool unlock_drive() { +- uint8_t cd_reply[16]; +- +- // Run "GetRegion" test +- uint8_t test = CD_TEST_REGION; +- cd_command(CD_CMD_TEST, &test, 1); +- +- // Should succeed with 3 +- if (cd_wait_int() != 3) { +- debug_write("Region read failed"); +- return false; +- } +- +- // Read actual region text and null terminate it +- int len = cd_read_reply(cd_reply); +- cd_reply[len] = 0; +- +- // Compare which is the fifth string we have to send to the backdoor +- const char * region_name; +- const char * p5_localized; +- if (strcmp((char *) cd_reply, "for Europe") == 0) { +- region_name = "European"; +- p5_localized = "(Europe)"; +- } else if (strcmp((char *) cd_reply, "for U/C") == 0) { +- region_name = "American"; +- p5_localized = "of America"; +- } else if (strcmp((char *) cd_reply, "for NETEU") == 0) { +- region_name = "NetYaroze (EU)"; +- p5_localized = "World wide"; +- } else if (strcmp((char *) cd_reply, "for NETNA") == 0) { +- region_name = "NetYaroze (US)"; +- p5_localized = "World wide"; +- } else { +- // +4 to skip past "for " +- debug_write("Unsupported region: %s", (char *) (cd_reply + 4)); +- return false; +- } +- +- debug_write("Drive region: %s", region_name); ++ debug_write("Drive region: %s", region_name); + + // Note the kernel's implementation of strlen returns 0 for nulls. + if ( +@@ -121,331 +600,884 @@ bool unlock_drive() { + return true; + } + ++#if !defined TOCPERFECT + void wait_lid_status(bool open) { + uint8_t cd_reply[16]; ++ ++ controller_input_start(); + + uint8_t expected = open ? 0x10 : 0x00; + do { +- // Issue Getstat command +- // We cannot issue the BIOS CD commands yet because we haven't called CdInit +- cd_command(CD_CMD_GETSTAT, NULL, 0); + +- // Always returns 3, no need to check +- cd_wait_int(); +- +- // Always returns one, no need to check either +- cd_read_reply(cd_reply); +- +- } while ((cd_reply[0] & 0x10) != expected); ++ j = padbuf[0][3] ^ 0xFF; ++ debug_write(""); // Vblank wait for controller input ++ ++ #if defined ROM ++ if(j == 0x40) { // X button ++ controller_input_stop(); ++ run_shell(); // launch Sony BIOS ++ } else if(j == 0x20) { // Circle button ++ read_memory_card_for_gameshark_codes(); ++ } else if(j == 0x10) { // Triangle button ++ format_memcard(); ++ } ++ #else ++ if(j == 0x20) { // Circle button ++ read_memory_card_for_gameshark_codes(); ++ } else if(j == 0x10) { // Triangle button ++ format_memcard(); ++ } ++ #endif ++ ++ // Issue Getstat command ++ // We cannot issue the BIOS CD commands yet because we haven't called CdInit ++ cd_command(CD_CMD_GETSTAT, NULL, 0); ++ ++ // Always returns 3, no need to check ++ cd_wait_int(); ++ ++ // Always returns one, no need to check either ++ cd_read_reply(cd_reply); ++ ++ } while ((cd_reply[0] & 0x10) != expected); ++ ++ controller_input_stop(); + } + + bool exe_file_okay(const exe_header_t * exe_header) { +- /* +- * Cannot assume this will be present. The first revision of +- * Jikkyou Powerful Pro Yakyuu '95 (J) (SLPS-00016) does not have this present in the PSX.EXE. +- * +- * Just warn about it. +- */ +- if (strncmp(exe_header->signature, "PS-X EXE", 8)) { +- debug_write("Warning: header has invalid signature"); +- } +- +- // Check that the address is within RAM +- uint32_t masked_load = (uint32_t) exe_header->offsets.load_addr & 0xFF800000; +- if ( +- masked_load != 0x00000000 && +- masked_load != 0x80000000 && +- masked_load != 0xA0000000 +- ) { +- debug_write("Error: header has an invalid load address"); +- return false; +- } +- +- // Check that the PC is within RAM *and* aligned to a word +- uint32_t masked_pc = (uint32_t) exe_header->offsets.initial_pc & 0xFF800003; +- if ( +- masked_pc != 0x00000000 && +- masked_pc != 0x80000000 && +- masked_pc != 0xA0000000 +- ) { +- debug_write("Error: header has an invalid start address"); +- return false; +- } +- +- // Maybe we could check if the load+size overlaps with the kernel-reserved area? Dunno. +- +- return true; ++ /* ++ * Cannot assume this will be present. The first revision of ++ * Jikkyou Powerful Pro Yakyuu '95 (J) (SLPS-00016) does not have this present in the PSX.EXE. ++ * ++ * Just warn about it. ++ */ ++ if (strncmp(exe_header->signature, "PS-X EXE", 8)) { ++ debug_write("Warning: header has invalid signature"); ++ } ++ ++ // Check that the address is within RAM ++ uint32_t masked_load = (uint32_t) exe_header->offsets.load_addr & 0xFF800000; ++ if ( ++ masked_load != 0x00000000 && ++ masked_load != 0x80000000 && ++ masked_load != 0xA0000000 ++ ) { ++ debug_write("Error: header has an invalid load address"); ++ return false; ++ } ++ ++ // Check that the PC is within RAM *and* aligned to a word ++ uint32_t masked_pc = (uint32_t) exe_header->offsets.initial_pc & 0xFF800003; ++ if ( ++ masked_pc != 0x00000000 && ++ masked_pc != 0x80000000 && ++ masked_pc != 0xA0000000 ++ ) { ++ debug_write("Error: header has an invalid start address"); ++ return false; ++ } ++ ++ // Maybe we could check if the load+size overlaps with the kernel-reserved area? Dunno. ++ ++ return true; + } + +-void try_boot_cd() { +- int32_t read; +- +- debug_write("Swap CD now"); +- wait_lid_status(true); +- wait_lid_status(false); ++bool is_lid_open() { ++ uint8_t cd_reply[16]; + +- debug_write("Initializing CD"); +- if (!CdInit()) { +- debug_write("Init failed"); +- return; +- } ++ // Issue Getstat command ++ // We cannot issue the BIOS CD commands yet because we haven't called CdInit ++ cd_command(CD_CMD_GETSTAT, NULL, 0); + +- /* +- * Use the space the BIOS has allocated for reading CD sectors. +- * +- * The English translation of Mizzurna Falls (J) (SLPS-01783) depends on the header being +- * present here (see issue #95 in GitHub). +- * +- * This address varies between PS1 and PS2. +- */ +- uint8_t * data_buffer = (uint8_t *) (bios_is_ps1() ? 0xA000B070 : 0xA000A8D0); ++ // Always returns 3, no need to check ++ cd_wait_int(); + +- debug_write("Checking game region"); +- if (CdReadSector(1, 4, data_buffer) != 1) { +- debug_write("Failed to read sector"); +- return; +- } ++ // Always returns one, no need to check either ++ cd_read_reply(cd_reply); + +- const char * game_region; +- bool game_is_pal = false; +- /* +- * EU: " Licensed by Sony Computer Entertainment Euro pe " +- * US: " Licensed by Sony Computer Entertainment Amer ica " +- * JP: " Licensed by Sony Computer Entertainment Inc.",0x0A +- * |- character we use, at 0x3C +- */ +- switch (data_buffer[0x3C]) { +- case 'E': +- game_region = "European"; +- game_is_pal = true; +- break; +- +- case 'A': +- game_region = "American"; +- break; +- +- case 'I': +- game_region = "Japanese"; +- break; +- +- default: +- game_region = "unknown"; +- } ++ if(cd_reply[0]==0x10) { ++ return true; ++ } else { ++ return false; ++ } ++} + +- debug_write("Game's region is %s. Using %s video.", game_region, game_is_pal ? "PAL" : "NTSC"); +- +- // Defaults if no SYSTEM.CNF file exists +- uint32_t tcb = BIOS_DEFAULT_TCB; +- uint32_t event = BIOS_DEFAULT_EVCB; +- void * stacktop = BIOS_DEFAULT_STACKTOP; +- const char * bootfile = "cdrom:PSX.EXE;1"; +- +- char bootfilebuf[32]; +- debug_write("Loading SYSTEM.CNF"); +- +- int32_t cnf_fd = FileOpen("cdrom:SYSTEM.CNF;1", FILE_READ); +- if (cnf_fd >= 0) { +- read = FileRead(cnf_fd, data_buffer, 2048); +- if (read < 0) { +- debug_write("Failed to read. Error %d.", read, BIOS_FCBS[cnf_fd]->last_error); +- FileClose(cnf_fd); +- return; +- } +- FileClose(cnf_fd); +- +- // Null terminate +- data_buffer[read] = '\0'; +- +- config_get_hex((char *) data_buffer, "TCB", &tcb); +- config_get_hex((char *) data_buffer, "EVENT", &event); +- config_get_hex((char *) data_buffer, "STACK", (uint32_t *) &stacktop); +- if (config_get_string((char *) data_buffer, "BOOT", bootfilebuf)) { +- bootfile = bootfilebuf; +- } ++bool licensed_drive() { ++ uint8_t getid_response[9]; ++ unsigned char gid; ++ ++ while(1) { ++ cd_command(CD_CMD_GETID,0,0); ++ gid = cd_wait_int(); ++ if(gid!=5) ++ break; ++ } ++ ++ cd_wait_int(); ++ cd_read_reply(getid_response); ++ ++ if(getid_response[0]==0x02 && getid_response[1]==0x00 && getid_response[2]==0x20 && getid_response[3]==0x00) { ++ return true; ++ } else { ++ return false; ++ } ++} ++#endif // TOCPERFECT ++ ++void re_cd_init() { ++ debug_write("BIOS re-init"); ++ bios_reinitialize(); ++ bios_inject_disc_error(); ++ debug_write("Stopping Motor"); // Significantly improves reading data from disc ++ cd_command(CD_CMD_STOP, NULL, 0); cd_wait_int(); cd_wait_int(); ++ ++ debug_write("CD init"); ++ if (!CdInit()) { ++ debug_write("CD init failed"); ++ return; ++ } ++} + +- } else { +- uint32_t errorCode = GetLastError(); +- if (errorCode != FILEERR_NOT_FOUND) { +- debug_write("Open error %d", errorCode); +- return; +- } ++void ps2_hardware_bug_software_fix(const uint32_t target_lba, uint8_t * data_buffer) { ++ if(target_lba > 1000) // Don't need it except for extremly large seeks, 1000 is a pretty small condition here tbh. ++ { ++ const uint32_t seek_step[5] = { ++ (target_lba/6), ++ ( (target_lba/6) * 2), ++ ( (target_lba/6) * 3), ++ ( (target_lba/6) * 4), ++ ( (target_lba/6) * 5), ++ }; ++ ++ for(int i = 0; i < 5; i++) { ++ if (CdReadSector(1, seek_step[i], data_buffer) != 1) { ++ debug_write("Failed to read sector at LBA: %d", seek_step[i]); ++ } else { ++ debug_write("Seeked to LBA: %d", seek_step[i]); ++ } ++ } ++ } ++} + +- debug_write("Not found"); +- } ++void try_boot_cd() { ++ bool is_psx_exe = false; ++ int32_t read; ++ ++// we don't use freepsxboot patches for maximum game compatibility ++#if defined FREEPSXBOOT ++ if(!gameid_device) { // GameID switches off of 0.MCR (freepsxboot memory card) automatically! ++ debug_write("REMOVE THE FREEPSXBOOT MEMORY CARD FROM YOUR CONSOLE..."); ++ debug_write("BEFORE BOOTING ANY GAME!"); ++ } ++#endif + +- // Use string format to reduce ROM usage +- debug_write(" * %s = %x", "TCB", tcb); +- debug_write(" * %s = %x", "EVENT", event); +- debug_write(" * %s = %x", "STACK", stacktop); +- debug_write(" * %s = %s", "BOOT", bootfile); ++ debug_write(""); ++#if defined ROM ++ if(enable_unlock) ++ { ++ debug_write("%s with the CD drive open to boot the Sony BIOS", press_cross); ++ } else { ++ debug_write("%s boot the Sony BIOS", press_cross); // japanese consoles don't necessarily need the drive open at this point to do this behavior, it could be in a lid-sensor block state ++ } ++#endif + +- /* +- * SetConf is run by BIOS with interrupts disabled. +- * +- * If an interrupt happens while the BIOS is reinitializing the TCBs (thread control blocks), +- * the interrupt handler will store the current thread state in the zero address, wiping +- * vital data, like the interrupt trampoline at 0x80. +- * +- * We do not need to reenable the interrupts because SetConf already does it for us. +- */ +- debug_write("Configuring kernel"); +- EnterCriticalSection(); +- SetConf(event, tcb, stacktop); +- +- debug_write("Clearing RAM"); +- uint8_t * user_start = (uint8_t *) 0x80010000; +- bzero(user_start, &__RO_START__ - user_start); +- +- debug_write("Reading executable header"); +- int32_t exe_fd = FileOpen(bootfile, FILE_READ); +- if (exe_fd < 0) { +- debug_write("Open error %d", GetLastError()); +- return; +- } +- file_control_block_t * exe_fcb = *BIOS_FCBS + exe_fd; ++ debug_write("%s load GameShark codes from a memory card", press_circle); ++ debug_write("%s FORMAT A MEMORY CARD", press_triangle); ++ debug_write(""); ++ ++#if !defined XSTATION ++ uint8_t cbuf[4]; // CD Command Buffer ++ ++#if !defined TOCPERFECT ++ if(enable_unlock) { ++ debug_write("Put in a backup/import disc, then close the drive to boot"); ++ wait_lid_status(true); // doesn't wait during the ROM method, unsure why but it is what we want as it allows us to auto-boot with the ROM boot method ++ wait_lid_status(false); ++ } else { ++ if(is_lid_open() || !licensed_drive() ) { // If lid is open drive is not licensed, and if lid is closed we check if it is licensed (if it is not licensed but not open then the drive is closed and the user can open it and license it) ++ debug_write("To begin boot:"); ++ debug_write("Put in a real NTSC-J PSX game disc, then block the lid sensor"); ++ wait_lid_status(true); ++ wait_lid_status(false); // Blocking lid sensor = 'closing lid' ++ ++ debug_write("CD init"); // Drive will be in licensed state after this is successful ++ if (!CdInit()) { ++ debug_write("CD Init failed"); ++ debug_write("Try unblocking then blocking the lid sensor again"); ++ return; ++ } ++ } // Drive is licensed and the lid is 'closed' at this point ++ debug_write("Drive is licensed"); ++ ++ debug_write("Stopping motor"); ++ cd_command(CD_CMD_STOP,0,0); cd_wait_int(); cd_wait_int(); ++ ++ controller_input_start(); // stopped by wait_lid_status() previously. It still makes sense to accept controller input before finally confirming you want to boot the disc ++ ++ debug_write("(Keep the lid sensor blocked until turning off the console)"); ++ debug_write("Remove the real NTSC-J PSX game disc"); ++ debug_write("Put in a backup/import disc, then press CROSS to boot"); // Thanks MottZilla! ++ ++ while(1) { ++ j = padbuf[0][3] ^ 0xFF; ++ ++ if(j == 0x40) { ++ break; // X button boots disc ++ } else if(j == 0x20) { // Circle button enables codes ++ read_memory_card_for_gameshark_codes(); // this allows Japanese console users to enable user supplied GameShark codes without having to unblock the lid sensor, resetting authentication which would just be more unnecessary steps. ++ } else if(j == 0x10) { // Triangle button formats memory card ++ format_memcard(); ++ } ++ ++ //debug_write("Button: %x", j); ++ debug_write(""); // Vblank wait for controller input ++ } ++ ++ controller_input_stop(); // stop MC/controller input at this point ++ } ++ ++#endif // TOCPERFECT ++ ++ if(!enable_unlock) { ++ if(bugged_setsession) { ++ debug_write("Sending SetSessionSuperUltraCommandSmash v2, please wait"); // DuckStation can get stuck here if you swap the disc and don't wait a few seconds (DuckStation auto starts the motor on disc swap which is actually super annoying since if this executes while it is doing the emulated motor on it will lock up, and it doesn't even need to work like that since real hardware doesn't and game code always starts up a swap disc for i.e. multi-disc games). DuckStation also always emulates a VC1A which triggers this code path with any Japanese BIOS. ++ ++ int8_t session = CD_SET_SESSION_2; ++ cd_command(CD_CMD_SET_SESSION, (unsigned char *)&session, 1); cd_wait_int(); cd_wait_int(); // There is a 3rd response we are ignoring by sending SetSession 1 next ASAP after SetSession 2. ++ ++ session = 1; ++ cd_command(CD_CMD_SET_SESSION, (unsigned char *)&session, 1); cd_wait_int(); cd_wait_int(); ++ } else { ++ debug_write("Sending SetSession 1"); ++ int8_t session = 1; ++ cd_command(CD_CMD_SET_SESSION, (unsigned char *)&session, 1); cd_wait_int(); cd_wait_int(); ++ ++ if(calibrate_laser) { // VC2 and VC3s do auto Bias/Gain calibration when reading a newly inserted real NTSC-J PS1 disc. A swapped in CD-R or just a different disc in general needs this to be updated ++ debug_write("Calibrating laser"); ++ cbuf[0] = 0x50; cbuf[1] = 0x38; cbuf[2] = 0x15; cbuf[3] = 0x0A; // ModeCompensateTrackingAutoGain ++ cd_command(CD_CMD_TEST, &cbuf[0], 4); ++ cd_wait_int(); ++ } ++ } ++ } ++#else // XSTATION DEFINED ++ debug_write("Open and then close the CD drive lid to continue"); ++ wait_lid_status(true); // doesn't wait during the ROM method, unsure why but it is what we want as it allows us to auto-boot with the ROM boot method ++ wait_lid_status(false); ++#endif // XSTATION ++ ++ /* ++ We have to re-initialize the BIOS, stop, and init in that order to prevent the process from possibly freezing at this point on Japanese consoles. ++ ++ The first reason this is required is because the SetSessionSuperUltraCommandSmash screws up interrupts since we are sending the 2nd SetSession command before the possible 3rd interrupt (which is a second INT5 response sent if session 2 does not actually exist). ++ ++ The second reason is because of how we are using the BIOS controller functions, to go back to a clean state a bios re-initialization also accomplishes that. ++ */ ++ ++ re_cd_init(); ++ ++ /* ++ * Use the space the BIOS has allocated for reading CD sectors. ++ * ++ * The English translation of Mizzurna Falls (J) (SLPS-01783) depends on the header being ++ * present here (see issue #95 in GitHub). ++ * ++ * This address varies between PS1 and PS2. ++ */ ++ uint8_t * data_buffer = (uint8_t *) (bios_is_ps1() ? 0xA000B070 : 0xA000A8D0); ++ ++ debug_write("Checking game region"); ++#if defined TOCPERFECT ++ if (CdReadSector(1, 15, data_buffer) != 1) { // Real license data sector is copied to sector 15 by TOCPerfect Patch before it writes Japanese license data to sector 4 to allow booting via CD Player Swap Trick on early SCPH-3000 models ++ debug_write("Failed to read sector"); ++ return; ++ } ++#else ++ if (CdReadSector(1, 4, data_buffer) != 1) { ++ debug_write("Failed to read sector"); ++ return; ++ } ++#endif + +- read = FileRead(exe_fd, data_buffer, 2048); +- if (read < 0) { +- debug_write("Failed to read. Error %d.", exe_fcb->last_error); +- FileClose(exe_fd); +- return; +- } ++ const char * game_region; ++ bool game_is_pal = false; ++ /* ++ * EU: " Licensed by Sony Computer Entertainment Euro pe " ++ * US: " Licensed by Sony Computer Entertainment Amer ica " ++ * JP: " Licensed by Sony Computer Entertainment Inc.",0x0A ++ * |- character we use, at 0x3C ++ */ ++ switch (data_buffer[0x3C]) { ++ case 'E': ++ game_region = "European"; ++ game_is_pal = true; ++ break; ++ ++ case 'A': ++ game_region = "American"; ++ break; ++ ++ case 'I': ++ game_region = "Japanese"; ++ break; ++ ++ default: ++ game_region = "unknown"; ++ } ++ ++ // PS2s in PS1 mode can't switch video modes (at least like this from what we know), see https://github.com/socram8888/tonyhax/issues/25 ++ if((bios_is_ps1() == true) && (game_is_pal != gpu_is_pal())) { ++ debug_write("Switching video mode to %s for game region %s", game_is_pal ? "PAL" : "NTSC", game_region); ++ debug_switch_standard(game_is_pal); ++ } else { ++ debug_write("Game region is %s", game_region); ++ } ++ ++ // Defaults if no SYSTEM.CNF file exists ++ uint32_t tcb = BIOS_DEFAULT_TCB; ++ uint32_t event = BIOS_DEFAULT_EVCB; ++ void * stacktop = BIOS_DEFAULT_STACKTOP; ++ char * bootfile = "cdrom:PSX.EXE;1"; ++ ++ char bootfilebuf[32]; ++ debug_write("Loading SYSTEM.CNF"); ++ ++// PS2s have a hardware/BIOS bug that results in a seek issue when doing massive seeks from say LBA 4 to 290000+ when dealing with 80 minute media, see https://github.com/socram8888/tonyhax/issues/24#issuecomment-1823585149 ++#if !defined(FREEPSXBOOT) && !defined(XSTATION) && !defined(ROM) && !defined(TOCPERFECT) ++ ++#if !defined(FAKE_PS2) ++ if(bios_is_ps1() == false) { ++#endif + +- /* +- * Patch executable header like stock does. Fixes issue #153 with King's Field (J) (SLPS-00017). +- * https://github.com/grumpycoders/pcsx-redux/blob/a072e38d78c12a4ce1dadf951d9cdfd7ea59220b/src/mips/openbios/main/main.c#L380-L381 +- */ +- exe_header_t * exe_header = (exe_header_t *) data_buffer; +- exe_header->offsets.initial_sp_base = stacktop; +- exe_header->offsets.initial_sp_offset = 0; ++ uint32_t system_cnf_lba = CdGetLbn("SYSTEM.CNF;1"); ++ if(system_cnf_lba != 0) { ++ debug_write("SYSTEM.CNF LBA: %d", system_cnf_lba); ++ ps2_hardware_bug_software_fix(system_cnf_lba, data_buffer); ++ } + +- /* +- * Patch executable load size, capping it to the file size. +- * +- * According to https://github.com/socram8888/tonyhax/issues/161, +- * Kileak, The Blood (J) (SLPS-00027) specifies in its header a an invalid load size, larger +- * than the actual executable. +- * +- * While the BIOS does not validate this, we do to ensure the file could be read in its +- * entirety and detect possible CD read errors. +- */ +- uint32_t actual_exe_size = exe_fcb->size - 2048; +- if (actual_exe_size < exe_header->offsets.load_size) { +- exe_header->offsets.load_size = actual_exe_size; +- } +- +- if (!exe_file_okay(exe_header)) { +- FileClose(exe_fd); +- return; +- } ++#if !defined(FAKE_PS2) ++ } ++#endif + +- // If the file overlaps tonyhax, we will use the unstable LoadAndExecute function +- // since that's all we can do. +- if ((uint8_t *) exe_header->offsets.load_addr + exe_header->offsets.load_size >= &__RO_START__) { +- debug_write("Executable won't fit. Using buggy BIOS call."); +- +- if (game_is_pal != gpu_is_pal()) { +- debug_write("Switching video mode"); +- debug_switch_standard(game_is_pal); +- } +- +- // Restore original error handler +- bios_restore_disc_error(); +- +- LoadAndExecute( +- bootfile, +- exe_header->offsets.initial_sp_base, +- exe_header->offsets.initial_sp_offset +- ); +- return; +- } ++#endif + +- debug_write( +- "Loading executable (%d bytes @ %x)", +- exe_header->offsets.load_size, +- exe_header->offsets.load_addr +- ); ++ int32_t cnf_fd = FileOpen("cdrom:SYSTEM.CNF;1", FILE_READ); ++ if (cnf_fd >= 0) { ++ read = FileRead(cnf_fd, data_buffer, 2048); ++ ++ if (read < 0) { ++ debug_write("Failed to read. Error %d.", read, BIOS_FCBS[cnf_fd]->last_error); ++ FileClose(cnf_fd); ++ return; ++ } ++ FileClose(cnf_fd); ++ ++ // Null terminate ++ data_buffer[read] = '\0'; ++ ++ config_get_hex((char *) data_buffer, "TCB", &tcb); ++ config_get_hex((char *) data_buffer, "EVENT", &event); ++ config_get_hex((char *) data_buffer, "STACK", (uint32_t *) &stacktop); ++ ++#if defined TOCPERFECT ++ if (config_get_string((char *) data_buffer, "BOOY", bootfilebuf)) { ++ bootfile = bootfilebuf; ++ } else { ++ uint32_t errorCode = GetLastError(); ++ if (errorCode != FILEERR_NOT_FOUND) { ++ debug_write("Open error %d", errorCode); ++ return; ++ } ++ ++ debug_write("Not found"); ++ } ++ } // Always has SYSTEM.CNF ++#else ++ if(config_get_string((char *) data_buffer, "BOOT", bootfilebuf)) { ++ bootfile = bootfilebuf; ++ } else { ++ uint32_t errorCode = GetLastError(); ++ if (errorCode != FILEERR_NOT_FOUND) { ++ debug_write("Open error %d", errorCode); ++ return; ++ } ++ ++ debug_write("Not found"); ++ } ++ } else { ++ debug_write("No SYSTEM.CNF"); ++ } ++#endif + +- read = FileRead(exe_fd, exe_header->offsets.load_addr, exe_header->offsets.load_size); +- if (read < 0) { +- debug_write("Failed to read. Error %d.", exe_fcb->last_error); +- return; +- } ++ // Use string format to reduce ROM usage ++ debug_write(" * %s = %x", "TCB", tcb); ++ debug_write(" * %s = %x", "EVENT", event); ++ debug_write(" * %s = %x", "STACK", stacktop); ++ debug_write(" * %s = %s", "BOOT", bootfile); ++ ++ if( ++ (strcmp(bootfile, "cdrom:\\psx.exe") == 0) || ++ // Cool Boarder's 3 (USA) (Beta) - http://redump.org/disc/75375/. Game does have save functionality, but we currently don't support betas. ++ ++ (strcmp(bootfile, "cdrom:\\PSX.EXE;1") == 0) || ++ // Dead Or Alive (USA) (Beta) (1-09-1998) - http://redump.org/disc/73759/. Game does not have save functionality yet implemented. We don't currently support betas either. ++ // Gokuu Densetsu: Magic Beast Warriors (Japan) - http://redump.org/disc/24258/. ++ ++ (strcmp(bootfile, "cdrom:\\psx.exe;1") == 0) || ++ // The Great Battle IV (Japan) - https://psxdatacenter.com/games/J/T/SLPS-00719.html. ++ ++ (strcmp(bootfile, "cdrom:PSX.EXE;1") == 0) ++ // Tokimeki Memorial: Forever with You (Japan) (Shokai Genteiban) - http://redump.org/disc/6788/. ++ // Tokimeki Memorial: Forever with You (Japan) (Rev 1) - http://redump.org/disc/6789/. ++ // Tokimeki Memorial: Forever with You (Japan) (Rev 2) - http://redump.org/disc/33338/. ++ // Tokimeki Memorial: Forever with You (Japan) (Rev 4) - http://redump.org/disc/6764/. ++ // Gussun Oyoyo (Japan) - http://redump.org/disc/11336/ ++ // All other PSX.EXE games without a system.cnf also match here ;) ++ ) { ++ is_psx_exe = true; ++ } ++ ++#if defined FAKE_MCPRO ++ debug_write("DEBUG: forcing MCPro code path"); ++#else ++ if(gameid_device) { ++#endif ++ int8_t bootfile_len = strlen(bootfile); ++ //debug_write("Bootfile len: %d", bootfile_len); ++ const char * base = "cdrom:"; // Device exe is loaded from. ++ const char * slps = "SLPS_"; // Sony Licensed Product Serial. ++ const char * scps = "SCPS_"; // Sony Computer Product Serial. ++ const char * end = ";1"; ++ char * send_as_gameid_tmp = bootfile; ++ ++ char send_as_gameid[20]; ++ // c // 0 ++ // d // 1 ++ // r // 2 ++ // o // 3 ++ // m // 4 ++ // : // 5 ++ // S // 6 ++ // L or C // 7 ++ // P // 8 ++ // S // 9 ++ // _ // 10 ++ // #?? // 11 ++ // #?? // 12 ++ // #?? // 13 ++ // . // 14 ++ // #?? // 15 ++ // #?? // 16 ++ // ; // 17 ++ // 1 // 18 ++ // termination '\0' // 19 ++ ++ if(is_psx_exe) { ++ re_cd_init(); // Reset before next read. ++ ++ if (CdReadSector(1, 16, data_buffer) != 1) { // No chance of overseek for PS2s because it is a very early sector at the inner part of the disc. ++ debug_write("Failed to read sector 16"); ++ return; ++ } ++ ++ unsigned char volume_creation_timestamp[17]; // 16 bytes for Volume Creation Timestamp + termination. ++ ++ for(int i = 0; i < 16; i++) { ++ volume_creation_timestamp[i] = data_buffer[0x32D + i]; ++ } ++ ++ volume_creation_timestamp[16] = '\0'; ++ debug_write("PSX.EXE ID: %s", (char *)volume_creation_timestamp); ++ const char * serial = get_psx_exe_gameid(volume_creation_timestamp); // See gameid-psx-exe.c. ++ ++ // Build full bootfile. ++ if(is_scps) { // Returned by gameid-psx-exe.c. Since there are so few SCPS PSX.EXE games in comparison to the massive amount of SLPS, we default to SLPS unless we are told not to. ++ mini_sprintf(send_as_gameid, "%s%s%s%s", base, scps, serial, end); ++ } else { ++ mini_sprintf(send_as_gameid, "%s%s%s%s", base, slps, serial, end); ++ } ++ ++ /* ++ Tests Arc The Lad Japan Rev 0/Rev 1 ++ if(strcmp(psx_exe_gameid, "cdrom:SCPS_100.08;1") != 0) { ++ debug_write("Mismatch"); ++ } ++ */ ++ ++ if(strcmp(serial, "0") == 0) { // The library sends back a literal string containing '0' for no match. ++ debug_write("Unknown PSX.EXE ID, unique GameID unavailable."); // As a last resort, we send PSX.EXE for gameid. ++ } ++ } else { ++ if(bootfile_len > 19) { // Don't count termination. We got crazy shit like 'TEKKEN3\SLUS_004.02;1' to deal with, that looks ugly in the pico memcard index.txt file that we need to clean up. I think it is too large so the pico memcard does stuff like TEKKEN3\SLUS_004.02, when it should be 'TEKKEN3\SLUS_004.02;1'. Anyways, the idea is to make it shorter without the parent dir to avoid this. ++ // Strip everything except the executable name (last 13 bytes). I.e. 'SLPS_123.45;1'. ++ send_as_gameid_tmp = &send_as_gameid_tmp[bootfile_len-13]; ++ //debug_write("Stripped: %s", send_as_gameid_tmp); ++ mini_sprintf(send_as_gameid, "%s%s", base, send_as_gameid_tmp); // Build it back, 'cdrom:XXXX_XXX.XX;1'. ++ } else { // is 19 (20 including termination) or less so is safe. If it is less, then terminator will get copied anyways cutting it short. ++ strcpy(send_as_gameid, send_as_gameid_tmp); // Note that send_as_gameid_tmp was previously assigned to the bootfile string. ++ } ++ } ++ ++ debug_write("Sending %s as gameid", send_as_gameid); ++ mcpro_sendid(send_as_gameid); // Sends the bootfile string as-is if the bootfile string itself is not greater then 20, or if there isn't a PSX.EXE GameID match. Otherwise, this got modified from the bootile string. ++#if !defined FAKE_MCPRO ++ } ++#endif + +- FileClose(exe_fd); ++ re_cd_init(); // Reset one last time to avoid potential lockups (here be dragons) ++ ++ /* ++ * SetConf is run by BIOS with interrupts disabled. ++ * ++ * If an interrupt happens while the BIOS is reinitializing the TCBs (thread control blocks), ++ * the interrupt handler will store the current thread state in the zero address, wiping ++ * vital data, like the interrupt trampoline at 0x80. ++ * ++ * We do not need to reenable the interrupts because SetConf already does it for us. ++ */ ++ debug_write("Configuring kernel"); ++ EnterCriticalSection(); ++ SetConf(event, tcb, stacktop); ++ ++#if !defined TOCPERFECT ++ if(did_read_mc) // before clearing RAM (which contains our mem card buffer if applicable) but after last bios_reinitalize/setconf() ++ parse_memcard_save_gameshark_codes(); ++#endif + +- patcher_apply(bootfile); ++ debug_write("Clearing RAM"); ++ bzero(user_start, &__RO_START__ - user_start); + +- if (game_is_pal != gpu_is_pal()) { +- debug_write("Switching video mode"); +- debug_switch_standard(game_is_pal); +- } ++ char * append_bypass_bootfile = "cdrom:\\APPEND.EXE;1"; + +- debug_write("Starting"); ++// Only 2 known no-swap bypass games that work with the Append No Swap Bypass (By mdmdj) method ++ if((strcmp("cdrom:\\SLPM_861.84;1", bootfile)) == 0) { // BeatMania Append 3rdMix ++ bootfile = append_bypass_bootfile; ++ //debug_write("Append No Swap Bypass detected"); ++ // no anti-piracy other then key disc in this one ++ } + +- // Restore original error handler +- bios_restore_disc_error(); ++ if((strcmp("cdrom:\\SLPM_862.29;1", bootfile)) == 0) { // BeatMania Append GottaMix ++ bootfile = append_bypass_bootfile; ++ is_beat_mania_append_gottamix = true; // used for anti-piracy bootfile match replacement ++ //debug_write("Append No Swap Bypass detected"); ++ } + +- // Games from WarmBoot start with interrupts disabled +- EnterCriticalSection(); ++ debug_write("Reading executable header"); + +- // FlushCache needs to be called with interrupts disabled +- FlushCache(); ++#if !defined(FREEPSXBOOT) && !defined(XSTATION) && !defined(ROM) && !defined(TOCPERFECT) + +- DoExecute(&exe_header->offsets, 0, 0); +-} ++#if !defined(FAKE_PS2) ++ if(bios_is_ps1() == false) { ++#endif + +-void main() { +- // Undo all possible fuckeries during exploiting +- bios_reinitialize(); ++ // CdGetLbn() needs any cdrom:\\, cdrom:\, or cdrom: in the bootfile string from SYSTEM.CNF to be stripped out. The function below is by Nicholas Noble ++ char * bootfile_for_CdGetLbn = 0; ++ uint32_t hash = 5381; ++ for (unsigned i = 0; i < 8; i++) { ++ hash = ((hash << 5) + hash) ^ bootfile[i]; ++ ++ switch (hash) { ++ case 0x5b730b88: ++ case 0xc9d47cd4: ++ case 0x04641708: ++ bootfile_for_CdGetLbn = bootfile + i + 1; ++ break; ++ } ++ } ++ ++ //debug_write("CdGetLbn BOOTFILE NAME: %s", bootfile_for_CdGetLbn); ++ const uint32_t bootfile_lba = CdGetLbn(bootfile_for_CdGetLbn); ++ ++ if(bootfile_lba > 0) { ++ debug_write("BOOTFILE LBA: %d", bootfile_lba); ++ ps2_hardware_bug_software_fix(bootfile_lba, data_buffer); ++ } ++ ++#if !defined(FAKE_PS2) ++ } ++#endif + +- // Mute the audio +- audio_halt(); ++#endif + +- // Initialize debug screen +- debug_init(); ++ int32_t exe_fd = FileOpen(bootfile, FILE_READ); ++ if (exe_fd < 0) { ++ debug_write("Open error %d", GetLastError()); ++ return; ++ } ++ ++ file_control_block_t * exe_fcb = *BIOS_FCBS + exe_fd; ++ ++ read = FileRead(exe_fd, data_buffer, 2048); ++ if (read < 0) { ++ debug_write("Failed to read. Error %d.", exe_fcb->last_error); ++ FileClose(exe_fd); ++ return; ++ } ++ ++ /* ++ * Patch executable header like stock does. Fixes issue #153 with King's Field (J) (SLPS-00017). ++ * https://github.com/grumpycoders/pcsx-redux/blob/a072e38d78c12a4ce1dadf951d9cdfd7ea59220b/src/mips/openbios/main/main.c#L380-L381 ++ */ ++ exe_header_t * exe_header = (exe_header_t *) data_buffer; ++ exe_header->offsets.initial_sp_base = stacktop; ++ exe_header->offsets.initial_sp_offset = 0; ++ ++ /* ++ * Patch executable load size, capping it to the file size. ++ * ++ * According to https://github.com/socram8888/tonyhax/issues/161, ++ * Kileak, The Blood (J) (SLPS-00027) specifies in its header a an invalid load size, larger ++ * than the actual executable. ++ * ++ * While the BIOS does not validate this, we do to ensure the file could be read in its ++ * entirety and detect possible CD read errors. ++ */ ++ uint32_t actual_exe_size = exe_fcb->size - 2048; ++ if (actual_exe_size < exe_header->offsets.load_size) { ++ exe_header->offsets.load_size = actual_exe_size; ++ } ++ ++ if (!exe_file_okay(exe_header)) { ++ FileClose(exe_fd); ++ return; ++ } ++ ++ /* ++ uint32_t sum; ++ uint8_t prev = 0; ++ uint8_t next; ++ uint8_t * ce = (uint8_t *) 0xC000; ++ ++ for (int i = 0; i < 0x100; i++) ++ { ++ //debug_write("%x: @ %x", &ce[i], ce[i]); ++ next = ce[i]; ++ sum = prev + next; ++ sum &= 0xFF; ++ prev = sum; ++ } ++ debug_write("Cheat engine checksum: %x", sum); ++ for (volatile int i = 0; i < 0x100000; i++); // won't be optimized out by -Os, pause ++ ++ uint8_t * codes = (uint8_t *) 0xD000; ++ ++ for (int i = 0; i < 0x1FFB; i++) ++ { ++ //debug_write("%x: @ %x", &codes[i], codes[i]); ++ next = codes[i]; ++ sum = prev + next; ++ sum &= 0xFF; ++ prev = sum; ++ } ++ debug_write("Cheat engine enabled codes Checksum: %x", sum); ++ for (volatile int i = 0; i < 0x100000; i++); // won't be optimized out by -Os, pause ++ */ ++ ++ // If the file overlaps tonyhax, we will use the unstable LoadAndExecute function ++ // since that's all we can do. ++ if ((uint8_t *) exe_header->offsets.load_addr + exe_header->offsets.load_size >= &__RO_START__) { ++ debug_write("Executable won't fit. Using buggy BIOS call."); ++ // Restore original error handler ++ bios_restore_disc_error(); ++ ++#if !defined XSTATION ++#if !defined AP_BYPASS_DISABLE ++ activate_anti_anti_piracy(bootfile, (int32_t) exe_header->offsets.load_addr); ++#endif ++#endif + +- debug_write("Integrity check %sed", integrity_ok ? "pass" : "fail"); +- if (!integrity_ok) { +- return; +- } ++ if((did_read_mc) && (!cheat_engine_installed)) { ++ install_cheat_engine(); ++ } ++ ++ LoadAndExecute( ++ bootfile, ++ exe_header->offsets.initial_sp_base, ++ exe_header->offsets.initial_sp_offset ++ ); ++ return; ++ } ++ ++ debug_write( ++ "Loading executable (%d bytes @ %x)", ++ exe_header->offsets.load_size, ++ exe_header->offsets.load_addr ++ ); ++ ++ /* ++ https://github.com/socram8888/tonyhax/issues/161 ++ ++ Kileak, The Blood (Japan) - http://redump.org/disc/14371/ specifies the wrong executable filesize (offset 0x1C in PSX.EXE). The filesize given (643072) includes the PSX.EXE header sector, when it is mandated to NOT include it in the total filesize. ++ ++ From No $ PSX SPX: ++ =============================== ++ 000h-007h ASCII ID "PS-X EXE" ++ 008h-00Fh Zerofilled ++ 010h Initial PC (usually 80010000h, or higher) ++ 014h Initial GP/R28 (usually 0) ++ 018h Destination Address in RAM (usually 80010000h, or higher) ++ 01Ch Filesize (must be N*800h) (excluding 800h-byte header) THIS RIGHT HERE ++ 020h Data section Start Address (usually 0) ++ 024h Data Section Size in bytes (usually 0) ++ 028h BSS section Start Address (usually 0) (when below Size=None) ++ 02Ch BSS section Size in bytes (usually 0) (0=None) ++ 030h Initial SP/R29 & FP/R30 Base (usually 801FFFF0h) (or 0=None) ++ 034h Initial SP/R29 & FP/R30 Offs (usually 0, added to above Base) ++ 038h-04Bh Reserved for A(43h) Function (should be zerofilled in exefile) ++ 04Ch-xxxh ASCII marker ++ "Sony Computer Entertainment Inc. for Japan area" ++ "Sony Computer Entertainment Inc. for Europe area" ++ "Sony Computer Entertainment Inc. for North America area" ++ (or often zerofilled in some homebrew files) ++ (the BIOS doesn't verify this string, and boots fine without it) ++ xxxh-7FFh Zerofilled ++ 800h... Code/Data (loaded to entry[018h] and up) ++ =============================== ++ */ ++ ++ read = FileRead(exe_fd, exe_header->offsets.load_addr, exe_header->offsets.load_size); ++ if (read < 0) { ++ debug_write("Failed to read. Error %d.", exe_fcb->last_error); ++ return; ++ } ++ ++ FileClose(exe_fd); ++ ++ debug_write("Starting"); ++ ++ // Restore original error handler ++ bios_restore_disc_error(); ++ ++#if !defined XSTATION ++#if !defined AP_BYPASS_DISABLE ++ activate_anti_anti_piracy(bootfile, (int32_t) exe_header->offsets.load_addr); ++#endif ++#endif + +- bios_inject_disc_error(); +- log_bios_version(); ++ if((did_read_mc) && (!cheat_engine_installed)) { ++ install_cheat_engine(); ++ } + +- debug_write("Resetting drive"); +- if (!cd_drive_init()) { +- debug_write("Reset failed"); +- return; +- } ++ // Games from WarmBoot start with interrupts disabled ++ EnterCriticalSection(); + +- debug_write("Unlocking drive"); +- if (!unlock_drive()) { +- return; +- } ++ // FlushCache needs to be called with interrupts disabled ++ FlushCache(); + +- while (1) { +-#if SOFTUART_PATCH +- patcher_apply_softuart(); +- std_out_puts("SoftUART ready\n"); +-#endif ++ DoExecute(&exe_header->offsets, 0, 0); ++} + +- try_boot_cd(); ++void main() { + +- debug_write("Reinitializing kernel"); +- bios_reinitialize(); +- bios_inject_disc_error(); +- } ++ uint8_t cd_controller_version[4]; ++ ++ // Undo all possible fuckeries during exploiting ++ bios_reinitialize(); ++ ++ // Mute the audio ++ audio_halt(); ++ ++ // Initialize debug screen ++ debug_init(); ++ ++ debug_write("Integrity check %sed", integrity_ok ? "pass" : "fail"); ++ if (!integrity_ok) { ++ return; ++ } ++ ++ bios_inject_disc_error(); ++ log_bios_version(); ++ ++ debug_write("Resetting Drive"); ++ cd_drive_init(); ++ ++ const int8_t version = CD_TEST_VERSION; ++ cd_command(CD_CMD_TEST, (unsigned char *)&version, 1); cd_wait_int(); ++ cd_read_reply(cd_controller_version); // Test Command $19,$20 gets the CDROM BIOS ++ debug_write("CD BIOS: %x", *(uint32_t*) cd_controller_version); ++ if(cd_controller_version[0] == 0x94) { ++ bugged_setsession = true; ++ enable_unlock = false; // VC0 A and VC0 B are both from 1994 and don't support the getregion command to figure out if it is unlockable or not. ++ } ++ else if(cd_controller_version[1] == 0x05 && cd_controller_version[2] == 0x16 && cd_controller_version[0] == 0x95 && cd_controller_version[3] == 0xC1) { ++ bugged_setsession = true; // NOTE I don't think this will ever be triggered but just in case. Earliest SCPH-3000s and late SCPH-1000s are VC0B and later SCPH-3000s are VC1B. Only unlockable systems have VC1A it seems. ++ } ++ else if((cd_controller_version[3] == 0xC2) || (cd_controller_version[3] == 0xC3)) { ++ calibrate_laser = true; ++ } ++ ++#if !defined XSTATION ++ if(enable_unlock) { ++ uint8_t cd_reply[16]; ++ // Run "GetRegion" test ++ uint8_t test = CD_TEST_REGION; ++ cd_command(CD_CMD_TEST, &test, 1); ++ ++ // Should succeed with 3 ++ if (cd_wait_int() != 3) { ++ debug_write("Region read failed"); ++ return; ++ } ++ ++ // Read actual region text and null terminate it ++ int len = cd_read_reply(cd_reply); ++ cd_reply[len] = 0; ++ ++ // Compare which is the fifth string we have to send to the backdoor ++ if (strcmp((char *) cd_reply, "for Europe") == 0) { ++ region_name = "European"; ++ p5_localized = "(Europe)"; ++ } else if (strcmp((char *) cd_reply, "for U/C") == 0) { ++ region_name = "American"; ++ p5_localized = "of America"; ++ } else if (strcmp((char *) cd_reply, "for NETEU") == 0) { ++ region_name = "NetYaroze (EU)"; ++ p5_localized = "World wide"; ++ } else if (strcmp((char *) cd_reply, "for NETNA") == 0) { ++ region_name = "NetYaroze (US)"; ++ p5_localized = "World wide"; ++ } else if (strcmp((char *) cd_reply, "for Japan") == 0) { ++ enable_unlock = 0; ++ } else { ++ // +4 to skip past "for " ++ debug_write("Unsupported region: %s", (char *) (cd_reply + 4)); ++ return; ++ } ++ ++ if(enable_unlock) { // Check again, this could be false now ++ #if defined TOCPERFECT // Get TOC via reset + unlock instead of unlock + opening/closing the drive lid for auto loading in TOCPerfect, thanks MottZilla! ++ debug_write("Resetting drive"); ++ cd_drive_reset(); ++ #endif ++ debug_write("Unlocking drive"); ++ if (!unlock_drive()) ++ return; ++ } ++ } ++#endif // XSTATION ++ ++ if(mcpro_check(MCPRO_PORT_0) == 0) { ++ debug_write("GameID device detected in slot 1"); ++ gameid_device = true; ++ } ++ ++ while (1) { ++ try_boot_cd(); ++ ++ debug_write("BIOS re-int"); ++ bios_reinitialize(); ++ bios_inject_disc_error(); ++ } + } + + void __attribute__((section(".start"))) start() { +- // Clear BSS +- bzero(&__BSS_START__, &__BSS_END__ - &__BSS_START__); ++ // Clear BSS ++ bzero(&__BSS_START__, &__BSS_END__ - &__BSS_START__); + +- // Execute integrity test +- integrity_test(); ++ // Execute integrity test ++ integrity_test(); + +- main(); ++ main(); + +- while(1); ++ while(1); + } +diff --git a/tmp/og-tonyhax.O0E/loader/secondary.ld b/loader/secondary.ld +index 7dc880a..2a912c2 100644 +--- a/tmp/og-tonyhax.O0E/loader/secondary.ld ++++ b/loader/secondary.ld +@@ -1,8 +1,8 @@ + MEMORY { +- ram(wrx) :ORIGIN = 0x801FA100, LENGTH = 0x3F00 ++ ram(wrx) :ORIGIN = 0x801EA300, LENGTH = 0xBD00 + } + SECTIONS { +- . = 0x801FA100; ++ . = 0x801EA300; + PROVIDE(__RO_START__ = .); + .text : + { +diff --git a/tmp/og-tonyhax.O0E/loader/str.c b/loader/str.c +index 0f9c895..4ae2e05 100644 +--- a/tmp/og-tonyhax.O0E/loader/str.c ++++ b/loader/str.c +@@ -1,6 +1,6 @@ + + #include "str.h" +-#include ++#include + + int isspace(int c) { + switch (c) { +diff --git a/loader/tonyhax-tpl-16kb.mcs b/loader/tonyhax-tpl-16kb.mcs +new file mode 100644 +index 0000000..bcacebd +Binary files /dev/null and b/loader/tonyhax-tpl-16kb.mcs differ +diff --git a/loader/tonyhax-tpl-24kb.mcs b/loader/tonyhax-tpl-24kb.mcs +new file mode 100644 +index 0000000..ed08bc6 +Binary files /dev/null and b/loader/tonyhax-tpl-24kb.mcs differ +diff --git a/loader/tonyhax-tpl-32kb.mcs b/loader/tonyhax-tpl-32kb.mcs +new file mode 100644 +index 0000000..0aab208 +Binary files /dev/null and b/loader/tonyhax-tpl-32kb.mcs differ +diff --git a/loader/tonyhax-tpl-ff9-16kb.mcs b/loader/tonyhax-tpl-ff9-16kb.mcs +new file mode 100644 +index 0000000..e6051f8 +Binary files /dev/null and b/loader/tonyhax-tpl-ff9-16kb.mcs differ +diff --git a/loader/tonyhax-tpl-ff9-24kb.mcs b/loader/tonyhax-tpl-ff9-24kb.mcs +new file mode 100644 +index 0000000..660761e +Binary files /dev/null and b/loader/tonyhax-tpl-ff9-24kb.mcs differ +diff --git a/loader/tonyhax-tpl-ff9-32kb.mcs b/loader/tonyhax-tpl-ff9-32kb.mcs +new file mode 100644 +index 0000000..4001e74 +Binary files /dev/null and b/loader/tonyhax-tpl-ff9-32kb.mcs differ +diff --git a/tmp/og-tonyhax.O0E/loader/tonyhax-tpl.mcs b/loader/tonyhax-tpl-original.mcs +similarity index 100% +rename from /tmp/og-tonyhax.O0E/loader/tonyhax-tpl.mcs +rename to loader/tonyhax-tpl-original.mcs +diff --git a/tmp/og-tonyhax.O0E/loader/util.h b/loader/util.h +index 6102557..db06d8a 100644 +--- a/tmp/og-tonyhax.O0E/loader/util.h ++++ b/loader/util.h +@@ -1,6 +1,6 @@ + + #pragma once +-#include ++#include + + /** + * Executes a delay of roughly the amount of specified deciseconds (1/10th of a second) diff --git a/loader/secondary.c b/loader/secondary.c index 42c6949..3789e0c 100644 --- a/loader/secondary.c +++ b/loader/secondary.c @@ -82,7 +82,7 @@ uint8_t padbuf[2][0x22]; // Joypad Buffers void try_boot_cd(); // for access in memory card features -// re-usable strings for debug_write(). Due to compiler optimzations for size already being enabled, this isn't as black and white as just put any repeated strings here. It has to make sense (and visibly reduce the size of secondary.elf) which it does in the below cases. +// re-usable strings for debug_write(). Due to compiler optimizations for size already being enabled, this isn't as black and white as just put any repeated strings here. It has to make sense (and visibly reduce the size of secondary.elf) which it does in the below cases. #define memory_card_in_slot "memory card in slot" #define press_triangle "Press TRIANGLE to" #define press_circle "Press CIRCLE to" @@ -124,6 +124,8 @@ void memory_card_and_controller_wait() { } uint8_t mcpro_check(uint8_t port){ + controller_input_start(); // For at least the ROM entrypoint, the BIOS is calling StartPad() (which also initializes memory cards and controllers) at some point before mottzilla's PS-EXE cartridge loader is taking over control and giving it to Tonyhax International. We need to call our own StartPad()and then stoppad() to close it correctly BEFORE we pull for a game id device. See https://github.com/alex-free/tonyhax/issues/69 . + controller_input_stop(); uint8_t mcpro_check_ret = MemCardPro_Ping(port); memory_card_and_controller_wait(); // docs say to wait return mcpro_check_ret; diff --git a/readme.md b/readme.md index a2ff139..d740086 100644 --- a/readme.md +++ b/readme.md @@ -51,15 +51,15 @@ Tonyhax International is a fork of the [Tonyhax](https://orca.pet/tonyhax/) "Sof ## Downloads -### Version 1.5.7 (10/7/2024) +### Version 1.5.8 (10/10/2024) -* [tonyhax-international-v1.5.7](https://github.com/alex-free/tonyhax/releases/download/v1.5.7i/tonyhax-international-v1.5.7.zip) +* [tonyhax-international-v1.5.8](https://github.com/alex-free/tonyhax/releases/download/v1.5.8i/tonyhax-international-v1.5.8.zip) ---------------------------------- Changes: -* Bug fix: v1.5.6 introduced a regression that caused some games to send an empty GameID string. This has been addressed with better boot file parsing for GameID. +* The loader now resets memory card/controller polling to a clean state during initial start up. This fixes a bug that caused the [ROM entrypoint](flashed-cheat-cart.md) to not detect GameID devices, since the BIOS is initializing the memory cards and controllers so close to the loader taking over execution from the BIOS. Thank you [@kimbapslice](https://github.com/kimbapslice) for bringing this [bug](https://github.com/alex-free/tonyhax/issues/69) to my attention and testing the fixes. ---------------------------------- diff --git a/variables-shared.mk b/variables-shared.mk index 817a7eb..f0af437 100644 --- a/variables-shared.mk +++ b/variables-shared.mk @@ -6,7 +6,7 @@ SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) # Common variables -TONYHAX_VERSION=v1.5.7 +TONYHAX_VERSION=v1.5.8 CC=mipsel-none-elf-gcc CFLAGS=-Wno-error=array-bounds -G0 -Oz -Wall -Wextra -Wno-main -EL -march=r3000 -mabi=32 -mfp32 -mno-abicalls -fno-pic -fdata-sections -ffunction-sections -fno-builtin -nostdlib -DTONYHAX_VERSION=$(TONYHAX_VERSION)