/*
 *  main_beos.cpp - Startup code for BeOS
 *
 *  Basilisk II (C) 1997-2008 Christian Bauer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <AppKit.h>
#include <InterfaceKit.h>
#include <KernelKit.h>
#include <StorageKit.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "sysdeps.h"
#include "cpu_emulation.h"
#include "xpram.h"
#include "timer.h"
#include "video.h"
#include "rom_patches.h"
#include "prefs.h"
#include "prefs_editor.h"
#include "sys.h"
#include "user_strings.h"
#include "version.h"
#include "main.h"

#include "sheep_driver.h"

#define DEBUG 0
#include "debug.h"


// Constants
const char APP_SIGNATURE[] = "application/x-vnd.cebix-BasiliskII";
const char ROM_FILE_NAME[] = "ROM";
const char RAM_AREA_NAME[] = "Macintosh RAM";
const char ROM_AREA_NAME[] = "Macintosh ROM";
const uint32 MSG_START = 'strt';			// Emulator start message
const uint32 ROM_AREA_SIZE = 0x500000;		// Enough to hold PowerMac ROM (for powerrom_cpu)

// Prototypes
#if __POWERPC__
static void sigsegv_handler(vregs *r);
#endif


// Application object
class BasiliskII : public BApplication {
public:
	BasiliskII() : BApplication(APP_SIGNATURE)
	{
		// Find application directory and cwd to it
		app_info the_info;
		GetAppInfo(&the_info);
		BEntry the_file(&the_info.ref);
		BEntry the_dir;
		the_file.GetParent(&the_dir);
		BPath the_path;
		the_dir.GetPath(&the_path);
		chdir(the_path.Path());

		// Initialize other variables
		rom_area = ram_area = -1;
		xpram_thread = tick_thread = -1;
		xpram_thread_active = true;
		tick_thread_active = true;
		AllowQuitting = true;
	}
	virtual void ReadyToRun(void);
	virtual void MessageReceived(BMessage *msg);
	void StartEmulator(void);
	virtual bool QuitRequested(void);
	virtual void Quit(void);

	thread_id xpram_thread;		// XPRAM watchdog
	thread_id tick_thread;		// 60Hz thread

	volatile bool xpram_thread_active;	// Flag for quitting the XPRAM thread
	volatile bool tick_thread_active;	// Flag for quitting the 60Hz thread

	bool AllowQuitting;			// Flag: Alt-Q quitting allowed

private:
	static status_t emul_func(void *arg);
	static status_t tick_func(void *arg);
	static status_t xpram_func(void *arg);
	static void sigsegv_invoc(int sig, void *arg, vregs *r);

	void init_rom(void);
	void load_rom(void);

	area_id rom_area;		// ROM area ID
	area_id ram_area;		// RAM area ID

	struct sigaction sigsegv_action;	// Data access exception signal (of emulator thread)

	// Exceptions
	class area_error {};
	class file_open_error {};
	class file_read_error {};
	class rom_size_error {};

	char* vmdir;
};

static BasiliskII *the_app;


// CPU and FPU type, addressing mode
int CPUType;
bool CPUIs68060;
int FPUType;
bool TwentyFourBitAddressing;


// Global variables for PowerROM CPU
thread_id emul_thread = -1;			// Emulator thread

#if __POWERPC__
int sheep_fd = -1;					// fd of sheep driver
#endif


/*
 *  Create application object and start it
 */

int main(int argc, char **argv)
{	
	the_app = new BasiliskII();
	the_app->Run();
	delete the_app;
	return 0;
}


/*
 *  Run application
 */

void BasiliskII::ReadyToRun(void)
{
	// Initialize variables
	RAMBaseHost = NULL;
	ROMBaseHost = NULL;
	srand(real_time_clock());
	tzset();

	// Print some info
	printf(GetString(STR_ABOUT_TEXT1), VERSION_MAJOR, VERSION_MINOR);
	printf(" %s\n", GetString(STR_ABOUT_TEXT2));

	// Delete old areas
	area_id old_ram_area = find_area(RAM_AREA_NAME);
	if (old_ram_area > 0)
		delete_area(old_ram_area);
	area_id old_rom_area = find_area(ROM_AREA_NAME);
	if (old_rom_area > 0)
		delete_area(old_rom_area);

	// Read preferences
	int argc = 0;
	char **argv = NULL;
	PrefsInit(vmdir, argc, argv);

	// Init system routines
	SysInit();

	// Show preferences editor (or start emulator directly)
	if (!PrefsFindBool("nogui"))
		PrefsEditor();
	else
		PostMessage(MSG_START);
}


/*
 *  Message received
 */

void BasiliskII::MessageReceived(BMessage *msg)
{
	switch (msg->what) {
		case MSG_START:
			StartEmulator();
			break;
		default:
			BApplication::MessageReceived(msg);
	}
}


/*
 *  Start emulator
 */

void BasiliskII::StartEmulator(void)
{
	char str[256];

#if REAL_ADDRESSING
	// Open sheep driver and remap low memory
	sheep_fd = open("/dev/sheep", 0);
	if (sheep_fd < 0) {
		sprintf(str, GetString(STR_NO_SHEEP_DRIVER_ERR), strerror(sheep_fd), sheep_fd);
		ErrorAlert(str);
		PostMessage(B_QUIT_REQUESTED);
		return;
	}
	status_t res = ioctl(sheep_fd, SHEEP_UP);
	if (res < 0) {
		sprintf(str, GetString(STR_SHEEP_UP_ERR), strerror(res), res);
		ErrorAlert(str);
		PostMessage(B_QUIT_REQUESTED);
		return;
	}
#endif

	// Create area for Mac RAM
	RAMSize = PrefsFindInt32("ramsize") & 0xfff00000;	// Round down to 1MB boundary
	if (RAMSize < 1024*1024) {
		WarningAlert(GetString(STR_SMALL_RAM_WARN));
		RAMSize = 1024*1024;
	}

	RAMBaseHost = (uint8 *)0x10000000;
	ram_area = create_area(RAM_AREA_NAME, (void **)&RAMBaseHost, B_BASE_ADDRESS, RAMSize, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
	if (ram_area < 0) {
		ErrorAlert(STR_NO_RAM_AREA_ERR);
		PostMessage(B_QUIT_REQUESTED);
		return;
	}
	D(bug("RAM area %ld at %p\n", ram_area, RAMBaseHost));

	// Create area and load Mac ROM
	try {
		init_rom();
	} catch (area_error) {
		ErrorAlert(STR_NO_ROM_AREA_ERR);
		PostMessage(B_QUIT_REQUESTED);
		return;
	} catch (file_open_error) {
		ErrorAlert(STR_NO_ROM_FILE_ERR);
		PostMessage(B_QUIT_REQUESTED);
		return;
	} catch (file_read_error) {
		ErrorAlert(STR_ROM_FILE_READ_ERR);
		PostMessage(B_QUIT_REQUESTED);
		return;
	} catch (rom_size_error) {
		ErrorAlert(STR_ROM_SIZE_ERR);
		PostMessage(B_QUIT_REQUESTED);
		return;
	}

	// Initialize everything
	if (!InitAll(NULL)) {
		PostMessage(B_QUIT_REQUESTED);
		return;
	}

	// Write protect ROM
	set_area_protection(rom_area, B_READ_AREA);

	// Disallow quitting with Alt-Q from now on
	AllowQuitting = false;

	// Start XPRAM watchdog thread
	xpram_thread = spawn_thread(xpram_func, "XPRAM Watchdog", B_LOW_PRIORITY, this);
	resume_thread(xpram_thread);

	// Start 60Hz interrupt
	tick_thread = spawn_thread(tick_func, "60Hz", B_REAL_TIME_PRIORITY, this);
	resume_thread(tick_thread);

	// Start emulator thread
	emul_thread = spawn_thread(emul_func, "MacOS", B_NORMAL_PRIORITY, this);
	resume_thread(emul_thread);
}


/*
 *  Quit emulator
 */

void QuitEmulator(void)
{
	the_app->AllowQuitting = true;
	be_app->PostMessage(B_QUIT_REQUESTED);
	exit_thread(0);
}

bool BasiliskII::QuitRequested(void)
{
	if (AllowQuitting)
		return BApplication::QuitRequested();
	else
		return false;
}

void BasiliskII::Quit(void)
{
	status_t l;

	// Stop 60Hz interrupt
	if (tick_thread > 0) {
		tick_thread_active = false;
		wait_for_thread(tick_thread, &l);
	}

	// Wait for emulator thread to finish
	if (emul_thread > 0)
		wait_for_thread(emul_thread, &l);

	// Exit 680x0 emulation
	Exit680x0();

	// Stop XPRAM watchdog thread
	if (xpram_thread > 0) {
		xpram_thread_active = false;
		suspend_thread(xpram_thread);	// Wake thread up from snooze()
		snooze(1000);
		resume_thread(xpram_thread);
		wait_for_thread(xpram_thread, &l);
	}

	// Deinitialize everything
	ExitAll();

	// Delete ROM area
	if (rom_area >= 0)
		delete_area(rom_area);

	// Delete RAM area
	if (ram_area >= 0)
		delete_area(ram_area);

#if REAL_ADDRESSING
	// Unmap low memory and close memory mess driver
	if (sheep_fd >= 0) {
		ioctl(sheep_fd, SHEEP_DOWN);
		close(sheep_fd);
	}
#endif

	// Exit system routines
	SysExit();

	// Exit preferences
	PrefsExit();

	BApplication::Quit();
}


/*
 *  Create area for ROM (sets rom_area) and load ROM file
 *
 *  area_error     : Cannot create area
 *  file_open_error: Cannot open ROM file
 *  file_read_error: Cannot read ROM file
 */

void BasiliskII::init_rom(void)
{
	// Create area for ROM
	ROMBaseHost = (uint8 *)0x40800000;
	rom_area = create_area(ROM_AREA_NAME, (void **)&ROMBaseHost, B_BASE_ADDRESS, ROM_AREA_SIZE, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
	if (rom_area < 0)
		throw area_error();
	D(bug("ROM area %ld at %p\n", rom_area, ROMBaseHost));

	// Load ROM
	load_rom();
}


/*
 *  Load ROM file
 *
 *  file_open_error: Cannot open ROM file
 *  file_read_error: Cannot read ROM file
 */

void BasiliskII::load_rom(void)
{
	// Get rom file path from preferences
	const char *rom_path = PrefsFindString("rom");

	// Try to open ROM file
	BFile file(rom_path ? rom_path : ROM_FILE_NAME, B_READ_ONLY);
	if (file.InitCheck() != B_NO_ERROR)
		throw file_open_error();

	printf(GetString(STR_READING_ROM_FILE));

	// Is the ROM size correct?
	off_t rom_size = 0;
	file.GetSize(&rom_size);
	if (rom_size != 64*1024 && rom_size != 128*1024 && rom_size != 256*1024 && rom_size != 512*1024 && rom_size != 1024*1024)
		throw rom_size_error();

	uint8 *rom = new uint8[rom_size];	// Reading directly into the area doesn't work
	ssize_t actual = file.Read((void *)rom, rom_size);
	if (actual == rom_size)
		memcpy(ROMBaseHost, rom, rom_size);
	delete[] rom;
	if (actual != rom_size)
		throw file_read_error();
	ROMSize = rom_size;
}


/*
 *  Emulator thread function
 */

status_t BasiliskII::emul_func(void *arg)
{
	BasiliskII *obj = (BasiliskII *)arg;

#if __POWERPC__
	// Install data access signal handler
	sigemptyset(&obj->sigsegv_action.sa_mask);
	obj->sigsegv_action.sa_handler = (__signal_func_ptr)(obj->sigsegv_invoc);
	obj->sigsegv_action.sa_flags = 0;
	obj->sigsegv_action.sa_userdata = arg;
	sigaction(SIGSEGV, &obj->sigsegv_action, NULL);
#endif

	// Exceptions will send signals
	disable_debugger(true);

	// Start 68k and jump to ROM boot routine
	Start680x0();

	// Quit program
	obj->AllowQuitting = true;
	be_app->PostMessage(B_QUIT_REQUESTED);
	return 0;
}


/*
 *  Code was patched, flush caches if neccessary (i.e. when using a real 680x0
 *  or a dynamically recompiling emulator)
 */

void FlushCodeCache(void *start, uint32 size)
{
}


/*
 *  Mutexes
 */

struct B2_mutex {
	int dummy;	//!!
};

B2_mutex *B2_create_mutex(void)
{
	return new B2_mutex;
}

void B2_lock_mutex(B2_mutex *mutex)
{
}

void B2_unlock_mutex(B2_mutex *mutex)
{
}

void B2_delete_mutex(B2_mutex *mutex)
{
	delete mutex;
}


/*
 *  Interrupt flags (must be handled atomically!)
 */

uint32 InterruptFlags = 0;

void SetInterruptFlag(uint32 flag)
{
	atomic_or((int32 *)&InterruptFlags, flag);
}

void ClearInterruptFlag(uint32 flag)
{
	atomic_and((int32 *)&InterruptFlags, ~flag);
}


/*
 *  60Hz thread (really 60.15Hz)
 */

status_t BasiliskII::tick_func(void *arg)
{
	BasiliskII *obj = (BasiliskII *)arg;
	int tick_counter = 0;
	bigtime_t current = system_time();

	while (obj->tick_thread_active) {

		// Wait
		current += 16625;
		snooze_until(current, B_SYSTEM_TIMEBASE);

		// Pseudo Mac 1Hz interrupt, update local time
		if (++tick_counter > 60) {
			tick_counter = 0;
			WriteMacInt32(0x20c, TimerDateTime());
			SetInterruptFlag(INTFLAG_1HZ);
			TriggerInterrupt();
		}

		// Trigger 60Hz interrupt
		SetInterruptFlag(INTFLAG_60HZ);
		TriggerInterrupt();
	}
	return 0;
}


/*
 *  XPRAM watchdog thread (saves XPRAM every minute)
 */

status_t BasiliskII::xpram_func(void *arg)
{
	uint8 last_xpram[XPRAM_SIZE];
	memcpy(last_xpram, XPRAM, XPRAM_SIZE);

	while (((BasiliskII *)arg)->xpram_thread_active) {
		snooze(60*1000000);
		if (memcmp(last_xpram, XPRAM, XPRAM_SIZE)) {
			memcpy(last_xpram, XPRAM, XPRAM_SIZE);
			SaveXPRAM();
		}
	}
	return 0;
}


/*
 *  Display error alert
 */

void ErrorAlert(const char *text)
{
	if (PrefsFindBool("nogui")) {
		printf(GetString(STR_SHELL_ERROR_PREFIX), text);
		return;
	}
	VideoQuitFullScreen();
	char str[256];
	sprintf(str, GetString(STR_GUI_ERROR_PREFIX), text);
	BAlert *alert = new BAlert(GetString(STR_ERROR_ALERT_TITLE), str, GetString(STR_QUIT_BUTTON), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
	alert->Go();
}


/*
 *  Display warning alert
 */

void WarningAlert(const char *text)
{
	if (PrefsFindBool("nogui")) {
		printf(GetString(STR_SHELL_WARNING_PREFIX), text);
		return;
	}
	char str[256];
	sprintf(str, GetString(STR_GUI_WARNING_PREFIX), text);
	BAlert *alert = new BAlert(GetString(STR_WARNING_ALERT_TITLE), str, GetString(STR_OK_BUTTON), NULL, NULL, B_WIDTH_AS_USUAL, B_INFO_ALERT);
	alert->Go();
}


/*
 *  Display choice alert
 */

bool ChoiceAlert(const char *text, const char *pos, const char *neg)
{
	char str[256];
	sprintf(str, GetString(STR_GUI_WARNING_PREFIX), text);
	BAlert *alert = new BAlert(GetString(STR_WARNING_ALERT_TITLE), str, pos, neg, NULL, B_WIDTH_AS_USUAL, B_INFO_ALERT);
	return alert->Go() == 0;
}


/*
 *  SEGV handler
 */

#if __POWERPC__
static uint32 segv_r[32];

asm void BasiliskII::sigsegv_invoc(register int sig, register void *arg, register vregs *r)
{
	mflr	r0
	stw		r0,8(r1)
	stwu	r1,-56(r1)

	lwz		r3,segv_r(r2)
	stmw	r13,13*4(r3)

	mr		r3,r5
	bl		sigsegv_handler

	lwz		r3,segv_r(r2)
	lmw		r13,13*4(r3)

	lwz		r0,56+8(r1)
	mtlr	r0
	addi	r1,r1,56
	blr
}

static void sigsegv_handler(vregs *r)
{
	// Fetch volatile registers
	segv_r[0] = r->r0;
	segv_r[1] = r->r1;
	segv_r[2] = r->r2;
	segv_r[3] = r->r3;
	segv_r[4] = r->r4;
	segv_r[5] = r->r5;
	segv_r[6] = r->r6;
	segv_r[7] = r->r7;
	segv_r[8] = r->r8;
	segv_r[9] = r->r9;
	segv_r[10] = r->r10;
	segv_r[11] = r->r11;
	segv_r[12] = r->r12;

	// Get opcode and divide into fields
	uint32 opcode = *(uint32 *)r->pc;
	uint32 primop = opcode >> 26;
	uint32 exop = (opcode >> 1) & 0x3ff;
	uint32 ra = (opcode >> 16) & 0x1f;
	uint32 rb = (opcode >> 11) & 0x1f;
	uint32 rd = (opcode >> 21) & 0x1f;
	uint32 imm = opcode & 0xffff;

	// Analyze opcode
	enum {
		TYPE_UNKNOWN,
		TYPE_LOAD,
		TYPE_STORE
	} transfer_type = TYPE_UNKNOWN;
	enum {
		SIZE_UNKNOWN,
		SIZE_BYTE,
		SIZE_HALFWORD,
		SIZE_WORD
	} transfer_size = SIZE_UNKNOWN;
	enum {
		MODE_UNKNOWN,
		MODE_NORM,
		MODE_U,
		MODE_X,
		MODE_UX
	} addr_mode = MODE_UNKNOWN;
	switch (primop) {
		case 31:
			switch (exop) {
				case 23:	// lwzx
					transfer_type = TYPE_LOAD; transfer_size = SIZE_WORD; addr_mode = MODE_X; break;
				case 55:	// lwzux
					transfer_type = TYPE_LOAD; transfer_size = SIZE_WORD; addr_mode = MODE_UX; break;
				case 87:	// lbzx
					transfer_type = TYPE_LOAD; transfer_size = SIZE_BYTE; addr_mode = MODE_X; break;
				case 119:	// lbzux
					transfer_type = TYPE_LOAD; transfer_size = SIZE_BYTE; addr_mode = MODE_UX; break;
				case 151:	// stwx
					transfer_type = TYPE_STORE; transfer_size = SIZE_WORD; addr_mode = MODE_X; break;
				case 183:	// stwux
					transfer_type = TYPE_STORE; transfer_size = SIZE_WORD; addr_mode = MODE_UX; break;
				case 215:	// stbx
					transfer_type = TYPE_STORE; transfer_size = SIZE_BYTE; addr_mode = MODE_X; break;
				case 247:	// stbux
					transfer_type = TYPE_STORE; transfer_size = SIZE_BYTE; addr_mode = MODE_UX; break;
				case 279:	// lhzx
					transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_X; break;
				case 311:	// lhzux
					transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_UX; break;
				case 343:	// lhax
					transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_X; break;
				case 375:	// lhaux
					transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_UX; break;
				case 407:	// sthx
					transfer_type = TYPE_STORE; transfer_size = SIZE_HALFWORD; addr_mode = MODE_X; break;
				case 439:	// sthux
					transfer_type = TYPE_STORE; transfer_size = SIZE_HALFWORD; addr_mode = MODE_UX; break;
			}
			break;

		case 32:	// lwz
			transfer_type = TYPE_LOAD; transfer_size = SIZE_WORD; addr_mode = MODE_NORM; break;
		case 33:	// lwzu
			transfer_type = TYPE_LOAD; transfer_size = SIZE_WORD; addr_mode = MODE_U; break;
		case 34:	// lbz
			transfer_type = TYPE_LOAD; transfer_size = SIZE_BYTE; addr_mode = MODE_NORM; break;
		case 35:	// lbzu
			transfer_type = TYPE_LOAD; transfer_size = SIZE_BYTE; addr_mode = MODE_U; break;
		case 36:	// stw
			transfer_type = TYPE_STORE; transfer_size = SIZE_WORD; addr_mode = MODE_NORM; break;
		case 37:	// stwu
			transfer_type = TYPE_STORE; transfer_size = SIZE_WORD; addr_mode = MODE_U; break;
		case 38:	// stb
			transfer_type = TYPE_STORE; transfer_size = SIZE_BYTE; addr_mode = MODE_NORM; break;
		case 39:	// stbu
			transfer_type = TYPE_STORE; transfer_size = SIZE_BYTE; addr_mode = MODE_U; break;
		case 40:	// lhz
			transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_NORM; break;
		case 41:	// lhzu
			transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_U; break;
		case 42:	// lha
			transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_NORM; break;
		case 43:	// lhau
			transfer_type = TYPE_LOAD; transfer_size = SIZE_HALFWORD; addr_mode = MODE_U; break;
		case 44:	// sth
			transfer_type = TYPE_STORE; transfer_size = SIZE_HALFWORD; addr_mode = MODE_NORM; break;
		case 45:	// sthu
			transfer_type = TYPE_STORE; transfer_size = SIZE_HALFWORD; addr_mode = MODE_U; break;
	}

	// Calculate effective address
	uint32 addr = 0;
	switch (addr_mode) {
		case MODE_X:
		case MODE_UX:
			if (ra == 0)
				addr = segv_r[rb];
			else
				addr = segv_r[ra] + segv_r[rb];
			break;
		case MODE_NORM:
		case MODE_U:
			if (ra == 0)
				addr = (int32)(int16)imm;
			else
				addr = segv_r[ra] + (int32)(int16)imm;
			break;
	}

	// Ignore ROM writes
	if (transfer_type == TYPE_STORE && addr >= (uint32)ROMBaseHost && addr < (uint32)ROMBaseHost + ROMSize) {
//		D(bug("WARNING: %s write access to ROM at %p, 68k pc %p\n", transfer_size == SIZE_BYTE ? "Byte" : transfer_size == SIZE_HALFWORD ? "Halfword" : "Word", addr, r->pc));
		if (addr_mode == MODE_U || addr_mode == MODE_UX)
			segv_r[ra] = addr;
		r->pc += 4;
		goto rti;
	}

	// For all other errors, jump into debugger
	char str[256];
	sprintf(str, "SIGSEGV\n"
		"   pc %08lx     lr %08lx    ctr %08lx    msr %08lx\n"
		"  xer %08lx     cr %08lx  fpscr %08lx\n"
		"   r0 %08lx     r1 %08lx     r2 %08lx     r3 %08lx\n"
		"   r4 %08lx     r5 %08lx     r6 %08lx     r7 %08lx\n"
		"   r8 %08lx     r9 %08lx    r10 %08lx    r11 %08lx\n"
		"  r12 %08lx    r13 %08lx    r14 %08lx    r15 %08lx\n"
		"  r16 %08lx    r17 %08lx    r18 %08lx    r19 %08lx\n"
		"  r20 %08lx    r21 %08lx    r22 %08lx    r23 %08lx\n"
		"  r24 %08lx    r25 %08lx    r26 %08lx    r27 %08lx\n"
		"  r28 %08lx    r29 %08lx    r30 %08lx    r31 %08lx\n",
		r->pc, r->lr, r->ctr, r->msr,
		r->xer, r->cr, r->fpscr,
		r->r0, r->r1, r->r2, r->r3,
		r->r4, r->r5, r->r6, r->r7,
		r->r8, r->r9, r->r10, r->r11,
		r->r12, segv_r[13], segv_r[14], segv_r[15],
		segv_r[16], segv_r[17], segv_r[18], segv_r[19],
		segv_r[20], segv_r[21], segv_r[22], segv_r[23],
		segv_r[24], segv_r[25], segv_r[26], segv_r[27],
		segv_r[28], segv_r[29], segv_r[30], segv_r[31]);
	disable_debugger(false);
	debugger(str);
	QuitEmulator();
	return;

rti:
	// Restore volatile registers
	r->r0 = segv_r[0];
	r->r1 = segv_r[1];
	r->r2 = segv_r[2];
	r->r3 = segv_r[3];
	r->r4 = segv_r[4];
	r->r5 = segv_r[5];
	r->r6 = segv_r[6];
	r->r7 = segv_r[7];
	r->r8 = segv_r[8];
	r->r9 = segv_r[9];
	r->r10 = segv_r[10];
	r->r11 = segv_r[11];
	r->r12 = segv_r[12];
}
#endif
