Some stuff for loading elves. Not yet all finished.
This commit is contained in:
parent
9497991fcd
commit
77d3369d26
|
@ -59,6 +59,9 @@
|
||||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="ELFSharp">
|
||||||
|
<HintPath>..\References\ELFSharp.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="EMU7800">
|
<Reference Include="EMU7800">
|
||||||
<HintPath>..\References\EMU7800.dll</HintPath>
|
<HintPath>..\References\EMU7800.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@ -732,7 +735,10 @@
|
||||||
<Compile Include="Consoles\Sega\gpgx\GenDbgHlp.cs" />
|
<Compile Include="Consoles\Sega\gpgx\GenDbgHlp.cs" />
|
||||||
<Compile Include="Consoles\Sega\gpgx\GPGX.cs" />
|
<Compile Include="Consoles\Sega\gpgx\GPGX.cs" />
|
||||||
<Compile Include="Consoles\Sega\gpgx\GPGXControlConverter.cs" />
|
<Compile Include="Consoles\Sega\gpgx\GPGXControlConverter.cs" />
|
||||||
|
<Compile Include="Consoles\Sega\gpgx\GPGXControlConverterDynamic.cs" />
|
||||||
|
<Compile Include="Consoles\Sega\gpgx\GPGXDynamic.cs" />
|
||||||
<Compile Include="Consoles\Sega\gpgx\LibGPGX.cs" />
|
<Compile Include="Consoles\Sega\gpgx\LibGPGX.cs" />
|
||||||
|
<Compile Include="Consoles\Sega\gpgx\LibGPGXDynamic.cs" />
|
||||||
<Compile Include="Consoles\Sega\Saturn\FilePiping.cs" />
|
<Compile Include="Consoles\Sega\Saturn\FilePiping.cs" />
|
||||||
<Compile Include="Consoles\Sega\Saturn\LibYabause.cs" />
|
<Compile Include="Consoles\Sega\Saturn\LibYabause.cs" />
|
||||||
<Compile Include="Consoles\Sega\Saturn\Yabause.cs" />
|
<Compile Include="Consoles\Sega\Saturn\Yabause.cs" />
|
||||||
|
@ -825,6 +831,7 @@
|
||||||
<Compile Include="CPUs\Z80\Registers.cs" />
|
<Compile Include="CPUs\Z80\Registers.cs" />
|
||||||
<Compile Include="CPUs\Z80\Tables.cs" />
|
<Compile Include="CPUs\Z80\Tables.cs" />
|
||||||
<Compile Include="CPUs\Z80\Z80A.cs" />
|
<Compile Include="CPUs\Z80\Z80A.cs" />
|
||||||
|
<Compile Include="ElfRunner.cs" />
|
||||||
<Compile Include="FileID.cs" />
|
<Compile Include="FileID.cs" />
|
||||||
<Compile Include="LibRetro.cs" />
|
<Compile Include="LibRetro.cs" />
|
||||||
<Compile Include="LibRetroEmulator.cs" />
|
<Compile Include="LibRetroEmulator.cs" />
|
||||||
|
@ -836,6 +843,7 @@
|
||||||
<Compile Include="Consoles\PC Engine\VDC.cs" />
|
<Compile Include="Consoles\PC Engine\VDC.cs" />
|
||||||
<Compile Include="Consoles\PC Engine\VDC.Render.cs" />
|
<Compile Include="Consoles\PC Engine\VDC.Render.cs" />
|
||||||
<Compile Include="Consoles\PC Engine\VPC.cs" />
|
<Compile Include="Consoles\PC Engine\VPC.cs" />
|
||||||
|
<Compile Include="MemoryBlock.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Consoles\Sega\Genesis\Genesis.cs" />
|
<Compile Include="Consoles\Sega\Genesis\Genesis.cs" />
|
||||||
<Compile Include="Consoles\Sega\Genesis\GenVDP.cs" />
|
<Compile Include="Consoles\Sega\Genesis\GenVDP.cs" />
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
||||||
Mouse
|
Mouse
|
||||||
};
|
};
|
||||||
|
|
||||||
[CoreConstructor("GEN")]
|
//[CoreConstructor("GEN")]
|
||||||
public GPGX(CoreComm comm, byte[] file, object Settings, object SyncSettings)
|
public GPGX(CoreComm comm, byte[] file, object Settings, object SyncSettings)
|
||||||
:this(comm, file, null, Settings, SyncSettings)
|
:this(comm, file, null, Settings, SyncSettings)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
||||||
|
{
|
||||||
|
public class GPGXControlConverterDynamic
|
||||||
|
{
|
||||||
|
// this isn't all done
|
||||||
|
|
||||||
|
struct CName
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public LibGPGXDynamic.INPUT_KEYS Key;
|
||||||
|
public CName(string Name, LibGPGXDynamic.INPUT_KEYS Key)
|
||||||
|
{
|
||||||
|
this.Name = Name;
|
||||||
|
this.Key = Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CName[] Genesis3 =
|
||||||
|
{
|
||||||
|
new CName("Up", LibGPGXDynamic.INPUT_KEYS.INPUT_UP),
|
||||||
|
new CName("Down", LibGPGXDynamic.INPUT_KEYS.INPUT_DOWN),
|
||||||
|
new CName("Left", LibGPGXDynamic.INPUT_KEYS.INPUT_LEFT),
|
||||||
|
new CName("Right", LibGPGXDynamic.INPUT_KEYS.INPUT_RIGHT),
|
||||||
|
new CName("A", LibGPGXDynamic.INPUT_KEYS.INPUT_A),
|
||||||
|
new CName("B", LibGPGXDynamic.INPUT_KEYS.INPUT_B),
|
||||||
|
new CName("C", LibGPGXDynamic.INPUT_KEYS.INPUT_C),
|
||||||
|
new CName("Start", LibGPGXDynamic.INPUT_KEYS.INPUT_START),
|
||||||
|
};
|
||||||
|
|
||||||
|
static CName[] Genesis6 =
|
||||||
|
{
|
||||||
|
new CName("Up", LibGPGXDynamic.INPUT_KEYS.INPUT_UP),
|
||||||
|
new CName("Down", LibGPGXDynamic.INPUT_KEYS.INPUT_DOWN),
|
||||||
|
new CName("Left", LibGPGXDynamic.INPUT_KEYS.INPUT_LEFT),
|
||||||
|
new CName("Right", LibGPGXDynamic.INPUT_KEYS.INPUT_RIGHT),
|
||||||
|
new CName("A", LibGPGXDynamic.INPUT_KEYS.INPUT_A),
|
||||||
|
new CName("B", LibGPGXDynamic.INPUT_KEYS.INPUT_B),
|
||||||
|
new CName("C", LibGPGXDynamic.INPUT_KEYS.INPUT_C),
|
||||||
|
new CName("Start", LibGPGXDynamic.INPUT_KEYS.INPUT_START),
|
||||||
|
new CName("X", LibGPGXDynamic.INPUT_KEYS.INPUT_X),
|
||||||
|
new CName("Y", LibGPGXDynamic.INPUT_KEYS.INPUT_Y),
|
||||||
|
new CName("Z", LibGPGXDynamic.INPUT_KEYS.INPUT_Z),
|
||||||
|
new CName("Mode", LibGPGXDynamic.INPUT_KEYS.INPUT_MODE),
|
||||||
|
};
|
||||||
|
|
||||||
|
static CName[] Mouse =
|
||||||
|
{
|
||||||
|
new CName("Mouse Left", LibGPGXDynamic.INPUT_KEYS.INPUT_MOUSE_LEFT),
|
||||||
|
new CName("Mouse Center", LibGPGXDynamic.INPUT_KEYS.INPUT_MOUSE_CENTER),
|
||||||
|
new CName("Mouse Right", LibGPGXDynamic.INPUT_KEYS.INPUT_MOUSE_RIGHT),
|
||||||
|
new CName("Mouse Start", LibGPGXDynamic.INPUT_KEYS.INPUT_MOUSE_START),
|
||||||
|
};
|
||||||
|
|
||||||
|
static CName[] Lightgun =
|
||||||
|
{
|
||||||
|
new CName("Lightgun Trigger", LibGPGXDynamic.INPUT_KEYS.INPUT_MENACER_TRIGGER),
|
||||||
|
new CName("Lightgun Start", LibGPGXDynamic.INPUT_KEYS.INPUT_MENACER_START),
|
||||||
|
};
|
||||||
|
|
||||||
|
static CName[] Activator =
|
||||||
|
{
|
||||||
|
new CName("1L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_1L),
|
||||||
|
new CName("1U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_1U),
|
||||||
|
new CName("2L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_2L),
|
||||||
|
new CName("2U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_2U),
|
||||||
|
new CName("3L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_3L),
|
||||||
|
new CName("3U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_3U),
|
||||||
|
new CName("4L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_4L),
|
||||||
|
new CName("4U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_4U),
|
||||||
|
new CName("5L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_5L),
|
||||||
|
new CName("5U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_5U),
|
||||||
|
new CName("6L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_6L),
|
||||||
|
new CName("6U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_6U),
|
||||||
|
new CName("7L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_7L),
|
||||||
|
new CName("7U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_7U),
|
||||||
|
new CName("8L", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_8L),
|
||||||
|
new CName("8U", LibGPGXDynamic.INPUT_KEYS.INPUT_ACTIVATOR_8U),
|
||||||
|
};
|
||||||
|
|
||||||
|
static CName[] XEA1P =
|
||||||
|
{
|
||||||
|
new CName("XE A", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_A),
|
||||||
|
new CName("XE B", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_B),
|
||||||
|
new CName("XE C", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_C),
|
||||||
|
new CName("XE D", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_D),
|
||||||
|
new CName("XE Start", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_START),
|
||||||
|
new CName("XE Select", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_SELECT),
|
||||||
|
new CName("XE E1", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_E1),
|
||||||
|
new CName("XE E2", LibGPGXDynamic.INPUT_KEYS.INPUT_XE_E2),
|
||||||
|
};
|
||||||
|
|
||||||
|
static ControllerDefinition.FloatRange MouseRange = new ControllerDefinition.FloatRange(-256, 0, 255);
|
||||||
|
// lightgun needs to be transformed to match the current screen resolution
|
||||||
|
static ControllerDefinition.FloatRange LightgunRange = new ControllerDefinition.FloatRange(0, 5000, 10000);
|
||||||
|
|
||||||
|
static ControllerDefinition.FloatRange XEA1PRange = new ControllerDefinition.FloatRange(-128, 0, 127);
|
||||||
|
|
||||||
|
LibGPGXDynamic.InputData target = null;
|
||||||
|
IController source = null;
|
||||||
|
|
||||||
|
List<Action> Converts = new List<Action>();
|
||||||
|
|
||||||
|
public ControllerDefinition ControllerDef { get; private set; }
|
||||||
|
|
||||||
|
void AddToController(int idx, int player, IEnumerable<CName> Buttons)
|
||||||
|
{
|
||||||
|
foreach (var Button in Buttons)
|
||||||
|
{
|
||||||
|
string Name = string.Format("P{0} {1}", player, Button.Name);
|
||||||
|
ControllerDef.BoolButtons.Add(Name);
|
||||||
|
var ButtonFlag = Button.Key;
|
||||||
|
Converts.Add(delegate()
|
||||||
|
{
|
||||||
|
if (source.IsPressed(Name))
|
||||||
|
target.pad[idx] |= ButtonFlag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoMouseAnalog(int idx, int player)
|
||||||
|
{
|
||||||
|
string NX = string.Format("P{0} Mouse X", player);
|
||||||
|
string NY = string.Format("P{0} Mouse Y", player);
|
||||||
|
ControllerDef.FloatControls.Add(NX);
|
||||||
|
ControllerDef.FloatControls.Add(NY);
|
||||||
|
ControllerDef.FloatRanges.Add(MouseRange);
|
||||||
|
ControllerDef.FloatRanges.Add(MouseRange);
|
||||||
|
Converts.Add(delegate()
|
||||||
|
{
|
||||||
|
target.analog[(2 * idx) + 0] = (short)source.GetFloat(NX);
|
||||||
|
target.analog[(2 * idx) + 1] = (short)source.GetFloat(NY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoLightgunAnalog(int idx, int player)
|
||||||
|
{
|
||||||
|
string NX = string.Format("P{0} Lightgun X", player);
|
||||||
|
string NY = string.Format("P{0} Lightgun Y", player);
|
||||||
|
ControllerDef.FloatControls.Add(NX);
|
||||||
|
ControllerDef.FloatControls.Add(NY);
|
||||||
|
ControllerDef.FloatRanges.Add(LightgunRange);
|
||||||
|
ControllerDef.FloatRanges.Add(LightgunRange);
|
||||||
|
Converts.Add(delegate()
|
||||||
|
{
|
||||||
|
target.analog[(2 * idx) + 0] = (short)(source.GetFloat(NX) / 10000.0f * (ScreenWidth - 1));
|
||||||
|
target.analog[(2 * idx) + 1] = (short)(source.GetFloat(NY) / 10000.0f * (ScreenHeight - 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoXEA1PAnalog(int idx, int player)
|
||||||
|
{
|
||||||
|
string NX = string.Format("P{0} Stick X", player);
|
||||||
|
string NY = string.Format("P{0} Stick Y", player);
|
||||||
|
string NZ = string.Format("P{0} Stick Z", player);
|
||||||
|
ControllerDef.FloatControls.Add(NX);
|
||||||
|
ControllerDef.FloatControls.Add(NY);
|
||||||
|
ControllerDef.FloatControls.Add(NZ);
|
||||||
|
ControllerDef.FloatRanges.Add(XEA1PRange);
|
||||||
|
ControllerDef.FloatRanges.Add(XEA1PRange);
|
||||||
|
ControllerDef.FloatRanges.Add(XEA1PRange);
|
||||||
|
Converts.Add(delegate()
|
||||||
|
{
|
||||||
|
target.analog[(2 * idx) + 0] = (short)(source.GetFloat(NX));
|
||||||
|
target.analog[(2 * idx) + 1] = (short)(source.GetFloat(NY));
|
||||||
|
// +2 is correct in how gpgx internally does this
|
||||||
|
target.analog[(2 * idx) + 2] = (short)(source.GetFloat(NZ));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPGXControlConverterDynamic(LibGPGXDynamic.InputData input)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Genesis Controller report:");
|
||||||
|
foreach (var e in input.system)
|
||||||
|
Console.WriteLine("S:{0}", e);
|
||||||
|
foreach (var e in input.dev)
|
||||||
|
Console.WriteLine("D:{0}", e);
|
||||||
|
|
||||||
|
int player = 1;
|
||||||
|
|
||||||
|
ControllerDef = new ControllerDefinition();
|
||||||
|
|
||||||
|
ControllerDef.BoolButtons.Add("Power");
|
||||||
|
ControllerDef.BoolButtons.Add("Reset");
|
||||||
|
|
||||||
|
for (int i = 0; i < LibGPGXDynamic.MAX_DEVICES; i++)
|
||||||
|
{
|
||||||
|
switch (input.dev[i])
|
||||||
|
{
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_PAD3B:
|
||||||
|
AddToController(i, player, Genesis3);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_PAD6B:
|
||||||
|
AddToController(i, player, Genesis6);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_MOUSE:
|
||||||
|
AddToController(i, player, Mouse);
|
||||||
|
DoMouseAnalog(i, player);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_NONE:
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_LIGHTGUN:
|
||||||
|
// supports menacers and justifiers
|
||||||
|
AddToController(i, player, Lightgun);
|
||||||
|
DoLightgunAnalog(i, player);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_PAD2B:
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_PADDLE:
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_SPORTSPAD:
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_TEREBI:
|
||||||
|
throw new Exception("Master System only device? Something went wrong.");
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_ACTIVATOR:
|
||||||
|
AddToController(i, player, Activator);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_XE_A1P:
|
||||||
|
AddToController(i, player, XEA1P);
|
||||||
|
DoXEA1PAnalog(i, player);
|
||||||
|
player++;
|
||||||
|
break;
|
||||||
|
case LibGPGXDynamic.INPUT_DEVICE.DEVICE_PICO:
|
||||||
|
// PICO isn't finished on the unmanaged side either
|
||||||
|
throw new Exception("Sega PICO not implemented yet!");
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown Genesis control device! Something went wrong.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerDef.Name = "GPGX Genesis Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Convert(IController source, LibGPGXDynamic.InputData target)
|
||||||
|
{
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
target.ClearAllBools();
|
||||||
|
foreach (var f in Converts)
|
||||||
|
f();
|
||||||
|
this.source = null;
|
||||||
|
this.target = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// must be set for proper lightgun operation
|
||||||
|
/// </summary>
|
||||||
|
public int ScreenWidth { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// must be set for proper lightgun operation
|
||||||
|
/// </summary>
|
||||||
|
public int ScreenHeight { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,900 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Common.BufferExtensions;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Common;
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
||||||
|
{
|
||||||
|
[CoreAttributes(
|
||||||
|
"Genplus-gx",
|
||||||
|
"",
|
||||||
|
isPorted: true,
|
||||||
|
isReleased: true,
|
||||||
|
portedVersion: "r874",
|
||||||
|
portedUrl: "https://code.google.com/p/genplus-gx/",
|
||||||
|
singleInstance: false
|
||||||
|
)]
|
||||||
|
public class GPGXDynamic : IEmulator, ISyncSoundProvider, IVideoProvider, ISaveRam, IStatable,
|
||||||
|
IInputPollable, IDebuggable, ISettable<GPGXDynamic.GPGXSettings, GPGXDynamic.GPGXSyncSettings>, IDriveLight
|
||||||
|
{
|
||||||
|
DiscSystem.Disc CD;
|
||||||
|
byte[] romfile;
|
||||||
|
bool drivelight;
|
||||||
|
|
||||||
|
bool disposed = false;
|
||||||
|
|
||||||
|
LibGPGXDynamic gpgx;
|
||||||
|
ElfRunner elf;
|
||||||
|
|
||||||
|
LibGPGXDynamic.load_archive_cb LoadCallback = null;
|
||||||
|
LibGPGXDynamic.input_cb InputCallback = null;
|
||||||
|
|
||||||
|
LibGPGXDynamic.InputData input = new LibGPGXDynamic.InputData();
|
||||||
|
|
||||||
|
public enum ControlType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
OnePlayer,
|
||||||
|
Normal,
|
||||||
|
Xea1p,
|
||||||
|
Activator,
|
||||||
|
Teamplayer,
|
||||||
|
Wayplay,
|
||||||
|
Mouse
|
||||||
|
};
|
||||||
|
|
||||||
|
[CoreConstructor("GEN")]
|
||||||
|
public GPGXDynamic(CoreComm comm, byte[] file, object Settings, object SyncSettings)
|
||||||
|
: this(comm, file, null, Settings, SyncSettings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPGXDynamic(CoreComm comm, byte[] rom, DiscSystem.Disc CD, object Settings, object SyncSettings)
|
||||||
|
{
|
||||||
|
ServiceProvider = new BasicServiceProvider(this);
|
||||||
|
(ServiceProvider as BasicServiceProvider).Register<ITraceable>(_tracer);
|
||||||
|
// this can influence some things internally
|
||||||
|
string romextension = "GEN";
|
||||||
|
|
||||||
|
// three or six button?
|
||||||
|
// http://www.sega-16.com/forum/showthread.php?4398-Forgotten-Worlds-giving-you-GAME-OVER-immediately-Fix-inside&highlight=forgotten%20worlds
|
||||||
|
|
||||||
|
//hack, don't use
|
||||||
|
//romfile = File.ReadAllBytes(@"D:\encodes\bizhawksrc\output\SANIC CD\PierSolar (E).bin");
|
||||||
|
if (rom != null && rom.Length > 16 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ROM too big! Did you try to load a CD as a ROM?");
|
||||||
|
}
|
||||||
|
|
||||||
|
elf = new ElfRunner(@"D:\encodes\bizhawksrc\output\dll\gpgx.elf");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gpgx = new LibGPGXDynamic();
|
||||||
|
elf.PopulateInterface(gpgx);
|
||||||
|
|
||||||
|
_SyncSettings = (GPGXSyncSettings)SyncSettings ?? new GPGXSyncSettings();
|
||||||
|
|
||||||
|
CoreComm = comm;
|
||||||
|
|
||||||
|
LoadCallback = new LibGPGXDynamic.load_archive_cb(load_archive);
|
||||||
|
|
||||||
|
this.romfile = rom;
|
||||||
|
this.CD = CD;
|
||||||
|
|
||||||
|
LibGPGXDynamic.INPUT_SYSTEM system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_NONE;
|
||||||
|
LibGPGXDynamic.INPUT_SYSTEM system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_NONE;
|
||||||
|
|
||||||
|
switch (this._SyncSettings.ControlType)
|
||||||
|
{
|
||||||
|
case ControlType.None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
case ControlType.Activator:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_ACTIVATOR;
|
||||||
|
system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_ACTIVATOR;
|
||||||
|
break;
|
||||||
|
case ControlType.Normal:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD;
|
||||||
|
system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD;
|
||||||
|
break;
|
||||||
|
case ControlType.OnePlayer:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD;
|
||||||
|
break;
|
||||||
|
case ControlType.Xea1p:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_XE_A1P;
|
||||||
|
break;
|
||||||
|
case ControlType.Teamplayer:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_TEAMPLAYER;
|
||||||
|
system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_TEAMPLAYER;
|
||||||
|
break;
|
||||||
|
case ControlType.Wayplay:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_WAYPLAY;
|
||||||
|
system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_WAYPLAY;
|
||||||
|
break;
|
||||||
|
case ControlType.Mouse:
|
||||||
|
system_a = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD;
|
||||||
|
// seems like mouse in port 1 would be supported, but not both at the same time
|
||||||
|
system_b = LibGPGXDynamic.INPUT_SYSTEM.SYSTEM_MOUSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!gpgx.gpgx_init(romextension, LoadCallback, this._SyncSettings.UseSixButton, system_a, system_b, this._SyncSettings.Region))
|
||||||
|
throw new Exception("gpgx_init() failed");
|
||||||
|
|
||||||
|
{
|
||||||
|
int fpsnum = 60;
|
||||||
|
int fpsden = 1;
|
||||||
|
gpgx.gpgx_get_fps(ref fpsnum, ref fpsden);
|
||||||
|
CoreComm.VsyncNum = fpsnum;
|
||||||
|
CoreComm.VsyncDen = fpsden;
|
||||||
|
DisplayType = CoreComm.VsyncRate > 55 ? DisplayType.NTSC : DisplayType.PAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute state size
|
||||||
|
{
|
||||||
|
byte[] tmp = new byte[gpgx.gpgx_state_max_size()];
|
||||||
|
int size = gpgx.gpgx_state_size(tmp, tmp.Length);
|
||||||
|
if (size <= 0)
|
||||||
|
throw new Exception("Couldn't Determine GPGX internal state size!");
|
||||||
|
savebuff = new byte[size];
|
||||||
|
savebuff2 = new byte[savebuff.Length + 13];
|
||||||
|
Console.WriteLine("GPGX Internal State Size: {0}", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetControllerDefinition();
|
||||||
|
|
||||||
|
// pull the default video size from the core
|
||||||
|
update_video_initial();
|
||||||
|
|
||||||
|
SetMemoryDomains();
|
||||||
|
|
||||||
|
InputCallback = new LibGPGXDynamic.input_cb(input_callback);
|
||||||
|
gpgx.gpgx_set_input_callback(InputCallback);
|
||||||
|
|
||||||
|
if (CD != null)
|
||||||
|
DriveLightEnabled = true;
|
||||||
|
|
||||||
|
PutSettings((GPGXSettings)Settings ?? new GPGXSettings());
|
||||||
|
|
||||||
|
InitMemCallbacks();
|
||||||
|
KillMemCallbacks();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
||||||
|
|
||||||
|
public bool DriveLightEnabled { get; private set; }
|
||||||
|
public bool DriveLightOn { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// core callback for file loading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">string identifying file to be loaded</param>
|
||||||
|
/// <param name="buffer">buffer to load file to</param>
|
||||||
|
/// <param name="maxsize">maximum length buffer can hold</param>
|
||||||
|
/// <returns>actual size loaded, or 0 on failure</returns>
|
||||||
|
int load_archive(string filename, IntPtr buffer, int maxsize)
|
||||||
|
{
|
||||||
|
byte[] srcdata = null;
|
||||||
|
|
||||||
|
if (buffer == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldn't satisfy firmware request {0} because buffer == NULL", filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename == "PRIMARY_ROM")
|
||||||
|
{
|
||||||
|
if (romfile == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldn't satisfy firmware request PRIMARY_ROM because none was provided.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
srcdata = romfile;
|
||||||
|
}
|
||||||
|
else if (filename == "PRIMARY_CD" || filename == "SECONDARY_CD")
|
||||||
|
{
|
||||||
|
if (filename == "PRIMARY_CD" && romfile != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Declined to satisfy firmware request PRIMARY_CD because PRIMARY_ROM was provided.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (CD == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldn't satisfy firmware request {0} because none was provided.", filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
srcdata = GetCDData();
|
||||||
|
if (srcdata.Length != maxsize)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldn't satisfy firmware request {0} because of struct size.", filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use fromtend firmware interface
|
||||||
|
|
||||||
|
string firmwareID = null;
|
||||||
|
switch (filename)
|
||||||
|
{
|
||||||
|
case "CD_BIOS_EU": firmwareID = "CD_BIOS_EU"; break;
|
||||||
|
case "CD_BIOS_JP": firmwareID = "CD_BIOS_JP"; break;
|
||||||
|
case "CD_BIOS_US": firmwareID = "CD_BIOS_US"; break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (firmwareID != null)
|
||||||
|
{
|
||||||
|
// this path will be the most common PEBKAC error, so be a bit more vocal about the problem
|
||||||
|
srcdata = CoreComm.CoreFileProvider.GetFirmware("GEN", firmwareID, false, "GPGX firmwares are usually required.");
|
||||||
|
if (srcdata == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Frontend couldn't satisfy firmware request GEN:{0}", firmwareID);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unrecognized firmware request {0}", filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srcdata != null)
|
||||||
|
{
|
||||||
|
if (srcdata.Length > maxsize)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldn't satisfy firmware request {0} because {1} > {2}", filename, srcdata.Length, maxsize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Marshal.Copy(srcdata, 0, buffer, srcdata.Length);
|
||||||
|
Console.WriteLine("Firmware request {0} satisfied at size {1}", filename, srcdata.Length);
|
||||||
|
return srcdata.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
//Console.WriteLine("Couldn't satisfy firmware request {0} for unknown reasons", filename);
|
||||||
|
//return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDRead(int lba, IntPtr dest, bool audio)
|
||||||
|
{
|
||||||
|
if (audio)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[2352];
|
||||||
|
if (lba < CD.LBACount)
|
||||||
|
{
|
||||||
|
CD.ReadLBA_2352(lba, data, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// audio seems to read slightly past the end of disks; probably innoculous
|
||||||
|
// just send back 0s.
|
||||||
|
// Console.WriteLine("!!{0} >= {1}", lba, CD.LBACount);
|
||||||
|
}
|
||||||
|
Marshal.Copy(data, 0, dest, 2352);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte[] data = new byte[2048];
|
||||||
|
CD.ReadLBA_2048(lba, data, 0);
|
||||||
|
Marshal.Copy(data, 0, dest, 2048);
|
||||||
|
drivelight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LibGPGXDynamic.cd_read_cb cd_callback_handle;
|
||||||
|
|
||||||
|
unsafe byte[] GetCDData()
|
||||||
|
{
|
||||||
|
LibGPGXDynamic.CDData ret = new LibGPGXDynamic.CDData();
|
||||||
|
int size = Marshal.SizeOf(ret);
|
||||||
|
|
||||||
|
ret.readcallback = cd_callback_handle = new LibGPGXDynamic.cd_read_cb(CDRead);
|
||||||
|
|
||||||
|
var ses = CD.Structure.Sessions[0];
|
||||||
|
int ntrack = ses.Tracks.Count;
|
||||||
|
|
||||||
|
// bet you a dollar this is all wrong
|
||||||
|
for (int i = 0; i < LibGPGXDynamic.CD_MAX_TRACKS; i++)
|
||||||
|
{
|
||||||
|
if (i < ntrack)
|
||||||
|
{
|
||||||
|
ret.tracks[i].start = ses.Tracks[i].Indexes[1].aba - 150;
|
||||||
|
ret.tracks[i].end = ses.Tracks[i].LengthInSectors + ret.tracks[i].start;
|
||||||
|
if (i == ntrack - 1)
|
||||||
|
{
|
||||||
|
ret.end = ret.tracks[i].end;
|
||||||
|
ret.last = ntrack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret.tracks[i].start = 0;
|
||||||
|
ret.tracks[i].end = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] retdata = new byte[size];
|
||||||
|
|
||||||
|
fixed (byte* p = &retdata[0])
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr(ret, (IntPtr)p, false);
|
||||||
|
}
|
||||||
|
return retdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region controller
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// size of native input struct
|
||||||
|
/// </summary>
|
||||||
|
int inputsize;
|
||||||
|
|
||||||
|
GPGXControlConverterDynamic ControlConverter;
|
||||||
|
|
||||||
|
public ControllerDefinition ControllerDefinition { get; private set; }
|
||||||
|
public IController Controller { get; set; }
|
||||||
|
|
||||||
|
void SetControllerDefinition()
|
||||||
|
{
|
||||||
|
inputsize = Marshal.SizeOf(typeof(LibGPGXDynamic.InputData));
|
||||||
|
if (!gpgx.gpgx_get_control(input, inputsize))
|
||||||
|
throw new Exception("gpgx_get_control() failed");
|
||||||
|
|
||||||
|
ControlConverter = new GPGXControlConverterDynamic(input);
|
||||||
|
ControllerDefinition = ControlConverter.ControllerDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibGPGXDynamic.INPUT_DEVICE[] GetDevices()
|
||||||
|
{
|
||||||
|
return (LibGPGXDynamic.INPUT_DEVICE[])input.dev.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// core callback for input
|
||||||
|
void input_callback()
|
||||||
|
{
|
||||||
|
InputCallbacks.Call();
|
||||||
|
IsLagFrame = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly InputCallbackSystem _inputCallbacks = new InputCallbackSystem();
|
||||||
|
|
||||||
|
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks; } }
|
||||||
|
|
||||||
|
private readonly TraceBuffer _tracer = new TraceBuffer();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// TODO: use render and rendersound
|
||||||
|
public void FrameAdvance(bool render, bool rendersound = true)
|
||||||
|
{
|
||||||
|
if (Controller["Reset"])
|
||||||
|
gpgx.gpgx_reset(false);
|
||||||
|
if (Controller["Power"])
|
||||||
|
gpgx.gpgx_reset(true);
|
||||||
|
|
||||||
|
// do we really have to get each time? nothing has changed
|
||||||
|
if (!gpgx.gpgx_get_control(input, inputsize))
|
||||||
|
throw new Exception("gpgx_get_control() failed!");
|
||||||
|
|
||||||
|
ControlConverter.ScreenWidth = vwidth;
|
||||||
|
ControlConverter.ScreenHeight = vheight;
|
||||||
|
ControlConverter.Convert(Controller, input);
|
||||||
|
|
||||||
|
if (!gpgx.gpgx_put_control(input, inputsize))
|
||||||
|
throw new Exception("gpgx_put_control() failed!");
|
||||||
|
|
||||||
|
IsLagFrame = true;
|
||||||
|
Frame++;
|
||||||
|
drivelight = false;
|
||||||
|
|
||||||
|
gpgx.gpgx_advance();
|
||||||
|
update_video();
|
||||||
|
update_audio();
|
||||||
|
|
||||||
|
if (IsLagFrame)
|
||||||
|
LagCount++;
|
||||||
|
|
||||||
|
if (CD != null)
|
||||||
|
DriveLightOn = drivelight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Frame { get; private set; }
|
||||||
|
public int LagCount { get; private set; }
|
||||||
|
public bool IsLagFrame { get; private set; }
|
||||||
|
|
||||||
|
public string SystemId { get { return "GEN"; } }
|
||||||
|
public bool DeterministicEmulation { get { return true; } }
|
||||||
|
public string BoardName { get { return null; } }
|
||||||
|
|
||||||
|
public CoreComm CoreComm { get; private set; }
|
||||||
|
|
||||||
|
#region saveram
|
||||||
|
|
||||||
|
byte[] DisposedSaveRam = null;
|
||||||
|
|
||||||
|
public byte[] CloneSaveRam()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
if (DisposedSaveRam != null)
|
||||||
|
{
|
||||||
|
return (byte[])DisposedSaveRam.Clone();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
IntPtr area = IntPtr.Zero;
|
||||||
|
gpgx.gpgx_get_sram(ref area, ref size);
|
||||||
|
if (size <= 0 || area == IntPtr.Zero)
|
||||||
|
return new byte[0];
|
||||||
|
gpgx.gpgx_sram_prepread();
|
||||||
|
|
||||||
|
byte[] ret = new byte[size];
|
||||||
|
Marshal.Copy(area, ret, 0, size);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreSaveRam(byte[] data)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(typeof(GPGX).ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
IntPtr area = IntPtr.Zero;
|
||||||
|
gpgx.gpgx_get_sram(ref area, ref size);
|
||||||
|
if (size <= 0 || area == IntPtr.Zero)
|
||||||
|
return;
|
||||||
|
if (size != data.Length)
|
||||||
|
throw new Exception("Unexpected saveram size");
|
||||||
|
|
||||||
|
Marshal.Copy(data, 0, area, size);
|
||||||
|
gpgx.gpgx_sram_commitwrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveRamModified
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return DisposedSaveRam != null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
IntPtr area = IntPtr.Zero;
|
||||||
|
gpgx.gpgx_get_sram(ref area, ref size);
|
||||||
|
return size > 0 && area != IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void ResetCounters()
|
||||||
|
{
|
||||||
|
Frame = 0;
|
||||||
|
IsLagFrame = false;
|
||||||
|
LagCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region savestates
|
||||||
|
|
||||||
|
private byte[] savebuff;
|
||||||
|
private byte[] savebuff2;
|
||||||
|
|
||||||
|
public void SaveStateText(System.IO.TextWriter writer)
|
||||||
|
{
|
||||||
|
var temp = SaveStateBinary();
|
||||||
|
temp.SaveAsHexFast(writer);
|
||||||
|
// write extra copy of stuff we don't use
|
||||||
|
writer.WriteLine("Frame {0}", Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadStateText(System.IO.TextReader reader)
|
||||||
|
{
|
||||||
|
string hex = reader.ReadLine();
|
||||||
|
byte[] state = new byte[hex.Length / 2];
|
||||||
|
state.ReadFromHexFast(hex);
|
||||||
|
LoadStateBinary(new System.IO.BinaryReader(new System.IO.MemoryStream(state)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveStateBinary(System.IO.BinaryWriter writer)
|
||||||
|
{
|
||||||
|
if (!gpgx.gpgx_state_save(savebuff, savebuff.Length))
|
||||||
|
throw new Exception("gpgx_state_save() returned false");
|
||||||
|
|
||||||
|
writer.Write(savebuff.Length);
|
||||||
|
writer.Write(savebuff);
|
||||||
|
// other variables
|
||||||
|
writer.Write(Frame);
|
||||||
|
writer.Write(LagCount);
|
||||||
|
writer.Write(IsLagFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadStateBinary(System.IO.BinaryReader reader)
|
||||||
|
{
|
||||||
|
int newlen = reader.ReadInt32();
|
||||||
|
if (newlen != savebuff.Length)
|
||||||
|
throw new Exception("Unexpected state size");
|
||||||
|
reader.Read(savebuff, 0, savebuff.Length);
|
||||||
|
if (!gpgx.gpgx_state_load(savebuff, savebuff.Length))
|
||||||
|
throw new Exception("gpgx_state_load() returned false");
|
||||||
|
// other variables
|
||||||
|
Frame = reader.ReadInt32();
|
||||||
|
LagCount = reader.ReadInt32();
|
||||||
|
IsLagFrame = reader.ReadBoolean();
|
||||||
|
update_video();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] SaveStateBinary()
|
||||||
|
{
|
||||||
|
var ms = new System.IO.MemoryStream(savebuff2, true);
|
||||||
|
var bw = new System.IO.BinaryWriter(ms);
|
||||||
|
SaveStateBinary(bw);
|
||||||
|
bw.Flush();
|
||||||
|
ms.Close();
|
||||||
|
return savebuff2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BinarySaveStatesPreferred { get { return true; } }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region debugging tools
|
||||||
|
|
||||||
|
private IMemoryDomains MemoryDomains;
|
||||||
|
|
||||||
|
unsafe void SetMemoryDomains()
|
||||||
|
{
|
||||||
|
var mm = new List<MemoryDomain>();
|
||||||
|
for (int i = LibGPGXDynamic.MIN_MEM_DOMAIN; i <= LibGPGXDynamic.MAX_MEM_DOMAIN; i++)
|
||||||
|
{
|
||||||
|
IntPtr area = IntPtr.Zero;
|
||||||
|
int size = 0;
|
||||||
|
IntPtr pname = gpgx.gpgx_get_memdom(i, ref area, ref size);
|
||||||
|
if (area == IntPtr.Zero || pname == IntPtr.Zero || size == 0)
|
||||||
|
continue;
|
||||||
|
string name = Marshal.PtrToStringAnsi(pname);
|
||||||
|
if (name == "VRAM")
|
||||||
|
{
|
||||||
|
// vram pokes need to go through hook which invalidates cached tiles
|
||||||
|
byte* p = (byte*)area;
|
||||||
|
mm.Add(new MemoryDomain(name, size, MemoryDomain.Endian.Unknown,
|
||||||
|
delegate(long addr)
|
||||||
|
{
|
||||||
|
if (addr < 0 || addr >= 65536)
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
return p[addr ^ 1];
|
||||||
|
},
|
||||||
|
delegate(long addr, byte val)
|
||||||
|
{
|
||||||
|
if (addr < 0 || addr >= 65536)
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
gpgx.gpgx_poke_vram(((int)addr) ^ 1, val);
|
||||||
|
},
|
||||||
|
byteSize: 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var byteSize = name.Contains("Z80") ? 1 : 2;
|
||||||
|
mm.Add(MemoryDomain.FromIntPtrSwap16(name, size, MemoryDomain.Endian.Big, area, writable: true, byteSize: byteSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemoryDomains = new MemoryDomainList(mm);
|
||||||
|
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(MemoryDomains);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||||
|
{
|
||||||
|
LibGPGXDynamic.RegisterInfo[] regs = new LibGPGXDynamic.RegisterInfo[gpgx.gpgx_getmaxnumregs()];
|
||||||
|
|
||||||
|
int n = gpgx.gpgx_getregs(regs);
|
||||||
|
if (n > regs.Length)
|
||||||
|
throw new InvalidOperationException("A buffer overrun has occured!");
|
||||||
|
var ret = new Dictionary<string, RegisterValue>();
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
// el hacko
|
||||||
|
string name = Marshal.PtrToStringAnsi(regs[i].Name);
|
||||||
|
byte size = 32;
|
||||||
|
if (name.Contains("68K SR") || name.StartsWith("Z80"))
|
||||||
|
size = 16;
|
||||||
|
ret[Marshal.PtrToStringAnsi(regs[i].Name)] =
|
||||||
|
new RegisterValue { BitSize = size, Value = (ulong)regs[i].Value };
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanStep(StepType type) { return false; }
|
||||||
|
|
||||||
|
[FeatureNotImplemented]
|
||||||
|
public void Step(StepType type) { throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
[FeatureNotImplemented]
|
||||||
|
public void SetCpuRegister(string register, int value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVDPViewContext(LibGPGXDynamic.VDPView view)
|
||||||
|
{
|
||||||
|
gpgx.gpgx_get_vdp_view(view);
|
||||||
|
gpgx.gpgx_flush_vram(); // fully regenerate internal caches as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem();
|
||||||
|
public IMemoryCallbackSystem MemoryCallbacks { get { return _memoryCallbacks; } }
|
||||||
|
|
||||||
|
LibGPGXDynamic.mem_cb ExecCallback;
|
||||||
|
LibGPGXDynamic.mem_cb ReadCallback;
|
||||||
|
LibGPGXDynamic.mem_cb WriteCallback;
|
||||||
|
|
||||||
|
void InitMemCallbacks()
|
||||||
|
{
|
||||||
|
ExecCallback = new LibGPGXDynamic.mem_cb(a => MemoryCallbacks.CallExecutes(a));
|
||||||
|
ReadCallback = new LibGPGXDynamic.mem_cb(a => MemoryCallbacks.CallReads(a));
|
||||||
|
WriteCallback = new LibGPGXDynamic.mem_cb(a => MemoryCallbacks.CallWrites(a));
|
||||||
|
_memoryCallbacks.ActiveChanged += RefreshMemCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RefreshMemCallbacks()
|
||||||
|
{
|
||||||
|
gpgx.gpgx_set_mem_callback(
|
||||||
|
MemoryCallbacks.HasReads ? ReadCallback : null,
|
||||||
|
MemoryCallbacks.HasWrites ? WriteCallback : null,
|
||||||
|
MemoryCallbacks.HasExecutes ? ExecCallback : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KillMemCallbacks()
|
||||||
|
{
|
||||||
|
gpgx.gpgx_set_mem_callback(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
|
{
|
||||||
|
// if (SaveRamModified)
|
||||||
|
// DisposedSaveRam = CloneSaveRam();
|
||||||
|
// KillMemCallbacks();
|
||||||
|
if (CD != null)
|
||||||
|
{
|
||||||
|
CD.Dispose();
|
||||||
|
}
|
||||||
|
if (elf != null)
|
||||||
|
{
|
||||||
|
elf.Dispose();
|
||||||
|
elf = null;
|
||||||
|
gpgx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region SoundProvider
|
||||||
|
|
||||||
|
short[] samples = new short[4096];
|
||||||
|
int nsamp = 0;
|
||||||
|
|
||||||
|
public ISoundProvider SoundProvider { get { return null; } }
|
||||||
|
public ISyncSoundProvider SyncSoundProvider { get { return this; } }
|
||||||
|
public bool StartAsyncSound() { return false; }
|
||||||
|
public void EndAsyncSound() { }
|
||||||
|
|
||||||
|
public void GetSamples(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
nsamp = this.nsamp;
|
||||||
|
samples = this.samples;
|
||||||
|
this.nsamp = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DiscardSamples()
|
||||||
|
{
|
||||||
|
this.nsamp = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_audio()
|
||||||
|
{
|
||||||
|
IntPtr src = IntPtr.Zero;
|
||||||
|
gpgx.gpgx_get_audio(ref nsamp, ref src);
|
||||||
|
if (src != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Copy(src, samples, 0, nsamp * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region VideoProvider
|
||||||
|
|
||||||
|
public DisplayType DisplayType { get; private set; }
|
||||||
|
|
||||||
|
int[] vidbuff = new int[0];
|
||||||
|
int vwidth;
|
||||||
|
int vheight;
|
||||||
|
public int[] GetVideoBuffer() { return vidbuff; }
|
||||||
|
public int VirtualWidth { get { return BufferWidth; } } // TODO
|
||||||
|
public int VirtualHeight { get { return BufferHeight; } } // TODO
|
||||||
|
public int BufferWidth { get { return vwidth; } }
|
||||||
|
public int BufferHeight { get { return vheight; } }
|
||||||
|
public int BackgroundColor { get { return unchecked((int)0xff000000); } }
|
||||||
|
|
||||||
|
void update_video_initial()
|
||||||
|
{
|
||||||
|
// hack: you should call update_video() here, but that gives you 256x192 on frame 0
|
||||||
|
// and we know that we only use GPGX to emulate genesis games that will always be 320x224 immediately afterwards
|
||||||
|
|
||||||
|
// so instead, just assume a 320x224 size now; if that happens to be wrong, it'll be fixed soon enough.
|
||||||
|
|
||||||
|
vwidth = 320;
|
||||||
|
vheight = 224;
|
||||||
|
vidbuff = new int[vwidth * vheight];
|
||||||
|
for (int i = 0; i < vidbuff.Length; i++)
|
||||||
|
vidbuff[i] = unchecked((int)0xff000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe void update_video()
|
||||||
|
{
|
||||||
|
int pitch = 0;
|
||||||
|
IntPtr src = IntPtr.Zero;
|
||||||
|
|
||||||
|
gpgx.gpgx_get_video(ref vwidth, ref vheight, ref pitch, ref src);
|
||||||
|
|
||||||
|
if (vidbuff.Length < vwidth * vheight)
|
||||||
|
vidbuff = new int[vwidth * vheight];
|
||||||
|
|
||||||
|
int rinc = (pitch / 4) - vwidth;
|
||||||
|
fixed (int* pdst_ = &vidbuff[0])
|
||||||
|
{
|
||||||
|
int* pdst = pdst_;
|
||||||
|
int* psrc = (int*)src;
|
||||||
|
|
||||||
|
for (int j = 0; j < vheight; j++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < vwidth; i++)
|
||||||
|
*pdst++ = *psrc++;// | unchecked((int)0xff000000);
|
||||||
|
psrc += rinc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Settings
|
||||||
|
|
||||||
|
GPGXSyncSettings _SyncSettings;
|
||||||
|
GPGXSettings _Settings;
|
||||||
|
|
||||||
|
public GPGXSettings GetSettings() { return _Settings.Clone(); }
|
||||||
|
public GPGXSyncSettings GetSyncSettings() { return _SyncSettings.Clone(); }
|
||||||
|
public bool PutSettings(GPGXSettings o)
|
||||||
|
{
|
||||||
|
_Settings = o;
|
||||||
|
gpgx.gpgx_set_draw_mask(_Settings.GetDrawMask());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public bool PutSyncSettings(GPGXSyncSettings o)
|
||||||
|
{
|
||||||
|
bool ret = GPGXSyncSettings.NeedsReboot(_SyncSettings, o);
|
||||||
|
_SyncSettings = o;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GPGXSettings
|
||||||
|
{
|
||||||
|
[DisplayName("Background Layer A")]
|
||||||
|
[Description("True to draw BG layer A")]
|
||||||
|
[DefaultValue(true)]
|
||||||
|
public bool DrawBGA { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Background Layer B")]
|
||||||
|
[Description("True to draw BG layer B")]
|
||||||
|
[DefaultValue(true)]
|
||||||
|
public bool DrawBGB { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Background Layer W")]
|
||||||
|
[Description("True to draw BG layer W")]
|
||||||
|
[DefaultValue(true)]
|
||||||
|
public bool DrawBGW { get; set; }
|
||||||
|
|
||||||
|
public GPGXSettings()
|
||||||
|
{
|
||||||
|
SettingsUtil.SetDefaultValues(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPGXSettings Clone()
|
||||||
|
{
|
||||||
|
return (GPGXSettings)MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibGPGXDynamic.DrawMask GetDrawMask()
|
||||||
|
{
|
||||||
|
LibGPGXDynamic.DrawMask ret = 0;
|
||||||
|
if (DrawBGA) ret |= LibGPGXDynamic.DrawMask.BGA;
|
||||||
|
if (DrawBGB) ret |= LibGPGXDynamic.DrawMask.BGB;
|
||||||
|
if (DrawBGW) ret |= LibGPGXDynamic.DrawMask.BGW;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GPGXSyncSettings
|
||||||
|
{
|
||||||
|
[DisplayName("Use Six Button Controllers")]
|
||||||
|
[Description("Controls the type of any attached normal controllers; six button controllers are used if true, otherwise three button controllers. Some games don't work correctly with six button controllers. Not relevant if other controller types are connected.")]
|
||||||
|
[DefaultValue(true)]
|
||||||
|
public bool UseSixButton { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Control Type")]
|
||||||
|
[Description("Sets the type of controls that are plugged into the console. Some games will automatically load with a different control type.")]
|
||||||
|
[DefaultValue(ControlType.Normal)]
|
||||||
|
public ControlType ControlType { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Autodetect Region")]
|
||||||
|
[Description("Sets the region of the emulated console. Many games can run on multiple regions and will behave differently on different ones. Some games may require a particular region.")]
|
||||||
|
[DefaultValue(LibGPGXDynamic.Region.Autodetect)]
|
||||||
|
public LibGPGXDynamic.Region Region { get; set; }
|
||||||
|
|
||||||
|
public GPGXSyncSettings()
|
||||||
|
{
|
||||||
|
SettingsUtil.SetDefaultValues(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPGXSyncSettings Clone()
|
||||||
|
{
|
||||||
|
return (GPGXSyncSettings)MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool NeedsReboot(GPGXSyncSettings x, GPGXSyncSettings y)
|
||||||
|
{
|
||||||
|
return !DeepEquality.DeepEquals(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
||||||
|
{
|
||||||
|
public class LibGPGXDynamic
|
||||||
|
{
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_get_video_t(ref int w, ref int h, ref int pitch, ref IntPtr buffer);
|
||||||
|
public gpgx_get_video_t gpgx_get_video;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_get_audio_t(ref int n, ref IntPtr buffer);
|
||||||
|
public gpgx_get_audio_t gpgx_get_audio;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int load_archive_cb(string filename, IntPtr buffer, int maxsize);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_advance_t();
|
||||||
|
public gpgx_advance_t gpgx_advance;
|
||||||
|
|
||||||
|
public enum Region : int
|
||||||
|
{
|
||||||
|
Autodetect = 0,
|
||||||
|
USA = 1,
|
||||||
|
Europe = 2,
|
||||||
|
Japan_NTSC = 3,
|
||||||
|
Japan_PAL = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate bool gpgx_init_t(string feromextension, load_archive_cb feload_archive_cb, bool sixbutton, INPUT_SYSTEM system_a, INPUT_SYSTEM system_b, Region region);
|
||||||
|
public gpgx_init_t gpgx_init;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_get_fps_t(ref int num, ref int den);
|
||||||
|
public gpgx_get_fps_t gpgx_get_fps;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int gpgx_state_max_size_t();
|
||||||
|
public gpgx_state_max_size_t gpgx_state_max_size;
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int gpgx_state_size_t(byte[] dest, int size);
|
||||||
|
public gpgx_state_size_t gpgx_state_size;
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate bool gpgx_state_save_t(byte[] dest, int size);
|
||||||
|
public gpgx_state_save_t gpgx_state_save;
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate bool gpgx_state_load_t(byte[] src, int size);
|
||||||
|
public gpgx_state_load_t gpgx_state_load;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate bool gpgx_get_control_t([Out]InputData dest, int bytes);
|
||||||
|
public gpgx_get_control_t gpgx_get_control;
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate bool gpgx_put_control_t([In]InputData src, int bytes);
|
||||||
|
public gpgx_put_control_t gpgx_put_control;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_get_sram_t(ref IntPtr area, ref int size);
|
||||||
|
public gpgx_get_sram_t gpgx_get_sram;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_clear_sram_t();
|
||||||
|
public gpgx_clear_sram_t gpgx_clear_sram;
|
||||||
|
|
||||||
|
public const int MIN_MEM_DOMAIN = 0;
|
||||||
|
public const int MAX_MEM_DOMAIN = 13;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
// apparently, if you use built in string marshalling, the interop will assume that
|
||||||
|
// the unmanaged char pointer was allocated in hglobal and try to free it that way
|
||||||
|
public delegate IntPtr gpgx_get_memdom_t(int which, ref IntPtr area, ref int size);
|
||||||
|
public gpgx_get_memdom_t gpgx_get_memdom;
|
||||||
|
|
||||||
|
// call this before reading sram returned by gpgx_get_sram()
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_sram_prepread_t();
|
||||||
|
public gpgx_sram_prepread_t gpgx_sram_prepread;
|
||||||
|
|
||||||
|
// call this after writing sram returned by gpgx_get_sram()
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_sram_commitwrite_t();
|
||||||
|
public gpgx_sram_commitwrite_t gpgx_sram_commitwrite;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_reset_t(bool hard);
|
||||||
|
public gpgx_reset_t gpgx_reset;
|
||||||
|
|
||||||
|
public const int MAX_DEVICES = 8;
|
||||||
|
|
||||||
|
public enum INPUT_SYSTEM : byte
|
||||||
|
{
|
||||||
|
SYSTEM_NONE = 0, // unconnected port
|
||||||
|
SYSTEM_MD_GAMEPAD = 1, // single 3-buttons or 6-buttons Control Pad
|
||||||
|
SYSTEM_MOUSE = 2, // Sega Mouse
|
||||||
|
SYSTEM_MENACER = 3, // Sega Menacer -- port B only
|
||||||
|
SYSTEM_JUSTIFIER = 4, // Konami Justifiers -- port B only
|
||||||
|
SYSTEM_XE_A1P = 5, // XE-A1P analog controller -- port A only
|
||||||
|
SYSTEM_ACTIVATOR = 6, // Sega Activator
|
||||||
|
SYSTEM_MS_GAMEPAD = 7, // single 2-buttons Control Pad -- Master System
|
||||||
|
SYSTEM_LIGHTPHASER = 8, // Sega Light Phaser -- Master System
|
||||||
|
SYSTEM_PADDLE = 9, // Sega Paddle Control -- Master System
|
||||||
|
SYSTEM_SPORTSPAD = 10, // Sega Sports Pad -- Master System
|
||||||
|
SYSTEM_TEAMPLAYER = 11, // Multi Tap -- Sega TeamPlayer
|
||||||
|
SYSTEM_WAYPLAY = 12, // Multi Tap -- EA 4-Way Play -- use both ports
|
||||||
|
};
|
||||||
|
|
||||||
|
public enum INPUT_DEVICE : byte
|
||||||
|
{
|
||||||
|
DEVICE_NONE = 0xff, // unconnected device = fixed ID for Team Player)
|
||||||
|
DEVICE_PAD3B = 0x00, // 3-buttons Control Pad = fixed ID for Team Player)
|
||||||
|
DEVICE_PAD6B = 0x01, // 6-buttons Control Pad = fixed ID for Team Player)
|
||||||
|
DEVICE_PAD2B = 0x02, // 2-buttons Control Pad
|
||||||
|
DEVICE_MOUSE = 0x03, // Sega Mouse
|
||||||
|
DEVICE_LIGHTGUN = 0x04, // Sega Light Phaser, Menacer or Konami Justifiers
|
||||||
|
DEVICE_PADDLE = 0x05, // Sega Paddle Control
|
||||||
|
DEVICE_SPORTSPAD = 0x06,// Sega Sports Pad
|
||||||
|
DEVICE_PICO = 0x07, // PICO tablet
|
||||||
|
DEVICE_TEREBI = 0x08, // Terebi Oekaki tablet
|
||||||
|
DEVICE_XE_A1P = 0x09, // XE-A1P analog controller
|
||||||
|
DEVICE_ACTIVATOR = 0x0a,// Activator
|
||||||
|
};
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void input_cb();
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_set_input_callback_t(input_cb cb);
|
||||||
|
public gpgx_set_input_callback_t gpgx_set_input_callback;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void mem_cb(uint addr);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_set_mem_callback_t(mem_cb read, mem_cb write, mem_cb exec);
|
||||||
|
public gpgx_set_mem_callback_t gpgx_set_mem_callback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// not every flag is valid for every device!
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum INPUT_KEYS : ushort
|
||||||
|
{
|
||||||
|
/* Default Input bitmasks */
|
||||||
|
INPUT_MODE = 0x0800,
|
||||||
|
INPUT_X = 0x0400,
|
||||||
|
INPUT_Y = 0x0200,
|
||||||
|
INPUT_Z = 0x0100,
|
||||||
|
INPUT_START = 0x0080,
|
||||||
|
INPUT_A = 0x0040,
|
||||||
|
INPUT_C = 0x0020,
|
||||||
|
INPUT_B = 0x0010,
|
||||||
|
INPUT_RIGHT = 0x0008,
|
||||||
|
INPUT_LEFT = 0x0004,
|
||||||
|
INPUT_DOWN = 0x0002,
|
||||||
|
INPUT_UP = 0x0001,
|
||||||
|
|
||||||
|
/* Master System specific bitmasks */
|
||||||
|
INPUT_BUTTON2 = 0x0020,
|
||||||
|
INPUT_BUTTON1 = 0x0010,
|
||||||
|
|
||||||
|
/* Mega Mouse specific bitmask */
|
||||||
|
INPUT_MOUSE_START = 0x0080,
|
||||||
|
INPUT_MOUSE_CENTER = 0x0040,
|
||||||
|
INPUT_MOUSE_RIGHT = 0x0020,
|
||||||
|
INPUT_MOUSE_LEFT = 0x0010,
|
||||||
|
|
||||||
|
/* Pico hardware specific bitmask */
|
||||||
|
INPUT_PICO_PEN = 0x0080,
|
||||||
|
INPUT_PICO_RED = 0x0010,
|
||||||
|
|
||||||
|
/* XE-1AP specific bitmask */
|
||||||
|
INPUT_XE_E1 = 0x0800,
|
||||||
|
INPUT_XE_E2 = 0x0400,
|
||||||
|
INPUT_XE_START = 0x0200,
|
||||||
|
INPUT_XE_SELECT = 0x0100,
|
||||||
|
INPUT_XE_A = 0x0080,
|
||||||
|
INPUT_XE_B = 0x0040,
|
||||||
|
INPUT_XE_C = 0x0020,
|
||||||
|
INPUT_XE_D = 0x0010,
|
||||||
|
|
||||||
|
/* Activator specific bitmasks */
|
||||||
|
INPUT_ACTIVATOR_8U = 0x8000,
|
||||||
|
INPUT_ACTIVATOR_8L = 0x4000,
|
||||||
|
INPUT_ACTIVATOR_7U = 0x2000,
|
||||||
|
INPUT_ACTIVATOR_7L = 0x1000,
|
||||||
|
INPUT_ACTIVATOR_6U = 0x0800,
|
||||||
|
INPUT_ACTIVATOR_6L = 0x0400,
|
||||||
|
INPUT_ACTIVATOR_5U = 0x0200,
|
||||||
|
INPUT_ACTIVATOR_5L = 0x0100,
|
||||||
|
INPUT_ACTIVATOR_4U = 0x0080,
|
||||||
|
INPUT_ACTIVATOR_4L = 0x0040,
|
||||||
|
INPUT_ACTIVATOR_3U = 0x0020,
|
||||||
|
INPUT_ACTIVATOR_3L = 0x0010,
|
||||||
|
INPUT_ACTIVATOR_2U = 0x0008,
|
||||||
|
INPUT_ACTIVATOR_2L = 0x0004,
|
||||||
|
INPUT_ACTIVATOR_1U = 0x0002,
|
||||||
|
INPUT_ACTIVATOR_1L = 0x0001,
|
||||||
|
|
||||||
|
/* Menacer */
|
||||||
|
INPUT_MENACER_TRIGGER = 0x0040,
|
||||||
|
INPUT_MENACER_START = 0x0080,
|
||||||
|
};
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public class InputData
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||||
|
public readonly INPUT_SYSTEM[] system = new INPUT_SYSTEM[2];
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICES)]
|
||||||
|
public readonly INPUT_DEVICE[] dev = new INPUT_DEVICE[MAX_DEVICES];
|
||||||
|
/// <summary>
|
||||||
|
/// digital inputs
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICES)]
|
||||||
|
public readonly INPUT_KEYS[] pad = new INPUT_KEYS[MAX_DEVICES];
|
||||||
|
/// <summary>
|
||||||
|
/// analog (x/y)
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEVICES * 2)]
|
||||||
|
public readonly short[] analog = new short[MAX_DEVICES * 2];
|
||||||
|
/// <summary>
|
||||||
|
/// gun horizontal offset
|
||||||
|
/// </summary>
|
||||||
|
public int x_offset;
|
||||||
|
/// <summary>
|
||||||
|
/// gun vertical offset
|
||||||
|
/// </summary>
|
||||||
|
public int y_offset;
|
||||||
|
|
||||||
|
public void ClearAllBools()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < pad.Length; i++)
|
||||||
|
pad[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int CD_MAX_TRACKS = 100;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void cd_read_cb(int lba, IntPtr dest, bool audio);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct CDTrack
|
||||||
|
{
|
||||||
|
public int start;
|
||||||
|
public int end;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public class CDData
|
||||||
|
{
|
||||||
|
public int end;
|
||||||
|
public int last;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CD_MAX_TRACKS)]
|
||||||
|
public readonly CDTrack[] tracks = new CDTrack[CD_MAX_TRACKS];
|
||||||
|
public cd_read_cb readcallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct VDPNameTable
|
||||||
|
{
|
||||||
|
public int Width; // in cells
|
||||||
|
public int Height; // in cells
|
||||||
|
public int Baseaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public class VDPView
|
||||||
|
{
|
||||||
|
public IntPtr VRAM;
|
||||||
|
public IntPtr PatternCache;
|
||||||
|
public IntPtr ColorCache;
|
||||||
|
public VDPNameTable NTA;
|
||||||
|
public VDPNameTable NTB;
|
||||||
|
public VDPNameTable NTW;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_get_vdp_view_t([Out] VDPView view);
|
||||||
|
public gpgx_get_vdp_view_t gpgx_get_vdp_view;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_poke_vram_t(int addr, byte value);
|
||||||
|
public gpgx_poke_vram_t gpgx_poke_vram;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_flush_vram_t();
|
||||||
|
public gpgx_flush_vram_t gpgx_flush_vram;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RegisterInfo
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
public IntPtr Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int gpgx_getmaxnumregs_t();
|
||||||
|
public gpgx_getmaxnumregs_t gpgx_getmaxnumregs;
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate int gpgx_getregs_t([Out] RegisterInfo[] regs);
|
||||||
|
public gpgx_getregs_t gpgx_getregs;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DrawMask : int
|
||||||
|
{
|
||||||
|
BGA = 1,
|
||||||
|
BGB = 2,
|
||||||
|
BGW = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
public delegate void gpgx_set_draw_mask_t(DrawMask mask);
|
||||||
|
public gpgx_set_draw_mask_t gpgx_set_draw_mask;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ELFSharp.ELF;
|
||||||
|
using ELFSharp.ELF.Sections;
|
||||||
|
using ELFSharp.ELF.Segments;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores
|
||||||
|
{
|
||||||
|
public sealed class ElfRunner : IDisposable
|
||||||
|
{
|
||||||
|
// TODO: a lot of things only work with our elves and aren't fully generalized
|
||||||
|
|
||||||
|
private ELF<long> _elf;
|
||||||
|
private MemoryBlock _base;
|
||||||
|
private long _loadoffset;
|
||||||
|
private Dictionary<string, SymbolEntry<long>> _symdict;
|
||||||
|
private List<SymbolEntry<long>> _symlist;
|
||||||
|
|
||||||
|
public ElfRunner(string filename)
|
||||||
|
{
|
||||||
|
// todo: hack up this baby to take Streams
|
||||||
|
_elf = ELFReader.Load<long>(filename);
|
||||||
|
|
||||||
|
var loadsegs = _elf.Segments.Where(s => s.Type == SegmentType.Load);
|
||||||
|
|
||||||
|
long orig_start = loadsegs.Min(s => s.Address);
|
||||||
|
orig_start &= ~(Environment.SystemPageSize - 1);
|
||||||
|
long orig_end = loadsegs.Max(s => s.Address + s.Size);
|
||||||
|
_base = new MemoryBlock(UIntPtr.Zero, orig_end - orig_start);
|
||||||
|
_loadoffset = (long)_base.Start - orig_start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_base.Set(_base.Start, _base.Size, MemoryBlock.Protection.RW);
|
||||||
|
|
||||||
|
foreach (var seg in loadsegs)
|
||||||
|
{
|
||||||
|
var data = seg.GetContents();
|
||||||
|
Marshal.Copy(data, 0, (IntPtr)(seg.Address + _loadoffset), data.Length);
|
||||||
|
}
|
||||||
|
RegisterSymbols();
|
||||||
|
ProcessRelocations();
|
||||||
|
|
||||||
|
|
||||||
|
_base.Set(_base.Start, _base.Size, MemoryBlock.Protection.R);
|
||||||
|
|
||||||
|
foreach (var sec in _elf.Sections.Where(s => s.Flags.HasFlag(SectionFlags.Allocatable)))
|
||||||
|
{
|
||||||
|
if (sec.Flags.HasFlag(SectionFlags.Executable))
|
||||||
|
_base.Set((UIntPtr)(sec.LoadAddress + _loadoffset), sec.Size, MemoryBlock.Protection.RX);
|
||||||
|
else if (sec.Flags.HasFlag(SectionFlags.Writable))
|
||||||
|
_base.Set((UIntPtr)(sec.LoadAddress + _loadoffset), sec.Size, MemoryBlock.Protection.RW);
|
||||||
|
}
|
||||||
|
|
||||||
|
//FixupGOT();
|
||||||
|
ConnectAllClibPatches();
|
||||||
|
Console.WriteLine("Loaded {0}@{1:X16}", filename, (long)_base.Start);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_base.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Elf32_Rel
|
||||||
|
{
|
||||||
|
public long Address;
|
||||||
|
public byte Type;
|
||||||
|
public int SymbolIdx;
|
||||||
|
public long Addend;
|
||||||
|
|
||||||
|
public Elf32_Rel(byte[] data, int start, int len)
|
||||||
|
{
|
||||||
|
if (len == 8 || len == 12)
|
||||||
|
{
|
||||||
|
Address = BitConverter.ToInt32(data, start);
|
||||||
|
Type = data[start + 4];
|
||||||
|
SymbolIdx = (int)(BitConverter.ToUInt32(data, start + 4) >> 8);
|
||||||
|
Addend = data.Length == 12 ? BitConverter.ToInt32(data, start + 8) : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// elfsharp does not read relocation tables, so there
|
||||||
|
private void ProcessRelocations()
|
||||||
|
{
|
||||||
|
foreach (var rel in _elf.Sections.Where(s => s.Name.StartsWith(".rel")))
|
||||||
|
{
|
||||||
|
byte[] data = rel.GetContents();
|
||||||
|
var symbols = Enumerable.Range(0, data.Length / 8)
|
||||||
|
.Select(i => new Elf32_Rel(data, i * 8, 8));
|
||||||
|
foreach (var symbol in symbols)
|
||||||
|
{
|
||||||
|
ApplyRelocation(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void ApplyRelocation(Elf32_Rel rel)
|
||||||
|
{
|
||||||
|
// http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf
|
||||||
|
// this is probably mostly wrong
|
||||||
|
|
||||||
|
long val = 0;
|
||||||
|
long A = rel.Addend;
|
||||||
|
// since all symbols were moved by the same amount, just add _loadoffset here
|
||||||
|
long S = _symlist[rel.SymbolIdx].Value + _loadoffset;
|
||||||
|
long B = _loadoffset;
|
||||||
|
switch (rel.Type)
|
||||||
|
{
|
||||||
|
case 0: val = 0; break;
|
||||||
|
case 1: val = S + A; break;
|
||||||
|
case 2: throw new NotImplementedException();
|
||||||
|
case 3: throw new NotImplementedException();
|
||||||
|
case 4: throw new NotImplementedException();
|
||||||
|
case 5: val = 0; break;
|
||||||
|
case 6: val = S; break;
|
||||||
|
case 7: val = S; break;
|
||||||
|
case 8: val = B + A; break;
|
||||||
|
case 9: throw new NotImplementedException();
|
||||||
|
case 10: throw new NotImplementedException();
|
||||||
|
default: throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
byte[] tmp = new byte[4];
|
||||||
|
Marshal.Copy((IntPtr)(rel.Address + _loadoffset), tmp, 0, 4);
|
||||||
|
long currentVal = BitConverter.ToUInt32(tmp, 0);
|
||||||
|
tmp = BitConverter.GetBytes((uint)(currentVal + val));
|
||||||
|
Marshal.Copy(tmp, 0, (IntPtr)(rel.Address + _loadoffset), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterSymbols()
|
||||||
|
{
|
||||||
|
var symbols = ((ISymbolTable)_elf.GetSection(".symtab"))
|
||||||
|
.Entries
|
||||||
|
.Cast<SymbolEntry<long>>();
|
||||||
|
|
||||||
|
// when there are duplicate names, don't register either in the dictionary
|
||||||
|
_symdict = symbols
|
||||||
|
.GroupBy(e => e.Name)
|
||||||
|
.Where(g => g.Count() == 1)
|
||||||
|
.ToDictionary(g => g.Key, g => g.First());
|
||||||
|
|
||||||
|
_symlist = symbols.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopulateInterface(object o)
|
||||||
|
{
|
||||||
|
var fields = o.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.Where(fi => typeof(Delegate).IsAssignableFrom(fi.FieldType))
|
||||||
|
.Where(fi => fi.FieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length > 0);
|
||||||
|
|
||||||
|
foreach (var fi in fields)
|
||||||
|
{
|
||||||
|
var sym = _symdict[fi.Name]; // TODO: allow some sort of EntryPoint attribute
|
||||||
|
if (sym.Type != SymbolType.Function)
|
||||||
|
throw new InvalidOperationException("Unexpected symbol type for alleged function!");
|
||||||
|
IntPtr ptr = (IntPtr)(sym.Value + _loadoffset);
|
||||||
|
fi.SetValue(o, Marshal.GetDelegateForFunctionPointer(ptr, fi.FieldType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
//GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//~ElfRunner()
|
||||||
|
//{
|
||||||
|
// Dispose(false);
|
||||||
|
//}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (_base != null)
|
||||||
|
{
|
||||||
|
_base.Dispose();
|
||||||
|
_base = null;
|
||||||
|
}
|
||||||
|
if (_heaps != null)
|
||||||
|
{
|
||||||
|
foreach (var b in _heaps.Values)
|
||||||
|
{
|
||||||
|
b.Dispose();
|
||||||
|
}
|
||||||
|
_heaps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region clib monkeypatches
|
||||||
|
// our clib expects a few function pointers to be defined for it
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// heap free callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="p">ptr</param>
|
||||||
|
/// <param name="size">bytesize</param>
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate void FreeMem_D(IntPtr p, IntPtr size);
|
||||||
|
/// <summary>
|
||||||
|
/// heap alloc callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">bytesize</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate IntPtr AllocMem_D(IntPtr size);
|
||||||
|
/// <summary>
|
||||||
|
/// exit() callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="status">desired exit code</param>
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
private delegate void Exit_D(int status);
|
||||||
|
|
||||||
|
[CLibPatch("_ZZFree")]
|
||||||
|
private void FreeMem(IntPtr p, IntPtr size)
|
||||||
|
{
|
||||||
|
MemoryBlock block;
|
||||||
|
if (_heaps.TryGetValue(p, out block))
|
||||||
|
{
|
||||||
|
_heaps.Remove(p);
|
||||||
|
block.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[CLibPatch("_ZZAlloc")]
|
||||||
|
private IntPtr AllocMem(IntPtr size)
|
||||||
|
{
|
||||||
|
var block = new MemoryBlock((long)size);
|
||||||
|
var p = (IntPtr)(long)block.Start;
|
||||||
|
_heaps[p] = block;
|
||||||
|
block.Set(block.Start, block.Size, MemoryBlock.Protection.RW);
|
||||||
|
Console.WriteLine("AllocMem: {0:X8}@{1:X16}", (long)size, (long)block.Start);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
[CLibPatch("_ZZExit")]
|
||||||
|
private void Exit(int status)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Client code called exit()");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// list of memoryblocks that need to be cleaned up
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<IntPtr, MemoryBlock> _heaps = new Dictionary<IntPtr, MemoryBlock>();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// list of delegates that need to not be GCed
|
||||||
|
/// </summary>
|
||||||
|
private List<Delegate> _delegates = new List<Delegate>();
|
||||||
|
|
||||||
|
private void ConnectAllClibPatches()
|
||||||
|
{
|
||||||
|
var methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
.Where(mi => mi.GetCustomAttributes(typeof(CLibPatchAttribute), false).Length > 0);
|
||||||
|
foreach (var mi in methods)
|
||||||
|
{
|
||||||
|
var delegateType = GetType().GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
.Single(t => t.Name == mi.Name + "_D");
|
||||||
|
var del = Delegate.CreateDelegate(delegateType, this, mi);
|
||||||
|
IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del);
|
||||||
|
_delegates.Add(del);
|
||||||
|
var sym = _symdict[((CLibPatchAttribute)mi.GetCustomAttributes(typeof(CLibPatchAttribute), false)[0]).NativeName];
|
||||||
|
if (sym.Size != IntPtr.Size)
|
||||||
|
throw new InvalidOperationException("Unexpected function pointer size patching clib!");
|
||||||
|
Marshal.Copy(new[] { ptr }, 0, (IntPtr)(sym.Value + _loadoffset), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
private class CLibPatchAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string NativeName { get; private set; }
|
||||||
|
public CLibPatchAttribute(string nativeName)
|
||||||
|
{
|
||||||
|
NativeName = nativeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores
|
||||||
|
{
|
||||||
|
public sealed class MemoryBlock : IDisposable
|
||||||
|
{
|
||||||
|
public UIntPtr Start { get; private set; }
|
||||||
|
public long Size { get; private set; }
|
||||||
|
public UIntPtr End { get; private set; }
|
||||||
|
public int PageSize { get { return Environment.SystemPageSize; } }
|
||||||
|
|
||||||
|
public MemoryBlock(long size)
|
||||||
|
: this(UIntPtr.Zero, size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryBlock(UIntPtr start, long size)
|
||||||
|
{
|
||||||
|
Start = Kernel32.VirtualAlloc(start, checked((UIntPtr)size),
|
||||||
|
Kernel32.AllocationType.RESERVE | Kernel32.AllocationType.COMMIT,
|
||||||
|
Kernel32.MemoryProtection.NOACCESS);
|
||||||
|
if (Start == UIntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("VirtualAlloc() returned NULL");
|
||||||
|
}
|
||||||
|
if (start != UIntPtr.Zero)
|
||||||
|
End = (UIntPtr)((long)start + size);
|
||||||
|
else
|
||||||
|
End = (UIntPtr)((long)Start + size);
|
||||||
|
Size = (long)End - (long)Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Protection
|
||||||
|
{
|
||||||
|
R, RW, RX, None
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(UIntPtr start, long length, Protection prot)
|
||||||
|
{
|
||||||
|
if ((ulong)start < (ulong)Start)
|
||||||
|
throw new ArgumentOutOfRangeException("start");
|
||||||
|
|
||||||
|
if ((ulong)start + (ulong)length > (ulong)End)
|
||||||
|
throw new ArgumentOutOfRangeException("length");
|
||||||
|
|
||||||
|
Kernel32.MemoryProtection p;
|
||||||
|
switch (prot)
|
||||||
|
{
|
||||||
|
case Protection.None: p = Kernel32.MemoryProtection.NOACCESS; break;
|
||||||
|
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
|
||||||
|
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
|
||||||
|
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException("prot");
|
||||||
|
}
|
||||||
|
Kernel32.MemoryProtection old;
|
||||||
|
if (!Kernel32.VirtualProtect(start, (UIntPtr)length, p, out old))
|
||||||
|
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (Start != UIntPtr.Zero)
|
||||||
|
{
|
||||||
|
Kernel32.VirtualFree(Start, UIntPtr.Zero, Kernel32.FreeType.RELEASE);
|
||||||
|
Start = UIntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~MemoryBlock()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Kernel32
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
|
||||||
|
AllocationType flAllocationType, MemoryProtection flProtect);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern bool VirtualFree(UIntPtr lpAddress, UIntPtr dwSize,
|
||||||
|
FreeType dwFreeType);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern bool VirtualProtect(UIntPtr lpAddress, UIntPtr dwSize,
|
||||||
|
MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
|
||||||
|
|
||||||
|
public enum FreeType : uint
|
||||||
|
{
|
||||||
|
DECOMMIT = 0x4000,
|
||||||
|
RELEASE = 0x8000
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum AllocationType : uint
|
||||||
|
{
|
||||||
|
COMMIT = 0x1000,
|
||||||
|
RESERVE = 0x2000,
|
||||||
|
RESET = 0x80000,
|
||||||
|
RESET_UNDO = 0x1000000,
|
||||||
|
LARGE_PAGES = 0x20000000,
|
||||||
|
PHYSICAL = 0x400000,
|
||||||
|
TOP_DOWN = 0x100000,
|
||||||
|
WRITE_WATCH = 0x200000
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MemoryProtection : uint
|
||||||
|
{
|
||||||
|
EXECUTE = 0x10,
|
||||||
|
EXECUTE_READ = 0x20,
|
||||||
|
EXECUTE_READWRITE = 0x40,
|
||||||
|
EXECUTE_WRITECOPY = 0x80,
|
||||||
|
NOACCESS = 0x01,
|
||||||
|
READONLY = 0x02,
|
||||||
|
READWRITE = 0x04,
|
||||||
|
WRITECOPY = 0x08,
|
||||||
|
GUARD_Modifierflag = 0x100,
|
||||||
|
NOCACHE_Modifierflag = 0x200,
|
||||||
|
WRITECOMBINE_Modifierflag = 0x400
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
[Flags]
|
||||||
|
public enum Protection
|
||||||
|
{
|
||||||
|
PAGE_NOACCESS = 0x01,
|
||||||
|
PAGE_READONLY = 0x02,
|
||||||
|
PAGE_READWRITE = 0x04,
|
||||||
|
PAGE_WRITECOPY = 0x08,
|
||||||
|
PAGE_EXECUTE = 0x10,
|
||||||
|
PAGE_EXECUTE_READ = 0x20,
|
||||||
|
PAGE_EXECUTE_READWRITE = 0x40,
|
||||||
|
PAGE_EXECUTE_WRITECOPY = 0x80,
|
||||||
|
PAGE_GUARD = 0x100,
|
||||||
|
PAGE_NOCACHE = 0x200,
|
||||||
|
PAGE_WRITECOMBINE = 0x400
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue