Cosmos/Demos/zMachine/Frotz.Net/source/Desktop/FrotzBase/ZTools/txio.cs
2016-06-09 10:34:36 -04:00

853 lines
No EOL
30 KiB
C#

/* txio.c
*
* I/O routines for Z code disassembler and story file dumper.
*
* Mark Howell 26 August 1992 howell_ma@movies.enet.dec.com
*
*/
using zword_t = System.UInt16;
using zbyte_t = System.Byte;
using System;
using System.IO;
namespace ZTools
{
public static class txio
{
internal static tx_h.zheader_t header;
internal static ulong story_scaler;
internal static ulong story_shift;
internal static ulong code_scaler;
internal static ulong code_shift;
internal static int property_mask;
internal static int property_size_mask;
internal static bool option_inform = false;
internal static int file_size = 0;
internal static String[] v1_lookup_table;
internal static String[] v3_lookup_table;
static String[] euro_substitute;
static String[] inform_euro_substitute;
internal static int lookup_table_loaded = 0;
internal static char[,] lookup_table;
static txio()
{
lookup_table = new char[3, 26];
v1_lookup_table = new String[] {
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
" 0123456789.,!?_#'\"/\\<-:()"
};
v3_lookup_table = new string[] {
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
" \n0123456789.,!?_#'\"/\\-:()"
};
euro_substitute = new string[] {
"ae", "oe", "ue", "Ae", "Oe", "Ue", "ss", ">>", "<<", "e",
"i", "y", "E", "I", "a", "e", "i", "o", "u", "y",
"A", "E", "I", "O", "U", "Y", "a", "e", "i", "o",
"u", "A", "E", "I", "O", "U", "a", "e", "i", "o",
"u", "A", "E", "I", "O", "U", "a", "A", "o", "O",
"a", "n", "o", "A", "N", "O", "ae", "AE", "c", "C",
"th", "th", "Th", "Th", "L", "oe", "OE", "!", "?"
};
inform_euro_substitute = new String[] {
"ae", "oe", "ue", "AE", "OE", "UE", "ss", ">>", "<<", ":e",
":i", ":y", ":E", ":I", "'a", "'e", "'i", "'o", "'u", "'y",
"'A", "'E", "'I", "'O", "'U", "'Y", "`a", "`e", "`i", "`o",
"`u", "`A", "`E", "`I", "`O", "`U", "^a", "^e", "^i", "^o",
"^u", "^A", "^E", "^I", "^O", "^U", "oa", "oA", "\\o", "\\O",
"~a", "~n", "~o", "~A", "~N", "~O", "ae", "AE", "cc", "cC",
"th", "et", "Th", "Et", "LL", "oe", "OE", "!!", "??"
};
}
const int TX_SCREEN_COLS = 79;
static char[] tx_line = null;
internal static int tx_line_pos = 0;
internal static int tx_col = 1;
internal static int tx_margin = 0;
internal static int tx_do_margin = 1;
internal static int tx_screen_cols = TX_SCREEN_COLS;
//internal class cache_entry_t
//{
// cache_entry_t flink;
// // uint page_number;
// zbyte_t[] data = new zbyte_t[tx_h.PAGE_SIZE];
//}
static Stream gfp = null;
//static cache_entry_t *cache = NULL;
// static uint current_data_page = 0;
//static cache_entry_t *current_data_cachep = NULL;
// static uint data_size;
static zbyte_t[] buffer;
internal static void configure(int min_version, int max_version)
{
// buffer = new zbyte_t[tx_h.PAGE_SIZE];
int i;
buffer = new zbyte_t[gfp.Length];
gfp.Read(buffer, 0, buffer.Length);
//#if !defined(lint)
// assert (sizeof (zheader_t) == 64);
// assert (sizeof (zheader_t) <= PAGE_SIZE);
//#endif /* !defined(lint) */
// read_page(0, buffer);
tx_h.datap = buffer;
header = new tx_h.zheader_t();
header.version = tx_h.get_byte(tx_h.H_VERSION);
header.config = tx_h.get_byte(tx_h.H_CONFIG);
header.release = tx_h.get_word(tx_h.H_RELEASE);
header.resident_size = tx_h.get_word(tx_h.H_RESIDENT_SIZE);
header.start_pc = tx_h.get_word(tx_h.H_START_PC);
header.dictionary = tx_h.get_word(tx_h.H_DICTIONARY);
header.objects = tx_h.get_word(tx_h.H_OBJECTS);
header.globals = tx_h.get_word(tx_h.H_GLOBALS);
header.dynamic_size = tx_h.get_word(tx_h.H_DYNAMIC_SIZE);
header.flags = tx_h.get_word(tx_h.H_FLAGS);
for (i = 0; i < header.serial.Length; i++)
header.serial[i] = tx_h.get_byte(tx_h.H_SERIAL + i);
header.abbreviations = tx_h.get_word(tx_h.H_ABBREVIATIONS);
header.file_size = tx_h.get_word(tx_h.H_FILE_SIZE);
header.checksum = tx_h.get_word(tx_h.H_CHECKSUM);
header.interpreter_number = tx_h.get_byte(tx_h.H_INTERPRETER_NUMBER);
header.interpreter_version = tx_h.get_byte(tx_h.H_INTERPRETER_VERSION);
header.screen_rows = tx_h.get_byte(tx_h.H_SCREEN_ROWS);
header.screen_columns = tx_h.get_byte(tx_h.H_SCREEN_COLUMNS);
header.screen_width = tx_h.get_word(tx_h.H_SCREEN_WIDTH);
header.screen_height = tx_h.get_word(tx_h.H_SCREEN_HEIGHT);
if (header.version != tx_h.V6)
{
header.font_width = (byte)tx_h.get_word(tx_h.H_FONT_WIDTH);
header.font_height = tx_h.get_byte(tx_h.H_FONT_HEIGHT);
}
else
{
header.font_width = (byte)tx_h.get_word(tx_h.H_FONT_HEIGHT);
header.font_height = tx_h.get_byte(tx_h.H_FONT_WIDTH);
}
header.routines_offset = tx_h.get_word(tx_h.H_ROUTINES_OFFSET);
header.strings_offset = tx_h.get_word(tx_h.H_STRINGS_OFFSET);
header.default_background = tx_h.get_byte(tx_h.H_DEFAULT_BACKGROUND);
header.default_foreground = tx_h.get_byte(tx_h.H_DEFAULT_FOREGROUND);
header.terminating_keys = tx_h.get_word(tx_h.H_TERMINATING_KEYS);
header.line_width = tx_h.get_word(tx_h.H_LINE_WIDTH);
header.specification_hi = tx_h.get_byte(tx_h.H_SPECIFICATION_HI);
header.specification_lo = tx_h.get_byte(tx_h.H_SPECIFICATION_LO);
header.alphabet = tx_h.get_word(tx_h.H_ALPHABET);
header.mouse_table = tx_h.get_word(tx_h.H_MOUSE_TABLE);
for (i = 0; i < header.name.Length; i++)
header.name[i] = tx_h.get_byte(tx_h.H_NAME + i);
if ((uint)header.version < (uint)min_version ||
(uint)header.version > (uint)max_version ||
((uint)header.config & tx_h.CONFIG_BYTE_SWAPPED) != 0)
{
throw new ArgumentException("\nFatal: wrong game or version\n");
}
if ((uint)header.version < tx_h.V4)
{
story_scaler = 2;
story_shift = 1;
code_scaler = 2;
code_shift = 1;
property_mask = tx_h.P3_MAX_PROPERTIES - 1;
property_size_mask = 0xe0;
}
else if ((uint)header.version < tx_h.V6)
{
story_scaler = 4;
story_shift = 2;
code_scaler = 4;
code_shift = 2;
property_mask = tx_h.P4_MAX_PROPERTIES - 1;
property_size_mask = 0x3f;
}
else if ((uint)header.version < tx_h.V8)
{
story_scaler = 8;
story_shift = 3;
code_scaler = 4;
code_shift = 2;
property_mask = tx_h.P4_MAX_PROPERTIES - 1;
property_size_mask = 0x3f;
}
else
{
story_scaler = 8;
story_shift = 3;
code_scaler = 8;
code_shift = 3;
property_mask = tx_h.P4_MAX_PROPERTIES - 1;
property_size_mask = 0x3f;
}
/* Calculate the file size */
if ((uint)header.file_size == 0)
{
throw new ArgumentException("Can't handle files with no length. Giving up!");
// file_size = get_story_size();
}
else if ((uint)header.version <= tx_h.V3)
file_size = header.file_size * 2;
else if ((uint)header.version <= tx_h.V5)
file_size = header.file_size * 4;
else
file_size = header.file_size * 8;
}/* configure */
internal static void open_story(byte[] story) {
gfp = new MemoryStream(story);
}
internal static void open_story(string storyname)
{
gfp = new FileStream(storyname, FileMode.Open);
if (gfp == null)
{
Console.Error.WriteLine("Fatal: game file not found\n");
}
} /* open_story */
internal static void close_story()
{
if (gfp != null)
{
gfp.Close();
}
}/* close_story */
internal static void read_page(uint page, byte[] buffer)
{
int bytes_to_read;
if (file_size == 0)
bytes_to_read = 64;
else if (page != (uint)(file_size / tx_h.PAGE_SIZE))
bytes_to_read = tx_h.PAGE_SIZE;
else
bytes_to_read = (int)(file_size & tx_h.PAGE_MASK);
gfp.Position = page * tx_h.PAGE_SIZE;
gfp.Read(buffer, 0, bytes_to_read);
} /* read_page */
//internal static void load_cache()
//{
// // ulong file_size;
// // uint i, file_pages, data_pages;
// // cache_entry_t *cachep;
// // /* Must have at least one cache page for memory calculation */
// // cachep = (cache_entry_t *) malloc (sizeof (cache_entry_t));
// // if (cachep == NULL) {
// // (void) fprintf (stderr, "\nFatal: insufficient memory\n");
// // exit (EXIT_FAILURE);
// // }
// // cachep->flink = cache;
// // cachep->page_number = 0;
// // cache = cachep;
// // /* Calculate dynamic cache pages required */
// // data_pages = ((uint) header.resident_size + PAGE_MASK) >> PAGE_SHIFT;
// // data_size = data_pages * PAGE_SIZE;
// // file_size = (ulong) header.file_size * story_scaler;
// // file_pages = (uint) ((file_size + PAGE_MASK) >> PAGE_SHIFT);
// // /* Allocate static data area and initialise it */
// // datap = (zbyte_t *) malloc ((size_t) data_size);
// // if (datap == NULL) {
// // (void) fprintf (stderr, "\nFatal: insufficient memory\n");
// // exit (EXIT_FAILURE);
// // }
// // for (i = 0; i < data_pages; i++)
// // read_page (i, &datap[i * PAGE_SIZE]);
// // /* Allocate cache pages and initialise them */
// // for (i = data_pages; cachep != NULL && i < file_pages && i < data_pages + MAX_CACHE; i++) {
// // cachep = (cache_entry_t *) malloc (sizeof (cache_entry_t));
// // if (cachep != NULL) {
// // cachep->flink = cache;
// // cachep->page_number = i;
// // read_page (cachep->page_number, cachep->data);
// // cache = cachep;
// // }
// // }
//} /* load_cache */
internal static zword_t read_data_word(ref ulong addr)
{
uint w;
w = (uint)read_data_byte(ref addr) << 8;
w |= read_data_byte(ref addr);
return (zword_t)w;
}/* txio.read_data_word */
internal static zbyte_t read_data_byte(ref ulong addr)
{
// uint page_number, page_offset;
zbyte_t value;
//if (addr < (ulong)txio.data_size)
// value = buffer[addr];
//else
//{
// page_number = (uint)(addr >> tx_h.PAGE_SHIFT);
// page_offset = (uint)(addr & (ulong)tx_h.PAGE_MASK);
// if (page_number != current_data_page)
// {
// current_data_cachep = update_cache(page_number);
// current_data_page = page_number;
// }
// value = current_data_cachep->data[page_offset];
//}
value = buffer[addr];
addr++;
return (value);
}/* txio.read_data_byte */
internal static int decode_text(ref ulong address)
{
int i, j, char_count, synonym_flag, synonym = 0, ascii_flag, ascii = 0;
int data, code, shift_state, shift_lock;
ulong addr;
/*
* Load correct character translation table for this game.
*/
if (lookup_table_loaded == 0)
{
for (i = 0; i < 3; i++)
{
for (j = 0; j < 26; j++)
{
if ((uint)header.alphabet > 0)
{
lookup_table[i, j] = (char)tx_h.get_byte(txio.header.alphabet + (i * 26) + j);
}
else
{
if ((uint)txio.header.version == tx_h.V1)
lookup_table[i, j] = v1_lookup_table[i][j];
else
lookup_table[i, j] = v3_lookup_table[i][j];
}
if (option_inform && lookup_table[i, j] == '\"')
{
lookup_table[i, j] = '~';
}
}
lookup_table_loaded = 1;
}
}
/* Set state variables */
shift_state = 0;
shift_lock = 0;
char_count = 0;
ascii_flag = 0;
synonym_flag = 0;
do
{
/*
* Read one 16 bit word. Each word contains three 5 bit codes. If the
* high bit is set then this is the last word in the string.
*/
data = txio.read_data_word(ref address);
for (i = 10; i >= 0; i -= 5)
{
/* Get code, high bits first */
code = (data >> i) & 0x1f;
/* Synonym codes */
if (synonym_flag > 0)
{
synonym_flag = 0;
synonym = (synonym - 1) * 64;
addr = (ulong)tx_h.get_word(header.abbreviations + synonym + (code * 2)) * 2;
char_count += txio.decode_text(ref addr);
shift_state = shift_lock;
/* ASCII codes */
}
else if (ascii_flag > 0)
{
/*
* If this is the first part ASCII code then remember it.
* Because the codes are only 5 bits you need two codes to make
* one eight bit ASCII character. The first code contains the
* top 3 bits. The second code contains the bottom 5 bits.
*/
if (ascii_flag++ == 1)
ascii = code << 5;
/*
* If this is the second part ASCII code then assemble the
* character from the two codes and output it.
*/
else
{
ascii_flag = 0;
txio.tx_printf("{0}", (char)(ascii | code));
char_count++;
}
/* Character codes */
}
else if (code > 5)
{
code -= 6;
/*
* If this is character 0 in the punctuation set then the next two
* codes make an ASCII character.
*/
if (shift_state == 2 && code == 0)
ascii_flag = 1;
/*
* If this is character 1 in the punctuation set then this
* is a new line.
*/
else if (shift_state == 2 && code == 1 && (uint)header.version > tx_h.V1)
txio.tx_printf("{0}", (option_inform) ? '^' : '\n');
/*
* This is a normal character so select it from the character
* table appropriate for the current shift state.
*/
else
{
txio.tx_printf("{0}", (char)lookup_table[shift_state, code]);
char_count++;
}
shift_state = shift_lock;
/* Special codes 0 to 5 */
}
else
{
/*
* Space: 0
*
* Output a space character.
*
*/
if (code == 0)
{
txio.tx_printf(" ");
char_count++;
}
else
{
/*
* The use of the synonym and shift codes is the only difference between
* the different versions.
*/
if ((uint)header.version < tx_h.V3)
{
/*
* Newline or synonym: 1
*
* Output a newline character or set synonym flag.
*
*/
if (code == 1)
{
if ((uint)header.version == tx_h.V1)
{
txio.tx_printf("{0}", (option_inform) ? '^' : '\n');
char_count++;
}
else
{
synonym_flag = 1;
synonym = code;
}
/*
* Shift keys: 2, 3, 4 or 5
*
* Shift keys 2 & 3 only shift the next character and can be used regardless of
* the state of the shift lock. Shift keys 4 & 5 lock the shift until reset.
*
* The following code implements the the shift code state transitions:
*
* +-------------+-------------+-------------+-------------+
* | Shift State | Lock State |
* +-------------+-------------+-------------+-------------+-------------+
* | Code | 2 | 3 | 4 | 5 |
* +-------------+-------------+-------------+-------------+-------------+
* | lowercase | uppercase | punctuation | uppercase | punctuation |
* | uppercase | punctuation | lowercase | punctuation | lowercase |
* | punctuation | lowercase | uppercase | lowercase | uppercase |
* +-------------+-------------+-------------+-------------+-------------+
*
*/
}
else
{
if (code < 4)
shift_state = (shift_lock + code + 2) % 3;
else
shift_lock = shift_state = (shift_lock + code) % 3;
}
}
else
{
/*
* Synonym table: 1, 2 or 3
*
* Selects which of three synonym tables the synonym
* code following in the next code is to use.
*
*/
if (code < 4)
{
synonym_flag = 1;
synonym = code;
/*
* Shift key: 4 or 5
*
* Selects the shift state for the next character,
* either uppercase (4) or punctuation (5). The shift
* state automatically gets reset back to lowercase for
* V3+ games after the next character is output.
*
*/
}
else
{
shift_state = code - 3;
shift_lock = 0;
}
}
}
}
}
} while ((data & 0x8000) == 0);
return (char_count);
}/* txio.decode_text */
//#ifdef __STDC__
//static cache_entry_t *update_cache (uint page_number)
//#else
//static cache_entry_t *update_cache (page_number)
//uint page_number;
//#endif
//{
// cache_entry_t *cachep, *lastp;
// for (lastp = cache, cachep = cache;
// cachep->flink != NULL &&
// cachep->page_number &&
// cachep->page_number != page_number;
// lastp = cachep, cachep = cachep->flink)
// ;
// if (cachep->page_number != page_number) {
// if (cachep->flink == NULL && cachep->page_number) {
// if (current_data_page == (uint) cachep->page_number)
// current_data_page = 0;
// }
// cachep->page_number = page_number;
// read_page (page_number, cachep->data);
// }
// if (lastp != cache) {
// lastp->flink = cachep->flink;
// cachep->flink = cache;
// cache = cachep;
// }
// return (cachep);
//}/* update_cache */
///*
// * get_story_size
// *
// * Calculate the size of the game file. Only used for very old games that do not
// * have the game file size in the header.
// *
// */
//#ifdef __STDC__
//static ulong get_story_size (void)
//#else
//static ulong get_story_size ()
//#endif
//{
// ulong file_length;
// /* Read whole file to calculate file size */
// rewind (gfp);
// for (file_length = 0; fgetc (gfp) != EOF; file_length++)
// ;
// rewind (gfp);
// return (file_length);
//}/* get_story_size */
internal static void tx_printf(String format, params Object[] args)
{
int count, i;
if (tx_screen_cols != 0)
{
if (tx_line == null || tx_line.Length == 0)
{
tx_line = new char[TX_SCREEN_COLS];
}
String temp = String.Format(format, args);
count = temp.Length;
if (count > TX_SCREEN_COLS)
{
throw new ArgumentException("\nFatal: buffer space overflow\n");
}
for (i = 0; i < count; i++)
{
tx_write_char(temp[i]);
}
}
else
sb.AppendFormat(format, args);
}/* txio.tx_printf */
private static void write_high_zscii(int c)
{
// static zword_t unicode_table[256];
// static int unicode_table_loaded;
// int unicode_table_addr;
// int length, i;
// if (!unicode_table_loaded) {
// if (header.mouse_table && (tx_h.get_word(header.mouse_table) > 2)) {
// unicode_table_addr = tx_h.get_word(header.mouse_table + 6);
// if (unicode_table_addr) {
// length = tx_h.get_byte(unicode_table_addr);
// for (i = 0; i < unicode_table_addr; i++)
// unicode_table[i + 155] = tx_h.get_word(unicode_table_addr + 1 + i*2);
// }
// }
// unicode_table_loaded = 1;
// }
// if ((c <= 0xdf) && !unicode_table[c]) {
// if (option_inform)
// txio.tx_printf("@%s", inform_euro_substitute[c - 0x9b]);
// else
// txio.tx_printf (euro_substitute[c - 0x9b]);
// }
// else /* no non-inform version of these. */
// txio.tx_printf("@{%x}", unicode_table[c]);
}
static void tx_write_char(int c)
{
int i;
int cp;
/* In V6 games a tab is a paragraph indent gap and a vertical tab is
an inter-sentence gap. Both can be set to a space for readability */
if (c == '\v' || c == '\t')
c = ' ';
// /* European characters should be substituted by their replacements. */
if (c >= 0x9b && c <= 0xfb)
{
write_high_zscii(c);
return;
}
if (tx_col == tx_screen_cols + 1 || c == '\n')
{
tx_do_margin = 1;
if (tx_line_pos < tx_line.Length) tx_line[tx_line_pos++] = '\0';
int eol = tx_line.ToString().IndexOf('\0');
if (eol == -1) eol = tx_line_pos;
String temp = new String(tx_line, 0, tx_line_pos);
cp = temp.LastIndexOf(' ');
if (c == ' ' || c == '\n' || cp == -1)
{
sb.Append(temp);
sb.Append('\n');
tx_line_pos = 0;
tx_col = 1;
clear_tx_line();
return;
}
else
{
tx_line[cp++] = '\0';
sb.Append(temp.Substring(0, cp));
sb.Append('\n');
tx_line_pos = 0;
tx_col = 1;
txio.tx_printf("{0}", temp.Substring(cp));
}
}
if (tx_do_margin > 0)
{
tx_do_margin = 0;
for (i = 1; i < tx_margin; i++)
tx_write_char(' ');
}
tx_line[tx_line_pos++] = (char)c;
tx_col++;
}/* tx_write_char */
private static void clear_tx_line()
{
for (int i = 0; i < tx_line.Length; i++)
{
tx_line[i] = ' ';
}
}
internal static void tx_fix_margin(int flag)
{
txio.tx_margin = (flag) > 0 ? txio.tx_col : 0;
}/* txio.tx_fix_margin */
internal static void tx_set_width(int width)
{
if (width > tx_screen_cols)
{
if (tx_line != null)
{
tx_line[tx_line_pos++] = '\0';
sb.Append(tx_line);
}
tx_line_pos = 0;
// free (tx_line);
// tx_line = NULL;
}
tx_screen_cols = width;
}/* tx_set_width */
internal static System.Text.StringBuilder sb;
internal static void startStringBuilder()
{
sb = new System.Text.StringBuilder();
}
internal static String getTextFromStringBuilder()
{
String text = sb.ToString();
sb.Length = 0;
foreach (char c in text)
{
if (c != '\0')
{
sb.Append(c);
}
}
text = sb.ToString();
sb.Length = 0;
return text;
}
}
}