Remove Genesis.cs core
This commit is contained in:
parent
621a533dde
commit
ffd743cac7
|
@ -28,7 +28,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
ControllerImages.Add("Dual Gameboy Controller", Properties.Resources.GBController);
|
||||
|
||||
ControllerImages.Add("SMS Controller", Properties.Resources.SMSController);
|
||||
ControllerImages.Add("Genesis 3-Button Controller", Properties.Resources.GENController);
|
||||
ControllerImages.Add("GPGX Genesis Controller", Properties.Resources.GENController);
|
||||
ControllerImages.Add("Saturn Controller", Properties.Resources.SaturnController);
|
||||
|
||||
|
|
|
@ -512,28 +512,28 @@
|
|||
<Compile Include="Consoles\Nintendo\GBA\LibVBANext.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.IDebuggable.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.IInputPollable.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.IMemoryDomains.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.ISaveRam.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.ISettable.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.ISoundProvider.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.IStatable.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\MGBAHawk.IVideoProvider.cs">
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
<DependentUpon>MGBAHawk.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\VBANext.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBA\VBANext.IDebuggable.cs">
|
||||
|
@ -1001,12 +1001,6 @@
|
|||
</Compile>
|
||||
<Compile Include="Consoles\PC Engine\PCEngine.TurboCD.cs" />
|
||||
<Compile Include="Consoles\PC Engine\ScsiCDBus.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Cart\EEPROM.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Cart\RomHeader.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Cart\SaveRAM.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Genesis.Input.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Genesis.IO.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Native68000\Musashi.cs" />
|
||||
<Compile Include="Consoles\Sega\gpgx64\GPGX.ISoundProvider.cs">
|
||||
<DependentUpon>GPGX.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -1259,12 +1253,6 @@
|
|||
<Compile Include="Libretro\LibretroCore_InputCallbacks.cs" />
|
||||
<Compile Include="MemoryBlock.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\Genesis.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\GenVDP.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\GenVDP.DMA.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\GenVDP.Render.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\MemoryMap.68000.cs" />
|
||||
<Compile Include="Consoles\Sega\Genesis\MemoryMap.Z80.cs" />
|
||||
<Compile Include="Sound\CDAudio.cs" />
|
||||
<Compile Include="Sound\HuC6280PSG.cs" />
|
||||
<Compile Include="Sound\IAsyncSoundProvider.cs" />
|
||||
|
@ -1282,7 +1270,6 @@
|
|||
<Content Include="Consoles\Nintendo\NES\Docs\sunsoft.txt" />
|
||||
<Content Include="Consoles\Nintendo\NES\Docs\sxrom_notes.txt" />
|
||||
<Content Include="Consoles\PC Engine\Compat.txt" />
|
||||
<Content Include="Consoles\Sega\Genesis\Compat.txt" />
|
||||
<Content Include="Consoles\Sega\SMS\Compat.txt" />
|
||||
<Content Include="Notes.txt" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
// State
|
||||
bool EepromEnabled;
|
||||
|
||||
int EepromSize;
|
||||
int EepromAddrMask;
|
||||
int SdaInAddr, SdaInBit;
|
||||
int SdaOutAddr, SdaOutBit;
|
||||
int SclAddr, SclBit;
|
||||
|
||||
int SdaInCurrValue, SclCurrValue;
|
||||
|
||||
// Code
|
||||
|
||||
void InitializeEeprom(GameInfo game)
|
||||
{
|
||||
if (game["EEPROM"] == false)
|
||||
return;
|
||||
|
||||
EepromEnabled = true;
|
||||
EepromAddrMask = game.GetHexValue("EEPROM_ADDR_MASK");
|
||||
EepromSize = EepromAddrMask + 1;
|
||||
|
||||
var t = game.OptionValue("SDA_IN").Split(':');
|
||||
SdaInAddr = int.Parse(t[0], NumberStyles.HexNumber);
|
||||
SdaInBit = int.Parse(t[1]);
|
||||
|
||||
t = game.OptionValue("SDA_OUT").Split(':');
|
||||
SdaOutAddr = int.Parse(t[0], NumberStyles.HexNumber);
|
||||
SdaOutBit = int.Parse(t[1]);
|
||||
|
||||
t = game.OptionValue("SCL").Split(':');
|
||||
SclAddr = int.Parse(t[0], NumberStyles.HexNumber);
|
||||
SclBit = int.Parse(t[1]);
|
||||
|
||||
SaveRAM = new byte[EepromSize];
|
||||
|
||||
Console.WriteLine("EEPROM enabled. Size: ${0:X} SDA_IN: ${1:X}:{2} SDA_OUT: ${3:X}:{4}, SCL: ${5:X}:{6}",
|
||||
EepromSize, SdaInAddr, SdaInBit, SdaOutAddr, SdaOutBit, SclAddr, SclBit);
|
||||
}
|
||||
|
||||
void WriteByteEeprom(int address, byte value)
|
||||
{
|
||||
if (address == SdaInAddr)
|
||||
{
|
||||
SdaInCurrValue = (value >> SdaInBit) & 1;
|
||||
Console.WriteLine("SDA_IN: {0}", SdaInCurrValue);
|
||||
}
|
||||
if (address == SclAddr)
|
||||
{
|
||||
SclCurrValue = (value >> SclBit) & 1;
|
||||
Console.WriteLine("SCL: {0}", SclCurrValue);
|
||||
}
|
||||
|
||||
// todo: logic!
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
public string RH_Console { get { return GetRomString(0x100, 0x10); } }
|
||||
public string RH_Copyright { get { return GetRomString(0x110, 0x10); } }
|
||||
public string RH_NameDomestic { get { return GetRomString(0x120, 0x30); } }
|
||||
public string RH_NameExport { get { return GetRomString(0x150, 0x30); } }
|
||||
public int RH_RomSize { get { return GetRomLongWord(0x1A4); } }
|
||||
public string RH_Region { get { return GetRomString(0x1F0, 3); } }
|
||||
|
||||
public bool RH_SRamPresent { get { return (RomData[0x1B2] & 0x40) != 0; } }
|
||||
public int RH_SRamCode { get { return (RomData[0x1B2] >> 3) & 3; } }
|
||||
public int RH_SRamStart { get { return GetRomLongWord(0x1B4); } }
|
||||
public int RH_SRamEnd { get { return GetRomLongWord(0x1B8); } }
|
||||
|
||||
public string RH_SRamInterpretation()
|
||||
{
|
||||
switch (RH_SRamCode)
|
||||
{
|
||||
case 0: return "Even and odd addresses";
|
||||
case 2: return "Even addresses";
|
||||
case 3: return "Odd addresses";
|
||||
default: return "Invalid type";
|
||||
}
|
||||
}
|
||||
|
||||
string GetRomString(int offset, int len)
|
||||
{
|
||||
return Encoding.ASCII.GetString(RomData, offset, len).Trim();
|
||||
}
|
||||
|
||||
int GetRomLongWord(int offset)
|
||||
{
|
||||
return (RomData[offset] << 24) | (RomData[offset + 1] << 16) | (RomData[offset + 2] << 8) | RomData[offset + 3];
|
||||
}
|
||||
|
||||
void LogCartInfo()
|
||||
{
|
||||
Console.WriteLine("==================");
|
||||
Console.WriteLine("ROM Cartridge Data");
|
||||
Console.WriteLine("==================");
|
||||
Console.WriteLine("System: {0}", RH_Console);
|
||||
Console.WriteLine("Copyright: {0}", RH_Copyright);
|
||||
Console.WriteLine("Name (Dom): {0}", RH_NameDomestic);
|
||||
Console.WriteLine("Name (Exp): {0}", RH_NameExport);
|
||||
Console.WriteLine("Region: {0}", RH_Region);
|
||||
Console.WriteLine("Rom Size: {0,7} (${0:X})", RH_RomSize);
|
||||
Console.WriteLine("SRAM Used: {0}", RH_SRamPresent);
|
||||
if (RH_SRamPresent)
|
||||
{
|
||||
Console.WriteLine("SRAM Start: {0,7} (${0:X})", RH_SRamStart);
|
||||
Console.WriteLine("SRAM End: {0,7} (${0:X})", RH_SRamEnd);
|
||||
Console.WriteLine("SRAM Type: {0}", RH_SRamInterpretation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
bool SaveRamEnabled;
|
||||
bool SaveRamEveryOtherByte;
|
||||
int SaveRamStartOffset;
|
||||
int SaveRamEndOffset;
|
||||
int SaveRamLength;
|
||||
|
||||
byte[] SaveRAM = new byte[0];
|
||||
|
||||
void InitializeSaveRam(GameInfo game)
|
||||
{
|
||||
if (EepromEnabled)
|
||||
return;
|
||||
|
||||
if (game["DisableSaveRam"] || RH_SRamPresent == false)
|
||||
return;
|
||||
|
||||
SaveRamEnabled = true;
|
||||
SaveRamEveryOtherByte = RH_SRamCode != 0;
|
||||
SaveRamStartOffset = RH_SRamStart;
|
||||
SaveRamEndOffset = RH_SRamEnd;
|
||||
|
||||
if (game["SaveRamStartOffset"])
|
||||
SaveRamStartOffset = game.GetHexValue("SaveRamStartOffset");
|
||||
if (game["SaveRamEndOffset"])
|
||||
SaveRamEndOffset = game.GetHexValue("SaveRamEndOffset");
|
||||
|
||||
SaveRamLength = (SaveRamEndOffset - SaveRamStartOffset) + 1;
|
||||
|
||||
if (SaveRamEveryOtherByte)
|
||||
SaveRamLength = ((SaveRamEndOffset - SaveRamStartOffset) / 2) + 1;
|
||||
|
||||
SaveRAM = new byte[SaveRamLength];
|
||||
|
||||
Console.WriteLine("SaveRAM enabled. Start: ${0:X6} End: ${1:X6} Length: ${2:X} Mode: {3}", SaveRamStartOffset, SaveRamEndOffset, SaveRamLength, RH_SRamInterpretation());
|
||||
}
|
||||
|
||||
public byte[] CloneSaveRam() { return (byte[])SaveRAM.Clone(); }
|
||||
public void StoreSaveRam(byte[] data) { Array.Copy(data, SaveRAM, data.Length); }
|
||||
|
||||
public bool SaveRamModified { get; private set; }
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
68000:
|
||||
|
||||
Timings:
|
||||
|
||||
- MULU/MULS/DIVU/DIVS have funky timings.
|
||||
- How many cycles does TRAP take to execute?
|
||||
- How many cycles does it take to accept an interrupt?
|
||||
- AND has some funky timings when it comes to immediates?
|
||||
|
||||
GAMES:
|
||||
|
||||
Ahhh! Real monsters - no sound
|
||||
Adventures of Mighty Max - crashes memory mapper
|
||||
Air Diver - suffer from lack of 2cell vscroll
|
||||
Alien Storm.... gfx glitches. control glitches?
|
||||
Altered Beast: start with 0 health, 0 lives???
|
||||
Another World .. broken.. (E) tho
|
||||
Arcus Odyssey does UNHANDLED Z80 READs... is this a problem?
|
||||
Awesome Possum - controls are wrong
|
||||
Batman - flicker and music is messed
|
||||
Batman Forever - significant gfx issues
|
||||
Battle Squadron - crashes memory mapper woo!
|
||||
Blood Shot - FPS game - some texture corruption
|
||||
Bonkers - substantial gfx corruption
|
||||
Buck Rogers crashes in a fun way
|
||||
Burning Force - some gfx issues. works way better than it has in the past though!
|
||||
Cheese Cat-astrophe - crashes renderer!!
|
||||
Chester Cheetah - freezes when starting new game
|
||||
Chuck Rock - Music messed up
|
||||
Contra Hard Corps: Scrolling is messed up in level 1... used to work.
|
||||
Dashin' Desperados .. bottom screen messed up
|
||||
Death Duel crashes my renderer... (!)
|
||||
Decap Attack - Item select screen messed up
|
||||
Double Dragon doesn't boot
|
||||
Devilish/Bad Omen - intro messed up.... interesting debug target
|
||||
Dune... freezes in intro
|
||||
Exile - Immediate gfx issues... Debug target.
|
||||
F1 World Championship... Gfx issue at bottom of screen... quite cool working game though!
|
||||
Fatal Rewind - appears to do a read.w on FFFFFF... that would be an address error. read.l messes up too. Ergo: basically doesnt work.
|
||||
Final Blow - Music messed up
|
||||
Fire Shark - Messed up sound
|
||||
Flavio's Raster FX Test.. doesnt work
|
||||
Foreman for Real doent boot
|
||||
Galaxy Force II - gfx issue in level select screen, and in level-end "shoot the core" part
|
||||
Gargoyles... gameplay is nonfunctional
|
||||
Gauntlet 4 .. title screen messed. gfx corruption. immediately - debug target.
|
||||
Golden Axe - controls are jacked up
|
||||
Golden Axe 3 intro.... weirder than before for sure
|
||||
Grind Stormer
|
||||
Herzog Zwei .. doesnt boot fully
|
||||
Insector X .. title screen gfx issue
|
||||
James Pond 3 crash in intro
|
||||
Jim Power - gfx glitches
|
||||
Jurassic Park 2 - crashes in intro
|
||||
Lemmings: Sound is royally effed... gfx glitches.
|
||||
Marvel Land .. holy shit thats psychadelic
|
||||
Mega Turrican some gfx glitches
|
||||
Mortal Kombat...
|
||||
MUSHA: Intro music starts too soon. Suffers from lack of 2-cell-vertical-scroll mode.
|
||||
Out of This World... pretty substantially broken
|
||||
Outrun 2019, much more significant gfx issues.
|
||||
Outrun, minor gfx issue. maybe related to h-int timing?
|
||||
Outrunners, some gfx issues... but not as bad as you might think!!! apparently doesnt use interlace mode?
|
||||
Panorama Cotton still not working right
|
||||
Power Monger messed up
|
||||
RamboIII - intro gfx corrupted - MAYBE GOOD DEBUGGING TARGET
|
||||
Shining in the Darkness: Check out sprites in the tavern... very odd
|
||||
Sonic 2: Aside from lack of interlace mode, the shadows in the special stage are white....?
|
||||
Sonic 2: Something still amiss with title screen sprite masking
|
||||
Sonic 3 serious gfx glitches
|
||||
Star Control - Shit gets crazy
|
||||
Steel Empire - controls messed up. probably gfx issues also.
|
||||
Sub-Terrania some gfx issues in intro
|
||||
Super Hang-On - Sprite priority/masking isnt happening the way its supposed to on the tracks.
|
||||
Super Fantasy Zone: Sound totally boned, missing graphics
|
||||
TaleSpin - gfx glitches
|
||||
The Humans
|
||||
The Immortal
|
||||
Truxton - Sound is jaaaacked.
|
||||
Verytex - gfx issues
|
||||
Zero Tolerance - gfx bugs that didnt used to happen :(
|
||||
Zombies At My Neighbors: doesnt boot really
|
||||
Zoop doesnt boot
|
||||
|
||||
|
||||
======================================================
|
||||
Fixed Issues: (listed for regression testing purposes)
|
||||
======================================================
|
||||
|
||||
(Sprites X/Y are 10-bit, but must be masked to 9-bit)
|
||||
- Dragon's Revenge.... ball sprite seems missing? of all the sprites to not show up...
|
||||
- Fire Shark - Sprites dont render... VERY similar to Truxton. Same engine?
|
||||
- Truxton - Sprites do not appear to be rendering.
|
||||
- Zero Wing - Sprites arent rendering.......
|
||||
|
||||
Flavio's DMA test... DMAs when it shouldnt!! - Masking off too much of the VDP command code
|
||||
|
||||
Fun-n-Games fails its fadeouts. -- Fixed CRAM reads. I failed math.
|
||||
|
||||
Sonic Spinball executes a VSRAM read -- Implemented VSRAM reads.
|
||||
|
||||
Bugs related to longword read/write VRAM. Multiple bugs were present including sign-extension and endians and crap.
|
||||
- Crusader of Centy- Text boxes messed up
|
||||
- Eternal Champions - immediate gfx corruption
|
||||
- Garfield... immediate gfx corruption. debug target.
|
||||
- Kid Chameleon - gfx corruption on bounce bricks and level-end effect
|
||||
|
||||
Games to test window calculation on:
|
||||
- Road Blasters (doesnt use window, but Window & Nametable A are at same location)
|
||||
- Out of this World - Same as Road Blasters
|
||||
- Musha,Gaiares - Window at top
|
||||
- Eliminate Down - Window at bottom
|
||||
- Monster World 4, Wonder Boy in Monster World - Window at top - needs window scroll plane size adjustment
|
||||
- D&D Warriors of the Eternal Sun - Window at bottom and at sides
|
||||
- Truxton, Fire Shark - Window on Right
|
||||
|
||||
======================================================
|
||||
|
||||
TODO: non-instant DMA emulation
|
||||
TODO: Add 68000/VDP interrupt enable delay (one instruction, similar to After Burner/PCE)
|
||||
TODO: H-Ints timing possibly not correct... Some game raster effects work, others don't work as expected. (could be HVC tho)
|
||||
TODO: Test DMA/ VDP command words.... I'm not at all convinced that VRAM is always correct
|
||||
|
||||
|
||||
==============
|
||||
Notable games:
|
||||
==============
|
||||
|
||||
Ghouls n Ghosts sets up the graphics planes backwards from normal, by setting the plane A to be low priority and Plane B to be high priority.
|
||||
If you have a bug in your priority code this may find it.
|
||||
|
||||
Revenge of Shinobi will not play DAC sounds if YM2612 registers are not initialized to L/R channels enabled.
|
||||
|
||||
Ballz doesnt really initialize hardly any VDP registers, relies on VDP registers powered-on to the correct values
|
||||
|
||||
Contra appears to use VDP A0 set = byte-swap. Not sure if its important in anyway in that game, but the byte swap happens.
|
||||
|
||||
Games that use VRAM->VRAM Copy: D&D Warriors of the Eternal Sun, MUSHA, Devilish, Viewpoint
|
||||
|
||||
Games that require accurate VRAM fill emulation include Thunder Force IV,
|
||||
Contra Hard Corps, Revenge of Shinobi, Taiga Drama, and Sword of Vermillion.
|
||||
|
||||
Sonic Spinball reads from VSRAM
|
||||
|
||||
Games to test for sprite masking/overflow:
|
||||
- Sonic 1 title screen (uses overflow to mask)
|
||||
- Sonic 2 title screen (uses sprite mask modes)
|
||||
- Galaxy Force 2 level select (uses sprite mask)
|
||||
- Landstalker sprite masking (chests and such)
|
||||
- Nemesis test rom
|
||||
|
||||
Games known to use 2-cell vertical scroll mode:
|
||||
- Air Diver
|
||||
- Exo Squad
|
||||
- Contra Hard Corps (giant robot boss on stage 1)
|
||||
- MUSHA (stage 3)
|
|
@ -1,112 +0,0 @@
|
|||
using System;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
public partial class GenVDP
|
||||
{
|
||||
// TODO: make this a requirement of constructor?
|
||||
public Func<int, short> DmaReadFrom68000; // TODO make ushort
|
||||
|
||||
public int DmaLength { get { return Registers[19] | (Registers[20] << 8); } }
|
||||
public int DmaMode { get { return (Registers[23] >> 6) & 3; } }
|
||||
|
||||
public int DmaSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((Registers[23] & 0x80) == 0) // 68000 -> VRAM copy mode
|
||||
return ((Registers[21] << 1) | (Registers[22] << 9) | (Registers[23] << 17)) & 0xFFFFFE;
|
||||
|
||||
// Else VRAM/VRAM copy mode
|
||||
return (Registers[21] | (Registers[22] << 8)) & 0xFFFFFE;
|
||||
}
|
||||
}
|
||||
|
||||
bool DmaFillModePending;
|
||||
|
||||
void ExecuteVramFill(ushort data)
|
||||
{
|
||||
if (data != 0)
|
||||
Console.WriteLine("fill word is not zero {0:X4}", data);
|
||||
|
||||
Log.Note("VDP", "DMA FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr);
|
||||
|
||||
// TODO: It should spread this out, not do it all at once.
|
||||
|
||||
int length = DmaLength;
|
||||
if (length == 0)
|
||||
length = 0x10000;
|
||||
|
||||
byte fillByte = (byte)(data >> 8);
|
||||
|
||||
do
|
||||
{
|
||||
VRAM[VdpDataAddr] = fillByte;
|
||||
Log.Note("VDP", "VRAM DMA FILL Write: [{0:X4}] = {1:X2}", VdpDataAddr, fillByte);
|
||||
UpdatePatternBuffer(VdpDataAddr);
|
||||
VdpDataAddr += Registers[15];
|
||||
} while (--length > 0);
|
||||
|
||||
// TOOD: test if the length register updated? One would assume so...
|
||||
Registers[19] = 0;
|
||||
Registers[20] = 0;
|
||||
|
||||
// TODO: Source registers should be incremented also (even for Fill)
|
||||
|
||||
DmaFillModePending = false;
|
||||
}
|
||||
|
||||
void Execute68000VramCopy()
|
||||
{
|
||||
Log.Note("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
|
||||
|
||||
int length = DmaLength;
|
||||
if (length == 0)
|
||||
length = 0x10000;
|
||||
|
||||
int source = DmaSource;
|
||||
|
||||
do
|
||||
{
|
||||
ushort value = (ushort)DmaReadFrom68000(source);
|
||||
source += 2;
|
||||
// TODO funky source behavior
|
||||
WriteVdpData(value);
|
||||
} while (--length > 0);
|
||||
|
||||
Registers[19] = 0;
|
||||
Registers[20] = 0;
|
||||
|
||||
// TODO: update DMA source registers.
|
||||
// TODO: find correct number of 68k cycles to burn
|
||||
}
|
||||
|
||||
void ExecuteVramVramCopy()
|
||||
{
|
||||
Log.Note("VDP", "DMA VRAM -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
|
||||
|
||||
int length = DmaLength;
|
||||
if (length == 0)
|
||||
length = 0x10000;
|
||||
|
||||
int source = DmaSource;
|
||||
|
||||
do
|
||||
{
|
||||
byte data = VRAM[source];
|
||||
VRAM[VdpDataAddr] = data;
|
||||
UpdatePatternBuffer(VdpDataAddr);
|
||||
Log.Note("VDP", "VRAM/VRAM Copy VRAM[{0:X4}] = {1:X2}", VdpDataAddr, data);
|
||||
source = (source + 1) & 0xFFFF;
|
||||
VdpDataAddr += Registers[0xF];
|
||||
} while (--length > 0);
|
||||
|
||||
Registers[19] = 0;
|
||||
Registers[20] = 0;
|
||||
|
||||
// TODO: length, source registers should be updated....
|
||||
// TODO: find correct number of 68k cycles to burn
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,525 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
public partial class GenVDP
|
||||
{
|
||||
// Priority buffer contents have the following values:
|
||||
// 0 = Backdrop color
|
||||
// 1 = Plane B Low Priority
|
||||
// 2 = Plane A Low Priority
|
||||
// 4 = Plane B High Priority
|
||||
// 5 = Plane A High Priority
|
||||
// 9 = Sprite has been drawn
|
||||
|
||||
byte[] PriorityBuffer = new byte[320];
|
||||
|
||||
static readonly byte[] PalXlatTable = { 0, 0, 36, 36, 73, 73, 109, 109, 145, 145, 182, 182, 219, 219, 255, 255 };
|
||||
|
||||
public void RenderLine()
|
||||
{
|
||||
if (DisplayEnabled)
|
||||
{
|
||||
Array.Clear(PriorityBuffer, 0, 320);
|
||||
|
||||
// TODO: I would like to be able to render Scroll A before Scroll B, in order to minimize overdraw.
|
||||
// But at the moment it complicates priority stuff.
|
||||
|
||||
if (CellBasedVertScroll == false)
|
||||
{
|
||||
RenderScrollB();
|
||||
RenderScrollA();
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderScrollBTwoCellVScroll();
|
||||
RenderScrollATwoCellVScroll();
|
||||
}
|
||||
|
||||
RenderSpritesScanline();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If display is disabled, fill in with background color.
|
||||
for (int i = 0; i < FrameWidth; i++)
|
||||
FrameBuffer[(ScanLine * FrameWidth) + i] = BackgroundColor;
|
||||
}
|
||||
|
||||
//if (ScanLine == 223) // shrug
|
||||
// RenderPalette();
|
||||
}
|
||||
|
||||
void RenderPalette()
|
||||
{
|
||||
for (int p = 0; p < 4; p++)
|
||||
for (int i = 0; i < 16; i++)
|
||||
FrameBuffer[(p * FrameWidth) + i] = Palette[(p * 16) + i];
|
||||
}
|
||||
|
||||
void RenderScrollAScanline(int xScroll, int yScroll, int nameTableBase, int startPixel, int endPixel, bool window)
|
||||
{
|
||||
const int lowPriority = 2;
|
||||
const int highPriority = 5;
|
||||
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
|
||||
int nameTableWidth = NameTableWidth;
|
||||
if (window)
|
||||
nameTableWidth = (DisplayWidth == 40) ? 64 : 32;
|
||||
|
||||
// this is hellllla slow. but not optimizing until we implement & understand
|
||||
// all scrolling modes, shadow & hilight, etc.
|
||||
// in thinking about this, you could convince me to optimize the PCE background renderer now.
|
||||
// Its way simple in comparison. But the PCE sprite renderer is way worse than gen.
|
||||
for (int x = startPixel; x < endPixel; x++)
|
||||
{
|
||||
int xTile = Math.Abs(((x + (1024 - xScroll)) / 8) % nameTableWidth);
|
||||
int xOfs = Math.Abs((x + (1024 - xScroll)) & 7);
|
||||
int yOfs = (ScanLine + yScroll) % 8;
|
||||
int cellOfs = nameTableBase + (yTile * nameTableWidth * 2) + (xTile * 2);
|
||||
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs + 1] << 8);
|
||||
int patternNo = nameTableEntry & 0x7FF;
|
||||
bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
|
||||
bool vFlip = ((nameTableEntry >> 12) & 1) != 0;
|
||||
bool priority = ((nameTableEntry >> 15) & 1) != 0;
|
||||
int palette = (nameTableEntry >> 13) & 3;
|
||||
|
||||
if (priority && PriorityBuffer[x] >= highPriority) continue;
|
||||
if (!priority && PriorityBuffer[x] >= lowPriority) continue;
|
||||
|
||||
if (vFlip) yOfs = 7 - yOfs;
|
||||
if (hFlip) xOfs = 7 - xOfs;
|
||||
|
||||
int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)];
|
||||
if (texel == 0) continue;
|
||||
int pixel = Palette[(palette * 16) + texel];
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = pixel;
|
||||
PriorityBuffer[x] = (byte)(priority ? highPriority : lowPriority);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderScrollAScanlineTwoCellVScroll(int xScroll, int nameTableBase, int startPixel, int endPixel, bool window)
|
||||
{
|
||||
const int lowPriority = 2;
|
||||
const int highPriority = 5;
|
||||
|
||||
int fineHScroll = xScroll & 15;
|
||||
int nameTableWidth = NameTableWidth;
|
||||
if (window)
|
||||
nameTableWidth = (DisplayWidth == 40) ? 64 : 32;
|
||||
|
||||
for (int x = startPixel; x < endPixel; x++)
|
||||
{
|
||||
int vsramUnitOffset = ((x - fineHScroll) / 16) % 40;
|
||||
int yScroll = VSRAM[vsramUnitOffset * 2] & 0x3FF;
|
||||
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
|
||||
|
||||
int xTile = Math.Abs(((x + (1024 - xScroll)) / 8) % nameTableWidth);
|
||||
int xOfs = Math.Abs((x + (1024 - xScroll)) & 7);
|
||||
int yOfs = (ScanLine + yScroll) % 8;
|
||||
int cellOfs = nameTableBase + (yTile * nameTableWidth * 2) + (xTile * 2);
|
||||
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs + 1] << 8);
|
||||
int patternNo = nameTableEntry & 0x7FF;
|
||||
bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
|
||||
bool vFlip = ((nameTableEntry >> 12) & 1) != 0;
|
||||
bool priority = ((nameTableEntry >> 15) & 1) != 0;
|
||||
int palette = (nameTableEntry >> 13) & 3;
|
||||
|
||||
if (priority && PriorityBuffer[x] >= highPriority) continue;
|
||||
if (!priority && PriorityBuffer[x] >= lowPriority) continue;
|
||||
|
||||
if (vFlip) yOfs = 7 - yOfs;
|
||||
if (hFlip) xOfs = 7 - xOfs;
|
||||
|
||||
int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)];
|
||||
if (texel == 0) continue;
|
||||
int pixel = Palette[(palette * 16) + texel];
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = pixel;
|
||||
PriorityBuffer[x] = (byte)(priority ? highPriority : lowPriority);
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateWindowScanlines(out int startScanline, out int endScanline)
|
||||
{
|
||||
int data = Registers[0x12];
|
||||
int windowVPosition = data & 31;
|
||||
bool fromTop = (data & 0x80) == 0;
|
||||
|
||||
if (windowVPosition == 0)
|
||||
{
|
||||
startScanline = -1;
|
||||
endScanline = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fromTop)
|
||||
{
|
||||
startScanline = 0;
|
||||
endScanline = (windowVPosition * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
startScanline = windowVPosition * 8;
|
||||
endScanline = FrameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateWindowPosition(out int startPixel, out int endPixel)
|
||||
{
|
||||
int data = Registers[0x11];
|
||||
int windowHPosition = (data & 31) * 2; // Window H position is set in 2-cell increments
|
||||
bool fromLeft = (data & 0x80) == 0;
|
||||
|
||||
if (windowHPosition == 0)
|
||||
{
|
||||
startPixel = -1;
|
||||
endPixel = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fromLeft)
|
||||
{
|
||||
startPixel = 0;
|
||||
endPixel = (windowHPosition * 8);
|
||||
if (endPixel > FrameWidth)
|
||||
endPixel = FrameWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPixel = windowHPosition * 8;
|
||||
endPixel = FrameWidth;
|
||||
if (startPixel > FrameWidth)
|
||||
{
|
||||
startPixel = -1;
|
||||
endPixel = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderScrollA()
|
||||
{
|
||||
// Calculate scroll offsets
|
||||
|
||||
int hscroll = CalcHScrollPlaneA(ScanLine);
|
||||
int vscroll = VSRAM[0] & 0x3FF;
|
||||
|
||||
// Calculate window dimensions
|
||||
|
||||
int startWindowScanline, endWindowScanline;
|
||||
int startWindowPixel, endWindowPixel;
|
||||
CalculateWindowScanlines(out startWindowScanline, out endWindowScanline);
|
||||
CalculateWindowPosition(out startWindowPixel, out endWindowPixel);
|
||||
|
||||
// Render scanline
|
||||
|
||||
if (ScanLine >= startWindowScanline && ScanLine < endWindowScanline) // Window takes up whole scanline
|
||||
{
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, FrameWidth, true);
|
||||
}
|
||||
else if (startWindowPixel != -1) // Window takes up partial scanline
|
||||
{
|
||||
if (startWindowPixel == 0) // Window grows from left side
|
||||
{
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, endWindowPixel, true);
|
||||
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, endWindowPixel, FrameWidth, false);
|
||||
}
|
||||
else // Window grows from right side
|
||||
{
|
||||
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, 0, startWindowPixel, false);
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, startWindowPixel, FrameWidth, true);
|
||||
}
|
||||
}
|
||||
else // No window this scanline
|
||||
{
|
||||
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, 0, FrameWidth, false);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderScrollATwoCellVScroll()
|
||||
{
|
||||
// Calculate scroll offsets
|
||||
|
||||
int hscroll = CalcHScrollPlaneA(ScanLine);
|
||||
|
||||
// Calculate window dimensions
|
||||
|
||||
int startWindowScanline, endWindowScanline;
|
||||
int startWindowPixel, endWindowPixel;
|
||||
CalculateWindowScanlines(out startWindowScanline, out endWindowScanline);
|
||||
CalculateWindowPosition(out startWindowPixel, out endWindowPixel);
|
||||
|
||||
// Render scanline
|
||||
|
||||
if (ScanLine >= startWindowScanline && ScanLine < endWindowScanline) // Window takes up whole scanline
|
||||
{
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, FrameWidth, true);
|
||||
}
|
||||
else if (startWindowPixel != -1) // Window takes up partial scanline
|
||||
{
|
||||
if (startWindowPixel == 0) // Window grows from left side
|
||||
{
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, endWindowPixel, true);
|
||||
RenderScrollAScanlineTwoCellVScroll(hscroll, NameTableAddrA, endWindowPixel, FrameWidth, false);
|
||||
}
|
||||
else // Window grows from right side
|
||||
{
|
||||
RenderScrollAScanlineTwoCellVScroll(hscroll, NameTableAddrA, 0, startWindowPixel, false);
|
||||
RenderScrollAScanline(0, 0, NameTableAddrWindow, startWindowPixel, FrameWidth, true);
|
||||
}
|
||||
}
|
||||
else // No window this scanline
|
||||
{
|
||||
RenderScrollAScanlineTwoCellVScroll(hscroll, NameTableAddrA, 0, FrameWidth, false);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderScrollB()
|
||||
{
|
||||
int bgColor = BackgroundColor;
|
||||
int xScroll = CalcHScrollPlaneB(ScanLine);
|
||||
int yScroll = VSRAM[1] & 0x3FF;
|
||||
|
||||
const int lowPriority = 1;
|
||||
const int highPriority = 4;
|
||||
|
||||
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
|
||||
|
||||
// this is hellllla slow. but not optimizing until we implement & understand
|
||||
// all scrolling modes, shadow & hilight, etc.
|
||||
// in thinking about this, you could convince me to optimize the PCE background renderer now.
|
||||
// Its way simple in comparison. But the PCE sprite renderer is way worse than gen.
|
||||
for (int x = 0; x < FrameWidth; x++)
|
||||
{
|
||||
int xTile = Math.Abs(((x + (1024 - xScroll)) / 8) % NameTableWidth);
|
||||
int xOfs = Math.Abs((x + (1024 - xScroll)) & 7);
|
||||
int yOfs = (ScanLine + yScroll) % 8;
|
||||
int cellOfs = NameTableAddrB + (yTile * NameTableWidth * 2) + (xTile * 2);
|
||||
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs + 1] << 8);
|
||||
int patternNo = nameTableEntry & 0x7FF;
|
||||
bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
|
||||
bool vFlip = ((nameTableEntry >> 12) & 1) != 0;
|
||||
bool priority = ((nameTableEntry >> 15) & 1) != 0;
|
||||
int palette = (nameTableEntry >> 13) & 3;
|
||||
|
||||
if (priority && PriorityBuffer[x] >= highPriority) continue;
|
||||
if (!priority && PriorityBuffer[x] >= lowPriority) continue;
|
||||
|
||||
if (vFlip) yOfs = 7 - yOfs;
|
||||
if (hFlip) xOfs = 7 - xOfs;
|
||||
|
||||
int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)];
|
||||
int pixel = Palette[(palette * 16) + texel];
|
||||
if (texel != 0)
|
||||
{
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = pixel;
|
||||
PriorityBuffer[x] = (byte)(priority ? highPriority : lowPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = bgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderScrollBTwoCellVScroll()
|
||||
{
|
||||
int bgColor = BackgroundColor;
|
||||
int xScroll = CalcHScrollPlaneB(ScanLine);
|
||||
int fineHScroll = xScroll & 15;
|
||||
|
||||
const int lowPriority = 1;
|
||||
const int highPriority = 4;
|
||||
|
||||
for (int x = 0; x < FrameWidth; x++)
|
||||
{
|
||||
int vsramUnitOffset = ((x - fineHScroll) / 16) % 40;
|
||||
int yScroll = VSRAM[(vsramUnitOffset * 2) + 1] & 0x3FF;
|
||||
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
|
||||
|
||||
int xTile = Math.Abs(((x + (1024 - xScroll)) / 8) % NameTableWidth);
|
||||
int xOfs = Math.Abs((x + (1024 - xScroll)) & 7);
|
||||
int yOfs = (ScanLine + yScroll) % 8;
|
||||
int cellOfs = NameTableAddrB + (yTile * NameTableWidth * 2) + (xTile * 2);
|
||||
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs + 1] << 8);
|
||||
int patternNo = nameTableEntry & 0x7FF;
|
||||
bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
|
||||
bool vFlip = ((nameTableEntry >> 12) & 1) != 0;
|
||||
bool priority = ((nameTableEntry >> 15) & 1) != 0;
|
||||
int palette = (nameTableEntry >> 13) & 3;
|
||||
|
||||
if (priority && PriorityBuffer[x] >= highPriority) continue;
|
||||
if (!priority && PriorityBuffer[x] >= lowPriority) continue;
|
||||
|
||||
if (vFlip) yOfs = 7 - yOfs;
|
||||
if (hFlip) xOfs = 7 - xOfs;
|
||||
|
||||
int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)];
|
||||
int pixel = Palette[(palette * 16) + texel];
|
||||
if (texel != 0)
|
||||
{
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = pixel;
|
||||
PriorityBuffer[x] = (byte)(priority ? highPriority : lowPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
FrameBuffer[(ScanLine * FrameWidth) + x] = bgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly int[] SpriteSizeTable = { 8, 16, 24, 32 };
|
||||
Sprite sprite;
|
||||
|
||||
void RenderSpritesScanline()
|
||||
{
|
||||
int scanLineBase = ScanLine * FrameWidth;
|
||||
int processedSprites = 0;
|
||||
int processedSpritesThisLine = 0;
|
||||
int processedDotsThisLine = 0;
|
||||
bool spriteMaskPrecursor = false;
|
||||
|
||||
// This is incredibly unoptimized. TODO...
|
||||
|
||||
FetchSprite(0);
|
||||
while (true)
|
||||
{
|
||||
if (sprite.Y > ScanLine || sprite.Y + sprite.HeightPixels <= ScanLine)
|
||||
goto nextSprite;
|
||||
|
||||
processedSpritesThisLine++;
|
||||
processedDotsThisLine += sprite.WidthPixels;
|
||||
|
||||
if (sprite.X > -128)
|
||||
spriteMaskPrecursor = true;
|
||||
|
||||
if (sprite.X == -128 && spriteMaskPrecursor)
|
||||
break; // apply sprite mask
|
||||
|
||||
if (sprite.X + sprite.WidthPixels <= 0)
|
||||
goto nextSprite;
|
||||
|
||||
if (sprite.HeightCells == 2)
|
||||
sprite.HeightCells = 2;
|
||||
|
||||
int yline = ScanLine - sprite.Y;
|
||||
if (sprite.VFlip)
|
||||
yline = sprite.HeightPixels - 1 - yline;
|
||||
int paletteBase = sprite.Palette * 16;
|
||||
if (sprite.HFlip == false)
|
||||
{
|
||||
int pattern = sprite.PatternIndex + ((yline / 8));
|
||||
|
||||
for (int xi = 0; xi < sprite.WidthPixels; xi++)
|
||||
{
|
||||
if (sprite.X + xi < 0 || sprite.X + xi >= FrameWidth)
|
||||
continue;
|
||||
|
||||
if (sprite.Priority == false && PriorityBuffer[sprite.X + xi] >= 3) continue;
|
||||
if (PriorityBuffer[sprite.X + xi] == 9) continue;
|
||||
|
||||
int pixel = PatternBuffer[((pattern + ((xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (xi & 7)];
|
||||
if (pixel != 0)
|
||||
{
|
||||
FrameBuffer[scanLineBase + sprite.X + xi] = Palette[paletteBase + pixel];
|
||||
PriorityBuffer[sprite.X + xi] = 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // HFlip
|
||||
int pattern = sprite.PatternIndex + ((yline / 8)) + (sprite.HeightCells * (sprite.WidthCells - 1));
|
||||
|
||||
for (int xi = 0; xi < sprite.WidthPixels; xi++)
|
||||
{
|
||||
if (sprite.X + xi < 0 || sprite.X + xi >= FrameWidth)
|
||||
continue;
|
||||
|
||||
if (sprite.Priority == false && PriorityBuffer[sprite.X + xi] >= 3) continue;
|
||||
if (PriorityBuffer[sprite.X + xi] == 9) continue;
|
||||
|
||||
int pixel = PatternBuffer[((pattern + ((-xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (7 - (xi & 7))];
|
||||
if (pixel != 0)
|
||||
{
|
||||
FrameBuffer[scanLineBase + sprite.X + xi] = Palette[paletteBase + pixel];
|
||||
PriorityBuffer[sprite.X + xi] = 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextSprite:
|
||||
if (sprite.Link == 0)
|
||||
break;
|
||||
if (++processedSprites >= SpriteLimit)
|
||||
break;
|
||||
if (processedSpritesThisLine >= SpritePerLineLimit)
|
||||
break;
|
||||
if (processedDotsThisLine >= DotsPerLineLimit)
|
||||
break;
|
||||
if (DisplayWidth == 32 && sprite.Link >= 64)
|
||||
break;
|
||||
FetchSprite(sprite.Link);
|
||||
}
|
||||
}
|
||||
|
||||
void FetchSprite(int spriteNo)
|
||||
{
|
||||
// Note - X/Y coordinates are 10-bits (3FF) but must be masked to 9-bits (1FF)
|
||||
// In interlace mode this behavior should change
|
||||
|
||||
int SatBase = SpriteAttributeTableAddr + (spriteNo * 8);
|
||||
sprite.Y = (VRAM[SatBase + 0] | (VRAM[SatBase + 1] << 8) & 0x1FF) - 128;
|
||||
sprite.X = (VRAM[SatBase + 6] | (VRAM[SatBase + 7] << 8) & 0x1FF) - 128;
|
||||
sprite.WidthPixels = SpriteSizeTable[(VRAM[SatBase + 3] >> 2) & 3];
|
||||
sprite.HeightPixels = SpriteSizeTable[VRAM[SatBase + 3] & 3];
|
||||
sprite.WidthCells = ((VRAM[SatBase + 3] >> 2) & 3) + 1;
|
||||
sprite.HeightCells = (VRAM[SatBase + 3] & 3) + 1;
|
||||
sprite.Link = VRAM[SatBase + 2] & 0x7F;
|
||||
sprite.PatternIndex = (VRAM[SatBase + 4] | (VRAM[SatBase + 5] << 8)) & 0x7FF;
|
||||
sprite.HFlip = ((VRAM[SatBase + 5] >> 3) & 1) != 0;
|
||||
sprite.VFlip = ((VRAM[SatBase + 5] >> 4) & 1) != 0;
|
||||
sprite.Palette = (VRAM[SatBase + 5] >> 5) & 3;
|
||||
sprite.Priority = ((VRAM[SatBase + 5] >> 7) & 1) != 0;
|
||||
}
|
||||
|
||||
struct Sprite
|
||||
{
|
||||
public int X, Y;
|
||||
public int WidthPixels, HeightPixels;
|
||||
public int WidthCells, HeightCells;
|
||||
public int Link;
|
||||
public int Palette;
|
||||
public int PatternIndex;
|
||||
public bool Priority;
|
||||
public bool HFlip;
|
||||
public bool VFlip;
|
||||
}
|
||||
|
||||
int CalcHScrollPlaneA(int line)
|
||||
{
|
||||
int ofs = 0;
|
||||
switch (Registers[11] & 3)
|
||||
{
|
||||
case 0: ofs = HScrollTableAddr; break;
|
||||
case 1: ofs = HScrollTableAddr + ((line & 7) * 4); break;
|
||||
case 2: ofs = HScrollTableAddr + ((line & ~7) * 4); break;
|
||||
case 3: ofs = HScrollTableAddr + (line * 4); break;
|
||||
}
|
||||
|
||||
int value = VRAM[ofs] | (VRAM[ofs + 1] << 8);
|
||||
return value & 0x3FF;
|
||||
}
|
||||
|
||||
int CalcHScrollPlaneB(int line)
|
||||
{
|
||||
int ofs = 0;
|
||||
switch (Registers[11] & 3)
|
||||
{
|
||||
case 0: ofs = HScrollTableAddr; break;
|
||||
case 1: ofs = HScrollTableAddr + ((line & 7) * 4); break;
|
||||
case 2: ofs = HScrollTableAddr + ((line & ~7) * 4); break;
|
||||
case 3: ofs = HScrollTableAddr + (line * 4); break;
|
||||
}
|
||||
|
||||
int value = VRAM[ofs + 2] | (VRAM[ofs + 3] << 8);
|
||||
return value & 0x3FF;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,572 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
using BizHawk.Common.IOExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
public sealed partial class GenVDP : IVideoProvider
|
||||
{
|
||||
// Memory
|
||||
public byte[] VRAM = new byte[0x10000];
|
||||
public ushort[] CRAM = new ushort[64];
|
||||
public ushort[] VSRAM = new ushort[40];
|
||||
public byte[] Registers = new byte[0x20];
|
||||
|
||||
public byte[] PatternBuffer = new byte[0x20000];
|
||||
public int[] Palette = new int[64];
|
||||
public int[] FrameBuffer = new int[320 * 224];
|
||||
public int FrameWidth = 320;
|
||||
public int FrameHeight = 224;
|
||||
|
||||
public int ScanLine;
|
||||
public int HIntLineCounter;
|
||||
|
||||
public bool HInterruptsEnabled { get { return (Registers[0] & 0x10) != 0; } }
|
||||
public bool DisplayEnabled { get { return (Registers[1] & 0x40) != 0; } }
|
||||
public bool VInterruptEnabled { get { return (Registers[1] & 0x20) != 0; } }
|
||||
public bool DmaEnabled { get { return (Registers[1] & 0x10) != 0; } }
|
||||
public bool CellBasedVertScroll { get { return (Registers[11] & 0x04) != 0; } }
|
||||
|
||||
public bool InDisplayPeriod { get { return ScanLine < 224 && DisplayEnabled; } }
|
||||
|
||||
ushort NameTableAddrA;
|
||||
ushort NameTableAddrB;
|
||||
ushort NameTableAddrWindow;
|
||||
ushort SpriteAttributeTableAddr;
|
||||
ushort HScrollTableAddr;
|
||||
int NameTableWidth = 32;
|
||||
int NameTableHeight = 32;
|
||||
|
||||
int DisplayWidth;
|
||||
int SpriteLimit;
|
||||
int SpritePerLineLimit;
|
||||
int DotsPerLineLimit;
|
||||
|
||||
bool ControlWordPending;
|
||||
ushort VdpDataAddr;
|
||||
byte VdpDataCode;
|
||||
|
||||
const int CommandVramRead = 0;
|
||||
const int CommandVramWrite = 1;
|
||||
const int CommandCramWrite = 3;
|
||||
const int CommandVsramRead = 4;
|
||||
const int CommandVsramWrite = 5;
|
||||
const int CommandCramRead = 8;
|
||||
|
||||
public ushort VdpStatusWord = 0x3400;
|
||||
public const int StatusHorizBlanking = 0x04;
|
||||
public const int StatusVerticalBlanking = 0x08;
|
||||
public const int StatusOddFrame = 0x10;
|
||||
public const int StatusSpriteCollision = 0x20;
|
||||
public const int StatusSpriteOverflow = 0x40;
|
||||
public const int StatusVerticalInterruptPending = 0x80;
|
||||
|
||||
public bool VdpDebug = false;
|
||||
|
||||
public Func<int> GetPC;
|
||||
|
||||
public GenVDP()
|
||||
{
|
||||
WriteVdpRegister(00, 0x04);
|
||||
WriteVdpRegister(01, 0x04);
|
||||
WriteVdpRegister(02, 0x30);
|
||||
WriteVdpRegister(03, 0x3C);
|
||||
WriteVdpRegister(04, 0x07);
|
||||
WriteVdpRegister(05, 0x67);
|
||||
WriteVdpRegister(10, 0xFF);
|
||||
WriteVdpRegister(12, 0x81);
|
||||
WriteVdpRegister(15, 0x02);
|
||||
Log.Note("VDP", "VDP init routine complete");
|
||||
}
|
||||
|
||||
public ushort ReadVdp(int addr)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
return ReadVdpData();
|
||||
case 4:
|
||||
case 6:
|
||||
return ReadVdpControl();
|
||||
default:
|
||||
return ReadHVCounter();
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteVdp(int addr, ushort data)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
WriteVdpData(data);
|
||||
return;
|
||||
case 4:
|
||||
case 6:
|
||||
WriteVdpControl(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteVdpControl(ushort data)
|
||||
{
|
||||
Log.Note("VDP", "Control Write {0:X4} (PC={1:X6})", data, GetPC());
|
||||
|
||||
if (ControlWordPending == false)
|
||||
{
|
||||
if ((data & 0xC000) == 0x8000)
|
||||
{
|
||||
int reg = (data >> 8) & 0x1F;
|
||||
byte value = (byte)(data & 0xFF);
|
||||
WriteVdpRegister(reg, value);
|
||||
VdpDataCode = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlWordPending = true;
|
||||
VdpDataAddr &= 0xC000;
|
||||
VdpDataAddr |= (ushort)(data & 0x3FFF);
|
||||
VdpDataCode &= 0x3C;
|
||||
VdpDataCode |= (byte)(data >> 14);
|
||||
//Console.WriteLine("Address = {0:X4}", VdpDataAddr);
|
||||
//Console.WriteLine("Code = {0:X2}", VdpDataCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlWordPending = false;
|
||||
|
||||
// Update data address and code
|
||||
VdpDataAddr &= 0x3FFF;
|
||||
VdpDataAddr |= (ushort)((data & 0x03) << 14);
|
||||
//Console.WriteLine("Address = {0:X4}", VdpDataAddr);
|
||||
VdpDataCode &= 0x03;
|
||||
VdpDataCode |= (byte)((data >> 2) & 0x3C);
|
||||
//Log.Note("VDP", "Code = {0:X2}", VdpDataCode);
|
||||
|
||||
if ((VdpDataCode & 0x20) != 0 && DmaEnabled) // DMA triggered
|
||||
{
|
||||
//Console.WriteLine("DMA TIME!");
|
||||
|
||||
// what type of DMA?
|
||||
switch (Registers[23] >> 6)
|
||||
{
|
||||
case 2:
|
||||
Log.Note("VDP", "VRAM FILL");
|
||||
DmaFillModePending = true;
|
||||
break;
|
||||
case 3:
|
||||
Log.Error("VDP", "VRAM COPY *");
|
||||
ExecuteVramVramCopy();
|
||||
break;
|
||||
default:
|
||||
Execute68000VramCopy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ushort ReadVdpControl()
|
||||
{
|
||||
VdpStatusWord |= 0x0200; // Fifo empty // TODO kill this, emulating the damn FIFO.
|
||||
ControlWordPending = false; // Hmm.. if this happens in an interrupt between 1st and 2nd word..
|
||||
|
||||
// sprite overflow flag should clear.
|
||||
// sprite collision flag should clear.
|
||||
|
||||
return VdpStatusWord;
|
||||
}
|
||||
|
||||
public void WriteVdpData(ushort data)
|
||||
{
|
||||
Log.Note("VDP", "Data port write: {0:X4} (PC={1:X6})", data, GetPC());
|
||||
ControlWordPending = false;
|
||||
|
||||
// byte-swap incoming data when A0 is set
|
||||
if ((VdpDataAddr & 1) != 0)
|
||||
{
|
||||
data = (ushort)((data >> 8) | (data << 8));
|
||||
Log.Error("VDP", "VRAM byte-swap is happening because A0 is not 0. [{0:X4}] = {1:X4}", VdpDataAddr, data);
|
||||
}
|
||||
|
||||
switch (VdpDataCode & 0xF)
|
||||
{
|
||||
case CommandVramWrite: // VRAM Write
|
||||
VRAM[VdpDataAddr & 0xFFFE] = (byte)data;
|
||||
VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte)(data >> 8);
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
|
||||
UpdatePatternBuffer(VdpDataAddr & 0xFFFE);
|
||||
UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
case CommandCramWrite: // CRAM write
|
||||
CRAM[(VdpDataAddr / 2) % 64] = data;
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data);
|
||||
ProcessPalette((VdpDataAddr / 2) % 64);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
case CommandVsramWrite: // VSRAM write
|
||||
VSRAM[(VdpDataAddr / 2) % 40] = data;
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
default:
|
||||
Log.Error("VPD", "VDP DATA WRITE WITH UNHANDLED CODE!!! {0}", VdpDataCode & 7);
|
||||
break;
|
||||
}
|
||||
|
||||
if (DmaFillModePending)
|
||||
{
|
||||
ExecuteVramFill(data);
|
||||
}
|
||||
}
|
||||
|
||||
public ushort ReadVdpData()
|
||||
{
|
||||
int orig_addr = VdpDataAddr;
|
||||
ushort retval = 0xBEEF;
|
||||
switch (VdpDataCode & 0x0F)
|
||||
{
|
||||
case CommandVramRead:
|
||||
//if ((VdpDataAddr & 1) != 0) throw new Exception("VRAM read is not word-aligned. what do?");
|
||||
retval = VRAM[VdpDataAddr & 0xFFFE];
|
||||
retval |= (ushort)(VRAM[(VdpDataAddr & 0xFFFE) + 1] << 8);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
case CommandVsramRead:
|
||||
retval = VSRAM[(VdpDataAddr / 2) % 40];
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
return retval;
|
||||
case CommandCramRead:
|
||||
retval = CRAM[(VdpDataAddr / 2) % 64];
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
return retval;
|
||||
default:
|
||||
throw new Exception("VRAM read with unexpected code!!! " + (VdpDataCode & 0x0F));
|
||||
}
|
||||
|
||||
Log.Note("VDP", "VDP Data Read from {0:X4} returning {1:X4}", orig_addr, retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
ushort ReadHVCounter()
|
||||
{
|
||||
int vcounter = ScanLine;
|
||||
if (vcounter > 0xEA)
|
||||
vcounter -= 7;
|
||||
// TODO generalize this across multiple video modes and stuff.
|
||||
|
||||
// TODO dont tie this to musashi cycle count.
|
||||
// Figure out a "clean" way to get cycle counter information available to VDP.
|
||||
// Oh screw that. The VDP and the cpu cycle counters are going to be intertwined pretty tightly.
|
||||
int hcounter = (488 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 488;
|
||||
// FIXME: totally utterly wrong.
|
||||
|
||||
ushort res = (ushort)((vcounter << 8) | (hcounter & 0xFF));
|
||||
//Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public void WriteVdpRegister(int register, byte data)
|
||||
{
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "Register {0}: {1:X2}", register, data);
|
||||
switch (register)
|
||||
{
|
||||
case 0x00: // Mode Set Register 1
|
||||
Registers[register] = data;
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "HINT enabled: " + HInterruptsEnabled);
|
||||
break;
|
||||
|
||||
case 0x01: // Mode Set Register 2
|
||||
//if (VdpDebug)
|
||||
//{
|
||||
// Registers[register] = data;
|
||||
// Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled);
|
||||
// Log.Note("VDP", "DmaEnabled: " + DmaEnabled);
|
||||
// Log.Note("VDP", "VINT enabled: " + VInterruptEnabled);
|
||||
//}
|
||||
break;
|
||||
|
||||
case 0x02: // Name Table Address for Layer A
|
||||
NameTableAddrA = (ushort)((data & 0x38) << 10);
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
||||
break;
|
||||
|
||||
case 0x03: // Name Table Address for Window
|
||||
NameTableAddrWindow = (ushort)((data & 0x3E) << 10);
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
||||
break;
|
||||
|
||||
case 0x04: // Name Table Address for Layer B
|
||||
NameTableAddrB = (ushort)(data << 13);
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
||||
break;
|
||||
|
||||
case 0x05: // Sprite Attribute Table Address
|
||||
SpriteAttributeTableAddr = (ushort)(data << 9);
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
||||
break;
|
||||
|
||||
case 0x0A: // H Interrupt Register
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "HInt occurs every {0} lines.", data);
|
||||
break;
|
||||
|
||||
case 0x0B: // VScroll/HScroll modes
|
||||
//if (VdpDebug)
|
||||
//{
|
||||
// if ((data & 4) != 0)
|
||||
// Log.Note("VDP", "VSCroll Every 2 Cells Enabled");
|
||||
// else
|
||||
// Log.Note("VDP", "Full Screen VScroll");
|
||||
|
||||
// int hscrollmode = data & 3;
|
||||
// switch (hscrollmode)
|
||||
// {
|
||||
// case 0: Log.Note("VDP", "Full Screen HScroll"); break;
|
||||
// case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!! But it'll work."); break;
|
||||
// case 2: Log.Note("VDP", "HScroll every 1 cell"); break;
|
||||
// case 3: Log.Note("VDP", "HScroll every line"); break;
|
||||
// }
|
||||
//}
|
||||
break;
|
||||
|
||||
case 0x0C: // Mode Set #4
|
||||
// TODO interlaced modes
|
||||
if ((data & 0x81) == 0)
|
||||
{
|
||||
// Display is 32 cells wide
|
||||
if (DisplayWidth != 32)
|
||||
{
|
||||
FrameBuffer = new int[256 * 224];
|
||||
FrameWidth = 256;
|
||||
DisplayWidth = 32;
|
||||
SpriteLimit = 64;
|
||||
SpritePerLineLimit = 16;
|
||||
DotsPerLineLimit = 256;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display is 40 cells wide
|
||||
if (DisplayWidth != 40)
|
||||
{
|
||||
FrameBuffer = new int[320 * 224];
|
||||
FrameWidth = 320;
|
||||
DisplayWidth = 40;
|
||||
SpriteLimit = 80;
|
||||
SpritePerLineLimit = 20;
|
||||
DotsPerLineLimit = 320;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0D: // H Scroll Table Address
|
||||
HScrollTableAddr = (ushort)(data << 10);
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr);
|
||||
break;
|
||||
|
||||
case 0x0F: // Auto Address Register Increment
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "Set Data Increment to " + data);
|
||||
break;
|
||||
|
||||
case 0x10: // Nametable Dimensions
|
||||
switch (data & 0x03)
|
||||
{
|
||||
case 0: NameTableWidth = 32; break;
|
||||
case 1: NameTableWidth = 64; break;
|
||||
case 2: NameTableWidth = 32; break; // invalid setting
|
||||
case 3: NameTableWidth = 128; break;
|
||||
}
|
||||
switch ((data >> 4) & 0x03)
|
||||
{
|
||||
case 0: NameTableHeight = 32; break;
|
||||
case 1: NameTableHeight = 64; break;
|
||||
case 2: NameTableHeight = 32; break; // invalid setting
|
||||
case 3: NameTableHeight = 128; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x11: // Window H Position
|
||||
int whp = data & 31;
|
||||
bool fromright = (data & 0x80) != 0;
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left");
|
||||
break;
|
||||
|
||||
case 0x12: // Window V
|
||||
whp = data & 31;
|
||||
fromright = (data & 0x80) != 0;
|
||||
//if (VdpDebug)
|
||||
//Log.Note("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
|
||||
break;
|
||||
|
||||
case 0x13: // DMA Length Low
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
break;
|
||||
|
||||
case 0x14: // DMA Length High
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
break;
|
||||
|
||||
case 0x15: // DMA Source Low
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
case 0x16: // DMA Source Mid
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
case 0x17: // DMA Source High
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
|
||||
}
|
||||
Registers[register] = data;
|
||||
}
|
||||
|
||||
void ProcessPalette(int slot)
|
||||
{
|
||||
byte r = PalXlatTable[(CRAM[slot] & 0x000F) >> 0];
|
||||
byte g = PalXlatTable[(CRAM[slot] & 0x00F0) >> 4];
|
||||
byte b = PalXlatTable[(CRAM[slot] & 0x0F00) >> 8];
|
||||
Palette[slot] = Colors.ARGB(r, g, b);
|
||||
}
|
||||
|
||||
void UpdatePatternBuffer(int addr)
|
||||
{
|
||||
PatternBuffer[(addr * 2) + 1] = (byte)(VRAM[addr ^ 1] & 0x0F);
|
||||
PatternBuffer[(addr * 2) + 0] = (byte)(VRAM[addr ^ 1] >> 4);
|
||||
}
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
return FrameBuffer;
|
||||
}
|
||||
|
||||
public int VirtualWidth { get { return 320; } }
|
||||
public int VirtualHeight { get { return FrameHeight; } }
|
||||
|
||||
public int BufferWidth
|
||||
{
|
||||
get { return FrameWidth; }
|
||||
}
|
||||
|
||||
public int BufferHeight
|
||||
{
|
||||
get { return FrameHeight; }
|
||||
}
|
||||
|
||||
public int BackgroundColor
|
||||
{
|
||||
get { return Palette[Registers[7] & 0x3F]; }
|
||||
}
|
||||
|
||||
#region State Save/Load Code
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[VDP]");
|
||||
|
||||
writer.Write("VRAM ");
|
||||
VRAM.SaveAsHex(writer);
|
||||
writer.Write("CRAM ");
|
||||
CRAM.SaveAsHex(writer);
|
||||
writer.Write("VSRAM ");
|
||||
VSRAM.SaveAsHex(writer);
|
||||
writer.Write("Registers ");
|
||||
Registers.SaveAsHex(writer);
|
||||
|
||||
writer.WriteLine("ControlWordPending {0}", ControlWordPending);
|
||||
writer.WriteLine("DmaFillModePending {0}", DmaFillModePending);
|
||||
writer.WriteLine("VdpDataAddr {0:X4}", VdpDataAddr);
|
||||
writer.WriteLine("VdpDataCode {0}", VdpDataCode);
|
||||
|
||||
writer.WriteLine("[/VDP]");
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
string[] args = reader.ReadLine().Split(' ');
|
||||
if (args[0].Trim() == "") continue;
|
||||
if (args[0] == "[/VDP]") break;
|
||||
else if (args[0] == "VRAM") VRAM.ReadFromHex(args[1]);
|
||||
else if (args[0] == "CRAM") CRAM.ReadFromHex(args[1]);
|
||||
else if (args[0] == "VSRAM") VSRAM.ReadFromHex(args[1]);
|
||||
else if (args[0] == "Registers") Registers.ReadFromHex(args[1]);
|
||||
else if (args[0] == "ControlWordPending") ControlWordPending = bool.Parse(args[1]);
|
||||
else if (args[0] == "DmaFillModePending") DmaFillModePending = bool.Parse(args[1]);
|
||||
else if (args[0] == "VdpDataAddr") VdpDataAddr = ushort.Parse(args[1], NumberStyles.HexNumber);
|
||||
else if (args[0] == "VdpDataCode") VdpDataCode = byte.Parse(args[1]);
|
||||
else
|
||||
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < CRAM.Length; i++)
|
||||
ProcessPalette(i);
|
||||
for (int i = 0; i < VRAM.Length; i++)
|
||||
UpdatePatternBuffer(i);
|
||||
for (int i = 0; i < Registers.Length; i++)
|
||||
WriteVdpRegister(i, Registers[i]);
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(VRAM);
|
||||
writer.Write(CRAM);
|
||||
writer.Write(VSRAM);
|
||||
writer.Write(Registers);
|
||||
|
||||
writer.Write(ControlWordPending);
|
||||
writer.Write(DmaFillModePending);
|
||||
writer.Write(VdpDataAddr);
|
||||
writer.Write(VdpDataCode);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
VRAM = reader.ReadBytes(VRAM.Length);
|
||||
CRAM = reader.ReadUInt16s(CRAM.Length);
|
||||
VSRAM = reader.ReadUInt16s(VSRAM.Length);
|
||||
Registers = reader.ReadBytes(Registers.Length);
|
||||
|
||||
ControlWordPending = reader.ReadBoolean();
|
||||
DmaFillModePending = reader.ReadBoolean();
|
||||
VdpDataAddr = reader.ReadUInt16();
|
||||
VdpDataCode = reader.ReadByte();
|
||||
|
||||
for (int i = 0; i < CRAM.Length; i++)
|
||||
ProcessPalette(i);
|
||||
for (int i = 0; i < VRAM.Length; i++)
|
||||
UpdatePatternBuffer(i);
|
||||
for (int i = 0; i < Registers.Length; i++)
|
||||
WriteVdpRegister(i, Registers[i]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
bool SegaCD = false;
|
||||
|
||||
class IOPort
|
||||
{
|
||||
public byte Data;
|
||||
public byte Control;
|
||||
public byte TxData;
|
||||
public byte RxData;
|
||||
public byte SCtrl;
|
||||
// TODO- a reference to connected device? That gets into the issue of configuring different types of controllers. :|
|
||||
|
||||
public bool TH { get { return (Data & 0x40) != 0; } }
|
||||
}
|
||||
|
||||
IOPort[] IOPorts = new IOPort[]
|
||||
{
|
||||
new IOPort { Data = 0x7F, TxData = 0xFF, RxData = 0xFF, SCtrl = 0xFF },
|
||||
new IOPort { Data = 0x7F, TxData = 0xFF, RxData = 0xFF, SCtrl = 0xFF },
|
||||
new IOPort { Data = 0x7F, TxData = 0xFF, RxData = 0xFF, SCtrl = 0xFF }
|
||||
};
|
||||
|
||||
byte ReadIO(int offset)
|
||||
{
|
||||
offset >>= 1;
|
||||
offset &= 0x0F;
|
||||
if (offset > 1)
|
||||
Log.Note("CPU", "^^^ IO Read {0}: 00", offset);
|
||||
switch (offset)
|
||||
{
|
||||
case 0: // version
|
||||
byte value = (byte)(SegaCD ? 0x00 : 0x20);
|
||||
switch ((char)RomData[0x01F0])
|
||||
{
|
||||
case 'J': value |= 0x00; break;
|
||||
case 'U': value |= 0x80; break;
|
||||
case 'E': value |= 0xC0; break;
|
||||
case 'A': value |= 0xC0; break;
|
||||
case '4': value |= 0x80; break;
|
||||
default: value |= 0x80; break;
|
||||
}
|
||||
//value |= 1; // US
|
||||
Log.Note("CPU", "^^^ IO Read 0: {0:X2}", value);
|
||||
return value;
|
||||
case 1: // Port A
|
||||
lagged = false;
|
||||
ReadController(ref IOPorts[0].Data);
|
||||
Log.Note("CPU", "^^^ IO Read 1: {0:X2}", IOPorts[0].Data);
|
||||
return IOPorts[0].Data;
|
||||
case 2: return 0xFF;
|
||||
case 3: return 0xFF;
|
||||
|
||||
case 0x04: return IOPorts[0].Control;
|
||||
case 0x05: return IOPorts[1].Control;
|
||||
case 0x06: return IOPorts[2].Control;
|
||||
|
||||
case 0x07: return IOPorts[0].TxData;
|
||||
case 0x08: return IOPorts[0].RxData;
|
||||
case 0x09: return IOPorts[0].SCtrl;
|
||||
|
||||
case 0x0A: return IOPorts[1].TxData;
|
||||
case 0x0B: return IOPorts[1].RxData;
|
||||
case 0x0C: return IOPorts[1].SCtrl;
|
||||
|
||||
case 0x0D: return IOPorts[2].TxData;
|
||||
case 0x0E: return IOPorts[2].RxData;
|
||||
case 0x0F: return IOPorts[2].SCtrl;
|
||||
}
|
||||
Log.Note("CPU", "^^^ IO Read {0}: {1:X2}", offset, 0xFF);
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void WriteIO(int offset, int value)
|
||||
{
|
||||
offset >>= 1;
|
||||
offset &= 0x0F;
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x00: break;
|
||||
|
||||
case 0x01: IOPorts[0].Data = (byte)value; break;
|
||||
case 0x02: IOPorts[1].Data = (byte)value; break;
|
||||
case 0x03: IOPorts[2].Data = (byte)value; break;
|
||||
|
||||
case 0x04: IOPorts[0].Control = (byte)value; break;
|
||||
case 0x05: IOPorts[1].Control = (byte)value; break;
|
||||
case 0x06: IOPorts[2].Control = (byte)value; break;
|
||||
|
||||
case 0x07: IOPorts[0].TxData = (byte)value; break;
|
||||
case 0x08: IOPorts[0].RxData = (byte)value; break;
|
||||
case 0x09: IOPorts[0].SCtrl = (byte)value; break;
|
||||
|
||||
case 0x0A: IOPorts[1].TxData = (byte)value; break;
|
||||
case 0x0B: IOPorts[1].RxData = (byte)value; break;
|
||||
case 0x0C: IOPorts[1].SCtrl = (byte)value; break;
|
||||
|
||||
case 0x0D: IOPorts[2].TxData = (byte)value; break;
|
||||
case 0x0E: IOPorts[2].RxData = (byte)value; break;
|
||||
case 0x0F: IOPorts[2].SCtrl = (byte)value; break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadController(ref byte data)
|
||||
{
|
||||
InputCallbacks.Call();
|
||||
data &= 0xC0;
|
||||
if ((data & 0x40) != 0) // TH high
|
||||
{
|
||||
if (Controller.IsPressed("P1 Up") == false) data |= 0x01;
|
||||
if (Controller.IsPressed("P1 Down") == false) data |= 0x02;
|
||||
if (Controller.IsPressed("P1 Left") == false) data |= 0x04;
|
||||
if (Controller.IsPressed("P1 Right") == false) data |= 0x08;
|
||||
if (Controller.IsPressed("P1 B") == false) data |= 0x10;
|
||||
if (Controller.IsPressed("P1 C") == false) data |= 0x20;
|
||||
}
|
||||
else
|
||||
{ // TH low
|
||||
if (Controller.IsPressed("P1 Up") == false) data |= 0x01;
|
||||
if (Controller.IsPressed("P1 Down") == false) data |= 0x02;
|
||||
if (Controller.IsPressed("P1 A") == false) data |= 0x10;
|
||||
if (Controller.IsPressed("P1 Start") == false) data |= 0x20;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
public static readonly ControllerDefinition GenesisController = new ControllerDefinition
|
||||
{
|
||||
Name = "Genesis 3-Button Controller",
|
||||
BoolButtons =
|
||||
{
|
||||
"Reset",
|
||||
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 C", "P1 Start"
|
||||
}
|
||||
};
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get { return GenesisController; } }
|
||||
public IController Controller { private get; set; }
|
||||
}
|
||||
}
|
|
@ -1,495 +0,0 @@
|
|||
#define MUSASHI
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Common.Components;
|
||||
using BizHawk.Emulation.Cores.Components;
|
||||
using BizHawk.Emulation.Cores.Components.M68000;
|
||||
using BizHawk.Emulation.Cores.Components.Z80;
|
||||
using Native68000;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
[CoreAttributes(
|
||||
"GenesisHawk",
|
||||
"Vecna",
|
||||
isPorted: false,
|
||||
isReleased: false
|
||||
)]
|
||||
public sealed partial class Genesis : IEmulator, ISaveRam, IStatable, IInputPollable
|
||||
{
|
||||
private int _lagcount = 0;
|
||||
private bool lagged = true;
|
||||
private bool islag = false;
|
||||
|
||||
// ROM
|
||||
public byte[] RomData;
|
||||
|
||||
// Machine stuff
|
||||
public MC68000 MainCPU;
|
||||
public Z80A SoundCPU;
|
||||
public GenVDP VDP;
|
||||
public SN76489 PSG;
|
||||
public YM2612 YM2612;
|
||||
public byte[] Ram = new byte[0x10000];
|
||||
public byte[] Z80Ram = new byte[0x2000];
|
||||
|
||||
private bool M68000HasZ80Bus = false;
|
||||
private bool Z80Reset = false;
|
||||
private bool Z80Runnable { get { return (Z80Reset == false && M68000HasZ80Bus == false); } }
|
||||
|
||||
private SoundMixer SoundMixer;
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public IInputCallbackSystem InputCallbacks { get { throw new NotImplementedException(); } }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
_lagcount = 0;
|
||||
islag = false;
|
||||
}
|
||||
|
||||
// Genesis timings:
|
||||
// 53,693,175 Machine clocks / sec
|
||||
// 7,670,454 Main 68000 cycles / sec (7 mclk divisor)
|
||||
// 3,579,545 Z80 cycles / sec (15 mclk divisor)
|
||||
|
||||
// At 59.92 FPS:
|
||||
// 896,081 mclks / frame
|
||||
// 128,011 Main 68000 cycles / frame
|
||||
// 59,738 Z80 cycles / frame
|
||||
|
||||
// At 262 lines/frame:
|
||||
// 3420 mclks / line
|
||||
// ~ 488.5 Main 68000 cycles / line
|
||||
// 228 Z80 cycles / line
|
||||
|
||||
// Video characteristics:
|
||||
// 224 lines are active display. The remaining 38 lines are vertical blanking.
|
||||
// In H40 mode, the dot clock is 480 pixels per line.
|
||||
// 320 are active display, the remaining 160 are horizontal blanking.
|
||||
// A total of 3420 mclks per line, but 2560 mclks are active display and 860 mclks are blanking.
|
||||
|
||||
#if MUSASHI
|
||||
VdpCallback _vdp;
|
||||
ReadCallback read8;
|
||||
ReadCallback read16;
|
||||
ReadCallback read32;
|
||||
WriteCallback write8;
|
||||
WriteCallback write16;
|
||||
WriteCallback write32;
|
||||
#endif
|
||||
|
||||
public Genesis(CoreComm comm, GameInfo game, byte[] rom)
|
||||
{
|
||||
ServiceProvider = new BasicServiceProvider(this);
|
||||
CoreComm = comm;
|
||||
MainCPU = new MC68000();
|
||||
SoundCPU = new Z80A();
|
||||
YM2612 = new YM2612() { MaxVolume = 23405 };
|
||||
PSG = new SN76489() { MaxVolume = 4681 };
|
||||
VDP = new GenVDP();
|
||||
VDP.DmaReadFrom68000 = ReadWord;
|
||||
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(VDP);
|
||||
SoundMixer = new SoundMixer(YM2612, PSG);
|
||||
|
||||
MainCPU.ReadByte = ReadByte;
|
||||
MainCPU.ReadWord = ReadWord;
|
||||
MainCPU.ReadLong = ReadLong;
|
||||
MainCPU.WriteByte = WriteByte;
|
||||
MainCPU.WriteWord = WriteWord;
|
||||
MainCPU.WriteLong = WriteLong;
|
||||
MainCPU.IrqCallback = InterruptCallback;
|
||||
|
||||
// ---------------------- musashi -----------------------
|
||||
#if MUSASHI
|
||||
_vdp = vdpcallback;
|
||||
read8 = Read8;
|
||||
read16 = Read16;
|
||||
read32 = Read32;
|
||||
write8 = Write8;
|
||||
write16 = Write16;
|
||||
write32 = Write32;
|
||||
|
||||
Musashi.RegisterVdpCallback(Marshal.GetFunctionPointerForDelegate(_vdp));
|
||||
Musashi.RegisterRead8(Marshal.GetFunctionPointerForDelegate(read8));
|
||||
Musashi.RegisterRead16(Marshal.GetFunctionPointerForDelegate(read16));
|
||||
Musashi.RegisterRead32(Marshal.GetFunctionPointerForDelegate(read32));
|
||||
Musashi.RegisterWrite8(Marshal.GetFunctionPointerForDelegate(write8));
|
||||
Musashi.RegisterWrite16(Marshal.GetFunctionPointerForDelegate(write16));
|
||||
Musashi.RegisterWrite32(Marshal.GetFunctionPointerForDelegate(write32));
|
||||
#endif
|
||||
// ---------------------- musashi -----------------------
|
||||
|
||||
SoundCPU.ReadMemory = ReadMemoryZ80;
|
||||
SoundCPU.WriteMemory = WriteMemoryZ80;
|
||||
SoundCPU.WriteHardware = (a, v) => { Console.WriteLine("Z80: Attempt I/O Write {0:X2}:{1:X2}", a, v); };
|
||||
SoundCPU.ReadHardware = x => 0xFF;
|
||||
SoundCPU.IRQCallback = () => SoundCPU.Interrupt = false;
|
||||
Z80Reset = true;
|
||||
RomData = new byte[0x400000];
|
||||
for (int i = 0; i < rom.Length; i++)
|
||||
RomData[i] = rom[i];
|
||||
|
||||
SetupMemoryDomains();
|
||||
#if MUSASHI
|
||||
Musashi.Init();
|
||||
Musashi.Reset();
|
||||
VDP.GetPC = () => Musashi.PC;
|
||||
#else
|
||||
MainCPU.Reset();
|
||||
VDP.GetPC = () => MainCPU.PC;
|
||||
#endif
|
||||
InitializeCartHardware(game);
|
||||
}
|
||||
|
||||
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
void InitializeCartHardware(GameInfo game)
|
||||
{
|
||||
LogCartInfo();
|
||||
InitializeEeprom(game);
|
||||
InitializeSaveRam(game);
|
||||
}
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound)
|
||||
{
|
||||
lagged = true;
|
||||
Frame++;
|
||||
PSG.BeginFrame(SoundCPU.TotalExecutedCycles);
|
||||
YM2612.BeginFrame(SoundCPU.TotalExecutedCycles);
|
||||
|
||||
// Do start-of-frame events
|
||||
VDP.HIntLineCounter = VDP.Registers[10];
|
||||
//VDP.VdpStatusWord &=
|
||||
unchecked { VDP.VdpStatusWord &= (ushort)~GenVDP.StatusVerticalBlanking; }
|
||||
|
||||
for (VDP.ScanLine = 0; VDP.ScanLine < 262; VDP.ScanLine++)
|
||||
{
|
||||
//Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine);
|
||||
|
||||
if (VDP.ScanLine < VDP.FrameHeight)
|
||||
VDP.RenderLine();
|
||||
|
||||
Exec68k(365);
|
||||
RunZ80(171);
|
||||
|
||||
// H-Int now?
|
||||
|
||||
VDP.HIntLineCounter--;
|
||||
if (VDP.HIntLineCounter < 0 && VDP.ScanLine < 224) // FIXME
|
||||
{
|
||||
VDP.HIntLineCounter = VDP.Registers[10];
|
||||
VDP.VdpStatusWord |= GenVDP.StatusHorizBlanking;
|
||||
|
||||
if (VDP.HInterruptsEnabled)
|
||||
{
|
||||
Set68kIrq(4);
|
||||
//Console.WriteLine("Fire hint!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Exec68k(488 - 365);
|
||||
RunZ80(228 - 171);
|
||||
|
||||
if (VDP.ScanLine == 224)
|
||||
{
|
||||
VDP.VdpStatusWord |= GenVDP.StatusVerticalInterruptPending;
|
||||
VDP.VdpStatusWord |= GenVDP.StatusVerticalBlanking;
|
||||
Exec68k(16); // this is stupidly wrong.
|
||||
// End-frame stuff
|
||||
if (VDP.VInterruptEnabled)
|
||||
Set68kIrq(6);
|
||||
|
||||
SoundCPU.Interrupt = true;
|
||||
//The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason.
|
||||
}
|
||||
}
|
||||
PSG.EndFrame(SoundCPU.TotalExecutedCycles);
|
||||
YM2612.EndFrame(SoundCPU.TotalExecutedCycles);
|
||||
|
||||
|
||||
|
||||
if (lagged)
|
||||
{
|
||||
_lagcount++;
|
||||
islag = true;
|
||||
}
|
||||
else
|
||||
islag = false;
|
||||
}
|
||||
|
||||
void Exec68k(int cycles)
|
||||
{
|
||||
#if MUSASHI
|
||||
Musashi.Execute(cycles);
|
||||
#else
|
||||
MainCPU.ExecuteCycles(cycles);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RunZ80(int cycles)
|
||||
{
|
||||
// I emulate the YM2612 synced to Z80 clock, for better or worse.
|
||||
// So we still need to keep the Z80 cycle count accurate even if the Z80 isn't running.
|
||||
|
||||
if (Z80Runnable)
|
||||
SoundCPU.ExecuteCycles(cycles);
|
||||
else
|
||||
SoundCPU.TotalExecutedCycles += cycles;
|
||||
}
|
||||
|
||||
void Set68kIrq(int irq)
|
||||
{
|
||||
#if MUSASHI
|
||||
Musashi.SetIRQ(irq);
|
||||
#else
|
||||
MainCPU.Interrupt = irq;
|
||||
#endif
|
||||
}
|
||||
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new Dictionary<string, RegisterValue>
|
||||
{
|
||||
{ "A-0", MainCPU.A[0].s32 },
|
||||
{ "A-1", MainCPU.A[1].s32 },
|
||||
{ "A-2", MainCPU.A[2].s32 },
|
||||
{ "A-3", MainCPU.A[3].s32 },
|
||||
{ "A-4", MainCPU.A[4].s32 },
|
||||
{ "A-5", MainCPU.A[5].s32 },
|
||||
{ "A-6", MainCPU.A[6].s32 },
|
||||
{ "A-7", MainCPU.A[7].s32 },
|
||||
|
||||
{ "D-0", MainCPU.D[0].s32 },
|
||||
{ "D-1", MainCPU.D[1].s32 },
|
||||
{ "D-2", MainCPU.D[2].s32 },
|
||||
{ "D-3", MainCPU.D[3].s32 },
|
||||
{ "D-4", MainCPU.D[4].s32 },
|
||||
{ "D-5", MainCPU.D[5].s32 },
|
||||
{ "D-6", MainCPU.D[6].s32 },
|
||||
{ "D-7", MainCPU.D[7].s32 },
|
||||
|
||||
{ "SR", MainCPU.SR },
|
||||
|
||||
{ "Flag X", MainCPU.X },
|
||||
{ "Flag N", MainCPU.N },
|
||||
{ "Flag Z", MainCPU.Z },
|
||||
{ "Flag V", MainCPU.V },
|
||||
{ "Flag C", MainCPU.C }
|
||||
};
|
||||
}
|
||||
|
||||
int vdpcallback(int level) // Musashi handler
|
||||
{
|
||||
InterruptCallback(level);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InterruptCallback(int level)
|
||||
{
|
||||
unchecked { VDP.VdpStatusWord &= (ushort)~GenVDP.StatusVerticalInterruptPending; }
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
||||
// TODO: Implement ISoundProvider
|
||||
/*
|
||||
public IAsyncSoundProvider SoundProvider
|
||||
{
|
||||
get { return SoundMixer; }
|
||||
}
|
||||
|
||||
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundMixer, 735); } }
|
||||
*/
|
||||
|
||||
public int Frame { get; set; }
|
||||
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
||||
public bool IsLagFrame { get { return islag; } set { islag = value; } }
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
public string SystemId { get { return "GEN"; } }
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
var buf = new byte[141501 + SaveRAM.Length];
|
||||
var stream = new MemoryStream(buf);
|
||||
var bwriter = new BinaryWriter(stream);
|
||||
SaveStateBinary(bwriter);
|
||||
|
||||
writer.WriteLine("Version 1");
|
||||
writer.Write("BigFatBlob ");
|
||||
buf.SaveAsHex(writer);
|
||||
|
||||
/*writer.WriteLine("[MegaDrive]");
|
||||
MainCPU.SaveStateText(writer, "Main68K");
|
||||
SoundCPU.SaveStateText(writer);
|
||||
PSG.SaveStateText(writer);
|
||||
VDP.SaveStateText(writer);
|
||||
writer.WriteLine("Frame {0}", Frame);
|
||||
writer.WriteLine("Lag {0}", _lagcount);
|
||||
writer.WriteLine("IsLag {0}", islag);
|
||||
writer.Write("MainRAM ");
|
||||
Ram.SaveAsHex(writer);
|
||||
writer.Write("Z80RAM ");
|
||||
Z80Ram.SaveAsHex(writer);
|
||||
writer.WriteLine("[/MegaDrive]");*/
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
var buf = new byte[141501 + SaveRAM.Length];
|
||||
var version = reader.ReadLine();
|
||||
if (version != "Version 1")
|
||||
throw new Exception("Not a valid state vesrion! sorry! your state is bad! Robust states will be added later!");
|
||||
var omgstate = reader.ReadLine().Split(' ')[1];
|
||||
buf.ReadFromHex(omgstate);
|
||||
LoadStateBinary(new BinaryReader(new MemoryStream(buf)));
|
||||
|
||||
/*while (true)
|
||||
{
|
||||
string[] args = reader.ReadLine().Split(' ');
|
||||
if (args[0].Trim() == "") continue;
|
||||
if (args[0] == "[MegaDrive]") continue;
|
||||
if (args[0] == "[/MegaDrive]") break;
|
||||
if (args[0] == "MainRAM")
|
||||
Ram.ReadFromHex(args[1]);
|
||||
else if (args[0] == "Z80RAM")
|
||||
Z80Ram.ReadFromHex(args[1]);
|
||||
else if (args[0] == "[Main68K]")
|
||||
MainCPU.LoadStateText(reader, "Main68K");
|
||||
else if (args[0] == "[Z80]")
|
||||
SoundCPU.LoadStateText(reader);
|
||||
else if (args[0] == "Frame")
|
||||
Frame = int.Parse(args[1]);
|
||||
else if (args[0] == "Lag")
|
||||
_lagcount = int.Parse(args[1]);
|
||||
else if (args[0] == "IsLag")
|
||||
islag = bool.Parse(args[1]);
|
||||
else if (args[0] == "[PSG]")
|
||||
PSG.LoadStateText(reader);
|
||||
else if (args[0] == "[VDP]")
|
||||
VDP.LoadStateText(reader);
|
||||
else
|
||||
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
||||
}*/
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
Musashi.SaveStateBinary(writer); // 124
|
||||
//SoundCPU.SaveStateBinary(writer); // 46 TODO fix this, there is no way to invoke this core from the UI for testing anyway.
|
||||
//PSG.SaveStateBinary(writer); // 15
|
||||
VDP.SaveStateBinary(writer); // 65781
|
||||
YM2612.SaveStateBinary(writer); // 1785
|
||||
|
||||
writer.Write(Ram); // 65535
|
||||
writer.Write(Z80Ram); // 8192
|
||||
|
||||
writer.Write(Frame); // 4
|
||||
writer.Write(M68000HasZ80Bus); // 1
|
||||
writer.Write(Z80Reset); // 1
|
||||
writer.Write(BankRegion); // 4
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
writer.Write(IOPorts[i].Data);
|
||||
writer.Write(IOPorts[i].TxData);
|
||||
writer.Write(IOPorts[i].RxData);
|
||||
writer.Write(IOPorts[i].SCtrl);
|
||||
}
|
||||
|
||||
if (SaveRAM.Length > 0)
|
||||
writer.Write(SaveRAM);
|
||||
|
||||
// TODO: EEPROM/cart HW state
|
||||
// TODO: lag counter crap
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
Musashi.LoadStateBinary(reader);
|
||||
//SoundCPU.LoadStateBinary(reader);
|
||||
//PSG.LoadStateBinary(reader);
|
||||
VDP.LoadStateBinary(reader);
|
||||
YM2612.LoadStateBinary(reader);
|
||||
|
||||
Ram = reader.ReadBytes(Ram.Length);
|
||||
Z80Ram = reader.ReadBytes(Z80Ram.Length);
|
||||
|
||||
Frame = reader.ReadInt32();
|
||||
M68000HasZ80Bus = reader.ReadBoolean();
|
||||
Z80Reset = reader.ReadBoolean();
|
||||
BankRegion = reader.ReadInt32();
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
IOPorts[i].Data = reader.ReadByte();
|
||||
IOPorts[i].TxData = reader.ReadByte();
|
||||
IOPorts[i].RxData = reader.ReadByte();
|
||||
IOPorts[i].SCtrl = reader.ReadByte();
|
||||
}
|
||||
|
||||
if (SaveRAM.Length > 0)
|
||||
SaveRAM = reader.ReadBytes(SaveRAM.Length);
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
var buf = new byte[141501 + SaveRAM.Length];
|
||||
var stream = new MemoryStream(buf);
|
||||
var writer = new BinaryWriter(stream);
|
||||
SaveStateBinary(writer);
|
||||
//Console.WriteLine("buf len = {0}", stream.Position);
|
||||
writer.Close();
|
||||
return buf;
|
||||
}
|
||||
|
||||
public bool BinarySaveStatesPreferred { get { return false; } }
|
||||
|
||||
//MemoryDomainList memoryDomains;
|
||||
|
||||
void SetupMemoryDomains()
|
||||
{
|
||||
/*
|
||||
var domains = new List<MemoryDomain>(3);
|
||||
var MainMemoryDomain = new MemoryDomain("Main RAM", Ram.Length, MemoryDomain.Endian.Big,
|
||||
addr => Ram[addr & 0xFFFF],
|
||||
(addr, value) => Ram[addr & 0xFFFF] = value);
|
||||
var Z80Domain = new MemoryDomain("Z80 RAM", Z80Ram.Length, MemoryDomain.Endian.Little,
|
||||
addr => Z80Ram[addr & 0x1FFF],
|
||||
(addr, value) => { Z80Ram[addr & 0x1FFF] = value; });
|
||||
|
||||
var VRamDomain = new MemoryDomain("Video RAM", VDP.VRAM.Length, MemoryDomain.Endian.Big,
|
||||
addr => VDP.VRAM[addr & 0xFFFF],
|
||||
(addr, value) => VDP.VRAM[addr & 0xFFFF] = value);
|
||||
|
||||
var RomDomain = new MemoryDomain("MD CART", RomData.Length, MemoryDomain.Endian.Big,
|
||||
addr => RomData[addr], //adelikat: For speed considerations, I didn't mask this, every tool that uses memory domains is smart enough not to overflow, if I'm wrong let me know!
|
||||
(addr, value) => RomData[addr & (RomData.Length - 1)] = value);
|
||||
|
||||
var SystemBusDomain = new MemoryDomain("System Bus", 0x1000000, MemoryDomain.Endian.Big,
|
||||
addr => (byte)ReadByte((int)addr),
|
||||
(addr, value) => Write8((uint)addr, (uint)value));
|
||||
|
||||
domains.Add(MainMemoryDomain);
|
||||
domains.Add(Z80Domain);
|
||||
domains.Add(VRamDomain);
|
||||
domains.Add(RomDomain);
|
||||
domains.Add(SystemBusDomain);
|
||||
memoryDomains = new MemoryDomainList(domains);
|
||||
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(memoryDomains);
|
||||
*/
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
|
@ -1,254 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
public sbyte ReadByte(int address)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
if (address < 0x200000)
|
||||
return (sbyte)RomData[address];
|
||||
|
||||
if (address < 0x400000)
|
||||
{
|
||||
if (SaveRamEnabled && address >= SaveRamStartOffset && address < SaveRamEndOffset)
|
||||
{
|
||||
if (SaveRamEveryOtherByte)
|
||||
return (sbyte)SaveRAM[(address - SaveRamStartOffset) >> 1];
|
||||
else
|
||||
return (sbyte)SaveRAM[address - SaveRamStartOffset];
|
||||
}
|
||||
return (sbyte)RomData[address];
|
||||
}
|
||||
|
||||
if (address >= 0xE00000)
|
||||
return (sbyte)Ram[address & 0xFFFF];
|
||||
|
||||
if (address == 0xA11100) // Z80 BUS status
|
||||
{
|
||||
//Console.WriteLine("QUERY z80 bus status. 68000 can access? " + (M68000HasZ80Bus && Z80Reset == false));
|
||||
return (sbyte)(M68000HasZ80Bus && Z80Reset == false ? 0 : 1);
|
||||
}
|
||||
|
||||
if (address >= 0xA10000 && address <= 0xA1001F)
|
||||
return (sbyte)ReadIO(address);
|
||||
|
||||
if ((address & 0xFF0000) == 0xA00000)
|
||||
return (sbyte)ReadMemoryZ80((ushort)(address & 0x7FFF));
|
||||
|
||||
if (address >= 0xC00000 && address < 0xC00010)
|
||||
{
|
||||
//Console.WriteLine("byte-reading the VDP. someone is probably stupid.");
|
||||
ushort value = VDP.ReadVdp(address & 0x0E);
|
||||
if ((address & 1) == 0) // read MSB
|
||||
return (sbyte)(value >> 8);
|
||||
return (sbyte)value; // read LSB
|
||||
}
|
||||
|
||||
Console.WriteLine("UNHANDLED READB {0:X6}", address);
|
||||
return 0x7D;
|
||||
}
|
||||
|
||||
public short ReadWord(int address)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
int maskedAddr;
|
||||
if (address < 0x400000)
|
||||
return (short)((RomData[address] << 8) | RomData[address + 1]);
|
||||
|
||||
if (address >= 0xE00000) // Work RAM
|
||||
{
|
||||
maskedAddr = address & 0xFFFE;
|
||||
return (short)((Ram[maskedAddr] << 8) | Ram[maskedAddr + 1]);
|
||||
}
|
||||
|
||||
if (address >= 0xC00000 && address < 0xC00010)
|
||||
return (short)VDP.ReadVdp(address & 0x0E);
|
||||
|
||||
if (address >= 0xA10000 && address <= 0xA1001F)
|
||||
return (sbyte)ReadIO(address);
|
||||
|
||||
if (address == 0xA11100) // Z80 BUS status
|
||||
return (short)(M68000HasZ80Bus && Z80Reset == false ? 0x0000 : 0x0100);
|
||||
|
||||
Console.WriteLine("UNHANDLED READW {0:X6}", address);
|
||||
return 0x7DCD;
|
||||
}
|
||||
|
||||
public int ReadLong(int address)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
int maskedAddr;
|
||||
if (address < 0x400000) // Cartridge ROM
|
||||
return (RomData[address] << 24) | (RomData[address + 1] << 16) | (RomData[address + 2] << 8) | RomData[address + 3];
|
||||
|
||||
if (address >= 0xE00000) // Work RAM
|
||||
{
|
||||
maskedAddr = address & 0xFFFF;
|
||||
return (Ram[maskedAddr] << 24) | (Ram[maskedAddr + 1] << 16) | (Ram[maskedAddr + 2] << 8) | Ram[maskedAddr + 3];
|
||||
}
|
||||
|
||||
if (address >= 0xC00000)
|
||||
{
|
||||
//Console.WriteLine("long-read from VDP");
|
||||
short msw = ReadWord(address);
|
||||
short msl = ReadWord(address + 2);
|
||||
return (msw << 16) | (ushort)msl;
|
||||
}
|
||||
|
||||
// try to handle certain things separate if they need to be separate? otherwise handle as 2x readwords?
|
||||
{
|
||||
return ((ushort)ReadWord(address) | (ushort)(ReadWord(address + 2) << 16));
|
||||
}
|
||||
//if (address == 0xA10008) return 0; // FIXME HACK for tg-sync.
|
||||
|
||||
//Console.WriteLine("UNHANDLED READL {0:X6}", address);
|
||||
//return 0x7DCDCDCD;
|
||||
}
|
||||
|
||||
public void WriteByte(int address, sbyte value)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
if (address >= 0xE00000) // Work RAM
|
||||
{
|
||||
//Console.WriteLine("MEM[{0:X4}] change from {1:X2} to {2:X2}", address & 0xFFFF, Ram[address & 0xFFFF], value);
|
||||
Ram[address & 0xFFFF] = (byte)value;
|
||||
return;
|
||||
}
|
||||
if ((address & 0xFF0000) == 0xA00000)
|
||||
{
|
||||
WriteMemoryZ80((ushort)(address & 0x7FFF), (byte)value);
|
||||
return;
|
||||
}
|
||||
if (address >= 0xA10000 && address <= 0xA1001F)
|
||||
{
|
||||
WriteIO(address, value);
|
||||
return;
|
||||
}
|
||||
if (address == 0xA11100)
|
||||
{
|
||||
M68000HasZ80Bus = (value & 1) != 0;
|
||||
//Console.WriteLine("68000 has the z80 bus: " + M68000HasZ80Bus);
|
||||
return;
|
||||
}
|
||||
if (address == 0xA11200) // Z80 RESET
|
||||
{
|
||||
Z80Reset = (value & 1) == 0;
|
||||
if (Z80Reset)
|
||||
SoundCPU.SoftReset();
|
||||
//Console.WriteLine("z80 reset: " + Z80Reset);
|
||||
return;
|
||||
}
|
||||
if (address >= 0xC00000 && address < 0xC00010)
|
||||
{
|
||||
// when writing to VDP in byte mode, the LSB is duplicated into the MSB
|
||||
VDP.WriteVdp(address & 0x1E, (ushort)((ushort)value | ((ushort)value << 8)));
|
||||
return;
|
||||
}
|
||||
if (address >= 0xC00011 && address <= 0xC00017 && (address & 1) != 0)
|
||||
{
|
||||
PSG.WritePsgData((byte)value, SoundCPU.TotalExecutedCycles);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SaveRamEnabled && address >= SaveRamStartOffset && address < SaveRamEndOffset)
|
||||
{
|
||||
if (SaveRamEveryOtherByte)
|
||||
SaveRAM[(address - SaveRamStartOffset) >> 1] = (byte)value;
|
||||
else
|
||||
SaveRAM[address - SaveRamStartOffset] = (byte)value;
|
||||
|
||||
SaveRamModified = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (EepromEnabled && (address == SclAddr || address == SdaInAddr))
|
||||
{
|
||||
WriteByteEeprom(address, (byte)value);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
Console.WriteLine("UNHANDLED WRITEB {0:X6}:{1:X2}", address, value);
|
||||
}
|
||||
|
||||
public void WriteWord(int address, short value)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
if (address >= 0xE00000) // Work RAM
|
||||
{
|
||||
//Console.WriteLine("MEM[{0:X4}] change to {1:X4}", address & 0xFFFF, value);
|
||||
Ram[(address & 0xFFFF) + 0] = (byte)(value >> 8);
|
||||
Ram[(address & 0xFFFF) + 1] = (byte)value;
|
||||
return;
|
||||
}
|
||||
if (address >= 0xC00000)
|
||||
{
|
||||
switch (address & 0x1F)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x02:
|
||||
VDP.WriteVdpData((ushort)value);
|
||||
return;
|
||||
case 0x04:
|
||||
case 0x06:
|
||||
VDP.WriteVdpControl((ushort)value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (address == 0xA11100) // Z80 BUSREQ
|
||||
{
|
||||
M68000HasZ80Bus = (value & 0x100) != 0;
|
||||
//Console.WriteLine("68000 has the z80 bus: " + M68000HasZ80Bus);
|
||||
return;
|
||||
}
|
||||
if (address == 0xA11200) // Z80 RESET
|
||||
{
|
||||
Z80Reset = (value & 0x100) == 0;
|
||||
if (Z80Reset)
|
||||
SoundCPU.SoftReset();
|
||||
//Console.WriteLine("z80 reset: " + Z80Reset);
|
||||
return;
|
||||
}
|
||||
Console.WriteLine("UNHANDLED WRITEW {0:X6}:{1:X4}", address, value);
|
||||
}
|
||||
|
||||
public void WriteLong(int address, int value)
|
||||
{
|
||||
address &= 0x00FFFFFF;
|
||||
|
||||
if (address >= 0xE00000) // Work RAM
|
||||
{
|
||||
//Console.WriteLine("MEM[{0:X4}] change to {1:X8}", address & 0xFFFF, value);
|
||||
Ram[(address & 0xFFFF) + 0] = (byte)(value >> 24);
|
||||
Ram[(address & 0xFFFF) + 1] = (byte)(value >> 16);
|
||||
Ram[(address & 0xFFFF) + 2] = (byte)(value >> 8);
|
||||
Ram[(address & 0xFFFF) + 3] = (byte)value;
|
||||
return;
|
||||
}
|
||||
if (address >= 0xC00000)
|
||||
{
|
||||
WriteWord(address, (short)(value >> 16));
|
||||
WriteWord(address + 2, (short)value);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("UNHANDLED WRITEL {0:X6}:{1:X8}", address, value);
|
||||
}
|
||||
|
||||
// Mushashi interop test stuff. TODO kill this when we're ready to ditch musashi.
|
||||
|
||||
public uint Read8(uint a) { /*Console.WriteLine("read8 {0:X}", a);*/ return (uint)ReadByte((int)a) & 0xFF; }
|
||||
public uint Read16(uint a) { /*Console.WriteLine("read16 {0:X}", a);*/ return (uint)ReadWord((int)a) & 0xFFFF; }
|
||||
public uint Read32(uint a) { /*Console.WriteLine("read32 {0:X}", a);*/ return (uint)ReadLong((int)a); }
|
||||
public void Write8(uint a, uint v) { /*Console.WriteLine("write8 {0:X}:{1:X2}", a, v);*/ WriteByte((int)a, (sbyte)v); }
|
||||
public void Write16(uint a, uint v) { /*Console.WriteLine("write16 {0:X}:{1:X4}", a, v);*/ WriteWord((int)a, (short)v); }
|
||||
public void Write32(uint a, uint v) { /*Console.WriteLine("write32 {0:X}:{1:X8}", a, v);*/ WriteLong((int)a, (int)v); }
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
||||
{
|
||||
partial class Genesis
|
||||
{
|
||||
private int BankRegion;
|
||||
|
||||
public byte ReadMemoryZ80(ushort address)
|
||||
{
|
||||
if (address < 0x4000)
|
||||
{
|
||||
//Console.WriteLine("read z80 memory {0:X4}: {1:X2}",address, Z80Ram[address & 0x1FFF]);
|
||||
return Z80Ram[address & 0x1FFF];
|
||||
}
|
||||
if (address >= 0x4000 && address < 0x6000)
|
||||
{
|
||||
//Console.WriteLine(" === Z80 READS FM STATUS ===");
|
||||
return YM2612.ReadStatus(SoundCPU.TotalExecutedCycles); // TODO: more than 1 read port probably?
|
||||
}
|
||||
if (address >= 0x8000)
|
||||
{
|
||||
// 68000 Bank region
|
||||
return (byte)ReadByte(BankRegion | (address & 0x7FFF));
|
||||
}
|
||||
if (address <= 0x6100) // read from bank address register - returns FF
|
||||
return 0xFF;
|
||||
Console.WriteLine("UNHANDLED Z80 READ {0:X4}", address);
|
||||
return 0xCD;
|
||||
}
|
||||
|
||||
public void WriteMemoryZ80(ushort address, byte value)
|
||||
{
|
||||
if (address < 0x4000)
|
||||
{
|
||||
//Console.WriteLine("write z80 memory {0:X4}: {1:X2}",address, value);
|
||||
Z80Ram[address & 0x1FFF] = value;
|
||||
return;
|
||||
}
|
||||
if (address >= 0x4000 && address < 0x6000)
|
||||
{
|
||||
//Console.WriteLine(" === Z80 WRITES YM2612 {0:X4}:{1:X2} ===",address, value);
|
||||
YM2612.Write(address & 3, value, SoundCPU.TotalExecutedCycles);
|
||||
return;
|
||||
}
|
||||
if (address < 0x6100)
|
||||
{
|
||||
BankRegion >>= 1;
|
||||
BankRegion |= (value & 1) << 23;
|
||||
BankRegion &= 0x00FF8000;
|
||||
//Console.WriteLine("Bank pointing at {0:X8}",BankRegion);
|
||||
return;
|
||||
}
|
||||
if (address >= 0x7F00 && address < 0x7F20)
|
||||
{
|
||||
switch (address & 0x1F)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x02:
|
||||
VDP.WriteVdpData((ushort)((value << 8) | value));
|
||||
return;
|
||||
|
||||
case 0x04:
|
||||
case 0x06:
|
||||
VDP.WriteVdpControl((ushort)((value << 8) | value));
|
||||
return;
|
||||
|
||||
case 0x11:
|
||||
case 0x13:
|
||||
case 0x15:
|
||||
case 0x17:
|
||||
PSG.WritePsgData(value, SoundCPU.TotalExecutedCycles);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (address >= 0x8000)
|
||||
{
|
||||
WriteByte(BankRegion | (address & 0x7FFF), (sbyte)value);
|
||||
return;
|
||||
}
|
||||
Console.WriteLine("UNHANDLED Z80 WRITE {0:X4}:{1:X2}", address, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
|
||||
namespace Native68000
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
delegate int VdpCallback(int i);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
delegate uint ReadCallback(uint a);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
delegate void WriteCallback(uint a, uint v);
|
||||
|
||||
public class Musashi
|
||||
{
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterVdpCallback(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterRead8(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterRead16(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterRead32(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterWrite8(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterWrite16(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void RegisterWrite32(IntPtr callback);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void Init();
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void Reset();
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void SetIRQ(int level);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Execute(int cycles);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int QueryCpuState(int regcode);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void SetCpuState(int regcode, int value);
|
||||
|
||||
[DllImport("MusashiDLL.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int GetCyclesRemaining();
|
||||
|
||||
public static int D0 { get { return QueryCpuState(0); } }
|
||||
public static int D1 { get { return QueryCpuState(1); } }
|
||||
public static int D2 { get { return QueryCpuState(2); } }
|
||||
public static int D3 { get { return QueryCpuState(3); } }
|
||||
public static int D4 { get { return QueryCpuState(4); } }
|
||||
public static int D5 { get { return QueryCpuState(5); } }
|
||||
public static int D6 { get { return QueryCpuState(6); } }
|
||||
public static int D7 { get { return QueryCpuState(7); } }
|
||||
|
||||
public static int A0 { get { return QueryCpuState(8); } }
|
||||
public static int A1 { get { return QueryCpuState(9); } }
|
||||
public static int A2 { get { return QueryCpuState(10); } }
|
||||
public static int A3 { get { return QueryCpuState(11); } }
|
||||
public static int A4 { get { return QueryCpuState(12); } }
|
||||
public static int A5 { get { return QueryCpuState(13); } }
|
||||
public static int A6 { get { return QueryCpuState(14); } }
|
||||
public static int A7 { get { return QueryCpuState(15); } }
|
||||
|
||||
public static int PC { get { return QueryCpuState(16); } }
|
||||
public static int SR { get { return QueryCpuState(17); } }
|
||||
public static int SP { get { return QueryCpuState(18); } }
|
||||
|
||||
public static void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
for (int i=0; i<31; i++)
|
||||
writer.Write(QueryCpuState(i));
|
||||
}
|
||||
|
||||
public static void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
for (int i = 0; i < 31; i++)
|
||||
SetCpuState(i, reader.ReadInt32());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue