mirror of
https://github.com/danbulant/Cosmos
synced 2026-05-27 22:12:25 +00:00
1281 lines
40 KiB
C#
1281 lines
40 KiB
C#
/* fastmem.c - Memory related functions (fast version without virtual memory)
|
|
* Copyright (c) 1995-1997 Stefan Jokisch
|
|
*
|
|
* This file is part of Frotz.
|
|
*
|
|
* Frotz is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Frotz is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
/*
|
|
* New undo mechanism added by Jim Dunleavy <jim.dunleavy@erha.ie>
|
|
*/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using zword = System.UInt16;
|
|
using zbyte = System.Byte;
|
|
|
|
using Frotz;
|
|
using Frotz.Constants;
|
|
|
|
namespace Frotz.Generic
|
|
{
|
|
|
|
internal struct RecordStruct
|
|
{
|
|
internal Story story_id;
|
|
internal zword release;
|
|
internal string serial;
|
|
|
|
public RecordStruct(Story story_id, zword release, string serial)
|
|
{
|
|
this.story_id = story_id;
|
|
this.release = release;
|
|
this.serial = serial;
|
|
|
|
}
|
|
}
|
|
|
|
internal static class FastMem
|
|
{
|
|
|
|
internal static RecordStruct[] records = {
|
|
new RecordStruct(Story.SHERLOCK, 97, "871026"),
|
|
new RecordStruct( Story.SHERLOCK, 21, "871214" ),
|
|
new RecordStruct( Story.SHERLOCK, 22, "880112" ),
|
|
new RecordStruct( Story.SHERLOCK, 26, "880127" ),
|
|
new RecordStruct( Story.SHERLOCK, 4, "880324" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 1, "870412" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 1, "870715" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 47, "870915" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 49, "870917" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 51, "870923" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 57, "871221" ),
|
|
new RecordStruct( Story.BEYOND_ZORK, 60, "880610" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 0, "870831" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 96, "880224" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 153, "880510" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 242, "880830" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 242, "880901" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 296, "881019" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 366, "890323" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 383, "890602" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 387, "890612" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 392, "890714" ),
|
|
new RecordStruct( Story.ZORK_ZERO, 393, "890714" ),
|
|
new RecordStruct( Story.SHOGUN, 292, "890314" ),
|
|
new RecordStruct( Story.SHOGUN, 295, "890321" ),
|
|
new RecordStruct( Story.SHOGUN, 311, "890510" ),
|
|
new RecordStruct( Story.SHOGUN, 320, "890627" ),
|
|
new RecordStruct( Story.SHOGUN, 321, "890629" ),
|
|
new RecordStruct( Story.SHOGUN, 322, "890706" ),
|
|
new RecordStruct( Story.ARTHUR, 40, "890502" ),
|
|
new RecordStruct( Story.ARTHUR, 41, "890504" ),
|
|
new RecordStruct( Story.ARTHUR, 54, "890606" ),
|
|
new RecordStruct( Story.ARTHUR, 63, "890622" ),
|
|
new RecordStruct( Story.ARTHUR, 74, "890714" ),
|
|
new RecordStruct( Story.JOURNEY, 46, "880603" ),
|
|
new RecordStruct( Story.JOURNEY, 2, "890303" ),
|
|
new RecordStruct( Story.JOURNEY, 26, "890316" ),
|
|
new RecordStruct( Story.JOURNEY, 30, "890322" ),
|
|
new RecordStruct( Story.JOURNEY, 51, "890522" ),
|
|
new RecordStruct( Story.JOURNEY, 54, "890526" ),
|
|
new RecordStruct( Story.JOURNEY, 77, "890616" ),
|
|
new RecordStruct( Story.JOURNEY, 79, "890627" ),
|
|
new RecordStruct( Story.JOURNEY, 83, "890706" ),
|
|
new RecordStruct( Story.LURKING_HORROR, 203, "870506" ),
|
|
new RecordStruct( Story.LURKING_HORROR, 219, "870912" ),
|
|
new RecordStruct( Story.LURKING_HORROR, 221, "870918" ),
|
|
new RecordStruct( Story.AMFV, 47, "850313" ),
|
|
new RecordStruct( Story.UNKNOWN, 0, "------" )
|
|
};
|
|
|
|
internal static string save_name = General.DEFAULT_SAVE_NAME;
|
|
internal static string auxilary_name = General.DEFAULT_AUXILARY_NAME;
|
|
|
|
internal static zbyte[] ZMData;
|
|
internal static zword ZMData_checksum = 0;
|
|
internal static byte[] storyData;
|
|
|
|
internal static long zmp = 0;
|
|
internal static long pcp = 0;
|
|
|
|
//private static System.IO.MemoryStream story_fp = null;
|
|
|
|
static bool first_restart = true;
|
|
static long init_fp_pos = 0;
|
|
private static System.IO.Stream story_fp;
|
|
|
|
#region zmp & pcp
|
|
|
|
internal static byte LO(zword v)
|
|
{
|
|
return (byte)(v & 0xff);
|
|
}
|
|
|
|
internal static byte HI(zword v)
|
|
{
|
|
return (byte)(v >> 8);
|
|
}
|
|
|
|
internal static void SET_WORD(long addr, zword v)
|
|
{
|
|
ZMData[addr] = HI(v);
|
|
ZMData[addr + 1] = LO(v);
|
|
|
|
DebugState.Output("ZMP: {0} -> {1}", addr, v);
|
|
}
|
|
|
|
internal static void LOW_WORD(long addr, out byte v)
|
|
{
|
|
v = (byte)((ZMData[addr] << 8) | ZMData[addr + 1]);
|
|
}
|
|
|
|
internal static void LOW_WORD(long addr, out zword v)
|
|
{
|
|
v = (ushort)((ZMData[addr] << 8) | ZMData[addr + 1]);
|
|
}
|
|
|
|
// TODO I'm suprised that they return the same thing
|
|
internal static void HIGH_WORD(long addr, out zword v)
|
|
{
|
|
LOW_WORD(addr, out v);
|
|
}
|
|
|
|
internal static void CODE_WORD(out zword v)
|
|
{
|
|
v = (zword)(ZMData[pcp] << 8 | ZMData[pcp + 1]);
|
|
pcp += 2;
|
|
}
|
|
|
|
internal static void SET_BYTE(long addr, byte v)
|
|
{
|
|
ZMData[addr] = v;
|
|
DebugState.Output("ZMP: {0} -> {1}", addr, v);
|
|
}
|
|
|
|
internal static void CODE_BYTE(out byte v)
|
|
{
|
|
v = ZMData[pcp++];
|
|
}
|
|
|
|
internal static void LOW_BYTE(long addr, out byte v)
|
|
{
|
|
v = ZMData[addr];
|
|
}
|
|
|
|
internal static void GET_PC(out long v)
|
|
{
|
|
v = (long)(pcp - zmp);
|
|
}
|
|
|
|
internal static void SET_PC(long v)
|
|
{
|
|
pcp = (long)(zmp + v);
|
|
}
|
|
#endregion
|
|
|
|
/*
|
|
* Data for the undo mechanism.
|
|
* This undo mechanism is based on the scheme used in Evin Robertson's
|
|
* Nitfol interpreter.
|
|
* Undo blocks are stored as differences between states.
|
|
*/
|
|
|
|
//typedef struct undo_struct undo_t;
|
|
internal class undo_struct
|
|
{
|
|
internal long pc;
|
|
internal long diff_size;
|
|
internal zword frame_count;
|
|
internal zword stack_size;
|
|
internal zword frame_offset;
|
|
/* undo diff and stack data follow */
|
|
|
|
internal long sp;
|
|
internal zword[] stack;
|
|
internal byte[] undo_data;
|
|
};
|
|
|
|
// static undo_struct first_undo = null, last_undo = null, curr_undo = null;
|
|
//static zbyte *undo_mem = NULL, *prev_zmp, *undo_diff;
|
|
|
|
internal static zbyte[] prev_zmp;
|
|
internal static zbyte[] undo_diff;
|
|
internal static System.Collections.Generic.List<undo_struct> undo_mem;
|
|
|
|
static int undo_count = 0;
|
|
|
|
/*
|
|
* get_header_extension
|
|
*
|
|
* Read a value from the header extension (former mouse table).
|
|
*
|
|
*/
|
|
|
|
internal static zword get_header_extension(int entry)
|
|
{
|
|
zword addr;
|
|
zword val;
|
|
|
|
if (main.h_extension_table == 0 || entry > main.hx_table_size)
|
|
return 0;
|
|
|
|
addr = (zword)(main.h_extension_table + 2 * entry);
|
|
LOW_WORD(addr, out val);
|
|
|
|
return val;
|
|
|
|
}/* get_header_extension */
|
|
|
|
/*
|
|
* set_header_extension
|
|
*
|
|
* Set an entry in the header extension (former mouse table).
|
|
*
|
|
*/
|
|
|
|
internal static void set_header_extension(int entry, zword val)
|
|
{
|
|
zword addr;
|
|
|
|
if (main.h_extension_table == 0 || entry > main.hx_table_size)
|
|
return;
|
|
|
|
addr = (zword)(main.h_extension_table + 2 * entry);
|
|
SET_WORD(addr, val);
|
|
|
|
}/* set_header_extension */
|
|
|
|
/*
|
|
* restart_header
|
|
*
|
|
* Set all header fields which hold information about the interpreter.
|
|
*
|
|
*/
|
|
|
|
internal static void restart_header()
|
|
{
|
|
zword screen_x_size;
|
|
zword screen_y_size;
|
|
zbyte font_x_size;
|
|
zbyte font_y_size;
|
|
|
|
int i;
|
|
|
|
SET_BYTE(ZMachine.H_CONFIG, main.h_config);
|
|
SET_WORD(ZMachine.H_FLAGS, main.h_flags);
|
|
|
|
if (main.h_version >= ZMachine.V4)
|
|
{
|
|
SET_BYTE(ZMachine.H_INTERPRETER_NUMBER, main.h_interpreter_number);
|
|
SET_BYTE(ZMachine.H_INTERPRETER_VERSION, main.h_interpreter_version);
|
|
SET_BYTE(ZMachine.H_SCREEN_ROWS, main.h_screen_rows);
|
|
SET_BYTE(ZMachine.H_SCREEN_COLS, main.h_screen_cols);
|
|
}
|
|
|
|
/* It's less trouble to use font size 1x1 for V5 games, especially
|
|
because of a bug in the unreleased German version of "Zork 1" */
|
|
|
|
if (main.h_version != ZMachine.V6)
|
|
{
|
|
screen_x_size = main.h_screen_cols;
|
|
screen_y_size = main.h_screen_rows;
|
|
font_x_size = 1;
|
|
font_y_size = 1;
|
|
}
|
|
else
|
|
{
|
|
screen_x_size = main.h_screen_width;
|
|
screen_y_size = main.h_screen_height;
|
|
font_x_size = main.h_font_width;
|
|
font_y_size = main.h_font_height;
|
|
}
|
|
|
|
if (main.h_version >= ZMachine.V5)
|
|
{
|
|
SET_WORD(ZMachine.H_SCREEN_WIDTH, screen_x_size);
|
|
SET_WORD(ZMachine.H_SCREEN_HEIGHT, screen_y_size);
|
|
SET_BYTE(ZMachine.H_FONT_HEIGHT, font_y_size);
|
|
SET_BYTE(ZMachine.H_FONT_WIDTH, font_x_size);
|
|
SET_BYTE(ZMachine.H_DEFAULT_BACKGROUND, main.h_default_background);
|
|
SET_BYTE(ZMachine.H_DEFAULT_FOREGROUND, main.h_default_foreground);
|
|
}
|
|
|
|
if ((main.h_version >= ZMachine.V3) && (main.h_user_name[0] != 0))
|
|
{
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
storeb((zword)(ZMachine.H_USER_NAME + i), main.h_user_name[i]);
|
|
}
|
|
}
|
|
SET_BYTE(ZMachine.H_STANDARD_HIGH, main.h_standard_high);
|
|
SET_BYTE(ZMachine.H_STANDARD_LOW, main.h_standard_low);
|
|
|
|
set_header_extension(ZMachine.HX_FLAGS, main.hx_flags);
|
|
set_header_extension(ZMachine.HX_FORE_COLOUR, main.hx_fore_colour);
|
|
set_header_extension(ZMachine.HX_BACK_COLOUR, main.hx_back_colour);
|
|
}/* restart_header */
|
|
|
|
/*
|
|
* init_memory
|
|
*
|
|
* Allocate memory and load the story file.
|
|
*
|
|
*/
|
|
|
|
internal static void init_memory()
|
|
{
|
|
long size;
|
|
zword addr;
|
|
zword n;
|
|
int i, j;
|
|
|
|
// TODO Abstract this part
|
|
/* Open story file */
|
|
// story_fp = new System.IO.FileStream(main.story_name, System.IO.FileMode.Open, System.IO.FileAccess.Read);
|
|
if (os_.preloadedFileData != null)
|
|
{
|
|
storyData = os_.preloadedFileData;
|
|
story_fp = new MemoryStream(storyData);
|
|
}
|
|
else
|
|
{
|
|
//story_fp = os_.path_open(main.story_data);
|
|
//if (story_fp == null)
|
|
//{
|
|
// os_.fatal("Cannot open story file");
|
|
//}
|
|
//init_fp_pos = story_fp.Position;
|
|
|
|
//storyData = new byte[story_fp.Length];
|
|
//story_fp.Read(storyData, 0, storyData.Length);
|
|
//story_fp.Position = 0;
|
|
throw new Exception("File loading doesn't work yet");
|
|
}
|
|
DebugState.Output("Starting story: {0}", main.story_name);
|
|
|
|
/* Allocate memory for story header */
|
|
|
|
ZMData = new byte[64];
|
|
Frotz.Other.ZMath.clearArray(ZMData);
|
|
|
|
/* Load header into memory */
|
|
if (story_fp.Read(ZMData, 0, 64) != 64)
|
|
{
|
|
os_.fatal("Story file read error");
|
|
}
|
|
|
|
/* Copy header fields to global variables */
|
|
LOW_BYTE(ZMachine.H_VERSION, out main.h_version);
|
|
|
|
if (main.h_version < ZMachine.V1 || main.h_version > ZMachine.V8)
|
|
{
|
|
os_.fatal("Unknown Z-code version");
|
|
}
|
|
|
|
LOW_BYTE(ZMachine.H_CONFIG, out main.h_config);
|
|
if (main.h_version == ZMachine.V3 && ((main.h_config & ZMachine.CONFIG_BYTE_SWAPPED) != 0))
|
|
{
|
|
os_.fatal("Byte swapped story file");
|
|
}
|
|
|
|
LOW_WORD(ZMachine.H_RELEASE, out main.h_release);
|
|
LOW_WORD(ZMachine.H_RESIDENT_SIZE, out main.h_resident_size);
|
|
LOW_WORD(ZMachine.H_START_PC, out main.h_start_pc);
|
|
LOW_WORD(ZMachine.H_DICTIONARY, out main.h_dictionary);
|
|
LOW_WORD(ZMachine.H_OBJECTS, out main.h_objects);
|
|
LOW_WORD(ZMachine.H_GLOBALS, out main.h_globals);
|
|
LOW_WORD(ZMachine.H_DYNAMIC_SIZE, out main.h_dynamic_size);
|
|
LOW_WORD(ZMachine.H_FLAGS, out main.h_flags);
|
|
|
|
for (i = 0, addr = ZMachine.H_SERIAL; i < 6; i++, addr++)
|
|
{
|
|
LOW_BYTE(addr, out main.h_serial[i]);
|
|
}
|
|
// TODO serial might need to be a char
|
|
|
|
/* Auto-detect buggy story files that need special fixes */
|
|
|
|
main.story_id = Story.UNKNOWN;
|
|
|
|
for (i = 0; records[i].story_id != Story.UNKNOWN; i++)
|
|
{
|
|
|
|
if (main.h_release == records[i].release)
|
|
{
|
|
|
|
for (j = 0; j < 6; j++)
|
|
if (main.h_serial[j] != records[i].serial[j])
|
|
goto no_match;
|
|
|
|
main.story_id = records[i].story_id;
|
|
|
|
}
|
|
|
|
no_match: ; /* null statement */
|
|
|
|
}
|
|
|
|
LOW_WORD(ZMachine.H_ABBREVIATIONS, out main.h_abbreviations);
|
|
LOW_WORD(ZMachine.H_FILE_SIZE, out main.h_file_size);
|
|
|
|
/* Calculate story file size in bytes */
|
|
if (main.h_file_size != 0)
|
|
{
|
|
main.story_size = 2 * main.h_file_size;
|
|
|
|
if (main.h_version >= ZMachine.V4)
|
|
{
|
|
main.story_size *= 2;
|
|
}
|
|
|
|
if (main.h_version >= ZMachine.V6)
|
|
{
|
|
main.story_size *= 2;
|
|
}
|
|
|
|
if (main.story_id == Story.AMFV && main.h_release == 47)
|
|
{
|
|
main.story_size = 2 * main.h_file_size;
|
|
}
|
|
else if (main.story_size > 0)
|
|
{/* os_path_open() set the size */
|
|
}
|
|
else
|
|
{/* some old games lack the file size entry */
|
|
main.story_size = story_fp.Length - init_fp_pos;
|
|
story_fp.Position = init_fp_pos + 64;
|
|
}
|
|
|
|
LOW_WORD(ZMachine.H_CHECKSUM, out main.h_checksum);
|
|
LOW_WORD(ZMachine.H_ALPHABET, out main.h_alphabet);
|
|
LOW_WORD(ZMachine.H_FUNCTIONS_OFFSET, out main.h_functions_offset);
|
|
LOW_WORD(ZMachine.H_STRINGS_OFFSET, out main.h_strings_offset);
|
|
LOW_WORD(ZMachine.H_TERMINATING_KEYS, out main.h_terminating_keys);
|
|
LOW_WORD(ZMachine.H_EXTENSION_TABLE, out main.h_extension_table);
|
|
|
|
/* Zork Zero beta and Macintosh versions don't have the graphics flag set */
|
|
|
|
if (main.story_id == Story.ZORK_ZERO)
|
|
{
|
|
if (main.h_release == 96 || main.h_release == 153 ||
|
|
main.h_release == 242 || main.h_release == 296)
|
|
{
|
|
main.h_flags |= ZMachine.GRAPHICS_FLAG;
|
|
}
|
|
}
|
|
|
|
/* Adjust opcode tables */
|
|
|
|
if (main.h_version <= ZMachine.V4)
|
|
{
|
|
Process.op0_opcodes[0x09] = new Process.zinstruction(Variable.z_pop);
|
|
Process.op0_opcodes[0x0f] = new Process.zinstruction(Math.z_not);
|
|
}
|
|
else
|
|
{
|
|
Process.op0_opcodes[0x09] = new Process.zinstruction(Process.z_catch);
|
|
Process.op0_opcodes[0x0f] = new Process.zinstruction(Process.z_call_n);
|
|
}
|
|
|
|
/* Allocate memory for story data */
|
|
|
|
byte[] temp = new byte[ZMData.Length];
|
|
Frotz.Other.ZMath.clearArray(temp);
|
|
|
|
System.Array.Copy(ZMData, temp, ZMData.Length);
|
|
|
|
ZMData = new byte[main.story_size];
|
|
Frotz.Other.ZMath.clearArray(ZMData);
|
|
System.Array.Copy(temp, ZMData, temp.Length);
|
|
|
|
/* Load story file in chunks of 32KB */
|
|
|
|
n = 0x8000;
|
|
|
|
for (size = 64; size < main.story_size; size += n)
|
|
{
|
|
if (main.story_size - size < 0x8000) n = (ushort)(main.story_size - size);
|
|
SET_PC(size);
|
|
|
|
int read = story_fp.Read(ZMData, (int)pcp, n);
|
|
|
|
if (read != n) os_.fatal("Story file read error");
|
|
}
|
|
|
|
// Take a moment to calculate the checksum of the story file in case verify is called
|
|
ZMData_checksum = 0;
|
|
for (int k = 64; k < ZMData.Length; k++)
|
|
{
|
|
ZMData_checksum += ZMData[k];
|
|
}
|
|
}
|
|
|
|
DebugState.Output("Story Size: {0}", main.story_size);
|
|
|
|
first_restart = true;
|
|
|
|
/* Read header extension table */
|
|
|
|
main.hx_table_size = get_header_extension(ZMachine.HX_TABLE_SIZE);
|
|
main.hx_unicode_table = get_header_extension(ZMachine.HX_UNICODE_TABLE);
|
|
main.hx_flags = get_header_extension(ZMachine.HX_FLAGS);
|
|
}/* init_memory */
|
|
|
|
/*
|
|
* init_undo
|
|
*
|
|
* Allocate memory for multiple undo. It is important not to occupy
|
|
* all the memory available, since the IO interface may need memory
|
|
* during the game, e.g. for loading sounds or pictures.
|
|
*
|
|
*/
|
|
|
|
internal static void init_undo()
|
|
{
|
|
prev_zmp = new byte[ZMData.Length];
|
|
undo_diff = new byte[ZMData.Length];
|
|
|
|
undo_mem = new System.Collections.Generic.List<undo_struct>();
|
|
|
|
System.Array.Copy(ZMData, prev_zmp, main.h_dynamic_size);
|
|
}/* init_undo */
|
|
|
|
/*
|
|
* free_undo
|
|
*
|
|
* Free count undo blocks from the beginning of the undo list.
|
|
*
|
|
*/
|
|
|
|
internal static void free_undo(int count)
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
undo_mem.RemoveAt(0);
|
|
}
|
|
|
|
}/* free_undo */
|
|
|
|
/*
|
|
* reset_memory
|
|
*
|
|
* Close the story file and deallocate memory.
|
|
*
|
|
*/
|
|
|
|
internal static void reset_memory()
|
|
{
|
|
if (story_fp != null) story_fp.Close();
|
|
|
|
undo_mem.Clear();
|
|
|
|
}/* reset_memory */
|
|
|
|
/*
|
|
* storeb
|
|
*
|
|
* Write a byte value to the dynamic Z-machine memory.
|
|
*
|
|
*/
|
|
|
|
internal static void storeb(zword addr, zbyte value)
|
|
{
|
|
|
|
if (addr >= main.h_dynamic_size)
|
|
Err.runtime_error(ErrorCodes.ERR_STORE_RANGE);
|
|
|
|
if (addr == ZMachine.H_FLAGS + 1)
|
|
{ /* flags register is modified */
|
|
|
|
main.h_flags &= (zword)(~(ZMachine.SCRIPTING_FLAG | ZMachine.FIXED_FONT_FLAG));
|
|
main.h_flags |= (zword)(value & (ZMachine.SCRIPTING_FLAG | ZMachine.FIXED_FONT_FLAG));
|
|
|
|
if ((value & ZMachine.SCRIPTING_FLAG) > 0)
|
|
{
|
|
if (!main.ostream_script)
|
|
Files.script_open();
|
|
}
|
|
else
|
|
{
|
|
if (main.ostream_script)
|
|
Files.script_close();
|
|
}
|
|
|
|
Screen.refresh_text_style();
|
|
|
|
}
|
|
|
|
SET_BYTE(addr, value);
|
|
|
|
DebugState.Output("storeb: {0} -> {1}", addr, value);
|
|
}/* storeb */
|
|
|
|
/*
|
|
* storew
|
|
*
|
|
* Write a word value to the dynamic Z-machine memory.
|
|
*
|
|
*/
|
|
|
|
internal static void storew(zword addr, zword value)
|
|
{
|
|
|
|
storeb((zword)(addr + 0), HI(value));
|
|
storeb((zword)(addr + 1), LO(value));
|
|
|
|
}/* storew */
|
|
|
|
/*
|
|
* z_restart, re-load dynamic area, clear the stack and set the PC.
|
|
*
|
|
* no zargs used
|
|
*
|
|
*/
|
|
|
|
internal static void z_restart()
|
|
{
|
|
Buffer.flush_buffer();
|
|
|
|
os_.restart_game(ZMachine.RESTART_BEGIN);
|
|
|
|
Random.seed_random(0);
|
|
|
|
if (!first_restart)
|
|
{
|
|
story_fp.Position = init_fp_pos;
|
|
|
|
int read = story_fp.Read(ZMData, 0, main.h_dynamic_size);
|
|
if (read != main.h_dynamic_size)
|
|
{
|
|
os_.fatal("Story file read error");
|
|
}
|
|
}
|
|
else first_restart = false;
|
|
|
|
restart_header();
|
|
Screen.restart_screen();
|
|
|
|
main.sp = main.fp = General.STACK_SIZE; // TODO Critical to make sure this logic works; sp = fp = stack + STACK_SIZE;
|
|
|
|
main.frame_count = 0;
|
|
|
|
if (main.h_version != ZMachine.V6)
|
|
{
|
|
|
|
zword pc = main.h_start_pc;
|
|
FastMem.SET_PC((int)pc);
|
|
|
|
}
|
|
else
|
|
{
|
|
Process.call(main.h_start_pc, 0, 0, 0);
|
|
}
|
|
|
|
os_.restart_game(ZMachine.RESTART_END);
|
|
|
|
}/* z_restart */
|
|
|
|
/*
|
|
* get_default_name
|
|
*
|
|
* Read a default file name from the memory of the Z-machine and
|
|
* copy it to a string.
|
|
*
|
|
*/
|
|
|
|
internal static string get_default_name(zword addr)
|
|
{
|
|
|
|
if (addr != 0)
|
|
{
|
|
|
|
var sb = new System.Text.StringBuilder();
|
|
|
|
zbyte len;
|
|
int i;
|
|
|
|
FastMem.LOW_BYTE(addr, out len);
|
|
addr++;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
|
|
zbyte c;
|
|
|
|
FastMem.LOW_BYTE(addr, out c);
|
|
addr++;
|
|
|
|
if (c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
|
|
// default_name[i] = c;
|
|
sb.Append((char)c);
|
|
|
|
}
|
|
|
|
// default_name[i] = 0;
|
|
|
|
if (sb.ToString().IndexOf(".") == -1)
|
|
{
|
|
sb.Append(".AUX");
|
|
return sb.ToString();
|
|
}
|
|
else
|
|
return auxilary_name;
|
|
}
|
|
return null;
|
|
|
|
}/* get_default_name */
|
|
|
|
/*
|
|
* z_restore, restore [a part of] a Z-machine state from disk
|
|
*
|
|
* zargs[0] = address of area to restore (optional)
|
|
* zargs[1] = number of bytes to restore
|
|
* zargs[2] = address of suggested file name
|
|
* zargs[3] = whether to ask for confirmation of the file name
|
|
*
|
|
*/
|
|
|
|
internal static void z_restore()
|
|
{
|
|
string new_name;
|
|
// string default_name;
|
|
System.IO.FileStream gfp;
|
|
|
|
zword success = 0;
|
|
|
|
if (Process.zargc != 0)
|
|
{
|
|
os_.fail("Need to implement optional args in z_restore");
|
|
gfp = null;
|
|
///* Get the file name */
|
|
|
|
//get_default_name (default_name, (FastMem.zargc >= 3) ? FastMem.zargs[2] : 0);
|
|
|
|
//if ((FastMem.zargc >= 4) ? FastMem.zargs[3] : 1) {
|
|
|
|
// if (os_read_file_name (new_name, default_name, ZMachine.FILE_LOAD_AUX) == 0)
|
|
// goto finished;
|
|
|
|
// strcpy (auxilary_name, new_name);
|
|
|
|
//} else strcpy (new_name, default_name);
|
|
|
|
///* Open auxilary file */
|
|
|
|
//if ((gfp = fopen (new_name, "rb")) == NULL)
|
|
// goto finished;
|
|
|
|
///* Load auxilary file */
|
|
|
|
//success = fread (zmp + zargs[0], 1, zargs[1], gfp);
|
|
|
|
///* Close auxilary file */
|
|
|
|
//fclose (gfp);
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
/* Get the file name */
|
|
|
|
if (!os_.read_file_name(out new_name, save_name, FileTypes.FILE_RESTORE))
|
|
goto finished;
|
|
|
|
save_name = new_name;
|
|
|
|
/* Open game file */
|
|
gfp = new System.IO.FileStream(new_name, System.IO.FileMode.Open);
|
|
if (gfp == null) goto finished;
|
|
|
|
if (main.option_save_quetzal == true)
|
|
{
|
|
success = Quetzal.restore_quetzal(gfp, story_fp);
|
|
|
|
}
|
|
else
|
|
{
|
|
os_.fail("Need to implement old style save");
|
|
/* Load game file */
|
|
|
|
// release = (unsigned) fgetc (gfp) << 8;
|
|
// release |= fgetc (gfp);
|
|
|
|
// (void) fgetc (gfp);
|
|
// (void) fgetc (gfp);
|
|
|
|
// /* Check the release number */
|
|
|
|
// if (release == h_release) {
|
|
|
|
// pc = (long) fgetc (gfp) << 16;
|
|
// pc |= (unsigned) fgetc (gfp) << 8;
|
|
// pc |= fgetc (gfp);
|
|
|
|
// SET_PC (pc)
|
|
|
|
// sp = stack + (fgetc (gfp) << 8);
|
|
// sp += fgetc (gfp);
|
|
// fp = stack + (fgetc (gfp) << 8);
|
|
// fp += fgetc (gfp);
|
|
|
|
// for (i = (int) (sp - stack); i < STACK_SIZE; i++) {
|
|
// stack[i] = (unsigned) fgetc (gfp) << 8;
|
|
// stack[i] |= fgetc (gfp);
|
|
// }
|
|
|
|
// fseek (story_fp, init_fp_pos, SEEK_SET);
|
|
|
|
// for (addr = 0; addr < h_dynamic_size; addr++) {
|
|
// int skip = fgetc (gfp);
|
|
// for (i = 0; i < skip; i++)
|
|
// zmp[addr++] = fgetc (story_fp);
|
|
// zmp[addr] = fgetc (gfp);
|
|
// (void) fgetc (story_fp);
|
|
// }
|
|
|
|
// /* Check for errors */
|
|
|
|
// if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size)
|
|
// success = -1;
|
|
// else
|
|
|
|
// /* Success */
|
|
|
|
// success = 2;
|
|
|
|
// } else print_string ("Invalid save file\n");
|
|
}
|
|
}
|
|
if ((short)success >= 0 && success != zword.MaxValue)
|
|
{
|
|
/* Close game file */
|
|
|
|
gfp.Close();
|
|
|
|
if ((short)success > 0)
|
|
{
|
|
zbyte old_screen_rows;
|
|
zbyte old_screen_cols;
|
|
|
|
/* In V3, reset the upper window. */
|
|
if (main.h_version == ZMachine.V3)
|
|
Screen.split_window(0);
|
|
|
|
LOW_BYTE(ZMachine.H_SCREEN_ROWS, out old_screen_rows);
|
|
LOW_BYTE(ZMachine.H_SCREEN_COLS, out old_screen_cols);
|
|
|
|
/* Reload cached header fields. */
|
|
restart_header();
|
|
|
|
/*
|
|
* Since QUETZAL files may be saved on many different machines,
|
|
* the screen sizes may vary a lot. Erasing the status window
|
|
* seems to cover up most of the resulting badness.
|
|
*/
|
|
if (main.h_version > ZMachine.V3 && main.h_version != ZMachine.V6
|
|
&& (main.h_screen_rows != old_screen_rows
|
|
|| main.h_screen_cols != old_screen_cols))
|
|
Screen.erase_window(1);
|
|
}
|
|
}
|
|
else
|
|
os_.fatal("Error reading save file");
|
|
|
|
|
|
finished:
|
|
|
|
if (main.h_version <= ZMachine.V3)
|
|
Process.branch(success > 0);
|
|
else
|
|
Process.store(success);
|
|
}/* z_restore */
|
|
|
|
/*
|
|
* mem_diff
|
|
*
|
|
* Set diff to a Quetzal-like difference between a and b,
|
|
* copying a to b as we go. It is assumed that diff points to a
|
|
* buffer which is large enough to hold the diff.
|
|
* mem_size is the number of bytes to compare.
|
|
* Returns the number of bytes copied to diff.
|
|
*
|
|
*/
|
|
|
|
static long mem_diff(zbyte[] a, zbyte[] b, zword mem_size, zbyte[] diff)
|
|
{
|
|
zword size = mem_size;
|
|
int dPtr = 0;
|
|
uint j;
|
|
zbyte c = 0;
|
|
|
|
int aPtr = 0;
|
|
int bPtr = 0;
|
|
|
|
for (; ; )
|
|
{
|
|
for (j = 0; size > 0 && (c = (zbyte)(a[aPtr++] ^ b[bPtr++])) == 0; j++)
|
|
size--;
|
|
if (size == 0) break;
|
|
|
|
size--;
|
|
|
|
if (j > 0x8000)
|
|
{
|
|
diff[dPtr++] = 0;
|
|
diff[dPtr++] = 0xff;
|
|
diff[dPtr++] = 0xff;
|
|
j -= 0x8000;
|
|
}
|
|
|
|
if (j > 0)
|
|
{
|
|
diff[dPtr++] = 0;
|
|
j--;
|
|
|
|
if (j <= 0x7f)
|
|
{
|
|
diff[dPtr++] = (byte)j;
|
|
}
|
|
else
|
|
{
|
|
diff[dPtr++] = (byte)((j & 0x7f) | 0x80);
|
|
diff[dPtr++] = (byte)((j & 0x7f80) >> 7);
|
|
}
|
|
}
|
|
diff[dPtr++] = c;
|
|
b[bPtr - 1] ^= c;
|
|
}
|
|
return dPtr;
|
|
|
|
}/* mem_diff */
|
|
|
|
/*
|
|
* mem_undiff
|
|
*
|
|
* Applies a quetzal-like diff to dest
|
|
*
|
|
*/
|
|
|
|
static void mem_undiff(zbyte[] diff, long diff_length, zbyte[] dest)
|
|
{
|
|
zbyte c;
|
|
uint diffPtr = 0;
|
|
uint destPtr = 0;
|
|
|
|
while (diff_length > 0)
|
|
{
|
|
c = diff[diffPtr++];
|
|
diff_length--;
|
|
if (c == 0)
|
|
{
|
|
uint runlen;
|
|
|
|
if (diff_length == 0) // TODO I'm not sure about this logic
|
|
return; /* Incomplete run */
|
|
runlen = diff[diffPtr++];
|
|
diff_length--;
|
|
if ((runlen & 0x80) > 0)
|
|
{
|
|
if (diff_length == 0)
|
|
return; /* Incomplete extended run */
|
|
c = diff[diffPtr++];
|
|
diff_length--;
|
|
runlen = (runlen & 0x7f) | (((uint)c) << 7);
|
|
}
|
|
|
|
destPtr += runlen + 1;
|
|
}
|
|
else
|
|
{
|
|
dest[destPtr++] ^= c;
|
|
}
|
|
}
|
|
|
|
}/* mem_undiff */
|
|
|
|
/*
|
|
* restore_undo
|
|
*
|
|
* This function does the dirty work for z_restore_undo.
|
|
*
|
|
*/
|
|
|
|
internal static int restore_undo()
|
|
{
|
|
if (main.option_undo_slots == 0) /* undo feature unavailable */
|
|
return -1;
|
|
|
|
if (undo_mem.Count == 0) return 0;
|
|
|
|
/* undo possible */
|
|
|
|
undo_struct undo = undo_mem[undo_mem.Count - 1];
|
|
|
|
System.Array.Copy(prev_zmp, ZMData, main.h_dynamic_size);
|
|
SET_PC(undo.pc);
|
|
main.sp = undo.sp;
|
|
main.fp = undo.frame_offset;
|
|
main.frame_count = undo.frame_count;
|
|
|
|
mem_undiff(undo.undo_data, undo.diff_size, prev_zmp);
|
|
|
|
// System.Array.Copy(undo.stack, 0, main.stack, undo.sp, undo.stack.Length);
|
|
Frotz.Other.ArrayCopy.Copy(undo.stack, 0, main.stack, undo.sp, undo.stack.Length);
|
|
|
|
undo_mem.Remove(undo);
|
|
|
|
restart_header();
|
|
|
|
return 2;
|
|
|
|
}/* restore_undo */
|
|
|
|
/*
|
|
* z_restore_undo, restore a Z-machine state from memory.
|
|
*
|
|
* no zargs used
|
|
*
|
|
*/
|
|
|
|
internal static void z_restore_undo()
|
|
{
|
|
|
|
Process.store((zword)restore_undo());
|
|
|
|
}/* z_restore_undo */
|
|
|
|
/*
|
|
* z_save, save [a part of] the Z-machine state to disk.
|
|
*
|
|
* zargs[0] = address of memory area to save (optional)
|
|
* zargs[1] = number of bytes to save
|
|
* zargs[2] = address of suggested file name
|
|
* zargs[3] = whether to ask for confirmation of the file name
|
|
*
|
|
*/
|
|
|
|
internal static void z_save()
|
|
{
|
|
string new_name;
|
|
string default_name;
|
|
System.IO.FileStream gfp;
|
|
|
|
zword success = 0;
|
|
|
|
if (Process.zargc != 0)
|
|
{
|
|
|
|
/* Get the file name */
|
|
|
|
default_name = get_default_name((zword)((Process.zargc >= 3) ? Process.zargs[2] : 0));
|
|
|
|
// if ((zargc >= 4) ? zargs[3] : 1) {
|
|
|
|
// if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0)
|
|
// goto finished;
|
|
|
|
// strcpy (auxilary_name, new_name);
|
|
|
|
// } else strcpy (new_name, default_name);
|
|
|
|
// /* Open auxilary file */
|
|
|
|
// if ((gfp = fopen (new_name, "wb")) == NULL)
|
|
// goto finished;
|
|
|
|
// /* Write auxilary file */
|
|
|
|
// success = fwrite (zmp + zargs[0], zargs[1], 1, gfp);
|
|
|
|
// /* Close auxilary file */
|
|
|
|
// fclose (gfp);
|
|
os_.fail("need to implement option save arguments");
|
|
}
|
|
else
|
|
{
|
|
if (!os_.read_file_name(out new_name, save_name, FileTypes.FILE_SAVE))
|
|
goto finished;
|
|
|
|
save_name = new_name;
|
|
|
|
/* Open game file */
|
|
|
|
if ((gfp = new System.IO.FileStream(new_name, System.IO.FileMode.OpenOrCreate)) == null)
|
|
goto finished;
|
|
|
|
if (main.option_save_quetzal == true)
|
|
{
|
|
//success = Quetzal.save_quetzal(gfp, story_fp);
|
|
|
|
}
|
|
else
|
|
{
|
|
os_.fail("Need to implement old style save");
|
|
|
|
// /* Write game file */
|
|
|
|
// fputc ((int) hi (h_release), gfp);
|
|
// fputc ((int) lo (h_release), gfp);
|
|
// fputc ((int) hi (h_checksum), gfp);
|
|
// fputc ((int) lo (h_checksum), gfp);
|
|
|
|
// GET_PC (pc)
|
|
|
|
// fputc ((int) (pc >> 16) & 0xff, gfp);
|
|
// fputc ((int) (pc >> 8) & 0xff, gfp);
|
|
// fputc ((int) (pc) & 0xff, gfp);
|
|
|
|
// nsp = (int) (sp - stack);
|
|
// nfp = (int) (fp - stack);
|
|
|
|
// fputc ((int) hi (nsp), gfp);
|
|
// fputc ((int) lo (nsp), gfp);
|
|
// fputc ((int) hi (nfp), gfp);
|
|
// fputc ((int) lo (nfp), gfp);
|
|
|
|
// for (i = nsp; i < STACK_SIZE; i++) {
|
|
// fputc ((int) hi (stack[i]), gfp);
|
|
// fputc ((int) lo (stack[i]), gfp);
|
|
// }
|
|
|
|
// fseek (story_fp, init_fp_pos, SEEK_SET);
|
|
|
|
// for (addr = 0, skip = 0; addr < h_dynamic_size; addr++)
|
|
// if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) {
|
|
// fputc (skip, gfp);
|
|
// fputc (zmp[addr], gfp);
|
|
// skip = 0;
|
|
// } else skip++;
|
|
}
|
|
|
|
/* Close game file and check for errors */
|
|
|
|
gfp.Close();
|
|
// TODO Not sure what to do with these
|
|
// if (gfp.Close() ) { // || ferror(story_fp)) {
|
|
// Text.print_string("Error writing save file\n");
|
|
// goto finished;
|
|
//}
|
|
|
|
/* Success */
|
|
|
|
success = 1;
|
|
|
|
}
|
|
|
|
finished:
|
|
|
|
if (main.h_version <= ZMachine.V3)
|
|
Process.branch(success > 0);
|
|
else
|
|
Process.store(success);
|
|
}/* z_save */
|
|
|
|
/*
|
|
* save_undo
|
|
*
|
|
* This function does the dirty work for z_save_undo.
|
|
*
|
|
*/
|
|
|
|
internal static int save_undo()
|
|
{
|
|
long diff_size;
|
|
int stack_size;
|
|
undo_struct p;
|
|
|
|
if (main.option_undo_slots == 0) /* undo feature unavailable */
|
|
return -1;
|
|
|
|
/* save undo possible */
|
|
|
|
if (undo_count == main.option_undo_slots)
|
|
free_undo(1);
|
|
|
|
p = new undo_struct();
|
|
|
|
diff_size = mem_diff(ZMData, prev_zmp, main.h_dynamic_size, undo_diff);
|
|
stack_size = main.stack.Length;
|
|
|
|
GET_PC(out p.pc);
|
|
p.frame_count = main.frame_count;
|
|
p.diff_size = diff_size;
|
|
p.stack_size = (zword)stack_size;
|
|
p.frame_offset = (zword)main.fp; // p->frame_offset = fp - stack;
|
|
|
|
// p.undo_data = undo_diff;
|
|
p.undo_data = new zbyte[diff_size];
|
|
// System.Array.Copy(undo_diff, p.undo_data, diff_size);
|
|
Frotz.Other.ArrayCopy.Copy(undo_diff, 0, p.undo_data, 0, diff_size);
|
|
|
|
p.stack = new zword[main.stack.Length - main.sp];
|
|
// System.Array.Copy(main.stack, main.sp, p.stack, 0, main.stack.Length - main.sp);
|
|
Frotz.Other.ArrayCopy.Copy(main.stack, main.sp, p.stack, 0, main.stack.Length - main.sp);
|
|
p.sp = main.sp;
|
|
|
|
undo_mem.Add(p);
|
|
|
|
return 1;
|
|
}/* save_undo */
|
|
|
|
/*
|
|
* z_save_undo, save the current Z-machine state for a future undo.
|
|
*
|
|
* no zargs used
|
|
*
|
|
*/
|
|
|
|
internal static void z_save_undo()
|
|
{
|
|
|
|
Process.store((zword)save_undo());
|
|
|
|
}/* z_save_undo */
|
|
|
|
/*
|
|
* z_verify, check the story file integrity.
|
|
*
|
|
* no zargs used
|
|
*
|
|
*/
|
|
|
|
internal static void z_verify()
|
|
{
|
|
//zword checksum = 0;
|
|
//long i;
|
|
|
|
/* Sum all bytes in story file except header bytes */
|
|
|
|
//for (i = 64; i < main.story_size; i++)
|
|
//{
|
|
// checksum += FastMem.ZMData_original[i];
|
|
//}
|
|
|
|
//for (i = 64; i < story_size; i++)
|
|
// checksum += fgetc(story_fp);
|
|
|
|
/* Branch if the checksums are equal */
|
|
|
|
Process.branch(ZMData_checksum == main.h_checksum);
|
|
|
|
}/* z_verify */
|
|
}
|
|
}
|