/* vm.c: Glulxe code related to the VM overall. Also miscellaneous stuff. Designed by Andrew Plotkin http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" /* The memory blocks which contain VM main memory and the stack. */ unsigned char *memmap = NULL; unsigned char *stack = NULL; /* Various memory addresses which are useful. These are loaded in from the game file header. */ glui32 ramstart; glui32 endgamefile; glui32 origendmem; glui32 stacksize; glui32 startfuncaddr; glui32 origstringtable; glui32 checksum; /* The VM registers. */ glui32 stackptr; glui32 frameptr; glui32 pc; glui32 stringtable; glui32 valstackbase; glui32 localsbase; glui32 endmem; glui32 protectstart, protectend; /* This is not needed for VM operation, but it may be needed for autosave/autorestore. */ glui32 prevpc; void (*stream_char_handler)(unsigned char ch); void (*stream_unichar_handler)(glui32 ch); /* setup_vm(): Read in the game file and build the machine, allocating all the memory necessary. */ void setup_vm() { unsigned char buf[4 * 7]; int res; pc = 0; /* Clear this, so that error messages are cleaner. */ prevpc = 0; /* Read in all the size constants from the game file header. */ stream_char_handler = NULL; stream_unichar_handler = NULL; glk_stream_set_position(gamefile, gamefile_start+8, seekmode_Start); res = glk_get_buffer_stream(gamefile, (char *)buf, 4 * 7); if (res != 4 * 7) { fatal_error("The game file header is too short."); } ramstart = Read4(buf+0); endgamefile = Read4(buf+4); origendmem = Read4(buf+8); stacksize = Read4(buf+12); startfuncaddr = Read4(buf+16); origstringtable = Read4(buf+20); checksum = Read4(buf+24); /* Set the protection range to (0, 0), meaning "off". */ protectstart = 0; protectend = 0; /* Do a few sanity checks. */ if ((ramstart & 0xFF) || (endgamefile & 0xFF) || (origendmem & 0xFF) || (stacksize & 0xFF)) { nonfatal_warning("One of the segment boundaries in the header is not " "256-byte aligned."); } if (endgamefile != gamefile_len) { nonfatal_warning("The gamefile length does not match the header " "endgamefile length."); } if (ramstart < 0x100 || endgamefile < ramstart || origendmem < endgamefile) { fatal_error("The segment boundaries in the header are in an impossible " "order."); } if (stacksize < 0x100) { fatal_error("The stack size in the header is too small."); } /* Allocate main memory and the stack. This is where memory allocation errors are most likely to occur. */ endmem = origendmem; memmap = (unsigned char *)glulx_malloc(origendmem); if (!memmap) { fatal_error("Unable to allocate Glulx memory space."); } stack = (unsigned char *)glulx_malloc(stacksize); if (!stack) { glulx_free(memmap); memmap = NULL; fatal_error("Unable to allocate Glulx stack space."); } stringtable = 0; /* Initialize various other things in the terp. */ init_operands(); init_accel(); init_serial(); /* Set up the initial machine state. */ vm_restart(); } /* finalize_vm(): Deallocate all the memory and shut down the machine. */ void finalize_vm() { stream_set_table(0); if (memmap) { glulx_free(memmap); memmap = NULL; } if (stack) { glulx_free(stack); stack = NULL; } final_serial(); } /* vm_restart(): Put the VM into a state where it's ready to begin executing the game. This is called both at startup time, and when the machine performs a "restart" opcode. */ void vm_restart() { glui32 lx; int res; /* Deactivate the heap (if it was active). */ heap_clear(); /* Reset memory to the original size. */ lx = change_memsize(origendmem, FALSE); if (lx) fatal_error("Memory could not be reset to its original size."); /* Load in all of main memory */ glk_stream_set_position(gamefile, gamefile_start, seekmode_Start); for (lx=0; lx= protectstart && lx < protectend) continue; memmap[lx] = res; } for (lx=endgamefile; lx endmem) { for (lx=endmem; lx= count) { /* It fits. */ array = dynarray; } else { dynarray_size = count+8; dynarray = glulx_realloc(dynarray, sizeof(glui32) * dynarray_size); if (!dynarray) fatal_error("Unable to reallocate function arguments."); array = dynarray; } } } if (!addr) { if (stackptr < valstackbase+4*count) fatal_error("Stack underflow in arguments."); stackptr -= 4*count; for (ix=0; ix= endmem) fatal_error_i("Memory access out of range", addr); if (count > 1) { addr += (count-1); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); } } /* verify_address_write(): Make sure that count bytes beginning with addr all fall within RAM. This is called at every memory write if VERIFY_MEMORY_ACCESS is defined in the header file. */ void verify_address_write(glui32 addr, glui32 count) { if (addr < ramstart) fatal_error_i("Memory write to read-only address", addr); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); if (count > 1) { addr += (count-1); if (addr >= endmem) fatal_error_i("Memory access out of range", addr); } } /* verify_array_addresses(): Make sure that an array of count elements (size bytes each), starting at addr, does not fall outside the memory map. This goes to some trouble that verify_address() does not, because we need to be wary of lengths near -- or beyond -- 0x7FFFFFFF. */ void verify_array_addresses(glui32 addr, glui32 count, glui32 size) { glui32 bytecount; if (addr >= endmem) fatal_error_i("Memory access out of range", addr); if (count == 0) return; bytecount = count*size; /* If just multiplying by the element size overflows, we have trouble. */ if (bytecount < count) fatal_error_i("Memory access way too long", addr); /* If the byte length by itself is too long, or if its end overflows, we have trouble. */ if (bytecount > endmem || addr+bytecount < addr) fatal_error_i("Memory access much too long", addr); /* The simple length test. */ if (addr+bytecount > endmem) fatal_error_i("Memory access too long", addr); }