Files
erkyrath.infocom-zcode-terps/amiga/yzip2.c
Andrew Plotkin b642da811e Initial commit.
2023-11-16 18:19:54 -05:00

894 lines
21 KiB
C

/************************************************************************/
/* YZIP File I/O */
/************************************************************************/
#include "yzip.h"
extern struct Window *ZWindow;
extern struct DiskObject *diskobj;
/*------------------------------*/
/* File I/O Globals */
/*------------------------------*/
/* This buffer is accessed by the kernel. Careful about changing its size */
GLOBAL CHAR savename[80]; /* buffer for file_select dialog */
GLOBAL CHAR saveback[80]; /* default (last successful) name */
CHAR *gamename = "Story.Data"; /* (on default drive) */
GLOBAL LONG game_ref;
GLOBAL LONG save_ref;
GLOBAL LONG actlen = 0; /* for reporting r/w transfer len */
/* These vars are used to avoid unnecessary Seeking (e.g. when reading
sequential blocks during $verify). AmigaDOS Seeking causes head
movement even when it shouldn't and thrashes the disk badly. */
LONG old_offset = -1; /* init to invalid vals */
LONG old_channel = -1;
/* DEAD, SEE bug_DiskFull
If a NEW save file is being created, check disk free space before
each write call. Prevents a useless Retry/Abort dialog (and in the
case of make_icon_file, a crash).
*/
BOOL disk_newsave;
LONG disk_free;
/************************************************************************/
/* misc file functions */
/************************************************************************/
/*------------------------------*/
/* getcwd */
/*------------------------------*/
/* Find the pathname of the current working directory. Uses a recursive
call to the followpath function. Its job is to continue to call the
ParentDir function until it obtains a lock value of zero, and to append
(to the given string) the name of the directory it pops into at each step.
In the root directory, the lock gives us the volume name of the disk
(how do we get the drive name instead?). The program appends a colon
after the volume name and a slash after each subdirectory along the way
to the one we're in currently. [Debugged from example in Peck p36.]
*/
extern struct FileLock *Lock(), *ParentDir();
getcwd (path)
char *path;
{
struct FileLock *cwdlock;
path[0] = 0; /* start with null string */
/* get a Read lock on the current directory. NOTE: followpath
unlocks the lock */
cwdlock = Lock ("", ACCESS_READ);
if (cwdlock != 0)
followpath (cwdlock, path, 1); /* add a final slash */
}
int followpath (lock, path, showslash)
struct FileLock *lock;
char *path;
int showslash;
{
struct FileInfoBlock *myinfo;
struct FileLock *newlock;
int success, error;
/* if we reach the end of the road, just exit */
if (!lock) return (0);
/* see if this dir has a parent; might fail because of an I/O error
or bacause somebody took out the disk */
newlock = ParentDir (lock);
error = IoErr ();
/* Gross bug: error #205 (object not found?) seems to get reported
about half the time for no apparent reason. Works correctly if ignored,
so do so ... */
if (error == 205) error = 0;
/* recursively call this same function to follow path up to the root */
if (!error)
error = followpath (newlock, path, 1);
if (!error) {
/* alloc memory AFTER the recursion, to minimize total usage */
myinfo = (struct FileInfoBlock *)
AllocMem (sizeof(struct FileInfoBlock), MEMF_CLEAR);
if (myinfo == 0) error = -1;
else {
success = Examine (lock, myinfo);
if (!success) error = -1;
else {
strcat (path, myinfo->fib_FileName); /* dir name */
if (newlock == 0) /* (actually vol name) */
strcat (path, ":");
else if (showslash)
strcat (path, "/");
}
FreeMem (myinfo, sizeof(struct FileInfoBlock));
}
}
UnLock (lock);
if (error) /* make null the global string */
path[0] = 0;
return (error); /* (zero if ok) */
}
/*------------------------------*/
/* geterr */
/*------------------------------*/
/* This is a front end for IoErr. It should be called /only/ when we know
an error occurred, and so expect a nonzero result. But IoErr sometimes
returns a zero anyway (e.g., after Create with a bad pathname). In
this case we return -1, so 68K stuff will know something went wrong. */
LONG geterr ()
{
LONG err = IoErr ();
if (!err) err = -1; /* make sure it's nonzero */
return (err);
}
/************************************************************************/
/* File Selection */
/************************************************************************/
/*------------------------------*/
/* file_select */
/*------------------------------*/
WORD
file_select (opsave, partial) /* get file and pathspec from user, */
/* combine and leave in SAVENAME */
BOOL opsave; /* zero if restore, otherwise save */
BOOL partial;
{
/* The Amiga lacks OS support for this function. We originally implemented
a simple prompt in the 68K kernel. For YZIP, we have added RJ Mical's
nifty FileIO routines. These now constitute our normal file interface.
If FileIO fails to initialize for some reason, or if the user
disables it deliberately by deleting our template icon, this returns -1,
which tells the kernel to fall back into our old-style prompt. */
return ((WORD) fsel_do (opsave, partial, &savename, ZWindow));
}
/*------------------------------*/
/* new_default */
/*------------------------------*/
VOID
new_default (okay) /* called at end of each SAVE/RESTORE */
BOOL okay;
{
if (okay) /* dest <- source */
{
strcpy (&saveback, &savename); /* update old defaults */
/* take this opportunity to update FileIO's records. (Actually,
this call is required only after a save ... ) */
fsel_written (&savename);
}
else
{
strcpy (&savename, &saveback); /* retrieve old defaults */
}
}
/*------------------------------*/
/* strip_spaces */
/*------------------------------*/
/* Remove any leading and/or trailing spaces from savenames, before
the file is created. AmigaDOS permits them, but they lead to confusing
RESTORE errors.
Note that we continue to allow embedded spaces.
*/
VOID
strip_spaces (filename)
CHAR *filename;
{
WORD len;
while (*filename == 32)
strcpy (filename, filename+1); /* move the string up */
len = strlen (filename);
while (len && (filename[len-1] == 32))
{
filename[len-1] = 0; /* chop off any trailing spaces */
--len;
}
}
/*------------------------------*/
/* divide_pathname */
/*------------------------------*/
/* Check whether a given filename is prefixed with a drive or directory name.
If so, return a pointer to the bare filename, otherwise return NULL.
Path syntax for Amiga is "DFn:PATH1/PATH2/PATHn/filename".
*/
CHAR *
divide_pathname (pathname)
CHAR *pathname;
{
CHAR *special; /* <:> and </> are the special chars */
/* if one or more directories were given, find the last one */
special = (CHAR *) strrchr (pathname, '/');
/* otherwise, see if a drive was given */
if (special == NULL)
special = (CHAR *) stpchr (pathname, ':');
if (special != NULL) /* found one, skip over the delimiter */
special++;
return (special); /* pointer to bare filename, or NULL */
}
/*------------------------------*/
/* drive_default */
/*------------------------------*/
/* If savename lacks a drive/directory spec but saveback includes one
(the default), prefix it to savename.
Also, take this opportunity to remove any leading/trailing spaces from
savename.
*/
VOID
drive_default ()
{
CHAR *barename;
CHAR temp[64];
/* check whether savename includes a drive/directory */
barename = (CHAR *) divide_pathname (&savename);
if (barename == NULL) /* Nope */
{
strip_spaces (&savename);
/* check whether a default drive/directory exists */
strcpy (&temp, &saveback);
barename = (CHAR *) divide_pathname (&temp);
if (barename != NULL) /* Yep */
{
/* and prefix the default drive/directory onto savename */
*barename = 0; /* chop off default filename */
strcat (&temp, &savename);
strcpy (&savename, &temp);
}
}
/* user DID supply a drive/directory, just check for spaces in filename */
else
{
strip_spaces (barename);
}
}
#if bug_DiskFull /* BUG GONE, THIS IS DEAD CODE */
/*------------------------------*/
/* disk_bytes_avail */
/*------------------------------*/
LONG
disk_bytes_avail () /* returns number of available bytes on save disk,
-1 if error. Must call AFTER save file is created */
{
struct FileLock *filelock;
LONG avail;
WORD pad1;
struct InfoData ZInfoData; /* information about the save disk */
WORD pad2;
struct InfoData *ZInfoDataPtr; /* pointer to the above */
LONG ptr;
/* Must first obtain a "lock" for a valid file on the desired disk.
We conveniently use the save file (thus it must already exist). */
filelock = Lock (&savename, ACCESS_READ);
if (!filelock) /* if error, exit now */
return (-1);
/* The InfoData structure must be longword aligned (for AmigaDOS).
Adjust alignment with the pad bytes, if necessary. */
pad1 = pad2 = 0; /* make the Compiler stop whining */
ptr = (LONG) &ZInfoData;
ptr = ptr>>2; ptr = ptr<<2;
ZInfoDataPtr = (struct InfoData *) ptr;
/* Determine how much space remains on the save disk. */
if (Info (filelock, ZInfoDataPtr))
{
avail =
(ZInfoDataPtr->id_NumBlocks
- ZInfoDataPtr->id_NumBlocksUsed) /* free blks */
* ZInfoDataPtr->id_BytesPerBlock;
}
else avail = -1; /* something wrong, fail */
UnLock (filelock); /* make sure to undo this lock */
return (avail);
}
#endif /* bug_DiskFull */
/************************************************************************/
/* Disk I/O */
/************************************************************************/
/*------------------------------*/
/* read_file */
/*------------------------------*/
LONG
read_file (channel, offset, length, buffer)
LONG channel;
LONG offset, length;
CHAR *buffer;
{
LONG former_pos, actual_len, err_len;
LONG error;
/* (Amiga) disk thrashing is reduced if unnecessary seeks are avoided */
if ((channel != old_channel) || (offset != old_offset))
{
former_pos = Seek (channel, offset, -1); /* AmigaDOS */
if (former_pos == -1)
{
old_offset = -1; /* force seek next time */
return (geterr());
}
}
old_channel = channel;
old_offset = offset + length;
actual_len = Read (channel, buffer, length);
/* Check the number of bytes actually read. This will detect certain
invalid save files (for example, files where Amiga crashed during their
creation, for whatever reason).
*/
if (actual_len == length)
error = 0; /* no error */
else {
old_offset = -1; /* force seek next time */
if (actual_len == -1)
error = geterr();
else
error = 1; /* length error */
/* [Old] We allow for a special case of actual length being 256 less than
expected. The kernel does paging in 512 byte blocks, while the Amiga
TFTP utility transfers data files in 256 byte blocks.
[New] Since files transfered with Kermit aren't padded at all, we
allow for a /game/ file read to return up to 511 bytes less than
the request, without reporting an error.
*/
if (channel == game_ref) {
err_len = length - actual_len;
if ((err_len >= 0) && (err_len < 512))
error = 0; /* no error */
}
}
actlen = actual_len; /* save as global (for 68K) */
return (error);
}
/*------------------------------*/
/* write_file */
/*------------------------------*/
LONG
write_file (channel, offset, length, buffer)
LONG channel;
LONG offset, length;
CHAR *buffer;
{
LONG error, actual_len;
/* Errors: zero means none, 999 means disk full, otherwise geterr() */
#if bug_DiskFull
if (disk_newsave) /* FALSE if writing over old file */
{
disk_free = disk_free - length;
if (disk_free < 0)
return (999); /* not enough room for write */
}
#endif
error = Seek (channel, offset, -1);
if (error == -1)
return geterr();
actual_len = Write (channel, buffer, length);
if (actual_len == -1)
return geterr();
else if (actual_len != length) /* (should have been caught above) */
return (999);
actlen = actual_len; /* save as global (for 68K) */
return (0); /* no error */
}
/*------------------------------*/
/* create_file */
/*------------------------------*/
LONG
create_file () /* create (if needed), and open, a SAVE file */
{
register LONG fd;
/* two ways to enter this routine: if disk_newsave is TRUE, we are creating
a new save file, otherwise we are writing over an old one */
fd = Open (&savename, MODE_NEWFILE);
#if bug_DiskFull /* avoid a write error */
if (fd && disk_newsave)
{
/* we just created the file, but must close it momentarily to get disk info.
Otherwise the Lock call fails */
Close (fd);
/* determine how much free space is on the disk, so file_write can fail
gracefully if necessary */
disk_free = disk_bytes_avail ();
fd = Open (&savename, MODE_NEWFILE);
}
#endif /* bug_DiskFull */
save_ref = fd; /* save global */
if (!fd)
return (geterr());
else
return (0); /* zero means OK */
}
/*------------------------------*/
/* open_file */
/*------------------------------*/
LONG
open_file () /* open an file to RESTORE from */
{
LONG fd;
fd = Open (&savename, MODE_OLDFILE);
save_ref = fd;
if (!fd)
return (geterr());
else
return (0); /* zero means OK */
}
/*------------------------------*/
/* close_file */
/*------------------------------*/
LONG
close_file () /* close a SAVE file */
{
if (save_ref) {
Close (save_ref);
save_ref = 0;
}
old_channel = -1; /* make sure to reset this one */
return (0); /* no error codes? */
}
/*------------------------------*/
/* delete_file */
/*------------------------------*/
LONG
delete_file ()
{
LONG success; /* actually BOOLEAN */
success = DeleteFile (&savename);
if (success)
return (0);
else return (1);
}
/*------------------------------*/
/* exist_file */
/*------------------------------*/
LONG
exist_file () /* called before every SAVE */
{
struct FileLock *fl;
fl = Lock (&savename, ACCESS_WRITE);
if (fl) /* non-zero if already exists */
{
disk_newsave = FALSE; /* set the global */
UnLock (fl); /* remove the lock */
}
else disk_newsave = TRUE;
return ((LONG) fl);
}
/*------------------------------*/
/* open_game */
/*------------------------------*/
LONG
open_game()
{
game_ref = Open (gamename, MODE_OLDFILE);
if (game_ref == 0)
return (geterr());
else return (0); /* zero means OK */
}
/*------------------------------*/
/* close_game */
/*------------------------------*/
LONG
close_game ()
{
if (game_ref) {
Close (game_ref);
game_ref = 0;
}
old_channel = -1; /* make sure to reset this one */
return (0);
}
/*------------------------------*/
/* make_icon_file */
/*------------------------------*/
LONG
make_icon_file () /* create a new save file icon, */
/* called only after a successful SAVE */
{
LONG error;
LONG object;
/* WORD len; */
/* diskobj (should have been) read into memory during init */
if (diskobj == NULL)
error = 1;
else
{
#if bug_DiskFull
/* PROBLEM: PutDiskObj is crashing the machine when a disk-full error occurs.
First, make sure we have enough room for a new icon file.
*/
#define MaxIconSize 1024 /* actually ours is under 400 */
if (disk_newsave) /* if OLD save, space not an issue */
{
disk_free = disk_bytes_avail ();
if (disk_free < MaxIconSize) return (1);
}
/* (Alternate fix. The preceeding code fixes this problem better.)
Do some preliminary error checking: Attempt to create a file with the
desired name, and put 1K of random data into it.
*/
strcat (&savename, ".info"); /* desired name */
error = create_file (); /* --> save_ref */
if (!error)
{
error = write_file (save_ref, 0, 1024, &savename);
close_file (save_ref); /* (no error codes) */
if (!error)
error = delete_file (); /* <-- savename */
else delete_file ();
}
len = strlen (&savename);
savename[len-5] = 0; /* chop off the ".info" */
if (error) /* if ANY error, fail now */
return (error);
#endif /* bug_diskfull */
/* Everything seems OK, go ahead and make the Save icon */
diskobj->do_CurrentX = NO_ICON_POSITION;
diskobj->do_CurrentY = NO_ICON_POSITION;
/* write icon file via same path as save file */
object = PutDiskObject (&savename, diskobj);
if (object == NULL)
error = geterr();
else error = 0;
}
return (error);
}
/************************************************************************/
/* Scripting */
/************************************************************************/
/* scripting globals -- two methods possible */
LONG print_file; /* scripting refnum */
/**
struct IOStdReq_MUNGED SIOStdReq; /-* not used *-/
struct MsgPort SMsgPort; **/
WORD prt_inited = 0; /* set when printer opened */
WORD prt_active = 0; /* set when scripting is active */
/*------------------------------*/
/* raw_script */
/*------------------------------*/
WORD
raw_script (buffer, length) /* send data to the printer device */
LONG buffer, length;
{
WORD error;
LONG seconds1, micros1;
LONG seconds2, micros2;
LONG actual_len;
/* A problem: AmigaDOS (rev 1.0) isn't passing back printer error codes.
Each attempt to print when the printer isn't ready causes a 30 second
timeout. A series of these effectively hangs the game.
To fix, use the delay to infer an error. If no error code is returned
but the delay exceeds 30 seconds, consider the printer not ready.
*/
CurrentTime (&seconds1, &micros1); /* starting time */
actual_len = Write (print_file, buffer, length);
if (actual_len == length)
error = 0;
else error = 1;
/* SIOStdReq.io_Data = (APTR) buffer;
SIOStdReq.io_Length = length;
SIOStdReq.io_Command = CMD_WRITE;
DoIO (&SIOStdReq);
error = (SIOStdReq.io_Error);
*/
CurrentTime (&seconds2, &micros2);
if (!error) /* says no error, but is it lying? */
{
if ((seconds2 - seconds1) >= (30 - 1))
error = 99;
}
return (error); /* error code, zero if OK */
}
/*------------------------------*/
/* script_init */
/*------------------------------*/
/*
Two approaches to printing were tried; neither returns errors. The hairy
one crashes (CloseDevice) when multi-tasked, so use the simple one.
*/
LONG
script_init (open) /* initialize/uninitialize the printer */
WORD open;
{
LONG error;
if (open)
{
print_file = Open ("PRT:", MODE_OLDFILE);
if (print_file) /* zero means error */
error = 0;
else error = 1;
/** OpenDevice ("printer.device", 0, &SIOStdReq, 0);
/-* set up the message port in the I/O request *-/
SMsgPort.mp_Node.ln_Type = NT_MSGPORT;
SMsgPort.mp_Flags = 0;
SMsgPort.mp_SigBit = AllocSignal (-1);
SMsgPort.mp_SigTask = (struct Task *) FindTask (NULL);
AddPort (&SMsgPort);
SIOStdReq.io_Message.mn_ReplyPort = &SMsgPort;
**/ }
else
{
if (print_file) /* call only if once opened */
Close (print_file);
error = 0; /* no close errors? */
/* clean up the various structures we created */
/** RemPort (&SMsgPort);
FreeSignal (SMsgPort.mp_SigBit);
CloseDevice (&SIOStdReq);
**/ }
return (error); /* zero if none */
}
/*------------------------------*/
/* script_open */
/*------------------------------*/
WORD
script_open (start) /* kernel issued request to start/stop scripting,
return status, either active or inactive */
WORD start;
{
CHAR reset_seq[2];
LONG error;
/* starting, may need to open/reset the printer */
if (start)
{
if (!prt_inited) /* device never opened, do so now */
{
error = script_init (TRUE);
if (!error)
prt_inited = TRUE; /* okay, opened */
}
if (prt_inited && !prt_active) /* new start, reset printer */
{
reset_seq[0] = '\33'; /* ESC ($1B) */
reset_seq[1] = 'c'; /* 'c' means reset */
error = raw_script (&reset_seq, 2);
if (!error)
prt_active = TRUE; /* okay, active */
}
}
else /* if stop, flag it and shut down */
{
prt_active = FALSE;
if (prt_inited)
{
script_init (FALSE); prt_inited = FALSE;
}
}
return (prt_active);
}
/*------------------------------*/
/* script_line */
/*------------------------------*/
WORD
script_line (buffer, length) /* script a line, tack on an EOL,
return error code (zero if none) */
LONG buffer, length;
{
CHAR eol_seq[2];
WORD error;
if (length > 0)
error = raw_script (buffer, length);
else error = 0;
/* The EOL seq used to be an "ISO standard" Esc-E (1B45), but under
AmigaDOS 1.2 this seems to have no effect. Changing to CRLF (0D0A)
works under both 1.1 and 1.2 except that the spacing is too wide
(resembles triple, used to resemble double). It appears that AmigaDOS
(/not/ the printer) automatically makes the initial CR a CRLF. So,
we will output only the CR and hope all printers are happy.
By the way, a way to check script spacing is to compare it to what
AmigaDOS does after typing "TYPE textfile TO PRT:". Also, some
printer behavior can be controlled from User Preferences, like
margins and 6- or 8-line spacing.
*/
if (!error)
{
eol_seq[0] = 0x0D;
/** eol_seq[1] = 0x0A; **/
error = raw_script (&eol_seq, 1);
}
return (error);
}