using System; using System.Runtime.InteropServices; using System.IO; using System.Collections.Generic; namespace BizHawk.Emulation.Consoles.PSX { public unsafe class Octoshock : IEmulator, IVideoProvider, ISoundProvider { public string SystemId { get { return "NULL"; } } public static readonly ControllerDefinition NullController = new ControllerDefinition { Name = "Null Controller" }; public string BoardName { get { return null; } } private int[] frameBuffer = new int[0]; private Random rand = new Random(); public CoreComm CoreComm { get; private set; } public IVideoProvider VideoProvider { get { return this; } } public ISoundProvider SoundProvider { get { return this; } } public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(this, 735); } } public bool StartAsyncSound() { return true; } public void EndAsyncSound() { } public static bool CheckIsPSX(DiscSystem.Disc disc) { bool ret = false; byte[] buf = new byte[59]; disc.ReadLBA_2352_Flat(0x24D8, buf, 0, 59); string sig = System.Text.ASCIIEncoding.ASCII.GetString(buf); //this string is considered highly unlikely to exist anywhere besides a psx disc if (sig == " Licensed by Sony Computer Entertainment") ret = true; return ret; } //we can only have one active core at a time, due to the lib being so static. //so we'll track the current one here and detach the previous one whenever a new one is booted up. static Octoshock CurrOctoshockCore; bool disposed = false; public void Dispose() { if (disposed) return; disposed = true; //BizHawk.Emulation.Consoles.Nintendo.SNES.LibsnesDll.snes_set_audio_sample(null); } public Octoshock(CoreComm comm) { var domains = new List(); CoreComm = comm; VirtualWidth = BufferWidth = 256; BufferHeight = 192; } void Attach() { //attach this core as the current if (CurrOctoshockCore != null) CurrOctoshockCore.Dispose(); CurrOctoshockCore = this; } //note to self: try to make mednafen have file IO callbacks into here: open, close, read, write. //we'll trick mednafen into using a virtual filesystem and track the fake files internally public void LoadCuePath(string path) { Attach(); //note to self: //consider loading a fake cue, which is generated by our Disc class, and converting all reads to the fake bin to reads into the disc class. //thatd be pretty cool.... (may need to add an absolute byte range read method into the disc class, which can traverse the requisite LBAs)... //...but... are there other ideas? LibMednahawkDll.psx_LoadCue(path); } static Octoshock() { LibMednahawkDll.dll_Initialize(); FopenCallback = new LibMednahawkDll.t_FopenCallback(FopenCallbackProc); FcloseCallback = new LibMednahawkDll.t_FcloseCallback(FcloseCallbackProc); FopCallback = new LibMednahawkDll.t_FopCallback(FopCallbackProc); LibMednahawkDll.dll_SetPropPtr(LibMednahawkDll.eProp.SetPtr_FopenCallback, Marshal.GetFunctionPointerForDelegate(FopenCallback)); LibMednahawkDll.dll_SetPropPtr(LibMednahawkDll.eProp.SetPtr_FcloseCallback, Marshal.GetFunctionPointerForDelegate(FcloseCallback)); LibMednahawkDll.dll_SetPropPtr(LibMednahawkDll.eProp.SetPtr_FopCallback, Marshal.GetFunctionPointerForDelegate(FopCallback)); } static LibMednahawkDll.t_FopenCallback FopenCallback; static LibMednahawkDll.t_FcloseCallback FcloseCallback; static LibMednahawkDll.t_FopCallback FopCallback; class VirtualFile : IDisposable { public Stream stream; public int id; public void Dispose() { if(stream != null) stream.Dispose(); stream = null; } } static Dictionary VirtualFiles = new Dictionary(); static IntPtr FopenCallbackProc(string fname, string mode) { //TODO - probably this should never really fail. but for now, mednafen tries to create a bunch of junk, so just return failure for files which cant be opened if (fname.StartsWith("$psx")) { string[] parts = fname.Split('/'); if (parts[0] != "$psx") throw new InvalidOperationException("Octoshock using some weird path we dont handle yet"); if (parts[1] == "firmware") { fname = Path.Combine(CurrOctoshockCore.CoreComm.PSX_FirmwaresPath, parts[2]); if (!File.Exists(fname)) { System.Windows.Forms.MessageBox.Show("the Octoshock core is referencing a firmware file which could not be found. Please make sure it's in your configured PSX firmwares folder. The referenced filename is: " + parts[1]); } } } Stream stream = null; if (mode == "rb") { if (File.Exists(fname)) stream = new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.Read); } else if (mode == "wb") stream = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.Read); else throw new InvalidOperationException("unexpected virtual file mode from libmednahawk"); if (stream == null) return IntPtr.Zero; //find a free id. dont use 0 because it looks like an error int id = 1; for (; ; ) { RETRY: foreach (var vfid in VirtualFiles.Keys) if (vfid == id) { id++; goto RETRY; } break; } var ret = new VirtualFile(); ret.id = id; ret.stream = stream; VirtualFiles[ret.id] = ret; return new IntPtr(ret.id); } static int FcloseCallbackProc(IntPtr fp) { int id = fp.ToInt32(); VirtualFiles[id].stream.Dispose(); VirtualFiles.Remove(id); return 0; } static byte[] fiobuf = new byte[10*1024]; static long FopCallbackProc(int op, IntPtr ptr, long a, long b, IntPtr fp) { var vf = VirtualFiles[fp.ToInt32()]; int amt = (int)(a*b); switch ((LibMednahawkDll.FOP)op) { case LibMednahawkDll.FOP.FOP_clearerr: return 0; case LibMednahawkDll.FOP.FOP_ferror: return 0; case LibMednahawkDll.FOP.FOP_fflush: vf.stream.Flush(); return 0; case LibMednahawkDll.FOP.FOP_fread: { if(fiobuf.Length < amt) fiobuf = new byte[amt]; int read = vf.stream.Read(fiobuf, 0, amt); Marshal.Copy(fiobuf, 0, ptr, amt); return read / a; } case LibMednahawkDll.FOP.FOP_fseeko: vf.stream.Seek(a, (SeekOrigin)b); return vf.stream.Position; case LibMednahawkDll.FOP.FOP_ftello: return vf.stream.Position; case LibMednahawkDll.FOP.FOP_fwrite: { if (fiobuf.Length < amt) fiobuf = new byte[amt]; Marshal.Copy(fiobuf, 0, ptr, amt); vf.stream.Write(fiobuf, 0, amt); return (int)b; } case LibMednahawkDll.FOP.FOP_size: return vf.stream.Length; default: throw new InvalidOperationException("INESTIMABLE GOPHER"); } } public void ResetFrameCounter() { // FIXME when all this stuff is implemented Frame = 0; LagCount = 0; //IsLagFrame = false; } public void FrameAdvance(bool render, bool rendersound) { LibMednahawkDll.psx_FrameAdvance(); if (render == false) return; int w = LibMednahawkDll.dll_GetPropPtr(LibMednahawkDll.eProp.GetPtr_FramebufferWidth).ToInt32(); int h = LibMednahawkDll.dll_GetPropPtr(LibMednahawkDll.eProp.GetPtr_FramebufferHeight).ToInt32(); int p = LibMednahawkDll.dll_GetPropPtr(LibMednahawkDll.eProp.GetPtr_FramebufferPitchPixels).ToInt32(); IntPtr iptr = LibMednahawkDll.dll_GetPropPtr(LibMednahawkDll.eProp.GetPtr_FramebufferPointer); void* ptr = iptr.ToPointer(); VirtualWidth = BufferWidth = w; BufferHeight = h; int len = w*h; if (frameBuffer.Length != len) frameBuffer = new int[len]; //todo - we could do the reformatting in the PSX core //better yet, we could send a buffer into the psx core before frame advance to use for outputting video to for (int y = 0, i = 0; y < h; y++) for (int x = 0; x < w; x++, i++) { frameBuffer[i] = (int)unchecked(((int*)ptr)[y * p + x] | (int)0xFF000000); } } public ControllerDefinition ControllerDefinition { get { return NullController; } } public IController Controller { get; set; } public int Frame { get; set; } public int LagCount { get { return 0; } set { return; } } public bool IsLagFrame { get { return false; } } public byte[] ReadSaveRam() { return null; } public void StoreSaveRam(byte[] data) { } public void ClearSaveRam() { } public bool DeterministicEmulation { get { return true; } } public bool SaveRamModified { get; set; } public void SaveStateText(TextWriter writer) { } public void LoadStateText(TextReader reader) { } public void SaveStateBinary(BinaryWriter writer) { } public void LoadStateBinary(BinaryReader reader) { } public byte[] SaveStateBinary() { return new byte[1]; } public bool BinarySaveStatesPreferred { get { return false; } } public int[] GetVideoBuffer() { return frameBuffer; } public int VirtualWidth { get; private set; } public int BufferWidth { get; private set; } public int BufferHeight { get; private set; } public int BackgroundColor { get { return 0; } } public void GetSamples(short[] samples) { } public void DiscardSamples() { } public int MaxVolume { get; set; } private IList memoryDomains; public IList MemoryDomains { get { return memoryDomains; } } public MemoryDomain MainMemory { get { return memoryDomains[0]; } } } }