"mamelink", a replacement for Fastlink using a MAME plugin
This commit is contained in:
parent
8620f53358
commit
cf4e289274
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -0,0 +1,3 @@
|
||||||
|
*.o
|
||||||
|
down
|
||||||
|
console_history
|
3
mamelink/Makefile
Normal file
3
mamelink/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
all: mamelink.o down
|
||||||
|
|
||||||
|
down: mamelink.o down.o
|
92
mamelink/down.c
Normal file
92
mamelink/down.c
Normal 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
128
mamelink/mamelink.c
Normal 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
6
mamelink/mamelink.h
Normal 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);
|
98
mamelink/mameplugins/mamelink/init.lua
Normal file
98
mamelink/mameplugins/mamelink/init.lua
Normal 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
|
10
mamelink/mameplugins/mamelink/plugin.json
Normal file
10
mamelink/mameplugins/mamelink/plugin.json
Normal 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
BIN
mamelink/reno.out
Normal file
Binary file not shown.
4
mamelink/reno.sh
Executable file
4
mamelink/reno.sh
Executable 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
6
mamelink/start
Executable 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
|
Loading…
Reference in a new issue