diff --git a/.gitignore b/.gitignore index e69de29..6c5efe8 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +down +console_history diff --git a/mamelink/Makefile b/mamelink/Makefile new file mode 100644 index 0000000..830b941 --- /dev/null +++ b/mamelink/Makefile @@ -0,0 +1,3 @@ +all: mamelink.o down + +down: mamelink.o down.o diff --git a/mamelink/down.c b/mamelink/down.c new file mode 100644 index 0000000..416bc41 --- /dev/null +++ b/mamelink/down.c @@ -0,0 +1,92 @@ +// A recreation of the "down" tool used by fred. Expects an object file +// following the "slinky" object format, piped to stdin. +// https://github.com/ssalevan/habiclient/blob/master/Tools/slinky/link.c + +#include +#include +#include +#include +#include "mamelink.h" + +typedef unsigned char byte; +typedef unsigned short word; + +byte buf[64 * 1024]; + +size_t tryreadbytes(size_t count) { + size_t readcount = 0; + while (readcount < count) { + ssize_t result = read(STDIN_FILENO, buf + readcount, count); + readcount += result; + assert(result >= 0); + if (result == 0) { + break; + } + } + return readcount; +} + +void readbytes(size_t count) { + assert(tryreadbytes(count)); +} + +word readword() { + readbytes(2); + return (unsigned short)buf[0] | ((unsigned short)buf[1] << 8); +} + +bool tryreadword(word *val) { + if (tryreadbytes(2) != 2) { + return false; + } else { + *val = (unsigned short)buf[0] | ((unsigned short)buf[1] << 8); + return true; + } +} + +word entryPoint = 0; + +void sendAbsoluteSegments() { + while (true) { + word start; + if (!tryreadword(&start) || start == 0xffff) { + break; + } + word end = readword(); + assert(end >= start); + + word count = end - start + 1; + readbytes(count); + printf("Segment: %4x-%4x\n", start, end); + down(buf, count, start); + if (start == end) { + entryPoint = start; + } + } +} + +void sendRelocatableSegments() { + // hope Slinky has already pre-relocated all segments?? + word start; + assert(!tryreadword(&start) || start == 0xffff); +} + +int main(int argc, char *argv) { + + Init(NULL); + + // object starts with "magic" divider + assert(readword() == 0xffff); + + sendAbsoluteSegments(); + sendRelocatableSegments(); + + if (entryPoint != 0) { + printf("SYS%d\n", entryPoint); + JumpTo(entryPoint); + } + + Finish(); + + return 0; +} \ No newline at end of file diff --git a/mamelink/mamelink.c b/mamelink/mamelink.c new file mode 100644 index 0000000..609ab2b --- /dev/null +++ b/mamelink/mamelink.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include + +static char* linkdir = NULL; + +#define MAMELINK_CONTINUE 0 +#define MAMELINK_PAUSE 1 +#define MAMELINK_LOAD 2 +#define MAMELINK_STORE 3 +#define MAMELINK_JUMP 4 + +static int prepare_cmd(char command) { + assert(linkdir != NULL); + + const char *pendingfilename = "/linkin.pending"; + char path[strlen(linkdir) + strlen(pendingfilename) + 1]; + int fd = -1; + + sprintf(path, "%s%s", linkdir, pendingfilename); + while (fd < 0) { + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (fd < 0) { + assert(errno == EEXIST); + usleep(100); + } + } + + write(fd, &command, 1); + return fd; +} + +static int send_cmd(int cmd_fd) { + assert(linkdir != NULL); + + const char *pendingfilename = "/linkin.pending"; + const char *infilename = "/linkin"; + const char *outfilename = "/linkout"; + char path[strlen(linkdir) + strlen(pendingfilename) + 1]; + char finalpath[strlen(linkdir) + strlen(infilename) + 1]; + + sprintf(path, "%s%s", linkdir, pendingfilename); + sprintf(finalpath, "%s%s", linkdir, infilename); + + rename(path, finalpath); + + // the plugin will delete linkin once it has completed processing + while (access(finalpath, F_OK) == 0) { + usleep(100); + } + sprintf(path, "%s%s", linkdir, outfilename); + int fd = open(path, O_RDONLY); + assert(fd >= 0); + return fd; +} + +static void close_response(int response_fd) { + assert(linkdir != NULL); + + const char *outfilename = "/linkout"; + char path[strlen(linkdir) + strlen(outfilename) + 1]; + + sprintf(path, "%s%s", linkdir, outfilename); + unlink(path); + close(response_fd); +} + +static void send_cmd_and_close(int cmd_fd) { + close_response(send_cmd(cmd_fd)); +} + +static void simple_cmd(char command) { + send_cmd_and_close(prepare_cmd(command)); +} + +void Init(char *initial_link_dir) { + if (initial_link_dir == NULL) { + initial_link_dir = getenv("MAMELINK"); + } + assert(initial_link_dir != NULL); + if (linkdir != NULL) { + free(linkdir); + } + linkdir = strdup(initial_link_dir); +} + +void Finish() { + if (linkdir != NULL) { + free(linkdir); + linkdir = NULL; + } +} + +static int write_word(int fd, unsigned short val) { + const char buf[2] = { val & 0xff, (val >> 8) & 0xff }; + return write(fd, buf, 2); +} + +void down(char *buf, unsigned short bytes, unsigned short c64Addr) { + int cmd_fd = prepare_cmd(MAMELINK_STORE); + write_word(cmd_fd, c64Addr); + write_word(cmd_fd, bytes); + write(cmd_fd, buf, bytes); + send_cmd_and_close(cmd_fd); +} + +void up(char *buf, unsigned short bytes, unsigned short c64Addr) { + int fd = prepare_cmd(MAMELINK_STORE); + write_word(fd, c64Addr); + write_word(fd, bytes); + fd = send_cmd(fd); + read(fd, buf, bytes); + close_response(fd); +} + +void Cont() { + simple_cmd(MAMELINK_CONTINUE); +} + +void JumpTo(unsigned short c64Addr) { + int fd = prepare_cmd(MAMELINK_JUMP); + write_word(fd, c64Addr); + send_cmd_and_close(fd); +} diff --git a/mamelink/mamelink.h b/mamelink/mamelink.h new file mode 100644 index 0000000..7efe87a --- /dev/null +++ b/mamelink/mamelink.h @@ -0,0 +1,6 @@ +void Init(char *initial_link_dir); +void Finish(); +void down(char *buf, unsigned short bytes, unsigned short c64Addr); +void up(char *buf, unsigned short bytes, unsigned short c64Addr); +void Cont(); +void JumpTo(unsigned short c64Addr); \ No newline at end of file diff --git a/mamelink/mameplugins/mamelink/init.lua b/mamelink/mameplugins/mamelink/init.lua new file mode 100644 index 0000000..7ab5b01 --- /dev/null +++ b/mamelink/mameplugins/mamelink/init.lua @@ -0,0 +1,98 @@ +-- license:MIT +-- copyright-holders: Jeremy Penner +local exports = { + name = "mamelink", + version = "0.0.1", + description = "Socket-based recreation of Lucasfilm's Fastlink interface", + license = "MIT", + author = { name = "Jeremy Penner" } +} + +local function readword(bytein) + local lo = bytein() + local hi = bytein() + return lo | (hi << 8) +end + +local function fastlink(bytein, byteout) + print("starting fastlink") + while true do + local command = bytein() + local cpu = manager.machine.devices[":u7"] + local mem = cpu.spaces.program + print("Got command", command) + if command == 0 then -- continue + manager.machine.debugger:command("go") + elseif command == 1 then -- pause + manager.machine.debugger:command("step") + elseif command == 2 then -- load bytes + local address = readword(bytein) + local length = readword(bytein) + for i = 1, length do + byteout(mem:read_u8(address + i - 1)) + end + elseif command == 3 then -- store bytes + local address = readword(bytein) + local length = readword(bytein) + for i = 1, length do + mem:write_u8(address + i - 1, bytein()) + end + elseif command == 4 then -- jump + local address = readword(bytein) + cpu.state["PC"].value = address + else + print("Unknown command: " .. tostring(command)) + end + end +end + +local function is_booted() + local cpu = manager.machine.devices[":u7"] + local mem = cpu.spaces.program + return mem +end + +function exports.startplugin() + local infilename = manager.plugins["mamelink"].directory .. "/linkin" + local outfilename = manager.plugins["mamelink"].directory .. "/linkout" + local pendingfilename = outfilename .. ".pending" + + local link = coroutine.create(fastlink) + + local linkio = {} + local function bytein() + while true do + local data = nil + if linkio.infile then + data = linkio.infile:read(1) + end + if data then + return string.byte(data) + else + coroutine.yield() + end + end + end + local function byteout(byte) + io.outfile:write(string.char(byte)) + end + + print(coroutine.resume(link, bytein, byteout)) + + emu.register_periodic(function() + if not is_booted() then return end + linkio.infile = io.open(infilename, "rb") + if linkio.infile then + linkio.outfile = io.open(pendingfilename, "wb") + print(coroutine.resume(link)) + linkio.infile:close() + linkio.infile = nil + linkio.outfile:close() + linkio.outfile = nil + os.rename(pendingfilename, outfilename) + os.remove(infilename) + end + end) +end + +return exports \ No newline at end of file diff --git a/mamelink/mameplugins/mamelink/plugin.json b/mamelink/mameplugins/mamelink/plugin.json new file mode 100644 index 0000000..31ffb3d --- /dev/null +++ b/mamelink/mameplugins/mamelink/plugin.json @@ -0,0 +1,10 @@ +{ + "plugin": { + "name": "mamelink", + "description": "Socket-based recreation of Lucasfilm's Fastlink interface", + "version": "0.0.1", + "author": "Jeremy Penner", + "type": "plugin", + "start": "false" + } +} \ No newline at end of file diff --git a/mamelink/reno.out b/mamelink/reno.out new file mode 100644 index 0000000..42e07c4 Binary files /dev/null and b/mamelink/reno.out differ diff --git a/mamelink/reno.sh b/mamelink/reno.sh new file mode 100755 index 0000000..af25f28 --- /dev/null +++ b/mamelink/reno.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +SCRIPTPATH="$( cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P )" +export MAMELINK="${SCRIPTPATH}/mameplugins/mamelink" +./down < reno.out diff --git a/mamelink/start b/mamelink/start new file mode 100755 index 0000000..3a189b9 --- /dev/null +++ b/mamelink/start @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +SCRIPTPATH="$( cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P )" +pluginspath="$(mame -showconfig | grep pluginspath | sed 's/^pluginspath \+//g')" +mame c64 -window -plugins -pluginspath "${pluginspath};${SCRIPTPATH}/mameplugins" -plugin mamelink & +export MAMELINK="${SCRIPTPATH}/mameplugins/mamelink" +./down < reno.out