mirror of
https://github.com/Pecusx/libretro-atari800.git
synced 2026-05-21 06:39:36 +02:00
2726 lines
63 KiB
C
2726 lines
63 KiB
C
/*
|
|
* devices.c - emulation of H:, P:, E: and K: Atari devices
|
|
*
|
|
* Copyright (C) 1995-1998 David Firth
|
|
* Copyright (C) 1998-2010 Atari800 development team (see DOC/CREDITS)
|
|
*
|
|
* This file is part of the Atari800 emulator project which emulates
|
|
* the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
|
|
*
|
|
* Atari800 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.
|
|
*
|
|
* Atari800 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 Atari800; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/* Bibliography:
|
|
[OSMAN] - Atari Home Computer System Technical Reference Notes - Operating System
|
|
User's Manual - CA016555 Rev. A - 1982 Atari, Inc.
|
|
*/
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
#ifdef HAVE_UNIXIO_H
|
|
/* VMS */
|
|
#include <unixio.h>
|
|
#endif
|
|
#ifdef HAVE_FILE_H
|
|
/* VMS */
|
|
#include <file.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_DIRECT_H
|
|
/* WIN32 */
|
|
#include <direct.h> /* mkdir, rmdir */
|
|
#endif
|
|
/* XXX: <sys/dir.h>, <ndir.h>, <sys/ndir.h> */
|
|
#ifdef HAVE_DIRENT_H
|
|
#include <dirent.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#include "atari.h"
|
|
#include "binload.h"
|
|
#include "cpu.h"
|
|
#include "devices.h"
|
|
#include "esc.h"
|
|
#include "log.h"
|
|
#include "memory.h"
|
|
#include "sio.h"
|
|
#include "sysrom.h"
|
|
#include "util.h"
|
|
#ifdef R_IO_DEVICE
|
|
#include "rdevice.h"
|
|
#endif
|
|
#ifdef __PLUS
|
|
#include "misc_win.h"
|
|
#endif
|
|
|
|
#ifndef S_IREAD
|
|
#define S_IREAD S_IRUSR
|
|
#endif
|
|
#ifndef S_IWRITE
|
|
#define S_IWRITE S_IWUSR
|
|
#endif
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
#include <windows.h>
|
|
|
|
#undef FILENAME_CONV
|
|
#undef FILENAME
|
|
|
|
#ifdef UNICODE
|
|
#define FILENAME_CONV \
|
|
WCHAR wfilename[FILENAME_MAX]; \
|
|
if (MultiByteToWideChar(CP_ACP, 0, filename, -1, wfilename, FILENAME_MAX) <= 0) \
|
|
return FALSE;
|
|
#define FILENAME wfilename
|
|
#else /* UNICODE */
|
|
#define FILENAME_CONV
|
|
#define FILENAME filename
|
|
#endif /* UNICODE */
|
|
|
|
#endif /* HAVE_WINDOWS_H */
|
|
|
|
|
|
/* Read Directory abstraction layer -------------------------------------- */
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
static char dir_path[FILENAME_MAX];
|
|
static WIN32_FIND_DATA wfd;
|
|
static HANDLE dh = INVALID_HANDLE_VALUE;
|
|
|
|
static int Devices_OpenDir(const char *filename)
|
|
{
|
|
FILENAME_CONV;
|
|
Util_splitpath(filename, dir_path, NULL);
|
|
if (dh != INVALID_HANDLE_VALUE)
|
|
FindClose(dh);
|
|
dh = FindFirstFile(FILENAME, &wfd);
|
|
if (dh == INVALID_HANDLE_VALUE) {
|
|
/* don't raise error if the path is ok but no file matches:
|
|
Win98 returns ERROR_FILE_NOT_FOUND,
|
|
WinCE returns ERROR_NO_MORE_FILES */
|
|
DWORD err = GetLastError();
|
|
if (err != ERROR_FILE_NOT_FOUND && err != ERROR_NO_MORE_FILES)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static int Devices_ReadDir(char *fullpath, char *filename, int *isdir,
|
|
int *readonly, int *size, char *timetext)
|
|
{
|
|
#ifdef UNICODE
|
|
char afilename[MAX_PATH];
|
|
#endif
|
|
if (dh == INVALID_HANDLE_VALUE)
|
|
return FALSE;
|
|
/* don't match "." nor ".." */
|
|
while (wfd.cFileName[0] == '.' &&
|
|
(wfd.cFileName[1] == '\0' || (wfd.cFileName[1] == '.' && wfd.cFileName[2] == '\0'))
|
|
) {
|
|
if (!FindNextFile(dh, &wfd)) {
|
|
FindClose(dh);
|
|
dh = INVALID_HANDLE_VALUE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
#ifdef UNICODE
|
|
if (WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, -1, afilename, MAX_PATH, NULL, NULL) <= 0)
|
|
strcpy(afilename, "?ERROR");
|
|
#define FOUND_FILENAME afilename
|
|
#else
|
|
#define FOUND_FILENAME wfd.cFileName
|
|
#endif /* UNICODE */
|
|
if (filename != NULL)
|
|
strcpy(filename, FOUND_FILENAME);
|
|
if (fullpath != NULL)
|
|
Util_catpath(fullpath, dir_path, FOUND_FILENAME);
|
|
if (isdir != NULL)
|
|
*isdir = (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
|
|
if (readonly != NULL)
|
|
*readonly = (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? TRUE : FALSE;
|
|
if (size != NULL)
|
|
*size = (int) wfd.nFileSizeLow;
|
|
if (timetext != NULL) {
|
|
FILETIME lt;
|
|
SYSTEMTIME st;
|
|
if (FileTimeToLocalFileTime(&wfd.ftLastWriteTime, <) != 0
|
|
&& FileTimeToSystemTime(<, &st) != 0) {
|
|
int hour = st.wHour;
|
|
char ampm = 'a';
|
|
if (hour >= 12) {
|
|
hour -= 12;
|
|
ampm = 'p';
|
|
}
|
|
if (hour == 0)
|
|
hour = 12;
|
|
sprintf(timetext, "%2d-%02d-%02d %2d:%02d%c",
|
|
st.wMonth, st.wDay, st.wYear % 100, hour, st.wMinute, ampm);
|
|
}
|
|
else
|
|
strcpy(timetext, " 1-01-01 12:00p");
|
|
}
|
|
|
|
if (!FindNextFile(dh, &wfd)) {
|
|
FindClose(dh);
|
|
dh = INVALID_HANDLE_VALUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#define DO_DIR
|
|
|
|
#elif defined(HAVE_OPENDIR)
|
|
|
|
static int match(const char *pattern, const char *filename)
|
|
{
|
|
if (strcmp(pattern, "*.*") == 0)
|
|
return TRUE;
|
|
|
|
for (;;) {
|
|
switch (*pattern) {
|
|
case '\0':
|
|
return (*filename == '\0');
|
|
case '?':
|
|
if (*filename == '\0' || *filename == '.')
|
|
return FALSE;
|
|
pattern++;
|
|
filename++;
|
|
break;
|
|
case '*':
|
|
if (Util_chrieq(*filename, pattern[1]))
|
|
pattern++;
|
|
else if (*filename == '\0')
|
|
return FALSE; /* because pattern[1] != '\0' */
|
|
else
|
|
filename++;
|
|
break;
|
|
default:
|
|
if (!Util_chrieq(*pattern, *filename))
|
|
return FALSE;
|
|
pattern++;
|
|
filename++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char dir_path[FILENAME_MAX];
|
|
static char filename_pattern[FILENAME_MAX];
|
|
static DIR *dp = NULL;
|
|
|
|
static int Devices_OpenDir(const char *filename)
|
|
{
|
|
Util_splitpath(filename, dir_path, filename_pattern);
|
|
if (dp != NULL)
|
|
closedir(dp);
|
|
dp = opendir(dir_path);
|
|
return dp != NULL;
|
|
}
|
|
|
|
static int Devices_ReadDir(char *fullpath, char *filename, int *isdir,
|
|
int *readonly, int *size, char *timetext)
|
|
{
|
|
struct dirent *entry;
|
|
char temppath[FILENAME_MAX];
|
|
#ifdef HAVE_STAT
|
|
struct stat status;
|
|
#endif
|
|
for (;;) {
|
|
entry = readdir(dp);
|
|
if (entry == NULL) {
|
|
closedir(dp);
|
|
dp = NULL;
|
|
return FALSE;
|
|
}
|
|
if (entry->d_name[0] == '.') {
|
|
/* don't match Unix hidden files unless specifically requested */
|
|
if (filename_pattern[0] != '.')
|
|
continue;
|
|
/* never match "." */
|
|
if (entry->d_name[1] == '\0')
|
|
continue;
|
|
/* never match ".." */
|
|
if (entry->d_name[1] == '.' && entry->d_name[2] == '\0')
|
|
continue;
|
|
}
|
|
if (match(filename_pattern, entry->d_name))
|
|
break;
|
|
}
|
|
if (filename != NULL)
|
|
strcpy(filename, entry->d_name);
|
|
Util_catpath(temppath, dir_path, entry->d_name);
|
|
if (fullpath != NULL)
|
|
strcpy(fullpath, temppath);
|
|
#ifdef HAVE_STAT
|
|
if (stat(temppath, &status) == 0) {
|
|
if (isdir != NULL)
|
|
*isdir = S_ISDIR(status.st_mode);
|
|
if (readonly != NULL)
|
|
*readonly = (status.st_mode & S_IWRITE) ? FALSE : TRUE;
|
|
if (size != NULL)
|
|
*size = (int) status.st_size;
|
|
if (timetext != NULL) {
|
|
#ifdef HAVE_LOCALTIME
|
|
struct tm *ft;
|
|
int hour;
|
|
char ampm = 'a';
|
|
ft = localtime(&status.st_mtime);
|
|
hour = ft->tm_hour;
|
|
if (hour >= 12) {
|
|
hour -= 12;
|
|
ampm = 'p';
|
|
}
|
|
if (hour == 0)
|
|
hour = 12;
|
|
sprintf(timetext, "%2d-%02d-%02d %2d:%02d%c",
|
|
ft->tm_mon + 1, ft->tm_mday, ft->tm_year % 100,
|
|
hour, ft->tm_min, ampm);
|
|
#else
|
|
strcpy(timetext, " 1-01-01 12:00p");
|
|
#endif /* HAVE_LOCALTIME */
|
|
}
|
|
}
|
|
else
|
|
#endif /* HAVE_STAT */
|
|
{
|
|
if (isdir != NULL)
|
|
*isdir = FALSE;
|
|
if (readonly != NULL)
|
|
*readonly = FALSE;
|
|
if (size != NULL)
|
|
*size = 0;
|
|
if (timetext != NULL)
|
|
strcpy(timetext, " 1-01-01 12:00p");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#define DO_DIR
|
|
|
|
#elif defined(PS2)
|
|
|
|
extern char dir_path[FILENAME_MAX];
|
|
|
|
int Atari_OpenDir(const char *filename);
|
|
|
|
#define Devices_OpenDir Atari_OpenDir
|
|
|
|
int Atari_ReadDir(char *fullpath, char *filename, int *isdir,
|
|
int *readonly, int *size, char *timetext);
|
|
|
|
static int Devices_ReadDir(char *fullpath, char *filename, int *isdir,
|
|
int *readonly, int *size, char *timetext)
|
|
{
|
|
char tmp_filename[FILENAME_MAX];
|
|
if (filename == NULL)
|
|
filename = tmp_filename;
|
|
do {
|
|
if (!Atari_ReadDir(fullpath, filename, isdir, readonly, size, timetext))
|
|
return FALSE;
|
|
/* reject "." and ".." */
|
|
} while (filename[0] == '.' &&
|
|
(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')));
|
|
return TRUE;
|
|
}
|
|
|
|
#define DO_DIR
|
|
|
|
#endif /* defined(PS2) */
|
|
|
|
|
|
/* Rename File/Directory abstraction layer ------------------------------- */
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
static int Devices_Rename(const char *oldname, const char *newname)
|
|
{
|
|
#ifdef UNICODE
|
|
WCHAR woldname[FILENAME_MAX];
|
|
WCHAR wnewname[FILENAME_MAX];
|
|
if (MultiByteToWideChar(CP_ACP, 0, oldname, -1, woldname, FILENAME_MAX) <= 0
|
|
|| MultiByteToWideChar(CP_ACP, 0, newname, -1, wnewname, FILENAME_MAX) <= 0)
|
|
return FALSE;
|
|
return MoveFile(woldname, wnewname) != 0;
|
|
#else
|
|
return MoveFile(oldname, newname) != 0;
|
|
#endif /* UNICODE */
|
|
}
|
|
|
|
#define DO_RENAME
|
|
|
|
#elif defined(HAVE_RENAME)
|
|
|
|
static int Devices_Rename(const char *oldname, const char *newname)
|
|
{
|
|
return rename(oldname, newname) == 0;
|
|
}
|
|
|
|
#define DO_RENAME
|
|
|
|
#endif
|
|
|
|
|
|
/* Set/Reset Read-Only Attribute abstraction layer ----------------------- */
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
/* Enables/disables read-only mode for the file. Returns TRUE on success. */
|
|
static int Devices_SetReadOnly(const char *filename, int readonly)
|
|
{
|
|
DWORD attr;
|
|
FILENAME_CONV;
|
|
attr = GetFileAttributes(FILENAME);
|
|
if (attr == 0xffffffff)
|
|
return FALSE;
|
|
return SetFileAttributes(FILENAME, readonly
|
|
? (attr | FILE_ATTRIBUTE_READONLY)
|
|
: (attr & ~FILE_ATTRIBUTE_READONLY)) != 0;
|
|
}
|
|
|
|
#define DO_LOCK
|
|
|
|
#elif defined(HAVE_CHMOD)
|
|
|
|
static int Devices_SetReadOnly(const char *filename, int readonly)
|
|
{
|
|
return chmod(filename, readonly ? S_IREAD : (S_IREAD | S_IWRITE)) == 0;
|
|
}
|
|
|
|
#define DO_LOCK
|
|
|
|
#endif /* defined(HAVE_CHMOD) */
|
|
|
|
|
|
/* Make Directory abstraction layer -------------------------------------- */
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
static int Devices_MakeDirectory(const char *filename)
|
|
{
|
|
FILENAME_CONV;
|
|
return CreateDirectory(FILENAME, NULL) != 0;
|
|
}
|
|
|
|
#define DO_MKDIR
|
|
|
|
#elif defined(HAVE_MKDIR)
|
|
#ifdef __LIBRETRO__
|
|
#include <file/file_path.h>
|
|
#endif
|
|
static int Devices_MakeDirectory(const char *filename)
|
|
{
|
|
#ifdef __LIBRETRO__
|
|
return path_mkdir(filename)==1;
|
|
#else
|
|
|
|
#ifdef __WIN32__
|
|
#define MKDIR_TAKES_ONE_ARG 1
|
|
#endif
|
|
return mkdir(filename
|
|
#ifndef MKDIR_TAKES_ONE_ARG
|
|
, 0777
|
|
#endif
|
|
) == 0;
|
|
|
|
#endif
|
|
}
|
|
|
|
#define DO_MKDIR
|
|
|
|
#endif /* defined(HAVE_MKDIR) */
|
|
|
|
|
|
/* Remove Directory abstraction layer ------------------------------------ */
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
static UBYTE Devices_RemoveDirectory(const char *filename)
|
|
{
|
|
FILENAME_CONV;
|
|
if (RemoveDirectory(FILENAME) != 0)
|
|
return 1;
|
|
return (UBYTE) ((HRESULT_CODE(GetLastError()) == ERROR_DIR_NOT_EMPTY) ? 167 : 150);
|
|
}
|
|
|
|
#define DO_RMDIR
|
|
|
|
#elif defined(HAVE_RMDIR)
|
|
|
|
static UBYTE Devices_RemoveDirectory(const char *filename)
|
|
{
|
|
if (rmdir(filename) == 0)
|
|
return 1;
|
|
return (UBYTE) ((errno == ENOTEMPTY) ? 167 : 150);
|
|
}
|
|
|
|
#define DO_RMDIR
|
|
|
|
#endif /* defined(HAVE_RMDIR) */
|
|
|
|
|
|
/* H: device emulation --------------------------------------------------- */
|
|
|
|
#define DEFAULT_H_PATH "H1:>DOS;>DOS"
|
|
|
|
/* emulator debugging mode */
|
|
static int devbug = FALSE;
|
|
|
|
/* host path for each H: unit */
|
|
char Devices_atari_h_dir[4][FILENAME_MAX];
|
|
|
|
/* read only mode for H: device */
|
|
int Devices_h_read_only = TRUE;
|
|
|
|
/* ';'-separated list of Atari paths checked by the "load executable"
|
|
command. if a path does not start with "Hn:", then the selected device
|
|
is used. */
|
|
char Devices_h_exe_path[FILENAME_MAX] = DEFAULT_H_PATH;
|
|
|
|
/* Devices_h_current_dir must be empty or terminated with Util_DIR_SEP_CHAR;
|
|
only Util_DIR_SEP_CHAR can be used as a directory separator here */
|
|
char Devices_h_current_dir[4][FILENAME_MAX];
|
|
|
|
/* stream open via H: device per IOCB */
|
|
static FILE *h_fp[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
|
|
/* H: text mode per IOCB */
|
|
static int h_textmode[8];
|
|
|
|
/* H: last read character per IOCB */
|
|
static int h_lastbyte[8];
|
|
|
|
/* last read character was CR, per IOCB */
|
|
static int h_wascr[8];
|
|
|
|
/* last operation: 'o': open, 'r': read, 'w': write, 'p': point, 'b': binary
|
|
load, per IOCB. This is needed to apply fseek(fp, 0, SEEK_CUR) between reads
|
|
and writes in update (12) mode, and to support the read-ahead of 1 byte
|
|
in Devices_h_read. */
|
|
static char h_lastop[8];
|
|
|
|
Util_tmpbufdef(static, h_tmpbuf[8])
|
|
|
|
/* IOCB #, 0-7 */
|
|
static int h_iocb;
|
|
|
|
/* H: device number, 0-3 */
|
|
static int h_devnum;
|
|
|
|
/* filename as specified after "Hn:" */
|
|
static char atari_filename[FILENAME_MAX];
|
|
|
|
#ifdef DO_RENAME
|
|
/* new filename (no directories!) */
|
|
static char new_filename[FILENAME_MAX];
|
|
#endif
|
|
|
|
/* atari_filename applied to H:'s current dir, with Util_DIR_SEP_CHARs only */
|
|
static char atari_path[FILENAME_MAX];
|
|
|
|
/* full filename for the current operation */
|
|
static char host_path[FILENAME_MAX];
|
|
|
|
int Devices_H_CountOpen(void)
|
|
{
|
|
int r = 0;
|
|
int i;
|
|
for (i = 0; i < 8; i++)
|
|
if (h_fp[i] != NULL)
|
|
r++;
|
|
return r;
|
|
}
|
|
|
|
void Devices_H_CloseAll(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < 8; i++)
|
|
if (h_fp[i] != NULL) {
|
|
Util_fclose(h_fp[i], h_tmpbuf[i]);
|
|
h_fp[i] = NULL;
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Init(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHINIT");
|
|
Devices_h_current_dir[0][0] = '\0';
|
|
Devices_h_current_dir[1][0] = '\0';
|
|
Devices_h_current_dir[2][0] = '\0';
|
|
Devices_h_current_dir[3][0] = '\0';
|
|
Devices_H_CloseAll();
|
|
}
|
|
|
|
int Devices_Initialise(int *argc, char *argv[])
|
|
{
|
|
int i;
|
|
int j;
|
|
for (i = j = 1; i < *argc; i++) {
|
|
int i_a = (i + 1 < *argc); /* is argument available? */
|
|
int a_m = FALSE; /* error, argument missing! */
|
|
|
|
if (strcmp(argv[i], "-H1") == 0) {
|
|
if (i_a)
|
|
Util_strlcpy(Devices_atari_h_dir[0], argv[++i], FILENAME_MAX);
|
|
else a_m = TRUE;
|
|
}
|
|
else if (strcmp(argv[i], "-H2") == 0) {
|
|
if (i_a)
|
|
Util_strlcpy(Devices_atari_h_dir[1], argv[++i], FILENAME_MAX);
|
|
else a_m = TRUE;
|
|
}
|
|
else if (strcmp(argv[i], "-H3") == 0) {
|
|
if (i_a)
|
|
Util_strlcpy(Devices_atari_h_dir[2], argv[++i], FILENAME_MAX);
|
|
else a_m = TRUE;
|
|
}
|
|
else if (strcmp(argv[i], "-H4") == 0) {
|
|
if (i_a)
|
|
Util_strlcpy(Devices_atari_h_dir[3], argv[++i], FILENAME_MAX);
|
|
else a_m = TRUE;
|
|
}
|
|
else if (strcmp(argv[i], "-Hpath") == 0) {
|
|
if (i_a)
|
|
Util_strlcpy(Devices_h_exe_path, argv[++i], FILENAME_MAX);
|
|
else a_m = TRUE;
|
|
}
|
|
else if (strcmp(argv[i], "-hreadonly") == 0)
|
|
Devices_h_read_only = TRUE;
|
|
else if (strcmp(argv[i], "-hreadwrite") == 0)
|
|
Devices_h_read_only = FALSE;
|
|
else if (strcmp(argv[i], "-devbug") == 0)
|
|
devbug = TRUE;
|
|
else {
|
|
if (strcmp(argv[i], "-help") == 0) {
|
|
Log_print("\t-H1 <path> Set path for H1: device");
|
|
Log_print("\t-H2 <path> Set path for H2: device");
|
|
Log_print("\t-H3 <path> Set path for H3: device");
|
|
Log_print("\t-H4 <path> Set path for H4: device");
|
|
Log_print("\t-Hpath <path> Set path for Atari executables on the H: device");
|
|
Log_print("\t-hreadonly Enable read-only mode for H: device");
|
|
Log_print("\t-hreadwrite Disable read-only mode for H: device");
|
|
Log_print("\t-devbug Debugging messages for H: and P: devices");
|
|
}
|
|
argv[j++] = argv[i];
|
|
}
|
|
|
|
if (a_m) {
|
|
Log_print("Missing argument for '%s'", argv[i]);
|
|
return FALSE;
|
|
}
|
|
}
|
|
*argc = j;
|
|
Devices_H_Init();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void Devices_Exit(void)
|
|
{
|
|
Devices_H_CloseAll();
|
|
}
|
|
|
|
#define IS_DIR_SEP(c) ((c) == '/' || (c) == '\\' || (c) == ':' || (c) == '>')
|
|
|
|
static int Devices_IsValidForFilename(char ch)
|
|
{
|
|
if ((ch >= 'A' && ch <= 'Z')
|
|
|| (ch >= 'a' && ch <= 'z')
|
|
|| (ch >= '0' && ch <= '9'))
|
|
return TRUE;
|
|
switch (ch) {
|
|
case '!':
|
|
case '#':
|
|
case '$':
|
|
case '&':
|
|
case '\'':
|
|
case '(':
|
|
case ')':
|
|
case '*':
|
|
case '-':
|
|
case '.':
|
|
case '?':
|
|
case '@':
|
|
case '_':
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
UWORD Devices_SkipDeviceName(void)
|
|
{
|
|
UWORD bufadr;
|
|
for (bufadr = MEMORY_dGetWordAligned(Devices_ICBALZ); ; bufadr++) {
|
|
char c = (char) MEMORY_dGetByte(bufadr);
|
|
if (c == ':')
|
|
return (UWORD) (bufadr + 1);
|
|
if (c < '!' || c > '\x7e')
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* devnum must be 0-3; p must point inside atari_filename */
|
|
static UWORD Devices_GetAtariPath(int devnum, char *p)
|
|
{
|
|
UWORD bufadr = Devices_SkipDeviceName();
|
|
if (bufadr != 0) {
|
|
while (p < atari_filename + sizeof(atari_filename) - 1) {
|
|
char c = (char) MEMORY_dGetByte(bufadr);
|
|
if (Devices_IsValidForFilename(c) || IS_DIR_SEP(c) || c == '<') {
|
|
*p++ = c;
|
|
bufadr++;
|
|
}
|
|
else {
|
|
/* end of filename */
|
|
/* now apply it to Devices_h_current_dir */
|
|
const char *q = atari_filename;
|
|
*p = '\0';
|
|
if (IS_DIR_SEP(*q)) {
|
|
/* absolute path on H: device */
|
|
q++;
|
|
p = atari_path;
|
|
}
|
|
else {
|
|
strcpy(atari_path, Devices_h_current_dir[devnum]);
|
|
p = atari_path + strlen(atari_path);
|
|
}
|
|
for (;;) {
|
|
/* we are here at the beginning of a path element,
|
|
i.e. at the beginning of atari_path or after Util_DIR_SEP_CHAR */
|
|
if (*q == '<'
|
|
|| (*q == '.' && q[1] == '.' && (q[2] == '\0' || IS_DIR_SEP(q[2])))) {
|
|
/* "<" or "..": parent directory */
|
|
if (p == atari_path) {
|
|
CPU_regY = 150; /* Sparta: directory not found */
|
|
CPU_SetN;
|
|
return 0;
|
|
}
|
|
do
|
|
p--;
|
|
while (p > atari_path && p[-1] != Util_DIR_SEP_CHAR);
|
|
if (*q == '.') {
|
|
if (q[2] != '\0')
|
|
q++;
|
|
q++;
|
|
}
|
|
q++;
|
|
continue;
|
|
}
|
|
if (IS_DIR_SEP(*q)) {
|
|
/* duplicate DIR_SEP */
|
|
CPU_regY = 165; /* bad filename */
|
|
CPU_SetN;
|
|
return 0;
|
|
}
|
|
do {
|
|
if (p >= atari_path + sizeof(atari_path) - 1) {
|
|
CPU_regY = 165; /* bad filename */
|
|
CPU_SetN;
|
|
return 0;
|
|
}
|
|
*p++ = *q;
|
|
if (*q == '\0')
|
|
return bufadr;
|
|
q++;
|
|
} while (!IS_DIR_SEP(*q));
|
|
*p++ = Util_DIR_SEP_CHAR;
|
|
q++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CPU_regY = 165; /* bad filename */
|
|
CPU_SetN;
|
|
return 0;
|
|
}
|
|
|
|
static int Devices_GetIOCB(void)
|
|
{
|
|
if ((CPU_regX & 0x8f) != 0) {
|
|
CPU_regY = 134; /* invalid IOCB number */
|
|
CPU_SetN;
|
|
return FALSE;
|
|
}
|
|
h_iocb = CPU_regX >> 4;
|
|
return TRUE;
|
|
}
|
|
|
|
static int Devices_GetNumber(int set_textmode)
|
|
{
|
|
int devnum;
|
|
if (!Devices_GetIOCB())
|
|
return -1;
|
|
devnum = MEMORY_dGetByte(Devices_ICDNOZ);
|
|
if (devnum > 9 || devnum == 0 || devnum == 5) {
|
|
CPU_regY = 160; /* invalid unit/drive number */
|
|
CPU_SetN;
|
|
return -1;
|
|
}
|
|
if (devnum < 5) {
|
|
if (set_textmode)
|
|
h_textmode[h_iocb] = FALSE;
|
|
return devnum - 1;
|
|
}
|
|
if (set_textmode)
|
|
h_textmode[h_iocb] = TRUE;
|
|
return devnum - 6;
|
|
}
|
|
|
|
static UWORD Devices_GetHostPath(int set_textmode)
|
|
{
|
|
UWORD bufadr;
|
|
h_devnum = Devices_GetNumber(set_textmode);
|
|
if (h_devnum < 0)
|
|
return 0;
|
|
bufadr = Devices_GetAtariPath(h_devnum, atari_filename);
|
|
if (bufadr == 0)
|
|
return 0;
|
|
Util_catpath(host_path, Devices_atari_h_dir[h_devnum], atari_path);
|
|
return bufadr;
|
|
}
|
|
|
|
static void Devices_H_Open(void)
|
|
{
|
|
FILE *fp;
|
|
UBYTE aux1;
|
|
#ifdef DO_DIR
|
|
UBYTE aux2;
|
|
char entryname[FILENAME_MAX];
|
|
int isdir;
|
|
int readonly;
|
|
int size;
|
|
char timetext[16];
|
|
#endif
|
|
|
|
if (devbug)
|
|
Log_print("HHOPEN");
|
|
|
|
if (Devices_GetHostPath(TRUE) == 0)
|
|
return;
|
|
|
|
if (h_fp[h_iocb] != NULL)
|
|
Util_fclose(h_fp[h_iocb], h_tmpbuf[h_iocb]);
|
|
|
|
#if 0
|
|
if (devbug)
|
|
Log_print("atari_filename=\"%s\", atari_path=\"%s\" host_path=\"%s\"", atari_filename, atari_path, host_path);
|
|
#endif
|
|
|
|
fp = NULL;
|
|
h_wascr[h_iocb] = FALSE;
|
|
h_lastop[h_iocb] = 'o';
|
|
|
|
aux1 = MEMORY_dGetByte(Devices_ICAX1Z);
|
|
switch (aux1) {
|
|
case 4:
|
|
/* don't bother using "r" for textmode:
|
|
we want to support LF, CR/LF and CR, not only native EOLs */
|
|
fp = Util_fopen(host_path, "rb", h_tmpbuf[h_iocb]);
|
|
if (fp != NULL) {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
}
|
|
break;
|
|
#ifdef DO_DIR
|
|
case 6:
|
|
case 7:
|
|
fp = Util_tmpopen(h_tmpbuf[h_iocb]);
|
|
if (fp == NULL) {
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
break;
|
|
}
|
|
if (!Devices_OpenDir(host_path)) {
|
|
Util_fclose(fp, h_tmpbuf[h_iocb]);
|
|
fp = NULL;
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
break;
|
|
}
|
|
aux2 = MEMORY_dGetByte(Devices_ICAX2Z);
|
|
if (aux2 >= 128) {
|
|
fprintf(fp, "\nVolume: HDISK%c\nDirectory: ", '1' + h_devnum);
|
|
/* if (strcmp(dir_path, Devices_atari_h_dir[h_devnum]) == 0) */
|
|
if (strchr(atari_path, Util_DIR_SEP_CHAR) == NULL)
|
|
fprintf(fp, "MAIN\n\n");
|
|
else {
|
|
char end_dir_str[FILENAME_MAX];
|
|
Util_splitpath(dir_path, NULL, end_dir_str);
|
|
fprintf(fp, "%s\n\n", /* Util_strupper */(end_dir_str));
|
|
}
|
|
}
|
|
|
|
while (Devices_ReadDir(NULL, entryname, &isdir, &readonly, &size,
|
|
(aux2 >= 128) ? timetext : NULL)) {
|
|
char *ext;
|
|
/* Util_strupper(entryname); */
|
|
ext = strrchr(entryname, '.');
|
|
if (ext == NULL)
|
|
ext = "";
|
|
else {
|
|
/* replace the dot with NUL,
|
|
so entryname is without extension */
|
|
*ext++ = '\0';
|
|
if (ext[0] != '\0' && ext[1] != '\0' && ext[2] != '\0' && ext[3] != '\0') {
|
|
ext[2] = '+';
|
|
ext[3] = '\0';
|
|
}
|
|
}
|
|
if (strlen(entryname) > 8) {
|
|
entryname[7] = '+';
|
|
entryname[8] = '\0';
|
|
}
|
|
if (aux2 >= 128) {
|
|
if (isdir)
|
|
fprintf(fp, "%-13s<DIR> %s\n", entryname, timetext);
|
|
else {
|
|
if (size > 999999)
|
|
size = 999999;
|
|
fprintf(fp, "%-9s%-3s %6d %s\n", entryname, ext, size, timetext);
|
|
}
|
|
}
|
|
else {
|
|
char dirchar = ' ';
|
|
size = (size + 255) >> 8;
|
|
if (size > 999)
|
|
size = 999;
|
|
if (isdir) {
|
|
if (MEMORY_dGetByte(0x700) == 'M') /* MyDOS */
|
|
dirchar = ':';
|
|
else /* Sparta */
|
|
ext = "\304\311\322"; /* "DIR" with bit 7 set */
|
|
}
|
|
fprintf(fp, "%c%c%-8s%-3s %03d\n", readonly ? '*' : ' ',
|
|
dirchar, entryname, ext, size);
|
|
}
|
|
}
|
|
|
|
if (aux2 >= 128)
|
|
fprintf(fp, " 999 FREE SECTORS\n");
|
|
else
|
|
fprintf(fp, "999 FREE SECTORS\n");
|
|
|
|
Util_rewind(fp);
|
|
h_textmode[h_iocb] = TRUE;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
break;
|
|
#endif /* DO_DIR */
|
|
case 8: /* write: "w" */
|
|
case 9: /* write at end of file (append): "a" */
|
|
case 12: /* write and read (update): "r+" || "w+" */
|
|
case 13: /* append and read: "a+" */
|
|
if (Devices_h_read_only) {
|
|
CPU_regY = 163; /* disk write-protected */
|
|
CPU_SetN;
|
|
break;
|
|
}
|
|
{
|
|
char mode[4];
|
|
char *p = mode + 1;
|
|
mode[0] = (aux1 & 1) ? 'a' : (aux1 < 12) ? 'w' : 'r';
|
|
if (!h_textmode[h_iocb])
|
|
*p++ = 'b';
|
|
if (aux1 >= 12)
|
|
*p++ = '+';
|
|
*p = '\0';
|
|
fp = Util_fopen(host_path, mode, h_tmpbuf[h_iocb]);
|
|
if (fp == NULL && aux1 == 12) {
|
|
mode[0] = 'w';
|
|
fp = Util_fopen(host_path, mode, h_tmpbuf[h_iocb]);
|
|
}
|
|
}
|
|
if (fp != NULL) {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
}
|
|
break;
|
|
default:
|
|
CPU_regY = 168; /* invalid device command */
|
|
CPU_SetN;
|
|
break;
|
|
}
|
|
h_fp[h_iocb] = fp;
|
|
}
|
|
|
|
static void Devices_H_Close(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHCLOS");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
if (h_fp[h_iocb] != NULL) {
|
|
Util_fclose(h_fp[h_iocb], h_tmpbuf[h_iocb]);
|
|
h_fp[h_iocb] = NULL;
|
|
}
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_H_Read(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHREAD");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
if (h_fp[h_iocb] != NULL) {
|
|
int ch;
|
|
if (h_lastop[h_iocb] != 'r') {
|
|
if (h_lastop[h_iocb] == 'w')
|
|
fseek(h_fp[h_iocb], 0, SEEK_CUR);
|
|
h_lastbyte[h_iocb] = fgetc(h_fp[h_iocb]);
|
|
h_lastop[h_iocb] = 'r';
|
|
}
|
|
ch = h_lastbyte[h_iocb];
|
|
if (ch != EOF) {
|
|
if (h_textmode[h_iocb]) {
|
|
switch (ch) {
|
|
case 0x0d:
|
|
h_wascr[h_iocb] = TRUE;
|
|
ch = 0x9b;
|
|
break;
|
|
case 0x0a:
|
|
if (h_wascr[h_iocb]) {
|
|
/* ignore LF next to CR */
|
|
ch = fgetc(h_fp[h_iocb]);
|
|
if (ch != EOF) {
|
|
if (ch == 0x0d) {
|
|
h_wascr[h_iocb] = TRUE;
|
|
ch = 0x9b;
|
|
}
|
|
else
|
|
h_wascr[h_iocb] = FALSE;
|
|
}
|
|
else {
|
|
CPU_regY = 136; /* end of file */
|
|
CPU_SetN;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
ch = 0x9b;
|
|
break;
|
|
default:
|
|
h_wascr[h_iocb] = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
CPU_regA = (UBYTE) ch;
|
|
/* [OSMAN] p. 79: Status should be 3 if next read would yield EOF.
|
|
But to set the stream's EOF flag, we need to read the next byte. */
|
|
h_lastbyte[h_iocb] = fgetc(h_fp[h_iocb]);
|
|
CPU_regY = feof(h_fp[h_iocb]) ? 3 : 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 136; /* end of file */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
else {
|
|
CPU_regY = 136; /* end of file; XXX: this seems to be what Atari DOSes return */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Write(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHWRIT");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
if (h_fp[h_iocb] != NULL) {
|
|
int ch;
|
|
if (h_lastop[h_iocb] == 'r')
|
|
fseek(h_fp[h_iocb], 0, SEEK_CUR);
|
|
h_lastop[h_iocb] = 'w';
|
|
ch = CPU_regA;
|
|
if (ch == 0x9b && h_textmode[h_iocb])
|
|
ch = '\n';
|
|
fputc(ch, h_fp[h_iocb]);
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 135; /* attempted to write to a read-only device */
|
|
/* XXX: this seems to be what Atari DOSes return */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Status(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHSTAT");
|
|
|
|
CPU_regY = 146; /* function not implemented in handler; XXX: check file existence? */
|
|
CPU_SetN;
|
|
}
|
|
|
|
#define CHECK_READ_ONLY \
|
|
if (Devices_h_read_only) { \
|
|
CPU_regY = 163; \
|
|
CPU_SetN; \
|
|
return; \
|
|
}
|
|
|
|
#ifdef DO_RENAME
|
|
|
|
static void fillin(const char *pattern, char *filename)
|
|
{
|
|
const char *filename_end = filename + strlen(filename);
|
|
for (;;) {
|
|
switch (*pattern) {
|
|
case '\0':
|
|
*filename = '\0';
|
|
return;
|
|
case '?':
|
|
pattern++;
|
|
if (filename < filename_end)
|
|
filename++;
|
|
break;
|
|
case '*':
|
|
if (filename >= filename_end || *filename == pattern[1])
|
|
pattern++;
|
|
else
|
|
filename++;
|
|
break;
|
|
default:
|
|
*filename++ = *pattern++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Rename(void)
|
|
{
|
|
UWORD bufadr;
|
|
char c;
|
|
char *p;
|
|
int num_changed = 0;
|
|
int num_failed = 0;
|
|
int num_locked = 0;
|
|
int readonly = FALSE;
|
|
|
|
if (devbug)
|
|
Log_print("RENAME Command");
|
|
CHECK_READ_ONLY;
|
|
|
|
bufadr = Devices_GetHostPath(FALSE);
|
|
if (bufadr == 0)
|
|
return;
|
|
/* skip space between filenames */
|
|
for (;;) {
|
|
c = (char) MEMORY_dGetByte(bufadr);
|
|
if (Devices_IsValidForFilename(c))
|
|
break;
|
|
if (c == '\0' || (UBYTE) c > 0x80 || IS_DIR_SEP(c)) {
|
|
CPU_regY = 165; /* bad filename */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
bufadr++;
|
|
}
|
|
/* get new filename */
|
|
p = new_filename;
|
|
do {
|
|
if (p >= new_filename + sizeof(new_filename) - 1) {
|
|
CPU_regY = 165; /* bad filename */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
*p++ = c;
|
|
bufadr++;
|
|
c = (char) MEMORY_dGetByte(bufadr);
|
|
} while (Devices_IsValidForFilename(c));
|
|
*p = '\0';
|
|
|
|
#ifdef DO_DIR
|
|
if (!Devices_OpenDir(host_path)) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
while (Devices_ReadDir(host_path, NULL, NULL, &readonly, NULL, NULL))
|
|
#endif /* DO_DIR */
|
|
{
|
|
/* Check file write permission to mimic Atari
|
|
permission system: read-only ("locked") file
|
|
cannot be renamed. */
|
|
if (readonly)
|
|
num_locked++;
|
|
else {
|
|
char new_dirpart[FILENAME_MAX];
|
|
char new_filepart[FILENAME_MAX];
|
|
char new_path[FILENAME_MAX];
|
|
/* split old filepath into dir part and file part */
|
|
Util_splitpath(host_path, new_dirpart, new_filepart);
|
|
/* replace old file part with new file part */
|
|
fillin(new_filename, new_filepart);
|
|
/* combine new filepath */
|
|
Util_catpath(new_path, new_dirpart, new_filepart);
|
|
if (Devices_Rename(host_path, new_path))
|
|
num_changed++;
|
|
else
|
|
num_failed++;
|
|
}
|
|
}
|
|
|
|
if (devbug)
|
|
Log_print("%d renamed, %d failed, %d locked",
|
|
num_changed, num_failed, num_locked);
|
|
|
|
if (num_locked) {
|
|
CPU_regY = 167; /* file locked */
|
|
CPU_SetN;
|
|
}
|
|
else if (num_failed != 0 || num_changed == 0) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
}
|
|
else {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
}
|
|
|
|
#endif /* DO_RENAME */
|
|
|
|
#ifdef HAVE_UTIL_UNLINK
|
|
|
|
static void Devices_H_Delete(void)
|
|
{
|
|
int num_deleted = 0;
|
|
int num_failed = 0;
|
|
int num_locked = 0;
|
|
int readonly = FALSE;
|
|
|
|
if (devbug)
|
|
Log_print("DELETE Command");
|
|
CHECK_READ_ONLY;
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
#ifdef DO_DIR
|
|
if (!Devices_OpenDir(host_path)) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
while (Devices_ReadDir(host_path, NULL, NULL, &readonly, NULL, NULL))
|
|
#endif /* DO_DIR */
|
|
{
|
|
/* Check file write permission to mimic Atari
|
|
permission system: read-only ("locked") file
|
|
cannot be deleted. Modern systems have
|
|
a different permission for file deletion. */
|
|
if (readonly)
|
|
num_locked++;
|
|
else
|
|
if (Util_unlink(host_path) == 0)
|
|
num_deleted++;
|
|
else
|
|
num_failed++;
|
|
}
|
|
|
|
if (devbug)
|
|
Log_print("%d deleted, %d failed, %d locked",
|
|
num_deleted, num_failed, num_locked);
|
|
|
|
if (num_locked) {
|
|
CPU_regY = 167; /* file locked */
|
|
CPU_SetN;
|
|
}
|
|
else if (num_failed != 0 || num_deleted == 0) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
}
|
|
else {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_UTIL_UNLINK */
|
|
|
|
#ifdef DO_LOCK
|
|
|
|
static void Devices_H_LockUnlock(int readonly)
|
|
{
|
|
int num_changed = 0;
|
|
int num_failed = 0;
|
|
|
|
CHECK_READ_ONLY;
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
#ifdef DO_DIR
|
|
if (!Devices_OpenDir(host_path)) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
while (Devices_ReadDir(host_path, NULL, NULL, NULL, NULL, NULL))
|
|
#endif /* DO_DIR */
|
|
{
|
|
if (Devices_SetReadOnly(host_path, readonly))
|
|
num_changed++;
|
|
else
|
|
num_failed++;
|
|
}
|
|
|
|
if (devbug)
|
|
Log_print("%d changed, %d failed",
|
|
num_changed, num_failed);
|
|
|
|
if (num_failed != 0 || num_changed == 0) {
|
|
CPU_regY = 170; /* file not found */
|
|
CPU_SetN;
|
|
}
|
|
else {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Lock(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("LOCK Command");
|
|
Devices_H_LockUnlock(TRUE);
|
|
}
|
|
|
|
static void Devices_H_Unlock(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("UNLOCK Command");
|
|
Devices_H_LockUnlock(FALSE);
|
|
}
|
|
|
|
#endif /* DO_LOCK */
|
|
|
|
static void Devices_H_Note(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("NOTE Command");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
if (h_fp[h_iocb] != NULL) {
|
|
long pos = ftell(h_fp[h_iocb]);
|
|
if (pos >= 0) {
|
|
int iocb = Devices_IOCB0 + h_iocb * 16;
|
|
/* In Devices_H_Read one byte is read ahead. Take it into account. */
|
|
if (h_lastop[h_iocb] == 'r' && h_lastbyte[h_iocb] != EOF)
|
|
--pos;
|
|
MEMORY_dPutByte(iocb + Devices_ICAX5, (UBYTE) pos);
|
|
MEMORY_dPutByte(iocb + Devices_ICAX3, (UBYTE) (pos >> 8));
|
|
MEMORY_dPutByte(iocb + Devices_ICAX4, (UBYTE) (pos >> 16));
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
else {
|
|
CPU_regY = 130; /* specified device does not exist; XXX: correct? */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
|
|
static void Devices_H_Point(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("POINT Command");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
if (h_fp[h_iocb] != NULL) {
|
|
int iocb = Devices_IOCB0 + h_iocb * 16;
|
|
long pos = (MEMORY_dGetByte(iocb + Devices_ICAX4) << 16) +
|
|
(MEMORY_dGetByte(iocb + Devices_ICAX3) << 8) + (MEMORY_dGetByte(iocb + Devices_ICAX5));
|
|
if (fseek(h_fp[h_iocb], pos, SEEK_SET) == 0) {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 166; /* invalid POINT request */
|
|
CPU_SetN;
|
|
}
|
|
h_lastop[h_iocb] = 'p';
|
|
}
|
|
else {
|
|
CPU_regY = 130; /* specified device does not exist; XXX: correct? */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
|
|
static FILE *binfile = NULL;
|
|
static FILE **binf = &binfile;
|
|
static int runBinFile;
|
|
static int initBinFile;
|
|
|
|
/* Read a word from file */
|
|
static int Devices_H_BinReadWord(void)
|
|
{
|
|
UBYTE buf[2];
|
|
if (fread(buf, 1, 2, *binf) != 2) {
|
|
fclose(*binf);
|
|
*binf = NULL;
|
|
if (BINLOAD_start_binloading) {
|
|
BINLOAD_start_binloading = FALSE;
|
|
Log_print("binload: not valid BIN file");
|
|
CPU_regY = 180; /* MyDOS: not a binary file */
|
|
CPU_SetN;
|
|
return -1;
|
|
}
|
|
if (runBinFile)
|
|
CPU_regPC = MEMORY_dGetWordAligned(0x2e0);
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
return -1;
|
|
}
|
|
return buf[0] + (buf[1] << 8);
|
|
}
|
|
|
|
static void Devices_H_BinLoaderCont(void)
|
|
{
|
|
if (*binf == NULL)
|
|
return;
|
|
if (BINLOAD_start_binloading) {
|
|
MEMORY_dPutByte(0x244, 0);
|
|
MEMORY_dPutByte(0x09, 1);
|
|
}
|
|
else
|
|
CPU_regS += 2; /* pop ESC code */
|
|
|
|
MEMORY_dPutByte(0x2e3, 0xd7);
|
|
do {
|
|
int temp;
|
|
UWORD from;
|
|
UWORD to;
|
|
do
|
|
temp = Devices_H_BinReadWord();
|
|
while (temp == 0xffff);
|
|
if (temp < 0)
|
|
return;
|
|
from = (UWORD) temp;
|
|
|
|
temp = Devices_H_BinReadWord();
|
|
if (temp < 0)
|
|
return;
|
|
to = (UWORD) temp;
|
|
|
|
if (devbug)
|
|
Log_print("H: Load: From %04X to %04X", from, to);
|
|
|
|
if (BINLOAD_start_binloading) {
|
|
if (runBinFile)
|
|
MEMORY_dPutWordAligned(0x2e0, from);
|
|
BINLOAD_start_binloading = FALSE;
|
|
}
|
|
|
|
to++;
|
|
do {
|
|
int byte = fgetc(*binf);
|
|
if (byte == EOF) {
|
|
fclose(*binf);
|
|
*binf = NULL;
|
|
if (runBinFile)
|
|
CPU_regPC = MEMORY_dGetWordAligned(0x2e0);
|
|
if (initBinFile && (MEMORY_dGetByte(0x2e3) != 0xd7)) {
|
|
/* run INIT routine which RTSes directly to RUN routine */
|
|
CPU_regPC--;
|
|
MEMORY_dPutByte(0x0100 + CPU_regS--, CPU_regPC >> 8); /* high */
|
|
MEMORY_dPutByte(0x0100 + CPU_regS--, CPU_regPC & 0xff); /* low */
|
|
CPU_regPC = MEMORY_dGetWordAligned(0x2e2);
|
|
}
|
|
return;
|
|
}
|
|
MEMORY_PutByte(from, (UBYTE) byte);
|
|
from++;
|
|
} while (from != to);
|
|
} while (!initBinFile || MEMORY_dGetByte(0x2e3) == 0xd7);
|
|
|
|
CPU_regS--;
|
|
ESC_Add((UWORD) (0x100 + CPU_regS), ESC_BINLOADER_CONT, Devices_H_BinLoaderCont);
|
|
CPU_regS--;
|
|
MEMORY_dPutByte(0x0100 + CPU_regS--, 0x01); /* high */
|
|
MEMORY_dPutByte(0x0100 + CPU_regS, CPU_regS + 1); /* low */
|
|
CPU_regS--;
|
|
CPU_regPC = MEMORY_dGetWordAligned(0x2e2);
|
|
CPU_SetC;
|
|
|
|
MEMORY_dPutByte(0x0300, 0x31); /* for "Studio Dream" */
|
|
}
|
|
|
|
static void Devices_H_LoadProceed(int mydos)
|
|
{
|
|
/* Log_print("MyDOS %d, AX1 %d, AX2 %d", mydos, MEMORY_dGetByte(Devices_ICAX1Z), MEMORY_dGetByte(Devices_ICAX2Z)); */
|
|
if (mydos) {
|
|
switch (MEMORY_dGetByte(Devices_ICAX1Z) /* XXX: & 7 ? */) {
|
|
case 4:
|
|
runBinFile = TRUE;
|
|
initBinFile = TRUE;
|
|
break;
|
|
case 5:
|
|
runBinFile = TRUE;
|
|
initBinFile = FALSE;
|
|
break;
|
|
case 6:
|
|
runBinFile = FALSE;
|
|
initBinFile = TRUE;
|
|
break;
|
|
case 7:
|
|
default:
|
|
runBinFile = FALSE;
|
|
initBinFile = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (MEMORY_dGetByte(Devices_ICAX2Z) < 128)
|
|
runBinFile = TRUE;
|
|
else
|
|
runBinFile = FALSE;
|
|
initBinFile = TRUE;
|
|
}
|
|
|
|
BINLOAD_start_binloading = TRUE;
|
|
Devices_H_BinLoaderCont();
|
|
}
|
|
|
|
static void Devices_H_Load(int mydos)
|
|
{
|
|
const char *p;
|
|
UBYTE buf[2];
|
|
if (devbug)
|
|
Log_print("LOAD Command");
|
|
h_devnum = Devices_GetNumber(FALSE);
|
|
if (h_devnum < 0)
|
|
return;
|
|
|
|
/* search for program on Devices_h_exe_path */
|
|
for (p = Devices_h_exe_path; *p != '\0'; ) {
|
|
int devnum;
|
|
const char *q;
|
|
char *r;
|
|
if (p[0] == 'H' && p[1] >= '1' && p[1] <= '4' && p[2] == ':') {
|
|
devnum = p[1] - '1';
|
|
p += 3;
|
|
}
|
|
else
|
|
devnum = h_devnum;
|
|
for (q = p; *q != '\0' && *q != ';'; q++);
|
|
r = atari_filename + (q - p);
|
|
if (q != p) {
|
|
memcpy(atari_filename, p, q - p);
|
|
if (!IS_DIR_SEP(q[-1]))
|
|
*r++ = '>';
|
|
}
|
|
if (Devices_GetAtariPath(devnum, r) == 0)
|
|
return;
|
|
Util_catpath(host_path, Devices_atari_h_dir[devnum], atari_path);
|
|
*binf = fopen(host_path, "rb");
|
|
if (*binf != NULL || *q == '\0')
|
|
break;
|
|
p = q + 1;
|
|
}
|
|
|
|
if (*binf == NULL) {
|
|
/* open from the specified location */
|
|
if (Devices_GetAtariPath(h_devnum, atari_filename) == 0)
|
|
return;
|
|
Util_catpath(host_path, Devices_atari_h_dir[h_devnum], atari_path);
|
|
*binf = fopen(host_path, "rb");
|
|
if (*binf == NULL) {
|
|
CPU_regY = 170;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* check header */
|
|
if (fread(buf, 1, 2, *binf) != 2 || buf[0] != 0xff || buf[1] != 0xff) {
|
|
fclose(*binf);
|
|
*binf = NULL;
|
|
Log_print("H: load: not valid BIN file");
|
|
CPU_regY = 180;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
|
|
Devices_H_LoadProceed(mydos);
|
|
}
|
|
|
|
static void Devices_H_FileLength(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("Get File Length Command");
|
|
if (!Devices_GetIOCB())
|
|
return;
|
|
/* if IOCB is closed then assume it is a MyDOS Load File command */
|
|
if (h_fp[h_iocb] == NULL)
|
|
Devices_H_Load(TRUE);
|
|
/* if we are running MyDOS then assume it is a MyDOS Load File command */
|
|
else if (MEMORY_dGetByte(0x700) == 'M') {
|
|
/* XXX: if (*binf != NULL) fclose(*binf); ? */
|
|
|
|
/* In Devices_H_Read one byte is read ahead. Take it into account. */
|
|
if (h_lastop[h_iocb] == 'r' && h_lastbyte[h_iocb] != EOF)
|
|
fseek(h_fp[h_iocb], -1, SEEK_CUR);
|
|
|
|
binf = &h_fp[h_iocb];
|
|
Devices_H_LoadProceed(TRUE);
|
|
binf = &binfile;
|
|
h_lastop[h_iocb] = 'b';
|
|
}
|
|
/* otherwise assume it is a file length command */
|
|
else {
|
|
int iocb = Devices_IOCB0 + h_iocb * 16;
|
|
int filesize;
|
|
#if 0
|
|
/* old, less portable implementation */
|
|
struct stat fstatus;
|
|
fstat(fileno(h_fp[h_iocb]), &fstatus);
|
|
filesize = fstatus.st_size;
|
|
#else
|
|
FILE *fp = h_fp[h_iocb];
|
|
long currentpos = ftell(fp);
|
|
filesize = Util_flen(fp);
|
|
fseek(fp, currentpos, SEEK_SET);
|
|
#endif
|
|
MEMORY_dPutByte(iocb + Devices_ICAX3, (UBYTE) filesize);
|
|
MEMORY_dPutByte(iocb + Devices_ICAX4, (UBYTE) (filesize >> 8));
|
|
MEMORY_dPutByte(iocb + Devices_ICAX5, (UBYTE) (filesize >> 16));
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
}
|
|
|
|
#ifdef DO_MKDIR
|
|
static void Devices_H_MakeDirectory(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("MKDIR Command");
|
|
CHECK_READ_ONLY;
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
if (Devices_MakeDirectory(host_path)) {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef DO_RMDIR
|
|
static void Devices_H_RemoveDirectory(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("RMDIR Command");
|
|
CHECK_READ_ONLY;
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
CPU_regY = Devices_RemoveDirectory(host_path);
|
|
if (CPU_regY >= 128)
|
|
CPU_SetN;
|
|
else
|
|
CPU_ClrN;
|
|
}
|
|
#endif
|
|
|
|
static void Devices_H_ChangeDirectory(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("CD Command");
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
if (!Util_direxists(host_path)) {
|
|
CPU_regY = 150;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
|
|
if (atari_path[0] == '\0')
|
|
Devices_h_current_dir[h_devnum][0] = '\0';
|
|
else {
|
|
char *p = Util_stpcpy(Devices_h_current_dir[h_devnum], atari_path);
|
|
p[0] = Util_DIR_SEP_CHAR;
|
|
p[1] = '\0';
|
|
}
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_H_DiskInfo(void)
|
|
{
|
|
static UBYTE info[16] = {
|
|
0x20, /* disk version: Sparta >= 2.0 */
|
|
0x00, /* sector size: 0x100 */
|
|
0xff, 0xff, /* total sectors: 0xffff */
|
|
0xff, 0xff, /* free sectors: 0xffff */
|
|
'H', 'D', 'I', 'S', 'K', '1' /* + devnum */, ' ', ' ', /* disk name */
|
|
1, /* seq. number (number of writes) */
|
|
1 /* + devnum */ /* random number (disk id) */
|
|
};
|
|
int devnum;
|
|
|
|
if (devbug)
|
|
Log_print("Get Disk Information Command");
|
|
|
|
devnum = Devices_GetNumber(FALSE);
|
|
if (devnum < 0)
|
|
return;
|
|
|
|
info[11] = (UBYTE) ('1' + devnum);
|
|
info[15] = (UBYTE) (1 + devnum);
|
|
MEMORY_CopyToMem(info, (UWORD) MEMORY_dGetWordAligned(Devices_ICBLLZ), 16);
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_H_ToAbsolutePath(void)
|
|
{
|
|
UWORD bufadr;
|
|
const char *p;
|
|
|
|
if (devbug)
|
|
Log_print("To Absolute Path Command");
|
|
|
|
if (Devices_GetHostPath(FALSE) == 0)
|
|
return;
|
|
|
|
/* XXX: we sometimes check here for directories
|
|
with a trailing Util_DIR_SEP_CHAR. It seems to work on Win32 and DJGPP. */
|
|
if (!Util_direxists(host_path)) {
|
|
CPU_regY = 150;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
|
|
bufadr = MEMORY_dGetWordAligned(Devices_ICBLLZ);
|
|
if (atari_path[0] != '\0') {
|
|
MEMORY_PutByte(bufadr, '>');
|
|
bufadr++;
|
|
for (p = atari_path; *p != '\0'; p++) {
|
|
if (*p == Util_DIR_SEP_CHAR) {
|
|
if (p[1] == '\0')
|
|
break;
|
|
MEMORY_PutByte(bufadr, '>');
|
|
}
|
|
else
|
|
MEMORY_PutByte(bufadr, (UBYTE) *p);
|
|
bufadr++;
|
|
}
|
|
}
|
|
MEMORY_PutByte(bufadr, 0x00);
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_H_Special(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("HHSPEC");
|
|
|
|
switch (MEMORY_dGetByte(Devices_ICCOMZ)) {
|
|
#ifdef DO_RENAME
|
|
case 0x20:
|
|
Devices_H_Rename();
|
|
return;
|
|
#endif
|
|
#ifdef HAVE_UTIL_UNLINK
|
|
case 0x21:
|
|
Devices_H_Delete();
|
|
return;
|
|
#endif
|
|
#ifdef DO_LOCK
|
|
case 0x23:
|
|
Devices_H_Lock();
|
|
return;
|
|
case 0x24:
|
|
Devices_H_Unlock();
|
|
return;
|
|
#endif
|
|
case 0x26:
|
|
Devices_H_Note();
|
|
return;
|
|
case 0x25:
|
|
Devices_H_Point();
|
|
return;
|
|
case 0x27: /* Sparta, MyDOS=Load */
|
|
Devices_H_FileLength();
|
|
return;
|
|
case 0x28: /* Sparta */
|
|
Devices_H_Load(FALSE);
|
|
return;
|
|
#ifdef DO_MKDIR
|
|
case 0x22: /* MyDOS */
|
|
case 0x2a: /* MyDOS, Sparta */
|
|
Devices_H_MakeDirectory();
|
|
return;
|
|
#endif
|
|
#ifdef DO_RMDIR
|
|
case 0x2b: /* Sparta */
|
|
Devices_H_RemoveDirectory();
|
|
return;
|
|
#endif
|
|
case 0x29: /* MyDOS */
|
|
case 0x2c: /* Sparta */
|
|
Devices_H_ChangeDirectory();
|
|
return;
|
|
case 0x2f: /* Sparta */
|
|
Devices_H_DiskInfo();
|
|
return;
|
|
case 0x30: /* Sparta */
|
|
Devices_H_ToAbsolutePath();
|
|
return;
|
|
case 0xfe:
|
|
if (devbug)
|
|
Log_print("FORMAT Command");
|
|
break;
|
|
default:
|
|
if (devbug)
|
|
Log_print("UNKNOWN Command %02X", MEMORY_dGetByte(Devices_ICCOMZ));
|
|
break;
|
|
}
|
|
|
|
CPU_regY = 168; /* invalid device command */
|
|
CPU_SetN;
|
|
}
|
|
|
|
|
|
/* P: device emulation --------------------------------------------------- */
|
|
|
|
char Devices_print_command[256] = "lpr %s";
|
|
|
|
int Devices_SetPrintCommand(const char *command)
|
|
{
|
|
const char *p = command;
|
|
int was_percent_s = FALSE;
|
|
while (*p != '\0') {
|
|
if (*p++ == '%') {
|
|
char c = *p++;
|
|
if (c == '%')
|
|
continue; /* %% is safe */
|
|
if (c == 's' && !was_percent_s) {
|
|
was_percent_s = TRUE; /* only one %s is safe */
|
|
continue;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
strcpy(Devices_print_command, command);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_SYSTEM
|
|
|
|
static FILE *phf = NULL;
|
|
static char spool_file[FILENAME_MAX];
|
|
|
|
static void Devices_P_Close(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("PHCLOS");
|
|
|
|
if (phf != NULL) {
|
|
fclose(phf);
|
|
phf = NULL;
|
|
|
|
#ifdef __PLUS
|
|
if (!Misc_ExecutePrintCmd(spool_file))
|
|
#endif
|
|
{
|
|
char command[256 + FILENAME_MAX]; /* 256 for Devices_print_command + FILENAME_MAX for spool_file */
|
|
int retval;
|
|
sprintf(command, Devices_print_command, spool_file);
|
|
if ((retval = system(command)) == -1)
|
|
Log_print("Print command \"%s\' failed", command);
|
|
#if defined(HAVE_UTIL_UNLINK) && !defined(VMS) && !defined(MACOSX)
|
|
if (Util_unlink(spool_file) != 0) {
|
|
perror(spool_file);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_P_Open(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("PHOPEN");
|
|
|
|
if (phf != NULL)
|
|
Devices_P_Close();
|
|
|
|
phf = Util_uniqopen(spool_file, "w");
|
|
if (phf != NULL) {
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
else {
|
|
CPU_regY = 144; /* device done error */
|
|
CPU_SetN;
|
|
}
|
|
}
|
|
|
|
static void Devices_P_Write(void)
|
|
{
|
|
UBYTE byte;
|
|
|
|
if (devbug)
|
|
Log_print("PHWRIT");
|
|
|
|
byte = CPU_regA;
|
|
if (byte == 0x9b)
|
|
byte = '\n';
|
|
|
|
fputc(byte, phf);
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_P_Status(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("PHSTAT");
|
|
}
|
|
|
|
static void Devices_P_Init(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("PHINIT");
|
|
|
|
if (phf != NULL) {
|
|
fclose(phf);
|
|
phf = NULL;
|
|
#ifdef HAVE_UTIL_UNLINK
|
|
Util_unlink(spool_file);
|
|
#endif
|
|
}
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
#endif /* HAVE_SYSTEM */
|
|
|
|
|
|
/* K: and E: handlers for BASIC version, using getchar() and putchar() --- */
|
|
|
|
#ifdef BASIC
|
|
|
|
static void Devices_E_Read(void)
|
|
{
|
|
int ch;
|
|
|
|
ch = getchar();
|
|
switch (ch) {
|
|
case EOF:
|
|
Atari800_Exit(FALSE);
|
|
exit(0);
|
|
break;
|
|
case '\n':
|
|
ch = 0x9b;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
CPU_regA = (UBYTE) ch;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_E_Write(void)
|
|
{
|
|
UBYTE ch;
|
|
|
|
ch = CPU_regA;
|
|
/* XXX: are '\f', '\b' and '\a' fully portable? */
|
|
switch (ch) {
|
|
case 0x7d: /* Clear Screen */
|
|
putchar('\x0c'); /* ASCII Form Feed */
|
|
break;
|
|
case 0x7e:
|
|
putchar('\x08'); /* ASCII Backspace */
|
|
break;
|
|
case 0x7f:
|
|
putchar('\t');
|
|
break;
|
|
case 0x9b:
|
|
putchar('\n');
|
|
break;
|
|
case 0xfd:
|
|
putchar('\x07'); /* ASCII Bell */
|
|
break;
|
|
default:
|
|
if ((ch >= 0x20) && (ch <= 0x7e))
|
|
putchar(ch);
|
|
break;
|
|
}
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_K_Read(void)
|
|
{
|
|
int ch;
|
|
int ch2;
|
|
|
|
ch = getchar();
|
|
switch (ch) {
|
|
case EOF:
|
|
Atari800_Exit(FALSE);
|
|
exit(0);
|
|
break;
|
|
case '\n':
|
|
ch = 0x9b;
|
|
break;
|
|
default:
|
|
/* ignore characters until EOF or EOL */
|
|
do
|
|
ch2 = getchar();
|
|
while (ch2 != EOF && ch2 != '\n');
|
|
break;
|
|
}
|
|
CPU_regA = (UBYTE) ch;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
#endif /* BASIC */
|
|
|
|
/* B: device emulation --------------------------------------------------- */
|
|
|
|
/* The B: device is intended as a handler for interoperating with a web browser.
|
|
* The device is OPENed, a url is WRITE-n, then it is CLOSEd.
|
|
* Upon closing, the ready flag in the structure is set to TRUE.
|
|
* This can tested by the host port, reset to FALSE then if the string contained
|
|
* in the url member looks like a url, a browser to can be spawned to that url.
|
|
* The user can also be notified via a popup that an Atari program is requesting
|
|
* browser access.
|
|
*/
|
|
|
|
struct DEV_B dev_b_status;
|
|
|
|
static void Devices_B_Open(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("B: OPEN");
|
|
|
|
if (MEMORY_dGetByte(Devices_ICAX1Z) != 8) {
|
|
CPU_regY = 163; /* read-only device */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
|
|
memset(dev_b_status.url, 0, sizeof(dev_b_status.url));
|
|
dev_b_status.pos = 0;
|
|
dev_b_status.ready = FALSE;
|
|
|
|
CPU_regY = 1; /* open OK */
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_B_Close(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("B: CLOSE (%s)", dev_b_status.url);
|
|
|
|
if (dev_b_status.pos > 0)
|
|
dev_b_status.ready = TRUE;
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_B_Write(void)
|
|
{
|
|
UBYTE byte;
|
|
|
|
byte = CPU_regA;
|
|
|
|
if (devbug)
|
|
Log_print("B: WRITE ([%d] %02X, '%c')", dev_b_status.pos, byte, byte);
|
|
|
|
if (byte == 0x9b)
|
|
byte = '\0';
|
|
|
|
if (dev_b_status.pos >= sizeof(dev_b_status.url) - 1) {
|
|
CPU_regY = 135; /* attempted to write to a read-only device */
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
dev_b_status.url[dev_b_status.pos++] = byte;
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
static void Devices_B_Null(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("B: NULL");
|
|
}
|
|
|
|
static void Devices_B_Read(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("B: READ");
|
|
|
|
CPU_regY = 136; /* end of file */
|
|
CPU_SetN;
|
|
}
|
|
|
|
static void Devices_B_Init(void)
|
|
{
|
|
if (devbug)
|
|
Log_print("B: INIT");
|
|
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
|
|
/* Atari BASIC loader ---------------------------------------------------- */
|
|
|
|
static UWORD ehopen_addr = 0;
|
|
static UWORD ehclos_addr = 0;
|
|
static UWORD ehread_addr = 0;
|
|
static UWORD ehwrit_addr = 0;
|
|
|
|
static void Devices_IgnoreReady(void);
|
|
static void Devices_GetBasicCommand(void);
|
|
static void Devices_OpenBasicFile(void);
|
|
static void Devices_ReadBasicFile(void);
|
|
static void Devices_CloseBasicFile(void);
|
|
|
|
static void Devices_RestoreHandler(UWORD address, UBYTE esc_code)
|
|
{
|
|
ESC_Remove(esc_code);
|
|
/* restore original OS code */
|
|
MEMORY_dCopyToMem(MEMORY_os - (Atari800_machine_type == Atari800_MACHINE_800
|
|
? 0xd800
|
|
: 0xc000) + address,
|
|
address, 3);
|
|
}
|
|
|
|
static void Devices_RestoreEHOPEN(void)
|
|
{
|
|
Devices_RestoreHandler(ehopen_addr, ESC_EHOPEN);
|
|
}
|
|
|
|
static void Devices_RestoreEHCLOS(void)
|
|
{
|
|
Devices_RestoreHandler(ehclos_addr, ESC_EHCLOS);
|
|
}
|
|
|
|
#ifndef BASIC
|
|
|
|
static void Devices_RestoreEHREAD(void)
|
|
{
|
|
Devices_RestoreHandler(ehread_addr, ESC_EHREAD);
|
|
}
|
|
|
|
static void Devices_RestoreEHWRIT(void)
|
|
{
|
|
Devices_RestoreHandler(ehwrit_addr, ESC_EHWRIT);
|
|
}
|
|
|
|
static void Devices_InstallIgnoreReady(void)
|
|
{
|
|
ESC_AddEscRts(ehwrit_addr, ESC_EHWRIT, Devices_IgnoreReady);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Atari Basic loader step 1: ignore "READY" printed on E: after booting */
|
|
/* or step 6: ignore "READY" printed on E: after the "ENTER" command */
|
|
|
|
static const UBYTE * const ready_prompt = (const UBYTE *) "\x9bREADY\x9b";
|
|
|
|
static const UBYTE *ready_ptr = NULL;
|
|
|
|
static const UBYTE *basic_command_ptr = NULL;
|
|
|
|
static void Devices_IgnoreReady(void)
|
|
{
|
|
if (ready_ptr != NULL && CPU_regA == *ready_ptr) {
|
|
ready_ptr++;
|
|
if (*ready_ptr == '\0') {
|
|
ready_ptr = NULL;
|
|
/* uninstall patch */
|
|
#ifdef BASIC
|
|
ESC_AddEscRts(ehwrit_addr, ESC_EHWRIT, Devices_E_Write);
|
|
#else
|
|
CPU_rts_handler = Devices_RestoreEHWRIT;
|
|
#endif
|
|
if (BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_SAVED) {
|
|
basic_command_ptr = (const UBYTE *) "RUN \"E:\"\x9b";
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_GetBasicCommand);
|
|
}
|
|
else if (BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_LISTED) {
|
|
basic_command_ptr = (const UBYTE *) "ENTER \"E:\"\x9b";
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_GetBasicCommand);
|
|
}
|
|
else if (BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_RUN) {
|
|
basic_command_ptr = (const UBYTE *) "RUN\x9b";
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_GetBasicCommand);
|
|
}
|
|
}
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
return;
|
|
}
|
|
/* not "READY" (maybe "BOOT ERROR" or a DOS message) */
|
|
if (BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_RUN) {
|
|
/* don't "RUN" if no "READY" (probably "ERROR") */
|
|
BINLOAD_loading_basic = 0;
|
|
ready_ptr = NULL;
|
|
}
|
|
if (ready_ptr != NULL) {
|
|
/* If ready_ptr != ready_prompt then we skipped some characters
|
|
from ready_prompt, which weren't part of full ready_prompt.
|
|
Well, they probably weren't that important. :-) */
|
|
ready_ptr = ready_prompt;
|
|
}
|
|
/* call original handler */
|
|
#ifdef BASIC
|
|
Devices_E_Write();
|
|
#else
|
|
CPU_rts_handler = Devices_InstallIgnoreReady;
|
|
Devices_RestoreEHWRIT();
|
|
CPU_regPC = ehwrit_addr;
|
|
#endif
|
|
}
|
|
|
|
/* Atari Basic loader step 2: type command to load file from E: */
|
|
/* or step 7: type "RUN" for ENTERed program */
|
|
|
|
static void Devices_GetBasicCommand(void)
|
|
{
|
|
if (basic_command_ptr != NULL) {
|
|
CPU_regA = *basic_command_ptr++;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
if (*basic_command_ptr != '\0')
|
|
return;
|
|
if (BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_SAVED || BINLOAD_loading_basic == BINLOAD_LOADING_BASIC_LISTED)
|
|
ESC_AddEscRts(ehopen_addr, ESC_EHOPEN, Devices_OpenBasicFile);
|
|
basic_command_ptr = NULL;
|
|
}
|
|
#ifdef BASIC
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_E_Read);
|
|
#else
|
|
CPU_rts_handler = Devices_RestoreEHREAD;
|
|
#endif
|
|
}
|
|
|
|
/* Atari Basic loader step 3: open file */
|
|
|
|
static void Devices_OpenBasicFile(void)
|
|
{
|
|
if (BINLOAD_bin_file != NULL) {
|
|
fseek(BINLOAD_bin_file, 0, SEEK_SET);
|
|
ESC_AddEscRts(ehclos_addr, ESC_EHCLOS, Devices_CloseBasicFile);
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_ReadBasicFile);
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
CPU_rts_handler = Devices_RestoreEHOPEN;
|
|
}
|
|
|
|
/* Atari Basic loader step 4: read byte */
|
|
|
|
static void Devices_ReadBasicFile(void)
|
|
{
|
|
if (BINLOAD_bin_file != NULL) {
|
|
int ch = fgetc(BINLOAD_bin_file);
|
|
if (ch == EOF) {
|
|
CPU_regY = 136;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
switch (BINLOAD_loading_basic) {
|
|
case BINLOAD_LOADING_BASIC_LISTED:
|
|
switch (ch) {
|
|
case 0x9b:
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_LISTED_ATARI;
|
|
break;
|
|
case 0x0a:
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_LISTED_LF;
|
|
ch = 0x9b;
|
|
break;
|
|
case 0x0d:
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_LISTED_CR_OR_CRLF;
|
|
ch = 0x9b;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case BINLOAD_LOADING_BASIC_LISTED_CR:
|
|
if (ch == 0x0d)
|
|
ch = 0x9b;
|
|
break;
|
|
case BINLOAD_LOADING_BASIC_LISTED_LF:
|
|
if (ch == 0x0a)
|
|
ch = 0x9b;
|
|
break;
|
|
case BINLOAD_LOADING_BASIC_LISTED_CRLF:
|
|
if (ch == 0x0a) {
|
|
ch = fgetc(BINLOAD_bin_file);
|
|
if (ch == EOF) {
|
|
CPU_regY = 136;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
}
|
|
if (ch == 0x0d)
|
|
ch = 0x9b;
|
|
break;
|
|
case BINLOAD_LOADING_BASIC_LISTED_CR_OR_CRLF:
|
|
if (ch == 0x0a) {
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_LISTED_CRLF;
|
|
ch = fgetc(BINLOAD_bin_file);
|
|
if (ch == EOF) {
|
|
CPU_regY = 136;
|
|
CPU_SetN;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_LISTED_CR;
|
|
if (ch == 0x0d)
|
|
ch = 0x9b;
|
|
break;
|
|
case BINLOAD_LOADING_BASIC_SAVED:
|
|
case BINLOAD_LOADING_BASIC_LISTED_ATARI:
|
|
default:
|
|
break;
|
|
}
|
|
CPU_regA = (UBYTE) ch;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
}
|
|
|
|
/* Atari Basic loader step 5: close file */
|
|
|
|
static void Devices_CloseBasicFile(void)
|
|
{
|
|
if (BINLOAD_bin_file != NULL) {
|
|
fclose(BINLOAD_bin_file);
|
|
BINLOAD_bin_file = NULL;
|
|
/* "RUN" ENTERed program */
|
|
if (BINLOAD_loading_basic != 0 && BINLOAD_loading_basic != BINLOAD_LOADING_BASIC_SAVED) {
|
|
ready_ptr = ready_prompt;
|
|
ESC_AddEscRts(ehwrit_addr, ESC_EHWRIT, Devices_IgnoreReady);
|
|
BINLOAD_loading_basic = BINLOAD_LOADING_BASIC_RUN;
|
|
}
|
|
else
|
|
BINLOAD_loading_basic = 0;
|
|
}
|
|
#ifdef BASIC
|
|
ESC_AddEscRts(ehread_addr, ESC_EHREAD, Devices_E_Read);
|
|
#else
|
|
Devices_RestoreEHREAD();
|
|
#endif
|
|
CPU_rts_handler = Devices_RestoreEHCLOS;
|
|
CPU_regY = 1;
|
|
CPU_ClrN;
|
|
}
|
|
|
|
|
|
/* Patches management ---------------------------------------------------- */
|
|
|
|
int Devices_enable_h_patch = TRUE;
|
|
int Devices_enable_p_patch = TRUE;
|
|
int Devices_enable_r_patch = FALSE;
|
|
int Devices_enable_b_patch = FALSE;
|
|
|
|
/* Devices_PatchOS is called by ESC_PatchOS to modify standard device
|
|
handlers in Atari OS. It puts escape codes at beginnings of OS routines,
|
|
so the patches work even if they are called directly, without CIO.
|
|
Returns TRUE if something has been patched.
|
|
Currently we only patch P: and, in BASIC version, E: and K:.
|
|
We don't replace C: with H: now, so the cassette works even
|
|
if H: is enabled.
|
|
*/
|
|
int Devices_PatchOS(void)
|
|
{
|
|
UWORD addr;
|
|
int i;
|
|
int patched = FALSE;
|
|
|
|
switch (Atari800_os_version) {
|
|
case SYSROM_A_NTSC:
|
|
case SYSROM_A_PAL:
|
|
case SYSROM_B_NTSC:
|
|
case SYSROM_800_CUSTOM:
|
|
addr = 0xf0e3;
|
|
break;
|
|
case SYSROM_AA00R10:
|
|
addr = 0xc4fa;
|
|
break;
|
|
case SYSROM_AA01R11:
|
|
addr = 0xc479;
|
|
break;
|
|
case SYSROM_BB00R1:
|
|
addr = 0xc43c;
|
|
break;
|
|
case SYSROM_BB01R2:
|
|
case SYSROM_BB01R3:
|
|
case SYSROM_BB01R4_OS:
|
|
case SYSROM_BB01R59:
|
|
case SYSROM_BB01R59A:
|
|
case SYSROM_XL_CUSTOM:
|
|
addr = 0xc42e;
|
|
break;
|
|
case SYSROM_BB02R3:
|
|
addr = 0xc42c;
|
|
break;
|
|
case SYSROM_BB02R3V4:
|
|
addr = 0xc43b;
|
|
break;
|
|
case SYSROM_CC01R4:
|
|
addr = 0xc3eb;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
UWORD devtab = MEMORY_dGetWord(addr + 1);
|
|
switch (MEMORY_dGetByte(addr)) {
|
|
#ifdef HAVE_SYSTEM
|
|
case 'P':
|
|
if (Devices_enable_p_patch) {
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_OPEN) + 1),
|
|
ESC_PHOPEN, Devices_P_Open);
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_CLOS) + 1),
|
|
ESC_PHCLOS, Devices_P_Close);
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_WRIT) + 1),
|
|
ESC_PHWRIT, Devices_P_Write);
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_STAT) + 1),
|
|
ESC_PHSTAT, Devices_P_Status);
|
|
ESC_AddEscRts2((UWORD) (devtab + Devices_TABLE_INIT), ESC_PHINIT,
|
|
Devices_P_Init);
|
|
patched = TRUE;
|
|
}
|
|
else {
|
|
ESC_Remove(ESC_PHOPEN);
|
|
ESC_Remove(ESC_PHCLOS);
|
|
ESC_Remove(ESC_PHWRIT);
|
|
ESC_Remove(ESC_PHSTAT);
|
|
ESC_Remove(ESC_PHINIT);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case 'E':
|
|
if (BINLOAD_loading_basic) {
|
|
ehopen_addr = MEMORY_dGetWord(devtab + Devices_TABLE_OPEN) + 1;
|
|
ehclos_addr = MEMORY_dGetWord(devtab + Devices_TABLE_CLOS) + 1;
|
|
ehread_addr = MEMORY_dGetWord(devtab + Devices_TABLE_READ) + 1;
|
|
ehwrit_addr = MEMORY_dGetWord(devtab + Devices_TABLE_WRIT) + 1;
|
|
ready_ptr = ready_prompt;
|
|
ESC_AddEscRts(ehwrit_addr, ESC_EHWRIT, Devices_IgnoreReady);
|
|
patched = TRUE;
|
|
}
|
|
#ifdef BASIC
|
|
else
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_WRIT) + 1),
|
|
ESC_EHWRIT, Devices_E_Write);
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_READ) + 1),
|
|
ESC_EHREAD, Devices_E_Read);
|
|
patched = TRUE;
|
|
break;
|
|
case 'K':
|
|
ESC_AddEscRts((UWORD) (MEMORY_dGetWord(devtab + Devices_TABLE_READ) + 1),
|
|
ESC_KHREAD, Devices_K_Read);
|
|
patched = TRUE;
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
addr += 3; /* Next Device in HATABS */
|
|
}
|
|
return patched;
|
|
}
|
|
|
|
/* New handling of H: device.
|
|
Previously we simply replaced C: device in OS with our H:.
|
|
Now we don't change ROM for H: patch, but add H: to HATABS in RAM
|
|
and put the device table and patches in unused address space
|
|
(0xd100-0xd1ff), which is meant for 'new devices' (like hard disk).
|
|
We have to continuously check if our H: is still in HATABS,
|
|
because RESET routine in Atari OS clears HATABS and initializes it
|
|
using a table in ROM (see Devices_PatchOS).
|
|
Before we put H: entry in HATABS, we must make sure that HATABS is there.
|
|
For example a program that doesn't use Atari OS can use this memory area
|
|
for its own data, and we shouldn't place 'H' there.
|
|
We also allow an Atari program to change address of H: device table.
|
|
So after we put H: entry in HATABS, we only check if 'H' is still where
|
|
we put it (h_entry_address).
|
|
Devices_UpdateHATABSEntry and Devices_RemoveHATABSEntry can be used to add
|
|
other devices than H:. */
|
|
|
|
#define HATABS 0x31a
|
|
|
|
UWORD Devices_UpdateHATABSEntry(char device, UWORD entry_address,
|
|
UWORD table_address)
|
|
{
|
|
UWORD address;
|
|
if (entry_address != 0 && MEMORY_dGetByte(entry_address) == device)
|
|
return entry_address;
|
|
if (MEMORY_dGetByte(HATABS) != 'P' || MEMORY_dGetByte(HATABS + 3) != 'C'
|
|
|| MEMORY_dGetByte(HATABS + 6) != 'E' || MEMORY_dGetByte(HATABS + 9) != 'S'
|
|
|| MEMORY_dGetByte(HATABS + 12) != 'K')
|
|
return entry_address;
|
|
for (address = HATABS + 15; address < HATABS + 33; address += 3) {
|
|
if (MEMORY_dGetByte(address) == device)
|
|
return address;
|
|
if (MEMORY_dGetByte(address) == 0) {
|
|
MEMORY_dPutByte(address, device);
|
|
MEMORY_dPutWord(address + 1, table_address);
|
|
return address;
|
|
}
|
|
}
|
|
/* HATABS full */
|
|
return entry_address;
|
|
}
|
|
|
|
void Devices_RemoveHATABSEntry(char device, UWORD entry_address,
|
|
UWORD table_address)
|
|
{
|
|
if (entry_address != 0 && MEMORY_dGetByte(entry_address) == device
|
|
&& MEMORY_dGetWord(entry_address + 1) == table_address) {
|
|
MEMORY_dPutByte(entry_address, 0);
|
|
MEMORY_dPutWord(entry_address + 1, 0);
|
|
}
|
|
}
|
|
|
|
static UWORD h_entry_address = 0;
|
|
#ifdef R_IO_DEVICE
|
|
static UWORD r_entry_address = 0;
|
|
#endif
|
|
static UWORD b_entry_address = 0;
|
|
|
|
#define H_DEVICE_BEGIN 0xd140
|
|
#define H_TABLE_ADDRESS 0xd140
|
|
#define H_PATCH_OPEN 0xd150
|
|
#define H_PATCH_CLOS 0xd153
|
|
#define H_PATCH_READ 0xd156
|
|
#define H_PATCH_WRIT 0xd159
|
|
#define H_PATCH_STAT 0xd15c
|
|
#define H_PATCH_SPEC 0xd15f
|
|
#define H_DEVICE_END 0xd161
|
|
|
|
#ifdef R_IO_DEVICE
|
|
#define R_DEVICE_BEGIN 0xd180
|
|
#define R_TABLE_ADDRESS 0xd180
|
|
#define R_PATCH_OPEN 0xd1a0
|
|
#define R_PATCH_CLOS 0xd1a3
|
|
#define R_PATCH_READ 0xd1a6
|
|
#define R_PATCH_WRIT 0xd1a9
|
|
#define R_PATCH_STAT 0xd1ac
|
|
#define R_PATCH_SPEC 0xd1af
|
|
#define R_PATCH_INIT 0xd1b3
|
|
#define R_DEVICE_END 0xd1b5
|
|
#endif
|
|
|
|
#define B_DEVICE_BEGIN 0xd1c0
|
|
#define B_TABLE_ADDRESS 0xd1c0
|
|
#define B_PATCH_OPEN 0xd1d0
|
|
#define B_PATCH_CLOS 0xd1d3
|
|
#define B_PATCH_READ 0xd1d6
|
|
#define B_PATCH_WRIT 0xd1d9
|
|
#define B_PATCH_STAT 0xd1dc
|
|
#define B_PATCH_SPEC 0xd1df
|
|
#define B_PATCH_INIT 0xd1e3
|
|
#define B_DEVICE_END 0xd1e5
|
|
|
|
void Devices_Frame(void)
|
|
{
|
|
if (Devices_enable_h_patch)
|
|
h_entry_address = Devices_UpdateHATABSEntry('H', h_entry_address, H_TABLE_ADDRESS);
|
|
|
|
#ifdef R_IO_DEVICE
|
|
if (Devices_enable_r_patch)
|
|
r_entry_address = Devices_UpdateHATABSEntry('R', r_entry_address, R_TABLE_ADDRESS);
|
|
#endif
|
|
|
|
if (Devices_enable_b_patch)
|
|
b_entry_address = Devices_UpdateHATABSEntry('B', b_entry_address, B_TABLE_ADDRESS);
|
|
}
|
|
|
|
/* this is called when Devices_enable_h_patch is toggled */
|
|
void Devices_UpdatePatches(void)
|
|
{
|
|
if (Devices_enable_h_patch) { /* enable H: device */
|
|
/* change memory attributes for the area, where we put
|
|
the H: handler table and patches */
|
|
MEMORY_SetROM(H_DEVICE_BEGIN, H_DEVICE_END);
|
|
/* set handler table */
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_OPEN, H_PATCH_OPEN - 1);
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_CLOS, H_PATCH_CLOS - 1);
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_READ, H_PATCH_READ - 1);
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_WRIT, H_PATCH_WRIT - 1);
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_STAT, H_PATCH_STAT - 1);
|
|
MEMORY_dPutWord(H_TABLE_ADDRESS + Devices_TABLE_SPEC, H_PATCH_SPEC - 1);
|
|
/* set patches */
|
|
ESC_AddEscRts(H_PATCH_OPEN, ESC_HHOPEN, Devices_H_Open);
|
|
ESC_AddEscRts(H_PATCH_CLOS, ESC_HHCLOS, Devices_H_Close);
|
|
ESC_AddEscRts(H_PATCH_READ, ESC_HHREAD, Devices_H_Read);
|
|
ESC_AddEscRts(H_PATCH_WRIT, ESC_HHWRIT, Devices_H_Write);
|
|
ESC_AddEscRts(H_PATCH_STAT, ESC_HHSTAT, Devices_H_Status);
|
|
ESC_AddEscRts(H_PATCH_SPEC, ESC_HHSPEC, Devices_H_Special);
|
|
/* H: in HATABS will be added next frame by Devices_Frame */
|
|
}
|
|
else { /* disable H: device */
|
|
/* remove H: entry from HATABS */
|
|
Devices_RemoveHATABSEntry('H', h_entry_address, H_TABLE_ADDRESS);
|
|
/* remove patches */
|
|
ESC_Remove(ESC_HHOPEN);
|
|
ESC_Remove(ESC_HHCLOS);
|
|
ESC_Remove(ESC_HHREAD);
|
|
ESC_Remove(ESC_HHWRIT);
|
|
ESC_Remove(ESC_HHSTAT);
|
|
ESC_Remove(ESC_HHSPEC);
|
|
/* fill memory area used for table and patches with 0xff */
|
|
MEMORY_dFillMem(H_DEVICE_BEGIN, 0xff, H_DEVICE_END - H_DEVICE_BEGIN + 1);
|
|
}
|
|
|
|
#ifdef R_IO_DEVICE
|
|
if (Devices_enable_r_patch) { /* enable R: device */
|
|
/* change memory attributes for the area, where we put
|
|
the R: handler table and patches */
|
|
MEMORY_SetROM(R_DEVICE_BEGIN, R_DEVICE_END);
|
|
/* set handler table */
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_OPEN, R_PATCH_OPEN - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_CLOS, R_PATCH_CLOS - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_READ, R_PATCH_READ - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_WRIT, R_PATCH_WRIT - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_STAT, R_PATCH_STAT - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_SPEC, R_PATCH_SPEC - 1);
|
|
MEMORY_dPutWord(R_TABLE_ADDRESS + Devices_TABLE_INIT, R_PATCH_INIT - 1);
|
|
/* set patches */
|
|
ESC_AddEscRts(R_PATCH_OPEN, ESC_ROPEN, RDevice_OPEN);
|
|
ESC_AddEscRts(R_PATCH_CLOS, ESC_RCLOS, RDevice_CLOS);
|
|
ESC_AddEscRts(R_PATCH_READ, ESC_RREAD, RDevice_READ);
|
|
ESC_AddEscRts(R_PATCH_WRIT, ESC_RWRIT, RDevice_WRIT);
|
|
ESC_AddEscRts(R_PATCH_STAT, ESC_RSTAT, RDevice_STAT);
|
|
ESC_AddEscRts(R_PATCH_SPEC, ESC_RSPEC, RDevice_SPEC);
|
|
ESC_AddEscRts(R_PATCH_INIT, ESC_RINIT, RDevice_INIT);
|
|
/* R: in HATABS will be added next frame by Devices_Frame */
|
|
}
|
|
else { /* disable R: device */
|
|
/* remove R: entry from HATABS */
|
|
Devices_RemoveHATABSEntry('R', r_entry_address, R_TABLE_ADDRESS);
|
|
/* remove patches */
|
|
ESC_Remove(ESC_ROPEN);
|
|
ESC_Remove(ESC_RCLOS);
|
|
ESC_Remove(ESC_RREAD);
|
|
ESC_Remove(ESC_RWRIT);
|
|
ESC_Remove(ESC_RSTAT);
|
|
ESC_Remove(ESC_RSPEC);
|
|
/* fill memory area used for table and patches with 0xff */
|
|
MEMORY_dFillMem(R_DEVICE_BEGIN, 0xff, R_DEVICE_END - R_DEVICE_BEGIN + 1);
|
|
}
|
|
#endif /* defined(R_IO_DEVICE) */
|
|
|
|
if (Devices_enable_b_patch) {
|
|
/* add B: device to HATABS */
|
|
MEMORY_SetROM(B_DEVICE_BEGIN, B_DEVICE_END);
|
|
/* set handler table */
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_OPEN, B_PATCH_OPEN - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_CLOS, B_PATCH_CLOS - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_READ, B_PATCH_READ - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_WRIT, B_PATCH_WRIT - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_STAT, B_PATCH_STAT - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_SPEC, B_PATCH_SPEC - 1);
|
|
MEMORY_dPutWord(B_TABLE_ADDRESS + Devices_TABLE_INIT, B_PATCH_INIT - 1);
|
|
/* set patches */
|
|
ESC_AddEscRts(B_PATCH_OPEN, ESC_BOPEN, Devices_B_Open);
|
|
ESC_AddEscRts(B_PATCH_CLOS, ESC_BCLOS, Devices_B_Close);
|
|
ESC_AddEscRts(B_PATCH_READ, ESC_BREAD, Devices_B_Read);
|
|
ESC_AddEscRts(B_PATCH_WRIT, ESC_BWRIT, Devices_B_Write);
|
|
ESC_AddEscRts(B_PATCH_STAT, ESC_BSTAT, Devices_B_Null);
|
|
ESC_AddEscRts(B_PATCH_SPEC, ESC_BSPEC, Devices_B_Null);
|
|
ESC_AddEscRts(B_PATCH_INIT, ESC_BINIT, Devices_B_Init);
|
|
}
|
|
else {
|
|
/* remove B: entry from HATABS */
|
|
Devices_RemoveHATABSEntry('B', b_entry_address, B_TABLE_ADDRESS);
|
|
/* remove patches */
|
|
ESC_Remove(ESC_BOPEN);
|
|
ESC_Remove(ESC_BCLOS);
|
|
ESC_Remove(ESC_BREAD);
|
|
ESC_Remove(ESC_BWRIT);
|
|
ESC_Remove(ESC_BSTAT);
|
|
ESC_Remove(ESC_BSPEC);
|
|
/* fill memory area used for table and patches with 0xff */
|
|
MEMORY_dFillMem(B_DEVICE_BEGIN, 0xff, B_DEVICE_END - B_DEVICE_BEGIN + 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
vim:ts=4:sw=4:
|
|
*/
|