mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-21 18:19:19 +01:00
Cheats support (backend only) (#94)
<!--- Provide a general summary of your changes in the Title above --> ## Description This PR implements cheat support (patcher + engine) with a simple API to provide Action Replay/Game Shark compatible cheats (with exception of cheats that utilize GS button). API consist of a single pointer to an array of the cheats ended with a double zero entry, For example, if you want to pass these cheats to the patcher: ``` D01F9B91 0020 // Majora's Mask (USA) Inventory Editor 803FDA3F 0002 ``` Put cheats in a `uint32_t` array as such (notice last two entries are zeros): ``` uint32_t cheats[] = { 0xD01F9B91, 0x0020, 0x803FDA3F, 0x0002, 0, 0, }; ``` And pass this array as a boot parameter: `menu->boot_params->cheat_list = cheats;` <!--- Describe your changes in detail --> ## Motivation and Context To provide users with ability to run game modifications in a easy way. <!--- What does this sample do? What problem does it solve? --> <!--- If it fixes/closes/resolves an open issue, please link to the issue here --> ## How Has This Been Tested? On a SummerCart64 flashcart + assembly instructions generation verified in ares emulator via GDB. <!-- (if applicable) --> <!--- Please describe in detail how you tested your sample/changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Screenshots No screenshots <!-- (if appropriate): --> ## Types of changes <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [x] My code follows the code style of this project. - [x] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. <!--- It would be nice if you could sign off your contribution by replacing the name with your GitHub user name and GitHub email contact. --> Signed-off-by: Polprzewodnikowy <sc@mateuszfaderewski.pl>
This commit is contained in:
parent
24f49f1447
commit
01968b55db
1
Makefile
1
Makefile
@ -23,6 +23,7 @@ N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs
|
||||
SRCS = \
|
||||
main.c \
|
||||
boot/boot.c \
|
||||
boot/cheats.c \
|
||||
boot/cic.c \
|
||||
boot/reboot.S \
|
||||
flashcart/64drive/64drive_ll.c \
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
#include "boot_io.h"
|
||||
#include "boot.h"
|
||||
#include "cheats.h"
|
||||
#include "cic.h"
|
||||
#include "reboot.h"
|
||||
|
||||
|
||||
#define C0_STATUS_FR (1 << 26)
|
||||
@ -10,10 +12,6 @@
|
||||
#define C0_STATUS_CU1 (1 << 29)
|
||||
|
||||
|
||||
extern uint32_t reboot_start __attribute__((section(".text")));
|
||||
extern size_t reboot_size __attribute__((section(".text")));
|
||||
|
||||
|
||||
static io32_t *boot_get_device_base (boot_params_t *params) {
|
||||
io32_t *device_base_address = ROM_CART;
|
||||
if (params->device_type == BOOT_DEVICE_TYPE_64DD) {
|
||||
@ -22,7 +20,7 @@ static io32_t *boot_get_device_base (boot_params_t *params) {
|
||||
return device_base_address;
|
||||
}
|
||||
|
||||
static void boot_detect_cic_seed (boot_params_t *params) {
|
||||
static cic_type_t boot_detect_cic (boot_params_t *params) {
|
||||
io32_t *base = boot_get_device_base(params);
|
||||
|
||||
uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8)));
|
||||
@ -31,11 +29,17 @@ static void boot_detect_cic_seed (boot_params_t *params) {
|
||||
dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3));
|
||||
dma_wait();
|
||||
|
||||
params->cic_seed = cic_get_seed(cic_detect(ipl3));
|
||||
return cic_detect(ipl3);
|
||||
}
|
||||
|
||||
|
||||
void boot (boot_params_t *params) {
|
||||
cic_type_t cic_type = boot_detect_cic(params);
|
||||
|
||||
if (params->detect_cic_seed) {
|
||||
params->cic_seed = cic_get_seed(cic_type);
|
||||
}
|
||||
|
||||
if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
|
||||
switch (get_tv_type()) {
|
||||
case TV_PAL:
|
||||
@ -53,10 +57,6 @@ void boot (boot_params_t *params) {
|
||||
}
|
||||
}
|
||||
|
||||
if (params->detect_cic_seed) {
|
||||
boot_detect_cic_seed(params);
|
||||
}
|
||||
|
||||
C0_WRITE_STATUS(C0_STATUS_CU1 | C0_STATUS_CU0 | C0_STATUS_FR);
|
||||
|
||||
while (!(cpu_io_read(&SP->SR) & SP_SR_HALT));
|
||||
@ -123,12 +123,16 @@ void boot (boot_params_t *params) {
|
||||
cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i])));
|
||||
}
|
||||
|
||||
bool cheats_installed = cheats_install(cic_type, params->cheat_list);
|
||||
|
||||
register uint32_t skip_rdram_reset asm ("a0");
|
||||
register uint32_t boot_device asm ("s3");
|
||||
register uint32_t tv_type asm ("s4");
|
||||
register uint32_t reset_type asm ("s5");
|
||||
register uint32_t cic_seed asm ("s6");
|
||||
register uint32_t version asm ("s7");
|
||||
|
||||
skip_rdram_reset = cheats_installed;
|
||||
boot_device = (params->device_type & 0x01);
|
||||
tv_type = (params->tv_type & 0x03);
|
||||
reset_type = BOOT_RESET_TYPE_COLD;
|
||||
@ -141,6 +145,7 @@ void boot (boot_params_t *params) {
|
||||
asm volatile (
|
||||
"la $t3, reboot \n"
|
||||
"jr $t3 \n" ::
|
||||
[skip_rdram_reset] "r" (skip_rdram_reset),
|
||||
[boot_device] "r" (boot_device),
|
||||
[tv_type] "r" (tv_type),
|
||||
[reset_type] "r" (reset_type),
|
||||
|
@ -38,6 +38,7 @@ typedef struct {
|
||||
boot_tv_type_t tv_type;
|
||||
uint8_t cic_seed;
|
||||
bool detect_cic_seed;
|
||||
uint32_t *cheat_list;
|
||||
} boot_params_t;
|
||||
|
||||
|
||||
|
335
src/boot/cheats.c
Normal file
335
src/boot/cheats.c
Normal file
@ -0,0 +1,335 @@
|
||||
#include <libdragon.h>
|
||||
|
||||
#include "boot_io.h"
|
||||
#include "cheats.h"
|
||||
#include "vr4300_asm.h"
|
||||
|
||||
#define HIT_INVALIDATE_I ((4 << 2) | 0)
|
||||
#define HIT_WRITE_BACK_D ((6 << 2) | 1)
|
||||
|
||||
#define D_CACHE_LINE_SIZE (16)
|
||||
|
||||
#define CAUSE_IRQ_PRE_NMI (1 << 12)
|
||||
#define CAUSE_EXC_CODE_MASK (0x7C)
|
||||
#define CAUSE_EXC_CODE_WATCH (0x5C)
|
||||
|
||||
#define WATCHLO_W (1 << 0)
|
||||
|
||||
#define RELOCATED_EXCEPTION_HANDLER_ADDRESS (0x80000120)
|
||||
#define EXCEPTION_HANDLER_ADDRESS (0x80000180)
|
||||
#define PATCHER_ADDRESS (0x80700000)
|
||||
#define ENGINE_TEMPORARY_ADDRESS (PATCHER_ADDRESS + 0x10000)
|
||||
#define DEFAULT_ENGINE_ADDRESS (0x807C5C00)
|
||||
|
||||
typedef struct {
|
||||
uint8_t type;
|
||||
uint32_t address;
|
||||
uint16_t value;
|
||||
} cheat_t;
|
||||
|
||||
typedef struct {
|
||||
cheat_t main;
|
||||
cheat_t sub;
|
||||
} cheat_entry_t;
|
||||
|
||||
typedef enum {
|
||||
SPECIAL_DISABLE_EXPANSION_PAK = 0xEE,
|
||||
SPECIAL_WRITE_BYTE_ON_BOOT = 0xF0,
|
||||
SPECIAL_WRITE_SHORT_ON_BOOT = 0xF1,
|
||||
SPECIAL_SET_STORE_LOCATION = 0xFF,
|
||||
} cheat_type_special_t;
|
||||
|
||||
#define IS_WIDTH_16(t) ((t) & (1 << 0))
|
||||
#define IS_CONDITION_NOT_EQUAL(t) ((t) & (1 << 1))
|
||||
#define IS_CONDITION_GS_BUTTON(t) ((t) & (1 << 3))
|
||||
|
||||
#define IS_TYPE_REPEATER(t) ((t) == 0x50)
|
||||
#define IS_TYPE_WRITE(t) ((((t)&0xF0) == 0x80) || (((t)&0xF0) == 0xA0))
|
||||
#define IS_TYPE_CONDITIONAL(t) (((t)&0xF0) == 0xD0)
|
||||
|
||||
#define IS_DOUBLE_ENTRY(t) (IS_TYPE_CONDITIONAL(t) || IS_TYPE_REPEATER(t))
|
||||
|
||||
static bool cheats_patch_ipl3 (cic_type_t cic_type, io32_t *target) {
|
||||
uint32_t patch_offset = 0;
|
||||
uint32_t j_instruction = I_J((uint32_t)(target));
|
||||
|
||||
io32_t *ipl3 = SP_MEM->DMEM;
|
||||
|
||||
switch (cic_type) {
|
||||
case CIC_5101: patch_offset = 476; break;
|
||||
case CIC_6101:
|
||||
case CIC_7102: patch_offset = 476; break;
|
||||
case CIC_x102: patch_offset = 475; break;
|
||||
case CIC_x103: patch_offset = 472; break;
|
||||
case CIC_x105: patch_offset = 499; break;
|
||||
case CIC_x106: patch_offset = 488; break;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
// NOTE: Check for "jr $t1" instruction
|
||||
// Libdragon IPL3 could be brute-force signed with any retail
|
||||
// CIC seed and checksum, and we support only retail libultra IPL3
|
||||
if (cpu_io_read(&ipl3[patch_offset]) != I_JR(REG_T1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (cic_type) {
|
||||
case CIC_x105:
|
||||
// NOTE: This disables game code checksum verification
|
||||
cpu_io_write(&ipl3[486], I_NOP());
|
||||
break;
|
||||
|
||||
case CIC_x106:
|
||||
// NOTE: CIC x106 IPL3 is partially scrambled
|
||||
j_instruction ^= 0x8188764A;
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
cpu_io_write(&ipl3[patch_offset], j_instruction);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cheats_get_next (uint32_t **cheat_list, cheat_entry_t *cheat) {
|
||||
cheat_t *c = &cheat->main;
|
||||
cheat->sub.type = 0;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
uint32_t raw[2] = {(*cheat_list)[0], (*cheat_list)[1]};
|
||||
|
||||
(*cheat_list) += 2;
|
||||
|
||||
if ((raw[0] == 0) && (raw[1] == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
c->type = ((raw[0] >> 24) & 0xFF);
|
||||
c->address = (raw[0] & 0xA07FFFFF);
|
||||
c->value = (raw[1] & 0xFFFF);
|
||||
|
||||
if (!IS_DOUBLE_ENTRY(c->type)) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = &cheat->sub;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static io32_t *cheats_get_engine_address (uint32_t *cheat_list) {
|
||||
cheat_entry_t cheat;
|
||||
while (cheats_get_next(&cheat_list, &cheat)) {
|
||||
if (cheat.main.type == SPECIAL_SET_STORE_LOCATION) {
|
||||
return (io32_t *)(cheat.main.address & 0x807FFFFF);
|
||||
}
|
||||
}
|
||||
return (io32_t *)(DEFAULT_ENGINE_ADDRESS);
|
||||
}
|
||||
|
||||
static void cheats_update_cache (volatile void *start, volatile void *end) {
|
||||
data_cache_hit_writeback(start, (end - start));
|
||||
inst_cache_hit_invalidate(start, (end - start));
|
||||
}
|
||||
|
||||
bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list) {
|
||||
if (!cheat_list) {
|
||||
return false;
|
||||
}
|
||||
|
||||
io32_t *engine_start = (io32_t *)(ENGINE_TEMPORARY_ADDRESS);
|
||||
io32_t *engine_p = engine_start;
|
||||
|
||||
io32_t *patcher_start = (io32_t *)(PATCHER_ADDRESS);
|
||||
io32_t *patcher_p = patcher_start;
|
||||
|
||||
if (cheats_patch_ipl3(cic_type, patcher_start)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
io32_t *final_engine_address = cheats_get_engine_address(cheat_list);
|
||||
|
||||
// Original watch exception handler code written by Jay Oster 'Parasyte'
|
||||
// https://github.com/parasyte/alt64/blob/master/utils.c#L1024-L1054
|
||||
|
||||
uint32_t ori_placeholder_instruction = I_ORI(REG_ZERO, REG_K0, A_OFFSET(RELOCATED_EXCEPTION_HANDLER_ADDRESS));
|
||||
uint32_t ori_placeholder_address = (uint32_t)(final_engine_address + 20);
|
||||
|
||||
// Load cause register
|
||||
*engine_p++ = I_MFC0(REG_K0, C0_REG_CAUSE);
|
||||
|
||||
// Disable watch exception when reset button is pressed
|
||||
*engine_p++ = I_ANDI(REG_K1, REG_K0, CAUSE_IRQ_PRE_NMI);
|
||||
*engine_p++ = I_BNEL(REG_K1, REG_ZERO, 1);
|
||||
*engine_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_LO);
|
||||
|
||||
// Check if watch exception ocurred, if yes then proceed to relocate the game exception handler
|
||||
*engine_p++ = I_ANDI(REG_K0, REG_K0, CAUSE_EXC_CODE_MASK);
|
||||
*engine_p++ = I_ORI(REG_K1, REG_ZERO, CAUSE_EXC_CODE_WATCH);
|
||||
*engine_p++ = I_BNE(REG_K0, REG_K1, 15); // Skips to after the 'eret' instruction
|
||||
|
||||
// Extract base register number from the store instruction
|
||||
*engine_p++ = I_MFC0(REG_K1, C0_REG_EPC);
|
||||
*engine_p++ = I_LW(REG_K1, 0, REG_K1);
|
||||
*engine_p++ = I_LUI(REG_K0, 0x03E0);
|
||||
*engine_p++ = I_AND(REG_K1, REG_K0, REG_K1);
|
||||
*engine_p++ = I_SRL(REG_K1, REG_K1, 5);
|
||||
|
||||
// Update create final instruction and update its target register number
|
||||
*engine_p++ = I_LUI(REG_K0, ori_placeholder_instruction >> 16);
|
||||
*engine_p++ = I_ORI(REG_K0, REG_K0, ori_placeholder_instruction);
|
||||
*engine_p++ = I_OR(REG_K0, REG_K0, REG_K1);
|
||||
|
||||
// Write created instruction into placeholder
|
||||
*engine_p++ = I_LUI(REG_K1, A_BASE(ori_placeholder_address));
|
||||
*engine_p++ = I_SW(REG_K0, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||
|
||||
// Force write and instruction cache invalidation
|
||||
*engine_p++ = I_CACHE(HIT_WRITE_BACK_D, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||
*engine_p++ = I_CACHE(HIT_INVALIDATE_I, A_OFFSET(ori_placeholder_address), REG_K1);
|
||||
|
||||
// Load address base and execute created instruction
|
||||
*engine_p++ = I_LUI(REG_K0, A_BASE(RELOCATED_EXCEPTION_HANDLER_ADDRESS));
|
||||
*engine_p++ = I_NOP();
|
||||
|
||||
// Return from the exception
|
||||
*engine_p++ = I_ERET();
|
||||
|
||||
cheat_entry_t cheat;
|
||||
|
||||
while (cheats_get_next(&cheat_list, &cheat)) {
|
||||
cheat_t *c = &cheat.main;
|
||||
|
||||
if (IS_TYPE_REPEATER(c->type)) {
|
||||
if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = ((c->address >> 8) & 0xFF);
|
||||
int step = (c->address & 0xFF);
|
||||
int16_t increment = (int16_t)(c->value);
|
||||
|
||||
c = &cheat.sub;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||
*engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||
|
||||
c->address += step;
|
||||
c->value += increment;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IS_TYPE_CONDITIONAL(c->type)) {
|
||||
if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||
*engine_p++ = IS_WIDTH_16(c->type) ? I_LHU(REG_K0, A_OFFSET(c->address), REG_K0)
|
||||
: I_LBU(REG_K0, A_OFFSET(c->address), REG_K0);
|
||||
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value & (IS_WIDTH_16(c->type) ? 0xFFFF : 0xFF));
|
||||
*engine_p++ = IS_CONDITION_NOT_EQUAL(c->type) ? I_BEQ(REG_K0, REG_K1, 3) : I_BNE(REG_K0, REG_K1, 3);
|
||||
|
||||
c = &cheat.sub;
|
||||
}
|
||||
|
||||
if (IS_TYPE_WRITE(c->type)) {
|
||||
if (IS_CONDITION_GS_BUTTON(c->type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*engine_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||
*engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||
*engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c->type) {
|
||||
case SPECIAL_WRITE_BYTE_ON_BOOT:
|
||||
case SPECIAL_WRITE_SHORT_ON_BOOT: {
|
||||
*patcher_p++ = I_LUI(REG_K0, A_BASE(c->address));
|
||||
*patcher_p++ = I_ORI(REG_K1, REG_ZERO, c->value);
|
||||
*patcher_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0)
|
||||
: I_SB(REG_K1, A_OFFSET(c->address), REG_K0);
|
||||
break;
|
||||
}
|
||||
case SPECIAL_DISABLE_EXPANSION_PAK: {
|
||||
*patcher_p++ = I_LUI(REG_K0, 0xA000);
|
||||
*patcher_p++ = I_LUI(REG_K1, 0x0040);
|
||||
*patcher_p++ = I_SW(REG_K1, 0x318, REG_K0);
|
||||
*patcher_p++ = I_SW(REG_K1, 0x3F0, REG_K0);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
*engine_p++ = I_J(RELOCATED_EXCEPTION_HANDLER_ADDRESS);
|
||||
*engine_p++ = I_NOP();
|
||||
|
||||
uint32_t j_engine_from_handler = I_J((uint32_t)(final_engine_address));
|
||||
|
||||
// Copy engine to the final location
|
||||
*patcher_p++ = I_LUI(REG_T3, A_BASE((uint32_t)(engine_start)));
|
||||
*patcher_p++ = I_ADDIU(REG_T3, REG_T3, A_OFFSET((uint32_t)(engine_start)));
|
||||
|
||||
*patcher_p++ = I_LUI(REG_T4, A_BASE((uint32_t)(engine_p)));
|
||||
*patcher_p++ = I_ADDIU(REG_T4, REG_T4, A_OFFSET((uint32_t)(engine_p)));
|
||||
|
||||
*patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address)));
|
||||
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address)));
|
||||
|
||||
*patcher_p++ = I_ORI(REG_T6, REG_ZERO, 0);
|
||||
|
||||
*patcher_p++ = I_LW(REG_K1, 0, REG_T3);
|
||||
*patcher_p++ = I_SW(REG_K1, 0, REG_T5);
|
||||
*patcher_p++ = I_ADDIU(REG_T3, REG_T3, 4);
|
||||
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, 4);
|
||||
*patcher_p++ = I_BNE(REG_T3, REG_T4, -5);
|
||||
*patcher_p++ = I_ADDIU(REG_T6, REG_T6, 4);
|
||||
|
||||
// Force write and invalidate instruction cache
|
||||
*patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address)));
|
||||
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address)));
|
||||
|
||||
*patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_T5);
|
||||
*patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_T5);
|
||||
*patcher_p++ = I_ADDIU(REG_T6, REG_T6, -D_CACHE_LINE_SIZE);
|
||||
*patcher_p++ = I_BGTZ(REG_T6, -4);
|
||||
*patcher_p++ = I_ADDIU(REG_T5, REG_T5, D_CACHE_LINE_SIZE);
|
||||
|
||||
// Write jump instruction to the exception handler
|
||||
*patcher_p++ = I_LUI(REG_K0, A_BASE(EXCEPTION_HANDLER_ADDRESS));
|
||||
*patcher_p++ = I_ADDIU(REG_K0, REG_K0, A_OFFSET(EXCEPTION_HANDLER_ADDRESS));
|
||||
|
||||
*patcher_p++ = I_LUI(REG_K1, j_engine_from_handler >> 16);
|
||||
*patcher_p++ = I_ORI(REG_K1, REG_K1, j_engine_from_handler);
|
||||
*patcher_p++ = I_SW(REG_K1, 0, REG_K0);
|
||||
*patcher_p++ = I_SW(REG_ZERO, 4, REG_K0);
|
||||
|
||||
*patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_K0);
|
||||
*patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_K0);
|
||||
|
||||
// Set watch exception on address 0x80000180
|
||||
*patcher_p++ = I_ORI(REG_K1, REG_ZERO, EXCEPTION_HANDLER_ADDRESS | WATCHLO_W);
|
||||
*patcher_p++ = I_MTC0(REG_K1, C0_REG_WATCH_LO);
|
||||
*patcher_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_HI);
|
||||
|
||||
// Jump back to the game code
|
||||
*patcher_p++ = I_JR(REG_T1);
|
||||
*patcher_p++ = I_NOP();
|
||||
|
||||
cheats_update_cache(engine_start, engine_p);
|
||||
cheats_update_cache(patcher_start, patcher_p);
|
||||
|
||||
return true;
|
||||
}
|
10
src/boot/cheats.h
Normal file
10
src/boot/cheats.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef CHEATS_H__
|
||||
#define CHEATS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cic.h"
|
||||
|
||||
bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list);
|
||||
|
||||
#endif
|
@ -1,3 +1,6 @@
|
||||
#include "reboot.h"
|
||||
|
||||
|
||||
#define IPL3_ENTRY 0xA4000040
|
||||
#define REBOOT_ADDRESS 0xA4001000
|
||||
#define STACK_ADDRESS 0xA4001FF0
|
||||
@ -34,9 +37,10 @@ reboot_entry:
|
||||
|
||||
li $sp, STACK_ADDRESS
|
||||
|
||||
reset_rdram:
|
||||
bnez $s5, reset_rdram_skip
|
||||
bnez $a0, reset_rdram_skip # Skip when cheats are enabled
|
||||
bnez $s5, reset_rdram_skip # Skip when reset type is set to NMI
|
||||
|
||||
reset_rdram:
|
||||
li $t0, RI_ADDRESS
|
||||
|
||||
sw $zero, RI_REFRESH($t0)
|
||||
|
17
src/boot/reboot.h
Normal file
17
src/boot/reboot.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef REBOOT_H__
|
||||
#define REBOOT_H__
|
||||
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
extern uint32_t reboot_start __attribute__((section(".text")));
|
||||
extern size_t reboot_size __attribute__((section(".text")));
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
397
src/boot/vr4300_asm.h
Normal file
397
src/boot/vr4300_asm.h
Normal file
@ -0,0 +1,397 @@
|
||||
#ifndef VR4300_ASM_H__
|
||||
#define VR4300_ASM_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef union {
|
||||
uint32_t raw;
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t rs : 5;
|
||||
uint32_t rt : 5;
|
||||
uint32_t imm : 16;
|
||||
} i_type;
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t target : 26;
|
||||
} j_type;
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t rs : 5;
|
||||
uint32_t rt : 5;
|
||||
uint32_t rd : 5;
|
||||
uint32_t sa : 5;
|
||||
uint32_t funct : 6;
|
||||
} r_type;
|
||||
|
||||
struct {
|
||||
uint32_t op : 6;
|
||||
uint32_t co : 1;
|
||||
uint32_t funct : 25;
|
||||
} c_type;
|
||||
} vr4300_instruction_t;
|
||||
|
||||
typedef enum {
|
||||
OP_SPECIAL,
|
||||
OP_REGIMM,
|
||||
OP_J,
|
||||
OP_JAL,
|
||||
OP_BEQ,
|
||||
OP_BNE,
|
||||
OP_BLEZ,
|
||||
OP_BGTZ,
|
||||
OP_ADDI,
|
||||
OP_ADDIU,
|
||||
OP_SLTI,
|
||||
OP_SLTIU,
|
||||
OP_ANDI,
|
||||
OP_ORI,
|
||||
OP_XORI,
|
||||
OP_LUI,
|
||||
OP_COP0,
|
||||
OP_COP1,
|
||||
OP_COP2,
|
||||
__OP_RESERVED_19,
|
||||
OP_BEQL,
|
||||
OP_BNEL,
|
||||
OP_BLEZL,
|
||||
OP_BGTZL,
|
||||
OP_DADDI,
|
||||
OP_DADDIU,
|
||||
OP_LDL,
|
||||
OP_LDR,
|
||||
__OP_RESERVED_28,
|
||||
__OP_RESERVED_29,
|
||||
__OP_RESERVED_30,
|
||||
__OP_RESERVED_31,
|
||||
OP_LB,
|
||||
OP_LH,
|
||||
OP_LWL,
|
||||
OP_LW,
|
||||
OP_LBU,
|
||||
OP_LHU,
|
||||
OP_LWR,
|
||||
OP_LWU,
|
||||
OP_SB,
|
||||
OP_SH,
|
||||
OP_SWL,
|
||||
OP_SW,
|
||||
OP_SDL,
|
||||
OP_SDR,
|
||||
OP_SWR,
|
||||
OP_CACHE,
|
||||
OP_LL,
|
||||
OP_LWC1,
|
||||
OP_LWC2,
|
||||
__OP_RESERVED_51,
|
||||
OP_LLD,
|
||||
OP_LDC1,
|
||||
OP_LDC2,
|
||||
OP_LD,
|
||||
OP_SC,
|
||||
OP_SWC1,
|
||||
OP_SWC2,
|
||||
__OP_RESERVED_59,
|
||||
OP_SCD,
|
||||
OP_SDC1,
|
||||
OP_SDC2,
|
||||
OP_SD,
|
||||
} vr4300_op_t;
|
||||
|
||||
typedef enum {
|
||||
FUNCT_SSL,
|
||||
__FUNCT_RESERVED_1,
|
||||
FUNCT_SRL,
|
||||
FUNCT_SRA,
|
||||
FUNCT_SLLV,
|
||||
__FUNCT_RESERVED_5,
|
||||
FUNCT_SRLV,
|
||||
FUNCT_SRAV,
|
||||
FUNCT_JR,
|
||||
FUNCT_JALR,
|
||||
__FUNCT_RESERVED_10,
|
||||
__FUNCT_RESERVED_11,
|
||||
FUNCT_SYSCALL,
|
||||
FUNCT_BREAK,
|
||||
__FUNCT_RESERVED_14,
|
||||
FUNCT_SYNC,
|
||||
FUNCT_MFHI,
|
||||
FUNCT_MTHI,
|
||||
FUNCT_MFLO,
|
||||
FUNCT_MTLO,
|
||||
FUNCT_DSLLV,
|
||||
__FUNCT_RESERVED_21,
|
||||
FUNCT_DSRLV,
|
||||
FUNCT_DSRAV,
|
||||
FUNCT_MULT,
|
||||
FUNCT_MULTU,
|
||||
FUNCT_DIV,
|
||||
FUNCT_DIVU,
|
||||
FUNCT_DMULT,
|
||||
FUNCT_DMULTU,
|
||||
FUNCT_DDIV,
|
||||
FUNCT_DDIVU,
|
||||
FUNCT_ADD,
|
||||
FUNCT_ADDU,
|
||||
FUNCT_SUB,
|
||||
FUNCT_SUBU,
|
||||
FUNCT_AND,
|
||||
FUNCT_OR,
|
||||
FUNCT_XOR,
|
||||
FUNCT_NOR,
|
||||
__FUNCT_RESERVED_40,
|
||||
__FUNCT_RESERVED_41,
|
||||
FUNCT_SLT,
|
||||
FUNCT_SLTU,
|
||||
FUNCT_DADD,
|
||||
FUNCT_DADDU,
|
||||
FUNCT_DSUB,
|
||||
FUNCT_DSUBU,
|
||||
FUNCT_TGE,
|
||||
FUNCT_TGEU,
|
||||
FUNCT_TLT,
|
||||
FUNCT_TLTU,
|
||||
FUNCT_TEQ,
|
||||
__FUNCT_RESERVED_53,
|
||||
FUNCT_TNE,
|
||||
__FUNCT_RESERVED_55,
|
||||
FUNCT_DSLL,
|
||||
__FUNCT_RESERVED_57,
|
||||
FUNCT_DSRL,
|
||||
FUNCT_DSRA,
|
||||
FUNCT_DSLL32,
|
||||
__FUNCT_RESERVED_61,
|
||||
FUNCT_DSRL32,
|
||||
FUNCT_DSRA32,
|
||||
} vr4300_funct_t;
|
||||
|
||||
typedef enum {
|
||||
REGIMM_BLTZ,
|
||||
REGIMM_BGEZ,
|
||||
REGIMM_BLTZL,
|
||||
REGIMM_BGEZL,
|
||||
__REGIMM_RESERVED_4,
|
||||
__REGIMM_RESERVED_5,
|
||||
__REGIMM_RESERVED_6,
|
||||
__REGIMM_RESERVED_7,
|
||||
REGIMM_TGEI,
|
||||
REGIMM_TGEIU,
|
||||
REGIMM_TLTI,
|
||||
REGIMM_TLTIU,
|
||||
REGIMM_TEQI,
|
||||
__REGIMM_RESERVED_13,
|
||||
REGIMM_TNEI,
|
||||
__REGIMM_RESERVED_15,
|
||||
REGIMM_BLTZAL,
|
||||
REGIMM_BGEZAL,
|
||||
REGIMM_BLTZALL,
|
||||
REGIMM_BGEZALL,
|
||||
__REGIMM_RESERVED_20,
|
||||
__REGIMM_RESERVED_21,
|
||||
__REGIMM_RESERVED_22,
|
||||
__REGIMM_RESERVED_23,
|
||||
__REGIMM_RESERVED_24,
|
||||
__REGIMM_RESERVED_25,
|
||||
__REGIMM_RESERVED_26,
|
||||
__REGIMM_RESERVED_27,
|
||||
__REGIMM_RESERVED_28,
|
||||
__REGIMM_RESERVED_29,
|
||||
__REGIMM_RESERVED_30,
|
||||
__REGIMM_RESERVED_31,
|
||||
} vr4300_regimm_t;
|
||||
|
||||
typedef enum {
|
||||
REG_ZERO,
|
||||
REG_AT,
|
||||
REG_V0,
|
||||
REG_V1,
|
||||
REG_A0,
|
||||
REG_A1,
|
||||
REG_A2,
|
||||
REG_A3,
|
||||
REG_T0,
|
||||
REG_T1,
|
||||
REG_T2,
|
||||
REG_T3,
|
||||
REG_T4,
|
||||
REG_T5,
|
||||
REG_T6,
|
||||
REG_T7,
|
||||
REG_S0,
|
||||
REG_S1,
|
||||
REG_S2,
|
||||
REG_S3,
|
||||
REG_S4,
|
||||
REG_S5,
|
||||
REG_S6,
|
||||
REG_S7,
|
||||
REG_T8,
|
||||
REG_T9,
|
||||
REG_K0,
|
||||
REG_K1,
|
||||
REG_GP,
|
||||
REG_SP,
|
||||
REG_FP,
|
||||
REG_RA,
|
||||
} vr4300_reg_t;
|
||||
|
||||
typedef enum {
|
||||
C0_REG_INDEX,
|
||||
C0_REG_RANDOM,
|
||||
C0_REG_ENTRY_LO_0,
|
||||
C0_REG_ENTRY_LO_1,
|
||||
C0_REG_CONTEXT,
|
||||
C0_REG_PAGE_MASK,
|
||||
C0_REG_WIRED,
|
||||
__C0_REG_RESERVED_7,
|
||||
C0_REG_BAD_V_ADDR,
|
||||
C0_REG_COUNT,
|
||||
C0_REG_ENTRY_HI,
|
||||
C0_REG_COMPARE,
|
||||
C0_REG_STATUS,
|
||||
C0_REG_CAUSE,
|
||||
C0_REG_EPC,
|
||||
C0_REG_PR_ID,
|
||||
C0_REG_CONFIG,
|
||||
C0_REG_LL_ADDR,
|
||||
C0_REG_WATCH_LO,
|
||||
C0_REG_WATCH_HI,
|
||||
C0_REG_X_CONTEXT,
|
||||
__C0_REG_RESERVED_21,
|
||||
__C0_REG_RESERVED_22,
|
||||
__C0_REG_RESERVED_23,
|
||||
__C0_REG_RESERVED_24,
|
||||
__C0_REG_RESERVED_25,
|
||||
C0_REG_PARITY_ERROR,
|
||||
C0_REG_CACHE_ERROR,
|
||||
C0_REG_TAG_LO,
|
||||
C0_REG_TAG_HI,
|
||||
C0_REG_ERROR_EPC,
|
||||
__C0_REG_RESERVED_31,
|
||||
} vr4300_c0_reg_t;
|
||||
|
||||
typedef enum {
|
||||
COPZ_RS_MF,
|
||||
COPZ_RS_DMF,
|
||||
COPZ_RS_CF,
|
||||
__COPZ_RS_RESERVED_3,
|
||||
COPZ_RS_MT,
|
||||
COPZ_RS_DMT,
|
||||
COPZ_RS_CT,
|
||||
__COPZ_RS_RESERVED_7,
|
||||
COPZ_RS_BC,
|
||||
__COPZ_RS_RESERVED_9,
|
||||
__COPZ_RS_RESERVED_10,
|
||||
__COPZ_RS_RESERVED_11,
|
||||
__COPZ_RS_RESERVED_12,
|
||||
__COPZ_RS_RESERVED_13,
|
||||
__COPZ_RS_RESERVED_14,
|
||||
__COPZ_RS_RESERVED_15,
|
||||
} vr4300_copz_rs_t;
|
||||
|
||||
typedef enum {
|
||||
__C0_FUNCT_RESERVED_0,
|
||||
C0_FUNCT_TLBR,
|
||||
C0_FUNCT_TLBWI,
|
||||
__C0_FUNCT_RESERVED_3,
|
||||
__C0_FUNCT_RESERVED_4,
|
||||
__C0_FUNCT_RESERVED_5,
|
||||
C0_FUNCT_TLBWR,
|
||||
__C0_FUNCT_RESERVED_7,
|
||||
C0_FUNCT_TLBP,
|
||||
__C0_FUNCT_RESERVED_9,
|
||||
__C0_FUNCT_RESERVED_10,
|
||||
__C0_FUNCT_RESERVED_11,
|
||||
__C0_FUNCT_RESERVED_12,
|
||||
__C0_FUNCT_RESERVED_13,
|
||||
__C0_FUNCT_RESERVED_14,
|
||||
__C0_FUNCT_RESERVED_15,
|
||||
__C0_FUNCT_RESERVED_16,
|
||||
__C0_FUNCT_RESERVED_17,
|
||||
__C0_FUNCT_RESERVED_18,
|
||||
__C0_FUNCT_RESERVED_19,
|
||||
__C0_FUNCT_RESERVED_20,
|
||||
__C0_FUNCT_RESERVED_21,
|
||||
__C0_FUNCT_RESERVED_22,
|
||||
__C0_FUNCT_RESERVED_23,
|
||||
C0_FUNCT_ERET,
|
||||
__C0_FUNCT_RESERVED_25,
|
||||
__C0_FUNCT_RESERVED_26,
|
||||
__C0_FUNCT_RESERVED_27,
|
||||
__C0_FUNCT_RESERVED_28,
|
||||
__C0_FUNCT_RESERVED_29,
|
||||
__C0_FUNCT_RESERVED_30,
|
||||
__C0_FUNCT_RESERVED_31,
|
||||
__C0_FUNCT_RESERVED_32,
|
||||
__C0_FUNCT_RESERVED_33,
|
||||
__C0_FUNCT_RESERVED_34,
|
||||
__C0_FUNCT_RESERVED_35,
|
||||
__C0_FUNCT_RESERVED_36,
|
||||
__C0_FUNCT_RESERVED_37,
|
||||
__C0_FUNCT_RESERVED_38,
|
||||
__C0_FUNCT_RESERVED_39,
|
||||
__C0_FUNCT_RESERVED_40,
|
||||
__C0_FUNCT_RESERVED_41,
|
||||
__C0_FUNCT_RESERVED_42,
|
||||
__C0_FUNCT_RESERVED_43,
|
||||
__C0_FUNCT_RESERVED_44,
|
||||
__C0_FUNCT_RESERVED_45,
|
||||
__C0_FUNCT_RESERVED_46,
|
||||
__C0_FUNCT_RESERVED_47,
|
||||
__C0_FUNCT_RESERVED_48,
|
||||
__C0_FUNCT_RESERVED_49,
|
||||
__C0_FUNCT_RESERVED_50,
|
||||
__C0_FUNCT_RESERVED_51,
|
||||
__C0_FUNCT_RESERVED_52,
|
||||
__C0_FUNCT_RESERVED_53,
|
||||
__C0_FUNCT_RESERVED_54,
|
||||
__C0_FUNCT_RESERVED_55,
|
||||
__C0_FUNCT_RESERVED_56,
|
||||
__C0_FUNCT_RESERVED_57,
|
||||
__C0_FUNCT_RESERVED_58,
|
||||
__C0_FUNCT_RESERVED_59,
|
||||
__C0_FUNCT_RESERVED_60,
|
||||
__C0_FUNCT_RESERVED_61,
|
||||
__C0_FUNCT_RESERVED_62,
|
||||
__C0_FUNCT_RESERVED_63,
|
||||
} vr4300_c0_funct;
|
||||
|
||||
#define __ASM_I_INST(o, s, t, i) \
|
||||
(((vr4300_instruction_t){.i_type = {.op = (o), .rs = (s), .rt = (t), .imm = (i)&0xFFFF}}).raw)
|
||||
#define __ASM_J_INST(o, t) (((vr4300_instruction_t){.j_type = {.op = (o), .target = (t)&0x3FFFFFF}}).raw)
|
||||
#define __ASM_R_INST(o, s, t, d, a, f) \
|
||||
(((vr4300_instruction_t){.r_type = {.op = (o), .rs = (s), .rt = (t), .rd = (d), .sa = (a), .funct = (f)}}).raw)
|
||||
#define __ASM_C_INST(o, c, f) (((vr4300_instruction_t){.c_type = {.op = (o), .co = (c), .funct = (f)}}).raw)
|
||||
|
||||
#define A_OFFSET(a) ((int16_t)((a)&0xFFFF))
|
||||
#define A_BASE(a) ((uint16_t)((((a) >> 16) & 0xFFFF) + (A_OFFSET(a) < 0 ? 1 : 0)))
|
||||
|
||||
#define I_ADDIU(rt, rs, immediate) __ASM_I_INST(OP_ADDIU, rs, rt, immediate)
|
||||
#define I_AND(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_AND)
|
||||
#define I_ANDI(rt, rs, immediate) __ASM_I_INST(OP_ANDI, rs, rt, immediate)
|
||||
#define I_BEQ(rs, rt, offset) __ASM_I_INST(OP_BEQ, rs, rt, offset)
|
||||
#define I_BGTZ(rs, offset) __ASM_I_INST(OP_BGTZ, rs, 0, offset)
|
||||
#define I_BNE(rs, rt, offset) __ASM_I_INST(OP_BNE, rs, rt, offset)
|
||||
#define I_BNEL(rs, rt, offset) __ASM_I_INST(OP_BNEL, rs, rt, offset)
|
||||
#define I_CACHE(op, offset, base) __ASM_I_INST(OP_CACHE, base, op, offset)
|
||||
#define I_ERET() __ASM_C_INST(OP_COP0, 1, C0_FUNCT_ERET)
|
||||
#define I_J(target) __ASM_J_INST(OP_J, (target >> 2))
|
||||
#define I_JR(rs) __ASM_R_INST(OP_SPECIAL, rs, REG_ZERO, REG_ZERO, 0, FUNCT_JR)
|
||||
#define I_LBU(rt, offset, base) __ASM_I_INST(OP_LBU, base, rt, offset)
|
||||
#define I_LHU(rt, offset, base) __ASM_I_INST(OP_LHU, base, rt, offset)
|
||||
#define I_LUI(rt, immediate) __ASM_I_INST(OP_LUI, 0, rt, immediate)
|
||||
#define I_LW(rt, offset, base) __ASM_I_INST(OP_LW, base, rt, offset)
|
||||
#define I_MFC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MF, rt, rd, 0, 0)
|
||||
#define I_MTC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MT, rt, rd, 0, 0)
|
||||
#define I_NOP() __ASM_R_INST(OP_SPECIAL, REG_ZERO, REG_ZERO, REG_ZERO, 0, FUNCT_SSL)
|
||||
#define I_OR(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_OR)
|
||||
#define I_ORI(rt, rs, immediate) __ASM_I_INST(OP_ORI, rs, rt, immediate)
|
||||
#define I_SB(rt, offset, base) __ASM_I_INST(OP_SB, base, rt, offset)
|
||||
#define I_SH(rt, offset, base) __ASM_I_INST(OP_SH, base, rt, offset)
|
||||
#define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL)
|
||||
#define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset)
|
||||
|
||||
#endif
|
@ -71,6 +71,7 @@ static void command_reboot (menu_t *menu) {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
menu->boot_params->cheat_list = NULL;
|
||||
};
|
||||
|
||||
static void command_send_file (menu_t *menu) {
|
||||
|
@ -136,10 +136,12 @@ static void load (menu_t *menu) {
|
||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break;
|
||||
default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break;
|
||||
}
|
||||
menu->boot_params->cheat_list = NULL;
|
||||
} else {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_64DD;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
menu->boot_params->cheat_list = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +100,7 @@ static void load (menu_t *menu) {
|
||||
menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM;
|
||||
menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH;
|
||||
menu->boot_params->detect_cic_seed = true;
|
||||
menu->boot_params->cheat_list = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
@ -310,6 +310,7 @@ static void load (menu_t *menu) {
|
||||
case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break;
|
||||
default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break;
|
||||
}
|
||||
menu->boot_params->cheat_list = NULL;
|
||||
}
|
||||
|
||||
static void deinit (void) {
|
||||
|
Loading…
Reference in New Issue
Block a user