"mamelink", a replacement for Fastlink using a MAME plugin

This commit is contained in:
Jeremy Penner 2024-01-01 20:45:35 -05:00
parent 8620f53358
commit cf4e289274
10 changed files with 350 additions and 0 deletions

3
.gitignore vendored
View file

@ -0,0 +1,3 @@
*.o
down
console_history

3
mamelink/Makefile Normal file
View file

@ -0,0 +1,3 @@
all: mamelink.o down
down: mamelink.o down.o

92
mamelink/down.c Normal file
View file

@ -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 <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#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;
}

128
mamelink/mamelink.c Normal file
View file

@ -0,0 +1,128 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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);
}

6
mamelink/mamelink.h Normal file
View file

@ -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);

View file

@ -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

View file

@ -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"
}
}

BIN
mamelink/reno.out Normal file

Binary file not shown.

4
mamelink/reno.sh Executable file
View file

@ -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

6
mamelink/start Executable file
View file

@ -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