gsplus/src/win32_host_fst.c

1869 lines
44 KiB
C

/*
GSPLUS - Advanced Apple IIGS Emulator Environment
Based on the KEGS emulator written by Kent Dickey
See COPYRIGHT.txt for Copyright information
See LICENSE.txt for license (GPL v2)
*/
#define _WIN32_WINNT 0x0600 // vista+
#include <Windows.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>
#include "defc.h"
#include "gsos.h"
#include "fst.h"
#include "host_common.h"
#ifdef _MSC_VER
#define strcasecmp stricmp
#define strncasecmp strnicmp
#endif
extern Engine_reg engine;
/* direct page offsets */
#define dp_call_number 0x30
#define dp_param_blk_ptr 0x32
#define dp_dev1_num 0x36
#define dp_dev2_num 0x38
#define dp_path1_ptr 0x3a
#define dp_path2_ptr 0x3e
#define dp_path_flag 0x42
#define global_buffer 0x009a00
struct directory {
int displacement;
int num_entries;
char *entries[1];
};
struct fd_entry {
struct fd_entry *next;
char *path;
int cookie;
int type;
int access;
HANDLE handle;
int translate;
struct directory *dir;
};
static void free_directory(struct directory *dd);
static struct directory *read_directory(const char *path, word16 *error);
#define COOKIE_BASE 0x8000
static word32 cookies[32] = { 0 };
static int alloc_cookie() {
for (int i = 0; i < 32; ++i) {
word32 x = cookies[i];
for (int j = 0; j < 32; ++j, x >>= 1) {
if (x & 0x01) continue;
cookies[i] |= (1 << j);
return COOKIE_BASE + (i * 32 + j);
}
}
return -1;
}
static int free_cookie(int cookie) {
if (cookie < COOKIE_BASE) return -1;
cookie -= COOKIE_BASE;
if (cookie >= 32 * 32) return -1;
int chunk = cookie / 32;
int offset = 1 << (cookie % 32);
word32 x = cookies[chunk];
if ((x & offset) == 0) return -1;
x &= ~offset;
cookies[chunk] = x;
return 0;
}
static struct fd_entry *fd_head = NULL;
static struct fd_entry *find_fd(int cookie) {
struct fd_entry *head = fd_head;
while (head) {
if (head->cookie == cookie) return head;
head = head->next;
}
return NULL;
}
static void free_fd(struct fd_entry *e) {
if (!e) return;
if (e->cookie) free_cookie(e->cookie);
if (e->handle != INVALID_HANDLE_VALUE) CloseHandle(e->handle);
if (e->dir) free_directory(e->dir);
free(e->path);
free(e);
}
static struct fd_entry *alloc_fd() {
struct fd_entry *e = calloc(sizeof(struct fd_entry), 1);
e->handle = INVALID_HANDLE_VALUE;
return e;
}
static word32 remove_fd(int cookie) {
word32 rv = invalidRefNum;
struct fd_entry *prev = NULL;
struct fd_entry *head = fd_head;
while (head) {
if (head->cookie == cookie) {
if (prev) prev->next = head->next;
else fd_head = head->next;
free_fd(head);
rv = 0;
break;
}
prev = head;
head = head->next;
}
return rv;
}
/*
* if this is an absolute path, verify and skip past :Host:
*
*/
static const char *check_path(const char *in, word32 *error) {
word32 tmp;
if (!error) error = &tmp;
*error = 0;
if (!in) return "";
/* check for .. */
const char *cp = in;
do {
while (*cp == '/') ++cp;
if (cp[0] == '.' && cp[1] == '.' && (cp[2] == '/' || cp[2] == 0))
{
*error = badPathSyntax;
return NULL;
}
cp = strchr(cp, '/');
} while(cp);
if (in[0] != '/') return in;
if (strncasecmp(in, "/Host", 5) == 0 && (in[5] == '/' || in[5] == 0)) {
in += 5;
while (*in == '/') ++in;
return in;
}
*error = volNotFound;
return NULL;
}
static char * get_gsstr(word32 ptr) {
if (!ptr) return NULL;
int length = get_memory16_c(ptr, 0);
ptr += 2;
char *str = host_gc_malloc(length + 1);
for (int i = 0; i < length; ++i) {
char c = get_memory_c(ptr+i, 0);
if (c == ':') c = '/';
str[i] = c;
}
str[length] = 0;
return str;
}
static char * get_pstr(word32 ptr) {
if (!ptr) return NULL;
int length = get_memory_c(ptr, 0);
ptr += 1;
char *str = host_gc_malloc(length + 1);
for (int i = 0; i < length; ++i) {
char c = get_memory_c(ptr+i, 0);
if (c == ':') c = '/';
str[i] = c;
}
str[length] = 0;
return str;
}
static word32 set_gsstr(word32 ptr, const char *str) {
if (!ptr) return paramRangeErr;
int l = str ? strlen(str) : 0;
word32 cap = get_memory16_c(ptr, 0);
ptr += 2;
if (cap < 4) return paramRangeErr;
set_memory16_c(ptr, l, 0);
ptr += 2;
if (cap < l + 4) return buffTooSmall;
for (int i = 0; i < l; ++i) {
char c = *str++;
if (c == '/') c = ':';
set_memory_c(ptr++, c, 0);
}
return 0;
}
static word32 set_gsstr_truncate(word32 ptr, const char *str) {
if (!ptr) return paramRangeErr;
int l = str ? strlen(str) : 0;
word32 cap = get_memory16_c(ptr, 0);
ptr += 2;
if (cap < 4) return paramRangeErr;
set_memory16_c(ptr, l, 0);
ptr += 2;
// get dir entry copies data even
// if buffTooSmall...
int rv = 0;
if (cap < l + 4) {
l = cap - 4;
rv = buffTooSmall;
}
for (int i = 0; i < l; ++i) {
char c = *str++;
if (c == '/') c = ':';
set_memory_c(ptr++, c, 0);
}
return rv;
}
static word32 set_pstr(word32 ptr, const char *str) {
if (!ptr) return paramRangeErr;
int l = str ? strlen(str) : 0;
if (l > 255) return buffTooSmall;
// / is the pascal separator.
set_memory_c(ptr++, l, 0);
for (int i = 0; i < l; ++i) {
set_memory_c(ptr++, *str++, 0);
}
return 0;
}
static word32 set_option_list(word32 ptr, word16 fstID, const byte *data, int size) {
if (!ptr) return 0;
// totalSize
word32 cap = get_memory16_c(ptr, 0);
ptr += 2;
if (cap < 4) return paramRangeErr;
// reqSize
set_memory16_c(ptr, size + 2, 0);
ptr += 2;
if (cap < size + 6) return buffTooSmall;
// fileSysID.
set_memory16_c(ptr, fstID, 0);
ptr += 2;
for (int i = 0; i < size; ++i)
set_memory_c(ptr++, *data++, 0);
return 0;
}
static char *get_path1(void) {
word32 direct = engine.direct;
word16 flags = get_memory16_c(direct + dp_path_flag, 0);
if (flags & (1 << 14))
return get_gsstr( get_memory24_c(direct + dp_path1_ptr, 0));
return NULL;
}
static char *get_path2(void) {
word32 direct = engine.direct;
word16 flags = get_memory16_c(direct + dp_path_flag, 0);
if (flags & (1 << 6))
return get_gsstr( get_memory24_c(direct + dp_path2_ptr, 0));
return NULL;
}
static word32 fst_shutdown(void) {
// close any remaining files.
struct fd_entry *head = fd_head;
while (head) {
struct fd_entry *next = head->next;
free_fd(head);
head = next;
}
host_shutdown();
return 0;
}
static word32 fst_startup(void) {
// if restart, close any previous files.
fst_shutdown();
memset(&cookies, 0, sizeof(cookies));
return host_startup();
}
static word32 fst_create(int class, const char *path) {
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
struct file_info fi;
memset(&fi, 0, sizeof(fi));
word16 pcount = 0;
if (class) {
pcount = get_memory16_c(pb, 0);
if (pcount >= 2) fi.access = get_memory16_c(pb + CreateRecGS_access, 0);
if (pcount >= 3) fi.file_type = get_memory16_c(pb + CreateRecGS_fileType, 0);
if (pcount >= 4) fi.aux_type = get_memory32_c(pb + CreateRecGS_auxType, 0);
if (pcount >= 5) fi.storage_type = get_memory16_c(pb + CreateRecGS_storageType, 0);
if (pcount >= 6) fi.eof = get_memory32_c(pb + CreateRecGS_eof, 0);
if (pcount >= 7) fi.resource_eof = get_memory32_c(pb + CreateRecGS_resourceEOF, 0);
if (pcount >= 4) {
afp_init(&fi.afp, fi.file_type, fi.aux_type);
fi.has_fi = 1;
}
} else {
fi.access = get_memory16_c(pb + CreateRec_fAccess, 0);
fi.file_type = get_memory16_c(pb + CreateRec_fileType, 0);
fi.aux_type = get_memory32_c(pb + CreateRec_auxType, 0);
fi.storage_type = get_memory16_c(pb + CreateRec_storageType, 0);
fi.create_date = host_get_date_time(pb + CreateRec_createDate);
afp_init(&fi.afp, fi.file_type, fi.aux_type);
fi.has_fi = 1;
}
int ok;
if (fi.storage_type == 0 && fi.file_type == 0x0f)
fi.storage_type = 0x0d;
if (fi.storage_type == 0x0d) {
ok = CreateDirectory(path, NULL);
if (!ok) return host_map_win32_error(GetLastError());
if (class) {
if (pcount >= 5) set_memory16_c(pb + CreateRecGS_storageType, fi.storage_type, 0);
} else {
set_memory16_c(pb + CreateRec_storageType, fi.storage_type, 0);
}
return 0;
}
if (fi.storage_type <= 3 || fi.storage_type == 0x05) {
// normal file.
// 0x05 is an extended/resource file but we don't do anything special.
HANDLE h = CreateFile(path,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (h == INVALID_HANDLE_VALUE) return host_map_win32_error(GetLastError());
// set ftype, auxtype...
host_set_file_info(path, &fi);
CloseHandle(h);
fi.storage_type = 1;
if (class) {
if (pcount >= 5) set_memory16_c(pb + CreateRecGS_storageType, fi.storage_type, 0);
} else {
set_memory16_c(pb + CreateRec_storageType, fi.storage_type, 0);
}
return 0;
}
if (fi.storage_type == 0x8005) {
// convert an existing file to an extended file.
// this checks that the file exists and has a 0-sized resource.
word32 rv = host_get_file_info(path, &fi);
if (rv) return rv;
if (fi.storage_type == extendedFile) return resExistsErr;
if (fi.storage_type < seedling || fi.storage_type > tree) return resAddErr;
return 0;
}
// other storage types?
return badStoreType;
}
static word32 fst_destroy(int class, const char *path) {
word16 rv = 0;
BOOL ok = 0;
word16 type = host_storage_type(path, &rv);
if (rv) return rv;
if (g_cfg_host_read_only) return drvrWrtProt;
switch(type) {
case 0: return fileNotFound;
case 0x0f: return badStoreType;
default: return badStoreType;
case directoryFile:
ok = RemoveDirectory(path);
break;
case sapling:
case seedling:
case tree:
case extendedFile:
ok = DeleteFile(path);
break;
}
if (!ok) return host_map_win32_error(GetLastError());
return 0;
}
static word32 fst_set_file_info(int class, const char *path) {
word16 rv = 0;
word16 type = host_storage_type(path, &rv);
if (rv) return rv;
if (g_cfg_host_read_only) return drvrWrtProt;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
struct file_info fi;
memset(&fi, 0, sizeof(fi));
// load up existing file types / finder info.
host_get_file_xinfo(path, &fi);
word32 option_list = 0;
if (class) {
word16 pcount = get_memory16_c(pb, 0);
if (pcount >= 2) fi.access = get_memory16_c(pb + FileInfoRecGS_access, 0);
if (pcount >= 3) fi.file_type = get_memory16_c(pb + FileInfoRecGS_fileType, 0);
if (pcount >= 4) fi.aux_type = get_memory32_c(pb + FileInfoRecGS_auxType, 0);
// reserved.
//if (pcount >= 5) fi.storage_type = get_memory16_c(pb + FileInfoRecGS_storageType, 0);
if (pcount >= 6) fi.create_date = host_get_date_time_rec(pb + FileInfoRecGS_createDateTime);
if (pcount >= 7) fi.modified_date = host_get_date_time_rec(pb + FileInfoRecGS_modDateTime);
if (pcount >= 8) option_list = get_memory24_c(pb + FileInfoRecGS_optionList, 0);
// remainder reserved
if (fi.has_fi) {
if (pcount < 4) fi.aux_type = fi.afp.prodos_aux_type;
if (pcount < 3) fi.file_type = fi.afp.prodos_file_type;
}
} else {
fi.access = get_memory16_c(pb + FileRec_fAccess, 0);
fi.file_type = get_memory16_c(pb + FileRec_fileType, 0);
fi.aux_type = get_memory32_c(pb + FileRec_auxType, 0);
// reserved.
//fi.storage_type = get_memory32_c(pb + FileRec_storageType, 0);
fi.create_date = host_get_date_time(pb + FileRec_createDate);
fi.modified_date = host_get_date_time(pb + FileRec_modDate);
}
if (fi.has_fi) {
fi.afp.prodos_file_type = fi.file_type;
fi.afp.prodos_aux_type = fi.aux_type;
}
else {
afp_init(&fi.afp, fi.file_type, fi.aux_type);
fi.has_fi = 1;
}
if (option_list) {
// total size, req size, fst id, data...
int total_size = get_memory16_c(option_list + 0, 0);
int req_size = get_memory16_c(option_list + 2, 0);
int fst_id = get_memory16_c(option_list + 4, 0);
int size = req_size - 6;
if ((fst_id == proDOSFSID || fst_id == hfsFSID || fst_id == appleShareFSID) && size >= 32) {
// this should never happen...
if (!fi.has_fi) afp_init(&fi.afp, fi.file_type, fi.aux_type);
fi.has_fi = 1;
for (int i = 0; i <32; ++i)
fi.afp.finder_info[i] = get_memory_c(option_list + 6 + i, 0);
}
}
// one more check... if ftype/auxtype doesn't match the ftype/auxtype in finder info
// update finder info
if (fi.has_fi) afp_synchronize(&fi.afp, prefer_prodos);
return host_set_file_info(path, &fi);
}
static word32 fst_get_file_info(int class, const char *path) {
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
struct file_info fi;
int rv = 0;
rv = host_get_file_info(path, &fi);
if (rv) return rv;
if (class) {
word16 pcount = get_memory16_c(pb, 0);
if (pcount >= 2) set_memory16_c(pb + FileInfoRecGS_access, fi.access, 0);
if (pcount >= 3) set_memory16_c(pb + FileInfoRecGS_fileType, fi.file_type, 0);
if (pcount >= 4) set_memory32_c(pb + FileInfoRecGS_auxType, fi.aux_type, 0);
if (pcount >= 5) set_memory16_c(pb + FileInfoRecGS_storageType, fi.storage_type, 0);
if (pcount >= 6) host_set_date_time_rec(pb + FileInfoRecGS_createDateTime, fi.create_date);
if (pcount >= 7) host_set_date_time_rec(pb + FileInfoRecGS_modDateTime, fi.modified_date);
if (pcount >= 8) {
word16 fst_id = hfsFSID;
//if (fi.storage_type == 0x0f) fst_id = mfsFSID;
word32 option_list = get_memory24_c(pb + FileInfoRecGS_optionList, 0);
rv = set_option_list(option_list, fst_id, fi.afp.finder_info, fi.has_fi ? 32 : 0);
}
if (pcount >= 9) set_memory32_c(pb + FileInfoRecGS_eof, fi.eof, 0);
if (pcount >= 10) set_memory32_c(pb + FileInfoRecGS_blocksUsed, fi.blocks, 0);
if (pcount >= 11) set_memory32_c(pb + FileInfoRecGS_resourceEOF, fi.resource_eof, 0);
if (pcount >= 12) set_memory32_c(pb + FileInfoRecGS_resourceBlocks, fi.resource_blocks, 0);
} else {
set_memory16_c(pb + FileRec_fAccess, fi.access, 0);
set_memory16_c(pb + FileRec_fileType, fi.file_type, 0);
set_memory32_c(pb + FileRec_auxType, fi.aux_type, 0);
set_memory16_c(pb + FileRec_storageType, fi.storage_type, 0);
host_set_date_time(pb + FileRec_createDate, fi.create_date);
host_set_date_time(pb + FileRec_modDate, fi.modified_date);
set_memory32_c(pb + FileRec_blocksUsed, fi.blocks, 0);
}
return rv;
}
static word32 fst_judge_name(int class, char *path) {
if (class == 0) return invalidClass;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word16 pcount = get_memory16_c(pb, 0);
word16 name_type = get_memory16_c(pb + JudgeNameRecGS_nameType, 0);
word32 name = pcount >= 5 ? get_memory24_c(pb + JudgeNameRecGS_name, 0) : 0;
// 255 max length.
if (pcount >= 4) set_memory16_c(pb + JudgeNameRecGS_maxLen, 255, 0);
if (pcount >= 6) set_memory16_c(pb + JudgeNameRecGS_nameFlags, 0, 0);
word16 nameFlags = 0;
word16 rv = 0;
if (name) {
word16 cap = get_memory16_c(name, 0);
if (cap < 4) return buffTooSmall;
word16 length = get_memory16_c(name + 2, 0);
if (length == 0) {
nameFlags |= 1 << 13;
rv = set_gsstr(name, "A");
} else {
// if volume name, only allow "Host" ?
if (length > 255) nameFlags |= 1 << 14;
for (int i = 0; i < length; ++i) {
char c = get_memory_c(name + 4 + i, 0);
if (c == 0 || c == ':' || c == '/' || c == '\\') {
nameFlags |= 1 << 15;
set_memory_c(name + 4 + i, '.', 0);
}
}
}
}
if (pcount >= 6) set_memory16_c(pb + JudgeNameRecGS_nameFlags, nameFlags, 0);
return rv;
}
static word32 fst_volume(int class) {
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word32 rv = 0;
if (class) {
word16 pcount = get_memory16_c(pb, 0);
if (pcount >= 2) rv = set_gsstr(get_memory24_c(pb + VolumeRecGS_volName, 0), ":Host");
// finder bug -- if used blocks is 0, doesn't display header.
if (pcount >= 3) set_memory32_c(pb + VolumeRecGS_totalBlocks, 0x007fffff, 0);
if (pcount >= 4) set_memory32_c(pb + VolumeRecGS_freeBlocks, 0x007fffff-1, 0);
if (pcount >= 5) set_memory16_c(pb + VolumeRecGS_fileSysID, mfsFSID, 0);
if (pcount >= 6) set_memory16_c(pb + VolumeRecGS_blockSize, 512, 0);
// handled via gs/os
//if (pcount >= 7) set_memory16_c(pb + VolumeRecGS_characteristics, 0, 0);
//if (pcount >= 8) set_memory16_c(pb + VolumeRecGS_deviceID, 0, 0);
} else {
// prodos 16 uses / sep.
rv = set_pstr(get_memory24_c(pb + VolumeRec_volName, 0), "/Host");
set_memory32_c(pb + VolumeRec_totalBlocks, 0x007fffff, 0);
set_memory32_c(pb + VolumeRec_freeBlocks, 0x007fffff-1, 0);
set_memory16_c(pb + VolumeRec_fileSysID, mfsFSID, 0);
}
return rv;
}
static word32 fst_clear_backup(int class, const char *path) {
word16 rv = 0;
word16 type = host_storage_type(path, &rv);
if (rv) return rv;
if (g_cfg_host_read_only) return drvrWrtProt;
return invalidFSTop;
}
static void free_directory(struct directory *dd) {
if (!dd) return;
for (int i = 0; i < dd->num_entries; ++i) {
free(dd->entries[i]);
}
free(dd);
}
static int qsort_callback(const void *a, const void *b) {
return strcmp(*(const char **)a, *(const char **)b);
}
/*
* exclude files from get_dir_entries.
*
*/
static int filter_directory_entry(const char *name) {
if (!name[0]) return 1;
if (name[0] == '.') {
return 1;
/*
if (!strcmp(name, ".")) return 1;
if (!strcmp(name, "..")) return 1;
if (!strncmp(name, "._", 2)) return 1; // ._ resource fork
if (!strcmp(name, ".fseventsd")) return 1;
*/
}
return 0;
}
static struct directory *read_directory(const char *path, word16 *error) {
HANDLE h;
WIN32_FIND_DATA data;
struct directory *dd;
int capacity = 100;
int size = sizeof(struct directory) + capacity * sizeof(char *);
memset(&data, 0, sizeof(data));
char *p = host_gc_append_path(path, "*");
h = FindFirstFile(p, &data);
if (h == INVALID_HANDLE_VALUE) {
*error = host_map_win32_error(GetLastError());
return NULL;
}
dd = (struct directory *)malloc(size);
if (!dd) {
FindClose(h);
*error = outOfMem;
return NULL;
}
memset(dd, 0, size);
do {
if (filter_directory_entry(data.cFileName)) continue;
if (dd->num_entries >= capacity) {
capacity += capacity;
size = sizeof(struct directory) + capacity * sizeof(char *);
struct directory * tmp = realloc(dd, size);
if (!tmp) {
*error = outOfMem;
free_directory(dd);
FindClose(h);
return NULL;
}
dd = tmp;
}
dd->entries[dd->num_entries++] = strdup(data.cFileName);
} while (FindNextFile(h, &data) != 0);
//?
DWORD e = GetLastError();
if (e != ERROR_NO_MORE_FILES) *error = host_map_win32_error(e);
FindClose(h);
// sort them....
qsort(dd->entries, dd->num_entries, sizeof(char *), qsort_callback);
return dd;
}
static HANDLE open_data_fork(const char *path, word16 *access, word16 *error) {
HANDLE h = INVALID_HANDLE_VALUE;
for (;;) {
DWORD wAccess = 0;
DWORD wShare = FILE_SHARE_READ;
DWORD wCreate = OPEN_EXISTING;
switch(*access) {
case readEnableAllowWrite:
case readWriteEnable:
wAccess = GENERIC_READ | GENERIC_WRITE;
break;
case readEnable:
wAccess = GENERIC_READ;
break;
case writeEnable:
wAccess = GENERIC_WRITE;
break;
}
h = CreateFile(path, wAccess, wShare, NULL, wCreate, FILE_ATTRIBUTE_NORMAL, NULL);
if (*access == readEnableAllowWrite) {
if (h == INVALID_HANDLE_VALUE) {
*access = readEnable;
continue;
}
*access = readWriteEnable;
}
break;
}
if (h == INVALID_HANDLE_VALUE) {
*error = host_map_win32_error(GetLastError());
}
return h;
}
static HANDLE open_resource_fork(const char *path, word16 *access, word16 *error) {
char *rpath = host_gc_append_string(path, ":AFP_Resource");
HANDLE h = INVALID_HANDLE_VALUE;
for (;;) {
DWORD wAccess = 0;
DWORD wShare = FILE_SHARE_READ;
DWORD wCreate = 0;
switch(*access) {
case readEnableAllowWrite:
case readWriteEnable:
wAccess = GENERIC_READ | GENERIC_WRITE;
wCreate = OPEN_ALWAYS;
break;
case readEnable:
wAccess = GENERIC_READ;
wCreate = OPEN_EXISTING;
break;
case writeEnable:
wAccess = GENERIC_WRITE;
wCreate = OPEN_ALWAYS;
break;
}
h = CreateFile(rpath, wAccess, wShare, NULL, wCreate, FILE_ATTRIBUTE_NORMAL, NULL);
if (*access == readEnableAllowWrite) {
if (h == INVALID_HANDLE_VALUE) {
*access = readEnable;
continue;
}
*access = readWriteEnable;
}
break;
}
if (h == INVALID_HANDLE_VALUE) {
*error = host_map_win32_error(GetLastError());
}
return h;
}
static word32 fst_open(int class, const char *path) {
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
struct file_info fi;
word16 rv = 0;
rv = host_get_file_info(path, &fi);
if (rv) return rv;
HANDLE h = INVALID_HANDLE_VALUE;
int type = file_regular;
struct directory *dd = NULL;
word16 pcount = 0;
word16 request_access = readEnableAllowWrite;
word16 access = 0;
word16 resource_number = 0;
if (class) {
pcount = get_memory16_c(pb, 0);
if (pcount >= 3) request_access = get_memory16_c(pb + OpenRecGS_requestAccess, 0);
if (pcount >= 4) resource_number = get_memory16_c(pb + OpenRecGS_resourceNumber, 0);
}
if (resource_number) {
if (resource_number > 1) return paramRangeErr;
type = file_resource;
}
if (access > 3) return paramRangeErr;
// special access checks for directories.
if (fi.storage_type == 0x0f || fi.storage_type == 0x0d) {
if (resource_number) return resForkNotFound;
switch (request_access) {
case readEnableAllowWrite:
request_access = readEnable;
break;
case writeEnable:
case readWriteEnable:
return invalidAccess;
}
type = file_directory;
}
if (g_cfg_host_read_only) {
switch (request_access) {
case readEnableAllowWrite:
request_access = readEnable;
break;
case readWriteEnable:
case writeEnable:
return invalidAccess;
break;
}
}
access = request_access;
switch(type) {
case file_regular:
h = open_data_fork(path, &access, &rv);
break;
case file_resource:
h = open_resource_fork(path, &access, &rv);
break;
case file_directory:
dd = read_directory(path, &rv);
break;
}
if (rv) return rv;
if (class) {
if (pcount >= 5) set_memory16_c(pb + OpenRecGS_access, access, 0);
if (pcount >= 6) set_memory16_c(pb + OpenRecGS_fileType, fi.file_type, 0);
if (pcount >= 7) set_memory32_c(pb + OpenRecGS_auxType, fi.aux_type, 0);
if (pcount >= 8) set_memory16_c(pb + OpenRecGS_storageType, fi.storage_type, 0);
if (pcount >= 9) host_set_date_time_rec(pb + OpenRecGS_createDateTime, fi.create_date);
if (pcount >= 10) host_set_date_time_rec(pb + OpenRecGS_modDateTime, fi.modified_date);
if (pcount >= 11) {
word16 fst_id = hfsFSID;
//if (fi.storage_type == 0x0f) fst_id = mfsFSID;
word32 option_list = get_memory24_c(pb + OpenRecGS_optionList, 0);
word32 tmp = set_option_list(option_list, fst_id, fi.afp.finder_info, fi.has_fi ? 32 : 0);
if (!rv) rv = tmp;
}
if (pcount >= 12) set_memory32_c(pb + OpenRecGS_eof, fi.eof, 0);
if (pcount >= 13) set_memory32_c(pb + OpenRecGS_blocksUsed, fi.blocks, 0);
if (pcount >= 14) set_memory32_c(pb + OpenRecGS_resourceEOF, fi.resource_eof, 0);
if (pcount >= 15) set_memory32_c(pb + OpenRecGS_resourceBlocks, fi.resource_blocks, 0);
}
// prodos 16 doesn't return anything in the parameter block.
struct fd_entry *e = alloc_fd();
if (!e) {
if (h != INVALID_HANDLE_VALUE) CloseHandle(h);
if (dd) free_directory(dd);
return outOfMem;
}
e->handle = h;
e->dir = dd;
e->cookie = alloc_cookie();
if (!e->cookie) {
free_fd(e);
return tooManyFilesOpen;
}
if (type == file_regular) {
if (g_cfg_host_crlf) {
if (fi.file_type == 0x04 || fi.file_type == 0xb0)
e->translate = translate_crlf;
}
if (g_cfg_host_merlin && fi.file_type == 0x04) {
int n = strlen(path);
if (n >= 3 && path[n-1] == 'S' && path[n-2] == '.')
e->translate = translate_merlin;
}
}
e->access = access;
e->path = strdup(path);
e->type = type;
// insert it in the linked list.
e->next = fd_head;
fd_head = e;
engine.xreg = e->cookie;
engine.yreg = access; // actual access, needed in fcr.
return rv;
}
static word32 fst_read(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
switch (e->type) {
case file_directory:
return badStoreType;
}
if (!(e->access & readEnable))
return invalidAccess;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word32 data_buffer = 0;
word32 request_count = 0;
word32 transfer_count = 0;
if (class) {
data_buffer = get_memory24_c(pb + IORecGS_dataBuffer, 0);
request_count = get_memory24_c(pb + IORecGS_requestCount, 0);
// pre-zero transfer count
set_memory32_c(pb + IORecGS_transferCount, 0, 0);
} else {
data_buffer = get_memory24_c(pb + FileIORec_dataBuffer, 0);
request_count = get_memory24_c(pb + FileIORec_requestCount, 0);
set_memory32_c(pb + FileIORec_transferCount, 0, 0);
}
if (request_count == 0) return 0;
word16 newline_mask;
word32 rv = 0;
int ok;
newline_mask = get_memory16_c(global_buffer, 0);
if (newline_mask) {
byte newline_table[256];
for (int i = 0; i < 256; ++i)
newline_table[i] = get_memory_c(global_buffer + 2 + i, 0);
for (word32 i = 0; i < request_count; ++i) {
byte b;
DWORD read_count;
ok = ReadFile(e->handle, &b, 1, &read_count, NULL);
if (!ok) return host_map_win32_error(GetLastError());
if (read_count == 0) break;
transfer_count++;
switch(e->translate) {
case translate_crlf:
host_lf_to_cr(&b, 1);
break;
case translate_merlin:
host_text_to_merlin(&b, 1);
break;
}
set_memory_c(data_buffer++, b, 0);
if (newline_table[b & newline_mask]) break;
}
if (transfer_count == 0) rv = eofEncountered;
}
else {
DWORD read_count;
byte *data = host_gc_malloc(request_count);
if (!data) return outOfMem;
ok = ReadFile(e->handle, data, request_count, &read_count, NULL);
if (!ok) rv = host_map_win32_error(GetLastError());
else if (read_count == 0) rv = eofEncountered;
if (read_count > 0) {
transfer_count = read_count;
switch(e->translate) {
case translate_crlf:
host_lf_to_cr(data, transfer_count);
break;
case translate_merlin:
host_text_to_merlin(data, transfer_count);
break;
}
for (size_t i = 0; i < transfer_count; ++i) {
set_memory_c(data_buffer + i, data[i], 0);
}
}
}
if (transfer_count) {
if (class)
set_memory32_c(pb + IORecGS_transferCount, transfer_count, 0);
else
set_memory32_c(pb + FileIORec_transferCount, transfer_count, 0);
}
return rv;
}
static word32 fst_write(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
switch (e->type) {
case file_directory:
return badStoreType;
}
if (g_cfg_host_read_only) return drvrWrtProt;
if (!(e->access & writeEnable))
return invalidAccess;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word32 data_buffer = 0;
word32 request_count = 0;
if (class) {
data_buffer = get_memory24_c(pb + IORecGS_dataBuffer, 0);
request_count = get_memory24_c(pb + IORecGS_requestCount, 0);
// pre-zero transfer count
set_memory32_c(pb + IORecGS_transferCount, 0, 0);
} else {
data_buffer = get_memory24_c(pb + FileIORec_dataBuffer, 0);
request_count = get_memory24_c(pb + FileIORec_requestCount, 0);
set_memory32_c(pb + FileIORec_transferCount, 0, 0);
}
if (request_count == 0) return 0;
byte *data = host_gc_malloc(request_count);
if (!data) return outOfMem;
for (word32 i = 0; i < request_count; ++i) {
data[i] = get_memory_c(data_buffer + i,0);
}
switch (e->translate) {
case translate_crlf:
host_cr_to_lf(data, request_count);
break;
case translate_merlin:
host_merlin_to_text(data, request_count);
break;
}
word32 rv = 0;
DWORD write_count = 0;
int ok = WriteFile(e->handle, data, request_count, &write_count, NULL);
if (!ok) rv = host_map_win32_error(GetLastError());
if (write_count > 0) {
if (class)
set_memory32_c(pb + IORecGS_transferCount, write_count, 0);
else
set_memory32_c(pb + FileIORec_transferCount, write_count, 0);
}
return rv;
}
static word32 fst_close(int class) {
int cookie = engine.yreg;
return remove_fd(cookie);
}
static word32 fst_flush(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
switch (e->type) {
case file_directory:
return badStoreType;
}
if (!FlushFileBuffers(e->handle)) return host_map_win32_error(GetLastError());
return 0;
}
static word16 win_seek(HANDLE h, word16 base, word32 displacement, LARGE_INTEGER *position) {
LARGE_INTEGER d;
int m = 0;
switch (base) {
case startPlus:
m = FILE_BEGIN;
d.QuadPart = displacement;
break;
case markPlus:
m = FILE_CURRENT;
d.QuadPart = displacement;
break;
case markMinus:
m = FILE_CURRENT;
d.QuadPart = -(LONGLONG)displacement;
break;
case eofMinus:
m = FILE_END;
d.QuadPart = -(LONGLONG)displacement;
break;
default:
return paramRangeErr;
}
if (!SetFilePointerEx(h, d, position, m)) return host_map_win32_error(GetLastError());
return 0;
}
static word32 fst_set_mark(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
switch (e->type) {
case file_directory:
return badStoreType;
}
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word16 base = 0;
word32 displacement = 0;
if (class) {
base = get_memory16_c(pb + SetPositionRecGS_base, 0);
displacement = get_memory32_c(pb + SetPositionRecGS_displacement, 0);
} else {
displacement = get_memory32_c(pb + MarkRec_position, 0);
}
if (base > markMinus) return paramRangeErr;
return win_seek(e->handle, base, displacement, NULL);
}
static word32 fst_set_eof(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
switch (e->type) {
case file_directory:
return badStoreType;
}
if (g_cfg_host_read_only) return drvrWrtProt;
if (!(e->access & writeEnable))
return invalidAccess;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word16 base = 0;
word32 displacement = 0;
if (class) {
base = get_memory16_c(pb + SetPositionRecGS_base, 0);
displacement = get_memory32_c(pb + SetPositionRecGS_displacement, 0);
} else {
displacement = get_memory32_c(pb + EOFRec_eofPosition, 0);
}
if (base > markMinus) return paramRangeErr;
// get the current mark
LARGE_INTEGER mark;
LARGE_INTEGER zero; zero.QuadPart = 0;
SetFilePointerEx(e->handle, zero, &mark, FILE_CURRENT);
word16 rv = win_seek(e->handle, base, displacement, NULL);
if (rv) return rv;
// SetEndOfFile sets the current positions as eof.
if (!SetEndOfFile(e->handle)) return host_map_win32_error(GetLastError());
// restore old mark. ???
SetFilePointerEx(e->handle, mark, NULL, FILE_BEGIN);
return 0;
}
static word32 fst_get_mark(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
switch (e->type) {
case file_directory:
return badStoreType;
}
// get the current mark
LARGE_INTEGER pos;
LARGE_INTEGER zero; zero.QuadPart = 0;
if (!SetFilePointerEx(e->handle, zero, &pos, FILE_CURRENT)) return host_map_win32_error(GetLastError());
if (class) {
set_memory32_c(pb + PositionRecGS_position, pos.LowPart, 0);
} else {
set_memory32_c(pb + MarkRec_position, pos.LowPart, 0);
}
return 0;
}
static word32 fst_get_eof(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
switch (e->type) {
case file_directory:
return badStoreType;
}
LARGE_INTEGER eof;
if (!GetFileSizeEx(e->handle, &eof)) return host_map_win32_error(GetLastError());
// what if > 32 bits?
if (class) {
set_memory32_c(pb + PositionRecGS_position, eof.LowPart, 0);
} else {
set_memory32_c(pb + MarkRec_position, eof.LowPart, 0);
}
return 0;
}
static word32 fst_get_dir_entry(int class) {
int cookie = engine.yreg;
struct fd_entry *e = find_fd(cookie);
if (!e) return invalidRefNum;
if (e->type != file_directory) return badFileFormat;
word32 pb = get_memory24_c(engine.direct + dp_param_blk_ptr, 0);
word16 base = 0;
word16 pcount = 0;
word32 displacement = 0;
word32 name = 0;
if (class) {
pcount = get_memory16_c(pb, 0);
base = get_memory16_c(pb + DirEntryRecGS_base, 0);
displacement = get_memory16_c(pb + DirEntryRecGS_displacement, 0);
name = get_memory24_c(pb + DirEntryRecGS_name, 0);
} else {
base = get_memory16_c(pb + DirEntryRec_base, 0);
displacement = get_memory16_c(pb + DirEntryRec_displacement, 0);
name = get_memory24_c(pb + DirEntryRec_nameBuffer, 0);
}
if (base == 0 && displacement == 0) {
// count them up.
int count = e->dir->num_entries;
e->dir->displacement = 0;
if (class) {
if (pcount >= 6) set_memory16_c(pb + DirEntryRecGS_entryNum, count, 0);
}
else {
set_memory16_c(pb + DirEntryRec_entryNum, count, 0);
}
return 0;
}
int dir_displacement = e->dir->displacement;
switch (base) {
case 0: // displacement is absolute entry number.
break;
case 1: // displacement is added to the current displacement.
displacement = dir_displacement + displacement;
break;
case 2: // displacement is substracted from current displacement.
displacement = dir_displacement - displacement;
break;
default:
return paramRangeErr;
}
//if (displacement) --displacement;
--displacement;
if (displacement < 0) return endOfDir;
if (displacement >= e->dir->num_entries) return endOfDir;
word32 rv = 0;
const char *dname = e->dir->entries[displacement++];
e->dir->displacement = displacement;
char *fullpath = host_gc_append_path(e->path, dname);
struct file_info fi;
rv = host_get_file_info(fullpath, &fi);
if (dname) fprintf(stderr, " - %s", dname);
// p16 and gs/os both use truncating c1 output string.
rv = set_gsstr_truncate(name, dname);
if (class) {
if (pcount > 2) set_memory16_c(pb + DirEntryRecGS_flags, fi.storage_type == 0x05 ? 0x8000 : 0, 0);
if (pcount >= 6) set_memory16_c(pb + DirEntryRecGS_entryNum, displacement, 0);
if (pcount >= 7) set_memory16_c(pb + DirEntryRecGS_fileType, fi.file_type, 0);
if (pcount >= 8) set_memory32_c(pb + DirEntryRecGS_eof, fi.eof, 0);
if (pcount >= 9) set_memory32_c(pb + DirEntryRecGS_blockCount, fi.blocks, 0);
if (pcount >= 10) host_set_date_time_rec(pb + DirEntryRecGS_createDateTime, fi.create_date);
if (pcount >= 11) host_set_date_time_rec(pb + DirEntryRecGS_modDateTime, fi.modified_date);
if (pcount >= 12) set_memory16_c(pb + DirEntryRecGS_access, fi.access, 0);
if (pcount >= 13) set_memory32_c(pb + DirEntryRecGS_auxType, fi.aux_type, 0);
if (pcount >= 14) set_memory16_c(pb + DirEntryRecGS_fileSysID, mfsFSID, 0);
if (pcount >= 15) {
word16 fst_id = hfsFSID;
//if (fi.storage_type == 0x0f) fst_id = mfsFSID;
word32 option_list = get_memory24_c(pb + DirEntryRecGS_optionList, 0);
word32 tmp = set_option_list(option_list, fst_id, fi.afp.finder_info, fi.has_fi ? 32 : 0);
if (!rv) rv = tmp;
}
if (pcount >= 16) set_memory32_c(pb + DirEntryRecGS_resourceEOF, fi.resource_eof, 0);
if (pcount >= 17) set_memory32_c(pb + DirEntryRecGS_resourceBlocks, fi.resource_blocks, 0);
}
else {
set_memory16_c(pb + DirEntryRec_flags, fi.storage_type == 0x05 ? 0x8000 : 0, 0);
set_memory16_c(pb + DirEntryRec_entryNum, displacement, 0);
set_memory16_c(pb + DirEntryRec_fileType, fi.file_type, 0);
set_memory32_c(pb + DirEntryRec_endOfFile, fi.eof, 0);
set_memory32_c(pb + DirEntryRec_blockCount, fi.blocks, 0);
host_set_date_time_rec(pb + DirEntryRec_createTime, fi.create_date);
host_set_date_time_rec(pb + DirEntryRec_modTime, fi.modified_date);
set_memory16_c(pb + DirEntryRec_access, fi.access, 0);
set_memory32_c(pb + DirEntryRec_auxType, fi.aux_type, 0);
set_memory16_c(pb + DirEntryRec_fileSysID, mfsFSID, 0);
}
return rv;
}
static word32 fst_change_path(int class, const char *path1, const char *path2) {
/* make sure they're not trying to rename the volume... */
BOOL ok = 0;
word16 rv = 0;
word16 type = host_storage_type(path1, &rv);
if (rv) return rv;
if (type == 0x0f) return badStoreType;
if (g_cfg_host_read_only) return drvrWrtProt;
if (!MoveFile(path1, path2)) return host_map_win32_error(GetLastError());
return 0;
}
static word32 fst_format(int class) {
return notBlockDev;
}
static word32 fst_erase(int class) {
return notBlockDev;
}
#ifdef __CYGWIN__
#include <sys/cygwin.h>
static char *expand_path(const char *path, word32 *error) {
char buffer[PATH_MAX];
ssize_t ok = cygwin_conv_path(CCP_POSIX_TO_WIN_A, path, buffer, sizeof(buffer));
if (ok < 0) {
*error = fstError;
return NULL;
}
return host_gc_strdup(buffer);
}
#else
#define expand_path(x, y) (x)
#endif
static const char *call_name(word16 call) {
static char* class1[] = {
// 0x00
"",
"CreateGS",
"DestroyGS",
"",
"ChangePathGS",
"SetFileInfoGS",
"GetFileInfoGS",
"JudgeNameGS",
"VolumeGS",
"",
"",
"ClearBackupGS",
"",
"",
"",
"",
// 0x10
"OpenGS",
"",
"ReadGS",
"WriteGS",
"CloseGS",
"FlushGS",
"SetMarkGS",
"GetMarkGS",
"SetEOFGS",
"GetEOFGS",
"",
"",
"GetDirEntryGS",
"",
"",
"",
// 0x20
"",
"",
"",
"",
"FormatGS",
"EraseDiskGS",
};
static char* class0[] = {
// 0x00
"",
"CREATE",
"DESTROY",
"",
"CHANGE_PATH",
"SET_FILE_INFO",
"GET_FILE_INFO",
"",
"VOLUME",
"",
"",
"CLEAR_BACKUP_BIT",
"",
"",
"",
"",
// 0X10
"OPEN",
"",
"READ",
"WRITE",
"CLOSE",
"FLUSH",
"SET_MARK",
"GET_MARK",
"SET_EOF",
"GET_EOF",
"",
"",
"GET_DIR_ENTRY",
"",
"",
"",
// 0X20
"",
"",
"",
"",
"FORMAT",
"ERASE_DISK",
};
if (call & 0x8000) {
static char *sys[] = {
"",
"fst_startup",
"fst_shutdown",
"fst_remove_vcr",
"fst_deferred_flush"
};
call &= ~0x8000;
if (call < sizeof(sys) / sizeof(sys[0]))
return sys[call];
return "";
}
int class = call >> 13;
call &= 0x1fff;
switch(class) {
case 0:
if (call < sizeof(class0) / sizeof(class0[0]))
return class0[call];
break;
case 1:
if (call < sizeof(class1) / sizeof(class1[0]))
return class1[call];
break;
}
return "";
}
void host_fst(void) {
/*
* input:
* c = set
* a = default error code
* x = gs/os callnum
* y = [varies]
*
* output:
* c = set/clear
* a = error code/0
* x = varies
* y = varies
*/
word32 acc = 0;
word16 call = engine.xreg;
fprintf(stderr, "Host FST: %04x %s", call, call_name(call));
if (call & 0x8000) {
fputs("\n", stderr);
// system level.
switch(call) {
case 0x8001:
acc = fst_startup();
break;
case 0x8002:
acc = fst_shutdown();
break;
default:
acc = badSystemCall;
break;
}
} else {
if (!host_root) {
acc = networkError;
engine.acc = acc;
SEC();
fprintf(stderr, " %02x %s\n", acc, host_error_name(acc));
return;
}
int class = call >> 13;
call &= 0x1fff;
if (class > 1) {
acc = invalidClass;
engine.acc = acc;
SEC();
fprintf(stderr, " %02x %s\n", acc, host_error_name(acc));
return;
}
char *path1 = NULL;
char *path2 = NULL;
char *path3 = NULL;
char *path4 = NULL;
const char *cp;
switch(call & 0xff) {
case 0x01:
case 0x02:
case 0x05:
case 0x06:
case 0x0b:
case 0x10:
path1 = get_path1();
break;
case 0x04:
path1 = get_path1();
path2 = get_path2();
break;
}
if (path1) fprintf(stderr, " - %s", path1);
if (path2) fprintf(stderr, " - %s", path2);
switch(call & 0xff) {
case 0x01:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_create(class, path3);
break;
case 0x02:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_destroy(class, path3);
break;
case 0x04:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
cp = check_path(path2, &acc);
if (acc) break;
path4 = host_gc_append_path(host_root, cp);
path4 = expand_path(path4, &acc);
if (acc) break;
acc = fst_change_path(class, path3, path4);
break;
case 0x05:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_set_file_info(class, path3);
break;
case 0x06:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_get_file_info(class, path3);
break;
case 0x07:
acc = fst_judge_name(class, path1);
break;
case 0x08:
acc = fst_volume(class);
break;
case 0x0b:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_clear_backup(class, path3);
break;
case 0x10:
cp = check_path(path1, &acc);
if (acc) break;
path3 = host_gc_append_path(host_root, cp);
path3 = expand_path(path3, &acc);
if (acc) break;
acc = fst_open(class, path3);
break;
case 0x012:
acc = fst_read(class);
break;
case 0x013:
acc = fst_write(class);
break;
case 0x14:
acc = fst_close(class);
break;
case 0x15:
acc = fst_flush(class);
break;
case 0x16:
acc = fst_set_mark(class);
break;
case 0x17:
acc = fst_get_mark(class);
break;
case 0x18:
acc = fst_set_eof(class);
break;
case 0x19:
acc = fst_get_eof(class);
break;
case 0x1c:
acc = fst_get_dir_entry(class);
break;
case 0x24:
acc = fst_format(class);
break;
case 0x25:
acc = fst_erase(class);
break;
default:
acc = invalidFSTop;
break;
}
fputs("\n", stderr);
}
if (acc) fprintf(stderr, " %02x %s\n", acc, host_error_name(acc));
host_gc_free();
engine.acc = acc;
if (acc) SEC();
else CLC();
}