"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