Waterbox: Connect basic stdio. You get an empty stdin, real stdout and stderr, and can present readonly files to the core.

This commit is contained in:
nattthebear 2017-07-01 21:02:52 -04:00
parent dafe5a43cd
commit bc9726f687
7 changed files with 344 additions and 67 deletions

View File

@ -52,6 +52,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
_exe.Exit();
}
private readonly List<string> _readonlyFiles = new List<string>();
public void AddReadonlyFile(byte[] data, string name)
{
_exe.AddReadonlyFile(data, name);
_readonlyFiles.Add(name);
}
public LibsnesApi(string dllPath)
{
_exe = new PeRunner(new PeRunnerOptions
@ -366,6 +374,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
_core.DllInit();
_exe.Seal();
_sealed = true;
foreach (var s in _readonlyFiles)
{
_exe.RemoveReadonlyFile(s);
}
_readonlyFiles.Clear();
}
public void SaveStateBinary(BinaryWriter writer)

View File

@ -294,16 +294,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
return "";
}
// build romfilename
string test = CoreComm.CoreFileProvider.GetFirmwarePath("SNES", firmwareId, false, "Game may function incorrectly without the requested firmware.");
string ret;
var data = CoreComm.CoreFileProvider.GetFirmware("SNES", firmwareId, false, "Game may function incorrectly without the requested firmware.");
if (data != null)
{
ret = hint;
Api.AddReadonlyFile(data, hint);
}
else
{
ret = "";
}
// we need to return something to bsnes
test = test ?? "";
Console.WriteLine("Served libsnes request for firmware \"{0}\" with \"{1}\"", hint, test);
Console.WriteLine("Served libsnes request for firmware \"{0}\"", hint);
// return the path we built
return test;
return ret;
}
private void snes_trace(uint which, string msg)

View File

@ -226,12 +226,111 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <summary>
/// syscall emulation layer, as well as a bit of other stuff
/// </summary>
private class Syscalls
private class Syscalls : IBinaryStateable
{
public interface IFileObject : IBinaryStateable
{
bool Open(FileAccess access);
bool Close();
Stream Stream { get; }
string Name { get; }
}
private class SpecialFile : IFileObject
{
// stdin, stdout, stderr
public string Name { get; }
public Stream Stream { get; }
public bool Close() => false;
public bool Open(FileAccess access) => false;
public void SaveStateBinary(BinaryWriter writer) { }
public void LoadStateBinary(BinaryReader reader) { }
public SpecialFile(Stream stream, string name)
{
Stream = stream;
Name = name;
}
}
private class ReadonlyFirmware : IFileObject
{
private readonly byte[] _data;
private readonly byte[] _hash;
public string Name { get; }
public Stream Stream { get; private set; }
public bool Close()
{
if (Stream == null)
return false;
Stream = null;
return true;
}
public bool Open(FileAccess access)
{
if (Stream != null || access != FileAccess.Read)
return false;
Stream = new MemoryStream(_data, false);
return true;
}
public void LoadStateBinary(BinaryReader br)
{
if (!br.ReadBytes(_hash.Length).SequenceEqual(_hash))
throw new InvalidOperationException("Savestate internal firmware mismatch");
var pos = br.ReadInt64();
if (pos == -1)
{
Stream = null;
}
else
{
if (Stream == null)
Open(FileAccess.Read);
Stream.Position = pos;
}
}
public void SaveStateBinary(BinaryWriter bw)
{
bw.Write(_hash);
bw.Write(Stream != null ? Stream.Position : -1);
}
public ReadonlyFirmware(byte[] data, string name)
{
_data = data;
_hash = WaterboxUtils.Hash(data);
Name = name;
}
}
private readonly List<IFileObject> _openFiles = new List<IFileObject>();
private readonly Dictionary<string, IFileObject> _availableFiles = new Dictionary<string, IFileObject>();
private readonly PeRunner _parent;
public Syscalls(PeRunner parent)
{
_parent = parent;
var stdin = new SpecialFile(Stream.Null, "___stdin");
var stdout = new SpecialFile(Console.OpenStandardOutput(), "___stdout");
var stderr = new SpecialFile(Console.OpenStandardError(), "___stderr");
_openFiles = new List<IFileObject>
{
stdin,
stdout,
stderr
};
_availableFiles = new Dictionary<string, IFileObject>
{
[stdin.Name] = stdin,
[stdout.Name] = stdout,
[stderr.Name] = stderr
};
}
private IntPtr _pthreadSelf;
@ -243,6 +342,14 @@ namespace BizHawk.Emulation.Cores.Waterbox
_pthreadSelf = Z.US(_parent._plainheap.Allocate(512, 1));
}
private Stream StreamForFd(int fd)
{
if (fd >= 0 && fd < _openFiles.Count)
return _openFiles[fd].Stream;
else
return null;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "log_output")]
public void DebugPuts(IntPtr s)
{
@ -297,41 +404,120 @@ namespace BizHawk.Emulation.Cores.Waterbox
[BizExport(CallingConvention.Cdecl, EntryPoint = "n0")]
public long Read(int fd, IntPtr buff, ulong count)
{
return 0;
var s = StreamForFd(fd);
if (s == null || !s.CanRead)
return -1;
var tmp = new byte[count];
var ret = s.Read(tmp, 0, (int)count);
Marshal.Copy(tmp, 0, buff, ret);
return ret;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n1")]
public long Write(int fd, IntPtr buff, ulong count)
{
var s = StreamForFd(fd);
if (s == null || !s.CanWrite)
return -1;
var tmp = new byte[count];
Marshal.Copy(buff, tmp, 0, (int)count);
s.Write(tmp, 0, tmp.Length);
return (long)count;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n19")]
public unsafe long Readv(int fd, Iovec* iov, int iovcnt)
{
return 0;
long ret = 0;
for (int i = 0; i < iovcnt; i++)
{
var len = Read(fd, iov[i].Base, iov[i].Length);
if (len < 0)
return len;
ret += len;
if (len != (long)iov[i].Length)
break;
}
return ret;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n20")]
public unsafe long Writev(int fd, Iovec* iov, int iovcnt)
{
long ret = 0;
for (int i = 0; i < iovcnt; i++)
{
ret += (long)iov[i].Length;
}
ret += Write(fd, iov[i].Base, iov[i].Length);
return ret;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n2")]
public int Open(string path, int flags, int mode)
{
return -1;
IFileObject o;
if (!_availableFiles.TryGetValue(path, out o))
return -1;
if (_openFiles.Contains(o))
return -1;
FileAccess access;
switch (flags & 3)
{
case 0:
access = FileAccess.Read;
break;
case 1:
access = FileAccess.Write;
break;
case 2:
access = FileAccess.ReadWrite;
break;
default:
return -1;
}
if (!o.Open(access))
return -1;
int fd;
for (fd = 0; fd < _openFiles.Count; fd++)
if (_openFiles[fd] == null)
break;
if (fd == _openFiles.Count)
_openFiles.Add(o);
else
_openFiles[fd] = o;
return fd;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n3")]
public int Close(int fd)
{
if (fd < 0 || fd >= _openFiles.Count)
return -1;
var o = _openFiles[fd];
if (o == null || !o.Close())
return -1;
_openFiles[fd] = null;
return 0;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n8")]
public long Seek(int fd, long offset, int type)
{
var s = StreamForFd(fd);
if (s == null || !s.CanSeek)
return -1;
SeekOrigin o;
switch (type)
{
case 0:
o = SeekOrigin.Begin;
break;
case 1:
o = SeekOrigin.Current;
break;
case 2:
o = SeekOrigin.End;
break;
default:
return -1;
}
return s.Seek(offset, o);
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n4")]
public int Stat(string path, IntPtr statbuf)
@ -475,6 +661,59 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : -1;
}
public void SaveStateBinary(BinaryWriter bw)
{
bw.Write(_availableFiles.Count);
foreach (var f in _availableFiles.Values.OrderBy(f => f.Name))
{
bw.Write(f.Name);
f.SaveStateBinary(bw);
}
bw.Write(_openFiles.Count);
foreach (var f in _openFiles)
{
bw.Write(f != null);
if (f != null)
bw.Write(f.Name);
}
}
public void LoadStateBinary(BinaryReader br)
{
if (_availableFiles.Count != br.ReadInt32())
throw new InvalidOperationException("Internal savestate error: Filelist change");
foreach (var f in _availableFiles.Values.OrderBy(f => f.Name))
{
if (br.ReadString() != f.Name)
throw new InvalidOperationException("Internal savestate error: Filelist change");
f.LoadStateBinary(br);
}
var c = br.ReadInt32();
_openFiles.Clear();
for (int i = 0; i < c; i++)
{
_openFiles.Add(br.ReadBoolean() ? _availableFiles[br.ReadString()] : null);
}
}
public void AddReadonlyFile(byte[] data, string name)
{
_availableFiles.Add(name, new ReadonlyFirmware(data, name));
}
public void RemoveReadonlyFile(string name)
{
IFileObject o;
if (!_availableFiles.TryGetValue(name, out o))
throw new InvalidOperationException("Firmware was never registered!");
var f = o as ReadonlyFirmware;
if (f == null)
throw new InvalidOperationException("Object was not a firmware!");
if (_openFiles.Contains(o))
throw new InvalidOperationException("Core never closed the firmware!");
_availableFiles.Remove(name);
}
}
/// <summary>
@ -559,8 +798,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
// int pthread_mutex_unlock(pthread_mutex_t* mutex);
[BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_unlock")]
public int PthreadMutexUnlock(IntPtr mutex) { return 0; }*/
}
/// <summary>
@ -832,6 +1069,27 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
}
/// <summary>
/// Adds a file that will appear to the waterbox core's libc. the file will be read only.
/// All savestates must have the same file list, so either leave it up forever or remove it during init!
/// </summary>
/// <param name="data"></param>
/// <param name="name">the filename that the unmanaged core will access the file by</param>
public void AddReadonlyFile(byte[] data, string name)
{
_syscalls.AddReadonlyFile((byte[])data.Clone(), name);
}
/// <summary>
/// Remove a file previously added by AddReadonlyFile. Frees the internal copy of the filedata, saving memory.
/// All savestates must have the same file list, so either leave it up forever or remove it during init!
/// </summary>
/// <param name="name"></param>
public void RemoveReadonlyFile(string name)
{
_syscalls.RemoveReadonlyFile(name);
}
public void SaveStateBinary(BinaryWriter bw)
{
bw.Write(_createstamp);

Binary file not shown.

View File

@ -118,12 +118,12 @@ static void cmd_trn(uint32_t which)
}
else
{
utils_log("SGB: TRN already queued!");
utils_log("SGB: TRN already queued!\n");
}
}
else
{
utils_log("SGB: cmd_trn bad length");
utils_log("SGB: cmd_trn bad length\n");
}
}
@ -147,7 +147,7 @@ static void cmd_pal(int a, int b)
}
else
{
utils_log("SGB: cmd_pal bad length");
utils_log("SGB: cmd_pal bad length\n");
}
}
@ -178,7 +178,7 @@ static void cmd_pal_set(void)
}
else
{
utils_log("SGB: cmd_pal bad length");
utils_log("SGB: cmd_pal bad length\n");
}
}
@ -187,13 +187,13 @@ static void cmd_attr_blk()
int nset = sgb.command[1];
if (nset <= 0 || nset >= 19)
{
utils_log("SGB: cmd_attr_blk bad nset");
utils_log("SGB: cmd_attr_blk bad nset\n");
return;
}
int npacket = (nset * 6 + 16) / 16;
if ((sgb.command[0] & 7) != npacket)
{
utils_log("SGB: cmd_attr_blk bad length");
utils_log("SGB: cmd_attr_blk bad length\n");
return;
}
for (int i = 0; i < nset; i++)
@ -242,13 +242,13 @@ static void cmd_attr_lin()
int nset = sgb.command[1];
if (nset <= 0 || nset >= 111)
{
utils_log("SGB: cmd_attr_lin bad nset");
utils_log("SGB: cmd_attr_lin bad nset\n");
return;
}
int npacket = (nset + 17) / 16;
if ((sgb.command[0] & 7) != npacket)
{
utils_log("SGB: cmd_attr_lin bad length");
utils_log("SGB: cmd_attr_lin bad length\n");
return;
}
for (int i = 0; i < nset; i++)
@ -315,7 +315,7 @@ static void cmd_attr_div()
}
else
{
utils_log("SGB: cmd_attr_div bad length");
utils_log("SGB: cmd_attr_div bad length\n");
}
}
@ -326,13 +326,13 @@ static void cmd_attr_chr()
int n = sgb.command[3] | sgb.command[4] << 8;
if (n > 360)
{
utils_log("SGB: cmd_attr_chr bad n");
utils_log("SGB: cmd_attr_chr bad n\n");
return;
}
int npacket = (n + 87) / 64;
if ((sgb.command[0] & 7) != npacket)
{
utils_log("SGB: cmd_attr_chr bad length");
utils_log("SGB: cmd_attr_chr bad length\n");
return;
}
uint8_t *dst = sgb.attr;
@ -386,7 +386,7 @@ static void cmd_attr_set()
}
else
{
utils_log("SGB: cmd_attr_set bad length");
utils_log("SGB: cmd_attr_set bad length\n");
}
}
@ -410,11 +410,11 @@ static void cmd_mlt_req(void)
sgb.joypad_index = 1;
break;
}
utils_log("SGB: %u joypads", sgb.num_joypads);
utils_log("SGB: %u joypads\n", sgb.num_joypads);
}
else
{
utils_log("SGB: cmd_mlt_req bad length");
utils_log("SGB: cmd_mlt_req bad length\n");
}
}
@ -440,7 +440,7 @@ static void cmd_mask(void)
}
else
{
utils_log("SGB: cmd_mask bad length");
utils_log("SGB: cmd_mask bad length\n");
}
}
@ -455,7 +455,7 @@ static void cmd_sound(void)
}
else
{
utils_log("SGB: cmd_sound bad length");
utils_log("SGB: cmd_sound bad length\n");
}
}
@ -465,69 +465,69 @@ static void do_command(void)
switch (command)
{
default:
utils_log("SGB: Unknown or unimplemented command %02xh", command);
utils_log("SGB: Unknown or unimplemented command %02xh\n", command);
break;
case 0x00: // PAL01
utils_log("SGB: PAL01");
utils_log("SGB: PAL01\n");
cmd_pal(0, 1);
break;
case 0x01: // PAL23
utils_log("SGB: PAL23");
utils_log("SGB: PAL23\n");
cmd_pal(2, 3);
break;
case 0x02: // PAL03
utils_log("SGB: PAL03");
utils_log("SGB: PAL03\n");
cmd_pal(0, 3);
break;
case 0x03: // PAL12
utils_log("SGB: PAL12");
utils_log("SGB: PAL12\n");
cmd_pal(1, 2);
break;
case 0x0a: // PAL_SET
utils_log("SGB: PAL_SET");
utils_log("SGB: PAL_SET\n");
cmd_pal_set();
break;
case 0x04: // ATTR_BLK
utils_log("SGB: ATTR_BLK");
utils_log("SGB: ATTR_BLK\n");
cmd_attr_blk();
break;
case 0x05: // ATTR_LIN
utils_log("SGB: ATTR_LIN");
utils_log("SGB: ATTR_LIN\n");
cmd_attr_lin();
break;
case 0x06: // ATTR_DIV
utils_log("SGB: ATTR_DIV");
utils_log("SGB: ATTR_DIV\n");
cmd_attr_div();
break;
case 0x07: // ATTR_CHR
utils_log("SGB: ATTR_CHR");
utils_log("SGB: ATTR_CHR\n");
cmd_attr_chr();
break;
case 0x16: // ATTR_SET
utils_log("SGB: ATTR_SET");
utils_log("SGB: ATTR_SET\n");
cmd_attr_set();
break;
case 0x17: // MASK_EN
utils_log("SGB: MASK_EN");
utils_log("SGB: MASK_EN\n");
cmd_mask();
break;
// unknown functions
case 0x0c: // ATRC_EN
utils_log("SGB: ATRC_EN??");
utils_log("SGB: ATRC_EN??\n");
break;
case 0x0d: // TEST_EN
utils_log("SGB: TEST_EN??");
utils_log("SGB: TEST_EN??\n");
break;
case 0x0e: // ICON_EN
utils_log("SGB: ICON_EN??");
utils_log("SGB: ICON_EN??\n");
break;
case 0x18: // OBJ_TRN
// no game used this
utils_log("SGB: OBJ_TRN??");
utils_log("SGB: OBJ_TRN??\n");
break;
// unimplementable functions
@ -535,46 +535,46 @@ static void do_command(void)
// TODO: Is it possible for this (and DATA_TRN) to write data to
// memory areas used for the attribute file, etc?
// If so, do games do this?
utils_log("SGB: DATA_SND!! %02x:%02x%02x [%02x]", sgb.command[3], sgb.command[2], sgb.command[1], sgb.command[4]);
utils_log("SGB: DATA_SND!! %02x:%02x%02x [%02x]\n", sgb.command[3], sgb.command[2], sgb.command[1], sgb.command[4]);
break;
case 0x10: // DATA_TRN
utils_log("SGB: DATA_TRN!!");
utils_log("SGB: DATA_TRN!!\n");
break;
case 0x12: // JUMP
utils_log("SGB: JUMP!!");
utils_log("SGB: JUMP!!\n");
break;
// joypad
case 0x11: // MLT_REQ
utils_log("SGB: MLT_REQ");
utils_log("SGB: MLT_REQ\n");
cmd_mlt_req();
break;
// sound
case 0x08: // SOUND
utils_log("SGB: SOUND %02x %02x %02x %02x", sgb.command[1], sgb.command[2], sgb.command[3], sgb.command[4]);
utils_log("SGB: SOUND %02x %02x %02x %02x\n", sgb.command[1], sgb.command[2], sgb.command[3], sgb.command[4]);
cmd_sound();
break;
// all vram transfers
case 0x09: // SOU_TRN
utils_log("SGB: SOU_TRN");
utils_log("SGB: SOU_TRN\n");
cmd_trn(TRN_SOUND);
break;
case 0x0b: // PAL_TRN
utils_log("SGB: PAL_TRN");
utils_log("SGB: PAL_TRN\n");
cmd_trn(TRN_PAL);
break;
case 0x13: // CHR_TRN
utils_log("SGB: CHR_TRN");
utils_log("SGB: CHR_TRN\n");
cmd_trn(sgb.command[1] & 1 ? TRN_CHR_HI : TRN_CHR_LOW);
break;
case 0x14: // PCT_TRN
utils_log("SGB: PCT_TRN");
utils_log("SGB: PCT_TRN\n");
cmd_trn(TRN_PCT);
break;
case 0x15: // ATTR_TRN
utils_log("SGB: ATTR_TRN");
utils_log("SGB: ATTR_TRN\n");
cmd_trn(TRN_ATTR);
break;
}
@ -590,7 +590,7 @@ static void do_packet(void)
if (sgb.expected_packets == 0) // huh?
{
utils_log("SGB: zero packet command");
utils_log("SGB: zero packet command\n");
sgb.expected_packets = 0;
sgb.next_packet = 0;
}
@ -617,7 +617,7 @@ int sgb_init(const uint8_t *spc, int length)
spc_reset(sgb.spc);
if (spc_load_spc(sgb.spc, spc, length) != NULL)
{
utils_log("SGB: Failed to load SPC");
utils_log("SGB: Failed to load SPC\n");
return 0;
}
@ -648,7 +648,7 @@ void sgb_write_ff00(uint8_t val, uint64_t time)
if (p14_fell)
do_packet();
else
utils_log("SGB: Stop bit not present");
utils_log("SGB: Stop bit not present\n");
sgb.read_index = 255;
}
else
@ -733,17 +733,17 @@ static void trn_sound(const uint8_t* data)
{
int len = data[0] | data[1] << 8;
int addr = data[2] | data[3] << 8;
utils_log("TRN_SOUND %04x %04x", addr, len);
utils_log("TRN_SOUND %04x %04x\n", addr, len);
uint8_t* dst = spc_get_ram(sgb.spc);
if (len > 0xffc)
{
utils_log("TRN_SOUND src overflow");
utils_log("TRN_SOUND src overflow\n");
return;
}
if (len + addr >= 0x10000)
{
utils_log("TRN_SOUND dst overflow");
utils_log("TRN_SOUND dst overflow\n");
return;
}
memcpy(dst + addr, data + 4, len);

View File

@ -36,7 +36,7 @@ void utils_log(const char *format, ...)
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
_debug_puts(buf);
fputs(buf, stdout);
va_end(args);
}
@ -48,7 +48,7 @@ void utils_log_urgent(const char *format, ...)
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
_debug_puts(buf);
fputs(buf, stdout);
va_end(args);
}

View File

@ -67,7 +67,7 @@ EXPORT int Init(const void *rom, int romlen, int sgb, const void *spc, int spcle
return 0; // failure
global_sgb = !!sgb;
if (global_sgb && global_cgb)
utils_log("Warn: CGB game in SGB mode");
utils_log("Warn: CGB game in SGB mode\n");
if (sgb && !sgb_init((const uint8_t*)spc, spclen))
return 0;