/*
    Zym -- a Z-machine interpreter for SymbOS (see README.TXT).

    Based on Fweep v0.8.4 by zzo38, but heavily modified/rewritten for SymbOS
    by prevtenet. Some code and ideas are borrowed from fweeplet by Alan Cox
    and ZORKMID by zzo38.

    This program is licensed under GNU GPL v3 or later version.
*/

#define VERSION "0.9"
#define YEAR "2022"
#define SYMBOS

#define UNDO
//#define PROFILER
//#define TRACE
//#define SIMPLEREAD
//#define BRACKETSYNONYMS
#define STACKSIZE 512
#define FRAMESIZE 64
#define LOWMEMSIZE 0x6000
#define LOWMEM_HIGHBYTE 0x60
// Note that LOWMEMSIZE must be a multiple of 0x2000, and that LOWMEM_HIGHBYTE must == LOWMEMSIZE >> 8.

// This marginally improves performance but, oddly enough, also marginally improves size???
#pragma opt_code_speed

#ifdef SYMBOS
typedef signed long long S64;
typedef unsigned long long U64;
typedef signed long S32;
typedef unsigned long U32;
typedef signed int S16;
typedef unsigned int U16;
typedef signed char S8;
typedef unsigned char U8;
#else
typedef signed long long S64;
typedef unsigned long long U64;
typedef signed int S32;
typedef unsigned int U32;
typedef signed short S16;
typedef unsigned short U16;
typedef signed char S8;
typedef unsigned char U8;
#endif

typedef struct {
	int var8[sizeof(U8) == 1 ? 1 : -9];
	int var16[sizeof(U16) == 2 ? 1 : -9];
	int var32[sizeof(U32) == 4 ? 1 : -9];
	int var64[sizeof(U64) == 8 ? 1 : -9];
} Typechecker;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef U8 boolean;
typedef U8 byte;

typedef struct {
	U32 pc;
	U16 start;
	U8 argc2;
	boolean stored;
} StackFrame;

#ifdef PROFILER
U16 profile_read[64];
U16 profile_write[64];
U16 profile_op[256];
#endif

const char zscii_conv_1[101];
const char zscii_conv_2[7];

char alpha[78];
char statusbuf[3][81];

#ifdef SYMBOS
U16 story;
char filepath[256];
#else
FILE* story;
char* filepath;
#endif // SYMBOS
char* story_name;
U32 game_size;
U8 sc_rows;
U8 sc_columns;

U8 packed_shift;

U16 object_table;
U16 dictionary_table;
U16 restart_address;
U16 synonym_table;
U16 alphabet_table;
U16 static_start;
U16 global_table;

boolean scorenote;
boolean nostatus;
boolean tandy;
boolean allow_undo;
boolean reverse;

U8 version;
U32 program_counter;
StackFrame frames[FRAMESIZE];
U16 stack[STACKSIZE];
U8 frameptr;
U16 stackptr;
#ifdef UNDO
U32 undo_addr;
U32 undo_frameaddr;
U16 undo_framesize;
U32 undo_stackaddr;
U16 undo_stacksize;
U32 undo_memaddr;
#endif

U16 stream3addr[16];
S8 stream3ptr;
boolean texting;
U8 window;
boolean buffering;
U8 cur_row[2];
S8 cur_col[2];
U8 status_lines;
U8 more_count;
U8 more_max;

U16 randv;
boolean predictable;

U16 inst_args[8];
#define inst_sargs ((S16*)inst_args)
char text_buffer[257];
U8 textptr;
U8 cur_prop_size;
U8 argc2;

U8 zch_shift;
U8 zch_shiftlock;
U8 zch_code;

U16 oldscore;

U8 wordstate;
#define WORD_END	1
#define WORD_SHIFT	2
#define WORD_LIT_1	3
#define WORD_LIT_2	4
#define WORD_LIT_3	5
#define WORD_BYTE	6
U8 *wordptr;
U8 *wordend;
U8 wordcode;

#ifdef SYMBOS
U8  reg_a;
U8  reg_e;
U16 reg_bc;
U16 reg_hl;
U16 reg_ix;
U8  reg_cy;

U16 gbackupShort;

U8  memstack_bank[64];
U16 memstack_addr[64];
U16 memstack_len[64];
U8  memstack_i;

#include "symbio-minified.c"
#endif

#ifdef SYMBOS
void memory_free_all();
void clean_exit() {
#ifdef PROFILER
    U8 n;
    for (n = 0; n < 64; n++)
        printf("Chunk %i: read %i, write %i\r\n", n, profile_read[n], profile_write[n]);
#endif
    if (story != 255)
        File_Close(story);
	memory_free_all();
	symbos_exit();
}
#else
void clean_exit() {
#ifdef PROFILER
    int n;
    for (n = 0; n < 256; n++)
        if (profile_op[n])
            printf("%02X=%i ", n, profile_op[n]);
#endif
	exit(0);
}
#endif

void randomize(U16 seed) {
	if (seed) {
		srand(seed);
		predictable = 1;
	} else {
		predictable = 0;
        #ifdef SYMBOS
            srand((U16)Multitasking_GetCounter());
        #else
            srand(time(NULL));
        #endif
	}
}

#ifdef SYMBOS

void move_cursor(U8);

U8* lowmem() __naked { // we force SymbosMake to put the lowmem cache in the CODE block by defining it as a routine (eww)
    __asm
        .ds LOWMEMSIZE
    __endasm;
}

void memory_free_chunk(U8 index) {
    if (index < (LOWMEMSIZE / 0x2000))
        return;
	reg_a = memstack_bank[index];
	reg_bc = memstack_len[index];
	reg_hl = memstack_addr[index];
	memstack_addr[index] = 0;
	__asm
        ld a, (_reg_a)
        ld bc, (_reg_bc)
        ld hl, (_reg_hl)
        rst #0x20
        .dw #0x811B
    __endasm;
}

void memory_free_all() {
	while (memstack_i)
		memory_free_chunk(--memstack_i);
}

U8 memory_allocate_chunk(U16 len) {
	reg_bc = len;
	gbackupShort = len;    // the assembler clobbers local variables, so back up in a global
	__asm
        // Run allocate and store results in global reg variables
        ld a, #0x00
        ld e, #0x00
        ld bc, (_reg_bc)
        rst #0x20
        .dw #0x8118
        ld (_reg_a), a        // Bank used
        ld (_reg_hl), hl      // Address used

        // Return 0 if carry flag is set (not enough memory)
        jp nc, 00001$
        ld l, #0x0
        pop ix
        ret
        00001$:
	__endasm;

	// Load new addresses onto memstack
	memstack_bank[memstack_i] = reg_a;
	memstack_addr[memstack_i] = reg_hl;
	memstack_len[memstack_i]  = gbackupShort;
	memstack_i++;
	return 1;
}

U8 memory_allocate(S32 len) {
	U16 chunk_size;

    // skip past lowmem
    memstack_i += (LOWMEMSIZE / 0x2000);
    len -= LOWMEMSIZE;

    // allocate remaining memory
    while (len > 0) {
        chunk_size = (len > 0x2000) ? 0x2000 : len;
        if (!memory_allocate_chunk(chunk_size)) {
            memory_free_all();
            return 0;
        }
        len -= chunk_size;
    }
	return 1;
}

#ifdef UNDO
// utility routine for memory_copy(), to save space
void memory_copy_addressing(U8 *bank, U32 *addr, boolean real, U16 *chunk_size) {
    if (real) {
        *bank = GET_APPLICATION_BANK();
    } else {
        if (*chunk_size > ((U32)*addr - (U32)(*addr & 0xFFE000)))
            *chunk_size =  (U32)*addr - (U32)(*addr & 0xFFE000);
        if (*addr < LOWMEMSIZE) {
            *bank = GET_APPLICATION_BANK();
            *addr = (U16)&lowmem + *addr;
        } else {
            *bank = memstack_bank[*addr >> 13];
            *addr = memstack_addr[*addr >> 13] + (*addr & 0x1FFF);
        }
    }
}

void memory_copy(U32 dest, boolean dest_real, U32 src, boolean src_real, S32 len) {
	U16 chunk_size;
	U8 dest_bank, src_bank;
	U32 dest_addr, src_addr;
    while (len > 0) {
        chunk_size = (len > 0x2000) ? 0x2000 : len;
        dest_addr = dest;
        src_addr = src;
        memory_copy_addressing(&dest_bank, &dest_addr, dest_real, &chunk_size);
        memory_copy_addressing(&src_bank,  &src_addr,  src_real,  &chunk_size);
        Banking_Copy(src_bank, (U16)src_addr, dest_bank, (U16)dest_addr, chunk_size);
        dest += chunk_size;
        src += chunk_size;
        len -= chunk_size;
    }
}
#endif

#pragma save
#pragma disable_warning 85
#ifdef PROFILER
U8 read8(U32 address) {
    profile_read[address >> 13]++;
    if (address < (U32)LOWMEMSIZE)
        return *(U8*)((U16)&lowmem + (U16)address);
    else
        return Banking_ReadByte(memstack_bank[address >> 13], (char*)memstack_addr[address >> 13] + (address & 0x1FFF));
#else
#ifdef SIMPLEREAD
void move_cursor(U8);
U8 read8(U32 address) {
    #ifdef TRACE
        printf("%s%08X", "\x1F\x3C\x19", address);
        move_cursor(window);
    #endif // TRACE
    if (address < (U32)LOWMEMSIZE)
        return *(U8*)((U16)&lowmem + (U16)address);
    else
        return Banking_ReadByte(memstack_bank[address >> 13], (char*)memstack_addr[address >> 13] + (address & 0x1FFF));
}
#else
U8 read8(U32 address) __naked {
    __asm
        ; IY = parameter location (address is a 32-bit value described below as abcd where a is the high byte, though note that this is stored dcba on the stack)
        ld  iy, #0x02
        add	iy, sp
        ; is (address < LOWMEMSIZE)? we do this by subtracting the high byte of (U16)LOWMEMSIZE from bytes b and c of the address; the other bytes are irrelevant since addresses are 3 bytes or less and lowmem is always at least 8k
        ld	a, 1(iy)
        sub	a, #LOWMEM_HIGHBYTE
        ld	a, 2(iy)
        sbc	a, #0x00
        jr	NC, 0001$ ; it is not
        ; it is, BC = (U16)address
        ld  b, 1(iy)
        ld  c, 0(iy)
        ; return *(lowmem + BC)
        ld  hl, #_lowmem
        add hl, bc
        ld  l, (hl)
        ret
    0001$:
        ; BC = abcd & 0x1FFF
        ld  a,  1(iy)   ; B = c & 0x1F
        and a,  #0x1F
        ld  b,  a
        ld  c,  0(iy)   ; C = d
        ; HL = (abcd >> 13), which is equivalent to (bc << 3) with the resulting high byte being the chunk number
        ld  h,  2(iy)   ; HL = bc
        ld  l,  1(iy)
        add hl, hl      ; HL <<= 3
        add hl, hl
        add hl, hl
        ld  l,  h
        ld  h,  #0x00
        ; A = memstack_bank[HL]
        push hl
        pop de
        ld  iy, #_memstack_bank
        add iy, de
        ld  a,  0(iy)
        ; HL = memstack_addr[HL] + BC
        add hl, hl      ; HL = HL * 2 (since memstack_addr is 16-bit)
        push hl
        pop de
        ld  iy, #_memstack_addr
        add iy, de
        ld  h,  1(iy)
        ld  l,  0(iy)
        add hl, bc
        ; call Banking_ReadByte
        rst #0x20
        .dw #0x812A
        ; load byte into return register and flee
        ld  l,  b
        ret
    __endasm;
}
#endif
#endif
#pragma restore

void write8(U32 address, U8 value) {
#ifdef PROFILER
    profile_write[address >> 13]++;
#endif
    if (address < (U32)LOWMEMSIZE) {
        *(U8*)((U16)&lowmem + (U16)address) = value;
    } else
        Banking_WriteByte(memstack_bank[address >> 13], (char*)memstack_addr[address >> 13] + (address & 0x1FFF), value);
}

// Note that the Z-machine stores words big-endian, while the Z80 is naturally little-endian.
U16 read16(U32 address) {
    return (((U16)read8(address) << 8) | read8(address + 1));
}

void write16(U32 address, U16 value) {
    write8(address,     value >> 8);
    write8(address + 1, value & 0xFF);
}

#else
U8 memory[0x91000];

U8 read8(U32 address) {
	return (memory[address]);
}

void write8(U32 address, U8 value) {
	memory[address] = value;
}

U16 read16(U32 address) {
	return (memory[address] << 8) | memory[address + 1];
}

void write16(U32 address, U16 value) {
	memory[address] = value >> 8;
	memory[address + 1] = value & 255;
}
#endif

void show_error(char* msg) {
    printf("\r\n*** %s\r\n", msg);
}

#ifdef SYMBOS
#define clear_screen() putchar(12); cur_row[0] = status_lines; cur_col[0] = 0; clear_statusbuf(0, 3); more_count = 0;
void move_cursor(U8 window) {
    _symbio_buf[0] = 31;
    _symbio_buf[1] = cur_col[window] + 1;
    _symbio_buf[2] = cur_row[window] + 1;
    _symbio_buf[3] = 0;
    Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), _symbio_buf, 1);
}
#else
#define clear_screen() printf("\e[2J"); cur_row[0] = 2; cur_col[0] = 0; clear_statusbuf(0, 3); more_count = 0;
#define move_cursor(x) printf("\e[%i;%iH", cur_row[x] + 1, cur_col[x] + 1)
#endif // SYMBOS

void clear_statusbuf(U8 start, U8 end) {
    U8 n;
    for (n = start; n < end && n < 3; n++) {
        memset(&statusbuf[n][0], 0xB1, sc_columns);
        statusbuf[n][sc_columns] = 0;
    }
}

void redraw_status() {
    U8 n;
    if (nostatus)
        return;
    cur_col[1] = 0;
    for (n = 0; n < status_lines && n < 3; n++) {
        cur_row[1] = n;
        move_cursor(1);
#ifdef SYMBOS
        Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), &statusbuf[n][0], 1);
#else
        printf("%s", &statusbuf[n][0]);
#endif
        move_cursor(window);
    }
}

void print(char* str) {
    U8 len = strlen(str);
#ifdef SYMBOS
    Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), str, 1);
#else
    printf("%s", str);
#endif
    if (window && status_lines && cur_row[1] < 3)
        memcpy(&statusbuf[cur_row[1]][cur_col[1]], str, len);
    cur_col[window] += len;
}

void print_newline() {
    if (cur_row[window] < sc_rows - 1) {
        cur_row[window]++;
    } else if (!window && (U8)cur_col[window] < sc_columns) { // scroll terminal (unless hitting end-of-line already scrolled it)
    #ifdef SYMBOS
        _symbio_buf[0] = 13;
        _symbio_buf[1] = 10;
        _symbio_buf[2] = 0;
        Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), _symbio_buf, 1);
    #else
        printf("\r\n");
    #endif
    }
    cur_col[window] = 0;
    move_cursor(window);
    if (!window) {
        more_count++;
        if (more_count >= more_max) {
            redraw_status();
            printf("[MORE]");
            getch();
            move_cursor(0);
            printf("      ");
            move_cursor(0);
            more_count = 0;
        }
    }
}

void text_flush(void) {
    U8 last_space = 0;
    U8 last_print = 0;
    U8 new_cur_col = cur_col[window];
    char c;
	static char junk[256];
	text_buffer[textptr] = 0;
	textptr = 0;
    while (c = text_buffer[textptr]) {
        if (c == 32) {
            last_space = textptr;
            if (reverse)
                text_buffer[textptr] = 0xB1;
        }
        if (c == 13) {
            text_buffer[textptr] = 0;
            print(text_buffer + last_print);
            last_print = textptr + 1;
            print_newline();
            new_cur_col = 255; // will wrap around to 0 after increment below
        } else if (new_cur_col >= sc_columns) {
            if (window || !buffering) {
                text_buffer[textptr] = 0;
                print(text_buffer + last_print);
                text_buffer[textptr] = c;
                last_print = textptr;
            } else {
                text_buffer[last_space] = 0;
                print(text_buffer + last_print);
                textptr = last_space;
                last_print = last_space + 1;
            }
            print_newline();
            new_cur_col = 255; // will wrap around to 0 after increment below
        }
        textptr++;
        new_cur_col++;
    }
    print(text_buffer + last_print);
	textptr = 0;
}

void char_print(U8 zscii) {
	if (!zscii)
		return;
	if (stream3ptr != -1) {
		U16 w = read16(stream3addr[stream3ptr]);
		write8(stream3addr[stream3ptr] + 2 + w, zscii);
		write16(stream3addr[stream3ptr], w + 1);
		return;
	}
	if (texting) {
        if (window && nostatus)
            return;
		if (zscii & 0x80) {
			text_buffer[textptr++] = zscii_conv_1[(zscii & 0x7F) - 27];
			if (zscii_conv_2[(zscii & 0x7F) - 87])
				text_buffer[textptr++] = zscii_conv_2[(zscii & 0x7F) - 87];
		} else if (zscii & 0x6F) {
            text_buffer[textptr++] = zscii;
        }
		if (zscii < 32 || textptr > 253)
			text_flush();
	}
}

U32 text_print(U32 address);

void zch_print(U8 z) {
	U8 zsl;
	if (zch_shift == 3) {
		zch_code = z << 5;
		zch_shift = 4;
	} else if (zch_shift == 4) {
        zch_code |= z;
        char_print(zch_code);
        zch_shift = zch_shiftlock;
    } else if (zch_shift >= 5) {
        zsl = zch_shiftlock;
        #ifdef BRACKETSYNONYMS
            char_print('{');
        #endif
        text_print((U32)read16(synonym_table + ((U16)z << 1) + ((zch_shift - 5) << 6)) << 1);
        #ifdef BRACKETSYNONYMS
            char_print('}');
        #endif
        zch_shiftlock = zsl;
        zch_shift = zch_shiftlock;
    } else if (z == 0) {
        char_print(32);
        zch_shift = zch_shiftlock;
    } else {
        if (z == 1) {
            zch_shift = 5;
        } else if ((z == 4 || z == 5) && (zch_shift == 1 || zch_shift == 2)) {
            zch_shift = zch_shiftlock = zch_shift & (z - 3);
        } else if (z == 4) {
            zch_shift = (zch_shift + 1) % 3;
        } else if (z == 5) {
            zch_shift = (zch_shift + 2) % 3;
        } else if (z == 2) {
            zch_shift = 6;
        } else if (z == 3) {
            zch_shift = 7;
        } else if (z == 6 && zch_shift == 2) {
            zch_shift = 3;
        } else if (z == 7 && zch_shift == 2) {
            char_print(13);
            zch_shift = zch_shiftlock;
        } else {
            if (alphabet_table)
                char_print(read8(alphabet_table + z + (zch_shift * 26) - 6));
            else
                char_print(alpha[z + (zch_shift * 26) - 6]);
            zch_shift = zch_shiftlock;
        }
    }
}

U32 text_print(U32 address) {
	U16 t;
	zch_shift = 0;
	zch_shiftlock = 0;
	for (;;) {
		t = read16(address);
		address += 2;
		zch_print((t >> 10) & 31);
		zch_print((t >> 5) & 31);
		zch_print(t & 31);
		if (t & 0x8000)
			return address;
	}
}

void clear_window(U8 window) {
    U8 n;
    text_flush();
#ifdef SYMBOS
    if (window && !nostatus) {
        Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), "\x1E", 1);
        for (n = 0; n < status_lines; n++)
            Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), "\x11\x0A\x0D", 1);
        clear_statusbuf(0, status_lines - 1);
    } else {
        _symbio_buf[0] = 31;
        _symbio_buf[1] = 80;
        _symbio_buf[2] = status_lines + 25;
        _symbio_buf[3] = 0;
        Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), _symbio_buf, 1);
        for (n = status_lines; n < sc_rows; n++)
            Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), "\x11\x0A\x0D", 1);
        textptr = 0;
        cur_row[0] = status_lines;
        cur_col[0] = 0;
        more_count = 0;
    }
#else
    if (window && !nostatus) {
        printf("\e[H");
        for (n = 0; n < status_lines; n++)
            printf("\e[2K\r\n");
        clear_statusbuf(0, status_lines - 1);
    } else {
        for (n = status_lines; n < sc_rows; n++)
            printf("\e[%i;0H\e[2K", n);
        textptr = 0;
        cur_row[0] = status_lines;
        cur_col[0] = 0;
        more_count = 0;
    }
#endif
    move_cursor(0);
}

void make_rectangle(U32 addr, U8 width, U8 height, U8 skip) {
	U8 old_column = cur_col[window];
	U8 w, h;
	for (h = 0; h < height; h++) {
		for (w = 0; w < width; w++)
			char_print(read8(addr++));
		addr += skip;
		if (h != height - 1) {
			char_print(13);
			cur_col[window] = old_column;
			move_cursor(window);
		}
	}
	text_flush();
}

U16 fetch(U8 var) {
	if (var & 0xF0) {
		return read16(global_table + ((var - 16) << 1));
	} else if (var) {
        return stack[frames[frameptr].start + var - 1];
    } else {
        return stack[--stackptr];
    }
}

U16 vfetch(U8 var) { // fetch() but do not pop on 0
	if (var & 0xF0) {
		return read16(global_table + ((var - 16) << 1));
	} else if (var) {
        return stack[frames[frameptr].start + var - 1];
    } else {
        return stack[stackptr - 1];
    }
}

void store(U8 var, U16 value) {
	if (var & 0xF0) {
		write16(global_table + ((var - 16) << 1), value);
	} else if (var) {
        stack[frames[frameptr].start + var - 1] = value;
    } else {
        stack[stackptr++] = value;
    }
}

void vstore(U8 var, U16 value) { // store() but do not push on 0
	if (var & 0xF0) {
		write16(global_table + ((var - 16) << 1), value);
	} else if (var) {
        stack[frames[frameptr].start + var - 1] = value;
    } else {
        stack[stackptr - 1] = value;
    }
}

#define storei(x) store(read8(program_counter++), x)
#define vstorei(x) vstore(read8(program_counter++), x)

void enter_routine(U32 address, boolean stored, U8 argc2) {
	U8 c = read8(address);
	U8 i;
	if (!address) {
        storei(0);
        return;
	}
	frames[frameptr].pc = program_counter;
	frames[++frameptr].argc2 = argc2;
	frames[frameptr].start = stackptr;
	frames[frameptr].stored = stored;
	program_counter = address + 1;
	if (version < 5) {
		for (i = 0; i < c; i++) {
			stack[stackptr++] = read16(program_counter);
			program_counter += 2;
		}
	} else {
		for (i = 0; i < c; i++)
			stack[stackptr++] = 0;
	}
	if (argc2 > c)
		argc2 = c;
	for (i = 0; i < argc2; i++)
		stack[frames[frameptr].start + i] = inst_args[i + 1];
}

void exit_routine(U16 result) {
	stackptr = frames[frameptr].start;
	program_counter = frames[--frameptr].pc;
	if (frames[frameptr + 1].stored)
		store(read8(program_counter - 1), result);
}

void branch(U8 cond) {
    // This is one of the "heaviest" routines in typical execution, after read8; unpacked to avoid doing unnecessary computation, but it should ideally be rewritten in asm.
    U8 v = read8(program_counter);
    U16 v2;
    if (!(v & 0x80))
        cond = !cond;
    if (v & 0x40) {
        // one-byte offset
        if (cond) {
            v &= 0x3F;
            if (v == 0)
                exit_routine(0);
            else if (v == 1)
                exit_routine(1);
            else
                program_counter += v - 1; // -1 instead of -2 because we have not actually incremented program_counter at this point
        } else {
            program_counter++; // increment past read byte (only does this if branch fails, since otherwise the new value will be overwritten anyway)
        }
    } else {
        // two-byte offset (14-bit signed integer... eww)
        if (cond) {
            if (v & 0x20) {
                program_counter += (S16)(((U16)(v | 0xC0) << 8) | read8(program_counter + 1)); // no -2 because we have not actually incremented program_counter at this point. Need to set both bits 14 and 15 because negative numbers are ones-compliment.
            } else {
                v2 = ((U16)(v & 0x3F) << 8) | read8(program_counter + 1);
                if (v2 == 0)
                    exit_routine(0);
                else if (v2 == 1)
                    exit_routine(1);
                else
                    program_counter += v2; // no -2 because we have not actually incremented program_counter at this point
            }
        } else {
            program_counter += 2; // increment past read bytes without reading second byte (only does this if branch fails, since otherwise the new value will be overwritten anyway)
        }
    }
}

// These routines are marginally faster when declared inline with #defines, as
// in the original, but the expansion saves a *lot* of space and facilitates
// zero-checking.

U16 obj_tree_get(U16 obj, U8 f) {
    if (version == 3)
        return read8(object_table + 57 + obj * 9 + f);
    else
        return read16(object_table + 118 + obj * 14 + f * 2);
}

void obj_tree_put(U16 obj, U8 f, U16 v) {
    if (obj == 0) return; // avoid Vile Zero Errors from Hell(tm)
	if (version == 3)
		write8(object_table + 57 + obj * 9 + f, v);
	else
		write16(object_table + 118 + obj * 14 + f * 2, v);
}

U16 obj_prop_addr(U16 obj) {
    if (version == 3)
        return read16(object_table + 60 + obj * 9);
    else
        return read16(object_table + 124 + obj * 14);
}

//#define obj_tree_get(o, f) (version > 3 ? read16(object_table + 118 + (o) * 14 + (f) * 2) : read8(object_table + 57 + (o) * 9 + (f)))
#define parent(x) obj_tree_get(x, 0)
#define sibling(x) obj_tree_get(x, 1)
#define child(x) obj_tree_get(x, 2)
#define set_parent(x, y) obj_tree_put(x, 0, y)
#define set_sibling(x, y) obj_tree_put(x, 1, y)
#define set_child(x, y) obj_tree_put(x, 2, y)
#define attribute(x) (version == 3 ? object_table + 53 + (x) * 9 : object_table + 112 + (x) * 14)
//#define obj_prop_addr(o) (read16(version > 3 ? (object_table + 124 + (o) * 14) : (object_table + 60 + (o) * 9)))

void insert_object(U16 obj, U16 dest) {
	U16 p = parent(obj);
	U16 s, x;
	if (p) {
		// Detach object from parent
		x = child(p);
		if (x == obj) {
			set_child(p, sibling(x));
		} else {
			while (s = sibling(x)) {
				if (s == obj) {
					set_sibling(x, sibling(s));
					break;
				}
				x = s;
			}
		}
	}
	if (dest) {
		// Attach object to new parent
		set_sibling(obj, child(dest));
		set_child(dest, obj);
	} else {
		set_sibling(obj, 0);
	}
	set_parent(obj, dest);
}

U32 property_address(U16 obj, U8 p) {
	U16 a = obj_prop_addr(obj);
	U8 b, b2;
	a += (read8(a) << 1) + 1;
	if (version == 3) {
        while (b = read8(a)) {
            cur_prop_size = (b >> 5) + 1;
            a++;
            if ((b & 31) == p)
                return a;
            a += cur_prop_size;
        }
	} else {
        while (b = read8(a)) {
            if (b & 0x80) {
                b2 = read8(++a) & 63;
                cur_prop_size = b2 ? b2 : 64;
            } else {
                cur_prop_size = (b >> 6) + 1;
            }
            a++;
            if ((b & 63) == p)
                return a;
            a += cur_prop_size;
        }
	}
	return 0;
}

U8 system_input() {
	char* p;
	input_again:
	text_flush();
	more_count = 0;
	cur_col[0] = 0;
	if (!gets(text_buffer)) {
		show_error("Stopped");
		clean_exit();
	}
	p = text_buffer + strlen(text_buffer);
	while (p != text_buffer && p[-1] < 32)
		*--p = 0; // remove "CRLF", etc
	return 13;
}

// Tighter state-machine version of the encoder, from fweeplet by Alan Cox.
// Added various optimizations + binary search.

U8 encodesym(void) {
	U8 i;
	switch (wordstate) {
	case WORD_END:
		// We pad to the end with blanks (5)
		return 5;
	case WORD_SHIFT:
		// Second byte of a 2 byte sequence doing an alphabet shift
		wordstate = WORD_BYTE;
		wordptr++;
		return wordcode;
	case WORD_LIT_1:
		// Writing out a non encodable symbol, byte 2 is 6
		wordstate = WORD_LIT_2;
		return 6;
	case WORD_LIT_2:
		// Then the 10bit code follows
		wordstate = WORD_LIT_2;
		return *wordptr >> 5;
	case WORD_LIT_3:
		wordstate = WORD_BYTE;
		return *wordptr++ & 31;
	case WORD_BYTE:
		// End padding
		if (wordptr == wordend) {
			wordstate = WORD_END;
			return 5;
		}
		// See if the symbol is encodable
		for (i = 0; i < 78; i++) {
			if (*wordptr == alpha[i] && i != 52 && i != 53) {
				wordcode = i % 26 + 6;
				// Shifted or not ?
				if (i >= 26)
					return *wordptr / 26 + 3;
				wordptr++;
				return wordcode;
			}
		}
		// No - in which case start emitting a literal
		wordstate = WORD_LIT_1;
		return 5;
	default:
		show_error("Bad parse");
		clean_exit();
	}
	return 0;
}

U16 encodeword(void) {
	U16 w;
	w = encodesym();
	w <<= 5;
	w |= encodesym();
	w <<= 5;
	w |= encodesym();
	return w;
}

void dictionary_encode(U8 *text, U8 len, U16 *wp) {
    U8 *p;
    U16 r;
	if (alphabet_table && version > 4) { // synchronize alphabet table (in case game has changed it)
        r = alphabet_table;
        p = alpha;
		while(p != alpha + 78)
			*p++ = read8(r++);
	}
	wordptr = text;
	wordend = text + len;
	wordstate = WORD_BYTE;
    if (version > 3)
        *wp++ = encodeword();
	*wp++ = encodeword();
	*wp = encodeword() | 0x8000;
}

S16 compare_words(U16 entry[], U16 word[]) {
    S16 check;
    check = entry[0] - word[0];
    if (check) return check;
    check = entry[1] - word[1];
    if (check) return check;
    if (version > 3) {
        check = entry[2] - word[2];
        if (check) return check;
    }
    return 0;
}

// Encode a word into the parse buffer
void add_to_parsebuf(U16 parsebuf, U16 dict, U8* d, int k, int el, int ne, int p, U16 flag) {
	// Encode the word into zscii
	int i;
	U16 n = parsebuf + (read8(parsebuf + 1) << 2);
	U16 vbuf[3];
	U16 dbuf[3];
	U8 cal;
	int cmp;
	S16 M, L = 0;
	S16 R = ne - 1;
	U16 dict_M;
	U16 word = 0; // 0 = failure
	U8 bytes_match;

	dictionary_encode(d, k, vbuf);

	// Hunt for a match
	if (ne > 0) {
        // use binary search
        while (L <= R) {
            M = (L + R) / 2;
            dict_M = dict + (M * el);
            // Get the word and compare
            dbuf[0] = read16(dict_M);
            dbuf[1] = read16(dict_M + 2);
            if (version > 3)
                dbuf[2] = read16(dict_M + 4);
            cmp = compare_words(dbuf, vbuf);
            if (cmp < 0) {
                L = M + 1;
            } else if (cmp > 0) {
                R = M - 1;
            } else {
                word = dict_M;
                break;
            }
        }
	} else {
        // use simple search
        ne = -ne;
        bytes_match = (version == 3) ? 4 : 6;
        for (i = 0; i < ne; i++) {
            dbuf[0] = read16(dict);
            dbuf[1] = read16(dict + 2);
            if (version > 3)
                dbuf[2] = read16(dict + 4);
            if (!memcmp(vbuf, dbuf, bytes_match)) {
                word = dict;
                break;
            }
            dict += el;
        }
	}
    write8(n + 5, p + 1 + (version > 4));
    write8(n + 4, k);
    write16(n + 2, word);
	write8(parsebuf + 1, read8(parsebuf + 1) + 1); // bump the count
}

#define Add_to_parsebuf() if(k)add_to_parsebuf(parsebuf,dict,d,k,el,ne,p1,flag),k=0;p1=p+1;

// Process a command line input
void tokenise(U16 text, U16 dict, U16 parsebuf, int len, U16 flag) {
	U8 d[10];
	int i, el, ne, k, p, p1;
	int l;

	memset(filepath, 0, 256 * sizeof(boolean)); // we reuse the filepath buffer to store word-separators - confusing but saves 256 bytes

	// Read the table of character codes that count as a word
	if (!dict) {
		l = read8(dictionary_table);
		for (i = 1; i <= l; i++)
			filepath[read8(dictionary_table + i)] = 1;
		dict = dictionary_table;
	}
	l = read8(dict);
	for (i = 1; i <= l; i++)
		filepath[read8(dict + i)] = 1;
	// Parse buf count
	write8(parsebuf + 1, 0);
	k = p = p1 = 0;
	// Get the length and number of entries
	el = read8(dict + read8(dict) + 1);
	ne = read16(dict + read8(dict) + 2);
	// Skip the header
	dict += read8(dict) + 4;

	// Walk the input
	while (p < len && read8(text + p)
	       && read8(parsebuf + 1) < read8(parsebuf)) {
	        // Get a symbol
		i = read8(text + p);
		// Case conversion
		if (i >= 'A' && i <= 'Z')
			i += 'a' - 'A';
		// Spaces break words - send the word to the buffer
		if (i == ' ') {
			if (k) {
                add_to_parsebuf(parsebuf, dict, d, k, el, ne, p1, flag);
                k = 0;
			}
            p1 = p + 1;
		} else if (filepath[i]) {
			// Symbols go the buffer on their own - queue any pending stuff first, then the symbol
			if (k)
                add_to_parsebuf(parsebuf, dict, d, k, el, ne, p1, flag);
            p1 = p + 1;
			*d = i;
            add_to_parsebuf(parsebuf, dict, d, 1, el, ne, p1, flag);
            k = 0;
		} else if (k < 10) {
			// Queue more symbol
			d[k++] = i;
		} else {
			// Discard extra bytes
			k++;
		}
		p++;
	}
	// Add the final entry
	if (k)
        add_to_parsebuf(parsebuf, dict, d, k, el, ne, p1, flag);
}
#undef Add_to_parsebuf

U8 line_input(void) {
	char* p;
	int c, cmax;
	U8 res;

	// do score notification if necessary
    if (scorenote && version == 3 && ((S16)fetch(17)) != oldscore) {
        c = ((S16)fetch(17)) - oldscore;
        printf("\r\n[The score has been %screased by %d.]\r\n", c >= 0 ? "in" : "de", c >= 0 ? c : -c);
        oldscore = (S16)fetch(17);
    }

    // redraw buffered upper window and read line
    redraw_status();
    res = system_input();

    // convert text to lowercase
    p = text_buffer;
    while (*p) {
        if (*p >= 'A' && *p <= 'Z')
            *p |= 0x20;
        p++;
    }

    // save to requested buffer
	p = text_buffer;
	c = 0;
	cmax = read8(inst_args[0]);
	if (version > 4) {
		// "Left over" characters are not implemented.
		while (*p && c < cmax) {
			write8(inst_args[0] + c + 2, *p++);
			++c;
		}
		write8(inst_args[0] + 1, c);
		if (inst_args[1])
			tokenise(inst_args[0] + 2, 0, inst_args[1], c, 0);
	} else {
		while (*p && c < cmax) {
			write8(inst_args[0] + c + 1, *p++);
			++c;
		}
		write8(inst_args[0] + c + 1, 0); // zero-terminate
		tokenise(inst_args[0] + 1, 0, inst_args[1], c, 0);
	}
	if (cur_row[window] < sc_rows - 1)
        print_newline();
	return res;
}

void game_restart(void) {
    U8 i;
    S32 len_left;
    U16 load_amount;
	stackptr = frameptr = 0;
	program_counter = restart_address;
#ifdef SYMBOS
    File_Seek(story, 64, SEEK_SET);
    len_left = File_Size(story) - 64 + 128; // add 128 extra bytes to account for filesystems which do not give an accurate File_Size()
    load_amount = ((len_left + 64) > LOWMEMSIZE) ? LOWMEMSIZE : (len_left + 64);
    File_Read(story, GET_APPLICATION_BANK(), (char*)((U16)&lowmem + 64), load_amount - 64);
    printf("\x0E\x5B%i kb loaded", load_amount >> 10);
    len_left -= LOWMEMSIZE;
	for (i = LOWMEMSIZE / 0x2000; len_left > 0; i++) {
        load_amount = (len_left > 0x2000) ? 0x2000 : len_left;
        File_Read(story, memstack_bank[i], (char*)memstack_addr[i], load_amount);
        len_left -= load_amount;
        if (i > 12)
            putchar('\x08');
        printf("\x0E\x5C%i kb loaded", (i * 8) + (load_amount >> 10));
	}
#else
    U32 addr = 64;
	clearerr(story);
	fseek(story, 64, SEEK_SET);
	while (!feof(story)) {
		if (!fread(memory + addr, 1024, 1, story))
			return;
		addr += 1024;
	}
#endif // SYMBOS
	clear_screen();
	if (version == 3) {
        cur_row[0] = 2;
        move_cursor(0);
	}
}

void game_save(U8 storage) {
#ifdef SYMBOS
    U8 i, f;
    U16 write_amount;
    S32 len_left;
#else
    FILE* f;
    char _symbio_buf[256];
#endif // SYMBOS
	printf("Save file: ");
	gets(_symbio_buf);
	if (!_symbio_buf[0]) {
		if (version == 3)
			branch(0);
		else
			store(storage, 0);
		return;
	}
#ifdef SYMBOS
    Shell_PathAdd(_termpid, GET_APPLICATION_BANK(), 0, _symbio_buf, filepath); // safe to reuse filepath since we only need it for game_begin()
    f = File_New(GET_APPLICATION_BANK(), filepath, 0);
    if (f > 7)
        goto save_failed;
#else
	f = fopen(_symbio_buf, "wb");
    if (!f)
        goto save_failed;
#endif // SYMBOS
	if (version == 3)
		branch(1);
	else
		store(storage, 2);
    frames[frameptr].pc = program_counter;
    frames[frameptr + 1].start = stackptr;
#ifdef SYMBOS
    File_Write(f, GET_APPLICATION_BANK(), &frameptr, sizeof(U8));
	File_Write(f, GET_APPLICATION_BANK(), (char*)&frames[0], (frameptr + 1) * sizeof(StackFrame));
    File_Write(f, GET_APPLICATION_BANK(), &stackptr, sizeof(U16));
	File_Write(f, GET_APPLICATION_BANK(), (char*)&stack[0], stackptr * sizeof(U16));
    len_left = static_start - 0x38;
    write_amount = len_left <= (LOWMEMSIZE - 0x38) ? len_left : (LOWMEMSIZE - 0x38);
    File_Write(f, GET_APPLICATION_BANK(), (char*)((U16)&lowmem) + 0x38, write_amount);
    len_left -= write_amount;
	for (i = LOWMEMSIZE / 0x2000; len_left > 0; i++) {
        write_amount = (len_left > 0x2000) ? 0x2000 : len_left;
        File_Write(f, memstack_bank[i], (char*)memstack_addr[i], write_amount);
        len_left -= write_amount;
	}
	File_Close(f);
#else
    fputc(frameptr, f);
	fwrite(frames, frameptr + 1, sizeof(StackFrame), f);
    fputc(stackptr & 0xFF, f);
    fputc(stackptr >> 8, f);
	fwrite(stack, stackptr, sizeof(U16), f);
    fwrite(memory + 0x38, static_start - 0x38, sizeof(U8), f);
    fclose(f);
#endif // SYMBOS
	if (version == 3)
		return;
	fetch(storage);
	store(storage, 1);
	return;

save_failed:
    show_error("Failed");
	// This seems to fail for V3 games.. investigate
	if (version == 3)
		branch(0);
	else
		store(storage, 0);
}

void game_restore(void) {
#ifdef SYMBOS
    U8 i, f;
    U16 load_amount;
    S32 len_left;
#else
    FILE* f;
    char _symbio_buf[256];
#endif // SYMBOS
	printf("Restore file: ");
	gets(_symbio_buf);
	if (!_symbio_buf[0])
		return;
#ifdef SYMBOS
    Shell_PathAdd(_termpid, GET_APPLICATION_BANK(), 0, _symbio_buf, filepath); // safe to reuse filepath since we only need it for game_begin()
	f = File_Open(GET_APPLICATION_BANK(), filepath);
    if (f > 7)
        goto restore_failed;
	File_Read(f, GET_APPLICATION_BANK(), &frameptr, sizeof(U8));
	File_Read(f, GET_APPLICATION_BANK(), (char*)&frames[0], (frameptr + 1) * sizeof(StackFrame));
	File_Read(f, GET_APPLICATION_BANK(), &stackptr, sizeof(U16));
	File_Read(f, GET_APPLICATION_BANK(), (char*)&stack[0], stackptr * sizeof(U16));
    len_left = static_start - 0x38;
    load_amount = len_left <= (LOWMEMSIZE - 0x38) ? len_left : (LOWMEMSIZE - 0x38);
    File_Read(f, GET_APPLICATION_BANK(), (char*)((U16)&lowmem) + 0x38, load_amount);
    len_left -= load_amount;
	for (i = LOWMEMSIZE / 0x2000; len_left > 0; i++) {
        load_amount = (len_left > 0x2000) ? 0x2000 : len_left;
        File_Read(f, memstack_bank[i], (char*)memstack_addr[i], load_amount);
        len_left -= load_amount;
	}
    File_Close(f);
#else
	f = fopen(_symbio_buf, "rb");
	if (!f)
        goto restore_failed;
	frameptr = fgetc(f);
	fread(frames, frameptr + 1, sizeof(StackFrame), f);
	stackptr = fgetc(f) | ((U16)fgetc(f) << 8);
	fread(stack, stackptr, sizeof(U16), f);
    fread(memory + 0x38, static_start - 0x38, sizeof(U8), f);
    fclose(f);
#endif // SYMBOS
    program_counter = frames[frameptr].pc;
    return;

restore_failed:
	show_error("Read error");
}

void v3_status() {
    char linebuf[81];
    char ampm[3];
    U16 gv2, gv3;
    text_flush();
    if (nostatus)
        return;
    reverse = 1;
    gv2 = fetch(0x11);
    gv3 = fetch(0x12);
    memset(linebuf, 0xB1, sc_columns);
    if (read8(1) & 0x02) {
        // time game
        if (gv2 > 12) {
            strcpy(ampm, "pm");
            gv2 %= 12;
        } else {
            strcpy(ampm, "am");
        }
        sprintf(linebuf + sc_columns - 13 - ((gv2 > 9) ? 1 : 0), "Time:\xB1%i:%02i%s\xB1", gv2, gv3, ampm);
    } else {
        // score game
        sprintf(linebuf + sc_columns - 19 - ((gv2 > 9) ? 1 : 0) - ((gv2 > 99) ? 1 : 0) - ((gv2 > 999) ? 1 : 0) - ((gv3 > 9) ? 1 : 0) - ((gv3 > 99) ? 1 : 0) - ((gv3 > 999) ? 1 : 0), "Score:\xB1%i\xB1\xB1Moves:\xB1%i\xB1", gv2, gv3);
    }
    window = 1;
    cur_col[1] = 0;
    cur_row[1] = 0;
    move_cursor(1);
    char_print(32);
    text_print(obj_prop_addr(fetch(0x10)) + 1);
    text_flush();
#ifdef SYMBOS
    Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), linebuf + cur_col[1], 1);
#else
    printf("%s", linebuf + cur_col[1]);
#endif // SYMBOS
    window = 0;
    reverse = 0;
    move_cursor(0);
}

void execute_instructions(void) {
    #ifdef TRACE
    #ifndef SYMBOS
    U32 old_program_counter;
    #endif // SYMBOS
    #endif // TRACE
	U8 in, i, operand_types, operand_types2;
	U16 at;
	S16 m, n;
	U32 u;// = program_counter - 1;
    U8 blob[10];
    U16 word[3];
	static U64 y;
	U8 nbuf[5];

	for (;;) {
        #ifdef TRACE
        #ifdef SYMBOS
            printf("%s%05X=%02X ", "\x1F\x46\x19", (U32)program_counter, (U32)read8(program_counter));
            move_cursor(window);
            getch();
        #else
            old_program_counter = program_counter;
        #endif
        #endif // TRACE

        in = read8(program_counter++);

        // get opcode/operands
        if (in & 0x80) {
            if (in >= 0xC0 || in == 0xBE) {
                // variable
                if (in == 0xBE)
                    in = read8(program_counter++); // extended opcode
                argc2 = 0;
                operand_types = read8(program_counter++);
                if (in == 0xEC || in == 0xFA)
                    operand_types2 = read8(program_counter++);
                else
                    operand_types2 = 0xFF;
                while (operand_types != 0xFF) {
                    i = 8;
                    while (i) {
                        i -= 2;
                        switch((operand_types >> i) & 0x03) {
                        case 0: // large
                            inst_args[argc2] = read8(program_counter++) << 8;
                            inst_args[argc2] |= read8(program_counter++);
                            break;
                        case 1: // small
                            inst_args[argc2] = read8(program_counter++);
                            break;
                        case 2: // variable
                            inst_args[argc2] = fetch(read8(program_counter++));
                            break;
                        case 3: // omit
                            inst_args[argc2] = 0;
                            goto done_getting_operands;
                        }
                        argc2 += 1;
                    }
                    operand_types = operand_types2;
                    operand_types2 = 0xFF;
                }
            } else {
                // short
                switch (in & 0x30) {
                case 0x00: // large
                    inst_args[0] = read8(program_counter++) << 8;
                    inst_args[0] |= read8(program_counter++);
                    argc2 = 1;
                    in &= 0x8F;
                    break;
                case 0x10: // small
                    inst_args[0] = read8(program_counter++);
                    argc2 = 1;
                    in &= 0x8F;
                    break;
                case 0x20: // variable
                    inst_args[0] = fetch(read8(program_counter++));
                    argc2 = 1;
                    in &= 0x8F;
                    break;
                default:
                    inst_args[argc2] = 0;
                    argc2 = 0;
                    break;
                }
            }
        } else {
            // long
            argc2 = 2;
            if (in & 0x40) { // variable
                inst_args[0] = fetch(read8(program_counter++));
            } else { // small
                inst_args[0] = read8(program_counter++);
            }
            if (in & 0x20) { // variable
                inst_args[1] = fetch(read8(program_counter++));
            } else { // small
                inst_args[1] = read8(program_counter++);
            }
            in &= 0x1F;
            in |= 0xC0;
        }
done_getting_operands:
        #ifdef TRACE
            #ifndef SYMBOS
                text_flush();
                printf("\r\n%06X = %02X, args %i = %04X %04X %04X %04X %04X %04X", old_program_counter, in, argc2, inst_args[0], inst_args[1], inst_args[2], inst_args[3], inst_args[4], inst_args[5]);
                cur_row[0] = 25;
            #endif
        #endif // TRACE
        #ifdef PROFILER
            profile_op[in]++;
        #endif // PROFILER

        // run instruction
        // Note that this switch is internally broken up into several different switch() statements, with dummy values, so that SDCC will optimize it into a jump table.
        switch (in) {
        case 0x00: // Save game
            if (argc2)
                storei(0);
            else
                game_save(read8(program_counter++));
            break;
        case 0x01: // Restore game
            storei(0);
            if (!argc2)
                game_restore();
            break;
        case 0x02: // Logical shift
            if (inst_sargs[1] > 0)
                storei(inst_args[0] << inst_args[1]);
            else
                storei(inst_args[0] >> -inst_args[1]);
            break;
        case 0x03: // Arithmetic shift
            if (inst_sargs[1] > 0)
                storei(inst_sargs[0] << inst_sargs[1]);
            else
                storei(inst_sargs[0] >> -inst_sargs[1]);
            break;
        case 0x04: // Set font
            text_flush();
            storei((*inst_args == 1 || *inst_args == 4) ? 4 : 0);
            if (!tandy)
                putchar(*inst_args == 3 ? 14 : 15);
            break;
        case 0x05: break;
        case 0x06: break;
        case 0x07: break;
        case 0x08: break;
        case 0x09: // Save undo buffer
            #ifdef UNDO
            if (allow_undo) {
                write8(undo_addr, frameptr);
                write8(undo_addr + 1, stackptr);
                write16(undo_addr + 2, program_counter & 0xFFFF);
                write16(undo_addr + 4, program_counter >> 16);
                #ifdef SYMBOS
                    memory_copy(undo_frameaddr, 0, (U32)&frames[0], 1, undo_framesize);
                    memory_copy(undo_stackaddr, 0, (U32)&stack[0], 1, undo_stacksize);
                    memory_copy(undo_memaddr, 0, 0x40, 0, static_start - 0x40);
                #else
                    memcpy(memory + undo_frameaddr, frames, undo_framesize);
                    memcpy(memory + undo_stackaddr, stack, undo_stacksize);
                    memcpy(memory + undo_memaddr, memory + 0x40, static_start - 0x40);
                #endif
                storei(1);
            } else {
                storei(-1);
            }
            #else
            storei(-1);
            #endif
            break;
        case 0x0A: // Restore undo buffer
            #ifdef UNDO
            if (allow_undo) {
                frameptr = read8(undo_addr);
                stackptr = read8(undo_addr + 1);
                program_counter = read16(undo_addr + 2) | ((U32)read16(undo_addr + 4) << 16);
                #ifdef SYMBOS
                    memory_copy((U32)&frames[0], 1, undo_frameaddr, 0, undo_framesize);
                    memory_copy((U32)&stack[0], 1, undo_stackaddr, 0, undo_stacksize);
                    memory_copy(0x40, 0, undo_memaddr, 0, static_start - 0x40);
                #else
                    memcpy(frames, memory + undo_frameaddr, undo_framesize);
                    memcpy(stack, memory + undo_stackaddr, undo_stacksize);
                    memcpy(memory + 0x40, memory + undo_memaddr, static_start - 0x40);
                #endif
                storei((argc2 && version > 8) ? *inst_args : 2);
            } else {
                storei(-1);
            }
            #else
            storei(-1);
            #endif
            break;
        case 0x0B: // Print unicode
            char_print('?');
            break;
        case 0x0C: // Check unicode
            storei(0);
            break;
        default: switch (in) {
        case 0x80: // Jump if zero
            branch(*inst_args == 0);
            break;
        case 0x81: // Sibling
            at = sibling(*inst_args);
            storei(at);
            branch(at != 0);
            break;
        case 0x82: // Child
            at = child(*inst_args);
            storei(at);
            branch(at != 0);
            break;
        case 0x83: // Parent
            storei(parent(*inst_args));
            break;
        case 0x84: // Property length
            in = read8(*inst_args - 1);
            storei(version == 3 ? (in >> 5) + 1 : in & 0x80 ? (in & 63 ? in & 63 : 64) : (in >> 6) + 1);
            break;
        case 0x85: // Increment
            vstore(*inst_args, vfetch(*inst_args) + 1);
            break;
        case 0x86: // Decrement
            vstore(*inst_args, vfetch(*inst_args) - 1);
            break;
        case 0x87: // Print by byte address
            text_print(*inst_args);
            break;
        case 0x88: // Call routine
            if (*inst_args) {
                program_counter++;
                enter_routine(((U32)*inst_args << packed_shift), 1, argc2 - 1);
            } else {
                storei(0);
            }
            break;
        case 0x89: // Remove object
            insert_object(*inst_args, 0); // FLAG CHECK THIS
            break;
        case 0x8A: // Print short name of object
            text_print(obj_prop_addr(*inst_args) + 1);
            break;
        case 0x8B: // Return
            exit_routine(*inst_args);
            break;
        case 0x8C: // Unconditional jump
            program_counter += *inst_sargs - 2;
            break;
        case 0x8D: // Print by packed address
            text_print(((U32)*inst_args << packed_shift));
            break;
        case 0x8E: // Load variable
            storei(vfetch(*inst_args));
            break;
        case 0x8F: // Not (versions 3-4) // Call routine and discard result (versions 5+)
            if (version > 4) {
                if (*inst_args)
                    enter_routine(((U32)*inst_args << packed_shift), 0, argc2 - 1);
            } else {
                storei(~*inst_args);
            }
            break;
        default: switch (in) {
        case 0xB0: // Return 1
            exit_routine(1);
            break;
        case 0xB1: // Return 0
            exit_routine(0);
            break;
        case 0xB2: // Print literal
            program_counter = text_print(program_counter);
            break;
        case 0xB3: // Print literal and return
            program_counter = text_print(program_counter);
            char_print(13);
            exit_routine(1);
            break;
        case 0xB4: // No operation
            break;
        case 0xB5: // Save
            if (version > 3)
                game_save(read8(program_counter++));
            else
                game_save(0);
            break;
        case 0xB6: // Restore
            if (version > 3)
                storei(0);
            else
                branch(0);
            game_restore();
            break;
        case 0xB7: // Restart
            game_restart();
            break;
        case 0xB8: // Return from stack
            exit_routine(stack[stackptr - 1]);
            break;
        case 0xB9: // Discard from stack // Catch
            if (version > 4)
                storei(frameptr);
            else
                stackptr--;
            break;
        case 0xBA: // Quit
            text_flush();
            clean_exit();
            break;
        case 0xBB: // Line break
            char_print(13);
            break;
        case 0xBC: // Show status
            if (version == 3)
                v3_status();
            break;
        case 0xBD: // Verify checksum
            branch(1);
            break;
        case 0xBE: break;
        case 0xBF: // Check if game disc is original
            branch(1);
            break;
        case 0xC0: break;
        case 0xC1: // Branch if equal
            for (n = 1; n < argc2; n++) {
                if (*inst_args == inst_args[n]) {
                    branch(1);
                    break;
                }
            }
            if (n == argc2)
                branch(0);
            break;
        case 0xC2: // Jump if less
            branch(inst_sargs[0] < inst_sargs[1]);
            break;
        case 0xC3: // Jump if greater
            branch(inst_sargs[0] > inst_sargs[1]);
            break;
        case 0xC4: // Decrement and branch if less
            vstore(*inst_args, n = vfetch(*inst_args) - 1);
            branch(n < inst_sargs[1]);
            break;
        case 0xC5: // Increment and branch if greater
            vstore(*inst_args, n = vfetch(*inst_args) + 1);
            branch(n > inst_sargs[1]);
            break;
        case 0xC6: // Check if one object is the parent of the other
            branch(parent(inst_args[0]) == inst_args[1]);
            break;
        case 0xC7: // Test bitmap
            branch((inst_args[0]&inst_args[1]) == inst_args[1]);
            break;
        case 0xC8: // Bitwise OR
            storei(inst_args[0] | inst_args[1]);
            break;
        case 0xC9: // Bitwise AND
            storei(inst_args[0] & inst_args[1]);
            break;
        case 0xCA: // Test attributes
            branch((read8(attribute(*inst_args) + (inst_args[1] >> 3)) & (0x80 >> (inst_args[1] & 7))) != 0);
            break;
        case 0xCB: // Set attribute
            write8(attribute(*inst_args) + (inst_args[1] >> 3), read8(attribute(*inst_args) + (inst_args[1] >> 3)) | (0x80 >> (inst_args[1] & 7)));
            break;
        case 0xCC: // Clear attribute
            write8(attribute(*inst_args) + (inst_args[1] >> 3), read8(attribute(*inst_args) + (inst_args[1] >> 3))&~(0x80 >> (inst_args[1] & 7)));
            break;
        case 0xCD: // Store to variable
            vstore(inst_args[0], inst_args[1]);
            break;
        case 0xCE: // Insert object
            insert_object(inst_args[0], inst_args[1]);
            break;
        case 0xCF: // Read 16-bit number from RAM/ROM
            storei(read16(inst_args[0] + (inst_sargs[1] << 1)));
            break;
        case 0xD0: // Read 8-bit number from RAM/ROM
            storei(read8(inst_args[0] + inst_sargs[1]));
            break;
        case 0xD1: // Read property
            if (u = property_address(inst_args[0], inst_args[1]))
                storei(cur_prop_size == 1 ? read8(u) : read16(u));
            else
                storei(read16(object_table + (inst_args[1] << 1) - 2));
            break;
        case 0xD2: // Get address of property
            storei(property_address(inst_args[0], inst_args[1]));
            break;
        case 0xD3: // Find next property
            if (inst_args[1]) {
                u = property_address(inst_args[0], inst_args[1]);
                u += cur_prop_size;
                storei(read8(u) & (version == 3 ? 31 : 63));
            } else {
                u = obj_prop_addr(inst_args[0]);
                u += (read8(u) << 1) + 1;
                storei(read8(u) & (version == 3 ? 31 : 63));
            }
            break;
        case 0xD4: // Addition
            storei(inst_sargs[0] + inst_sargs[1]);
            break;
        case 0xD5: // Subtraction
            storei(inst_sargs[0] - inst_sargs[1]);
            break;
        case 0xD6: // Multiplication
            storei(inst_sargs[0]*inst_sargs[1]);
            break;
        case 0xD7: // Division
            if (inst_args[1]) {
                n = inst_sargs[0] / inst_sargs[1];
            } else {
                show_error("Div/0");
                n = 0; // undefined behavior, but do this to avoid a compiler warning
            }
            storei(n);
            break;
        case 0xD8: // Modulo
            if (inst_args[1]) {
                n = inst_sargs[0] % inst_sargs[1];
            } else {
                show_error("Div/0");
                n = 0; // undefined behavior, but do this to avoid a compiler warning
            }
            storei(n);
            break;
        case 0xD9: // Call routine
            if (*inst_args) {
                program_counter++;
                enter_routine(((U32)*inst_args << packed_shift), 1, argc2 - 1);
            } else {
                storei(0);
            }
            break;
        case 0xDA: // Call routine and discard result
            if (*inst_args)
                enter_routine(((U32)*inst_args << packed_shift), 0, argc2 - 1);
            break;
        case 0xDB: // Set colors
            break;
        case 0xDC: // Throw
            frameptr = inst_args[1];
            exit_routine(*inst_args);
            break;
        case 0xDD: break;
        case 0xDE: break;
        case 0xDF: break;
        case 0xE0: // Call routine // Read from extended RAM
            if (*inst_args) {
                program_counter++;
                enter_routine(((U32)*inst_args << packed_shift), 1, argc2 - 1);
            } else {
                storei(0);
            }
            break;
        case 0xE1: // Write 16-bit number to RAM
            write16(inst_args[0] + (inst_sargs[1] << 1), inst_args[2]);
            break;
        case 0xE2: // Write 8-bit number to RAM
            write8(inst_args[0] + inst_sargs[1], inst_args[2]);
            break;
        case 0xE3: // Write property
            u = property_address(inst_args[0], inst_args[1]);
            if (cur_prop_size == 1)
                write8(u, inst_args[2]);
            else
                write16(u, inst_args[2]);
            break;
        case 0xE4: // Read line of input
            if (version == 3)
                v3_status();
            n = line_input();
            if (version > 4)
                storei(n);
            break;
        case 0xE5: // Print character
            char_print(*inst_args);
            break;
        case 0xE6: // Print number
            sprintf(blob, "%i", *inst_sargs);
            for (n = 0; blob[n]; n++)
                char_print(blob[n]);
            break;
        case 0xE7: // Random number generator
            if (*inst_sargs > 0) {
                storei((rand() % (*inst_sargs)) + 1);
            } else {
                randomize(-*inst_sargs);
                storei(0);
            }
            break;
        case 0xE8: // Push to stack
            stack[stackptr++] = *inst_args;
            break;
        case 0xE9: // Pop from stack
            if (*inst_args)
                store(*inst_args, stack[--stackptr]);
            else
                stack[stackptr - 2] = stack[stackptr - 1], stackptr--;
            break;
        case 0xEA: // Split window
            text_flush();
            status_lines = *inst_args;
            more_max = sc_rows - status_lines - 1;
            if (version == 3)
                clear_window(1);
            if (cur_row[0] < status_lines)
                cur_row[0] = status_lines;
            break;
        case 0xEB: // Set active window
            text_flush();
            window = *inst_args;
            if (window) {
                cur_row[1] = 0;
                cur_col[1] = 0;
            }
            move_cursor(window);
            break;
        case 0xEC: // Call routine
            if (*inst_args) {
                program_counter++;
                enter_routine(((U32)*inst_args << packed_shift), 1, argc2 - 1);
            } else {
                storei(0);
            }
            break;
        case 0xED: // Clear window
            if ((S16)*inst_args == -1) {
                status_lines = 0;
                clear_screen();
            } else if ((S16)*inst_args == -2) {
                clear_screen();
            } else {
                clear_window(*inst_args);
            }
            break;
        case 0xEE: // Erase line
            #ifdef SYMBOS
                n = cur_col[window];
                memset(_symbio_buf, ' ', sc_columns - cur_col[window]);
                print(_symbio_buf);
                cur_col[window] = n;
                move_cursor(window);
            #endif
            break;
        case 0xEF: // Set cursor position
            text_flush();
            cur_row[window] = inst_args[0] - 1;
            cur_col[window] = inst_args[1] - 1;
            if (!window)
                cur_row[window] += status_lines;
            move_cursor(window);
            break;
        case 0xF0: // Get cursor position
            if (window) {
                write8(*inst_args, cur_row[1] + 1);
                write8(*inst_args + 1, cur_col[1] + 1);
            } else {
                write8(*inst_args, cur_row[0] - status_lines + 1);
                write8(*inst_args + 1, cur_col[0] + 1);
            }
            break;
        case 0xF1: // Set text style (we only support 1 = Reverse or resetting everything to 0 = Roman)
            text_flush();
            if (*inst_args < 2)
                reverse = *inst_args;
            break;
        case 0xF2: // Buffer mode
            buffering = *inst_args;
            break;
        case 0xF3: // Select output stream
            switch (*inst_sargs) {
            case 1:
                texting = 1;
                break;
            case 2:
                write8(0x11, read8(0x11) | 1);
                break;
            case 3:
                if (stream3ptr != 15) {
                    stream3addr[++stream3ptr] = inst_args[1];
                    write16(inst_args[1], 0);
                }
                break;
            case 4:
                break;
            case -1:
                texting = 0;
                break;
            case -2:
                write8(0x11, read8(0x11)&~1);
                break;
            case -3:
                if (stream3ptr != -1)
                    stream3ptr--;
                break;
            case -4:
                break;
            }
	        break;
        case 0xF4: // Select input stream
            break;
        case 0xF5: // Sound effects
            break;
        case 0xF6: // Read a single character
            text_flush();
            n = getch();
            storei(n);
            break;
        case 0xF7: // Scan a table
            if (argc2 < 4)
                inst_args[3] = 0x82;
            u = inst_args[1];
            while (inst_args[2]) {
                if (*inst_args == (inst_args[3] & 0x80 ? read16(u) : read8(u)))
                    break;
                u += inst_args[3] & 0x7F;
                inst_args[2]--;
            }
            storei(inst_args[2] ? u : 0);
            branch(inst_args[2] != 0);
            break;
        case 0xF8: // Not
            storei(~*inst_args);
            break;
        case 0xF9: // Call routine and discard results // Write extended RAM
            if (*inst_args)
                enter_routine(((U32)*inst_args << packed_shift), 0, argc2 - 1);
            break;
        case 0xFA: // Call routine and discard results
            if (*inst_args)
                enter_routine(((U32)*inst_args << packed_shift), 0, argc2 - 1);
            break;
        case 0xFB: // Tokenise text
            if (argc2 < 4)
                inst_args[3] = 0;
            if (argc2 < 3)
                inst_args[2] = 0;
            tokenise(inst_args[0] + 2, inst_args[2], inst_args[1], read8(inst_args[0] + 1), inst_args[3]);
            break;
        case 0xFC: // Encode text in dictionary format
            for (n = 0; n < 9; n++)
                blob[n] = read8(inst_args[0] + inst_args[2] + n);
            dictionary_encode(blob, inst_args[1], word);
            write16(inst_args[3], word[0]);
            write16(inst_args[3] + 2, word[1]);
            write16(inst_args[3] + 4, word[2]);
            break;
        case 0xFD: // Copy a table
            if (!inst_args[1]) {
                // zero!
                while (inst_args[2])
                    write8(inst_args[0] + --inst_args[2], 0);
            } else {
                if (inst_sargs[2] > 0 && inst_args[1] > inst_args[0]) {
                    // backward!
                    m = inst_sargs[2];
                    while (m--)
                        write8(inst_args[1] + m, read8(inst_args[0] + m));
                } else {
                    // forward!
                    if (inst_sargs[2] < 0)
                        inst_sargs[2] *= -1;
                    m = 0;
                    while (m < inst_sargs[2]) {
                        write8(inst_args[1] + m, read8(inst_args[0] + m));
                        m++;
                    }
                }
            }
            break;
        case 0xFE: // Print a rectangle of text
            make_rectangle(inst_args[0], inst_args[1], argc2 > 2 ? inst_args[2] : 1, argc2 > 3 ? inst_sargs[3] : 0);
            // (I assume the skip is signed, since many other things are, and +32768 isn't useful anyways.)
            break;
        case 0xFF: // Check argument count
            branch(frames[frameptr].argc2 >= *inst_args);
            break;
        default:
            printf("\r\n*** Bad opcode: %02X (near %05X)\r\n", in, program_counter);
            clean_exit();
            break;
        }}}
	}
}

void help(void) {
	puts("usage: zym [options] story"); putchar('\r');
    puts(" -n  = Enable score notification in Version 3 games."); putchar('\r');
	puts(" -s  = Do not show status line / upper window."); putchar('\r');
	puts(" -t  = Select Tandy mode for Infocom games."); putchar('\r');
    #ifdef UNDO
	puts(" -u  = Enable UNDO (slower).\r\n");
    #else
    puts("");
    #endif
}

void game_begin(void) {
    U32 total_size;
	if (story == 255) {
#ifdef SYMBOS
        story = File_Open(GET_APPLICATION_BANK(), filepath);
        if (story > 7) {
            show_error("Can't open file");
            clean_exit();
        }
        game_size = File_Size(story) + 128; // add 128 extra bytes to account for filesystems which do not give an accurate File_Size()
#else
		story = fopen(filepath, "rb");
        if (!story) {
            show_error("Can't open file");
            clean_exit();
        }
        fseek(story, 0, SEEK_END);
        game_size = ftell(story);
        fseek(story, 0, SEEK_SET);
#endif
    }
#ifdef SYMBOS
    File_Read(story, GET_APPLICATION_BANK(), (char*)((U16)&lowmem), 64);
#else
	rewind(story);
    fread(memory, 64, 1, story);
#endif
    version = read8(0);
	switch(version) {
	case 3:
        packed_shift = 1;
		write8(0x01, read8(0x01) & 0xAF);
		write8(0x01, read8(0x01) | 0x10);
		if (tandy)
			write8(0x01, read8(0x01) | 0x08);
		break;
	case 4:
        packed_shift = 2;
		write8(0x01, 0x00);
		break;
	case 5:
        packed_shift = 2;
		alphabet_table = read16(0x34);
		break;
	case 8:
        packed_shift = 3;
		alphabet_table = read16(0x34);
		break;
	default:
        unsupported:
        printf("\r\n*** Unsupported version: %i\r\n", version);
        clean_exit();
		break;
	}
	restart_address = read16(0x06);
	dictionary_table = read16(0x08);
	object_table = read16(0x0A);
	global_table = read16(0x0C);
	static_start = read16(0x0E);
	write8(0x11, read8(0x11) & 0x53);
	synonym_table = read16(0x18);
	if (version > 3) {
		write8(0x1E, tandy ? 11 : 6);
		write8(0x20, sc_rows);
		write8(0x21, sc_columns);
	}
	if (version > 4) {
		write8(0x01, 0x10);
		write8(0x23, sc_columns);
		write8(0x25, sc_rows);
		write8(0x26, 1);
		write8(0x27, 1);
		write8(0x2C, 2);
		write8(0x2D, 9);
	}
#ifdef UNDO
	if (allow_undo) {
        write8(0x11, read8(0x11) | 0x10);
        undo_addr = game_size;
        undo_frameaddr = undo_addr + 6;
        undo_framesize = FRAMESIZE * sizeof(StackFrame);
        undo_stackaddr = undo_frameaddr + undo_framesize;
        undo_stacksize = STACKSIZE * sizeof(U16);
        undo_memaddr = undo_stackaddr + undo_stacksize;
        total_size = game_size + (undo_framesize + undo_stacksize + 6) + (static_start - 0x40);
    } else {
#endif
        write8(0x11, read8(0x11) & 0xEF);
        total_size = game_size;
#ifdef UNDO
    }
#endif
#ifdef SYMBOS
    if (!memory_allocate(total_size)) {
        show_error("Out of memory");
        clean_exit();
    }
    printf("1 kb loaded");
#endif
	cur_row[0] = 0;
	cur_col[0] = 0;
	randomize(0);
}

#ifdef SYMBOS
void main() {
#else
int main(int argc, char* argv[]) {
#endif
    int n;
#ifdef SYMBOS
	symbos_coninit();
#endif

	puts("\r\nZym - Z-Machine interpreter for SymbOS\r\n"
         "Version " VERSION ", (C) Copyright " YEAR " Daniel Gaskell.\r\n");

	// initialize globals
	tandy = 0;
    nostatus = 0;
	memcpy(zscii_conv_1, "\x84\x94\x81\x8E\x99\x9A\xE1\xAF\xAE\x89\x8B\x98\x45\x49\xA0\x82\xA1\xA2\xA3\x79\x41\x90\x49\x4F\x55\x59\x85\x8A\x8D\x95\x97A\x90IOU\x83\x88\x8C\x93\x96AEIOU\x86\x8Fo\xE9\x84\xA4\x94\x8E\xA5\x99\x91\x92\x87\x80ttTT\x9CoO\xAD\xA8", 69);
	memcpy(zscii_conv_2, "hhhh\x00eE", 7);
	memcpy(alpha, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ^0123456789.,!?_#'\"/\\-:()", 80);
    story_name = 0;
    story = 255;
    scorenote = 0;
    reverse = 0;
    allow_undo = 0;
    packed_shift = 0;
    object_table = 0;
    dictionary_table = 0;
    restart_address = 0;
    synonym_table = 0;
    alphabet_table = 0;
    static_start = 0;
    global_table = 0;
    program_counter = 0;
    frameptr = 0;
    stackptr = 0;
    stream3ptr = -1;
    texting = 1;
    window = 0;
    buffering = 1;
    cur_row[0] = 0;
    cur_col[0] = 0;
    cur_row[1] = 0;
    cur_col[1] = 0;
    status_lines = 0;
    predictable = 0;
    textptr = 0;
    cur_prop_size = 0;
    zch_shift = 0;
    zch_shiftlock = 0;
    zch_code = 0;
    oldscore = 0;
#ifdef SYMBOS
    sc_rows = _termheight;
    sc_columns = _termwidth;
    memstack_i = 0;
#else
	sc_rows = 25;
	sc_columns = 80;
#endif
    more_count = 0;
    more_max = sc_rows - 1;
    clear_statusbuf(0, 2);
#ifdef PROFILER
    for (n = 0; n < 64; n++) {
        profile_read[n] = 0;
        profile_write[n] = 0;
    }
    for (n = 0; n < 256; n++) {
        profile_op[n] = 0;
    }
#endif

    n = 0;
	while(++n <= argc && argv[n][0] == '-') {
		switch(argv[n][1]) {
			case 'n':
				scorenote = 1;
				break;
			case 's':
				nostatus = 1;
				break;
			case 't':
				tandy = 1;
				break;
			case 'u':
			    allow_undo = 1;
				break;
			default:
				help();
		}
	}
	story_name = argv[n];
	if (argc < 2 || !story_name) {
		help();
		clean_exit();
	}
	#ifdef SYMBOS
        Shell_PathAdd(_termpid, GET_APPLICATION_BANK(), 0, story_name, filepath);
    #else
        filepath = story_name;
	#endif

	printf("Loading %s... ", filepath);
	game_begin();
	game_restart();
	execute_instructions();
}
