void frame_set_var(unsigned frame, int var, zword val) { if(var == 0 || var > 0x10) { n_show_error(E_STACK, "variable not writable from arbitrary frame", var); return; } if(var > stack_frames[frame].num_locals) n_show_error(E_STACK, "writing nonexistant local", var); stack_stack[stack_frames[frame].stack_stack_start + (var - 1)] = val;; }
zword frame_get_var(unsigned frame, int var) { if(var == 0 || var > 0x10) { n_show_error(E_STACK, "variable not readable from arbitrary frame", var); return 0; } if(var > stack_frames[frame].num_locals) n_show_error(E_STACK, "reading nonexistant local", var); return stack_stack[stack_frames[frame].stack_stack_start + (var - 1)]; }
void mop_call(zword dest, unsigned num_args, zword *args, int result_var) { unsigned i; offset destPC; unsigned num_local; if(dest == 0) { check_set_var(result_var, 0); return; } destPC = UNPACKR(dest); /* printf("call %x -> %x\n", oldPC, destPC); */ #ifndef FAST if(destPC > game_size) { n_show_error(E_INSTR, "call past end of story", dest); check_set_var(result_var, 0); return; } #endif num_local = HIBYTE(destPC); /* first byte is # of local variables */ #ifndef FAST if(num_local > 15) { n_show_error(E_INSTR, "call to non-function (initial byte > 15)", dest); check_set_var(result_var, 0); return; } #endif destPC++; /* Go on past the variable count */ if(zversion < 5) { for(i = num_args; i < num_local; i++) args[i] = HIWORD(destPC + i * ZWORD_SIZE); /* Load default locals */ destPC += num_local * ZWORD_SIZE; } else { for(i = num_args; i < num_local; i++) args[i] = 0; /* locals default to zero */ } add_stack_frame(PC, num_local, args, num_args, result_var); PC = destPC; /*n_show_debug(E_DEBUG, "function starting", dest);*/ }
void op_throw(void) { #ifndef FAST if(operand[1] > frame_count) { n_show_error(E_STACK, "attempting to throw away non-existent frames", operand[1]); return; } #endif if(operand[1] != 0) { frame_count = operand[1]; mop_func_return(operand[0]); } else { n_show_error(E_STACK, "attempting to throw away initial frame", operand[0]); } }
N_INLINE static void take_branch(zbyte branch) { int o = branch & b00111111; if(!(branch & b01000000)) {/* Bit 6 clear means 'branch occupies two bytes'*/ o = (o << 8) + HIBYTE(PC); PC++; if(branch & b00100000) o = -((1 << 14) - o); } if(o == 0) mop_func_return(0); else if(o == 1) mop_func_return(1); else PC += o - 2; #ifndef FAST if(PC > game_size) { n_show_error(E_INSTR, "attempt to conditionally jump outside of story", o - 2); PC -= o - 2; return; } #endif /* printf("cjmp %x -> %x\n", oldPC, PC); */ }
/* Returns a direction it wants you to explore in. Take the direction and call automap_unexplore, which'll take you back to the @read. If it returns NULL, we're finished; get player input */ const char *automap_explore(void) { if(automap_explored) { n_show_error(E_SAVE, "tried to explore when we just did so", automap_explored); return NULL; } if(!loc_exp) return NULL; if(automap_dir == NUM_EXITS) { fast_saveundo(); automap_location = evaluate_expression(loc_exp, stack_get_depth()).v; automap_dir = 0; } else { automap_dir++; if(automap_dir == NUM_EXITS) { loc_node *r = room_find(automap_location, TRUE); r->found = TRUE; automap_calc_exits(r, 0); allow_saveundo = TRUE; allow_output = TRUE; return NULL; } } allow_saveundo = FALSE; allow_output = FALSE; automap_explored = TRUE; return dirways[automap_dir].name; }
void z_tokenise(const char *text, int length, zword parse_dest, zword dictionarytable, BOOL write_unrecognized) { zword separatortable; zword numparsedloc; int numseparators; int maxwords, parsed_words; if(parse_dest > dynamic_size || parse_dest < 64) { n_show_error(E_OUTPUT, "parse table in invalid location", parse_dest); return; } numseparators = LOBYTE(dictionarytable); separatortable = dictionarytable + 1; dictionarytable += numseparators + 1; maxwords = LOBYTE(parse_dest); numparsedloc = parse_dest + 1; parse_dest+=2; if(maxwords == 0) n_show_warn(E_OUTPUT, "small parse size", maxwords); parsed_words = tokenise(dictionarytable, text, length, &parse_dest, maxwords, separatortable, numseparators, write_unrecognized); LOBYTEwrite(numparsedloc, parsed_words); }
void op_save5(void) { unsigned i; char filename[256]; unsigned length; strid_t file = NULL; offset end; switch(numoperands) { case 0: op_save4(); return; case 1: n_show_error(E_INSTR, "call save with bad number of operands", numoperands); mop_store_result(0); return; case 2: file = n_file_prompt(fileusage_Data | fileusage_BinaryMode, filemode_Write); break; default: length = LOBYTE(operand[2]); if(length > 13) n_show_port(E_INSTR, "save with filename > 13 characters", length); for(i = 0; i < length; i++) filename[i] = glk_char_to_upper(LOBYTE(operand[2] + 1 + i)); filename[length] = 0; file = n_file_name(fileusage_Data | fileusage_BinaryMode, filemode_Write, filename); break; } if(!file) { mop_store_result(0); return; } end = ((offset) operand[0]) + operand[1]; if(end > 65535 || end > total_size) { n_show_error(E_MEMORY, "attempt to save data out of range", end); mop_store_result(0); return; } w_glk_put_buffer_stream(file, (char *) z_memory + operand[0], operand[1]); glk_stream_close(file, NULL); mop_store_result(1); }
static zword stack_pop(void) { #ifndef FAST if(stack_pointer <= stack_min) { n_show_error(E_STACK, "underflow - excessive popping", stack_pointer); return 0; } #endif return stack_stack[--stack_pointer]; }
static automap_path *automap_find_path(loc_node *location, loc_node *dest, BOOL by_walking) { automap_path *path = NULL; automap_path *rev; automap_path newnode; loc_node *p; /* Find the distances of all nodes from dest */ n_hash_enumerate(&rooms, make_distant); automap_calc_distances(dest, 0, by_walking); /* If dest isn't reachable, location's distance will still be infinite */ if(location->dist == INFINITY) return NULL; /* At each step, go toward a nearer node 'till we're there */ p = location; while(p != dest) { unsigned i; unsigned best_dir; glui32 best_dist = INFINITY; loc_node *best_node = NULL; unsigned maxdir = by_walking ? NUM_EXITS : NUM_DIRS; for(i = 0; i < maxdir; i++) { loc_node *thisdest; if(by_walking) thisdest = p->exits[i]; else thisdest = automap_edge_follow(p, i); if(thisdest && thisdest->dist < best_dist) { best_dir = i; best_dist = thisdest->dist; best_node = thisdest; } } if(!best_node) { n_show_error(E_SYSTEM, "couldn't find path there", 0); return NULL; } newnode.loc = p; newnode.dir = best_dir; LEadd(path, newnode); p = best_node; } rev = NULL; while(path) { LEadd(rev, *path); LEremove(path); } return rev; }
static loc_node *automap_edge_follow(loc_node *location, int dir) { if(location->outgoing[dir] == NULL) return NULL; if(location->outgoing[dir]->dest[0] == location) return location->outgoing[dir]->dest[1]; if(location->outgoing[dir]->dest[1] == location) return location->outgoing[dir]->dest[0]; n_show_error(E_SYSTEM, "edge isn't connected to what it should be", 0); return NULL; }
void remove_stack_frame(void) { #ifndef FAST if(frame_count == 0) { n_show_error(E_STACK, "attempt to remove initial stack frame", 0); return; } #endif stack_pointer = stack_frames[frame_count].stack_stack_start; frame_count--; stack_min = stack_frames[frame_count].stack_stack_start + stack_frames[frame_count].num_locals; local_vars = stack_stack + stack_frames[frame_count].stack_stack_start; }
N_INLINE zword get_var(int var) { if(var < 0x10) { if(var != 0) { #ifndef FAST if(var > stack_frames[frame_count].num_locals) n_show_error(E_INSTR, "reading nonexistant local", var); #endif return local_vars[var - 1]; } return stack_pop(); } return LOWORD(z_globaltable + (var - 0x10) * ZWORD_SIZE); }
N_INLINE void set_var(int var, zword val) { if(var < 0x10) { if(var != 0) { #ifndef FAST if(var > stack_frames[frame_count].num_locals) n_show_error(E_INSTR, "setting nonexistant local", var); #endif local_vars[var - 1] = val; } else { stack_push(val); } } else { LOWORDwrite(z_globaltable + (var - 0x10) * ZWORD_SIZE, val); } }
/* Just like restoreundo, but the opposite ;) The idea is to go to the @save_undo location, but return 0 instead of 2 so the game thinks it just successfully saved the game. For games which don't contain @save_undo, jumps to right after the @read instruction. */ BOOL restoreredo(void) { strid_t stack; int i; glui32 wid, hei; stream_result_t poo; move_difference *p = movelist; if(!p || move_index <= 0) return FALSE; move_index--; for(i = 0; i < move_index; i++) { p=p->next; if(!p) return FALSE; } quetzal_undiff(prevstate, dynamic_size, p->delta, p->deltalength, TRUE); n_memcpy(z_memory, prevstate, dynamic_size); stack = glk_stream_open_memory((char *) p->stackchunk, p->stacklength, filemode_Read, 0); quetzal_stack_restore(stack, p->stacklength); glk_stream_close(stack, &poo); if(poo.readcount != p->stacklength) { n_show_error(E_SAVE, "incorrect stack size assessment", poo.readcount); return FALSE; } if(p->PC_in_instruction) { PC = p->PC; mop_store_result(0); false_undo = FALSE; } else { PC = p->PC; false_undo = FALSE; } has_done_save_undo = TRUE; z_find_size(&wid, &hei); set_header(wid, hei); return TRUE; }
/* Perform various sanity checks on the stack to make sure all is well in Denmark. */ BOOL verify_stack(void) { zword f; if(frame_count > frame_max) { n_show_error(E_STACK, "more frames than max", frame_count); return FALSE; } if(!stack_frames) { n_show_error(E_STACK, "no frames", 0); return FALSE; } for(f = 0; f < frame_count; f++) { if(stack_frames[f].stack_stack_start > stack_pointer) { n_show_error(E_STACK, "stack start after end", f); return FALSE; } if(stack_frames[f].return_PC > total_size) { n_show_error(E_STACK, "PC after end of game", f); return FALSE; } if(stack_frames[f].num_locals > 15) { n_show_error(E_STACK, "too many locals", f); return FALSE; } if(stack_frames[f].arguments > 7) { n_show_error(E_STACK, "too many arguments", f); return FALSE; } if(stack_frames[f].result_variable > 255) { n_show_error(E_STACK, "result variable too big", f); return FALSE; } if(stack_frames[f].result_variable < -2) { n_show_error(E_STACK, "unknown magic result variable", f); return FALSE; } } return TRUE; }
/* Returns TRUE if it improved any */ static BOOL automap_increase_along_path(automap_path *path, int oldcount, loc_node *center, int effort) { automap_path *p; int exploring; int explore_max = effort > 1; if(!effort) return FALSE; /* Takes two passes at trying to improve the situation. The first time (!exploring), it tries increasing the length of each edge along the path, observing the results and then undoing the increase. If it was able to improve anything, it returns with the best improvement. Otherwise it tries increasing the length of each edge and calling itself; If its child is able to improve things, then it returns with both lengthenings in effect. */ for(exploring = 0; exploring <= explore_max; exploring++) { edge *best_edge = NULL; int best_count = oldcount; int smallest_new = 10000; for(p = path; p; p=p->next) { int newcount; edge *e = automap_get_edge(p->loc, p->dir); int old_min_length = e->min_length; int old_guess_length = e->guess_length; if(p->next && p->next->loc != automap_edge_follow(p->loc, p->dir)) n_show_error(E_SYSTEM, "path doesn't follow itself", 0); e->guess_length += 2; e->min_length = e->guess_length; if(!exploring) { newcount = automap_find_and_count_interference(center); if(newcount < best_count || (newcount == best_count && newcount < oldcount && e->min_length < smallest_new)) { best_edge = e; best_count = newcount; smallest_new = e->min_length; } } else { if(automap_increase_along_path(p, oldcount, center, effort-1)) return TRUE; } e->min_length = old_min_length; e->guess_length = old_guess_length; } if(!exploring && best_edge) { best_edge->guess_length += 2; best_edge->min_length = best_edge->guess_length; automap_find_and_count_interference(center); return TRUE; } } return FALSE; }
void decode(void) { unsigned optypes; int maxoperands; while(!exit_decoder) { #ifdef ANDGLK extern int do_autosave; extern char AUTOSAVE_FILE[]; extern offset stack_pointer; extern zword *local_vars; extern zword frame_count; offset save_pcp = PC; offset save_oldpcp = oldPC; offset save_sp = stack_pointer; zword* save_fp = local_vars; zword save_optypes = optypes; zword save_maxoperands = maxoperands; int save_frame_count = frame_count; zword save_zargs[8] = { operand[0], operand[1], operand[2], operand[3], operand[4], operand[5], operand[6], operand[7] }; int zarg_ix, save_zargc = 0; //zargc; #endif zbyte opcode = HIBYTE(PC); //LOGD("state: PC:%x op:%x", (glui32)PC, (glui32)opcode); oldPC = PC; #ifdef DEBUGGING if(do_check_watches) check_watches(); #endif PC++; #ifndef NO_TICK glk_tick(); /* tick tock hickery dock the mouse ran up the clock */ #endif /* Top bits decide opcode/operand encoding */ switch(opcode >> 4) { case 0: case 1: /* long 2OP */ operand[0] = HIBYTE(PC); /* small constant */ operand[1] = HIBYTE(PC+1); /* small constant */ numoperands = 2; PC += 2; opcode += OFFSET_2OP - 0x00; break; case 2: case 3: /* long 2OP */ operand[0] = HIBYTE(PC); /* small constant */ operand[1] = get_var(HIBYTE(PC+1)); /* variable */ numoperands = 2; PC += 2; opcode += OFFSET_2OP - 0x20; break; case 4: case 5: /* long 2OP */ operand[0] = get_var(HIBYTE(PC)); /* variable */ operand[1] = HIBYTE(PC+1); /* small constant */ numoperands = 2; PC += 2; opcode += OFFSET_2OP - 0x40; break; case 6: case 7: /* long 2OP */ operand[0] = get_var(HIBYTE(PC)); /* variable */ operand[1] = get_var(HIBYTE(PC+1)); /* variable */ numoperands = 2; PC += 2; opcode += OFFSET_2OP - 0x60; break; case 8: /* short 1OP */ operand[0] = HIWORD(PC); /* large constant */ numoperands = 1; PC += ZWORD_SIZE; opcode += OFFSET_1OP - 0x80; break; case 9: /* short 1OP */ operand[0] = HIBYTE(PC); /* small constant */ numoperands = 1; PC += 1; opcode += OFFSET_1OP - 0x90; break; case 10: /* short 1OP */ operand[0] = get_var(HIBYTE(PC)); /* variable */ numoperands = 1; PC += 1; opcode += OFFSET_1OP - 0xa0; break; case 11: /* short 0OP */ if(opcode != 0xbe) { numoperands = 0; opcode += OFFSET_0OP - 0xb0; break; } /* EXTENDED */ opcode = HIBYTE(PC); /* Get the extended opcode */ optypes = HIBYTE(PC+1); PC += 2; #ifndef FAST if(OFFSET_EXT + opcode > OFFSET_END) { n_show_error(E_INSTR, "unknown extended opcode", opcode); break; } #endif for(numoperands = 0; numoperands < 4; numoperands++) { switch(optypes & (3 << 6)) { /* Look at the highest two bits. */ case 0 << 6: operand[numoperands] = HIWORD(PC); PC+=ZWORD_SIZE; break; case 1 << 6: operand[numoperands] = HIBYTE(PC); PC++; break; case 2 << 6: operand[numoperands] = get_var(HIBYTE(PC)); PC++; break; default: goto END_OF_EXTENDED; /* inky says, "use the goto." */ } optypes <<= 2; /* Move the next two bits into position. */ } END_OF_EXTENDED: opcode += OFFSET_EXT; break; case 12: case 13: case 14: case 15: /* variable operand count */ maxoperands = 4; optypes = ((unsigned) HIBYTE(PC)) << 8; /* Shift left so our loop will */ /* be the same for both 4 and 8 operands */ PC++; if(opcode == 0xec || opcode == 0xfa) { /* call_vs2 and call_vn2 */ maxoperands = 8; optypes |= HIBYTE(PC); /* Fill the bottom 8 bits */ PC++; } for(numoperands = 0; numoperands < maxoperands; numoperands++) { switch(optypes & (3 << 14)) { /* Look at the highest two bits. */ case 0 << 14: operand[numoperands] = HIWORD(PC); PC+=ZWORD_SIZE; break; case 1 << 14: operand[numoperands] = HIBYTE(PC); PC++; break; case 2 << 14: operand[numoperands] = get_var(HIBYTE(PC)); PC++; break; default: goto END_OF_VARIABLE; } optypes <<= 2; /* Move the next two bits into position. */ } END_OF_VARIABLE: opcode += OFFSET_2OP - 0xc0; break; } opcodetable[opcode](); #ifdef ANDGLK if (do_autosave) { PC = save_pcp; oldPC = save_oldpcp; stack_pointer = save_sp; local_vars = save_fp; numoperands = save_zargc; frame_count = save_frame_count; for (zarg_ix=0; zarg_ix<8; ++zarg_ix) operand[zarg_ix] = save_zargs[zarg_ix]; optypes = save_optypes; maxoperands = save_maxoperands; savegame(); do_autosave = 0; AUTOSAVE_FILE[0] = '\0'; continue; } #endif } }
BOOL quetzal_stack_save(strid_t stream) { unsigned frame_num = 0; if(zversion == 6) frame_num++; if(!verify_stack()) { n_show_error(E_SAVE, "stack did not pass verification before saving", 0); return FALSE; } /* We have to look one ahead to see how much stack space a frame uses; when we get to the last frame, there will be no next frame, so this won't work if there wasn't a frame there earlier with the correct info. Add and remove a frame to make things happy */ add_stack_frame(0, 0, NULL, 0, 0); remove_stack_frame(); for(; frame_num <= frame_count; frame_num++) { unsigned n; int num_locals; unsigned stack_size; glui32 qframe[5]; const unsigned char argarray[8] = { b00000000, b00000001, b00000011, b00000111, b00001111, b00011111, b00111111, b01111111 }; qframe[qreturnPC] = stack_frames[frame_num].return_PC; qframe[qvar] = stack_frames[frame_num].result_variable; num_locals = stack_frames[frame_num].num_locals; if(num_locals > 15) { n_show_error(E_SAVE, "num_locals too big", num_locals); return FALSE; } qframe[qflags] = num_locals; if(stack_frames[frame_num].result_variable == -1) { qframe[qflags] |= b00010000; qframe[qvar] = 0; } if(stack_frames[frame_num].arguments > 7) { n_show_error(E_SAVE, "too many function arguments", stack_frames[frame_num].arguments); return FALSE; } qframe[qargs] = argarray[stack_frames[frame_num].arguments]; stack_size = (stack_frames[frame_num+1].stack_stack_start - stack_frames[frame_num].stack_stack_start - num_locals); qframe[qeval] = stack_size; if(frame_num == 0) { qframe[qreturnPC] = 0; qframe[qflags] = 0; qframe[qvar] = 0; qframe[qargs] = 0; } emptystruct(stream, qstackframe, qframe); for(n = 0; n < num_locals + stack_size; n++) { unsigned char v[ZWORD_SIZE]; zword z = stack_stack[stack_frames[frame_num].stack_stack_start + n]; MSBencodeZ(v, z); w_glk_put_buffer_stream(stream, (char *) v, ZWORD_SIZE); } } return TRUE; }
BOOL quetzal_stack_restore(strid_t stream, glui32 qsize) { glui32 i = 0; int num_frames = 0; kill_stack(); init_stack(1024, 128); while(i < qsize) { unsigned n; unsigned num_locals; zword locals[16]; int num_args; int var; glui32 qframe[5]; i += fillstruct(stream, qstackframe, qframe, NULL); if(qframe[qreturnPC] > total_size) { n_show_error(E_SAVE, "function return PC past end of memory", qframe[qreturnPC]); return FALSE; } if((qframe[qflags] & b11100000) != 0) { n_show_error(E_SAVE, "expected top bits of flag to be zero", qframe[qflags]); return FALSE; } var = qframe[qvar]; if(qframe[qflags] & b00010000) /* from a call_n */ var = -1; num_locals = qframe[qflags] & b00001111; if(num_locals > 15) { n_show_error(E_SAVE, "too many locals", num_locals); return FALSE; } num_args = 0; switch(qframe[qargs]) { default: n_show_error(E_SAVE, "invalid argument count", qframe[qargs]); return FALSE; case b01111111: num_args++; case b00111111: num_args++; case b00011111: num_args++; case b00001111: num_args++; case b00000111: num_args++; case b00000011: num_args++; case b00000001: num_args++; case b00000000: ; } for(n = 0; n < num_locals; n++) { unsigned char v[ZWORD_SIZE]; glk_get_buffer_stream(stream, (char *) v, ZWORD_SIZE); locals[n] = MSBdecodeZ(v); i+=ZWORD_SIZE; } if(zversion != 6 && num_frames == 0) ; /* dummy stack frame; don't really use it */ else add_stack_frame(qframe[qreturnPC], num_locals, locals, num_args, var); for(n = 0; n < qframe[qeval]; n++) { unsigned char v[ZWORD_SIZE]; glk_get_buffer_stream(stream, (char *) v, ZWORD_SIZE); stack_push(MSBdecodeZ(v)); i += ZWORD_SIZE; } num_frames++; } if(!verify_stack()) { n_show_error(E_SAVE, "restored stack fails verification", 0); return FALSE; } return TRUE; }
BOOL saveundo(BOOL in_instruction) { move_difference newdiff; strid_t stack; stream_result_t poo; if(!allow_saveundo) return TRUE; /* In games which provide @save_undo, we will have already issued a faked saveundo before the first @save_undo hits, since there hadn't been any @save_undo before the first read line. So when this happens, wipe the fake saveundo in favor of the real one */ if(in_instruction && movelist && !movelist->next && !movelist->PC_in_instruction) init_undo(); if(!quetzal_diff(z_memory, prevstate, dynamic_size, &newdiff.delta, &newdiff.deltalength, TRUE)) return FALSE; #ifdef PARANOID { char *newmem = (char *) n_malloc(dynamic_size); n_memcpy(newmem, prevstate, dynamic_size); quetzal_undiff(newmem, dynamic_size, newdiff.delta, newdiff.deltalength, TRUE); if(n_memcmp(z_memory, newmem, dynamic_size)) { n_show_error(E_SAVE, "save doesn't match itself", 0); } n_free(newmem); } #endif newdiff.PC = PC; newdiff.oldPC = oldPC; newdiff.PC_in_instruction = in_instruction; newdiff.stacklength = get_quetzal_stack_size(); newdiff.stackchunk = (zbyte *) n_malloc(newdiff.stacklength); stack = glk_stream_open_memory((char *) newdiff.stackchunk, newdiff.stacklength, filemode_Write, 0); if(!stack) { n_free(newdiff.delta); n_free(newdiff.stackchunk); return FALSE; } if(!quetzal_stack_save(stack)) { glk_stream_close(stack, NULL); n_free(newdiff.delta); n_free(newdiff.stackchunk); return FALSE; } glk_stream_close(stack, &poo); if(poo.writecount != newdiff.stacklength) { n_show_error(E_SAVE, "incorrect stack size assessment", poo.writecount); n_free(newdiff.delta); n_free(newdiff.stackchunk); return FALSE; } while(move_index-- > 0) { n_free(movelist->delta); n_free(movelist->stackchunk); LEremove(movelist); } LEadd(movelist, newdiff); move_index++; n_memcpy(prevstate, z_memory, dynamic_size); has_done_save_undo = TRUE; return TRUE; }