dolphin/Source/DSPSpy/main_spy.cpp
Matthew Brennan Jones ad5d28af4e Fixed issue with DSP Dump not closing files.
When dumping the two DSP ROMs to file, if it failed to open one of them, neither will be closed. It should now close any that were opened.
2014-06-23 13:08:43 -07:00

733 lines
18 KiB
C++

// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
// This is a test program for running code on the Wii DSP, with full control over input
// and automatic compare with output. VERY useful for figuring out what those little
// ops actually do.
// It's very unpolished though
// Use Dolphin's dsptool to generate a new dsp_code.h.
// Originally written by duddie and modified by FIRES. Then further modified by ector.
#include <gccore.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <network.h>
#include <ogcsys.h>
#include <time.h>
#include <fat.h>
#include <fcntl.h>
#include <debug.h>
#include <unistd.h>
#include <ogc/color.h>
#include <ogc/consol.h>
#ifdef _MSC_VER
// Just for easy looking :)
#define HW_RVL //HW_DOL
#endif
#ifdef HW_RVL
#include <wiiuse/wpad.h>
#include <sdcard/wiisd_io.h>
#endif
#include "ConsoleHelper.h"
#include "dspregs.h"
// This is where the DSP binary is.
#include "dsp_code.h"
#include "mem_dump.h"
// Communication with the real DSP and with the DSP emulator.
#include "dsp_interface.h"
#include "real_dsp.h"
// #include "virtual_dsp.h"
// Used for communications with the DSP, such as dumping registers etc.
u16 dspbuffer[16 * 1024] __attribute__ ((aligned (0x4000)));
static void *xfb = nullptr;
void (*reboot)() = (void(*)())0x80001800;
GXRModeObj *rmode;
static vu16* const _dspReg = (u16*)0xCC005000;
u16 *dspbufP;
u16 *dspbufC;
u32 *dspbufU;
u16 dspreg_in[32] = {
0x0410, 0x0510, 0x0610, 0x0710, 0x0810, 0x0910, 0x0a10, 0x0b10,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0855, 0x0966, 0x0a77, 0x0b88,
0x0014, 0xfff5, 0x00ff, 0x2200, 0x0000, 0x0000, 0x0000, 0x0000,
0x0003, 0x0004, 0x8000, 0x000C, 0x0007, 0x0008, 0x0009, 0x000a,
}; /// ax_h_1 ax_h_1
/* ttt ?
u16 dspreg_in[32] = {
0x0e4c, 0x03c0, 0x0bd9, 0x06a3, 0x0c06, 0x0240, 0x0010, 0x0ecc,
0x0000, 0x0000, 0x0000, 0x0000, 0x0322, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x00ff, 0x1b41, 0x0000, 0x0040, 0x00ff, 0x0000,
0x1000, 0x96cc, 0x0000, 0x0000, 0x3fc0, 0x96cc, 0x0000, 0x0000,
}; */
// if i set bit 0x4000 of SR my tests crashes :(
/*
// zelda 0x00da
u16 dspreg_in[32] = {
0x0a50, 0x0ca2, 0x04f8, 0x0ab0, 0x8039, 0x0000, 0x0000, 0x0000,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x03d1, 0x0000, 0x0418, 0x0002, // r08 must have a value ... no idea why (ector: it's the looped addressing regs)
0x0000, 0x0000, 0x00ff, 0x1804, 0xdb70, 0x4ddb, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0xde6d, 0x0000, 0x0000, 0x0000, 0x004e,
};*/
u16 dspreg_out[1000][32];
/*
// gba ucode dmas result here
u32 SecParams_out[2] __attribute__ ((aligned (0x20))) = {
0x11223344, // key
0x55667788 // bootinfo
};
// ripped from demo
u32 SecParams_in[8] __attribute__ ((aligned (0x20))) = {
0xDB967E0F, // key from gba
0x00000002,
0x00000002,
0x00001078,
(u32)SecParams_out, //0x80075060, // ptr to receiving buffer
// padding?
0x00000000,
0x00000000,
0x00000000
};
*/
// UI (interactive register editing)
u32 ui_mode;
enum {
UIM_SEL = 1,
UIM_EDIT_REG = 2,
UIM_EDIT_BIN = 4,
};
// Currently selected register.
s32 cursor_reg = 0;
// Currently selected digit.
s32 small_cursor_x;
// Value currently being edited.
u16 *reg_value;
RealDSP real_dsp;
// Currently running microcode
int curUcode = 0, runningUcode = 1;
int dsp_steps = 0;
// When comparing regs, ignore the loop stack registers.
bool regs_equal(int reg, u16 value1, u16 value2)
{
if (reg >= DSP_REG_ST0 && reg <= DSP_REG_ST3)
return true;
else
return value1 == value2;
}
void print_reg_block(int x, int y, int sel, const u16 *regs, const u16 *compare_regs)
{
for (int j = 0; j < 4 ; j++)
{
for (int i = 0; i < 8 ; i++)
{
// Do not even display the loop stack registers.
const int reg = j * 8 + i;
CON_SetColor(sel == reg ? CON_BRIGHT_YELLOW : CON_GREEN);
CON_Printf(x + j * 8, i + y, "%02x ", reg);
if (j != 1 || i < 4)
{
u8 color1 = regs_equal(reg, regs[reg], compare_regs[reg]) ? CON_BRIGHT_WHITE : CON_BRIGHT_RED;
for (int k = 0; k < 4; k++)
{
if (sel == reg && k == small_cursor_x && ui_mode == UIM_EDIT_REG)
CON_SetColor(CON_BRIGHT_CYAN);
else
CON_SetColor(color1);
CON_Printf(x + 3 + j * 8 + k, i + y, "%01x", (regs[reg] >> ((3 - k) * 4)) & 0xf);
}
}
}
}
CON_SetColor(CON_WHITE);
CON_Printf(x+2, y+9, "ACC0: %02x %04x %04x", regs[DSP_REG_ACH0]&0xff, regs[DSP_REG_ACM0], regs[DSP_REG_ACL0]);
CON_Printf(x+2, y+10, "ACC1: %02x %04x %04x", regs[DSP_REG_ACH1]&0xff, regs[DSP_REG_ACM1], regs[DSP_REG_ACL1]);
CON_Printf(x+2, y+11, "AX0: %04x %04x", regs[DSP_REG_AXH0], regs[DSP_REG_AXL0]);
CON_Printf(x+2, y+12, "AX1: %04x %04x", regs[DSP_REG_AXH1], regs[DSP_REG_AXL1]);
}
void print_regs(int _step, int _dsp_steps)
{
const u16 *regs = _step == 0 ? dspreg_in : dspreg_out[_step - 1];
const u16 *regs2 = dspreg_out[_step];
print_reg_block(0, 2, _step == 0 ? cursor_reg : -1, regs, regs2);
print_reg_block(33, 2, -1, regs2, regs);
CON_SetColor(CON_WHITE);
CON_Printf(33, 17, "%i / %i ", _step + 1, _dsp_steps);
return;
static int count = 0;
int x = 0, y = 16;
if (count > 2)
CON_Clear();
count = 0;
CON_SetColor(CON_WHITE);
for (int i = 0x0; i < 0xf70 ; i++)
{
if (dspbufC[i] != mem_dump[i])
{
CON_Printf(x, y, "%04x=%04x", i, dspbufC[i]);
count++;
x += 10;
if (x >= 60) {
x = 0;
y++;
}
}
}
CON_Printf(4, 25, "%08x", count);
}
void UpdateLastMessage(const char* msg)
{
CON_PrintRow(4, 24, msg);
}
void DumpDSP_ROMs(const u16* rom, const u16* coef)
{
#ifdef HW_RVL
char filename[260] = {0};
sprintf(filename, "sd:/dsp_rom.bin");
FILE *fROM = fopen(filename, "wb");
sprintf(filename, "sd:/dsp_coef.bin");
FILE *fCOEF = fopen(filename, "wb");
if (fROM && fCOEF)
{
fwrite(MEM_PHYSICAL_TO_K0(rom), 0x2000, 1, fROM);
fwrite(MEM_PHYSICAL_TO_K0(coef), 0x1000, 1, fCOEF);
UpdateLastMessage("DSP ROMs dumped to SD");
}
else
{
UpdateLastMessage("SD Write Error");
}
if (fROM)
fclose(fROM);
if (fCOEF)
fclose(fCOEF);
#else
// Allow to connect to gdb (dump ram... :s)
_break();
#endif
}
void ui_pad_sel(void)
{
#ifdef HW_RVL
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_RIGHT)
cursor_reg += 8;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_LEFT)
cursor_reg -= 8;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_UP)
cursor_reg--;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_DOWN)
cursor_reg++;
cursor_reg &= 0x1f;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)
{
ui_mode = UIM_EDIT_REG;
reg_value = &dspreg_in[cursor_reg];
}
#else
if (PAD_ButtonsDown(0) & PAD_BUTTON_RIGHT)
cursor_reg += 8;
if (PAD_ButtonsDown(0) & PAD_BUTTON_LEFT)
cursor_reg -= 8;
if (PAD_ButtonsDown(0) & PAD_BUTTON_UP)
cursor_reg--;
if (PAD_ButtonsDown(0) & PAD_BUTTON_DOWN)
cursor_reg++;
cursor_reg &= 0x1f;
if (PAD_ButtonsDown(0) & PAD_BUTTON_A)
{
ui_mode = UIM_EDIT_REG;
reg_value = &dspreg_in[cursor_reg];
}
#endif
}
void ui_pad_edit_reg(void)
{
#ifdef HW_RVL
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_RIGHT)
small_cursor_x++;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_LEFT)
small_cursor_x--;
small_cursor_x &= 0x3;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_UP)
*reg_value += 0x1 << (4 * (3 - small_cursor_x));
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_DOWN)
*reg_value -= 0x1 << (4 * (3 - small_cursor_x));
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)
ui_mode = UIM_SEL;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_1)
*reg_value = 0;
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_2)
*reg_value = 0xffff;
#else
if (PAD_ButtonsDown(0) & PAD_BUTTON_RIGHT)
small_cursor_x++;
if (PAD_ButtonsDown(0) & PAD_BUTTON_LEFT)
small_cursor_x--;
small_cursor_x &= 0x3;
if (PAD_ButtonsDown(0) & PAD_BUTTON_UP)
*reg_value += 0x1 << (4 * (3 - small_cursor_x));
if (PAD_ButtonsDown(0) & PAD_BUTTON_DOWN)
*reg_value -= 0x1 << (4 * (3 - small_cursor_x));
if (PAD_ButtonsDown(0) & PAD_BUTTON_A)
ui_mode = UIM_SEL;
if (PAD_ButtonsDown(0) & PAD_BUTTON_X)
*reg_value = 0;
if (PAD_ButtonsDown(0) & PAD_BUTTON_Y)
*reg_value = 0xffff;
#endif
}
void handle_dsp_mail(void)
{
// Should put a loop around this too.
if (DSP_CheckMailFrom())
{
u32 mail = DSP_ReadMailFrom();
if (mail == 0x8071feed)
{
// DSP ready for task. Let's send one.
// First, prepare data.
for (int n = 0 ; n < 32 ; n++)
dspbufC[0x00 + n] = dspreg_in[n];
DCFlushRange(dspbufC, 0x2000);
// Then send the code.
DCFlushRange((void *)dsp_code[curUcode], 0x2000);
// DMA ucode to iram base, entry point is just after exception vectors...0x10
// NOTE: for any ucode made by dsptool, the block length will be 8191
real_dsp.SendTask((void *)MEM_VIRTUAL_TO_PHYSICAL(dsp_code[curUcode]), 0, sizeof(dsp_code[curUcode])-1, 0x10);
runningUcode = curUcode + 1;
// Clear exception status since we've loaded a new ucode
CON_BlankRow(25);
}
else if ((mail & 0xffff0000) == 0x8bad0000)
{
// dsp_base.inc is reporting an exception happened
CON_PrintRow(4, 25, "%s caused exception %x at step %i", UCODE_NAMES[curUcode], mail & 0xff, dsp_steps);
}
else if (mail == 0x8888dead)
{
// Send memory dump (dsp dram from someone's cube?)
// not really sure why this is important - I guess just to try to keep tests predictable
u16* tmpBuf = (u16 *)MEM_VIRTUAL_TO_PHYSICAL(mem_dump);
while (real_dsp.CheckMailTo());
real_dsp.SendMailTo((u32)tmpBuf);
while (real_dsp.CheckMailTo());
}
else if (mail == 0x8888beef)
{
// Provide register base to dsp (if using dsp_base.inc, it will dma them to the correct place)
while (real_dsp.CheckMailTo());
real_dsp.SendMailTo((u32)dspbufP);
while (real_dsp.CheckMailTo());
}
else if (mail == 0x8888feeb)
{
// We got a stepful of registers.
DCInvalidateRange(dspbufC, 0x2000);
for (int i = 0 ; i < 32 ; i++)
dspreg_out[dsp_steps][i] = dspbufC[0xf80 + i];
dsp_steps++;
while (real_dsp.CheckMailTo());
real_dsp.SendMailTo(0x8000dead);
while (real_dsp.CheckMailTo());
}
// ROM dumping mails
else if (mail == 0x8888c0de)
{
// DSP has copied irom to its dram...send address so it can dma it back
while (real_dsp.CheckMailTo());
real_dsp.SendMailTo((u32)dspbufP);
while (real_dsp.CheckMailTo());
}
else if (mail == 0x8888da7a)
{
// DSP has copied coef to its dram...send address so it can dma it back
while (real_dsp.CheckMailTo());
real_dsp.SendMailTo((u32)&dspbufP[0x1000]);
while (real_dsp.CheckMailTo());
// Now we can do something useful with the buffer :)
DumpDSP_ROMs(dspbufP, &dspbufP[0x1000]);
}
// SDK status mails
/*
// GBA ucode
else if (mail == 0xdcd10000) // DSP_INIT
{
real_dsp.SendMailTo(0xabba0000);
while (real_dsp.CheckMailTo());
DCFlushRange(SecParams_in, sizeof(SecParams_in));
CON_PrintRow(4, 25, "SecParams_out = %x", SecParams_in[4]);
real_dsp.SendMailTo((u32)SecParams_in);
while (real_dsp.CheckMailTo());
}
else if (mail == 0xdcd10003) // DSP_DONE
{
real_dsp.SendMailTo(0xcdd1babe); // custom mail to tell dsp to halt (calls end_of_test)
while (real_dsp.CheckMailTo());
DCInvalidateRange(SecParams_out, sizeof(SecParams_out));
CON_PrintRow(4, 26, "SecParams_out: %08x %08x",
SecParams_out[0], SecParams_out[1]);
}
*/
CON_PrintRow(2, 1, "UCode: %d/%d %s, Last mail: %08x",
curUcode + 1, NUM_UCODES, UCODE_NAMES[curUcode], mail);
}
}
void dump_all_ucodes(bool fastmode)
{
char filename[260] = {0};
char temp[100];
u32 written;
sprintf(filename, "sd:/dsp_dump_all.bin");
FILE *f2 = fopen(filename, "wb");
fclose(f2);
for (int UCodeToDump = 0; UCodeToDump < NUM_UCODES; UCodeToDump++)
{
// First, change the microcode
dsp_steps = 0;
curUcode = UCodeToDump;
runningUcode = 0;
DCInvalidateRange(dspbufC, 0x2000);
DCFlushRange(dspbufC, 0x2000);
real_dsp.Reset();
VIDEO_WaitVSync();
// Loop over handling mail until we've stopped stepping
// dsp_steps-3 compensates for mails to setup the ucode
for (int steps_cache = dsp_steps-3; steps_cache <= dsp_steps; steps_cache++)
handle_dsp_mail();
VIDEO_WaitVSync();
sprintf(filename, "sd:/dsp_dump_all.bin");
FILE *f2 = fopen(filename, "ab");
if (fastmode == false)
{
// Then write microcode dump to file
sprintf(filename, "sd:/dsp_dump%d.bin", UCodeToDump);
FILE *f = fopen(filename, "wb");
if (f)
{
// First write initial regs
written = fwrite(dspreg_in, 1, 32 * 2, f);
// Then write all the dumps.
written += fwrite(dspreg_out, 1, dsp_steps * 32 * 2, f);
fclose(f);
}
else
{
UpdateLastMessage("SD Write Error");
break;
}
}
if (f2) //all in 1 dump file (extra)
{
if (UCodeToDump == 0) {
// First write initial regs
written = fwrite(dspreg_in, 1, 32 * 2, f2);
written += fwrite(dspreg_out, 1, dsp_steps * 32 * 2, f2);
}
else {
written = fwrite(dspreg_out, 1, dsp_steps * 32 * 2, f2);
}
fclose(f2);
if (UCodeToDump < NUM_UCODES-1)
{
sprintf(temp, "Dump %d Successful. Wrote %d bytes, steps: %d", UCodeToDump+1, written, dsp_steps);
UpdateLastMessage(temp);
}
else {
UpdateLastMessage("DUMPING DONE!");
}
}
else
{
UpdateLastMessage("SD Write Error");
break;
}
}
}
// Shove common, un-dsp-ish init things here
void InitGeneral()
{
// Initialize the video system
VIDEO_Init();
// This function initializes the attached controllers
PAD_Init();
#ifdef HW_RVL
WPAD_Init();
#endif
// Obtain the preferred video mode from the system
// This will correspond to the settings in the Wii menu
rmode = VIDEO_GetPreferredMode(nullptr);
// Allocate memory for the display in the uncached region
xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
// Set up the video registers with the chosen mode
VIDEO_Configure(rmode);
// Tell the video hardware where our display memory is
VIDEO_SetNextFramebuffer(xfb);
// Make the display visible
VIDEO_SetBlack(FALSE);
// Flush the video register changes to the hardware
VIDEO_Flush();
// Wait for Video setup to complete
VIDEO_WaitVSync();
if (rmode->viTVMode & VI_NON_INTERLACE)
VIDEO_WaitVSync();
// Initialize the console, required for printf
CON_Init(xfb, 20, 64, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth * VI_DISPLAY_PIX_SZ);
#ifdef HW_RVL
// Initialize FAT so we can write to SD.
__io_wiisd.startup();
fatMountSimple("sd", &__io_wiisd);
#else
// Init debug over BBA...change IPs to suite your needs
tcp_localip="192.168.1.103";
tcp_netmask="255.255.255.0";
tcp_gateway="192.168.1.2";
DEBUG_Init(GDBSTUB_DEVICE_TCP, GDBSTUB_DEF_TCPPORT);
#endif
}
void ExitToLoader()
{
#ifdef HW_RVL
fatUnmount("sd");
__io_wiisd.shutdown();
#endif
UpdateLastMessage("Exiting...");
real_dsp.Reset();
reboot();
}
int main()
{
InitGeneral();
ui_mode = UIM_SEL;
dspbufP = (u16 *)MEM_VIRTUAL_TO_PHYSICAL(dspbuffer); // physical
dspbufC = dspbuffer; // cached
dspbufU = (u32 *)(MEM_K0_TO_K1(dspbuffer)); // uncached
DCInvalidateRange(dspbuffer, 0x2000);
for (int j = 0; j < 0x800; j++)
dspbufU[j] = 0xffffffff;
// Initialize DSP.
real_dsp.Init();
int show_step = 0;
while (true)
{
handle_dsp_mail();
VIDEO_WaitVSync();
PAD_ScanPads();
if (PAD_ButtonsDown(0) & PAD_BUTTON_START)
ExitToLoader();
#ifdef HW_RVL
WPAD_ScanPads();
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME)
ExitToLoader();
CON_Printf(2, 18, "Controls:");
CON_Printf(4, 19, "+/- (GC:'L'/'R') to move");
CON_Printf(4, 20, "A (GC:'A') to edit register; B (GC:'B') to start over");
CON_Printf(4, 21, "1 (GC:'Z') to move next microcode");
CON_Printf(4, 22, "2 (GC:'X') dump results to SD; UP (GC:'Y') dump results to SD (SINGLE FILE)");
CON_Printf(4, 23, "Home (GC:'START') to exit");
#else
CON_Printf(2, 18, "Controls:");
CON_Printf(4, 19, "L/R to move");
CON_Printf(4, 20, "A to edit register, B to start over");
CON_Printf(4, 21, "Z to move to next microcode");
CON_Printf(4, 22, "Start to exit");
#endif
print_regs(show_step, dsp_steps);
switch (ui_mode)
{
case UIM_SEL:
ui_pad_sel();
break;
case UIM_EDIT_REG:
ui_pad_edit_reg();
break;
case UIM_EDIT_BIN:
// ui_pad_edit_bin();
break;
default:
break;
}
DCFlushRange(xfb, 0x200000);
// Use B to start over.
#ifdef HW_RVL
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_B) || (PAD_ButtonsDown(0) & PAD_BUTTON_B))
#else
if (PAD_ButtonsDown(0) & PAD_BUTTON_B)
#endif
{
dsp_steps = 0; // Let's not add the new steps after the original ones. That was just annoying.
DCInvalidateRange(dspbufC, 0x2000);
DCFlushRange(dspbufC, 0x2000);
// Reset the DSP.
real_dsp.Reset();
UpdateLastMessage("OK");
}
// Navigate between results using + and - buttons.
#ifdef HW_RVL
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_PLUS) || (PAD_ButtonsDown(0) & PAD_TRIGGER_R))
#else
if (PAD_ButtonsDown(0) & PAD_TRIGGER_R)
#endif
{
show_step++;
if (show_step >= dsp_steps)
show_step = 0;
UpdateLastMessage("OK");
}
#ifdef HW_RVL
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_MINUS) || (PAD_ButtonsDown(0) & PAD_TRIGGER_L))
#else
if (PAD_ButtonsDown(0) & PAD_TRIGGER_L)
#endif
{
show_step--;
if (show_step < 0)
show_step = dsp_steps - 1;
UpdateLastMessage("OK");
}
#ifdef HW_RVL
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_1) || (PAD_ButtonsDown(0) & PAD_TRIGGER_Z))
#else
if (PAD_ButtonsDown(0) & PAD_TRIGGER_Z)
#endif
{
curUcode++;
if (curUcode == NUM_UCODES)
curUcode = 0;
// Reset step counters since we're in a new ucode.
show_step = 0;
dsp_steps = 0;
DCInvalidateRange(dspbufC, 0x2000);
for (int n = 0 ; n < 0x2000 ; n++)
{
// dspbufU[n/2] = 0; dspbufC[n] = 0;
}
DCFlushRange(dspbufC, 0x2000);
// Reset the DSP.
real_dsp.Reset();
UpdateLastMessage("OK");
// Waiting for video to synchronize (enough time to set our new microcode)
VIDEO_WaitVSync();
}
#ifdef HW_RVL
// Probably could offer to save to sd gecko or something on gc...
// The future is web-based reporting ;)
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_2) || (PAD_ButtonsDown(0) & PAD_BUTTON_X))
{
dump_all_ucodes(false);
}
// Dump all results into 1 file (skip file per ucode part) = FAST because of LIBFAT filecreate bug
if ((WPAD_ButtonsDown(0) & WPAD_BUTTON_UP) || (PAD_ButtonsDown(0) & PAD_BUTTON_Y))
{
dump_all_ucodes(true);
}
#endif
} // end main loop
ExitToLoader();
// Will never reach here, but just to be sure..
exit(0);
return 0;
}