CPCHawk: Initial project outline (may or may not come to something eventually)
This commit is contained in:
parent
4160af8eaa
commit
531833c7b0
|
@ -99,6 +99,9 @@ namespace BizHawk.Client.ApiHawk
|
|||
case "ZXSpectrum":
|
||||
return CoreSystem.ZXSpectrum;
|
||||
|
||||
case "AmstradCPC":
|
||||
return CoreSystem.AmstradCPC;
|
||||
|
||||
case "VB":
|
||||
case "NGP":
|
||||
case "DNGP":
|
||||
|
@ -211,6 +214,9 @@ namespace BizHawk.Client.ApiHawk
|
|||
case CoreSystem.ZXSpectrum:
|
||||
return "ZXSpectrum";
|
||||
|
||||
case CoreSystem.AmstradCPC:
|
||||
return "AmstradCPC";
|
||||
|
||||
default:
|
||||
throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value.ToString()));
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
Libretro,
|
||||
VirtualBoy,
|
||||
NeoGeoPocket,
|
||||
ZXSpectrum
|
||||
ZXSpectrum,
|
||||
AmstradCPC
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,8 @@ namespace BizHawk.Client.Common
|
|||
return SystemInfo.NeoGeoPocket;
|
||||
case "ZXSpectrum":
|
||||
return SystemInfo.ZXSpectrum;
|
||||
case "AmstradCPC":
|
||||
return SystemInfo.AmstradCPC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace BizHawk.Client.Common
|
|||
RomData = FileData;
|
||||
}
|
||||
else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX" ||
|
||||
file.Extension == ".PZX" || file.Extension == ".CSW" || file.Extension == ".WAV")
|
||||
file.Extension == ".PZX" || file.Extension == ".CSW" || file.Extension == ".WAV" || file.Extension == ".CDT")
|
||||
{
|
||||
// these are not roms. unforunately if treated as such there are certain edge-cases
|
||||
// where a header offset is detected. This should mitigate this issue until a cleaner solution is found
|
||||
|
|
|
@ -25,6 +25,7 @@ using BizHawk.Emulation.DiscSystem;
|
|||
using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
||||
using BizHawk.Emulation.Cores.Consoles.Sega.Saturn;
|
||||
using BizHawk.Emulation.Cores.Consoles.NEC.PCFX;
|
||||
using BizHawk.Emulation.Cores.Computers.AmstradCPC;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
|
@ -673,7 +674,22 @@ namespace BizHawk.Client.Common
|
|||
(ZXSpectrum.ZXSpectrumSettings)GetCoreSettings<ZXSpectrum>(),
|
||||
(ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings<ZXSpectrum>());
|
||||
break;
|
||||
case "PSX":
|
||||
case "AmstradCPC":
|
||||
|
||||
List<GameInfo> cpcGI = new List<GameInfo>();
|
||||
foreach (var a in xmlGame.Assets)
|
||||
{
|
||||
cpcGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) });
|
||||
}
|
||||
|
||||
nextEmulator = new ZXSpectrum(
|
||||
nextComm,
|
||||
xmlGame.Assets.Select(a => a.Value), //.First(),
|
||||
cpcGI, // GameInfo.NullInstance,
|
||||
(AmstradCPC.AmstradCPCSettings)GetCoreSettings<ZXSpectrum>(),
|
||||
(AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings<ZXSpectrum>());
|
||||
break;
|
||||
case "PSX":
|
||||
var entries = xmlGame.AssetFullPaths;
|
||||
var discs = new List<Disc>();
|
||||
var discNames = new List<string>();
|
||||
|
@ -1017,7 +1033,11 @@ namespace BizHawk.Client.Common
|
|||
var zx = new ZXSpectrum(nextComm, Enumerable.Repeat(rom.RomData, 1), Enumerable.Repeat(rom.GameInfo, 1).ToList(), GetCoreSettings<ZXSpectrum>(), GetCoreSyncSettings<ZXSpectrum>());
|
||||
nextEmulator = zx;
|
||||
break;
|
||||
case "GBA":
|
||||
case "AmstradCPC":
|
||||
var cpc = new AmstradCPC(nextComm, Enumerable.Repeat(rom.RomData, 1), Enumerable.Repeat(rom.GameInfo, 1).ToList(), GetCoreSettings<AmstradCPC>(), GetCoreSyncSettings<AmstradCPC>());
|
||||
nextEmulator = cpc;
|
||||
break;
|
||||
case "GBA":
|
||||
if (Global.Config.GBA_UsemGBA)
|
||||
{
|
||||
core = CoreInventory.Instance["GBA", "mGBA"];
|
||||
|
|
|
@ -193,14 +193,19 @@ namespace BizHawk.Client.Common
|
|||
/// </summary>
|
||||
public static SystemInfo ZXSpectrum { get; } = new SystemInfo("ZX Spectrum", CoreSystem.ZXSpectrum, 2);
|
||||
|
||||
#endregion Get SystemInfo
|
||||
/// <summary>
|
||||
/// Gets the <see cref="SystemInfo"/> instance for AmstradCPC
|
||||
/// </summary>
|
||||
public static SystemInfo AmstradCPC { get; } = new SystemInfo("Amstrad CPC", CoreSystem.AmstradCPC, 2);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="SystemInfo"/> by its <see cref="CoreSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="system"><see cref="CoreSystem"/> you're looking for</param>
|
||||
/// <returns><see cref="SystemInfo"/></returns>
|
||||
public static SystemInfo FindByCoreSystem(CoreSystem system)
|
||||
#endregion Get SystemInfo
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="SystemInfo"/> by its <see cref="CoreSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="system"><see cref="CoreSystem"/> you're looking for</param>
|
||||
/// <returns><see cref="SystemInfo"/></returns>
|
||||
public static SystemInfo FindByCoreSystem(CoreSystem system)
|
||||
{
|
||||
return _allSystemInfos.Find(s => s.System == system);
|
||||
}
|
||||
|
|
|
@ -298,6 +298,12 @@ namespace BizHawk.Client.Common
|
|||
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
|
||||
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 },
|
||||
|
||||
new PathEntry { System = "AmstradCPC", SystemDisplayName = "Amstrad CPC", Type = "Base", Path = Path.Combine(".", "AmstradCPC"), Ordinal = 0 },
|
||||
new PathEntry { System = "AmstradCPC", SystemDisplayName = "Amstrad CPC", Type = "ROM", Path = ".", Ordinal = 1 },
|
||||
new PathEntry { System = "AmstradCPC", SystemDisplayName = "Amstrad CPC", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
|
||||
new PathEntry { System = "AmstradCPC", SystemDisplayName = "Amstrad CPC", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
|
||||
new PathEntry { System = "AmstradCPC", SystemDisplayName = "Amstrad CPC", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 },
|
||||
|
||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Base", Path = Path.Combine(".", "PSX"), Ordinal = 0 },
|
||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "ROM", Path = ".", Ordinal = 1 },
|
||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
|
||||
|
|
|
@ -61,17 +61,18 @@ namespace BizHawk.Client.Common
|
|||
["C64_DREAN"] = PALNCarrier * 2 / 7 / 312 / 65,
|
||||
["INTV"] = 59.92,
|
||||
|
||||
["ZXSpectrum_PAL"] = 50.080128205
|
||||
["ZXSpectrum_PAL"] = 50.080128205,
|
||||
["AmstradCPC_PAL"] = 50.08012820512821,
|
||||
|
||||
// according to ryphecha, using
|
||||
// clocks[2] = { 53.693182e06, 53.203425e06 }; //ntsc console, pal console
|
||||
// lpf[2][2] = { { 263, 262.5 }, { 314, 312.5 } }; //ntsc,pal; noninterlaced, interlaced
|
||||
// cpl[2] = { 3412.5, 3405 }; //ntsc mode, pal mode
|
||||
// PAL PS1: 0, PAL Mode: 0, Interlaced: 0 --- 59.826106 (53.693182e06/(263*3412.5))
|
||||
// PAL PS1: 0, PAL Mode: 0, Interlaced: 1 --- 59.940060 (53.693182e06/(262.5*3412.5))
|
||||
// PAL PS1: 1, PAL Mode: 1, Interlaced: 0 --- 49.761427 (53.203425e06/(314*3405))
|
||||
// PAL PS1: 1, PAL Mode: 1, Interlaced: 1 --- 50.000282(53.203425e06/(312.5*3405))
|
||||
};
|
||||
// according to ryphecha, using
|
||||
// clocks[2] = { 53.693182e06, 53.203425e06 }; //ntsc console, pal console
|
||||
// lpf[2][2] = { { 263, 262.5 }, { 314, 312.5 } }; //ntsc,pal; noninterlaced, interlaced
|
||||
// cpl[2] = { 3412.5, 3405 }; //ntsc mode, pal mode
|
||||
// PAL PS1: 0, PAL Mode: 0, Interlaced: 0 --- 59.826106 (53.693182e06/(263*3412.5))
|
||||
// PAL PS1: 0, PAL Mode: 0, Interlaced: 1 --- 59.940060 (53.693182e06/(262.5*3412.5))
|
||||
// PAL PS1: 1, PAL Mode: 1, Interlaced: 0 --- 49.761427 (53.203425e06/(314*3405))
|
||||
// PAL PS1: 1, PAL Mode: 1, Interlaced: 1 --- 50.000282(53.203425e06/(312.5*3405))
|
||||
};
|
||||
|
||||
public double this[string systemId, bool pal]
|
||||
{
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return new[]
|
||||
{
|
||||
".NES", ".FDS", ".UNF", ".SMS", ".GG", ".SG", ".GB", ".GBC", ".GBA", ".PCE", ".SGX", ".BIN", ".SMD", ".GEN", ".MD", ".SMC", ".SFC", ".A26", ".A78", ".LNX", ".COL", ".ROM", ".M3U", ".CUE", ".CCD", ".SGB", ".Z64", ".V64", ".N64", ".WS", ".WSC", ".XML", ".DSK", ".DO", ".PO", ".PSF", ".MINIPSF", ".NSF",
|
||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX", ".PZX", ".CSW", ".WAV"
|
||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX", ".PZX", ".CSW", ".WAV", ".CDT"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2086,7 +2086,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (VersionInfo.DeveloperBuild)
|
||||
{
|
||||
return FormatFilter(
|
||||
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;*.tzx;*.pzx;*.csw;*.wav;%ARCH%",
|
||||
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;*.tzx;*.pzx;*.csw;*.wav;*.cdt;%ARCH%",
|
||||
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
|
||||
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
|
||||
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
|
||||
|
@ -2115,6 +2115,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
"Virtual Boy", "*.vb;%ARCH%",
|
||||
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
|
||||
"Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;*.csw;*.wav;%ARCH%",
|
||||
"Amstrad CPC", "*.cdt;%ARCH%",
|
||||
"All Files", "*.*");
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
tt.TabPages[pageidx].Controls.Add(createpanel(settings, cat.Value, tt.Size));
|
||||
|
||||
// zxhawk hack - it uses multiple categoryLabels
|
||||
if (Global.Emulator.SystemId == "ZXSpectrum")
|
||||
if (Global.Emulator.SystemId == "ZXSpectrum" || Global.Emulator.SystemId == "AmstradCPC")
|
||||
pageidx++;
|
||||
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (buckets[0].Count > 0)
|
||||
{
|
||||
// ZXHawk needs to skip this bit
|
||||
if (Global.Emulator.SystemId == "ZXSpectrum")
|
||||
if (Global.Emulator.SystemId == "ZXSpectrum" || Global.Emulator.SystemId == "AmstradCPC")
|
||||
return;
|
||||
|
||||
string tabname = (Global.Emulator.SystemId == "C64") ? "Keyboard" : "Console"; // hack
|
||||
|
@ -272,7 +272,16 @@ namespace BizHawk.Client.EmuHawk
|
|||
pictureBox1.Size = Properties.Resources.ZXSpectrumKeyboards.Size;
|
||||
tableLayoutPanel1.ColumnStyles[1].Width = Properties.Resources.ZXSpectrumKeyboards.Width;
|
||||
}
|
||||
}
|
||||
|
||||
if (controlName == "AmstradCPC Controller")
|
||||
{
|
||||
/*
|
||||
pictureBox1.Image = Properties.Resources.ZXSpectrumKeyboards;
|
||||
pictureBox1.Size = Properties.Resources.ZXSpectrumKeyboards.Size;
|
||||
tableLayoutPanel1.ColumnStyles[1].Width = Properties.Resources.ZXSpectrumKeyboards.Width;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// lazy methods, but they're not called often and actually
|
||||
// tracking all of the ControllerConfigPanels wouldn't be simpler
|
||||
|
|
|
@ -52,8 +52,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
{ "GBC", "Game Boy Color" },
|
||||
{ "PCFX", "PC-FX" },
|
||||
{ "32X", "32X" },
|
||||
{ "ZXSpectrum", "ZX Spectrum" }
|
||||
};
|
||||
{ "ZXSpectrum", "ZX Spectrum" },
|
||||
{ "AmstradCPC", "Amstrad CPC" }
|
||||
};
|
||||
|
||||
public string TargetSystem = null;
|
||||
|
||||
|
|
|
@ -144,7 +144,8 @@
|
|||
"PCFX",
|
||||
"PSX",
|
||||
"SAT",
|
||||
"ZXSpectrum"});
|
||||
"ZXSpectrum",
|
||||
"AmstradCPC"});
|
||||
this.SystemDropDown.Location = new System.Drawing.Point(425, 75);
|
||||
this.SystemDropDown.Name = "SystemDropDown";
|
||||
this.SystemDropDown.Size = new System.Drawing.Size(69, 21);
|
||||
|
|
|
@ -310,6 +310,10 @@ namespace BizHawk.Emulation.Common
|
|||
game.System = "ZXSpectrum";
|
||||
break;
|
||||
|
||||
case ".CDT":
|
||||
game.System = "AmstradCPC";
|
||||
break;
|
||||
|
||||
case ".TAP":
|
||||
byte[] head = romData.Take(8).ToArray();
|
||||
if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE"))
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace BizHawk.Emulation.Common
|
|||
new SystemInfo { SystemId = "C64", FullName = "Commodore 64" },
|
||||
new SystemInfo { SystemId = "AppleII", FullName = "Apple II" },
|
||||
new SystemInfo { SystemId = "INTV", FullName = "Intellivision" },
|
||||
new SystemInfo { SystemId = "ZXSpectrum", FullName = "Sinclair ZX Spectrum" }
|
||||
new SystemInfo { SystemId = "ZXSpectrum", FullName = "Sinclair ZX Spectrum" },
|
||||
new SystemInfo { SystemId = "AmstradCPC", FullName = "Amstrad CPC" }
|
||||
};
|
||||
|
||||
public SystemInfo this[string systemId]
|
||||
|
|
|
@ -125,6 +125,58 @@
|
|||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Calculator\TI83LinkPort.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.Controllers.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.IDebuggable.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.IEmulator.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.InputPollable.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.ISettable.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.IStatable.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.Messaging.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IBeeperDevice.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IFDDHost.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IJoystick.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IKeyboard.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPortIODevice.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPSG.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\CRCT\CRCT_6845.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Datacorder\DatacorderDevice.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\CHRN.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.Definitions.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.FDC.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.FDD.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.IPortIODevice.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPD765.Timing.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Disk\NECUPS765.Static.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\Input\StandardKeyboard.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\AY38912.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\Beeper.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\PSG.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.GateArray.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Memory.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Port.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.Input.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.Media.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.Memory.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.Port.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\GateArrayBase.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\MachineType.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Machine\PPIBase.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Disk\CPCExtendedFloppyDisk.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Disk\CPCFloppyDisk.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Disk\DiskType.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Disk\FloppyDisk.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\MediaConverter.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\MediaConverterType.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Tape\TapeCommand.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Tape\TapeDataBlock.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\Media\Tape\CDT\CdtConverter.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\ROM\RomData.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\SoundProviderMixer.cs" />
|
||||
<Compile Include="Computers\AmstradCPC\AmstradCPC.IMemoryDomains.cs" />
|
||||
<Compile Include="Computers\AppleII\AppleII.cs" />
|
||||
<Compile Include="Computers\AppleII\AppleII.IDebuggable.cs">
|
||||
<DependentUpon>AppleII.cs</DependentUpon>
|
||||
|
@ -1435,6 +1487,7 @@
|
|||
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Port.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Screen.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.Screen.cs" />
|
||||
<None Include="Computers\AmstradCPC\readme.md" />
|
||||
<None Include="Computers\SinclairSpectrum\readme.md" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Media.cs" />
|
||||
<None Include="Consoles\Atari\docs\stella.pdf" />
|
||||
|
@ -1447,6 +1500,7 @@
|
|||
<None Include="Resources\128.ROM.gz" />
|
||||
<None Include="Resources\48.ROM.gz" />
|
||||
<None Include="Resources\cgb_boot.bin.gz" />
|
||||
<None Include="Resources\cpc464.rom.gz" />
|
||||
<None Include="Resources\dmg_boot.bin.gz" />
|
||||
<None Include="Resources\plus2.rom.gz" />
|
||||
<None Include="Resources\plus2a.rom.gz" />
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
using System.Collections.Generic;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * Controllers *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The one CPCHawk ControllerDefinition
|
||||
/// </summary>
|
||||
public ControllerDefinition AmstradCPCControllerDefinition
|
||||
{
|
||||
get
|
||||
{
|
||||
ControllerDefinition definition = new ControllerDefinition();
|
||||
definition.Name = "AmstradCPC Controller";
|
||||
|
||||
// joysticks
|
||||
List<string> joys1 = new List<string>
|
||||
{
|
||||
// P1 Joystick
|
||||
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Fire1", "P1 Fire2", "P1 Fire3"
|
||||
};
|
||||
|
||||
foreach (var s in joys1)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "J1";
|
||||
}
|
||||
|
||||
List<string> joys2 = new List<string>
|
||||
{
|
||||
// P2 Joystick
|
||||
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Fire",
|
||||
};
|
||||
|
||||
foreach (var s in joys2)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "J2";
|
||||
}
|
||||
/*
|
||||
List<string> joys3 = new List<string>
|
||||
{
|
||||
// P3 Joystick
|
||||
"P3 Up", "P3 Down", "P3 Left", "P3 Right", "P3 Button",
|
||||
};
|
||||
|
||||
foreach (var s in joys3)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "J3 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType3.ToString() + ")";
|
||||
}
|
||||
*/
|
||||
|
||||
// keyboard
|
||||
List<string> keys = new List<string>
|
||||
{
|
||||
/// http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
|
||||
/// http://www.cpcwiki.eu/index.php/File:Grimware_cpc464_version3_case_top.jpg
|
||||
|
||||
// Keyboard - row 1
|
||||
"Key ESC", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Dash", "Key Hat", "Key CLR", "Key DEL",
|
||||
// Keyboard - row 2
|
||||
"Key TAB", "Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P", "Key @", "Key LeftBracket", "Key RETURN",
|
||||
// Keyboard - row 3
|
||||
"Key CAPSLOCK", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Colon", "Key SemiColon", "Key RightBracket",
|
||||
// Keyboard - row 4
|
||||
"Key SHIFT", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Comma", "Key Period", "Key ForwardSlash", "Key BackSlash",
|
||||
// Keyboard - row 5
|
||||
"Key SPACE", "Key CONTROL",
|
||||
// Keyboard - Cursor
|
||||
"Key CURUP", "Key CURDOWN", "Key CURLEFT", "Key CURRIGHT", "Key COPY",
|
||||
// Keyboard - Numpad
|
||||
"Key NUM0", "Key NUM1", "Key NUM2", "Key NUM3", "Key NUM4", "Key NUM5", "Key NUM6", "Key NUM7", "Key NUM8", "Key NUM9", "Key NUMPERIOD", "KEY ENTER"
|
||||
};
|
||||
|
||||
foreach (var s in keys)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "Keyboard";
|
||||
}
|
||||
|
||||
// Power functions
|
||||
List<string> power = new List<string>
|
||||
{
|
||||
// Power functions
|
||||
"Reset", "Power"
|
||||
};
|
||||
|
||||
foreach (var s in power)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "Power";
|
||||
}
|
||||
|
||||
// Datacorder (tape device)
|
||||
List<string> tape = new List<string>
|
||||
{
|
||||
// Tape functions
|
||||
"Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape",
|
||||
"Insert Previous Tape", "Next Tape Block", "Prev Tape Block", "Get Tape Status"
|
||||
};
|
||||
|
||||
foreach (var s in tape)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "Datacorder";
|
||||
}
|
||||
|
||||
// Datacorder (tape device)
|
||||
List<string> disk = new List<string>
|
||||
{
|
||||
// Tape functions
|
||||
"Insert Next Disk", "Insert Previous Disk", /*"Eject Current Disk",*/ "Get Disk Status"
|
||||
};
|
||||
|
||||
foreach (var s in disk)
|
||||
{
|
||||
definition.BoolButtons.Add(s);
|
||||
definition.CategoryLabels[s] = "Amstrad Disk Drive";
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The possible joystick types
|
||||
/// </summary>
|
||||
public enum JoystickType
|
||||
{
|
||||
NULL,
|
||||
Joystick1,
|
||||
Joystick2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * IDebugggable *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC : IDebuggable
|
||||
{
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new Dictionary<string, RegisterValue>
|
||||
{
|
||||
["A"] = _cpu.Regs[_cpu.A],
|
||||
["AF"] = _cpu.Regs[_cpu.F] + (_cpu.Regs[_cpu.A] << 8),
|
||||
["B"] = _cpu.Regs[_cpu.B],
|
||||
["BC"] = _cpu.Regs[_cpu.C] + (_cpu.Regs[_cpu.B] << 8),
|
||||
["C"] = _cpu.Regs[_cpu.C],
|
||||
["D"] = _cpu.Regs[_cpu.D],
|
||||
["DE"] = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8),
|
||||
["E"] = _cpu.Regs[_cpu.E],
|
||||
["F"] = _cpu.Regs[_cpu.F],
|
||||
["H"] = _cpu.Regs[_cpu.H],
|
||||
["HL"] = _cpu.Regs[_cpu.L] + (_cpu.Regs[_cpu.H] << 8),
|
||||
["I"] = _cpu.Regs[_cpu.I],
|
||||
["IX"] = _cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8),
|
||||
["IY"] = _cpu.Regs[_cpu.Iyl] + (_cpu.Regs[_cpu.Iyh] << 8),
|
||||
["L"] = _cpu.Regs[_cpu.L],
|
||||
["PC"] = _cpu.Regs[_cpu.PCl] + (_cpu.Regs[_cpu.PCh] << 8),
|
||||
["R"] = _cpu.Regs[_cpu.R],
|
||||
["Shadow AF"] = _cpu.Regs[_cpu.F_s] + (_cpu.Regs[_cpu.A_s] << 8),
|
||||
["Shadow BC"] = _cpu.Regs[_cpu.C_s] + (_cpu.Regs[_cpu.B_s] << 8),
|
||||
["Shadow DE"] = _cpu.Regs[_cpu.E_s] + (_cpu.Regs[_cpu.D_s] << 8),
|
||||
["Shadow HL"] = _cpu.Regs[_cpu.L_s] + (_cpu.Regs[_cpu.H_s] << 8),
|
||||
["SP"] = _cpu.Regs[_cpu.Iyl] + (_cpu.Regs[_cpu.Iyh] << 8),
|
||||
["Flag C"] = _cpu.FlagC,
|
||||
["Flag N"] = _cpu.FlagN,
|
||||
["Flag P/V"] = _cpu.FlagP,
|
||||
["Flag 3rd"] = _cpu.Flag3,
|
||||
["Flag H"] = _cpu.FlagH,
|
||||
["Flag 5th"] = _cpu.Flag5,
|
||||
["Flag Z"] = _cpu.FlagZ,
|
||||
["Flag S"] = _cpu.FlagS
|
||||
};
|
||||
}
|
||||
|
||||
public void SetCpuRegister(string register, int value)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
case "A":
|
||||
_cpu.Regs[_cpu.A] = (ushort)value;
|
||||
break;
|
||||
case "AF":
|
||||
_cpu.Regs[_cpu.F] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.A] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "B":
|
||||
_cpu.Regs[_cpu.B] = (ushort)value;
|
||||
break;
|
||||
case "BC":
|
||||
_cpu.Regs[_cpu.C] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.B] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "C":
|
||||
_cpu.Regs[_cpu.C] = (ushort)value;
|
||||
break;
|
||||
case "D":
|
||||
_cpu.Regs[_cpu.D] = (ushort)value;
|
||||
break;
|
||||
case "DE":
|
||||
_cpu.Regs[_cpu.E] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.D] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "E":
|
||||
_cpu.Regs[_cpu.E] = (ushort)value;
|
||||
break;
|
||||
case "F":
|
||||
_cpu.Regs[_cpu.F] = (ushort)value;
|
||||
break;
|
||||
case "H":
|
||||
_cpu.Regs[_cpu.H] = (ushort)value;
|
||||
break;
|
||||
case "HL":
|
||||
_cpu.Regs[_cpu.L] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.H] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "I":
|
||||
_cpu.Regs[_cpu.I] = (ushort)value;
|
||||
break;
|
||||
case "IX":
|
||||
_cpu.Regs[_cpu.Ixl] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.Ixh] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "IY":
|
||||
_cpu.Regs[_cpu.Iyl] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.Iyh] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "L":
|
||||
_cpu.Regs[_cpu.L] = (ushort)value;
|
||||
break;
|
||||
case "PC":
|
||||
_cpu.Regs[_cpu.PCl] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.PCh] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "R":
|
||||
_cpu.Regs[_cpu.R] = (ushort)value;
|
||||
break;
|
||||
case "Shadow AF":
|
||||
_cpu.Regs[_cpu.F_s] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.A_s] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "Shadow BC":
|
||||
_cpu.Regs[_cpu.C_s] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.B_s] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "Shadow DE":
|
||||
_cpu.Regs[_cpu.E_s] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.D_s] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "Shadow HL":
|
||||
_cpu.Regs[_cpu.L_s] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.H_s] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
case "SP":
|
||||
_cpu.Regs[_cpu.SPl] = (ushort)(value & 0xFF);
|
||||
_cpu.Regs[_cpu.SPh] = (ushort)(value & 0xFF00);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; }
|
||||
|
||||
public bool CanStep(StepType type) => false;
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void Step(StepType type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public long TotalExecutedCycles => _cpu.TotalExecutedCycles;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * IEmulator *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC : IEmulator
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get; set; }
|
||||
|
||||
public void FrameAdvance(IController controller, bool render, bool renderSound)
|
||||
{
|
||||
_controller = controller;
|
||||
|
||||
bool ren = render;
|
||||
bool renSound = renderSound;
|
||||
|
||||
if (DeterministicEmulation)
|
||||
{
|
||||
ren = true;
|
||||
renSound = true;
|
||||
}
|
||||
|
||||
_isLag = true;
|
||||
|
||||
if (_tracer.Enabled)
|
||||
{
|
||||
_cpu.TraceCallback = s => _tracer.Put(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cpu.TraceCallback = null;
|
||||
}
|
||||
|
||||
_machine.ExecuteFrame(ren, renSound);
|
||||
|
||||
if (_isLag)
|
||||
{
|
||||
_lagCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public int Frame
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_machine == null)
|
||||
return 0;
|
||||
else
|
||||
return _machine.FrameCount;
|
||||
}
|
||||
}
|
||||
|
||||
public string SystemId => "AmstradCPC";
|
||||
|
||||
private bool deterministicEmulation;
|
||||
public bool DeterministicEmulation
|
||||
{
|
||||
get { return deterministicEmulation; }
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
_machine.FrameCount = 0;
|
||||
_lagCount = 0;
|
||||
_isLag = false;
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_machine != null)
|
||||
{
|
||||
_machine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * Memory Domains *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC
|
||||
{
|
||||
internal IMemoryDomains memoryDomains;
|
||||
private readonly Dictionary<string, MemoryDomainByteArray> _byteArrayDomains = new Dictionary<string, MemoryDomainByteArray>();
|
||||
private bool _memoryDomainsInit = false;
|
||||
|
||||
private void SetupMemoryDomains()
|
||||
{
|
||||
var domains = new List<MemoryDomain>
|
||||
{
|
||||
new MemoryDomainDelegate("System Bus", 0x10000, MemoryDomain.Endian.Little,
|
||||
(addr) =>
|
||||
{
|
||||
if (addr < 0 || addr >= 65536)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
return _machine.ReadBus((ushort)addr);
|
||||
},
|
||||
(addr, value) =>
|
||||
{
|
||||
if (addr < 0 || addr >= 65536)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_machine.WriteBus((ushort)addr, value);
|
||||
}, 1)
|
||||
};
|
||||
|
||||
SyncAllByteArrayDomains();
|
||||
|
||||
memoryDomains = new MemoryDomainList(_byteArrayDomains.Values.Concat(domains).ToList());
|
||||
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(memoryDomains);
|
||||
|
||||
_memoryDomainsInit = true;
|
||||
}
|
||||
|
||||
private void SyncAllByteArrayDomains()
|
||||
{
|
||||
SyncByteArrayDomain("ROM0", _machine.ROM0);
|
||||
SyncByteArrayDomain("ROM1", _machine.ROM1);
|
||||
SyncByteArrayDomain("RAM0", _machine.RAM0);
|
||||
SyncByteArrayDomain("RAM1", _machine.RAM1);
|
||||
SyncByteArrayDomain("RAM2", _machine.RAM2);
|
||||
SyncByteArrayDomain("RAM3", _machine.RAM3);
|
||||
}
|
||||
|
||||
private void SyncByteArrayDomain(string name, byte[] data)
|
||||
{
|
||||
|
||||
if (_memoryDomainsInit || _byteArrayDomains.ContainsKey(name))
|
||||
{
|
||||
var m = _byteArrayDomains[name];
|
||||
m.Data = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
var m = new MemoryDomainByteArray(name, MemoryDomain.Endian.Little, data, true, 1);
|
||||
_byteArrayDomains.Add(name, m);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * ISettable *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC : ISettable<AmstradCPC.AmstradCPCSettings, AmstradCPC.AmstradCPCSyncSettings>
|
||||
{
|
||||
internal AmstradCPCSettings Settings = new AmstradCPCSettings();
|
||||
internal AmstradCPCSyncSettings SyncSettings = new AmstradCPCSyncSettings();
|
||||
|
||||
public AmstradCPCSettings GetSettings()
|
||||
{
|
||||
return Settings.Clone();
|
||||
}
|
||||
|
||||
public AmstradCPCSyncSettings GetSyncSettings()
|
||||
{
|
||||
return SyncSettings.Clone();
|
||||
}
|
||||
|
||||
public bool PutSettings(AmstradCPCSettings o)
|
||||
{
|
||||
|
||||
// restore user settings to devices
|
||||
if (_machine != null && _machine.AYDevice != null)
|
||||
{
|
||||
((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = o.AYPanConfig;
|
||||
_machine.AYDevice.Volume = o.AYVolume;
|
||||
}
|
||||
if (_machine != null && _machine.TapeBuzzer != null)
|
||||
{
|
||||
((Beeper)_machine.TapeBuzzer as Beeper).Volume = o.TapeVolume;
|
||||
}
|
||||
|
||||
|
||||
Settings = o;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PutSyncSettings(AmstradCPCSyncSettings o)
|
||||
{
|
||||
bool ret = AmstradCPCSyncSettings.NeedsReboot(SyncSettings, o);
|
||||
SyncSettings = o;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public class AmstradCPCSettings
|
||||
{
|
||||
[DisplayName("AY-3-8912 Panning Config")]
|
||||
[Description("Set the PSG panning configuration.\nThe chip has 3 audio channels that can be outputed in different configurations")]
|
||||
[DefaultValue(AY38912.AYPanConfig.ABC)]
|
||||
public AY38912.AYPanConfig AYPanConfig { get; set; }
|
||||
|
||||
[DisplayName("AY-3-8912 Volume")]
|
||||
[Description("The AY chip volume")]
|
||||
[DefaultValue(75)]
|
||||
public int AYVolume { get; set; }
|
||||
|
||||
[DisplayName("Core OSD Message Verbosity")]
|
||||
[Description("Full: Display all GUI messages\nMedium: Display only emulator/device generated messages\nNone: Show no messages")]
|
||||
[DefaultValue(OSDVerbosity.Medium)]
|
||||
public OSDVerbosity OSDMessageVerbosity { get; set; }
|
||||
|
||||
[DisplayName("Tape Loading Volume")]
|
||||
[Description("The buzzer volume when the tape is playing")]
|
||||
[DefaultValue(50)]
|
||||
public int TapeVolume { get; set; }
|
||||
|
||||
public AmstradCPCSettings Clone()
|
||||
{
|
||||
return (AmstradCPCSettings)MemberwiseClone();
|
||||
}
|
||||
|
||||
public AmstradCPCSettings()
|
||||
{
|
||||
BizHawk.Common.SettingsUtil.SetDefaultValues(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class AmstradCPCSyncSettings
|
||||
{
|
||||
[DisplayName("Deterministic Emulation")]
|
||||
[Description("If true, the core agrees to behave in a completely deterministic manner")]
|
||||
[DefaultValue(true)]
|
||||
public bool DeterministicEmulation { get; set; }
|
||||
|
||||
[DisplayName("CPC model")]
|
||||
[Description("The model of Amstrad CPC machine to be emulated")]
|
||||
[DefaultValue(MachineType.CPC464)]
|
||||
public MachineType MachineType { get; set; }
|
||||
|
||||
public AmstradCPCSyncSettings Clone()
|
||||
{
|
||||
return (AmstradCPCSyncSettings)MemberwiseClone();
|
||||
}
|
||||
|
||||
public AmstradCPCSyncSettings()
|
||||
{
|
||||
BizHawk.Common.SettingsUtil.SetDefaultValues(this);
|
||||
}
|
||||
|
||||
public static bool NeedsReboot(AmstradCPCSyncSettings x, AmstradCPCSyncSettings y)
|
||||
{
|
||||
return !DeepEquality.DeepEquals(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verbosity of the CPCHawk generated OSD messages
|
||||
/// </summary>
|
||||
public enum OSDVerbosity
|
||||
{
|
||||
/// <summary>
|
||||
/// Show all OSD messages
|
||||
/// </summary>
|
||||
Full,
|
||||
/// <summary>
|
||||
/// Only show machine/device generated messages
|
||||
/// </summary>
|
||||
Medium,
|
||||
/// <summary>
|
||||
/// No core-driven OSD messages
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System.IO;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * IStatable *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC : IStatable
|
||||
{
|
||||
|
||||
public bool BinarySaveStatesPreferred
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
SyncState(new Serializer(writer));
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
SyncState(new Serializer(reader));
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter bw)
|
||||
{
|
||||
SyncState(new Serializer(bw));
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader br)
|
||||
{
|
||||
SyncState(new Serializer(br));
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
SaveStateBinary(bw);
|
||||
bw.Flush();
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private void SyncState(Serializer ser)
|
||||
{
|
||||
byte[] core = null;
|
||||
if (ser.IsWriter)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Close();
|
||||
core = ms.ToArray();
|
||||
}
|
||||
|
||||
if (ser.IsWriter)
|
||||
{
|
||||
ser.SyncEnum("_machineType", ref _machineType);
|
||||
|
||||
_cpu.SyncState(ser);
|
||||
ser.BeginSection("AmstradCPC");
|
||||
_machine.SyncState(ser);
|
||||
ser.Sync("Frame", ref _machine.FrameCount);
|
||||
ser.Sync("LagCount", ref _lagCount);
|
||||
ser.Sync("IsLag", ref _isLag);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
if (ser.IsReader)
|
||||
{
|
||||
var tmpM = _machineType;
|
||||
ser.SyncEnum("_machineType", ref _machineType);
|
||||
if (tmpM != _machineType && _machineType.ToString() != "72")
|
||||
{
|
||||
string msg = "SAVESTATE FAILED TO LOAD!!\n\n";
|
||||
msg += "Current Configuration: " + tmpM.ToString();
|
||||
msg += "\n";
|
||||
msg += "Saved Configuration: " + _machineType.ToString();
|
||||
msg += "\n\n";
|
||||
msg += "If you wish to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again.";
|
||||
CoreComm.ShowMessage(msg);
|
||||
_machineType = tmpM;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cpu.SyncState(ser);
|
||||
ser.BeginSection("AmstradCPC");
|
||||
_machine.SyncState(ser);
|
||||
ser.Sync("Frame", ref _machine.FrameCount);
|
||||
ser.Sync("LagCount", ref _lagCount);
|
||||
ser.Sync("IsLag", ref _isLag);
|
||||
ser.EndSection();
|
||||
|
||||
SyncAllByteArrayDomains();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * IInputPollable *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC : IInputPollable
|
||||
{
|
||||
public int LagCount
|
||||
{
|
||||
get { return _lagCount; }
|
||||
set { _lagCount = value; }
|
||||
}
|
||||
|
||||
public bool IsLagFrame
|
||||
{
|
||||
get { return _isLag; }
|
||||
set { _isLag = value; }
|
||||
}
|
||||
|
||||
public IInputCallbackSystem InputCallbacks { get; }
|
||||
|
||||
private int _lagCount = 0;
|
||||
private bool _isLag = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,495 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * Handles all messaging (OSD) operations *
|
||||
/// </summary>
|
||||
public partial class AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a message to the OSD
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="category"></param>
|
||||
public void SendMessage(string message, MessageCategory category)
|
||||
{
|
||||
if (!CheckMessageSettings(category))
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case MessageCategory.Tape:
|
||||
sb.Append("DATACORDER: ");
|
||||
sb.Append(message);
|
||||
break;
|
||||
case MessageCategory.Input:
|
||||
sb.Append("INPUT DETECTED: ");
|
||||
sb.Append(message);
|
||||
break;
|
||||
case MessageCategory.Disk:
|
||||
sb.Append("DISK DRIVE: ");
|
||||
sb.Append(message);
|
||||
break;
|
||||
case MessageCategory.Emulator:
|
||||
case MessageCategory.Misc:
|
||||
sb.Append("CPCHAWK: ");
|
||||
sb.Append(message);
|
||||
break;
|
||||
}
|
||||
|
||||
CoreComm.Notify(sb.ToString());
|
||||
}
|
||||
|
||||
#region Input Message Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when certain input presses are detected
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
public void OSD_FireInputMessage(string input)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(input);
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Input);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DiskDevice Message Methods
|
||||
|
||||
/// <summary>
|
||||
/// Disk message that is fired on core init
|
||||
/// </summary>
|
||||
public void OSD_DiskInit()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (_machine.diskImages != null && _machine.UPDDiskDevice != null)
|
||||
{
|
||||
sb.Append("Disk Media Imported (count: " + _machine.diskImages.Count() + ")");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disk message that is fired when a new disk is inserted into the drive
|
||||
/// </summary>
|
||||
public void OSD_DiskInserted()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_machine.UPDDiskDevice == null)
|
||||
{
|
||||
sb.Append("No Drive Present");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
return;
|
||||
}
|
||||
|
||||
sb.Append("DISK INSERTED (" + _machine.DiskMediaIndex + ": " + _diskInfo[_machine.DiskMediaIndex].Name + ")");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that prints the current status of the tape device
|
||||
/// </summary>
|
||||
public void OSD_ShowDiskStatus()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_machine.UPDDiskDevice == null)
|
||||
{
|
||||
sb.Append("No Drive Present");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Disk Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_machine.UPDDiskDevice != null)
|
||||
{
|
||||
if (_machine.UPDDiskDevice.DiskPointer == null)
|
||||
{
|
||||
sb.Append("No Disk Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
return;
|
||||
}
|
||||
|
||||
sb.Append("Disk: " + _machine.DiskMediaIndex + ": " + _diskInfo[_machine.DiskMediaIndex].Name);
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
sb.Clear();
|
||||
/*
|
||||
string protection = "None";
|
||||
protection = Enum.GetName(typeof(ProtectionType), _machine.UPDDiskDevice.DiskPointer.Protection);
|
||||
if (protection == "None")
|
||||
protection += " (OR UNKNOWN)";
|
||||
|
||||
sb.Append("Detected Protection: " + protection);
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
sb.Clear();
|
||||
*/
|
||||
|
||||
sb.Append("Status: ");
|
||||
|
||||
if (_machine.UPDDiskDevice.DriveLight)
|
||||
sb.Append("READING/WRITING DATA");
|
||||
else
|
||||
sb.Append("UNKNOWN");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
|
||||
sb.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TapeDevice Message Methods
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired on core init
|
||||
/// </summary>
|
||||
public void OSD_TapeInit()
|
||||
{
|
||||
if (_tapeInfo.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Tape Media Imported (count: " + _tapeInfo.Count() + ")");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when tape is playing
|
||||
/// </summary>
|
||||
public void OSD_TapePlaying()
|
||||
{
|
||||
if (_tapeInfo.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when tape is stopped
|
||||
/// </summary>
|
||||
public void OSD_TapeStopped()
|
||||
{
|
||||
if (_tapeInfo.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when tape is rewound
|
||||
/// </summary>
|
||||
public void OSD_TapeRTZ()
|
||||
{
|
||||
if (_tapeInfo.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a new tape is inserted into the datacorder
|
||||
/// </summary>
|
||||
public void OSD_TapeInserted()
|
||||
{
|
||||
if (_tapeInfo.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a tape is stopped automatically
|
||||
/// </summary>
|
||||
public void OSD_TapeStoppedAuto()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("STOPPED (Auto Tape Trap Detected)");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a tape is started automatically
|
||||
/// </summary>
|
||||
public void OSD_TapePlayingAuto()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("PLAYING (Auto Tape Trap Detected)");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a new block starts playing
|
||||
/// </summary>
|
||||
public void OSD_TapePlayingBlockInfo(string blockinfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("...Starting Block " + blockinfo);
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a tape block is skipped (because it is empty)
|
||||
/// </summary>
|
||||
public void OSD_TapePlayingSkipBlockInfo(string blockinfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("...Skipping Empty Block " + blockinfo);
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when a tape is started automatically
|
||||
/// </summary>
|
||||
public void OSD_TapeEndDetected(string blockinfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("...Skipping Empty Block " + blockinfo);
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when user has manually skipped to the next block
|
||||
/// </summary>
|
||||
public void OSD_TapeNextBlock(string blockinfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("Manual Skip Next " + blockinfo);
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that is fired when user has manually skipped to the next block
|
||||
/// </summary>
|
||||
public void OSD_TapePrevBlock(string blockinfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sb.Append("Manual Skip Prev " + blockinfo);
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tape message that prints the current status of the tape device
|
||||
/// </summary>
|
||||
public void OSD_ShowTapeStatus()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (_tapeInfo.Count == 0)
|
||||
{
|
||||
sb.Append("No Tape Loaded");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
return;
|
||||
}
|
||||
|
||||
sb.Append("Status: ");
|
||||
|
||||
if (_machine.TapeDevice.TapeIsPlaying)
|
||||
sb.Append("PLAYING");
|
||||
else
|
||||
sb.Append("STOPPED");
|
||||
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
sb.Clear();
|
||||
|
||||
sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name);
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
sb.Clear();
|
||||
|
||||
sb.Append("Block: ");
|
||||
sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) +
|
||||
" of " + _machine.TapeDevice.DataBlocks.Count() + ") " +
|
||||
_machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].BlockDescription);
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
sb.Clear();
|
||||
|
||||
sb.Append("Block Pos: ");
|
||||
|
||||
int pos = _machine.TapeDevice.Position;
|
||||
int end = _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].DataPeriods.Count;
|
||||
double p = 0;
|
||||
if (end != 0)
|
||||
p = ((double)pos / (double)end) * (double)100;
|
||||
|
||||
sb.Append(p.ToString("N0") + "%");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
sb.Clear();
|
||||
|
||||
// get position within the tape itself
|
||||
sb.Append("Tape Pos: ");
|
||||
var ind = _machine.TapeDevice.CurrentDataBlockIndex;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < ind; i++)
|
||||
{
|
||||
cnt += _machine.TapeDevice.DataBlocks[i].DataPeriods.Count;
|
||||
}
|
||||
// now we are at our current block
|
||||
int ourPos = cnt + pos;
|
||||
cnt += end;
|
||||
// count periods in the remaining blocks
|
||||
for (int i = ind + 1; i < _machine.TapeDevice.DataBlocks.Count; i++)
|
||||
{
|
||||
cnt += _machine.TapeDevice.DataBlocks[i].DataPeriods.Count;
|
||||
}
|
||||
// work out overall position within the tape
|
||||
p = 0;
|
||||
p = ((double)ourPos / (double)cnt) * (double)100;
|
||||
sb.Append(p.ToString("N0") + "%");
|
||||
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether message category is allowed to be sent
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckMessageSettings(MessageCategory category)
|
||||
{
|
||||
switch (Settings.OSDMessageVerbosity)
|
||||
{
|
||||
case OSDVerbosity.Full:
|
||||
return true;
|
||||
case OSDVerbosity.None:
|
||||
return false;
|
||||
case OSDVerbosity.Medium:
|
||||
switch (category)
|
||||
{
|
||||
case MessageCategory.Disk:
|
||||
case MessageCategory.Emulator:
|
||||
case MessageCategory.Tape:
|
||||
case MessageCategory.Misc:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different message categories
|
||||
/// </summary>
|
||||
public enum MessageCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// No defined category as such
|
||||
/// </summary>
|
||||
Misc,
|
||||
/// <summary>
|
||||
/// User generated input messages (at the moment only tape/disk controls)
|
||||
/// </summary>
|
||||
Input,
|
||||
/// <summary>
|
||||
/// Tape device generated messages
|
||||
/// </summary>
|
||||
Tape,
|
||||
/// <summary>
|
||||
/// Disk device generated messages
|
||||
/// </summary>
|
||||
Disk,
|
||||
/// <summary>
|
||||
/// Emulator generated messages
|
||||
/// </summary>
|
||||
Emulator
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using BizHawk.Emulation.Cores.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPCHawk: Core Class
|
||||
/// * Main Initialization *
|
||||
/// </summary>
|
||||
[Core(
|
||||
"CPCHawk",
|
||||
"Asnivor",
|
||||
isPorted: false,
|
||||
isReleased: false)]
|
||||
public partial class AmstradCPC : IRegionable, IDriveLight
|
||||
{
|
||||
public AmstradCPC(CoreComm comm, IEnumerable<byte[]> files, List<GameInfo> game, object settings, object syncSettings)
|
||||
{
|
||||
var ser = new BasicServiceProvider(this);
|
||||
ServiceProvider = ser;
|
||||
InputCallbacks = new InputCallbackSystem();
|
||||
MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
CoreComm = comm;
|
||||
_gameInfo = game;
|
||||
_cpu = new Z80A();
|
||||
_tracer = new TraceBuffer { Header = _cpu.TraceHeader };
|
||||
_files = files?.ToList() ?? new List<byte[]>();
|
||||
|
||||
if (settings == null)
|
||||
settings = new AmstradCPCSettings();
|
||||
if (syncSettings == null)
|
||||
syncSettings = new AmstradCPCSyncSettings();
|
||||
|
||||
PutSyncSettings((AmstradCPCSyncSettings)syncSettings ?? new AmstradCPCSyncSettings());
|
||||
PutSettings((AmstradCPCSettings)settings ?? new AmstradCPCSettings());
|
||||
|
||||
deterministicEmulation = ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).DeterministicEmulation;
|
||||
|
||||
switch (SyncSettings.MachineType)
|
||||
{
|
||||
case MachineType.CPC464:
|
||||
ControllerDefinition = AmstradCPCControllerDefinition;
|
||||
Init(MachineType.CPC464, _files);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Machine not yet emulated");
|
||||
}
|
||||
|
||||
_cpu.MemoryCallbacks = MemoryCallbacks;
|
||||
|
||||
HardReset = _machine.HardReset;
|
||||
SoftReset = _machine.SoftReset;
|
||||
|
||||
_cpu.FetchMemory = _machine.ReadMemory;
|
||||
_cpu.ReadMemory = _machine.ReadMemory;
|
||||
_cpu.WriteMemory = _machine.WriteMemory;
|
||||
_cpu.ReadHardware = _machine.ReadPort;
|
||||
_cpu.WriteHardware = _machine.WritePort;
|
||||
_cpu.FetchDB = _machine.PushBus;
|
||||
//_cpu.OnExecFetch = _machine.CPUMon.OnExecFetch;
|
||||
|
||||
ser.Register<ITraceable>(_tracer);
|
||||
ser.Register<IDisassemblable>(_cpu);
|
||||
ser.Register<IVideoProvider>(_machine.GateArray);
|
||||
|
||||
// initialize sound mixer and attach the various ISoundProvider devices
|
||||
SoundMixer = new SoundProviderMixer((int)(32767 / 10), "Tape Audio", (ISoundProvider)_machine.TapeBuzzer);
|
||||
if (_machine.AYDevice != null)
|
||||
SoundMixer.AddSource(_machine.AYDevice, "AY-3-3912");
|
||||
|
||||
// set audio device settings
|
||||
if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AY38912))
|
||||
{
|
||||
((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = ((AmstradCPCSettings)settings as AmstradCPCSettings).AYPanConfig;
|
||||
_machine.AYDevice.Volume = ((AmstradCPCSettings)settings as AmstradCPCSettings).AYVolume;
|
||||
}
|
||||
|
||||
if (_machine.TapeBuzzer != null)
|
||||
{
|
||||
((Beeper)_machine.TapeBuzzer as Beeper).Volume = ((AmstradCPCSettings)settings as AmstradCPCSettings).TapeVolume;
|
||||
}
|
||||
|
||||
ser.Register<ISoundProvider>(SoundMixer);
|
||||
|
||||
HardReset();
|
||||
SetupMemoryDomains();
|
||||
}
|
||||
|
||||
public Action HardReset;
|
||||
public Action SoftReset;
|
||||
|
||||
private readonly Z80A _cpu;
|
||||
private readonly TraceBuffer _tracer;
|
||||
public IController _controller;
|
||||
public CPCBase _machine;
|
||||
|
||||
public List<GameInfo> _gameInfo;
|
||||
public List<GameInfo> _tapeInfo = new List<GameInfo>();
|
||||
public List<GameInfo> _diskInfo = new List<GameInfo>();
|
||||
|
||||
private SoundProviderMixer SoundMixer;
|
||||
|
||||
private readonly List<byte[]> _files;
|
||||
|
||||
private byte[] GetFirmware(int length, params string[] names)
|
||||
{
|
||||
// Amstrad licensed ROMs are free to distribute and shipped with BizHawk
|
||||
byte[] embeddedRom = new byte[length];
|
||||
bool embeddedFound = true;
|
||||
switch (names.FirstOrDefault())
|
||||
{
|
||||
case "464ROM":
|
||||
embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.cpc464_rom));
|
||||
break;
|
||||
default:
|
||||
embeddedFound = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (embeddedFound)
|
||||
return embeddedRom;
|
||||
|
||||
// Embedded ROM not found, maybe this is a peripheral ROM?
|
||||
var result = names.Select(n => CoreComm.CoreFileProvider.GetFirmware("AmstradCPC", n, false)).FirstOrDefault(b => b != null && b.Length == length);
|
||||
if (result == null)
|
||||
{
|
||||
throw new MissingFirmwareException($"At least one of these firmwares is required: {string.Join(", ", names)}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MachineType _machineType;
|
||||
|
||||
private void Init(MachineType machineType, List<byte[]> files)
|
||||
{
|
||||
_machineType = machineType;
|
||||
|
||||
// setup the emulated model based on the MachineType
|
||||
switch (machineType)
|
||||
{
|
||||
case MachineType.CPC464:
|
||||
_machine = new CPC464(this, _cpu, files);
|
||||
var _systemRom16 = GetFirmware(0x4000, "464ROM");
|
||||
var romData16 = RomData.InitROM(machineType, _systemRom16);
|
||||
_machine.InitROM(romData16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region IRegionable
|
||||
|
||||
public DisplayType Region => DisplayType.PAL;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDriveLight
|
||||
|
||||
public bool DriveLightEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DriveLightOn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_machine != null &&
|
||||
(_machine.TapeDevice != null && _machine.TapeDevice.TapeIsPlaying) ||
|
||||
(_machine.UPDDiskDevice != null && _machine.UPDDiskDevice.DriveLight))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a beeper/buzzer device
|
||||
/// </summary>
|
||||
public interface IBeeperDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialisation
|
||||
/// </summary>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="tStatesPerFrame"></param>
|
||||
void Init(int sampleRate, int tStatesPerFrame);
|
||||
|
||||
/// <summary>
|
||||
/// Processes an incoming pulse value and adds it to the blipbuffer
|
||||
/// </summary>
|
||||
/// <param name="pulse"></param>
|
||||
void ProcessPulseValue(bool pulse);
|
||||
|
||||
/// <summary>
|
||||
/// State serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
void SyncState(Serializer ser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an object that can load a floppy disk image
|
||||
/// </summary>
|
||||
public interface IFDDHost
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently inserted diskimage
|
||||
/// </summary>
|
||||
FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
/// <param name="diskData"></param>
|
||||
void FDD_LoadDisk(byte[] diskData);
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
void FDD_EjectDisk();
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
bool FDD_IsDiskLoaded { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a spectrum joystick
|
||||
/// </summary>
|
||||
public interface IJoystick
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of joystick
|
||||
/// </summary>
|
||||
JoystickType JoyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of all the possibly button press names
|
||||
/// </summary>
|
||||
string[] ButtonCollection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The player number that this controller is currently assigned to
|
||||
/// </summary>
|
||||
int PlayerNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the joystick line based on key pressed
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isPressed"></param>
|
||||
void SetJoyInput(string key, bool isPressed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of a particular joystick binding
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
bool GetJoyInput(string key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a spectrum keyboard
|
||||
/// </summary>
|
||||
public interface IKeyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// The calling spectrumbase class
|
||||
/// </summary>
|
||||
CPCBase _machine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The keyboard matrix for a particular CPC model
|
||||
/// </summary>
|
||||
string[] KeyboardMatrix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Other keyboard keys that are not in the matrix
|
||||
/// (usually keys derived from key combos)
|
||||
/// </summary>
|
||||
string[] NonMatrixKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the spectrum key state
|
||||
/// </summary>
|
||||
bool[] KeyStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected line
|
||||
/// </summary>
|
||||
int CurrentLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the current line status
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
byte ReadCurrentLine();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the CPC key status
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isPressed"></param>
|
||||
void SetKeyStatus(string key, bool isPressed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of a CPC key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
bool GetKeyStatus(string key);
|
||||
|
||||
void SyncState(Serializer ser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a PSG device (in this case an AY-3-891x)
|
||||
/// </summary>
|
||||
public interface IPSG : ISoundProvider, IPortIODevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Initlization routine
|
||||
/// </summary>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="tStatesPerFrame"></param>
|
||||
void Init(int sampleRate, int tStatesPerFrame);
|
||||
|
||||
/// <summary>
|
||||
/// Activates a register
|
||||
/// </summary>
|
||||
int SelectedRegister { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the PSG
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void PortWrite(int value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads from the PSG
|
||||
/// </summary>
|
||||
int PortRead();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets the PSG
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// The volume of the AY chip
|
||||
/// </summary>
|
||||
int Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of a frame
|
||||
/// </summary>
|
||||
void StartFrame();
|
||||
|
||||
/// <summary>
|
||||
/// called at the end of a frame
|
||||
/// </summary>
|
||||
void EndFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the sound based on number of frame cycles
|
||||
/// </summary>
|
||||
/// <param name="frameCycle"></param>
|
||||
void UpdateSound(int frameCycle);
|
||||
|
||||
/// <summary>
|
||||
/// IStatable serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
void SyncState(Serializer ser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a device that utilizes port IN & OUT
|
||||
/// </summary>
|
||||
public interface IPortIODevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
bool ReadPort(ushort port, ref int result);
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
bool WritePort(ushort port, int result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Cathode Ray Tube Controller Chip - 6845
|
||||
/// http://www.cpcwiki.eu/index.php/CRTC
|
||||
///
|
||||
/// </summary>
|
||||
public class CRCT_6845 : IPortIODevice
|
||||
{
|
||||
#region Devices
|
||||
|
||||
private CPCBase _machine { get; set; }
|
||||
private CRCTType ChipType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public CRCT_6845(CRCTType chipType, CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
ChipType = chipType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
/*
|
||||
Index Register Name Range CPC Setting Notes
|
||||
0 Horizontal Total 00000000 63 Width of the screen, in characters. Should always be 63 (64 characters). 1 character == 1μs.
|
||||
1 Horizontal Displayed 00000000 40 Number of characters displayed. Once horizontal character count (HCC) matches this value, DISPTMG is set to 1.
|
||||
2 Horizontal Sync Position 00000000 46 When to start the HSync signal.
|
||||
3 Horizontal and Vertical Sync Widths VVVVHHHH 128+14 HSync pulse width in characters (0 means 16 on some CRTC), should always be more than 8; VSync width in scan-lines. (0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)
|
||||
4 Vertical Total x0000000 38 Height of the screen, in characters.
|
||||
5 Vertical Total Adjust xxx00000 0 Measured in scanlines, can be used for smooth vertical scrolling on CPC.
|
||||
6 Vertical Displayed x0000000 25 Height of displayed screen in characters. Once vertical character count (VCC) matches this value, DISPTMG is set to 1.
|
||||
7 Vertical Sync position x0000000 30 When to start the VSync signal, in characters.
|
||||
8 Interlace and Skew xxxxxx00 0 00: No interlace; 01: Interlace Sync Raster Scan Mode; 10: No Interlace; 11: Interlace Sync and Video Raster Scan Mode
|
||||
9 Maximum Raster Address xxx00000 7 Maximum scan line address on CPC can hold between 0 and 7, higher values' upper bits are ignored
|
||||
10 Cursor Start Raster xBP00000 0 Cursor not used on CPC. B = Blink On/Off; P = Blink Period Control (Slow/Fast). Sets first raster row of character that cursor is on to invert.
|
||||
11 Cursor End Raster xxx00000 0 Sets last raster row of character that cursor is on to invert
|
||||
12 Display Start Address (High) xx000000 32
|
||||
13 Display Start Address (Low) 00000000 0 Allows you to offset the start of screen memory for hardware scrolling, and if using memory from address &0000 with the firmware.
|
||||
14 Cursor Address (High) xx000000 0
|
||||
15 Cursor Address (Low) 00000000 0
|
||||
16 Light Pen Address (High) xx000000 Read Only
|
||||
17 Light Pen Address (Low) 00000000 Read Only
|
||||
*/
|
||||
/// <summary>
|
||||
/// 6845 internal registers
|
||||
/// </summary>
|
||||
private byte[] Regs = new byte[18];
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected register
|
||||
/// </summary>
|
||||
private int SelectedRegister;
|
||||
|
||||
/// <summary>
|
||||
/// CPC register default values
|
||||
/// </summary>
|
||||
private byte[] RegDefaults = new byte[] { 63, 40, 52, 52, 20, 8, 16, 19, 0, 11, 73, 10, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
/// <summary>
|
||||
/// Register masks
|
||||
/// </summary>
|
||||
private byte[] CPCMask = new byte[] { 255, 255, 255, 255, 127, 31, 127, 126, 3, 31, 31, 31, 63, 255, 63, 255, 63, 255 };
|
||||
|
||||
/// <summary>
|
||||
/// CRTC Register constants
|
||||
/// </summary>
|
||||
public const int HOR_TOTAL = 0;
|
||||
public const int HOR_DISPLAYED = 1;
|
||||
public const int HOR_SYNC_POS = 2;
|
||||
public const int HOR_AND_VER_SYNC_WIDTHS = 3;
|
||||
public const int VER_TOTAL = 4;
|
||||
public const int VER_TOTAL_ADJUST = 5;
|
||||
public const int VER_DISPLAYED = 6;
|
||||
public const int VER_SYNC_POS = 7;
|
||||
public const int INTERLACE_SKEW = 8;
|
||||
public const int MAX_RASTER_ADDR = 9;
|
||||
public const int CUR_START_RASTER = 10;
|
||||
public const int CUR_END_RASTER = 11;
|
||||
public const int DISP_START_ADDR_H = 12;
|
||||
public const int DISP_START_ADDR_L = 13;
|
||||
public const int CUR_ADDR_H = 14;
|
||||
public const int CUR_ADDR_L = 15;
|
||||
public const int LPEN_ADDR_H = 16;
|
||||
public const int LPEN_ADDR_L = 17;
|
||||
|
||||
public int horSyncWidth;
|
||||
public int verSyncWidth;
|
||||
|
||||
public int currCol;
|
||||
public int currLineInRow;
|
||||
public int currRow;
|
||||
|
||||
public bool isHSYNC;
|
||||
public bool isVSYNC;
|
||||
|
||||
public int HSYNCcnt;
|
||||
public int VSYNCcnt;
|
||||
|
||||
public bool DISPTMG;
|
||||
|
||||
private long LastTCycle;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// The CRTC runs at 1MHz, so effectively every 4 T-States
|
||||
/// </summary>
|
||||
public void CycleClock()
|
||||
{
|
||||
if (HSYNCcnt > 0)
|
||||
{
|
||||
HSYNCcnt--;
|
||||
if (HSYNCcnt == 0)
|
||||
{
|
||||
// HSYNC is over
|
||||
isHSYNC = false;
|
||||
}
|
||||
}
|
||||
|
||||
// move one column
|
||||
currCol++;
|
||||
|
||||
// scanline
|
||||
if (currCol == Regs[HOR_TOTAL] + 1)
|
||||
{
|
||||
// we have reached the end of the current scanline
|
||||
currCol = 0;
|
||||
|
||||
// take care of VSYNC
|
||||
if (VSYNCcnt > 0)
|
||||
{
|
||||
VSYNCcnt--;
|
||||
if (VSYNCcnt == 0)
|
||||
{
|
||||
// VSYNC is over
|
||||
isVSYNC = false;
|
||||
}
|
||||
}
|
||||
|
||||
// increment row
|
||||
currLineInRow++;
|
||||
|
||||
if (currLineInRow == Regs[MAX_RASTER_ADDR] + 1)
|
||||
{
|
||||
currLineInRow = 0;
|
||||
currRow++;
|
||||
|
||||
if (currRow == Regs[VER_TOTAL] + 1)
|
||||
{
|
||||
currRow = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// check for vsync
|
||||
if (!isVSYNC && currRow == Regs[VER_SYNC_POS])
|
||||
{
|
||||
isVSYNC = true;
|
||||
VSYNCcnt = verSyncWidth;
|
||||
//vsync happening
|
||||
}
|
||||
}
|
||||
else if (!isHSYNC && currCol == Regs[HOR_SYNC_POS])
|
||||
{
|
||||
// HSYNC starts
|
||||
isHSYNC = true;
|
||||
HSYNCcnt = horSyncWidth;
|
||||
// hsync happening
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the chip
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// set regs to default
|
||||
for (int i = 0; i < 18; i++)
|
||||
Regs[i] = RegDefaults[i];
|
||||
|
||||
SelectedRegister = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
/// <summary>
|
||||
/// Selects a register
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void RegisterSelect(int data)
|
||||
{
|
||||
SelectedRegister = data & 0x1F;
|
||||
}
|
||||
|
||||
/*
|
||||
RegIdx Register Name Type
|
||||
0 1 2 3 4
|
||||
0 Horizontal Total Write Only Write Only Write Only (note 2) (note 3)
|
||||
1 Horizontal Displayed Write Only Write Only Write Only (note 2) (note 3)
|
||||
2 Horizontal Sync Position Write Only Write Only Write Only (note 2) (note 3)
|
||||
3 H and V Sync Widths Write Only Write Only Write Only (note 2) (note 3)
|
||||
4 Vertical Total Write Only Write Only Write Only (note 2) (note 3)
|
||||
5 Vertical Total Adjust Write Only Write Only Write Only (note 2) (note 3)
|
||||
6 Vertical Displayed Write Only Write Only Write Only (note 2) (note 3)
|
||||
7 Vertical Sync position Write Only Write Only Write Only (note 2) (note 3)
|
||||
8 Interlace and Skew Write Only Write Only Write Only (note 2) (note 3)
|
||||
9 Maximum Raster Address Write Only Write Only Write Only (note 2) (note 3)
|
||||
10 Cursor Start Raster Write Only Write Only Write Only (note 2) (note 3)
|
||||
11 Cursor End Raster Write Only Write Only Write Only (note 2) (note 3)
|
||||
12 Disp. Start Address (High) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
|
||||
13 Disp. Start Address (Low) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
|
||||
14 Cursor Address (High) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
|
||||
15 Cursor Address (Low) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
|
||||
16 Light Pen Address (High) Read Only Read Only Read Only Read Only (note 2) (note 3)
|
||||
17 Light Pen Address (Low) Read Only Read Only Read Only Read Only (note 2) (note 3)
|
||||
|
||||
1. On type 0 and 1, if a Write Only register is read from, "0" is returned.
|
||||
2. See the document "Extra CPC Plus Hardware Information" for more details.
|
||||
3. CRTC type 4 is the same as CRTC type 3. The registers also repeat as they do on the type 3.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the currently selected register
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void WriteRegister(int data)
|
||||
{
|
||||
// 16 and 17 are read only registers on all types
|
||||
if (SelectedRegister == 16 || SelectedRegister == 17)
|
||||
return;
|
||||
|
||||
// non existing registers
|
||||
if (SelectedRegister > 17)
|
||||
return;
|
||||
|
||||
Regs[SelectedRegister] = (byte)(data & CPCMask[SelectedRegister]);
|
||||
|
||||
if (SelectedRegister == HOR_AND_VER_SYNC_WIDTHS)
|
||||
{
|
||||
switch (ChipType)
|
||||
{
|
||||
case CRCTType.Hitachi_HD6845S:
|
||||
// Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC. Bits 3..0 define Horizontal Sync Width.
|
||||
// If 0 is programmed no HSYNC is generated.
|
||||
horSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
|
||||
verSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 4) & 0x0F;
|
||||
break;
|
||||
case CRCTType.UMC_UM6845R:
|
||||
// Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed no HSYNC is generated.
|
||||
horSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
|
||||
verSyncWidth = 16;
|
||||
break;
|
||||
case CRCTType.Motorola_MC6845:
|
||||
// Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed this gives a HSYNC width of 16.
|
||||
horSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
|
||||
if (horSyncWidth == 0)
|
||||
horSyncWidth = 16;
|
||||
verSyncWidth = 16;
|
||||
break;
|
||||
case CRCTType.Amstrad_AMS40489:
|
||||
case CRCTType.Amstrad_40226:
|
||||
// Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC.Bits 3..0 define Horizontal Sync Width.
|
||||
// If 0 is programmed this gives a HSYNC width of 16.
|
||||
horSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
|
||||
verSyncWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 4) & 0x0F;
|
||||
if (horSyncWidth == 0)
|
||||
horSyncWidth = 16;
|
||||
if (verSyncWidth == 0)
|
||||
verSyncWidth = 16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads from the currently selected register
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private bool ReadRegister(ref int data)
|
||||
{
|
||||
bool addressed = false;
|
||||
switch (SelectedRegister)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
if ((int)ChipType == 0 || (int)ChipType == 1)
|
||||
{
|
||||
addressed = true;
|
||||
data = 0;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
case 13:
|
||||
addressed = true;
|
||||
if ((int)ChipType == 0)
|
||||
data = Regs[SelectedRegister];
|
||||
else if ((int)ChipType == 1)
|
||||
data = 0;
|
||||
break;
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
addressed = true;
|
||||
data = Regs[SelectedRegister];
|
||||
break;
|
||||
|
||||
default:
|
||||
// registers 18-31 read as 0, on type 0 and 2. registers 18-30 read as 0 on type1, register 31 reads as 0x0ff.
|
||||
if (SelectedRegister >= 18 && SelectedRegister <= 30)
|
||||
{
|
||||
switch ((int)ChipType)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
case 1:
|
||||
addressed = true;
|
||||
data = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (SelectedRegister == 31)
|
||||
{
|
||||
if ((int)ChipType == 1)
|
||||
{
|
||||
addressed = true;
|
||||
data = 0x0ff;
|
||||
}
|
||||
else if ((int)ChipType == 0 || (int)ChipType == 2)
|
||||
{
|
||||
addressed = true;
|
||||
data = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return addressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads from the status register
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool ReadStatus(ref int data)
|
||||
{
|
||||
bool addressed = false;
|
||||
switch ((int)ChipType)
|
||||
{
|
||||
case 1:
|
||||
// read status
|
||||
//todo!!
|
||||
addressed = true;
|
||||
break;
|
||||
case 0:
|
||||
case 2:
|
||||
// status reg not available
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
// read from internal register instead
|
||||
addressed = ReadRegister(ref data);
|
||||
break;
|
||||
}
|
||||
return addressed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PortIODevice
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadPort(ushort port, ref int result)
|
||||
{
|
||||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||||
|
||||
// The 6845 is selected when bit 14 of the I/O port address is set to "0"
|
||||
bool accessed = !portBits[14];
|
||||
if (!accessed)
|
||||
return false;
|
||||
|
||||
// Bit 9 and 8 of the I/O port address define the function to access
|
||||
if (portBits[8] == false && portBits[9] == true)
|
||||
{
|
||||
// read status register
|
||||
accessed = ReadStatus(ref result);
|
||||
}
|
||||
else if (portBits[8] == true && portBits[9] == true)
|
||||
{
|
||||
// read data register
|
||||
accessed = ReadRegister(ref result);
|
||||
}
|
||||
|
||||
return accessed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool WritePort(ushort port, int result)
|
||||
{
|
||||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||||
|
||||
// The 6845 is selected when bit 14 of the I/O port address is set to "0"
|
||||
bool accessed = !portBits[14];
|
||||
if (!accessed)
|
||||
return false;
|
||||
|
||||
// Bit 9 and 8 of the I/O port address define the function to access
|
||||
if (portBits[8] == false && portBits[9] == false)
|
||||
{
|
||||
// Select 6845 register
|
||||
RegisterSelect(result);
|
||||
}
|
||||
else if (portBits[8] == true && portBits[9] == false)
|
||||
{
|
||||
// Write 6845 register data
|
||||
WriteRegister(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("CRTC");
|
||||
ser.SyncEnum("ChipType", ref ChipType);
|
||||
ser.Sync("Regs", ref Regs, false);
|
||||
ser.Sync("SelectedRegister", ref SelectedRegister);
|
||||
ser.Sync("verSyncWidth", ref verSyncWidth);
|
||||
ser.Sync("currCol", ref currCol);
|
||||
ser.Sync("currLineInRow", ref currLineInRow);
|
||||
ser.Sync("currRow", ref currRow);
|
||||
ser.Sync("isHSYNC", ref isHSYNC);
|
||||
ser.Sync("isVSYNC", ref isVSYNC);
|
||||
ser.Sync("HSYNCcnt", ref HSYNCcnt);
|
||||
ser.Sync("VSYNCcnt", ref VSYNCcnt);
|
||||
ser.Sync("LastTCycle", ref LastTCycle);
|
||||
ser.Sync("DISPTMG", ref DISPTMG);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// The types of CRCT chip found in the CPC range
|
||||
/// </summary>
|
||||
public enum CRCTType
|
||||
{
|
||||
Hitachi_HD6845S = 0,
|
||||
UMC_UM6845R = 1,
|
||||
Motorola_MC6845 = 2,
|
||||
Amstrad_AMS40489 = 3,
|
||||
Amstrad_40226 = 4
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,978 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the tape device (or build-in datacorder as it was called +2 and above)
|
||||
/// </summary>
|
||||
public class DatacorderDevice : IPortIODevice
|
||||
{
|
||||
#region Construction
|
||||
|
||||
private CPCBase _machine { get; set; }
|
||||
private Z80A _cpu { get; set; }
|
||||
private IBeeperDevice _buzzer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public DatacorderDevice()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the datacorder device
|
||||
/// </summary>
|
||||
/// <param name="machine"></param>
|
||||
public void Init(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
_cpu = _machine.CPU;
|
||||
_buzzer = machine.TapeBuzzer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Information
|
||||
|
||||
/// <summary>
|
||||
/// Internal counter used to trigger tape buzzer output
|
||||
/// </summary>
|
||||
private int counter = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the current tape data block that is loaded
|
||||
/// </summary>
|
||||
private int _currentDataBlockIndex = 0;
|
||||
public int CurrentDataBlockIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dataBlocks.Count() > 0) { return _currentDataBlockIndex; }
|
||||
else { return -1; }
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == _currentDataBlockIndex) { return; }
|
||||
if (value < _dataBlocks.Count() && value >= 0)
|
||||
{
|
||||
_currentDataBlockIndex = value;
|
||||
_position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current position within the current data block
|
||||
/// </summary>
|
||||
private int _position = 0;
|
||||
public int Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count) { return 0; }
|
||||
else { return _position; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the tape is currently playing or not
|
||||
/// </summary>
|
||||
private bool _tapeIsPlaying = false;
|
||||
public bool TapeIsPlaying
|
||||
{
|
||||
get { return _tapeIsPlaying; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of the currently loaded data blocks
|
||||
/// </summary>
|
||||
private List<TapeDataBlock> _dataBlocks = new List<TapeDataBlock>();
|
||||
public List<TapeDataBlock> DataBlocks
|
||||
{
|
||||
get { return _dataBlocks; }
|
||||
set { _dataBlocks = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the last CPU t-state value
|
||||
/// </summary>
|
||||
private long _lastCycle = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Edge
|
||||
/// </summary>
|
||||
private int _waitEdge = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Current tapebit state
|
||||
/// </summary>
|
||||
private bool currentState = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Datacorder Device Settings
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the device should autodetect when the Z80 has entered into
|
||||
/// 'load' mode and auto-play the tape if neccesary
|
||||
/// </summary>
|
||||
private bool _autoPlay;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Emulator
|
||||
|
||||
/// <summary>
|
||||
/// Should be fired at the end of every frame
|
||||
/// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented)
|
||||
/// </summary>
|
||||
public void EndFrame()
|
||||
{
|
||||
MonitorFrame();
|
||||
}
|
||||
|
||||
public void StartFrame()
|
||||
{
|
||||
//_buzzer.ProcessPulseValue(currentState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tape Controls
|
||||
|
||||
/// <summary>
|
||||
/// Starts the tape playing from the beginning of the current block
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
if (_tapeIsPlaying)
|
||||
return;
|
||||
|
||||
_machine.CPC.OSD_TapePlaying();
|
||||
|
||||
// update the lastCycle
|
||||
_lastCycle = _cpu.TotalExecutedCycles;
|
||||
|
||||
// reset waitEdge and position
|
||||
_waitEdge = 0;
|
||||
_position = 0;
|
||||
|
||||
if (
|
||||
_dataBlocks.Count > 0 && // data blocks are present &&
|
||||
_currentDataBlockIndex >= 0 // the current data block index is 1 or greater
|
||||
)
|
||||
{
|
||||
while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count)
|
||||
{
|
||||
// we are at the end of a data block - move to the next
|
||||
_position = 0;
|
||||
_currentDataBlockIndex++;
|
||||
|
||||
// are we at the end of the tape?
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for end of tape
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count)
|
||||
{
|
||||
// end of tape reached. Rewind to beginning
|
||||
AutoStopTape();
|
||||
RTZ();
|
||||
return;
|
||||
}
|
||||
|
||||
// update waitEdge with the current position in the current block
|
||||
_waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position];
|
||||
|
||||
// sign that the tape is now playing
|
||||
_tapeIsPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the tape
|
||||
/// (should move to the beginning of the next block)
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (!_tapeIsPlaying)
|
||||
return;
|
||||
|
||||
_machine.CPC.OSD_TapeStopped();
|
||||
|
||||
// sign that the tape is no longer playing
|
||||
_tapeIsPlaying = false;
|
||||
|
||||
if (
|
||||
_currentDataBlockIndex >= 0 && // we are at datablock 1 or above
|
||||
_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count - 1 // the block is still playing back
|
||||
)
|
||||
{
|
||||
// move to the next block
|
||||
_currentDataBlockIndex++;
|
||||
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count())
|
||||
{
|
||||
_currentDataBlockIndex = -1;
|
||||
}
|
||||
|
||||
// reset waitEdge and position
|
||||
_waitEdge = 0;
|
||||
_position = 0;
|
||||
|
||||
if (
|
||||
_currentDataBlockIndex < 0 && // block index is -1
|
||||
_dataBlocks.Count() > 0 // number of blocks is greater than 0
|
||||
)
|
||||
{
|
||||
// move the index on to 0
|
||||
_currentDataBlockIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// update the lastCycle
|
||||
_lastCycle = _cpu.TotalExecutedCycles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewinds the tape to it's beginning (return to zero)
|
||||
/// </summary>
|
||||
public void RTZ()
|
||||
{
|
||||
Stop();
|
||||
_machine.CPC.OSD_TapeRTZ();
|
||||
_currentDataBlockIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a block skip operation on the current tape
|
||||
/// TRUE: skip forward
|
||||
/// FALSE: skip backward
|
||||
/// </summary>
|
||||
/// <param name="skipForward"></param>
|
||||
public void SkipBlock(bool skipForward)
|
||||
{
|
||||
int blockCount = _dataBlocks.Count;
|
||||
int targetBlockId = _currentDataBlockIndex;
|
||||
|
||||
if (skipForward)
|
||||
{
|
||||
if (_currentDataBlockIndex == blockCount - 1)
|
||||
{
|
||||
// last block, go back to beginning
|
||||
targetBlockId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBlockId++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_currentDataBlockIndex == 0)
|
||||
{
|
||||
// already first block, goto last block
|
||||
targetBlockId = blockCount - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBlockId--;
|
||||
}
|
||||
}
|
||||
|
||||
var bl = _dataBlocks[targetBlockId];
|
||||
|
||||
StringBuilder sbd = new StringBuilder();
|
||||
sbd.Append("(");
|
||||
sbd.Append((targetBlockId + 1) + " of " + _dataBlocks.Count());
|
||||
sbd.Append(") : ");
|
||||
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
|
||||
sbd.Append(bl.BlockDescription);
|
||||
if (bl.MetaData.Count > 0)
|
||||
{
|
||||
sbd.Append(" - ");
|
||||
sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value);
|
||||
//sbd.Append("\n");
|
||||
//sbd.Append(bl.MetaData.Skip(1).First().Key + ": " + bl.MetaData.Skip(1).First().Value);
|
||||
}
|
||||
|
||||
if (skipForward)
|
||||
_machine.CPC.OSD_TapeNextBlock(sbd.ToString());
|
||||
else
|
||||
_machine.CPC.OSD_TapePrevBlock(sbd.ToString());
|
||||
|
||||
CurrentDataBlockIndex = targetBlockId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new tape and sets up the tape device accordingly
|
||||
/// </summary>
|
||||
/// <param name="tapeData"></param>
|
||||
public void LoadTape(byte[] tapeData)
|
||||
{
|
||||
// instantiate converters
|
||||
CdtConverter cdtSer = new CdtConverter(this);
|
||||
|
||||
// CDT
|
||||
if (cdtSer.CheckType(tapeData))
|
||||
{
|
||||
// this file has a tzx header - attempt serialization
|
||||
try
|
||||
{
|
||||
cdtSer.Read(tapeData);
|
||||
// reset block index
|
||||
CurrentDataBlockIndex = 0;
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// exception during operation
|
||||
var e = ex;
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nTape image file has a valid CDT header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the tape
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
RTZ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tape Device Methods
|
||||
|
||||
/// <summary>
|
||||
/// Is called every cpu cycle but runs every 50 t-states
|
||||
/// This enables the tape devices to play out even if the spectrum itself is not
|
||||
/// requesting tape data
|
||||
/// </summary>
|
||||
public void TapeCycle()
|
||||
{
|
||||
if (TapeIsPlaying)
|
||||
{
|
||||
counter++;
|
||||
|
||||
if (counter > 20)
|
||||
{
|
||||
counter = 0;
|
||||
bool state = GetEarBit(_machine.CPU.TotalExecutedCycles);
|
||||
_buzzer.ProcessPulseValue(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the spectrum 'EAR' input reading data from the tape
|
||||
/// </summary>
|
||||
/// <param name="cpuCycles"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetEarBit(long cpuCycle)
|
||||
{
|
||||
// decide how many cycles worth of data we are capturing
|
||||
long cycles = cpuCycle - _lastCycle;
|
||||
|
||||
//bool is48k = _machine.IsIn48kMode();
|
||||
|
||||
// check whether tape is actually playing
|
||||
if (_tapeIsPlaying == false)
|
||||
{
|
||||
// it's not playing. Update lastCycle and return
|
||||
_lastCycle = cpuCycle;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for end of tape
|
||||
if (_currentDataBlockIndex < 0)
|
||||
{
|
||||
// end of tape reached - RTZ (and stop)
|
||||
RTZ();
|
||||
return currentState;
|
||||
}
|
||||
|
||||
// process the cycles based on the waitEdge
|
||||
while (cycles >= _waitEdge)
|
||||
{
|
||||
// decrement cycles
|
||||
cycles -= _waitEdge;
|
||||
|
||||
if (_position == 0 && _tapeIsPlaying)
|
||||
{
|
||||
// start of block - take care of initial pulse level for PZX
|
||||
switch (_dataBlocks[_currentDataBlockIndex].BlockDescription)
|
||||
{
|
||||
case BlockType.PULS:
|
||||
// initial pulse level is always low
|
||||
if (currentState)
|
||||
FlipTapeState();
|
||||
break;
|
||||
case BlockType.DATA:
|
||||
// initial pulse level is stored in block
|
||||
if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
|
||||
FlipTapeState();
|
||||
break;
|
||||
case BlockType.PAUS:
|
||||
// initial pulse level is stored in block
|
||||
if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
|
||||
FlipTapeState();
|
||||
break;
|
||||
}
|
||||
|
||||
// notify about the current block
|
||||
var bl = _dataBlocks[_currentDataBlockIndex];
|
||||
|
||||
StringBuilder sbd = new StringBuilder();
|
||||
sbd.Append("(");
|
||||
sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count());
|
||||
sbd.Append(") : ");
|
||||
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
|
||||
sbd.Append(bl.BlockDescription);
|
||||
if (bl.MetaData.Count > 0)
|
||||
{
|
||||
sbd.Append(" - ");
|
||||
sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value);
|
||||
}
|
||||
_machine.CPC.OSD_TapePlayingBlockInfo(sbd.ToString());
|
||||
}
|
||||
|
||||
|
||||
// increment the current period position
|
||||
_position++;
|
||||
|
||||
if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count())
|
||||
{
|
||||
// we have reached the end of the current block
|
||||
|
||||
if (_dataBlocks[_currentDataBlockIndex].DataPeriods.Count() == 0)
|
||||
{
|
||||
// notify about the current block (we are skipping it because its empty)
|
||||
var bl = _dataBlocks[_currentDataBlockIndex];
|
||||
StringBuilder sbd = new StringBuilder();
|
||||
sbd.Append("(");
|
||||
sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count());
|
||||
sbd.Append(") : ");
|
||||
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
|
||||
sbd.Append(bl.BlockDescription);
|
||||
if (bl.MetaData.Count > 0)
|
||||
{
|
||||
sbd.Append(" - ");
|
||||
sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value);
|
||||
}
|
||||
_machine.CPC.OSD_TapePlayingSkipBlockInfo(sbd.ToString());
|
||||
|
||||
}
|
||||
|
||||
// skip any empty blocks (and process any command blocks)
|
||||
while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count())
|
||||
{
|
||||
// check for any commands
|
||||
var command = _dataBlocks[_currentDataBlockIndex].Command;
|
||||
var block = _dataBlocks[_currentDataBlockIndex];
|
||||
bool shouldStop = false;
|
||||
switch (command)
|
||||
{
|
||||
case TapeCommand.STOP_THE_TAPE:
|
||||
case TapeCommand.STOP_THE_TAPE_48K:
|
||||
throw new Exception("spectrum tape command found in CPC tape");
|
||||
|
||||
/*
|
||||
// Stop the tape command found - if this is the end of the tape RTZ
|
||||
// otherwise just STOP and move to the next block
|
||||
case TapeCommand.STOP_THE_TAPE:
|
||||
|
||||
_machine.CPC.OSD_TapeStoppedAuto();
|
||||
shouldStop = true;
|
||||
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count())
|
||||
RTZ();
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
_monitorTimeOut = 2000;
|
||||
|
||||
break;
|
||||
case TapeCommand.STOP_THE_TAPE_48K:
|
||||
if (is48k)
|
||||
{
|
||||
_machine.CPC.OSD_TapeStoppedAuto();
|
||||
shouldStop = true;
|
||||
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count())
|
||||
RTZ();
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
_monitorTimeOut = 2000;
|
||||
}
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldStop)
|
||||
break;
|
||||
|
||||
_position = 0;
|
||||
_currentDataBlockIndex++;
|
||||
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for end of tape
|
||||
if (_currentDataBlockIndex >= _dataBlocks.Count())
|
||||
{
|
||||
_currentDataBlockIndex = -1;
|
||||
RTZ();
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
// update waitEdge with current position within the current block
|
||||
_waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position];
|
||||
|
||||
// flip the current state
|
||||
FlipTapeState();
|
||||
|
||||
}
|
||||
|
||||
// update lastCycle and return currentstate
|
||||
_lastCycle = cpuCycle - (long)cycles;
|
||||
|
||||
// play the buzzer
|
||||
//_buzzer.ProcessPulseValue(false, currentState);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
private void FlipTapeState()
|
||||
{
|
||||
currentState = !currentState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flash loading implementation
|
||||
/// (Deterministic Emulation must be FALSE)
|
||||
/// </summary>
|
||||
private bool FlashLoad()
|
||||
{
|
||||
// deterministic emulation must = false
|
||||
//if (_machine.Spectrum.SyncSettings.DeterministicEmulation)
|
||||
//return;
|
||||
|
||||
var util = _machine.CPC;
|
||||
|
||||
if (_currentDataBlockIndex < 0)
|
||||
_currentDataBlockIndex = 0;
|
||||
|
||||
if (_currentDataBlockIndex >= DataBlocks.Count)
|
||||
return false;
|
||||
|
||||
//var val = GetEarBit(_cpu.TotalExecutedCycles);
|
||||
//_buzzer.ProcessPulseValue(true, val);
|
||||
|
||||
ushort addr = _cpu.RegPC;
|
||||
|
||||
if (_machine.CPC.SyncSettings.DeterministicEmulation)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
var tb = DataBlocks[_currentDataBlockIndex];
|
||||
var tData = tb.BlockData;
|
||||
|
||||
if (tData == null || tData.Length < 2)
|
||||
{
|
||||
// skip this
|
||||
return false;
|
||||
}
|
||||
|
||||
var toRead = tData.Length - 1;
|
||||
|
||||
if (toRead < _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
toRead = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8);
|
||||
}
|
||||
|
||||
if (toRead <= 0)
|
||||
return false;
|
||||
|
||||
var parity = tData[0];
|
||||
|
||||
if (parity != _cpu.Regs[_cpu.F_s] + (_cpu.Regs[_cpu.A_s] << 8) >> 8)
|
||||
return false;
|
||||
|
||||
util.SetCpuRegister("Shadow AF", 0x0145);
|
||||
|
||||
for (var i = 0; i < toRead; i++)
|
||||
{
|
||||
var v = tData[i + 1];
|
||||
_cpu.Regs[_cpu.L] = v;
|
||||
parity ^= v;
|
||||
var d = (ushort)(_cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8) + 1);
|
||||
_machine.WriteBus(d, v);
|
||||
}
|
||||
var pc = (ushort)0x05DF;
|
||||
|
||||
if (_cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8) == toRead &&
|
||||
toRead + 1 < tData.Length)
|
||||
{
|
||||
var v = tData[toRead + 1];
|
||||
_cpu.Regs[_cpu.L] = v;
|
||||
parity ^= v;
|
||||
_cpu.Regs[_cpu.B] = 0xB0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cpu.Regs[_cpu.L] = 1;
|
||||
_cpu.Regs[_cpu.B] = 0;
|
||||
_cpu.Regs[_cpu.F] = 0x50;
|
||||
_cpu.Regs[_cpu.A] = parity;
|
||||
pc = 0x05EE;
|
||||
}
|
||||
|
||||
_cpu.Regs[_cpu.H] = parity;
|
||||
var de = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8);
|
||||
util.SetCpuRegister("DE", de - toRead);
|
||||
var ix = _cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8);
|
||||
util.SetCpuRegister("IX", ix + toRead);
|
||||
|
||||
util.SetCpuRegister("PC", pc);
|
||||
|
||||
_currentDataBlockIndex++;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TapeMonitor
|
||||
|
||||
private long _lastINCycle = 0;
|
||||
private int _monitorCount;
|
||||
private int _monitorTimeOut;
|
||||
private ushort _monitorLastPC;
|
||||
private ushort[] _monitorLastRegs = new ushort[7];
|
||||
|
||||
/// <summary>
|
||||
/// Resets the TapeMonitor
|
||||
/// </summary>
|
||||
private void MonitorReset()
|
||||
{
|
||||
_lastINCycle = 0;
|
||||
_monitorCount = 0;
|
||||
_monitorLastPC = 0;
|
||||
_monitorLastRegs = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An iteration of the monitor process
|
||||
/// </summary>
|
||||
public void MonitorRead()
|
||||
{
|
||||
long cpuCycle = _cpu.TotalExecutedCycles;
|
||||
int delta = (int)(cpuCycle - _lastINCycle);
|
||||
_lastINCycle = cpuCycle;
|
||||
|
||||
var nRegs = new ushort[]
|
||||
{
|
||||
_cpu.Regs[_cpu.A],
|
||||
_cpu.Regs[_cpu.B],
|
||||
_cpu.Regs[_cpu.C],
|
||||
_cpu.Regs[_cpu.D],
|
||||
_cpu.Regs[_cpu.E],
|
||||
_cpu.Regs[_cpu.H],
|
||||
_cpu.Regs[_cpu.L]
|
||||
};
|
||||
|
||||
if (delta > 0 &&
|
||||
delta < 96 &&
|
||||
_cpu.RegPC == _monitorLastPC &&
|
||||
_monitorLastRegs != null)
|
||||
{
|
||||
int dCnt = 0;
|
||||
int dVal = 0;
|
||||
|
||||
for (int i = 0; i < nRegs.Length; i++)
|
||||
{
|
||||
if (_monitorLastRegs[i] != nRegs[i])
|
||||
{
|
||||
dVal = _monitorLastRegs[i] - nRegs[i];
|
||||
dCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
if (dCnt == 1 &&
|
||||
(dVal == 1 || dVal == -1))
|
||||
{
|
||||
_monitorCount++;
|
||||
|
||||
if (_monitorCount >= 16 && _autoPlay)
|
||||
{
|
||||
if (!_tapeIsPlaying)
|
||||
{
|
||||
Play();
|
||||
_machine.CPC.OSD_TapePlayingAuto();
|
||||
}
|
||||
|
||||
_monitorTimeOut = 50;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_monitorCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_monitorLastRegs = nRegs;
|
||||
_monitorLastPC = _cpu.RegPC;
|
||||
}
|
||||
|
||||
public void AutoStopTape()
|
||||
{
|
||||
if (!_tapeIsPlaying)
|
||||
return;
|
||||
|
||||
if (!_autoPlay)
|
||||
return;
|
||||
|
||||
Stop();
|
||||
_machine.CPC.OSD_TapeStoppedAuto();
|
||||
}
|
||||
|
||||
public void AutoStartTape()
|
||||
{
|
||||
if (_tapeIsPlaying)
|
||||
return;
|
||||
|
||||
if (!_autoPlay)
|
||||
return;
|
||||
|
||||
Play();
|
||||
_machine.CPC.OSD_TapePlayingAuto();
|
||||
}
|
||||
|
||||
public int MaskableInterruptCount = 0;
|
||||
|
||||
private void MonitorFrame()
|
||||
{
|
||||
if (_tapeIsPlaying && _autoPlay)
|
||||
{
|
||||
if (DataBlocks.Count > 1 ||
|
||||
(_dataBlocks[_currentDataBlockIndex].BlockDescription != BlockType.CSW_Recording &&
|
||||
_dataBlocks[_currentDataBlockIndex].BlockDescription != BlockType.WAV_Recording))
|
||||
{
|
||||
// we should only stop the tape when there are multiple blocks
|
||||
// if we just have one big block (maybe a CSW or WAV) then auto stopping will cock things up
|
||||
_monitorTimeOut--;
|
||||
}
|
||||
|
||||
if (_monitorTimeOut < 0)
|
||||
{
|
||||
if (_dataBlocks[_currentDataBlockIndex].BlockDescription != BlockType.PAUSE_BLOCK &&
|
||||
_dataBlocks[_currentDataBlockIndex].BlockDescription != BlockType.PAUS)
|
||||
{
|
||||
AutoStopTape();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// fallback in case usual monitor detection methods do not work
|
||||
|
||||
// number of t-states since last IN operation
|
||||
long diff = _machine.CPU.TotalExecutedCycles - _lastINCycle;
|
||||
|
||||
// get current datablock
|
||||
var block = DataBlocks[_currentDataBlockIndex];
|
||||
|
||||
// is this a pause block?
|
||||
if (block.BlockDescription == BlockType.PAUS || block.BlockDescription == BlockType.PAUSE_BLOCK)
|
||||
{
|
||||
// dont autostop the tape here
|
||||
return;
|
||||
}
|
||||
|
||||
// pause in ms at the end of the current block
|
||||
int blockPause = block.PauseInMS;
|
||||
|
||||
// timeout in t-states (equiv. to blockpause)
|
||||
int timeout = ((_machine.GateArray.FrameLength * 50) / 1000) * blockPause;
|
||||
|
||||
// dont use autostop detection if block has no pause at the end
|
||||
if (timeout == 0)
|
||||
return;
|
||||
|
||||
// dont autostop if there is only 1 block
|
||||
if (DataBlocks.Count == 1 || _dataBlocks[_currentDataBlockIndex].BlockDescription == BlockType.CSW_Recording ||
|
||||
_dataBlocks[_currentDataBlockIndex].BlockDescription == BlockType.WAV_Recording
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (diff >= timeout * 2)
|
||||
{
|
||||
// There have been no attempted tape reads by the CPU within the double timeout period
|
||||
// Autostop the tape
|
||||
AutoStopTape();
|
||||
_lastCycle = _cpu.TotalExecutedCycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPortIODevice
|
||||
|
||||
/// <summary>
|
||||
/// Mask constants
|
||||
/// </summary>
|
||||
private const int TAPE_BIT = 0x40;
|
||||
private const int EAR_BIT = 0x10;
|
||||
private const int MIC_BIT = 0x08;
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadPort(ushort port, ref int result)
|
||||
{
|
||||
if (TapeIsPlaying)
|
||||
{
|
||||
GetEarBit(_cpu.TotalExecutedCycles);
|
||||
}
|
||||
if (currentState)
|
||||
{
|
||||
result |= TAPE_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
result &= ~TAPE_BIT;
|
||||
}
|
||||
|
||||
if (!TapeIsPlaying)
|
||||
{
|
||||
if (_machine.UPDDiskDevice == null || !_machine.UPDDiskDevice.FDD_IsDiskLoaded)
|
||||
MonitorRead();
|
||||
}
|
||||
if (_machine.UPDDiskDevice == null || !_machine.UPDDiskDevice.FDD_IsDiskLoaded)
|
||||
MonitorRead();
|
||||
|
||||
/*
|
||||
|
||||
if (TapeIsPlaying)
|
||||
{
|
||||
if (GetEarBit(_cpu.TotalExecutedCycles))
|
||||
{
|
||||
result &= ~(TAPE_BIT); // reset is EAR ON
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (TAPE_BIT); // set is EAR Off
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_machine.KeyboardDevice.IsIssue2Keyboard)
|
||||
{
|
||||
if ((_machine.LASTULAOutByte & (EAR_BIT + MIC_BIT)) == 0)
|
||||
{
|
||||
result &= ~(TAPE_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= (TAPE_BIT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((_machine.LASTULAOutByte & EAR_BIT) == 0)
|
||||
{
|
||||
result &= ~(TAPE_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= TAPE_BIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool WritePort(ushort port, int result)
|
||||
{
|
||||
if (!TapeIsPlaying)
|
||||
{
|
||||
currentState = ((byte)result & 0x10) != 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
/// <summary>
|
||||
/// Bizhawk state serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("DatacorderDevice");
|
||||
ser.Sync("counter", ref counter);
|
||||
ser.Sync("_currentDataBlockIndex", ref _currentDataBlockIndex);
|
||||
ser.Sync("_position", ref _position);
|
||||
ser.Sync("_tapeIsPlaying", ref _tapeIsPlaying);
|
||||
ser.Sync("_lastCycle", ref _lastCycle);
|
||||
ser.Sync("_waitEdge", ref _waitEdge);
|
||||
ser.Sync("currentState", ref currentState);
|
||||
ser.Sync("_lastINCycle", ref _lastINCycle);
|
||||
ser.Sync("_monitorCount", ref _monitorCount);
|
||||
ser.Sync("_monitorTimeOut", ref _monitorTimeOut);
|
||||
ser.Sync("_monitorLastPC", ref _monitorLastPC);
|
||||
ser.Sync("_monitorLastRegs", ref _monitorLastRegs, false);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for the sector CHRN structure
|
||||
/// </summary>
|
||||
public class CHRN
|
||||
{
|
||||
/// <summary>
|
||||
/// Track
|
||||
/// </summary>
|
||||
public byte C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Side
|
||||
/// </summary>
|
||||
public byte H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector ID
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector Size
|
||||
/// </summary>
|
||||
public byte N { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
private byte _flag1;
|
||||
public byte Flag1
|
||||
{
|
||||
get { return _flag1; }
|
||||
set { _flag1 = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
private byte _flag2;
|
||||
public byte Flag2
|
||||
{
|
||||
get { return _flag2; }
|
||||
set { _flag2 = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the last transmitted/received data bytes
|
||||
/// </summary>
|
||||
public byte[] DataBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID for the read/write data command
|
||||
/// </summary>
|
||||
public int DataID { get; set; }
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// </summary>
|
||||
public bool ST1MA
|
||||
{
|
||||
get { return NECUPD765.GetBit(0, _flag1); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
/// </summary>
|
||||
public bool ST1ND
|
||||
{
|
||||
get { return NECUPD765.GetBit(2, _flag1); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(2, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(2, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// </summary>
|
||||
public bool ST1DE
|
||||
{
|
||||
get { return NECUPD765.GetBit(5, _flag1); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// </summary>
|
||||
public bool ST1EN
|
||||
{
|
||||
get { return NECUPD765.GetBit(7, _flag1); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(7, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(7, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// </summary>
|
||||
public bool ST2MD
|
||||
{
|
||||
get { return NECUPD765.GetBit(0, _flag2); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// </summary>
|
||||
public bool ST2BC
|
||||
{
|
||||
get { return NECUPD765.GetBit(1, _flag2); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(1, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(1, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// </summary>
|
||||
public bool ST2WC
|
||||
{
|
||||
get { return NECUPD765.GetBit(4, _flag2); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(4, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(4, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// </summary>
|
||||
public bool ST2DD
|
||||
{
|
||||
get { return NECUPD765.GetBit(5, _flag2); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// </summary>
|
||||
public bool ST2CM
|
||||
{
|
||||
get { return NECUPD765.GetBit(6, _flag2); }
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(6, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(6, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,826 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Definitions
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the current phase of the controller
|
||||
/// </summary>
|
||||
private enum Phase
|
||||
{
|
||||
/// <summary>
|
||||
/// FDC is in an idle state, awaiting the next initial command byte
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// FDC is in a state waiting for the next command instruction
|
||||
/// A command consists of a command byte (eventually including the MF, MK, SK bits), and up to eight parameter bytes
|
||||
/// </summary>
|
||||
Command,
|
||||
|
||||
/// <summary>
|
||||
/// During this phase, the actual data is transferred (if any). Usually that are the data bytes for the read/written sector(s), except for the Format Track Command,
|
||||
/// in that case four bytes for each sector are transferred
|
||||
/// </summary>
|
||||
Execution,
|
||||
|
||||
/// <summary>
|
||||
/// Returns up to seven result bytes (depending on the command) that are containing status information. The Recalibrate and Seek Track commands do not return result bytes directly,
|
||||
/// instead the program must wait until the Main Status Register signalizes that the command has been completed, and then it must (!) send a
|
||||
/// Sense Interrupt State command to 'terminate' the Seek/Recalibrate command.
|
||||
/// </summary>
|
||||
Result
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The lifecycle of an instruction
|
||||
/// Similar to phase, this describes the current 'sub-phase' we are in when dealing with an instruction
|
||||
/// </summary>
|
||||
private enum InstructionState
|
||||
{
|
||||
/// <summary>
|
||||
/// FDC has received a command byte and is currently reading parameter bytes from the data bus
|
||||
/// </summary>
|
||||
ReceivingParameters,
|
||||
|
||||
/// <summary>
|
||||
/// All parameter bytes have been received. This phase allows any neccessary setup before instruction execution starts
|
||||
/// </summary>
|
||||
PreExecution,
|
||||
|
||||
/// <summary>
|
||||
/// The start of instruction execution. This may end up with the FDC moving into result phase,
|
||||
/// but also may also prepare the way for further processing to occur later in execution phase
|
||||
/// </summary>
|
||||
StartExecute,
|
||||
|
||||
/// <summary>
|
||||
/// Data is read or written in execution phase
|
||||
/// </summary>
|
||||
ExecutionReadWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Execution phase is well under way. This state primarily deals with data transfer between CPU and FDC
|
||||
/// </summary>
|
||||
ExecutionWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Execution phase is well under way. This state primarily deals with data transfer between FDC and CPU
|
||||
/// </summary>
|
||||
ExecutionRead,
|
||||
|
||||
/// <summary>
|
||||
/// Execution has finished and results bytes are ready to be read by the CPU
|
||||
/// Initial result setup
|
||||
/// </summary>
|
||||
StartResult,
|
||||
|
||||
/// <summary>
|
||||
/// Result processing
|
||||
/// </summary>
|
||||
ProcessResult,
|
||||
|
||||
/// <summary>
|
||||
/// Results are being sent
|
||||
/// </summary>
|
||||
SendingResults,
|
||||
|
||||
/// <summary>
|
||||
/// Final cleanup tasks when the instruction has fully completed
|
||||
/// </summary>
|
||||
Completed
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents internal interrupt state of the FDC
|
||||
/// </summary>
|
||||
public enum InterruptState
|
||||
{
|
||||
/// <summary>
|
||||
/// There is no interrupt
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Execution interrupt
|
||||
/// </summary>
|
||||
Execution,
|
||||
/// <summary>
|
||||
/// Result interrupt
|
||||
/// </summary>
|
||||
Result,
|
||||
/// <summary>
|
||||
/// Ready interrupt
|
||||
/// </summary>
|
||||
Ready,
|
||||
/// <summary>
|
||||
/// Seek interrupt
|
||||
/// </summary>
|
||||
Seek
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible main states that each drive can be in
|
||||
/// </summary>
|
||||
public enum DriveMainState
|
||||
{
|
||||
/// <summary>
|
||||
/// Drive is not doing anything
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Seek operation is in progress
|
||||
/// </summary>
|
||||
Seek,
|
||||
/// <summary>
|
||||
/// Recalibrate operation is in progress
|
||||
/// </summary>
|
||||
Recalibrate,
|
||||
/// <summary>
|
||||
/// A scan data operation is in progress
|
||||
/// </summary>
|
||||
Scan,
|
||||
/// <summary>
|
||||
/// A read ID operation is in progress
|
||||
/// </summary>
|
||||
ReadID,
|
||||
/// <summary>
|
||||
/// A read data operation is in progress
|
||||
/// </summary>
|
||||
ReadData,
|
||||
/// <summary>
|
||||
/// A read diagnostic (read track) operation is in progress
|
||||
/// </summary>
|
||||
ReadDiagnostic,
|
||||
/// <summary>
|
||||
/// A write id (format track) operation is in progress
|
||||
/// </summary>
|
||||
WriteID,
|
||||
/// <summary>
|
||||
/// A write data operation is in progress
|
||||
/// </summary>
|
||||
WriteData,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State information during a seek/recalibration operation
|
||||
/// </summary>
|
||||
public enum SeekSubState
|
||||
{
|
||||
/// <summary>
|
||||
/// Seek hasnt started yet
|
||||
/// </summary>
|
||||
Idle,
|
||||
/// <summary>
|
||||
/// Delayed
|
||||
/// </summary>
|
||||
Wait,
|
||||
/// <summary>
|
||||
/// Setup for head move
|
||||
/// </summary>
|
||||
MoveInit,
|
||||
/// <summary>
|
||||
/// Seek is currently happening
|
||||
/// </summary>
|
||||
HeadMove,
|
||||
/// <summary>
|
||||
/// Head move with no delay
|
||||
/// </summary>
|
||||
MoveImmediate,
|
||||
/// <summary>
|
||||
/// Ready to complete
|
||||
/// </summary>
|
||||
PerformCompletion,
|
||||
/// <summary>
|
||||
/// Seek operation has completed
|
||||
/// </summary>
|
||||
SeekCompleted
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seek int code
|
||||
/// </summary>
|
||||
public enum SeekIntStatus
|
||||
{
|
||||
Normal,
|
||||
Abnormal,
|
||||
DriveNotReady,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction of a specific command
|
||||
/// </summary>
|
||||
private enum CommandDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Data flows from UPD765A to Z80
|
||||
/// </summary>
|
||||
OUT,
|
||||
/// <summary>
|
||||
/// Data flows from Z80 to UPD765A
|
||||
/// </summary>
|
||||
IN
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum defining the different types of result that can be returned
|
||||
/// </summary>
|
||||
private enum ResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard 7 result bytes are returned
|
||||
/// </summary>
|
||||
Standard,
|
||||
/// <summary>
|
||||
/// 1 byte returned - ST3
|
||||
/// (used for SenseDriveStatus)
|
||||
/// </summary>
|
||||
ST3,
|
||||
/// <summary>
|
||||
/// 1 byte returned - ST0
|
||||
/// (used for version & invalid)
|
||||
/// </summary>
|
||||
ST0,
|
||||
/// <summary>
|
||||
/// 2 bytes returned for sense interrupt status command
|
||||
/// ST0
|
||||
/// CurrentCylinder
|
||||
/// </summary>
|
||||
Interrupt
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible list of encountered drive status errors
|
||||
/// </summary>
|
||||
public enum Status
|
||||
{
|
||||
/// <summary>
|
||||
/// No error detected
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// An undefined error has been detected
|
||||
/// </summary>
|
||||
Undefined,
|
||||
/// <summary>
|
||||
/// Drive is not ready
|
||||
/// </summary>
|
||||
DriveNotReady,
|
||||
/// <summary>
|
||||
/// Invalid command received
|
||||
/// </summary>
|
||||
Invalid,
|
||||
/// <summary>
|
||||
/// The disk has its write protection tab enabled
|
||||
/// </summary>
|
||||
WriteProtected,
|
||||
/// <summary>
|
||||
/// The requested sector has not been found
|
||||
/// </summary>
|
||||
SectorNotFound
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the direction that the head is moving over the cylinders
|
||||
/// Increment: Track number increasing (head moving from outside of disk inwards)
|
||||
/// Decrement: Track number decreasing (head moving from inside of disk outwards)
|
||||
/// </summary>
|
||||
public enum SkipDirection
|
||||
{
|
||||
Increment,
|
||||
Decrement
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
// Command Instruction Constants
|
||||
// Designates the default postitions within the cmdbuffer array
|
||||
|
||||
public const int CM_HEAD = 0;
|
||||
/// <summary>
|
||||
/// C - Track
|
||||
/// </summary>
|
||||
public const int CM_C = 1;
|
||||
/// <summary>
|
||||
/// H - Side
|
||||
/// </summary>
|
||||
public const int CM_H = 2;
|
||||
/// <summary>
|
||||
/// R - Sector ID
|
||||
/// </summary>
|
||||
public const int CM_R = 3;
|
||||
/// <summary>
|
||||
/// N - Sector size
|
||||
/// </summary>
|
||||
public const int CM_N = 4;
|
||||
/// <summary>
|
||||
/// EOT - End of track
|
||||
/// </summary>
|
||||
public const int CM_EOT = 5;
|
||||
/// <summary>
|
||||
/// GPL - Gap length
|
||||
/// </summary>
|
||||
public const int CM_GPL = 6;
|
||||
/// <summary>
|
||||
/// DTL - Data length
|
||||
/// </summary>
|
||||
public const int CM_DTL = 7;
|
||||
/// <summary>
|
||||
/// STP - Step
|
||||
/// </summary>
|
||||
public const int CM_STP = 7;
|
||||
|
||||
// Result Instruction Constants
|
||||
// Designates the default postitions within the cmdbuffer array
|
||||
|
||||
/// <summary>
|
||||
/// Status register 0
|
||||
/// </summary>
|
||||
public const int RS_ST0 = 0;
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
public const int RS_ST1 = 1;
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
public const int RS_ST2 = 2;
|
||||
/// <summary>
|
||||
/// C - Track
|
||||
/// </summary>
|
||||
public const int RS_C = 3;
|
||||
/// <summary>
|
||||
/// H - Side
|
||||
/// </summary>
|
||||
public const int RS_H = 4;
|
||||
/// <summary>
|
||||
/// R - Sector ID
|
||||
/// </summary>
|
||||
public const int RS_R = 5;
|
||||
/// <summary>
|
||||
/// N - Sector size
|
||||
/// </summary>
|
||||
public const int RS_N = 6;
|
||||
|
||||
// Main Status Register Constants
|
||||
// Designates the bit positions within the Main status register
|
||||
|
||||
/// <summary>
|
||||
/// FDD0 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 0 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D0B = 0;
|
||||
/// <summary>
|
||||
/// FDD1 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 1 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D1B = 1;
|
||||
/// <summary>
|
||||
/// FDD2 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 2 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D2B = 2;
|
||||
/// <summary>
|
||||
/// FDD3 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 3 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D3B = 3;
|
||||
/// <summary>
|
||||
/// FDC Busy (still in command-, execution- or result-phase)
|
||||
/// A Read or Write command is in orocess. (FDC Busy) FDC will not accept any other command
|
||||
/// </summary>
|
||||
public const int MSR_CB = 4;
|
||||
/// <summary>
|
||||
/// Execution Mode (still in execution-phase, non_DMA_only)
|
||||
/// This bit is set only during execution ohase (Execution Mode) in non-DMA mode When DB5 goes low, execution phase has ended and result phase has started.It operates only during
|
||||
/// non-DMA mode of operation
|
||||
/// </summary>
|
||||
public const int MSR_EXM = 5;
|
||||
/// <summary>
|
||||
/// Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
|
||||
/// Indicates direction of data transfer between FDC and data regrster If DIO = 1, then transfer is from data register to the
|
||||
/// processor.If DIO = 0, then transfer is from the processor to data register
|
||||
/// </summary>
|
||||
public const int MSR_DIO = 6;
|
||||
/// <summary>
|
||||
/// Request For Master (1=ready for next byte) (see b6 for direction)
|
||||
/// ndicates data register IS ready to send or receive data to or from the processor Both bits DIO and RQM should be
|
||||
/// used to perform the hand-shaking functions of “ready” and “directron” to the processor
|
||||
/// </summary>
|
||||
public const int MSR_RQM = 7;
|
||||
|
||||
// Status Register 0 Constants
|
||||
// Designates the bit positions within the status register 0
|
||||
|
||||
/// <summary>
|
||||
/// Unit Select (driveno during interrupt)
|
||||
/// This flag IS used to indicate a drive unit number at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_US0 = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unit Select (driveno during interrupt)
|
||||
/// This flag IS used to indicate a drive unit number at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_US1 = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Head Address (head during interrupt)
|
||||
/// State of the head at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_HD = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Not Ready (drive not ready or non-existing 2nd head selected)
|
||||
/// Not Ready - When the FDD IS in the not-ready state and a Read or Write command IS Issued, this
|
||||
/// flag IS set If a Read or Write command is issued to side 1 of a single-sided drive, then this flag IS set
|
||||
/// </summary>
|
||||
public const int SR0_NR = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Equipment Check (drive failure or recalibrate failed (retry))
|
||||
/// Equipment check - If a fault srgnal IS received from the FDD, or if the track 0 srgnal fails to occur after 77
|
||||
/// step pulses(Recalibrate Command) then this flag is set
|
||||
/// </summary>
|
||||
public const int SR0_EC = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Seek End (Set if seek-command completed)
|
||||
/// Seek end - When the FDC completes the Seek command, this flag IS set lo 1 (high)
|
||||
/// </summary>
|
||||
public const int SR0_SE = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Interrupt Code (low byte)
|
||||
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
/// or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
/// </summary>
|
||||
public const int SR0_IC0 = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Interrupt Code (high byte)
|
||||
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
/// or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
/// </summary>
|
||||
public const int SR0_IC1 = 7;
|
||||
|
||||
// Status Register 1 Constants
|
||||
// Designates the bit positions within the status register 1
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// Missing address mark - This bit is set i f the FDC does not detect the IDAM before 2 index pulses It is also set if
|
||||
/// the FDC cannot find the DAM or DDAM after the IDAM i s found.MD bit of ST2 is also set at this time
|
||||
/// </summary>
|
||||
public const int SR1_MA = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Not Writeable (tried to write/format disc with wprot_tab=on)
|
||||
/// Not writable (write protect) - During execution of Write Data, Write Deleted Data or Write ID command. if the FDC
|
||||
/// detect: a write protect srgnal from the FDD.then this flag is Set
|
||||
/// </summary>
|
||||
public const int SR1_NW = 1;
|
||||
|
||||
/// <summary>
|
||||
/// No Data
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
///
|
||||
/// During execution of Read Data. Read Deleted Data Write Data.Write Deleted Data or Scan command, if the FDC cannot find
|
||||
/// the sector specified in the IDR(2)Register, this flag i s set.
|
||||
///
|
||||
/// During execution of the Read ID command. if the FDC cannot read the ID field without an error, then this flag IS set
|
||||
///
|
||||
/// During execution of the Read Diagnostic command. if the starting sector cannot be found, then this flag is set
|
||||
/// </summary>
|
||||
public const int SR1_ND = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
|
||||
/// Overrun - If the FDC i s not serviced by the host system during data transfers within a certain time interval.this flaa i s set
|
||||
/// </summary>
|
||||
public const int SR1_OR = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// Data error - When the FDC detects a CRC(1) error in either the ID field or the data field, this flag is set
|
||||
/// </summary>
|
||||
public const int SR1_DE = 5;
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// End of cylinder - When the FDC tries to access a sector beyond the final sector of a cylinder, this flag I S set
|
||||
/// </summary>
|
||||
public const int SR1_EN = 7;
|
||||
|
||||
// Status Register 2 Constants
|
||||
// Designates the bit positions within the status register 2
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// Missing address mark - When data IS read from the medium, i f the FDC cannot find a data address mark or deleted
|
||||
/// data address mark, then this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_MD = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// Bad cylinder - This bit i s related to the ND bit. and when the contents of C on the medium is different
|
||||
/// from that stored i n the IDR and the contents of C IS FFH.then this flag IS set
|
||||
/// </summary>
|
||||
public const int SR2_BC = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Scan Not Satisfied (no fitting sector found)
|
||||
/// Scan not satisfied - During execution of the Scan command, i f the F D cannot find a sector on the cylinder
|
||||
/// which meets the condition.then this flag i s set
|
||||
/// </summary>
|
||||
public const int SR2_SN = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Scan Equal Hit (equal)
|
||||
/// Scan equal hit - During execution of the Scan command. i f the condition of “equal” is satisfied, this flag i s set
|
||||
/// </summary>
|
||||
public const int SR2_SH = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// Wrong cylinder - This bit IS related to the ND bit, and when the contents of C(3) on the medium is different
|
||||
/// from that stored i n the IDR.this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_WC = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// Data error in data field - If the FDC detects a CRC error i n the data field then this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_DD = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// Control mark - During execution of the Read Data or Scan command, if the FDC encounters a sector
|
||||
/// which contains a deleted data address mark, this flag is set Also set if DAM is
|
||||
/// found during Read Deleted Data
|
||||
/// </summary>
|
||||
public const int SR2_CM = 6;
|
||||
|
||||
// Status Register 3 Constants
|
||||
// Designates the bit positions within the status register 3
|
||||
|
||||
/// <summary>
|
||||
/// Unit select 0
|
||||
/// Unit Select (pin 28,29 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_US0 = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unit select 1
|
||||
/// Unit Select (pin 28,29 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_US1 = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Head address (side select)
|
||||
/// Head Address (pin 27 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_HD = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Two Side (0=yes, 1=no (!))
|
||||
/// Two-side - This bit IS used to indicate the status of the two-side signal from the FDD
|
||||
/// </summary>
|
||||
public const int SR3_TS = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Track 0 (on track 0 we are)
|
||||
/// Track 0 - This bit IS used to indicate the status of the track 0 signal from the FDD
|
||||
/// </summary>
|
||||
public const int SR3_T0 = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Ready - status of the ready signal from the fdd
|
||||
/// Ready (drive ready signal)
|
||||
/// </summary>
|
||||
public const int SR3_RY = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Write Protected (write protected)
|
||||
/// Write protect - status of the wp signal from the fdd
|
||||
/// </summary>
|
||||
public const int SR3_WP = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Fault - This bit is used to indicate the status of the fault signal from the FDD
|
||||
/// Fault (if supported: 1=Drive failure)
|
||||
/// </summary>
|
||||
public const int SR3_FT = 7;
|
||||
|
||||
// Interrupt Code Masks
|
||||
|
||||
/// <summary>
|
||||
/// 1 = aborted:readfail / OK if EN (end of track)
|
||||
/// </summary>
|
||||
public const byte IC_OK = 0x00;
|
||||
|
||||
/// <summary>
|
||||
/// 1 = aborted:readfail / OK if EN (end of track)
|
||||
/// </summary>
|
||||
public const byte IC_ABORTED_RF_OKEN = 0x40;
|
||||
|
||||
/// <summary>
|
||||
/// 2 = unknown cmd or senseint with no int occured
|
||||
/// </summary>
|
||||
public const byte IC_NO_INT_OCCURED = 0x80;
|
||||
|
||||
/// <summary>
|
||||
/// 3 = aborted:disc removed etc
|
||||
/// </summary>
|
||||
public const byte IC_ABORTED_DISCREMOVED = 0xC0;
|
||||
|
||||
// command code constants
|
||||
public const int CC_READ_DATA = 0x06;
|
||||
public const int CC_READ_ID = 0x0a;
|
||||
public const int CC_SPECIFY = 0x03;
|
||||
public const int CC_READ_DIAGNOSTIC = 0x02;
|
||||
public const int CC_SCAN_EQUAL = 0x11;
|
||||
public const int CC_SCAN_HIGHOREQUAL = 0x1d;
|
||||
public const int CC_SCAN_LOWOREQUAL = 0x19;
|
||||
public const int CC_READ_DELETEDDATA = 0x0c;
|
||||
public const int CC_WRITE_DATA = 0x05;
|
||||
public const int CC_WRITE_ID = 0x0d;
|
||||
public const int CC_WRITE_DELETEDDATA = 0x09;
|
||||
public const int CC_SEEK = 0x0f;
|
||||
public const int CC_RECALIBRATE = 0x07;
|
||||
public const int CC_SENSE_INTSTATUS = 0x08;
|
||||
public const int CC_SENSE_DRIVESTATUS = 0x04;
|
||||
public const int CC_VERSION = 0x10;
|
||||
public const int CC_INVALID = 0x00;
|
||||
|
||||
// drive seek state constants
|
||||
public const int SEEK_IDLE = 0;
|
||||
public const int SEEK_SEEK = 1;
|
||||
public const int SEEK_RECALIBRATE = 2;
|
||||
// seek interrupt
|
||||
public const int SEEK_INTACKNOWLEDGED = 3;
|
||||
public const int SEEK_NORMALTERM = 4;
|
||||
public const int SEEK_ABNORMALTERM = 5;
|
||||
public const int SEEK_DRIVENOTREADY = 6;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Classes & Structs
|
||||
|
||||
/// <summary>
|
||||
/// Class that holds information about a specific command
|
||||
/// </summary>
|
||||
private class Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
||||
/// </summary>
|
||||
//public int BitMask { get; set; }
|
||||
/// <summary>
|
||||
/// The command code after bitmask has been applied
|
||||
/// </summary>
|
||||
public int CommandCode { get; set; }
|
||||
/// <summary>
|
||||
/// The number of bytes that make up the full command
|
||||
/// </summary>
|
||||
public int ParameterByteCount { get; set; }
|
||||
/// <summary>
|
||||
/// The number of result bytes that will be generated from the command
|
||||
/// </summary>
|
||||
public int ResultByteCount { get; set; }
|
||||
/// <summary>
|
||||
/// The command direction
|
||||
/// IN - Z80 to UPD765A
|
||||
/// OUT - UPD765A to Z80
|
||||
/// </summary>
|
||||
public CommandDirection Direction { get; set; }
|
||||
/// <summary>
|
||||
/// Command makes use of the MT bit
|
||||
/// </summary>
|
||||
public bool MT;
|
||||
/// <summary>
|
||||
/// Command makes use of the MF bit
|
||||
/// </summary>
|
||||
public bool MF;
|
||||
/// <summary>
|
||||
/// Command makes use of the SK bit
|
||||
/// </summary>
|
||||
public bool SK;
|
||||
/// <summary>
|
||||
/// Read/Write command that is READ
|
||||
/// </summary>
|
||||
public bool IsRead;
|
||||
/// <summary>
|
||||
/// Read/Write command that is WRITE
|
||||
/// </summary>
|
||||
public bool IsWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate function that is called by this command
|
||||
/// bool 1: EXECUTE - if TRUE the command will be executed. if FALSE the method will instead parse commmand parameter bytes
|
||||
/// bool 2: RESULT - if TRUE
|
||||
/// </summary>
|
||||
public Action CommandDelegate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for command parameters
|
||||
/// </summary>
|
||||
public class CommandParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The requested drive
|
||||
/// </summary>
|
||||
public byte UnitSelect;
|
||||
/// <summary>
|
||||
/// The requested physical side
|
||||
/// </summary>
|
||||
public byte Side;
|
||||
/// <summary>
|
||||
/// The requested track (C)
|
||||
/// </summary>
|
||||
public byte Cylinder;
|
||||
/// <summary>
|
||||
/// The requested head (H)
|
||||
/// </summary>
|
||||
public byte Head;
|
||||
/// <summary>
|
||||
/// The requested sector (R)
|
||||
/// </summary>
|
||||
public byte Sector;
|
||||
/// <summary>
|
||||
/// The specified sector size (N)
|
||||
/// </summary>
|
||||
public byte SectorSize;
|
||||
/// <summary>
|
||||
/// The end of track or last sector value (EOT)
|
||||
/// </summary>
|
||||
public byte EOT;
|
||||
/// <summary>
|
||||
/// Gap3 length (GPL)
|
||||
/// </summary>
|
||||
public byte Gap3Length;
|
||||
/// <summary>
|
||||
/// Data length (DTL) - When N is defined as 00, DTL stands for the data length
|
||||
/// which users are going to read out or write into the sector
|
||||
/// </summary>
|
||||
public byte DTL;
|
||||
|
||||
/// <summary>
|
||||
/// Clear down
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
UnitSelect = 0;
|
||||
Side = 0;
|
||||
Cylinder = 0;
|
||||
Head = 0;
|
||||
Sector = 0;
|
||||
SectorSize = 0;
|
||||
EOT = 0;
|
||||
Gap3Length = 0;
|
||||
DTL = 0;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("ActiveCmdParams");
|
||||
|
||||
ser.Sync("UnitSelect", ref UnitSelect);
|
||||
ser.Sync("Side", ref Side);
|
||||
ser.Sync("Cylinder", ref Cylinder);
|
||||
ser.Sync("Head", ref Head);
|
||||
ser.Sync("Sector", ref Sector);
|
||||
ser.Sync("SectorSize", ref SectorSize);
|
||||
ser.Sync("EOT", ref EOT);
|
||||
ser.Sync("Gap3Length", ref Gap3Length);
|
||||
ser.Sync("DTL", ref DTL);
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,893 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Floppy drive related stuff
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IFDDHost
|
||||
{
|
||||
#region Drive State
|
||||
|
||||
/// <summary>
|
||||
/// FDD Flag - motor on/off
|
||||
/// </summary>
|
||||
public bool FDD_FLAG_MOTOR;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently active disk drive
|
||||
/// </summary>
|
||||
public int DiskDriveIndex
|
||||
{
|
||||
get { return _diskDriveIndex; }
|
||||
set
|
||||
{
|
||||
// when index is changed update the ActiveDrive
|
||||
_diskDriveIndex = value;
|
||||
ActiveDrive = DriveStates[_diskDriveIndex];
|
||||
}
|
||||
}
|
||||
private int _diskDriveIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active drive
|
||||
/// </summary>
|
||||
private DriveState ActiveDrive;
|
||||
|
||||
/// <summary>
|
||||
/// Array that holds state information for each possible drive
|
||||
/// </summary>
|
||||
private DriveState[] DriveStates = new DriveState[4];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FDD Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initialization / reset of the floppy drive subsystem
|
||||
/// </summary>
|
||||
private void FDD_Init()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
DriveState ds = new DriveState(i, this);
|
||||
DriveStates[i] = ds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the requested sector
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private FloppyDisk.Sector GetSector()
|
||||
{
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
// get the current track
|
||||
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
|
||||
|
||||
// get the current sector index
|
||||
int index = ActiveDrive.SectorIndex;
|
||||
|
||||
// make sure this index exists
|
||||
if (index > trk.Sectors.Length)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// index hole count
|
||||
int iHole = 0;
|
||||
|
||||
// loop through the sectors in a track
|
||||
// the loop ends with either the sector being found
|
||||
// or the index hole being passed twice
|
||||
while (iHole <= 2)
|
||||
{
|
||||
// does the requested sector match the current sector
|
||||
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
|
||||
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
|
||||
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
|
||||
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
|
||||
{
|
||||
// sector has been found
|
||||
sector = trk.Sectors[index];
|
||||
|
||||
UnSetBit(SR2_BC, ref Status2);
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
break;
|
||||
}
|
||||
|
||||
// check for bad cylinder
|
||||
if (trk.Sectors[index].SectorIDInfo.C == 255)
|
||||
{
|
||||
SetBit(SR2_BC, ref Status2);
|
||||
}
|
||||
// check for no cylinder
|
||||
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
|
||||
{
|
||||
SetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// incrememnt sector index
|
||||
index++;
|
||||
|
||||
// have we reached the index hole?
|
||||
if (trk.Sectors.Length <= index)
|
||||
{
|
||||
// wrap around
|
||||
index = 0;
|
||||
iHole++;
|
||||
}
|
||||
}
|
||||
|
||||
// search loop has completed and the sector may or may not have been found
|
||||
|
||||
// bad cylinder detected?
|
||||
if (Status2.Bit(SR2_BC))
|
||||
{
|
||||
// remove WC
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// update sectorindex on drive
|
||||
ActiveDrive.SectorIndex = index;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
// IFDDHost methods that fall through to the currently active drive
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
/// <param name="tapeData"></param>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// we are only going to load into the first drive
|
||||
DriveStates[0].FDD_LoadDisk(diskData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
DriveStates[0].FDD_EjectDisk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded
|
||||
{
|
||||
get { return DriveStates[DiskDriveIndex].FDD_IsDiskLoaded; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the disk object from drive 0
|
||||
/// </summary>
|
||||
public FloppyDisk DiskPointer
|
||||
{
|
||||
get { return DriveStates[0].Disk; }
|
||||
}
|
||||
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Status Class
|
||||
|
||||
/// <summary>
|
||||
/// Holds specfic state information about a drive
|
||||
/// </summary>
|
||||
private class DriveState : IFDDHost
|
||||
{
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// The drive ID from an FDC perspective
|
||||
/// </summary>
|
||||
public int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this drive ready
|
||||
/// TRUE if both drive exists and has a disk inserted
|
||||
/// </summary>
|
||||
public bool FLAG_READY
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disk is write protected (TRUE BY DEFAULT)
|
||||
/// </summary>
|
||||
public bool FLAG_WRITEPROTECT = false;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for seek steps
|
||||
/// One step for each indexpulse (track index) until seeked track
|
||||
/// </summary>
|
||||
public int SeekCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Seek status
|
||||
/// </summary>
|
||||
public int SeekStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Age counter
|
||||
/// </summary>
|
||||
public int SeekAge;
|
||||
|
||||
/// <summary>
|
||||
/// The current side
|
||||
/// </summary>
|
||||
public byte CurrentSide;
|
||||
|
||||
/// <summary>
|
||||
/// The current track index in the DiskTracks array
|
||||
/// </summary>
|
||||
public byte TrackIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of the current cylinder
|
||||
/// </summary>
|
||||
public byte CurrentTrackID
|
||||
{
|
||||
get
|
||||
{
|
||||
// default invalid track
|
||||
int id = 0xff;
|
||||
|
||||
if (Disk == null)
|
||||
return (byte)id;
|
||||
|
||||
if (Disk.DiskTracks.Count() == 0)
|
||||
return (byte)id;
|
||||
|
||||
if (TrackIndex >= Disk.GetTrackCount())
|
||||
TrackIndex = 0;
|
||||
else if (TrackIndex < 0)
|
||||
TrackIndex = 0;
|
||||
|
||||
var track = Disk.DiskTracks[TrackIndex];
|
||||
|
||||
id = track.TrackNumber;
|
||||
|
||||
return (byte)id;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = 0; i < Disk.GetTrackCount(); i++)
|
||||
{
|
||||
if (Disk.DiskTracks[i].TrackNumber == value)
|
||||
{
|
||||
TrackIndex = (byte)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The new track that the drive is seeking to
|
||||
/// (used in seek operations)
|
||||
/// </summary>
|
||||
public int SeekingTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The current sector index in the Sectors array
|
||||
/// </summary>
|
||||
public int SectorIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The currently loaded floppy disk
|
||||
/// </summary>
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent controller
|
||||
/// </summary>
|
||||
private NECUPD765 FDC;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lookups
|
||||
|
||||
/// <summary>
|
||||
/// TRUE if we are on track 0
|
||||
/// </summary>
|
||||
public bool FLAG_TRACK0
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TrackIndex == 0) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/*
|
||||
/// <summary>
|
||||
/// Moves the head across the disk cylinders
|
||||
/// </summary>
|
||||
/// <param name="direction"></param>
|
||||
/// <param name="cylinderCount"></param>
|
||||
public void MoveHead(SkipDirection direction, int cylinderCount)
|
||||
{
|
||||
// get total tracks
|
||||
int trackCount = Disk.DiskTracks.Count();
|
||||
|
||||
int trk = 0;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case SkipDirection.Increment:
|
||||
trk = (int)CurrentTrack + cylinderCount;
|
||||
if (trk >= trackCount)
|
||||
{
|
||||
// past the last track
|
||||
trk = trackCount - 1;
|
||||
}
|
||||
else if (trk < 0)
|
||||
trk = 0;
|
||||
break;
|
||||
case SkipDirection.Decrement:
|
||||
trk = (int)CurrentTrack - cylinderCount;
|
||||
if (trk < 0)
|
||||
{
|
||||
// before the first track
|
||||
trk = 0;
|
||||
}
|
||||
else if (trk >= trackCount)
|
||||
trk = trackCount - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// move the head
|
||||
CurrentTrack = (byte)trk;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// Finds a supplied sector
|
||||
/// </summary>
|
||||
/// <param name="resBuffer"></param>
|
||||
/// <param name=""></param>
|
||||
/// <param name=""></param>
|
||||
/// <returns></returns>
|
||||
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
|
||||
{
|
||||
int index =CurrentSector;
|
||||
int lc = 0;
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
bool found = false;
|
||||
|
||||
do
|
||||
{
|
||||
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
|
||||
if (sector != null && sector.SectorID == prms.Sector)
|
||||
{
|
||||
// sector found
|
||||
// check for data errors
|
||||
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
|
||||
{
|
||||
// data errors found
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// sector doesnt match
|
||||
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
|
||||
if (c == 255)
|
||||
{
|
||||
// bad cylinder
|
||||
resBuffer[RS_ST2] |= 0x02;
|
||||
}
|
||||
else if (prms.Cylinder != c)
|
||||
{
|
||||
// cylinder mismatch
|
||||
resBuffer[RS_ST2] |= 0x10;
|
||||
}
|
||||
|
||||
// increment index
|
||||
index++;
|
||||
|
||||
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
|
||||
{
|
||||
// out of bounds
|
||||
index = 0;
|
||||
lc++;
|
||||
}
|
||||
|
||||
} while (lc < 2);
|
||||
|
||||
if ((resBuffer[RS_ST2] & 0x02) != 0)
|
||||
{
|
||||
// bad cylinder set - remove no cylinder
|
||||
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
|
||||
}
|
||||
|
||||
// update current sector
|
||||
CurrentSector = index;
|
||||
|
||||
if (found)
|
||||
return sector;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a result buffer
|
||||
/// </summary>
|
||||
/// <param name="resBuffer"></param>
|
||||
/// <param name="chrn"></param>
|
||||
public void FillResult(ref byte[] resBuffer, CHRN chrn)
|
||||
{
|
||||
// clear results
|
||||
resBuffer[RS_ST0] = 0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
|
||||
if (chrn == null)
|
||||
{
|
||||
// no chrn supplied
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates the result buffer with ReadID data
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void ReadID(ref byte[] resBuffer)
|
||||
{
|
||||
if (CheckDriveStatus() == false)
|
||||
{
|
||||
// drive not ready
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
return;
|
||||
}
|
||||
|
||||
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
|
||||
|
||||
if (track != null && track.NumberOfSectors > 0)
|
||||
{
|
||||
// formatted track
|
||||
|
||||
// get the current sector
|
||||
int index = CurrentSector;
|
||||
|
||||
// is the index out of bounds?
|
||||
if (index >= track.NumberOfSectors)
|
||||
{
|
||||
// reset the index
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// read the sector data
|
||||
var data = track.Sectors[index];
|
||||
resBuffer[RS_C] = data.TrackNumber;
|
||||
resBuffer[RS_H] = data.SideNumber;
|
||||
resBuffer[RS_R] = data.SectorID;
|
||||
resBuffer[RS_N] = data.SectorSize;
|
||||
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
|
||||
// increment the current sector
|
||||
CurrentSector = index + 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unformatted track?
|
||||
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
|
||||
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
|
||||
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
|
||||
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
|
||||
|
||||
SetBit(SR0_IC0, ref ST0);
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0x01;
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// The drive performs a seek operation if necessary
|
||||
/// Return value TRUE indicates seek complete
|
||||
/// </summary>
|
||||
public void DoSeek()
|
||||
{
|
||||
if (CurrentState != DriveMainState.Recalibrate &&
|
||||
CurrentState != DriveMainState.Seek)
|
||||
{
|
||||
// no seek/recalibrate has been asked for
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetBit(ID, FDC.StatusMain))
|
||||
{
|
||||
// drive is already seeking
|
||||
return;
|
||||
}
|
||||
|
||||
RunSeekCycle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a seek cycle
|
||||
/// </summary>
|
||||
public void RunSeekCycle()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (SeekState)
|
||||
{
|
||||
// seek or recalibrate has been requested
|
||||
case SeekSubState.Idle:
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
// recalibrate always seeks to track 0
|
||||
SeekingTrack = 0;
|
||||
}
|
||||
SeekState = SeekSubState.MoveInit;
|
||||
|
||||
// mark drive as busy
|
||||
// this should be cleared by SIS command
|
||||
SetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
break;
|
||||
|
||||
// setup for the head move
|
||||
case SeekSubState.MoveInit:
|
||||
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
// we are already at the required track
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
!FLAG_TRACK0)
|
||||
{
|
||||
// recalibration fail
|
||||
SeekIntState = SeekIntStatus.Abnormal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
// equipment check
|
||||
SetBit(SR0_EC, ref FDC.Status0);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
FLAG_TRACK0)
|
||||
{
|
||||
// recalibration success
|
||||
SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
|
||||
{
|
||||
// drive not ready
|
||||
FLAG_READY = false;
|
||||
|
||||
// drive not ready
|
||||
SeekIntState = SeekIntStatus.DriveNotReady;
|
||||
|
||||
// cancel any interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.None;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SeekCounter > 1)
|
||||
{
|
||||
// not ready to seek yet
|
||||
SeekCounter--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.MoveImmediate;
|
||||
break;
|
||||
}
|
||||
|
||||
// head move
|
||||
SeekState = SeekSubState.HeadMove;
|
||||
|
||||
break;
|
||||
|
||||
case SeekSubState.HeadMove:
|
||||
|
||||
// do the seek
|
||||
SeekCounter = FDC.SRT;
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, 1);
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, 1);
|
||||
}
|
||||
|
||||
// should the seek be completed now?
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
// seek not finished yet
|
||||
return;
|
||||
|
||||
// seek emulation processed immediately
|
||||
case SeekSubState.MoveImmediate:
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, delta);
|
||||
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, delta);
|
||||
}
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
|
||||
case SeekSubState.PerformCompletion:
|
||||
SeekDone();
|
||||
SeekState = SeekSubState.SeekCompleted;
|
||||
break;
|
||||
|
||||
case SeekSubState.SeekCompleted:
|
||||
// seek has already completed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a seek operation has completed
|
||||
/// </summary>
|
||||
public void SeekDone()
|
||||
{
|
||||
SeekCounter = 0;
|
||||
SeekingTrack = CurrentTrack;
|
||||
|
||||
// generate ST0 register data
|
||||
|
||||
// get only the IC bits
|
||||
IntStatus &= IC_ABORTED_DISCREMOVED;
|
||||
|
||||
// drive ready?
|
||||
if (!FLAG_READY)
|
||||
{
|
||||
SetBit(SR0_NR, ref IntStatus);
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
|
||||
// are we recalibrating?
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// set seek end
|
||||
SetBit(SR0_SE, ref IntStatus);
|
||||
/*
|
||||
// head address
|
||||
if (CurrentSide > 0)
|
||||
{
|
||||
SetBit(SR0_HD, ref IntStatus);
|
||||
|
||||
// drive only supports 1 head
|
||||
// set the EC bit
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
// UnitSelect
|
||||
SetUnitSelect(ID, ref IntStatus);
|
||||
|
||||
// move to none state
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
//SeekState = SeekSubState.SeekCompleted;
|
||||
|
||||
// set the seek interrupt flag for this drive
|
||||
// this will be cleared at the next successful senseint
|
||||
FLAG_SEEK_INTERRUPT = true;
|
||||
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public DriveState(int driveID, NECUPD765 fdc)
|
||||
{
|
||||
ID = driveID;
|
||||
FDC = fdc;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
/// <param name="tapeData"></param>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// try dsk first
|
||||
FloppyDisk fdd = null;
|
||||
bool found = false;
|
||||
|
||||
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiskType.CPCExtended:
|
||||
fdd = new CPCExtendedFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.CPC:
|
||||
fdd = new CPCFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
Disk = fdd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
Disk = null;
|
||||
//FLAG_READY = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Disk != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateSerialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync("ID", ref ID);
|
||||
ser.Sync("FLAG_WRITEPROTECT", ref FLAG_WRITEPROTECT);
|
||||
//ser.Sync("FLAG_DISKCHANGED", ref FLAG_DISKCHANGED);
|
||||
//ser.Sync("FLAG_RECALIBRATING", ref FLAG_RECALIBRATING);
|
||||
//ser.Sync("FLAG_SEEK_INTERRUPT", ref FLAG_SEEK_INTERRUPT);
|
||||
//ser.Sync("IntStatus", ref IntStatus);
|
||||
//ser.Sync("ST0", ref ST0);
|
||||
//ser.Sync("RecalibrationCounter", ref RecalibrationCounter);
|
||||
ser.Sync("SeekCounter", ref SeekCounter);
|
||||
ser.Sync("SeekStatus", ref SeekStatus);
|
||||
ser.Sync("SeekAge", ref SeekAge);
|
||||
ser.Sync("CurrentSide", ref CurrentSide);
|
||||
//ser.Sync("CurrentTrack", ref CurrentTrack);
|
||||
ser.Sync("TrackIndex", ref TrackIndex);
|
||||
ser.Sync("SeekingTrack", ref SeekingTrack);
|
||||
//ser.Sync("CurrentSector", ref CurrentSector);
|
||||
ser.Sync("SectorIndex", ref SectorIndex);
|
||||
//ser.Sync("RAngles", ref RAngles);
|
||||
//ser.Sync("DataPointer", ref DataPointer);
|
||||
//ser.SyncEnum("CurrentState", ref CurrentState);
|
||||
//ser.SyncEnum("SeekState", ref SeekState);
|
||||
//ser.SyncEnum("SeekIntState", ref SeekIntState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// IPortIODevice
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IPortIODevice
|
||||
{
|
||||
#region Dev Logging
|
||||
|
||||
public string outputfile = @"D:\Dropbox\Dropbox\_Programming\TASVideos\BizHawk\output\zxhawkio-" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv";
|
||||
public string outputString = "STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN\r\n";
|
||||
public bool writeDebug = false;
|
||||
|
||||
public List<string> dLog = new List<string>
|
||||
{
|
||||
"STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN"
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Status read
|
||||
* Data write
|
||||
* Data read
|
||||
* CMD code
|
||||
* CMD string
|
||||
* MT flag
|
||||
* MK flag
|
||||
* SK flag
|
||||
* */
|
||||
private string[] workingArr = new string[3];
|
||||
|
||||
private void BuildCSVLine()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
sb.Append(workingArr[i]);
|
||||
sb.Append(",");
|
||||
workingArr[i] = "";
|
||||
}
|
||||
|
||||
sb.Append(ActiveCommand.CommandCode).Append(",");
|
||||
|
||||
sb.Append(CMD_FLAG_MT).Append(",");
|
||||
sb.Append(CMD_FLAG_MF).Append(",");
|
||||
sb.Append(CMD_FLAG_SK).Append(",");
|
||||
|
||||
sb.Append(CommCounter).Append(",");
|
||||
sb.Append(ResCounter).Append(",");
|
||||
sb.Append(ExecCounter).Append(",");
|
||||
sb.Append(ExecLength);
|
||||
|
||||
//sb.Append("\r\n");
|
||||
|
||||
//outputString += sb.ToString();
|
||||
dLog.Add(sb.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadPort(ushort port, ref int data)
|
||||
{
|
||||
BitArray bits = new BitArray(new byte[] { (byte)data });
|
||||
|
||||
if (port == 0x3ffd)
|
||||
{
|
||||
// Z80 is trying to read from the data register
|
||||
data = ReadDataRegister();
|
||||
if (writeDebug)
|
||||
{
|
||||
workingArr[2] = data.ToString();
|
||||
//outputString += ",," + data + "," + ActiveCommand.CommandCode + "\r\n";
|
||||
BuildCSVLine();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port == 0x2ffd)
|
||||
{
|
||||
// read main status register
|
||||
// this can happen at any time
|
||||
data = ReadMainStatus();
|
||||
if (writeDebug)
|
||||
{
|
||||
//outputString += data + ",,," + ActiveCommand.CommandCode + "\r\n";
|
||||
workingArr[0] = data.ToString();
|
||||
BuildCSVLine();
|
||||
//System.IO.File.WriteAllText(outputfile, outputString);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public bool WritePort(ushort port, int data)
|
||||
{
|
||||
BitArray bits = new BitArray(new byte[] { (byte)data });
|
||||
|
||||
if (port == 0x3ffd)
|
||||
{
|
||||
// Z80 is attempting to write to the data register
|
||||
WriteDataRegister((byte)data);
|
||||
if (writeDebug)
|
||||
{
|
||||
//outputString += "," + data + ",," + ActiveCommand.CommandCode + "\r\n";
|
||||
workingArr[1] = data.ToString();
|
||||
BuildCSVLine();
|
||||
//System.IO.File.WriteAllText(outputfile, outputString);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port == 0x1ffd)
|
||||
{
|
||||
// set disk motor on/off
|
||||
FDD_FLAG_MOTOR = bits[3];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Timimng
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
/// <summary>
|
||||
/// The current Z80 cycle
|
||||
/// </summary>
|
||||
private long CurrentCPUCycle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_machine == null)
|
||||
return 0;
|
||||
else
|
||||
return _machine.CPU.TotalExecutedCycles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The last CPU cycle when the FDC accepted an IO read/write
|
||||
/// </summary>
|
||||
private long LastCPUCycle;
|
||||
|
||||
/// <summary>
|
||||
/// The current delay figure (in Z80 t-states)
|
||||
/// This implementation only introduces delay upon main status register reads
|
||||
/// All timing calculations should be done during the other read/write operations
|
||||
/// </summary>
|
||||
private long StatusDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the numbers of Z80 cycles per MS
|
||||
/// </summary>
|
||||
private long CPUCyclesPerMs;
|
||||
|
||||
/// <summary>
|
||||
/// The floppy drive emulated clock speed
|
||||
/// </summary>
|
||||
public const double DriveClock = 31250;
|
||||
|
||||
/// <summary>
|
||||
/// The number of floppy drive cycles per MS
|
||||
/// </summary>
|
||||
public long DriveCyclesPerMs;
|
||||
|
||||
/// <summary>
|
||||
/// The number of T-States in one floppy drive clock tick
|
||||
/// </summary>
|
||||
public long StatesPerDriveTick;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for measuring when the floppy drive is ready to run a cycle
|
||||
/// </summary>
|
||||
private long TickCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Internal drive cycle counter
|
||||
/// </summary>
|
||||
private int DriveCycleCounter = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the timing routines
|
||||
/// </summary>
|
||||
private void TimingInit()
|
||||
{
|
||||
// z80 timing
|
||||
double frameSize = _machine.GateArray.FrameLength;
|
||||
double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
|
||||
long tPerSecond = (long)(frameSize * rRate);
|
||||
CPUCyclesPerMs = tPerSecond / 1000;
|
||||
|
||||
// drive timing
|
||||
double dRate = DriveClock / frameSize;
|
||||
long dPerSecond = (long)(frameSize * dRate);
|
||||
DriveCyclesPerMs = dPerSecond / 1000;
|
||||
|
||||
long TStatesPerDriveCycle = (long)((double)_machine.GateArray.Z80ClockSpeed / DriveClock);
|
||||
StatesPerDriveTick = TStatesPerDriveCycle;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by reads to the main status register
|
||||
/// Returns true if there is no delay
|
||||
/// Returns false if read is to be deferred
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CheckTiming()
|
||||
{
|
||||
// get delta
|
||||
long delta = CurrentCPUCycle - LastCPUCycle;
|
||||
|
||||
if (StatusDelay >= delta)
|
||||
{
|
||||
// there is still delay remaining
|
||||
StatusDelay -= delta;
|
||||
LastCPUCycle = CurrentCPUCycle;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no delay remaining
|
||||
StatusDelay = 0;
|
||||
LastCPUCycle = CurrentCPUCycle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
using BizHawk.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The NEC floppy disk controller (and floppy drive) found in the +3
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
#region Devices
|
||||
|
||||
/// <summary>
|
||||
/// The emulated spectrum machine
|
||||
/// </summary>
|
||||
private CPCBase _machine;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor
|
||||
/// </summary>
|
||||
/// <param name="machine"></param>
|
||||
public NECUPD765()
|
||||
{
|
||||
InitCommandList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization routine
|
||||
/// </summary>
|
||||
public void Init(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
FDD_Init();
|
||||
TimingInit();
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the FDC
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// setup main status
|
||||
StatusMain = 0;
|
||||
|
||||
Status0 = 0;
|
||||
Status1 = 0;
|
||||
Status2 = 0;
|
||||
Status3 = 0;
|
||||
|
||||
SetBit(MSR_RQM, ref StatusMain);
|
||||
|
||||
SetPhase_Idle();
|
||||
|
||||
//FDC_FLAG_RQM = true;
|
||||
//ActiveDirection = CommandDirection.IN;
|
||||
SRT = 6;
|
||||
HUT = 16;
|
||||
HLT = 2;
|
||||
HLT_Counter = 0;
|
||||
HUT_Counter = 0;
|
||||
IndexPulseCounter = 0;
|
||||
CMD_FLAG_MF = false;
|
||||
|
||||
foreach (var d in DriveStates)
|
||||
{
|
||||
//d.SeekingTrack = d.CurrentTrack;
|
||||
////d.SeekCounter = 0;
|
||||
//d.FLAG_SEEK_INTERRUPT = false;
|
||||
//d.IntStatus = 0;
|
||||
//d.SeekState = SeekSubState.Idle;
|
||||
//d.SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the command structure
|
||||
/// Each command represents one of the internal UPD765 commands
|
||||
/// </summary>
|
||||
private void InitCommandList()
|
||||
{
|
||||
CommandList = new List<Command>
|
||||
{
|
||||
// read data
|
||||
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read id
|
||||
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 7 },
|
||||
// specify
|
||||
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// read diagnostic
|
||||
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan equal
|
||||
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan high or equal
|
||||
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan low or equal
|
||||
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read deleted data
|
||||
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write data
|
||||
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write id
|
||||
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 5, ResultByteCount = 7 },
|
||||
// write deleted data
|
||||
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// seek
|
||||
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// recalibrate (seek track00)
|
||||
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 0 },
|
||||
// sense interrupt status
|
||||
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 2 },
|
||||
// sense drive status
|
||||
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 1 },
|
||||
// version
|
||||
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
// invalid
|
||||
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("NEC-UPD765");
|
||||
|
||||
#region FDD
|
||||
|
||||
ser.Sync("FDD_FLAG_MOTOR", ref FDD_FLAG_MOTOR);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
ser.BeginSection("HITDrive_" + i);
|
||||
DriveStates[i].SyncState(ser);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
ser.Sync("DiskDriveIndex", ref _diskDriveIndex);
|
||||
// set active drive
|
||||
DiskDriveIndex = _diskDriveIndex;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registers
|
||||
|
||||
ser.Sync("_RegMain", ref StatusMain);
|
||||
ser.Sync("_Reg0", ref Status0);
|
||||
ser.Sync("_Reg1", ref Status1);
|
||||
ser.Sync("_Reg2", ref Status2);
|
||||
ser.Sync("_Reg3", ref Status3);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller state
|
||||
|
||||
ser.Sync("DriveLight", ref DriveLight);
|
||||
ser.SyncEnum("ActivePhase", ref ActivePhase);
|
||||
//ser.SyncEnum("ActiveDirection", ref ActiveDirection);
|
||||
ser.SyncEnum("ActiveInterrupt", ref ActiveInterrupt);
|
||||
ser.Sync("CommBuffer", ref CommBuffer, false);
|
||||
ser.Sync("CommCounter", ref CommCounter);
|
||||
ser.Sync("ResBuffer", ref ResBuffer, false);
|
||||
ser.Sync("ExecBuffer", ref ExecBuffer, false);
|
||||
ser.Sync("ExecCounter", ref ExecCounter);
|
||||
ser.Sync("ExecLength", ref ExecLength);
|
||||
ser.Sync("InterruptResultBuffer", ref InterruptResultBuffer, false);
|
||||
ser.Sync("ResCounter", ref ResCounter);
|
||||
ser.Sync("ResLength", ref ResLength);
|
||||
ser.Sync("LastSectorDataWriteByte", ref LastSectorDataWriteByte);
|
||||
ser.Sync("LastSectorDataReadByte", ref LastSectorDataReadByte);
|
||||
ser.Sync("LastByteReceived", ref LastByteReceived);
|
||||
|
||||
ser.Sync("_cmdIndex", ref _cmdIndex);
|
||||
// resync the ActiveCommand
|
||||
CMDIndex = _cmdIndex;
|
||||
|
||||
ActiveCommandParams.SyncState(ser);
|
||||
|
||||
ser.Sync("IndexPulseCounter", ref IndexPulseCounter);
|
||||
//ser.SyncEnum("_activeStatus", ref _activeStatus);
|
||||
//ser.SyncEnum("_statusRaised", ref _statusRaised);
|
||||
|
||||
ser.Sync("CMD_FLAG_MT", ref CMD_FLAG_MT);
|
||||
ser.Sync("CMD_FLAG_MF", ref CMD_FLAG_MF);
|
||||
ser.Sync("CMD_FLAG_SK", ref CMD_FLAG_SK);
|
||||
ser.Sync("SRT", ref SRT);
|
||||
ser.Sync("HUT", ref HUT);
|
||||
ser.Sync("HLT", ref HLT);
|
||||
ser.Sync("ND", ref ND);
|
||||
ser.Sync("SRT_Counter", ref SRT_Counter);
|
||||
ser.Sync("HUT_Counter", ref HUT_Counter);
|
||||
ser.Sync("HLT_Counter", ref HLT_Counter);
|
||||
|
||||
ser.Sync("SectorDelayCounter", ref SectorDelayCounter);
|
||||
ser.Sync("SectorID", ref SectorID);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Timing
|
||||
|
||||
ser.Sync("LastCPUCycle", ref LastCPUCycle);
|
||||
ser.Sync("StatusDelay", ref StatusDelay);
|
||||
ser.Sync("TickCounter", ref TickCounter);
|
||||
ser.Sync("DriveCycleCounter", ref DriveCycleCounter);
|
||||
|
||||
#endregion
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper methods
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the specified bit value from supplied byte
|
||||
/// </summary>
|
||||
/// <param name="bitNumber"></param>
|
||||
/// <param name="dataByte"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetBit(int bitNumber, byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return false;
|
||||
|
||||
BitArray bi = new BitArray(new byte[] { dataByte });
|
||||
|
||||
return bi[bitNumber];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified bit of the supplied byte to 1
|
||||
/// </summary>
|
||||
/// <param name="bitNumber"></param>
|
||||
/// <param name=""></param>
|
||||
public static void SetBit(int bitNumber, ref byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return;
|
||||
|
||||
int db = (int)dataByte;
|
||||
|
||||
db |= 1 << bitNumber;
|
||||
|
||||
dataByte = (byte)db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified bit of the supplied byte to 0
|
||||
/// </summary>
|
||||
/// <param name="bitNumber"></param>
|
||||
/// <param name=""></param>
|
||||
public static void UnSetBit(int bitNumber, ref byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return;
|
||||
|
||||
int db = (int)dataByte;
|
||||
|
||||
db &= ~(1 << bitNumber);
|
||||
|
||||
dataByte = (byte)db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a drive number (0-3) based on the first two bits of the supplied byte
|
||||
/// </summary>
|
||||
/// <param name="dataByte"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetUnitSelect(byte dataByte)
|
||||
{
|
||||
int driveNumber = dataByte & 0x03;
|
||||
return driveNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the first two bits of a byte based on the supplied drive number (0-3)
|
||||
/// </summary>
|
||||
/// <param name="driveNumber"></param>
|
||||
/// <param name="dataByte"></param>
|
||||
public static void SetUnitSelect(int driveNumber, ref byte dataByte)
|
||||
{
|
||||
switch (driveNumber)
|
||||
{
|
||||
case 0:
|
||||
UnSetBit(SR0_US0, ref dataByte);
|
||||
UnSetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
case 1:
|
||||
SetBit(SR0_US0, ref dataByte);
|
||||
UnSetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
case 2:
|
||||
SetBit(SR0_US1, ref dataByte);
|
||||
UnSetBit(SR0_US0, ref dataByte);
|
||||
break;
|
||||
case 3:
|
||||
SetBit(SR0_US0, ref dataByte);
|
||||
SetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The 48k keyboard device
|
||||
/// </summary>
|
||||
public class StandardKeyboard : IKeyboard
|
||||
{
|
||||
public CPCBase _machine { get; set; }
|
||||
|
||||
private int _currentLine;
|
||||
public int CurrentLine
|
||||
{
|
||||
get { return _currentLine; }
|
||||
set { _currentLine = value; }
|
||||
}
|
||||
|
||||
private bool[] _keyStatus;
|
||||
public bool[] KeyStatus
|
||||
{
|
||||
get { return _keyStatus; }
|
||||
set { _keyStatus = value; }
|
||||
}
|
||||
|
||||
private string[] _keyboardMatrix;
|
||||
public string[] KeyboardMatrix
|
||||
{
|
||||
get { return _keyboardMatrix; }
|
||||
set { _keyboardMatrix = value; }
|
||||
}
|
||||
|
||||
private string[] _nonMatrixKeys;
|
||||
public string[] NonMatrixKeys
|
||||
{
|
||||
get { return _nonMatrixKeys; }
|
||||
set { _nonMatrixKeys = value; }
|
||||
}
|
||||
|
||||
public StandardKeyboard(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
|
||||
// scancode rows, ascending (Bit0 - Bit7)
|
||||
KeyboardMatrix = new string[]
|
||||
{
|
||||
// 0x40
|
||||
"Key CURUP", "Key CURRIGHT", "Key CURDOWN", "Key NUM9", "Key NUM6", "Key NUM3", "Key ENTER", "Key NUMPERIOD",
|
||||
// 0x41
|
||||
"Key CURLEFT", "Key COPY", "Key NUM7", "Key NUM8", "Key NUM5", "Key NUM1", "Key NUM2", "Key NUM0",
|
||||
// 0x42
|
||||
"Key CLR", "Key LeftBracket", "Key RETURN", "Key RightBracket", "Key NUM4", "Key SHIFT", "Key BackSlash", "Key CONTROL",
|
||||
// 0x43
|
||||
"Key Hat", "Key Dash", "Key @", "Key P", "Key SemiColon", "Key Colon", "Key ForwardSlash", "Key Period",
|
||||
// 0x44
|
||||
"Key 0", "Key 9", "Key O", "Key I", "Key L", "Key K", "Key M", "Key Comma",
|
||||
// 0x45
|
||||
"Key 8", "Key 7", "Key U", "Key Y", "Key H", "Key J", "Key N", "Key SPACE",
|
||||
// 0x46
|
||||
"Key 6", "Key 5", "Key R", "Key T", "Key G", "Key F", "Key B", "Key V",
|
||||
// 0x47
|
||||
"Key 4", "Key 3", "Key E", "Key W", "Key S", "Key D", "Key C", "Key X",
|
||||
// 0x48
|
||||
"Key 1", "Key 2", "Key ESC", "Key Q", "Key TAB", "Key A", "Key CAPSLOCK", "Key Z",
|
||||
// 0x49
|
||||
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Fire1", "P1 Fire2", "P1 Fire3", "Key DEL",
|
||||
|
||||
};
|
||||
|
||||
// keystatus array to match the matrix
|
||||
KeyStatus = new bool[8 * 10];
|
||||
|
||||
// nonmatrix keys (anything that hasnt already been taken)
|
||||
var nonMatrix = new List<string>();
|
||||
|
||||
foreach (var key in _machine.CPC.AmstradCPCControllerDefinition.BoolButtons)
|
||||
{
|
||||
if (!KeyboardMatrix.Any(s => s == key))
|
||||
nonMatrix.Add(key);
|
||||
}
|
||||
|
||||
NonMatrixKeys = nonMatrix.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the currently selected line
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte ReadCurrentLine()
|
||||
{
|
||||
var lin = _currentLine - 0x40;
|
||||
var pos = lin * 8;
|
||||
var l = KeyStatus.Skip(pos).Take(8).Reverse().ToArray();
|
||||
BitArray bi = new BitArray(l);
|
||||
byte[] bytes = new byte[1];
|
||||
bi.CopyTo(bytes, 0);
|
||||
return bytes[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the key within the matrix
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public int GetKeyIndexFromMatrix(string key)
|
||||
{
|
||||
int index = Array.IndexOf(KeyboardMatrix, key);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets key status
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="isPressed"></param>
|
||||
public void SetKeyStatus(string key, bool isPressed)
|
||||
{
|
||||
int index = GetKeyIndexFromMatrix(key);
|
||||
KeyStatus[index] = isPressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a key's status
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public bool GetKeyStatus(string key)
|
||||
{
|
||||
int index = GetKeyIndexFromMatrix(key);
|
||||
return KeyStatus[index];
|
||||
}
|
||||
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Keyboard");
|
||||
ser.Sync("currentLine", ref _currentLine);
|
||||
ser.Sync("keyStatus", ref _keyStatus, false);
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,818 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Based heavily on the YM-2149F / AY-3-8910 emulator used in Unreal Speccy
|
||||
/// (Originally created under Public Domain license by SMT jan.2006) ///
|
||||
/// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.cpp
|
||||
/// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.h
|
||||
/// </summary>
|
||||
public class AY38912 : IPSG
|
||||
{
|
||||
#region Device Fields
|
||||
|
||||
/// <summary>
|
||||
/// The emulated machine (passed in via constructor)
|
||||
/// </summary>
|
||||
private CPCBase _machine;
|
||||
private IKeyboard _keyboard => _machine.KeyboardDevice;
|
||||
|
||||
private int _tStatesPerFrame;
|
||||
private int _sampleRate;
|
||||
private int _samplesPerFrame;
|
||||
private int _tStatesPerSample;
|
||||
private short[] _audioBuffer;
|
||||
private int _audioBufferIndex;
|
||||
private int _lastStateRendered;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor
|
||||
/// </summary>
|
||||
public AY38912(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the AY chip
|
||||
/// </summary>
|
||||
public void Init(int sampleRate, int tStatesPerFrame)
|
||||
{
|
||||
InitTiming(sampleRate, tStatesPerFrame);
|
||||
UpdateVolume();
|
||||
Reset();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPortIODevice
|
||||
|
||||
public bool ReadPort(ushort port, ref int value)
|
||||
{
|
||||
if (port != 0xfffd)
|
||||
{
|
||||
// port read is not addressing this device
|
||||
return false;
|
||||
}
|
||||
|
||||
value = PortRead();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool WritePort(ushort port, int value)
|
||||
{
|
||||
if (port == 0xfffd)
|
||||
{
|
||||
// register select
|
||||
SelectedRegister = value & 0x0f;
|
||||
return true;
|
||||
}
|
||||
else if (port == 0xbffd)
|
||||
{
|
||||
// Update the audiobuffer based on the current CPU cycle
|
||||
// (this process the previous data BEFORE writing to the currently selected register)
|
||||
int d = (int)(_machine.CurrentFrameCycle);
|
||||
BufferUpdate(d);
|
||||
|
||||
// write to register
|
||||
PortWrite(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AY Implementation
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// AY mixer panning configuration
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AYPanConfig
|
||||
{
|
||||
MONO = 0,
|
||||
ABC = 1,
|
||||
ACB = 2,
|
||||
BAC = 3,
|
||||
BCA = 4,
|
||||
CAB = 5,
|
||||
CBA = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AY panning configuration
|
||||
/// </summary>
|
||||
public AYPanConfig PanningConfiguration
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentPanTab;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _currentPanTab)
|
||||
{
|
||||
_currentPanTab = value;
|
||||
UpdateVolume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AY chip output volume
|
||||
/// (0 - 100)
|
||||
/// </summary>
|
||||
public int Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
//value = Math.Max(0, value);
|
||||
//value = Math.Max(100, value);
|
||||
if (_volume == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_volume = value;
|
||||
UpdateVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected register
|
||||
/// </summary>
|
||||
public int SelectedRegister
|
||||
{
|
||||
get { return _activeRegister; }
|
||||
set
|
||||
{
|
||||
_activeRegister = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the PSG
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
/*
|
||||
_noiseVal = 0x0FFFF;
|
||||
_outABC = 0;
|
||||
_outNoiseABC = 0;
|
||||
_counterNoise = 0;
|
||||
_counterA = 0;
|
||||
_counterB = 0;
|
||||
_counterC = 0;
|
||||
_EnvelopeCounterBend = 0;
|
||||
|
||||
// clear all the registers
|
||||
for (int i = 0; i < 14; i++)
|
||||
{
|
||||
SelectedRegister = i;
|
||||
PortWrite(0);
|
||||
}
|
||||
|
||||
randomSeed = 1;
|
||||
|
||||
// number of frames to update
|
||||
var fr = (_audioBufferIndex * _tStatesPerFrame) / _audioBuffer.Length;
|
||||
|
||||
// update the audio buffer
|
||||
BufferUpdate(fr);
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value from the currently selected register
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int PortRead()
|
||||
{
|
||||
if (_activeRegister == 14)
|
||||
{
|
||||
// exteral keyboard register
|
||||
return _keyboard.ReadCurrentLine();
|
||||
}
|
||||
|
||||
return _registers[_activeRegister];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the currently selected register
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void PortWrite(int value)
|
||||
{
|
||||
if (_activeRegister == 14)
|
||||
{
|
||||
// external keyboard register
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeRegister >= 0x10)
|
||||
return;
|
||||
|
||||
byte val = (byte)value;
|
||||
|
||||
if (((1 << _activeRegister) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13))) != 0)
|
||||
val &= 0x0F;
|
||||
|
||||
if (((1 << _activeRegister) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10))) != 0)
|
||||
val &= 0x1F;
|
||||
|
||||
if (_activeRegister != 13 && _registers[_activeRegister] == val)
|
||||
return;
|
||||
|
||||
_registers[_activeRegister] = val;
|
||||
|
||||
switch (_activeRegister)
|
||||
{
|
||||
// Channel A (Combined Pitch)
|
||||
// (not written to directly)
|
||||
case 0:
|
||||
case 1:
|
||||
_dividerA = _registers[AY_A_FINE] | (_registers[AY_A_COARSE] << 8);
|
||||
break;
|
||||
// Channel B (Combined Pitch)
|
||||
// (not written to directly)
|
||||
case 2:
|
||||
case 3:
|
||||
_dividerB = _registers[AY_B_FINE] | (_registers[AY_B_COARSE] << 8);
|
||||
break;
|
||||
// Channel C (Combined Pitch)
|
||||
// (not written to directly)
|
||||
case 4:
|
||||
case 5:
|
||||
_dividerC = _registers[AY_C_FINE] | (_registers[AY_C_COARSE] << 8);
|
||||
break;
|
||||
// Noise Pitch
|
||||
case 6:
|
||||
_dividerN = val * 2;
|
||||
break;
|
||||
// Mixer
|
||||
case 7:
|
||||
_bit0 = 0 - ((val >> 0) & 1);
|
||||
_bit1 = 0 - ((val >> 1) & 1);
|
||||
_bit2 = 0 - ((val >> 2) & 1);
|
||||
_bit3 = 0 - ((val >> 3) & 1);
|
||||
_bit4 = 0 - ((val >> 4) & 1);
|
||||
_bit5 = 0 - ((val >> 5) & 1);
|
||||
break;
|
||||
// Channel Volumes
|
||||
case 8:
|
||||
_eMaskA = (val & 0x10) != 0 ? -1 : 0;
|
||||
_vA = ((val & 0x0F) * 2 + 1) & ~_eMaskA;
|
||||
break;
|
||||
case 9:
|
||||
_eMaskB = (val & 0x10) != 0 ? -1 : 0;
|
||||
_vB = ((val & 0x0F) * 2 + 1) & ~_eMaskB;
|
||||
break;
|
||||
case 10:
|
||||
_eMaskC = (val & 0x10) != 0 ? -1 : 0;
|
||||
_vC = ((val & 0x0F) * 2 + 1) & ~_eMaskC;
|
||||
break;
|
||||
// Envelope (Combined Duration)
|
||||
// (not written to directly)
|
||||
case 11:
|
||||
case 12:
|
||||
_dividerE = _registers[AY_E_FINE] | (_registers[AY_E_COARSE] << 8);
|
||||
break;
|
||||
// Envelope Shape
|
||||
case 13:
|
||||
// reset the envelope counter
|
||||
_countE = 0;
|
||||
|
||||
if ((_registers[AY_E_SHAPE] & 4) != 0)
|
||||
{
|
||||
// attack
|
||||
_eState = 0;
|
||||
_eDirection = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// decay
|
||||
_eState = 31;
|
||||
_eDirection = -1;
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
// IO Port - not implemented
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start of frame
|
||||
/// </summary>
|
||||
public void StartFrame()
|
||||
{
|
||||
_audioBufferIndex = 0;
|
||||
BufferUpdate(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of frame
|
||||
/// </summary>
|
||||
public void EndFrame()
|
||||
{
|
||||
BufferUpdate(_tStatesPerFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the audiobuffer based on the current frame t-state
|
||||
/// </summary>
|
||||
/// <param name="frameCycle"></param>
|
||||
public void UpdateSound(int frameCycle)
|
||||
{
|
||||
BufferUpdate(frameCycle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// Register indicies
|
||||
/// </summary>
|
||||
private const int AY_A_FINE = 0;
|
||||
private const int AY_A_COARSE = 1;
|
||||
private const int AY_B_FINE = 2;
|
||||
private const int AY_B_COARSE = 3;
|
||||
private const int AY_C_FINE = 4;
|
||||
private const int AY_C_COARSE = 5;
|
||||
private const int AY_NOISEPITCH = 6;
|
||||
private const int AY_MIXER = 7;
|
||||
private const int AY_A_VOL = 8;
|
||||
private const int AY_B_VOL = 9;
|
||||
private const int AY_C_VOL = 10;
|
||||
private const int AY_E_FINE = 11;
|
||||
private const int AY_E_COARSE = 12;
|
||||
private const int AY_E_SHAPE = 13;
|
||||
private const int AY_PORT_A = 14;
|
||||
private const int AY_PORT_B = 15;
|
||||
|
||||
/// <summary>
|
||||
/// The register array
|
||||
/*
|
||||
The AY-3-8910/8912 contains 16 internal registers as follows:
|
||||
|
||||
Register Function Range
|
||||
0 Channel A fine pitch 8-bit (0-255)
|
||||
1 Channel A course pitch 4-bit (0-15)
|
||||
2 Channel B fine pitch 8-bit (0-255)
|
||||
3 Channel B course pitch 4-bit (0-15)
|
||||
4 Channel C fine pitch 8-bit (0-255)
|
||||
5 Channel C course pitch 4-bit (0-15)
|
||||
6 Noise pitch 5-bit (0-31)
|
||||
7 Mixer 8-bit (see below)
|
||||
8 Channel A volume 4-bit (0-15, see below)
|
||||
9 Channel B volume 4-bit (0-15, see below)
|
||||
10 Channel C volume 4-bit (0-15, see below)
|
||||
11 Envelope fine duration 8-bit (0-255)
|
||||
12 Envelope course duration 8-bit (0-255)
|
||||
13 Envelope shape 4-bit (0-15)
|
||||
14 I/O port A 8-bit (0-255)
|
||||
15 I/O port B 8-bit (0-255) (Not present on the AY-3-8912)
|
||||
|
||||
* The volume registers (8, 9 and 10) contain a 4-bit setting but if bit 5 is set then that channel uses the
|
||||
envelope defined by register 13 and ignores its volume setting.
|
||||
* The mixer (register 7) is made up of the following bits (low=enabled):
|
||||
|
||||
Bit: 7 6 5 4 3 2 1 0
|
||||
Register: I/O I/O Noise Noise Noise Tone Tone Tone
|
||||
Channel: B A C B A C B A
|
||||
|
||||
The AY-3-8912 ignores bit 7 of this register.
|
||||
*/
|
||||
/// </summary>
|
||||
private int[] _registers = new int[16];
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected register
|
||||
/// </summary>
|
||||
private byte _activeRegister;
|
||||
|
||||
/// <summary>
|
||||
/// The frequency of the AY chip
|
||||
/// </summary>
|
||||
private static int _chipFrequency = 1773400;
|
||||
|
||||
/// <summary>
|
||||
/// The rendering resolution of the chip
|
||||
/// </summary>
|
||||
private double _resolution = 50D * 8D / _chipFrequency;
|
||||
|
||||
/// <summary>
|
||||
/// Channel generator state
|
||||
/// </summary>
|
||||
private int _bitA;
|
||||
private int _bitB;
|
||||
private int _bitC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope state
|
||||
/// </summary>
|
||||
private int _eState;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope direction
|
||||
/// </summary>
|
||||
private int _eDirection;
|
||||
|
||||
/// <summary>
|
||||
/// Noise seed
|
||||
/// </summary>
|
||||
private int _noiseSeed;
|
||||
|
||||
/// <summary>
|
||||
/// Mixer state
|
||||
/// </summary>
|
||||
private int _bit0;
|
||||
private int _bit1;
|
||||
private int _bit2;
|
||||
private int _bit3;
|
||||
private int _bit4;
|
||||
private int _bit5;
|
||||
|
||||
/// <summary>
|
||||
/// Noise generator state
|
||||
/// </summary>
|
||||
private int _bitN;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope masks
|
||||
/// </summary>
|
||||
private int _eMaskA;
|
||||
private int _eMaskB;
|
||||
private int _eMaskC;
|
||||
|
||||
/// <summary>
|
||||
/// Amplitudes
|
||||
/// </summary>
|
||||
private int _vA;
|
||||
private int _vB;
|
||||
private int _vC;
|
||||
|
||||
/// <summary>
|
||||
/// Channel gen counters
|
||||
/// </summary>
|
||||
private int _countA;
|
||||
private int _countB;
|
||||
private int _countC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope gen counter
|
||||
/// </summary>
|
||||
private int _countE;
|
||||
|
||||
/// <summary>
|
||||
/// Noise gen counter
|
||||
/// </summary>
|
||||
private int _countN;
|
||||
|
||||
/// <summary>
|
||||
/// Channel gen dividers
|
||||
/// </summary>
|
||||
private int _dividerA;
|
||||
private int _dividerB;
|
||||
private int _dividerC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope gen divider
|
||||
/// </summary>
|
||||
private int _dividerE;
|
||||
|
||||
/// <summary>
|
||||
/// Noise gen divider
|
||||
/// </summary>
|
||||
private int _dividerN;
|
||||
|
||||
/// <summary>
|
||||
/// Panning table list
|
||||
/// </summary>
|
||||
private static List<uint[]> PanTabs = new List<uint[]>
|
||||
{
|
||||
// MONO
|
||||
new uint[] { 50,50, 50,50, 50,50 },
|
||||
// ABC
|
||||
new uint[] { 100,10, 66,66, 10,100 },
|
||||
// ACB
|
||||
new uint[] { 100,10, 10,100, 66,66 },
|
||||
// BAC
|
||||
new uint[] { 66,66, 100,10, 10,100 },
|
||||
// BCA
|
||||
new uint[] { 10,100, 100,10, 66,66 },
|
||||
// CAB
|
||||
new uint[] { 66,66, 10,100, 100,10 },
|
||||
// CBA
|
||||
new uint[] { 10,100, 66,66, 100,10 }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected panning configuration
|
||||
/// </summary>
|
||||
private AYPanConfig _currentPanTab = AYPanConfig.ABC;
|
||||
|
||||
/// <summary>
|
||||
/// The current volume
|
||||
/// </summary>
|
||||
private int _volume = 75;
|
||||
|
||||
/// <summary>
|
||||
/// Volume tables state
|
||||
/// </summary>
|
||||
private uint[][] _volumeTables;
|
||||
|
||||
/// <summary>
|
||||
/// Volume table to be used
|
||||
/// </summary>
|
||||
private static uint[] AYVolumes = new uint[]
|
||||
{
|
||||
0x0000,0x0000,0x0340,0x0340,0x04C0,0x04C0,0x06F2,0x06F2,
|
||||
0x0A44,0x0A44,0x0F13,0x0F13,0x1510,0x1510,0x227E,0x227E,
|
||||
0x289F,0x289F,0x414E,0x414E,0x5B21,0x5B21,0x7258,0x7258,
|
||||
0x905E,0x905E,0xB550,0xB550,0xD7A0,0xD7A0,0xFFFF,0xFFFF,
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Forces an update of the volume tables
|
||||
/// </summary>
|
||||
private void UpdateVolume()
|
||||
{
|
||||
int upperFloor = 40000;
|
||||
var inc = (0xFFFF - upperFloor) / 100;
|
||||
|
||||
var vol = inc * _volume; // ((ulong)0xFFFF * (ulong)_volume / 100UL) - 20000 ;
|
||||
_volumeTables = new uint[6][];
|
||||
|
||||
// parent array
|
||||
for (int j = 0; j < _volumeTables.Length; j++)
|
||||
{
|
||||
_volumeTables[j] = new uint[32];
|
||||
|
||||
// child array
|
||||
for (int i = 0; i < _volumeTables[j].Length; i++)
|
||||
{
|
||||
_volumeTables[j][i] = (uint)(
|
||||
(PanTabs[(int)_currentPanTab][j] * AYVolumes[i] * vol) /
|
||||
(3 * 65535 * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int mult_const;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes timing information for the frame
|
||||
/// </summary>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="frameTactCount"></param>
|
||||
private void InitTiming(int sampleRate, int frameTactCount)
|
||||
{
|
||||
_sampleRate = sampleRate;
|
||||
_tStatesPerFrame = frameTactCount;
|
||||
_samplesPerFrame = 882;
|
||||
|
||||
_tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) /
|
||||
//(16D * (double)_sampleRate),
|
||||
//MidpointRounding.AwayFromZero);
|
||||
|
||||
//_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
|
||||
_audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50];
|
||||
_audioBufferIndex = 0;
|
||||
|
||||
mult_const = ((_chipFrequency / 8) << 14) / _machine.GateArray.Z80ClockSpeed;
|
||||
|
||||
var aytickspercputick = (double)_machine.GateArray.Z80ClockSpeed / (double)_chipFrequency;
|
||||
int ayCyclesPerSample = (int)((double)_tStatesPerSample * (double)aytickspercputick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the audiobuffer based on the current frame t-state
|
||||
/// </summary>
|
||||
/// <param name="cycle"></param>
|
||||
private void BufferUpdate(int cycle)
|
||||
{
|
||||
if (cycle > _tStatesPerFrame)
|
||||
{
|
||||
// we are outside of the frame - just process the last value
|
||||
cycle = _tStatesPerFrame;
|
||||
}
|
||||
|
||||
// get the current length of the audiobuffer
|
||||
int bufferLength = _samplesPerFrame; // _audioBuffer.Length;
|
||||
|
||||
int toEnd = ((bufferLength * cycle) / _tStatesPerFrame);
|
||||
|
||||
// loop through the number of samples we need to render
|
||||
while (_audioBufferIndex < toEnd)
|
||||
{
|
||||
// run the AY chip processing at the correct resolution
|
||||
for (int i = 0; i < _tStatesPerSample / 14; i++)
|
||||
{
|
||||
if (++_countA >= _dividerA)
|
||||
{
|
||||
_countA = 0;
|
||||
_bitA ^= -1;
|
||||
}
|
||||
|
||||
if (++_countB >= _dividerB)
|
||||
{
|
||||
_countB = 0;
|
||||
_bitB ^= -1;
|
||||
}
|
||||
|
||||
if (++_countC >= _dividerC)
|
||||
{
|
||||
_countC = 0;
|
||||
_bitC ^= -1;
|
||||
}
|
||||
|
||||
if (++_countN >= _dividerN)
|
||||
{
|
||||
_countN = 0;
|
||||
_noiseSeed = (_noiseSeed * 2 + 1) ^ (((_noiseSeed >> 16) ^ (_noiseSeed >> 13)) & 1);
|
||||
_bitN = 0 - ((_noiseSeed >> 16) & 1);
|
||||
}
|
||||
|
||||
if (++_countE >= _dividerE)
|
||||
{
|
||||
_countE = 0;
|
||||
_eState += +_eDirection;
|
||||
|
||||
if ((_eState & ~31) != 0)
|
||||
{
|
||||
var mask = (1 << _registers[AY_E_SHAPE]);
|
||||
|
||||
if ((mask & ((1 << 0) | (1 << 1) | (1 << 2) |
|
||||
(1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) |
|
||||
(1 << 7) | (1 << 9) | (1 << 15))) != 0)
|
||||
{
|
||||
_eState = _eDirection = 0;
|
||||
}
|
||||
else if ((mask & ((1 << 8) | (1 << 12))) != 0)
|
||||
{
|
||||
_eState &= 31;
|
||||
}
|
||||
else if ((mask & ((1 << 10) | (1 << 14))) != 0)
|
||||
{
|
||||
_eDirection = -_eDirection;
|
||||
_eState += _eDirection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 11,13
|
||||
_eState = 31;
|
||||
_eDirection = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mix the sample
|
||||
var mixA = ((_eMaskA & _eState) | _vA) & ((_bitA | _bit0) & (_bitN | _bit3));
|
||||
var mixB = ((_eMaskB & _eState) | _vB) & ((_bitB | _bit1) & (_bitN | _bit4));
|
||||
var mixC = ((_eMaskC & _eState) | _vC) & ((_bitC | _bit2) & (_bitN | _bit5));
|
||||
|
||||
var l = _volumeTables[0][mixA];
|
||||
var r = _volumeTables[1][mixA];
|
||||
|
||||
l += _volumeTables[2][mixB];
|
||||
r += _volumeTables[3][mixB];
|
||||
l += _volumeTables[4][mixC];
|
||||
r += _volumeTables[5][mixC];
|
||||
|
||||
_audioBuffer[_audioBufferIndex * 2] = (short)l;
|
||||
_audioBuffer[(_audioBufferIndex * 2) + 1] = (short)r;
|
||||
|
||||
_audioBufferIndex++;
|
||||
}
|
||||
|
||||
_lastStateRendered = cycle;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region ISoundProvider
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_audioBuffer = new short[_samplesPerFrame * 2];
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
nsamp = _samplesPerFrame;
|
||||
samples = _audioBuffer;
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
public int nullDump = 0;
|
||||
|
||||
/// <summary>
|
||||
/// State serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("PSG-AY");
|
||||
|
||||
ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
|
||||
ser.Sync("_sampleRate", ref _sampleRate);
|
||||
ser.Sync("_samplesPerFrame", ref _samplesPerFrame);
|
||||
ser.Sync("_tStatesPerSample", ref _tStatesPerSample);
|
||||
ser.Sync("_audioBufferIndex", ref _audioBufferIndex);
|
||||
ser.Sync("_audioBuffer", ref _audioBuffer, false);
|
||||
|
||||
ser.Sync("_registers", ref _registers, false);
|
||||
ser.Sync("_activeRegister", ref _activeRegister);
|
||||
ser.Sync("_bitA", ref _bitA);
|
||||
ser.Sync("_bitB", ref _bitB);
|
||||
ser.Sync("_bitC", ref _bitC);
|
||||
ser.Sync("_eState", ref _eState);
|
||||
ser.Sync("_eDirection", ref _eDirection);
|
||||
ser.Sync("_noiseSeed", ref _noiseSeed);
|
||||
ser.Sync("_bit0", ref _bit0);
|
||||
ser.Sync("_bit1", ref _bit1);
|
||||
ser.Sync("_bit2", ref _bit2);
|
||||
ser.Sync("_bit3", ref _bit3);
|
||||
ser.Sync("_bit4", ref _bit4);
|
||||
ser.Sync("_bit5", ref _bit5);
|
||||
ser.Sync("_bitN", ref _bitN);
|
||||
ser.Sync("_eMaskA", ref _eMaskA);
|
||||
ser.Sync("_eMaskB", ref _eMaskB);
|
||||
ser.Sync("_eMaskC", ref _eMaskC);
|
||||
ser.Sync("_vA", ref _vA);
|
||||
ser.Sync("_vB", ref _vB);
|
||||
ser.Sync("_vC", ref _vC);
|
||||
ser.Sync("_countA", ref _countA);
|
||||
ser.Sync("_countB", ref _countB);
|
||||
ser.Sync("_countC", ref _countC);
|
||||
ser.Sync("_countE", ref _countE);
|
||||
ser.Sync("_countN", ref _countN);
|
||||
ser.Sync("_dividerA", ref _dividerA);
|
||||
ser.Sync("_dividerB", ref _dividerB);
|
||||
ser.Sync("_dividerC", ref _dividerC);
|
||||
ser.Sync("_dividerE", ref _dividerE);
|
||||
ser.Sync("_dividerN", ref _dividerN);
|
||||
ser.SyncEnum("_currentPanTab", ref _currentPanTab);
|
||||
ser.Sync("_volume", ref nullDump);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
ser.Sync("volTable" + i, ref _volumeTables[i], false);
|
||||
}
|
||||
|
||||
if (ser.IsReader)
|
||||
_volume = _machine.CPC.Settings.AYVolume;
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical Beeper class
|
||||
/// Used to emulate the sound generated by tape loading
|
||||
/// This implementation uses BlipBuffer and should *always* output at 44100 with 882 samples per frame
|
||||
/// (so that it can be mixed easily further down the line)
|
||||
/// </summary>
|
||||
public class Beeper : ISoundProvider, IBeeperDevice
|
||||
{
|
||||
#region Fields and Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sample Rate
|
||||
/// This usually has to be 44100 for ISoundProvider
|
||||
/// </summary>
|
||||
private int _sampleRate;
|
||||
public int SampleRate
|
||||
{
|
||||
get { return _sampleRate; }
|
||||
set { _sampleRate = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buzzer volume
|
||||
/// Accepts an int 0-100 value
|
||||
/// </summary>
|
||||
private int _volume;
|
||||
public int Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return VolumeConverterOut(_volume);
|
||||
}
|
||||
set
|
||||
{
|
||||
var newVol = VolumeConverterIn(value);
|
||||
if (newVol != _volume)
|
||||
blip.Clear();
|
||||
_volume = VolumeConverterIn(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The last used volume (used to modify blipbuffer delta values)
|
||||
/// </summary>
|
||||
private int lastVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The number of cpu cycles per frame
|
||||
/// </summary>
|
||||
private long _tStatesPerFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The parent emulated machine
|
||||
/// </summary>
|
||||
private CPCBase _machine;
|
||||
|
||||
/// <summary>
|
||||
/// The last pulse
|
||||
/// </summary>
|
||||
private bool LastPulse;
|
||||
|
||||
/// <summary>
|
||||
/// The last T-State (cpu cycle) that the last pulse was received
|
||||
/// </summary>
|
||||
private long LastPulseTState;
|
||||
|
||||
/// <summary>
|
||||
/// Device blipbuffer
|
||||
/// </summary>
|
||||
private readonly BlipBuffer blip = new BlipBuffer(882);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Takes an int 0-100 and returns the relevant short volume to output
|
||||
/// </summary>
|
||||
/// <param name="vol"></param>
|
||||
/// <returns></returns>
|
||||
private int VolumeConverterIn(int vol)
|
||||
{
|
||||
int maxLimit = short.MaxValue / 3;
|
||||
int increment = maxLimit / 100;
|
||||
|
||||
return vol * increment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes an short volume and returns the relevant int value 0-100
|
||||
/// </summary>
|
||||
/// <param name="vol"></param>
|
||||
/// <returns></returns>
|
||||
private int VolumeConverterOut(int shortvol)
|
||||
{
|
||||
int maxLimit = short.MaxValue / 3;
|
||||
int increment = maxLimit / 100;
|
||||
|
||||
if (shortvol > maxLimit)
|
||||
shortvol = maxLimit;
|
||||
|
||||
return shortvol / increment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialisation
|
||||
|
||||
public Beeper(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the beeper
|
||||
/// </summary>
|
||||
public void Init(int sampleRate, int tStatesPerFrame)
|
||||
{
|
||||
blip.SetRates((tStatesPerFrame * 50), sampleRate);
|
||||
_sampleRate = sampleRate;
|
||||
_tStatesPerFrame = tStatesPerFrame;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IBeeperDevice
|
||||
|
||||
/// <summary>
|
||||
/// Processes an incoming pulse value and adds it to the blipbuffer
|
||||
/// </summary>
|
||||
/// <param name="pulse"></param>
|
||||
public void ProcessPulseValue(bool pulse)
|
||||
{
|
||||
if (!_machine._renderSound)
|
||||
return;
|
||||
|
||||
if (LastPulse == pulse)
|
||||
{
|
||||
// no change
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, 0);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (pulse)
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, (short)(_volume));
|
||||
else
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, -(short)(_volume));
|
||||
|
||||
lastVolume = _volume;
|
||||
}
|
||||
|
||||
LastPulse = pulse;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ISoundProvider
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
blip.Clear();
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
blip.EndFrame((uint)_tStatesPerFrame);
|
||||
nsamp = blip.SamplesAvailable();
|
||||
samples = new short[nsamp * 2];
|
||||
blip.ReadSamples(samples, nsamp, true);
|
||||
for (int i = 0; i < nsamp * 2; i += 2)
|
||||
{
|
||||
samples[i + 1] = samples[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Buzzer");
|
||||
ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
|
||||
ser.Sync("_sampleRate", ref _sampleRate);
|
||||
ser.Sync("LastPulse", ref LastPulse);
|
||||
ser.Sync("LastPulseTState", ref LastPulseTState);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
public sealed class PSG : ISoundProvider
|
||||
{
|
||||
private readonly BlipBuffer _blip = new BlipBuffer(4096);
|
||||
private short[] _sampleBuffer = new short[0];
|
||||
|
||||
|
||||
public PSG()
|
||||
{
|
||||
_blip.SetRates(4000000 / 4.0, 44100);
|
||||
}
|
||||
|
||||
public ushort[] Register = new ushort[16];
|
||||
|
||||
public int total_clock; // TODO: what is this used for?
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
clock_A = clock_B = clock_C = 0x1000;
|
||||
noise_clock = 0x20;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
Register[i] = 0x0000;
|
||||
}
|
||||
sync_psg_state();
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_blip.Clear();
|
||||
_sampleClock = 0;
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
{
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
_blip.EndFrame((uint)_sampleClock);
|
||||
_sampleClock = 0;
|
||||
|
||||
nsamp = _blip.SamplesAvailable();
|
||||
int targetLength = nsamp * 2;
|
||||
if (_sampleBuffer.Length != targetLength)
|
||||
{
|
||||
_sampleBuffer = new short[targetLength];
|
||||
}
|
||||
|
||||
_blip.ReadSamplesLeft(_sampleBuffer, nsamp);
|
||||
for (int i = 0; i < _sampleBuffer.Length; i += 2)
|
||||
{
|
||||
_sampleBuffer[i + 1] = _sampleBuffer[i];
|
||||
}
|
||||
|
||||
samples = _sampleBuffer;
|
||||
}
|
||||
|
||||
public void GetSamples(short[] samples)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private static readonly int[] VolumeTable =
|
||||
{
|
||||
0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA,
|
||||
0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA
|
||||
};
|
||||
|
||||
private int _sampleClock;
|
||||
private int _latchedSample;
|
||||
|
||||
private int TotalExecutedCycles;
|
||||
private int PendingCycles;
|
||||
private int psg_clock;
|
||||
private int sq_per_A, sq_per_B, sq_per_C;
|
||||
private int clock_A, clock_B, clock_C;
|
||||
private int vol_A, vol_B, vol_C;
|
||||
private bool A_on, B_on, C_on;
|
||||
private bool A_up, B_up, C_up;
|
||||
private bool A_noise, B_noise, C_noise;
|
||||
|
||||
private int env_per;
|
||||
private int env_clock;
|
||||
private int env_shape;
|
||||
private int env_E;
|
||||
private int E_up_down;
|
||||
private int env_vol_A, env_vol_B, env_vol_C;
|
||||
|
||||
private int noise_clock;
|
||||
private int noise_per;
|
||||
private int noise = 0x1;
|
||||
|
||||
public Func<ushort, bool, ushort> ReadMemory;
|
||||
public Func<ushort, ushort, bool, bool> WriteMemory;
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("PSG");
|
||||
|
||||
ser.Sync("Register", ref Register, false);
|
||||
ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles);
|
||||
ser.Sync("Pending_Cycles", ref PendingCycles);
|
||||
|
||||
ser.Sync("psg_clock", ref psg_clock);
|
||||
ser.Sync("clock_A", ref clock_A);
|
||||
ser.Sync("clock_B", ref clock_B);
|
||||
ser.Sync("clock_C", ref clock_C);
|
||||
ser.Sync("noise_clock", ref noise_clock);
|
||||
ser.Sync("env_clock", ref env_clock);
|
||||
ser.Sync("A_up", ref A_up);
|
||||
ser.Sync("B_up", ref B_up);
|
||||
ser.Sync("C_up", ref C_up);
|
||||
ser.Sync("noise", ref noise);
|
||||
ser.Sync("env_E", ref env_E);
|
||||
ser.Sync("E_up_down", ref E_up_down);
|
||||
|
||||
sync_psg_state();
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
public ushort? ReadPSG(ushort addr, bool peek)
|
||||
{
|
||||
if (addr >= 0x01F0 && addr <= 0x01FF)
|
||||
{
|
||||
return (ushort)(Register[addr - 0x01F0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void sync_psg_state()
|
||||
{
|
||||
sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8));
|
||||
if (sq_per_A == 0)
|
||||
{
|
||||
sq_per_A = 0x1000;
|
||||
}
|
||||
|
||||
sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8));
|
||||
if (sq_per_B == 0)
|
||||
{
|
||||
sq_per_B = 0x1000;
|
||||
}
|
||||
|
||||
sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8));
|
||||
if (sq_per_C == 0)
|
||||
{
|
||||
sq_per_C = 0x1000;
|
||||
}
|
||||
|
||||
env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8));
|
||||
if (env_per == 0)
|
||||
{
|
||||
env_per = 0x10000;
|
||||
}
|
||||
|
||||
env_per *= 2;
|
||||
|
||||
A_on = Register[8].Bit(0);
|
||||
B_on = Register[8].Bit(1);
|
||||
C_on = Register[8].Bit(2);
|
||||
A_noise = Register[8].Bit(3);
|
||||
B_noise = Register[8].Bit(4);
|
||||
C_noise = Register[8].Bit(5);
|
||||
|
||||
noise_per = Register[9] & 0x1F;
|
||||
if (noise_per == 0)
|
||||
{
|
||||
noise_per = 0x20;
|
||||
}
|
||||
|
||||
var shape_select = Register[10] & 0xF;
|
||||
|
||||
if (shape_select < 4)
|
||||
env_shape = 0;
|
||||
else if (shape_select < 8)
|
||||
env_shape = 1;
|
||||
else
|
||||
env_shape = 2 + (shape_select - 8);
|
||||
|
||||
vol_A = Register[11] & 0xF;
|
||||
env_vol_A = (Register[11] >> 4) & 0x3;
|
||||
|
||||
vol_B = Register[12] & 0xF;
|
||||
env_vol_B = (Register[12] >> 4) & 0x3;
|
||||
|
||||
vol_C = Register[13] & 0xF;
|
||||
env_vol_C = (Register[13] >> 4) & 0x3;
|
||||
}
|
||||
|
||||
public bool WritePSG(ushort addr, ushort value, bool poke)
|
||||
{
|
||||
if (addr >= 0x01F0 && addr <= 0x01FF)
|
||||
{
|
||||
var reg = addr - 0x01F0;
|
||||
|
||||
value &= 0xFF;
|
||||
|
||||
if (reg == 4 || reg == 5 || reg == 6 || reg == 10)
|
||||
value &= 0xF;
|
||||
|
||||
if (reg == 9)
|
||||
value &= 0x1F;
|
||||
|
||||
if (reg == 11 || reg == 12 || reg == 13)
|
||||
value &= 0x3F;
|
||||
|
||||
Register[addr - 0x01F0] = value;
|
||||
|
||||
sync_psg_state();
|
||||
|
||||
if (reg == 10)
|
||||
{
|
||||
env_clock = env_per;
|
||||
|
||||
if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5)
|
||||
{
|
||||
env_E = 15;
|
||||
E_up_down = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
env_E = 0;
|
||||
E_up_down = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void generate_sound(int cycles_to_do)
|
||||
{
|
||||
// there are 4 cpu cycles for every psg cycle
|
||||
bool sound_out_A;
|
||||
bool sound_out_B;
|
||||
bool sound_out_C;
|
||||
|
||||
for (int i = 0; i < cycles_to_do; i++)
|
||||
{
|
||||
psg_clock++;
|
||||
|
||||
if (psg_clock == 4)
|
||||
{
|
||||
psg_clock = 0;
|
||||
|
||||
total_clock++;
|
||||
|
||||
clock_A--;
|
||||
clock_B--;
|
||||
clock_C--;
|
||||
|
||||
noise_clock--;
|
||||
env_clock--;
|
||||
|
||||
// clock noise
|
||||
if (noise_clock == 0)
|
||||
{
|
||||
noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0);
|
||||
noise_clock = noise_per;
|
||||
}
|
||||
|
||||
if (env_clock == 0)
|
||||
{
|
||||
env_clock = env_per;
|
||||
|
||||
env_E += E_up_down;
|
||||
|
||||
if (env_E == 16 || env_E == -1)
|
||||
{
|
||||
|
||||
// we just completed a period of the envelope, determine what to do now based on the envelope shape
|
||||
if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9)
|
||||
{
|
||||
E_up_down = 0;
|
||||
env_E = 0;
|
||||
}
|
||||
else if (env_shape == 5 || env_shape == 7)
|
||||
{
|
||||
E_up_down = 0;
|
||||
env_E = 15;
|
||||
}
|
||||
else if (env_shape == 4 || env_shape == 8)
|
||||
{
|
||||
if (env_E == 16)
|
||||
{
|
||||
env_E = 15;
|
||||
E_up_down = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
env_E = 0;
|
||||
E_up_down = 1;
|
||||
}
|
||||
}
|
||||
else if (env_shape == 2)
|
||||
{
|
||||
env_E = 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
env_E = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clock_A == 0)
|
||||
{
|
||||
A_up = !A_up;
|
||||
clock_A = sq_per_A;
|
||||
}
|
||||
|
||||
if (clock_B == 0)
|
||||
{
|
||||
B_up = !B_up;
|
||||
clock_B = sq_per_B;
|
||||
}
|
||||
|
||||
if (clock_C == 0)
|
||||
{
|
||||
C_up = !C_up;
|
||||
clock_C = sq_per_C;
|
||||
}
|
||||
|
||||
|
||||
sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up);
|
||||
sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up);
|
||||
sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up);
|
||||
|
||||
// now calculate the volume of each channel and add them together
|
||||
int v;
|
||||
|
||||
if (env_vol_A == 0)
|
||||
{
|
||||
v = (short)(sound_out_A ? VolumeTable[vol_A] : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift_A = 3 - env_vol_A;
|
||||
if (shift_A < 0)
|
||||
shift_A = 0;
|
||||
v = (short)(sound_out_A ? (VolumeTable[env_E] >> shift_A) : 0);
|
||||
}
|
||||
|
||||
if (env_vol_B == 0)
|
||||
{
|
||||
v += (short)(sound_out_B ? VolumeTable[vol_B] : 0);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift_B = 3 - env_vol_B;
|
||||
if (shift_B < 0)
|
||||
shift_B = 0;
|
||||
v += (short)(sound_out_B ? (VolumeTable[env_E] >> shift_B) : 0);
|
||||
}
|
||||
|
||||
if (env_vol_C == 0)
|
||||
{
|
||||
v += (short)(sound_out_C ? VolumeTable[vol_C] : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift_C = 3 - env_vol_C;
|
||||
if (shift_C < 0)
|
||||
shift_C = 0;
|
||||
v += (short)(sound_out_C ? (VolumeTable[env_E] >> shift_C) : 0);
|
||||
}
|
||||
|
||||
if (v != _latchedSample)
|
||||
{
|
||||
_blip.AddDelta((uint)_sampleClock, v - _latchedSample);
|
||||
_latchedSample = v;
|
||||
}
|
||||
|
||||
_sampleClock++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
public class GateArray_CPC464 : GateArrayBase
|
||||
{
|
||||
public GateArray_CPC464(CPCBase machine)
|
||||
: base(machine)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPC464
|
||||
/// * Memory *
|
||||
/// </summary>
|
||||
public partial class CPC464 : CPCBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulates reading from the bus
|
||||
/// ROM paging should be handled here
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public override byte ReadBus(ushort addr)
|
||||
{
|
||||
int divisor = addr / 0x4000;
|
||||
byte result = 0xff;
|
||||
|
||||
switch (divisor)
|
||||
{
|
||||
// 0x000 or LowerROM
|
||||
case 0:
|
||||
if (LowerROMPaged)
|
||||
result = ROM0[addr % 0x4000];
|
||||
else
|
||||
result = RAM0[addr % 0x4000];
|
||||
break;
|
||||
|
||||
// 0x4000
|
||||
case 1:
|
||||
result = RAM1[addr % 0x4000];
|
||||
break;
|
||||
|
||||
// 0x8000
|
||||
case 2:
|
||||
result = RAM2[addr % 0x4000];
|
||||
break;
|
||||
|
||||
// 0xc000 or UpperROM
|
||||
case 3:
|
||||
if (UpperROMPaged)
|
||||
result = ROM1[addr % 0x4000];
|
||||
else
|
||||
result = RAM3[addr % 0x4000];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates writing to the bus
|
||||
/// Writes to the bus ALWAYS go to RAM, regardless of what upper and lower ROMs are paged in
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <param name="value"></param>
|
||||
public override void WriteBus(ushort addr, byte value)
|
||||
{
|
||||
int divisor = addr / 0x4000;
|
||||
|
||||
switch (divisor)
|
||||
{
|
||||
// RAM 0x000
|
||||
case 0:
|
||||
RAM0[addr % 0x4000] = value;
|
||||
break;
|
||||
|
||||
// RAM 0x4000
|
||||
case 1:
|
||||
RAM1[addr % 0x4000] = value;
|
||||
break;
|
||||
|
||||
// RAM 0x8000
|
||||
case 2:
|
||||
RAM2[addr % 0x4000] = value;
|
||||
break;
|
||||
|
||||
// RAM 0xc000
|
||||
case 3:
|
||||
RAM3[addr % 0x4000] = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte of data from a specified memory address
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
var data = ReadBus(addr);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data to a specified memory address
|
||||
/// (with memory contention if appropriate)
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <param name="value"></param>
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteBus(addr, value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the ROM
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="startAddress"></param>
|
||||
public override void InitROM(RomData romData)
|
||||
{
|
||||
RomData = romData;
|
||||
for (int i = 0; i < 0x4000; i++)
|
||||
{
|
||||
ROM0[i] = RomData.RomBytes[i];
|
||||
if (RomData.RomBytes.Length > 0x4000)
|
||||
ROM1[i] = RomData.RomBytes[i + 0x4000];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPC464
|
||||
/// * Port *
|
||||
/// </summary>
|
||||
public partial class CPC464 : CPCBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a byte of data from a specified port address
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public override byte ReadPort(ushort port)
|
||||
{
|
||||
int result = 0xff;
|
||||
|
||||
if (CRCT.ReadPort(port, ref result))
|
||||
{
|
||||
return (byte)result;
|
||||
}
|
||||
else if (GateArray.ReadPort(port, ref result))
|
||||
{
|
||||
return (byte)result;
|
||||
}
|
||||
else if (PPI.ReadPort(port, ref result))
|
||||
{
|
||||
return (byte)result;
|
||||
}
|
||||
|
||||
return (byte)result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data to a specified port address
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="value"></param>
|
||||
public override void WritePort(ushort port, byte value)
|
||||
{
|
||||
if (CRCT.WritePort(port, (int)value))
|
||||
{ }
|
||||
else if (GateArray.WritePort(port, (int)value))
|
||||
{ }
|
||||
else if (PPI.WritePort(port, (int)value))
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// CPC464 construction
|
||||
/// </summary>
|
||||
public partial class CPC464 : CPCBase
|
||||
{
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor
|
||||
/// </summary>
|
||||
/// <param name="spectrum"></param>
|
||||
/// <param name="cpu"></param>
|
||||
public CPC464(AmstradCPC cpc, Z80A cpu, List<byte[]> files)
|
||||
{
|
||||
CPC = cpc;
|
||||
CPU = cpu;
|
||||
|
||||
FrameLength = 79872;
|
||||
|
||||
CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this);
|
||||
GateArray = new GateArray_CPC464(this);
|
||||
PPI = new PPIBase(this);
|
||||
|
||||
KeyboardDevice = new StandardKeyboard(this);
|
||||
|
||||
TapeBuzzer = new Beeper(this);
|
||||
TapeBuzzer.Init(44100, FrameLength);
|
||||
|
||||
AYDevice = new AY38912(this);
|
||||
AYDevice.Init(44100, FrameLength);
|
||||
|
||||
TapeDevice = new DatacorderDevice();
|
||||
TapeDevice.Init(this);
|
||||
|
||||
InitializeMedia(files);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Input *
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
{
|
||||
string Play = "Play Tape";
|
||||
string Stop = "Stop Tape";
|
||||
string RTZ = "RTZ Tape";
|
||||
string Record = "Record Tape";
|
||||
string NextTape = "Insert Next Tape";
|
||||
string PrevTape = "Insert Previous Tape";
|
||||
string NextBlock = "Next Tape Block";
|
||||
string PrevBlock = "Prev Tape Block";
|
||||
string TapeStatus = "Get Tape Status";
|
||||
|
||||
string NextDisk = "Insert Next Disk";
|
||||
string PrevDisk = "Insert Previous Disk";
|
||||
string EjectDisk = "Eject Current Disk";
|
||||
string DiskStatus = "Get Disk Status";
|
||||
|
||||
string HardResetStr = "Power";
|
||||
string SoftResetStr = "Reset";
|
||||
|
||||
bool pressed_Play = false;
|
||||
bool pressed_Stop = false;
|
||||
bool pressed_RTZ = false;
|
||||
bool pressed_NextTape = false;
|
||||
bool pressed_PrevTape = false;
|
||||
bool pressed_NextBlock = false;
|
||||
bool pressed_PrevBlock = false;
|
||||
bool pressed_TapeStatus = false;
|
||||
bool pressed_NextDisk = false;
|
||||
bool pressed_PrevDisk = false;
|
||||
bool pressed_EjectDisk = false;
|
||||
bool pressed_DiskStatus = false;
|
||||
bool pressed_HardReset = false;
|
||||
bool pressed_SoftReset = false;
|
||||
|
||||
/// <summary>
|
||||
/// Cycles through all the input callbacks
|
||||
/// This should be done once per frame
|
||||
/// </summary>
|
||||
public void PollInput()
|
||||
{
|
||||
CPC.InputCallbacks.Call();
|
||||
|
||||
lock (this)
|
||||
{
|
||||
// parse single keyboard matrix keys.
|
||||
// J1 and J2 are scanned as part of the keyboard
|
||||
for (var i = 0; i < KeyboardDevice.KeyboardMatrix.Length; i++)
|
||||
{
|
||||
string key = KeyboardDevice.KeyboardMatrix[i];
|
||||
bool prevState = KeyboardDevice.GetKeyStatus(key);
|
||||
bool currState = CPC._controller.IsPressed(key);
|
||||
|
||||
if (currState != prevState)
|
||||
KeyboardDevice.SetKeyStatus(key, currState);
|
||||
}
|
||||
|
||||
// non matrix keys (J2)
|
||||
foreach (string k in KeyboardDevice.NonMatrixKeys)
|
||||
{
|
||||
if (!k.StartsWith("P2"))
|
||||
continue;
|
||||
|
||||
bool currState = CPC._controller.IsPressed(k);
|
||||
|
||||
switch (k)
|
||||
{
|
||||
case "P2 Up":
|
||||
if (currState)
|
||||
KeyboardDevice.SetKeyStatus("Key 6", true);
|
||||
else if (!KeyboardDevice.GetKeyStatus("Key 6"))
|
||||
KeyboardDevice.SetKeyStatus("Key 6", false);
|
||||
break;
|
||||
case "P2 Down":
|
||||
if (currState)
|
||||
KeyboardDevice.SetKeyStatus("Key 5", true);
|
||||
else if (!KeyboardDevice.GetKeyStatus("Key 5"))
|
||||
KeyboardDevice.SetKeyStatus("Key 5", false);
|
||||
break;
|
||||
case "P2 Left":
|
||||
if (currState)
|
||||
KeyboardDevice.SetKeyStatus("Key R", true);
|
||||
else if (!KeyboardDevice.GetKeyStatus("Key R"))
|
||||
KeyboardDevice.SetKeyStatus("Key R", false);
|
||||
break;
|
||||
case "P2 Right":
|
||||
if (currState)
|
||||
KeyboardDevice.SetKeyStatus("Key T", true);
|
||||
else if (!KeyboardDevice.GetKeyStatus("Key T"))
|
||||
KeyboardDevice.SetKeyStatus("Key T", false);
|
||||
break;
|
||||
case "P2 Fire":
|
||||
if (currState)
|
||||
KeyboardDevice.SetKeyStatus("Key G", true);
|
||||
else if (!KeyboardDevice.GetKeyStatus("Key G"))
|
||||
KeyboardDevice.SetKeyStatus("Key G", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tape control
|
||||
if (CPC._controller.IsPressed(Play))
|
||||
{
|
||||
if (!pressed_Play)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(Play);
|
||||
TapeDevice.Play();
|
||||
pressed_Play = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_Play = false;
|
||||
|
||||
if (CPC._controller.IsPressed(Stop))
|
||||
{
|
||||
if (!pressed_Stop)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(Stop);
|
||||
TapeDevice.Stop();
|
||||
pressed_Stop = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_Stop = false;
|
||||
|
||||
if (CPC._controller.IsPressed(RTZ))
|
||||
{
|
||||
if (!pressed_RTZ)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(RTZ);
|
||||
TapeDevice.RTZ();
|
||||
pressed_RTZ = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_RTZ = false;
|
||||
|
||||
if (CPC._controller.IsPressed(Record))
|
||||
{
|
||||
|
||||
}
|
||||
if (CPC._controller.IsPressed(NextTape))
|
||||
{
|
||||
if (!pressed_NextTape)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(NextTape);
|
||||
TapeMediaIndex++;
|
||||
pressed_NextTape = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_NextTape = false;
|
||||
|
||||
if (CPC._controller.IsPressed(PrevTape))
|
||||
{
|
||||
if (!pressed_PrevTape)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(PrevTape);
|
||||
TapeMediaIndex--;
|
||||
pressed_PrevTape = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_PrevTape = false;
|
||||
|
||||
if (CPC._controller.IsPressed(NextBlock))
|
||||
{
|
||||
if (!pressed_NextBlock)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(NextBlock);
|
||||
TapeDevice.SkipBlock(true);
|
||||
pressed_NextBlock = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_NextBlock = false;
|
||||
|
||||
if (CPC._controller.IsPressed(PrevBlock))
|
||||
{
|
||||
if (!pressed_PrevBlock)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(PrevBlock);
|
||||
TapeDevice.SkipBlock(false);
|
||||
pressed_PrevBlock = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_PrevBlock = false;
|
||||
|
||||
if (CPC._controller.IsPressed(TapeStatus))
|
||||
{
|
||||
if (!pressed_TapeStatus)
|
||||
{
|
||||
//Spectrum.OSD_FireInputMessage(TapeStatus);
|
||||
CPC.OSD_ShowTapeStatus();
|
||||
pressed_TapeStatus = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_TapeStatus = false;
|
||||
|
||||
if (CPC._controller.IsPressed(HardResetStr))
|
||||
{
|
||||
if (!pressed_HardReset)
|
||||
{
|
||||
HardReset();
|
||||
pressed_HardReset = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_HardReset = false;
|
||||
|
||||
if (CPC._controller.IsPressed(SoftResetStr))
|
||||
{
|
||||
if (!pressed_SoftReset)
|
||||
{
|
||||
SoftReset();
|
||||
pressed_SoftReset = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_SoftReset = false;
|
||||
|
||||
// disk control
|
||||
if (CPC._controller.IsPressed(NextDisk))
|
||||
{
|
||||
if (!pressed_NextDisk)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(NextDisk);
|
||||
DiskMediaIndex++;
|
||||
pressed_NextDisk = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_NextDisk = false;
|
||||
|
||||
if (CPC._controller.IsPressed(PrevDisk))
|
||||
{
|
||||
if (!pressed_PrevDisk)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(PrevDisk);
|
||||
DiskMediaIndex--;
|
||||
pressed_PrevDisk = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_PrevDisk = false;
|
||||
|
||||
if (CPC._controller.IsPressed(EjectDisk))
|
||||
{
|
||||
if (!pressed_EjectDisk)
|
||||
{
|
||||
CPC.OSD_FireInputMessage(EjectDisk);
|
||||
//if (UPDDiskDevice != null)
|
||||
// UPDDiskDevice.FDD_EjectDisk();
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_EjectDisk = false;
|
||||
|
||||
if (CPC._controller.IsPressed(DiskStatus))
|
||||
{
|
||||
if (!pressed_DiskStatus)
|
||||
{
|
||||
//Spectrum.OSD_FireInputMessage(TapeStatus);
|
||||
CPC.OSD_ShowDiskStatus();
|
||||
pressed_DiskStatus = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
pressed_DiskStatus = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether input read has been requested
|
||||
/// This forms part of the IEmulator LagFrame implementation
|
||||
/// </summary>
|
||||
private bool inputRead;
|
||||
public bool InputRead
|
||||
{
|
||||
get { return inputRead; }
|
||||
set { inputRead = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Imported media *
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The tape or disk image(s) that are passed in from the main ZXSpectrum class
|
||||
/// </summary>
|
||||
protected List<byte[]> mediaImages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tape images
|
||||
/// </summary>
|
||||
public List<byte[]> tapeImages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disk images
|
||||
/// </summary>
|
||||
public List<byte[]> diskImages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently 'loaded' tape image
|
||||
/// </summary>
|
||||
protected int tapeMediaIndex;
|
||||
public int TapeMediaIndex
|
||||
{
|
||||
get { return tapeMediaIndex; }
|
||||
set
|
||||
{
|
||||
int tmp = value;
|
||||
int result = value;
|
||||
|
||||
if (tapeImages == null || tapeImages.Count() == 0)
|
||||
{
|
||||
// no tape images found
|
||||
return;
|
||||
}
|
||||
|
||||
if (value >= tapeImages.Count())
|
||||
{
|
||||
// media at this index does not exist - loop back to 0
|
||||
result = 0;
|
||||
}
|
||||
else if (value < 0)
|
||||
{
|
||||
// negative index not allowed - move to last item in the collection
|
||||
result = tapeImages.Count() - 1;
|
||||
}
|
||||
|
||||
// load the media into the tape device
|
||||
tapeMediaIndex = result;
|
||||
// fire osd message
|
||||
//Spectrum.OSD_TapeInserted();
|
||||
LoadTapeMedia();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently 'loaded' disk image
|
||||
/// </summary>
|
||||
protected int diskMediaIndex;
|
||||
public int DiskMediaIndex
|
||||
{
|
||||
get { return diskMediaIndex; }
|
||||
set
|
||||
{
|
||||
int tmp = value;
|
||||
int result = value;
|
||||
|
||||
if (diskImages == null || diskImages.Count() == 0)
|
||||
{
|
||||
// no tape images found
|
||||
return;
|
||||
}
|
||||
|
||||
if (value >= diskImages.Count())
|
||||
{
|
||||
// media at this index does not exist - loop back to 0
|
||||
result = 0;
|
||||
}
|
||||
else if (value < 0)
|
||||
{
|
||||
// negative index not allowed - move to last item in the collection
|
||||
result = diskImages.Count() - 1;
|
||||
}
|
||||
|
||||
// load the media into the disk device
|
||||
diskMediaIndex = result;
|
||||
|
||||
// fire osd message
|
||||
//Spectrum.OSD_DiskInserted();
|
||||
|
||||
LoadDiskMedia();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on first instantiation (and subsequent core reboots)
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
protected void InitializeMedia(List<byte[]> files)
|
||||
{
|
||||
mediaImages = files;
|
||||
LoadAllMedia();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load all media into the relevant structures
|
||||
/// </summary>
|
||||
protected void LoadAllMedia()
|
||||
{
|
||||
tapeImages = new List<byte[]>();
|
||||
diskImages = new List<byte[]>();
|
||||
|
||||
int cnt = 0;
|
||||
foreach (var m in mediaImages)
|
||||
{
|
||||
switch (IdentifyMedia(m))
|
||||
{
|
||||
case CPCMediaType.Tape:
|
||||
tapeImages.Add(m);
|
||||
CPC._tapeInfo.Add(CPC._gameInfo[cnt]);
|
||||
break;
|
||||
case CPCMediaType.Disk:
|
||||
diskImages.Add(m);
|
||||
CPC._diskInfo.Add(CPC._gameInfo[cnt]);
|
||||
break;
|
||||
}
|
||||
|
||||
cnt++;
|
||||
}
|
||||
|
||||
if (tapeImages.Count > 0)
|
||||
LoadTapeMedia();
|
||||
|
||||
if (diskImages.Count > 0)
|
||||
LoadDiskMedia();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a tape into the tape device based on tapeMediaIndex
|
||||
/// </summary>
|
||||
protected void LoadTapeMedia()
|
||||
{
|
||||
TapeDevice.LoadTape(tapeImages[tapeMediaIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a disk into the disk device based on diskMediaIndex
|
||||
/// </summary>
|
||||
protected void LoadDiskMedia()
|
||||
{
|
||||
/*
|
||||
if (this.GetType() != typeof(ZX128Plus3))
|
||||
{
|
||||
Spectrum.CoreComm.ShowMessage("You are trying to load one of more disk images.\n\n Please select ZX Spectrum +3 emulation immediately and reboot the core");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Spectrum.CoreComm.ShowMessage("You are attempting to load a disk into the +3 disk drive.\n\nThis DOES NOT currently work properly but IS under active development.");
|
||||
}
|
||||
|
||||
UPDDiskDevice.FDD_LoadDisk(diskImages[diskMediaIndex]);
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies and sorts the various media types
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private CPCMediaType IdentifyMedia(byte[] data)
|
||||
{
|
||||
// get first 16 bytes as a string
|
||||
string hdr = Encoding.ASCII.GetString(data.Take(16).ToArray());
|
||||
|
||||
// disk checking first
|
||||
if (hdr.ToUpper().Contains("EXTENDED CPC DSK") || hdr.ToUpper().Contains("MV - CPC"))
|
||||
{
|
||||
// amstrad .dsk disk file
|
||||
return CPCMediaType.Disk;
|
||||
}
|
||||
|
||||
// tape checking
|
||||
if (hdr.ToUpper().StartsWith("ZXTAPE!"))
|
||||
{
|
||||
// cdt tape file
|
||||
return CPCMediaType.Tape;
|
||||
}
|
||||
|
||||
|
||||
// not found
|
||||
return CPCMediaType.None;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CPCMediaType
|
||||
{
|
||||
None,
|
||||
Tape,
|
||||
Disk
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Memory *
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
{
|
||||
#region Memory Fields & Properties
|
||||
|
||||
/// <summary>
|
||||
/// ROM Banks
|
||||
/// </summary>
|
||||
public byte[] ROM0 = new byte[0x4000];
|
||||
public byte[] ROM1 = new byte[0x4000];
|
||||
|
||||
/// <summary>
|
||||
/// RAM Banks
|
||||
/// </summary>
|
||||
public byte[] RAM0 = new byte[0x4000]; // Bank 0
|
||||
public byte[] RAM1 = new byte[0x4000]; // Bank 1
|
||||
public byte[] RAM2 = new byte[0x4000]; // Bank 2
|
||||
public byte[] RAM3 = new byte[0x4000]; // Bank 3
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether Upper ROM is paged in
|
||||
/// </summary>
|
||||
public bool UpperROMPaged;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether Lower ROM is paged in
|
||||
/// </summary>
|
||||
public bool LowerROMPaged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory Related Methods
|
||||
|
||||
/// <summary>
|
||||
/// Simulates reading from the bus
|
||||
/// Paging should be handled here
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public abstract byte ReadBus(ushort addr);
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a value onto the data bus that should be valid as long as the interrupt is true
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public virtual byte PushBus()
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates writing to the bus
|
||||
/// Paging should be handled here
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <param name="value"></param>
|
||||
public abstract void WriteBus(ushort addr, byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte of data from a specified memory address
|
||||
/// (with memory contention if appropriate)
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public abstract byte ReadMemory(ushort addr);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data to a specified memory address
|
||||
/// (with memory contention if appropriate)
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <param name="value"></param>
|
||||
public abstract void WriteMemory(ushort addr, byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the ROM
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
public abstract void InitROM(RomData romData);
|
||||
|
||||
/// <summary>
|
||||
/// ULA reads the memory at the specified address
|
||||
/// (No memory contention)
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public virtual byte FetchScreenMemory(ushort addr)
|
||||
{
|
||||
var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000));
|
||||
//var value = ReadBus(addr);
|
||||
return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Port Access *
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a byte of data from a specified port address
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public abstract byte ReadPort(ushort port);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data to a specified port address
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="value"></param>
|
||||
public abstract void WritePort(ushort port, byte value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Main properties / fields / contruction*
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
{
|
||||
#region Devices
|
||||
|
||||
/// <summary>
|
||||
/// The calling ZXSpectrum class (piped in via constructor)
|
||||
/// </summary>
|
||||
public AmstradCPC CPC { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the instantiated Z80 cpu (piped in via constructor)
|
||||
/// </summary>
|
||||
public Z80A CPU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ROM and extended info
|
||||
/// </summary>
|
||||
public RomData RomData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Amstrad datacorder device
|
||||
/// </summary>
|
||||
public virtual DatacorderDevice TapeDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// beeper output for the tape
|
||||
/// </summary>
|
||||
public IBeeperDevice TapeBuzzer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Device representing the AY-3-8912 chip found in the CPC
|
||||
/// </summary>
|
||||
public IPSG AYDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The keyboard device
|
||||
/// Technically, this is controlled by the PSG, but has been abstracted due to the port over from ZXHawk
|
||||
/// </summary>
|
||||
public IKeyboard KeyboardDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Amstrad disk drive
|
||||
/// </summary>
|
||||
public virtual NECUPD765 UPDDiskDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Cathode Ray Tube Controller chip
|
||||
/// </summary>
|
||||
public CRCT_6845 CRCT { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Amstrad gate array
|
||||
/// </summary>
|
||||
public GateArrayBase GateArray { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The PPI contoller chip
|
||||
/// </summary>
|
||||
public PPIBase PPI { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of a standard frame in CPU cycles
|
||||
/// </summary>
|
||||
public int FrameLength;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Emulator State
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the frame has ended
|
||||
/// </summary>
|
||||
public bool FrameCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Overflow from the previous frame (in Z80 cycles)
|
||||
/// </summary>
|
||||
public int OverFlow;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of frames rendered
|
||||
/// </summary>
|
||||
public int FrameCount;
|
||||
|
||||
/// <summary>
|
||||
/// The current cycle (T-State) that we are at in the frame
|
||||
/// </summary>
|
||||
public long _frameCycles;
|
||||
|
||||
/// <summary>
|
||||
/// Stores where we are in the frame after each CPU cycle
|
||||
/// </summary>
|
||||
public long LastFrameStartCPUTick;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current frame cycle according to the CPU tick count
|
||||
/// </summary>
|
||||
public virtual long CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick;
|
||||
|
||||
/// <summary>
|
||||
/// Non-Deterministic bools
|
||||
/// </summary>
|
||||
public bool _render;
|
||||
public bool _renderSound;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Mask constants & misc
|
||||
/// </summary>
|
||||
protected const int BORDER_BIT = 0x07;
|
||||
protected const int EAR_BIT = 0x10;
|
||||
protected const int MIC_BIT = 0x08;
|
||||
protected const int TAPE_BIT = 0x40;
|
||||
protected const int AY_SAMPLE_RATE = 16;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Emulation Loop
|
||||
|
||||
/// <summary>
|
||||
/// Executes a single frame
|
||||
/// </summary>
|
||||
public virtual void ExecuteFrame(bool render, bool renderSound)
|
||||
{
|
||||
GateArray.FrameEnd = false;
|
||||
//ULADevice.ULACycleCounter = CurrentFrameCycle;
|
||||
|
||||
InputRead = false;
|
||||
_render = render;
|
||||
_renderSound = renderSound;
|
||||
|
||||
FrameCompleted = false;
|
||||
|
||||
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
|
||||
TapeDevice.StartFrame();
|
||||
|
||||
if (_renderSound)
|
||||
{
|
||||
if (AYDevice != null)
|
||||
AYDevice.StartFrame();
|
||||
}
|
||||
|
||||
PollInput();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// run the CPU Monitor cycle
|
||||
//CPUMon.ExecuteCycle();
|
||||
|
||||
GateArray.ClockCycle();
|
||||
|
||||
// cycle the tape device
|
||||
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
|
||||
TapeDevice.TapeCycle();
|
||||
|
||||
// has frame end been reached?
|
||||
if (GateArray.FrameEnd)
|
||||
break;
|
||||
}
|
||||
|
||||
OverFlow = (int)CurrentFrameCycle - GateArray.FrameLength;
|
||||
|
||||
// we have reached the end of a frame
|
||||
LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow;
|
||||
|
||||
if (AYDevice != null)
|
||||
AYDevice.EndFrame();
|
||||
|
||||
FrameCount++;
|
||||
|
||||
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
|
||||
TapeDevice.EndFrame();
|
||||
|
||||
FrameCompleted = true;
|
||||
|
||||
// is this a lag frame?
|
||||
CPC.IsLagFrame = !InputRead;
|
||||
|
||||
// FDC debug
|
||||
if (UPDDiskDevice != null && UPDDiskDevice.writeDebug)
|
||||
{
|
||||
// only write UPD log every second
|
||||
if (FrameCount % 10 == 0)
|
||||
{
|
||||
System.IO.File.AppendAllLines(UPDDiskDevice.outputfile, UPDDiskDevice.dLog);
|
||||
UPDDiskDevice.dLog = new System.Collections.Generic.List<string>();
|
||||
//System.IO.File.WriteAllText(UPDDiskDevice.outputfile, UPDDiskDevice.outputString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset Functions
|
||||
|
||||
/// <summary>
|
||||
/// Hard reset of the emulated machine
|
||||
/// </summary>
|
||||
public virtual void HardReset()
|
||||
{
|
||||
/*
|
||||
//ULADevice.ResetInterrupt();
|
||||
ROMPaged = 0;
|
||||
SpecialPagingMode = false;
|
||||
RAMPaged = 0;
|
||||
CPU.RegPC = 0;
|
||||
|
||||
Spectrum.SetCpuRegister("SP", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("IY", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("IX", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("AF", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("BC", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("DE", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("HL", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("SP", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow AF", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow BC", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow DE", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow HL", 0xFFFF);
|
||||
|
||||
CPU.Regs[CPU.I] = 0;
|
||||
CPU.Regs[CPU.R] = 0;
|
||||
|
||||
TapeDevice.Reset();
|
||||
if (AYDevice != null)
|
||||
AYDevice.Reset();
|
||||
|
||||
byte[][] rams = new byte[][]
|
||||
{
|
||||
RAM0,
|
||||
RAM1,
|
||||
RAM2,
|
||||
RAM3,
|
||||
RAM4,
|
||||
RAM5,
|
||||
RAM6,
|
||||
RAM7
|
||||
};
|
||||
|
||||
foreach (var r in rams)
|
||||
{
|
||||
for (int i = 0; i < r.Length; i++)
|
||||
{
|
||||
r[i] = 0x00;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft reset of the emulated machine
|
||||
/// </summary>
|
||||
public virtual void SoftReset()
|
||||
{
|
||||
/*
|
||||
//ULADevice.ResetInterrupt();
|
||||
ROMPaged = 0;
|
||||
SpecialPagingMode = false;
|
||||
RAMPaged = 0;
|
||||
CPU.RegPC = 0;
|
||||
|
||||
Spectrum.SetCpuRegister("SP", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("IY", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("IX", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("AF", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("BC", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("DE", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("HL", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("SP", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow AF", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow BC", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow DE", 0xFFFF);
|
||||
Spectrum.SetCpuRegister("Shadow HL", 0xFFFF);
|
||||
|
||||
CPU.Regs[CPU.I] = 0;
|
||||
CPU.Regs[CPU.R] = 0;
|
||||
|
||||
TapeDevice.Reset();
|
||||
if (AYDevice != null)
|
||||
AYDevice.Reset();
|
||||
|
||||
byte[][] rams = new byte[][]
|
||||
{
|
||||
RAM0,
|
||||
RAM1,
|
||||
RAM2,
|
||||
RAM3,
|
||||
RAM4,
|
||||
RAM5,
|
||||
RAM6,
|
||||
RAM7
|
||||
};
|
||||
|
||||
foreach (var r in rams)
|
||||
{
|
||||
for (int i = 0; i < r.Length; i++)
|
||||
{
|
||||
r[i] = 0x00;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IStatable
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("CPCMachine");
|
||||
ser.Sync("FrameCompleted", ref FrameCompleted);
|
||||
ser.Sync("OverFlow", ref OverFlow);
|
||||
ser.Sync("FrameCount", ref FrameCount);
|
||||
ser.Sync("_frameCycles", ref _frameCycles);
|
||||
ser.Sync("inputRead", ref inputRead);
|
||||
ser.Sync("LastFrameStartCPUTick", ref LastFrameStartCPUTick);
|
||||
ser.Sync("ROM0", ref ROM0, false);
|
||||
ser.Sync("ROM1", ref ROM1, false);
|
||||
ser.Sync("RAM0", ref RAM0, false);
|
||||
ser.Sync("RAM1", ref RAM1, false);
|
||||
ser.Sync("RAM2", ref RAM2, false);
|
||||
ser.Sync("RAM3", ref RAM3, false);
|
||||
|
||||
CRCT.SyncState(ser);
|
||||
|
||||
//KeyboardDevice.SyncState(ser);
|
||||
//BuzzerDevice.SyncState(ser);
|
||||
TapeBuzzer.SyncState(ser);
|
||||
//.SyncState(ser);
|
||||
//CPUMon.SyncState(ser);
|
||||
|
||||
if (AYDevice != null)
|
||||
{
|
||||
AYDevice.SyncState(ser);
|
||||
((AY38912)AYDevice as AY38912).PanningConfiguration = CPC.Settings.AYPanConfig;
|
||||
}
|
||||
|
||||
ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
|
||||
if (ser.IsReader)
|
||||
TapeMediaIndex = tapeMediaIndex;
|
||||
|
||||
TapeDevice.SyncState(ser);
|
||||
|
||||
ser.Sync("diskMediaIndex", ref diskMediaIndex);
|
||||
if (ser.IsReader)
|
||||
DiskMediaIndex = diskMediaIndex;
|
||||
|
||||
if (UPDDiskDevice != null)
|
||||
{
|
||||
UPDDiskDevice.SyncState(ser);
|
||||
}
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,543 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Amstrad Gate Array *
|
||||
/// https://web.archive.org/web/20170612081209/http://www.grimware.org/doku.php/documentations/devices/gatearray
|
||||
/// </summary>
|
||||
public abstract class GateArrayBase : IVideoProvider
|
||||
{
|
||||
public int Z80ClockSpeed = 4000000;
|
||||
public int FrameLength = 79872;
|
||||
|
||||
#region Devices
|
||||
|
||||
private CPCBase _machine;
|
||||
private Z80A CPU => _machine.CPU;
|
||||
private CRCT_6845 CRCT => _machine.CRCT;
|
||||
private IPSG PSG => _machine.AYDevice;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// CRTC Register constants
|
||||
/// </summary>
|
||||
public const int HOR_TOTAL = 0;
|
||||
public const int HOR_DISPLAYED = 1;
|
||||
public const int HOR_SYNC_POS = 2;
|
||||
public const int HOR_AND_VER_SYNC_WIDTHS = 3;
|
||||
public const int VER_TOTAL = 4;
|
||||
public const int VER_TOTAL_ADJUST = 5;
|
||||
public const int VER_DISPLAYED = 6;
|
||||
public const int VER_SYNC_POS = 7;
|
||||
public const int INTERLACE_SKEW = 8;
|
||||
public const int MAX_RASTER_ADDR = 9;
|
||||
public const int CUR_START_RASTER = 10;
|
||||
public const int CUR_END_RASTER = 11;
|
||||
public const int DISP_START_ADDR_H = 12;
|
||||
public const int DISP_START_ADDR_L = 13;
|
||||
public const int CUR_ADDR_H = 14;
|
||||
public const int CUR_ADDR_L = 15;
|
||||
public const int LPEN_ADDR_H = 16;
|
||||
public const int LPEN_ADDR_L = 17;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Palletes
|
||||
|
||||
/// <summary>
|
||||
/// The standard CPC Pallete (ordered by firmware #)
|
||||
/// http://www.cpcwiki.eu/index.php/CPC_Palette
|
||||
/// </summary>
|
||||
private static readonly int[] CPCFirmwarePalette =
|
||||
{
|
||||
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||||
Colors.ARGB(0x00, 0x00, 0x80), // Blue
|
||||
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||||
Colors.ARGB(0x80, 0x00, 0x00), // Red
|
||||
Colors.ARGB(0x80, 0x00, 0x80), // Magenta
|
||||
Colors.ARGB(0x80, 0x00, 0xFF), // Mauve
|
||||
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple
|
||||
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||||
Colors.ARGB(0x00, 0x80, 0x00), // Green
|
||||
Colors.ARGB(0x00, 0x80, 0x80), // Cyan
|
||||
Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue
|
||||
Colors.ARGB(0x80, 0x80, 0x00), // Yellow
|
||||
Colors.ARGB(0x80, 0x80, 0x80), // White
|
||||
Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue
|
||||
Colors.ARGB(0xFF, 0x80, 0x00), // Orange
|
||||
Colors.ARGB(0xFF, 0x80, 0x80), // Pink
|
||||
Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta
|
||||
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green
|
||||
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||||
Colors.ARGB(0x80, 0xFF, 0x00), // Lime
|
||||
Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green
|
||||
Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan
|
||||
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow
|
||||
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The standard CPC Pallete (ordered by hardware #)
|
||||
/// http://www.cpcwiki.eu/index.php/CPC_Palette
|
||||
/// </summary>
|
||||
private static readonly int[] CPCHardwarePalette =
|
||||
{
|
||||
Colors.ARGB(0x80, 0x80, 0x80), // White
|
||||
Colors.ARGB(0x80, 0x80, 0x80), // White (duplicate)
|
||||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green
|
||||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow
|
||||
Colors.ARGB(0x00, 0x00, 0x80), // Blue
|
||||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple
|
||||
Colors.ARGB(0x00, 0x80, 0x80), // Cyan
|
||||
Colors.ARGB(0xFF, 0x80, 0x80), // Pink
|
||||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple (duplicate)
|
||||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow (duplicate)
|
||||
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||||
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||||
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||||
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||||
Colors.ARGB(0xFF, 0x80, 0x00), // Orange
|
||||
Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta
|
||||
Colors.ARGB(0x00, 0x00, 0x80), // Blue (duplicate)
|
||||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green (duplicate)
|
||||
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||||
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||||
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||||
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||||
Colors.ARGB(0x00, 0x80, 0x00), // Green
|
||||
Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue
|
||||
Colors.ARGB(0x80, 0x00, 0x80), // Magenta
|
||||
Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green
|
||||
Colors.ARGB(0x80, 0xFF, 0x00), // Lime
|
||||
Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan
|
||||
Colors.ARGB(0x80, 0x00, 0x00), // Red
|
||||
Colors.ARGB(0x80, 0x00, 0xFF), // Mauve
|
||||
Colors.ARGB(0x80, 0x80, 0x00), // Yellow
|
||||
Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public GateArrayBase(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
PenColours = new int[17];
|
||||
SetupScreenSize();
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inits the pen lookup table
|
||||
/// </summary>
|
||||
public void SetupScreenMapping()
|
||||
{
|
||||
for (int m = 0; m < 4; m++)
|
||||
{
|
||||
Lookup[m] = new int[256 * 8];
|
||||
int pos = 0;
|
||||
|
||||
for (int b = 0; b < 256; b++)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case 1:
|
||||
int pc = (((b & 0x80) >> 7) | ((b & 0x80) >> 2));
|
||||
Lookup[m][pos++] = pc;
|
||||
Lookup[m][pos++] = pc;
|
||||
pc = (((b & 0x40) >> 6) | ((b & 0x04) >> 1));
|
||||
Lookup[m][pos++] = pc;
|
||||
Lookup[m][pos++] = pc;
|
||||
pc = (((b & 0x20) >> 5) | (b & 0x02));
|
||||
Lookup[m][pos++] = pc;
|
||||
Lookup[m][pos++] = pc;
|
||||
pc = (((b & 0x10) >> 4) | ((b & 0x01) << 1));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
bool pixel_on = ((b & (1 << i)) != 0);
|
||||
Lookup[m][pos++] = pixel_on ? 1 : 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 0:
|
||||
int pc2 = (b & 0xAA);
|
||||
pc2 = (
|
||||
((pc2 & 0x80) >> 7) |
|
||||
((pc2 & 0x08) >> 2) |
|
||||
((pc2 & 0x20) >> 3) |
|
||||
((pc2 & 0x02) << 2));
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
pc2 = (b & 0x55);
|
||||
pc2 = (
|
||||
((pc2 & 0x40) >> 6) |
|
||||
((pc2 & 0x04) >> 1) |
|
||||
((pc2 & 0x10) >> 2) |
|
||||
((pc2 & 0x01) << 3));
|
||||
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
Lookup[m][pos++] = pc2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private int[] PenColours;
|
||||
private int CurrentPen;
|
||||
private int ScreenMode;
|
||||
private int INTScanlineCnt;
|
||||
private int VSYNCDelyCnt;
|
||||
|
||||
private int[][] Lookup = new int[4][];
|
||||
|
||||
private bool DoModeUpdate;
|
||||
|
||||
private int LatchedMode;
|
||||
private int buffPos;
|
||||
|
||||
public bool FrameEnd;
|
||||
|
||||
public bool WaitLine;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clock Operations
|
||||
|
||||
/// <summary>
|
||||
/// The gatearray runs on a 16Mhz clock
|
||||
/// (for the purposes of emulation, we will use a 4Mhz clock)
|
||||
/// From this it generates:
|
||||
/// 1Mhz clock for the CRTC chip
|
||||
/// 1Mhz clock for the AY-3-8912 PSG
|
||||
/// 4Mhz clock for the Z80 CPU
|
||||
/// </summary>
|
||||
public void ClockCycle()
|
||||
{
|
||||
// 4-phase clock
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
// Phase 1
|
||||
case 1:
|
||||
CRCT.CycleClock();
|
||||
CPU.ExecuteOne();
|
||||
break;
|
||||
// Phase 2
|
||||
case 2:
|
||||
CPU.ExecuteOne();
|
||||
break;
|
||||
// Phase 3
|
||||
case 3:
|
||||
// video fetch
|
||||
break;
|
||||
// Phase 4
|
||||
case 4:
|
||||
// video fetch
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
/// <summary>
|
||||
/// Selects the pen
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void SetPen(BitArray bi)
|
||||
{
|
||||
if (bi[4])
|
||||
{
|
||||
// border select
|
||||
CurrentPen = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
// pen select
|
||||
byte[] b = new byte[1];
|
||||
bi.CopyTo(b, 0);
|
||||
CurrentPen = b[0] & 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects colour for the currently selected pen
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void SetPenColour(BitArray bi)
|
||||
{
|
||||
byte[] b = new byte[1];
|
||||
bi.CopyTo(b, 0);
|
||||
var colour = b[0] & 0x1f;
|
||||
PenColours[CurrentPen] = colour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the actual ARGB pen colour value
|
||||
/// </summary>
|
||||
/// <param name="idx"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetPenColour(int idx)
|
||||
{
|
||||
return CPCHardwarePalette[PenColours[idx]];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Screen mode and ROM config
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void SetReg2(BitArray bi)
|
||||
{
|
||||
byte[] b = new byte[1];
|
||||
bi.CopyTo(b, 0);
|
||||
|
||||
// screen mode
|
||||
var mode = b[0] & 0x03;
|
||||
ScreenMode = mode;
|
||||
|
||||
// ROM
|
||||
|
||||
// upper
|
||||
if ((b[0] & 0x08) != 0)
|
||||
{
|
||||
_machine.UpperROMPaged = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_machine.UpperROMPaged = true;
|
||||
}
|
||||
|
||||
// lower
|
||||
if ((b[0] & 0x04) != 0)
|
||||
{
|
||||
_machine.LowerROMPaged = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_machine.LowerROMPaged = true;
|
||||
}
|
||||
|
||||
// INT delay
|
||||
if ((b[0] & 0x10) != 0)
|
||||
{
|
||||
INTScanlineCnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only available on machines with a 64KB memory expansion
|
||||
/// Default assume we dont have this
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void SetRAM(BitArray bi)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void InterruptACK()
|
||||
{
|
||||
INTScanlineCnt &= 0x01f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
CurrentPen = 0;
|
||||
ScreenMode = 1;
|
||||
for (int i = 0; i < 17; i++)
|
||||
PenColours[i] = 0;
|
||||
INTScanlineCnt = 0;
|
||||
VSYNCDelyCnt = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPortIODevice
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadPort(ushort port, ref int result)
|
||||
{
|
||||
// gate array is OUT only
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool WritePort(ushort port, int result)
|
||||
{
|
||||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||||
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
|
||||
|
||||
// The gate array responds to port 0x7F
|
||||
bool accessed = !portBits[15];
|
||||
if (!accessed)
|
||||
return false;
|
||||
|
||||
// Bit 9 and 8 of the data byte define the function to access
|
||||
if (!dataBits[6] && !dataBits[7])
|
||||
{
|
||||
// select pen
|
||||
SetPen(dataBits);
|
||||
}
|
||||
|
||||
if (dataBits[6] && !dataBits[7])
|
||||
{
|
||||
// select colour for selected pen
|
||||
SetPenColour(dataBits);
|
||||
}
|
||||
|
||||
if (!dataBits[6] && dataBits[7])
|
||||
{
|
||||
// select screen mode, ROM configuration and interrupt control
|
||||
SetReg2(dataBits);
|
||||
}
|
||||
|
||||
if (dataBits[6] && dataBits[7])
|
||||
{
|
||||
// RAM memory management
|
||||
SetRAM(dataBits);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IVideoProvider
|
||||
|
||||
/// <summary>
|
||||
/// Video output buffer
|
||||
/// </summary>
|
||||
public int[] ScreenBuffer;
|
||||
|
||||
private int _virtualWidth;
|
||||
private int _virtualHeight;
|
||||
private int _bufferWidth;
|
||||
private int _bufferHeight;
|
||||
|
||||
public int BackgroundColor
|
||||
{
|
||||
get { return CPCHardwarePalette[16]; }
|
||||
}
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
get { return _virtualWidth; }
|
||||
set { _virtualWidth = value; }
|
||||
}
|
||||
|
||||
public int VirtualHeight
|
||||
{
|
||||
get { return _virtualHeight; }
|
||||
set { _virtualHeight = value; }
|
||||
}
|
||||
|
||||
public int BufferWidth
|
||||
{
|
||||
get { return _bufferWidth; }
|
||||
set { _bufferWidth = value; }
|
||||
}
|
||||
|
||||
public int BufferHeight
|
||||
{
|
||||
get { return _bufferHeight; }
|
||||
set { _bufferHeight = value; }
|
||||
}
|
||||
|
||||
public int VsyncNumerator
|
||||
{
|
||||
get { return Z80ClockSpeed * 50; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public int VsyncDenominator
|
||||
{
|
||||
get { return Z80ClockSpeed; }
|
||||
}
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
return ScreenBuffer;
|
||||
}
|
||||
|
||||
protected void SetupScreenSize()
|
||||
{
|
||||
/*
|
||||
* Rect Pixels: Mode 0: 160×200 pixels with 16 colors (4 bpp)
|
||||
Sqaure Pixels: Mode 1: 320×200 pixels with 4 colors (2 bpp)
|
||||
Rect Pixels: Mode 2: 640×200 pixels with 2 colors (1 bpp)
|
||||
Rect Pixels: Mode 3: 160×200 pixels with 4 colors (2bpp) (this is not an official mode, but rather a side-effect of the hardware)
|
||||
*
|
||||
* */
|
||||
|
||||
// define maximum screen buffer size
|
||||
// to fit all possible resolutions, 640x400 should do it
|
||||
// therefore a scanline will take two buffer rows
|
||||
// and buffer columns will be:
|
||||
// Mode 1: 2 pixels
|
||||
// Mode 2: 1 pixel
|
||||
// Mode 0: 4 pixels
|
||||
// Mode 3: 4 pixels
|
||||
|
||||
BufferWidth = 640;
|
||||
BufferHeight = 400;
|
||||
VirtualHeight = BufferHeight;
|
||||
VirtualWidth = BufferWidth;
|
||||
ScreenBuffer = new int[BufferWidth * BufferHeight];
|
||||
croppedBuffer = ScreenBuffer;
|
||||
}
|
||||
|
||||
protected int[] croppedBuffer;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The various CPC models CPCHawk emulates
|
||||
/// </summary>
|
||||
public enum MachineType
|
||||
{
|
||||
/// <summary>
|
||||
/// Original Amstrad CPC model with builtin datacorder
|
||||
/// </summary>
|
||||
CPC464,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,488 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Emulates the PPI (8255) chip
|
||||
/// http://www.cpcwiki.eu/imgs/d/df/PPI_M5L8255AP-5.pdf
|
||||
/// http://www.cpcwiki.eu/index.php/8255
|
||||
/// </summary>
|
||||
public class PPIBase : IPortIODevice
|
||||
{
|
||||
#region Devices
|
||||
|
||||
private CPCBase _machine;
|
||||
private CRCT_6845 CRTC => _machine.CRCT;
|
||||
private GateArrayBase GateArray => _machine.GateArray;
|
||||
private IPSG PSG => _machine.AYDevice;
|
||||
private DatacorderDevice Tape => _machine.TapeDevice;
|
||||
private IKeyboard Keyboard => _machine.KeyboardDevice;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private State PortA = new State("PortA");
|
||||
private State PortB = new State("PortB");
|
||||
private State PortCU = new State("PortCU");
|
||||
private State PortCL = new State("PortCL");
|
||||
|
||||
private class State
|
||||
{
|
||||
public string Ident;
|
||||
public int Data;
|
||||
public bool Input;
|
||||
public int OpMode;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
OpMode = 0;
|
||||
Input = true;
|
||||
Data = 255;
|
||||
}
|
||||
|
||||
public State(string ident)
|
||||
{
|
||||
Ident = ident;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("PPI_" + Ident);
|
||||
ser.Sync("Data", ref Data);
|
||||
ser.Sync("Input", ref Input);
|
||||
ser.Sync("OpMode", ref OpMode);
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public PPIBase(CPCBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
Reset();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PortA.Reset();
|
||||
PortB.Reset();
|
||||
PortCL.Reset();
|
||||
PortCU.Reset();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PORT A
|
||||
/*
|
||||
I/O Mode 0,
|
||||
For writing data to PSG all bits must be set to output,
|
||||
for reading data from PSG all bits must be set to input (thereafter, output direction should be restored, for compatibility with the BIOS).
|
||||
|
||||
Bit Description Usage
|
||||
7-0 PSG.DATA PSG Databus (Sound/Keyboard/Joystick)
|
||||
*/
|
||||
/// <summary>
|
||||
/// Reads from Port A
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int INPortA()
|
||||
{
|
||||
if (PortA.Input)
|
||||
{
|
||||
// read from AY
|
||||
return PSG.PortRead();
|
||||
}
|
||||
else
|
||||
{
|
||||
// return stored port data
|
||||
return PortA.Data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to Port A
|
||||
/// </summary>
|
||||
private void OUTPortA(int data)
|
||||
{
|
||||
PortA.Data = data;
|
||||
|
||||
if (!PortA.Input)
|
||||
{
|
||||
// write to AY
|
||||
PSG.PortWrite(data);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Port B
|
||||
/*
|
||||
I/O Mode 0,
|
||||
Input
|
||||
|
||||
Bit Description Usage in CPC Usage in KC Compact
|
||||
7 CAS.IN Cassette data input Same as on CPC
|
||||
6 PRN.BUSY Parallel/Printer port ready signal, "1" = not ready, "0" = Ready Same as on CPC
|
||||
5 /EXP Expansion Port /EXP pin Same as on CPC
|
||||
4 LK4 Screen Refresh Rate ("1"=50Hz, "0"=60Hz) Set to "1"=50Hz (but ignored by the KC BIOS, which always uses 50Hz even if LK4 is changed)
|
||||
3 LK3 3bit Distributor ID. Usually set to 4=Awa, 5=Schneider, or 7=Amstrad, see LK-selectable Brand Names for details. Purpose unknown (set to "1")
|
||||
2 LK2 Purpose unknown (set to "0")
|
||||
1 LK1 Expansion Port /TEST pin
|
||||
0 CRTC VSYNC Vertical Sync ("1"=VSYNC active, "0"=VSYNC inactive) Same as on CPC
|
||||
|
||||
LK1-4 are links on the mainboard ("0" bits are wired to GND). On CPC464,CPC664,CPC6128 and GX4000 they are labeled LK1-LK4, on the CPC464+ and CPC6128+ they are labeled LK101-LK103
|
||||
(and LK104, presumably?).
|
||||
Bit5 (/EXP) can be used by a expansion device to report its presence. "1" = device connected, "0" = device not connected.
|
||||
This is not always used by all expansion devices. is it used by any expansions? [in the DDI-1 disc interface, /EXP connects to the ROM bank selection, bank 0 or bank 7]
|
||||
If port B is programmed as an output, you can make a fake vsync visible to the Gate-Array by writing 1 to bit 0. You can then turn it off by writing 0 to bit 0.
|
||||
It is fake in the sense that it is not generated by the CRTC as it normally is. This fake vsync doesn't work on all CPCs. It is not known if it is dependent on CRTC or 8255 or both.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Reads from Port B
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int INPortB()
|
||||
{
|
||||
if (PortB.Input)
|
||||
{
|
||||
// start with every bit reset
|
||||
BitArray rBits = new BitArray(8);
|
||||
|
||||
// Bit0 - Vertical Sync ("1"=VSYNC active, "0"=VSYNC inactive)
|
||||
if (CRTC.isVSYNC)
|
||||
rBits[0] = true;
|
||||
|
||||
// Bits1-3 - Distributor ID. Usually set to 4=Awa, 5=Schneider, or 7=Amstrad
|
||||
// force AMstrad
|
||||
rBits[1] = true;
|
||||
rBits[2] = true;
|
||||
rBits[3] = true;
|
||||
|
||||
// Bit4 - Screen Refresh Rate ("1"=50Hz, "0"=60Hz)
|
||||
rBits[4] = true;
|
||||
|
||||
// Bit5 - Expansion Port /EXP pin
|
||||
rBits[5] = false;
|
||||
|
||||
// Bit6 - Parallel/Printer port ready signal, "1" = not ready, "0" = Ready
|
||||
rBits[6] = true;
|
||||
|
||||
// Bit7 - Cassette data input
|
||||
rBits[7] = Tape.GetEarBit(_machine.CPU.TotalExecutedCycles);
|
||||
|
||||
// return the byte
|
||||
byte[] bytes = new byte[1];
|
||||
rBits.CopyTo(bytes, 0);
|
||||
return bytes[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return PortB.Data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to Port B
|
||||
/// </summary>
|
||||
private void OUTPortB(int data)
|
||||
{
|
||||
// just store the value
|
||||
PortB.Data = data;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Port C
|
||||
/*
|
||||
upper: I/O Mode 0, lower: I/O mode 0,
|
||||
upper: output, lower: output
|
||||
|
||||
Bit Description Usage
|
||||
7 PSG BDIR PSG function selection
|
||||
6 PSG BC1
|
||||
5 Cassette Write data Cassette Out (sometimes also used as Printer Bit7, see 8bit Printer Ports)
|
||||
4 Cassette Motor Control set bit to "1" for motor on, or "0" for motor off
|
||||
0-3 Keyboard line Select keyboard line to be scanned (0-15)
|
||||
|
||||
PSG function selection:
|
||||
|
||||
Bit 7 Bit 6 Function
|
||||
0 0 Inactive
|
||||
0 1 Read from selected PSG register
|
||||
1 0 Write to selected PSG register
|
||||
1 1 Select PSG register
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Reads from Port C
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int INPortC()
|
||||
{
|
||||
var val = PortCU.Data;
|
||||
|
||||
if (PortCU.Input)
|
||||
val |= 0xf0;
|
||||
|
||||
if (PortCL.Input)
|
||||
val |= 0x0f;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to Port C
|
||||
/// </summary>
|
||||
private void OUTPortC(int data)
|
||||
{
|
||||
PortCL.Data = data;
|
||||
PortCU.Data = data;
|
||||
|
||||
if (!PortCU.Input)
|
||||
{
|
||||
// ay register set and write
|
||||
PSG.SelectedRegister = data;
|
||||
PSG.PortWrite(data);
|
||||
|
||||
// cassette motor control
|
||||
byte val = (byte)data;
|
||||
var motor = val.Bit(4);
|
||||
}
|
||||
|
||||
if (!PortCL.Input)
|
||||
{
|
||||
// which keyboard row to scan
|
||||
Keyboard.CurrentLine = PortCL.Data & 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PPI Control
|
||||
/*
|
||||
This register has two different functions depending on bit7 of the data written to this register.
|
||||
|
||||
PPI Control with Bit7=1
|
||||
|
||||
If Bit 7 is "1" then the other bits will initialize Port A-B as Input or Output:
|
||||
|
||||
Bit 0 IO-Cl Direction for Port C, lower bits (always 0=Output in CPC)
|
||||
Bit 1 IO-B Direction for Port B (always 1=Input in CPC)
|
||||
Bit 2 MS0 Mode for Port B and Port Cl (always zero in CPC)
|
||||
Bit 3 IO-Ch Direction for Port C, upper bits (always 0=Output in CPC)
|
||||
Bit 4 IO-A Direction for Port A (0=Output, 1=Input)
|
||||
Bit 5,6 MS0,MS1 Mode for Port A and Port Ch (always zero in CPC)
|
||||
Bit 7 SF Must be "1" to setup the above bits
|
||||
|
||||
CAUTION: Writing to PIO Control Register (with Bit7 set), automatically resets PIO Ports A,B,C to 00h each!
|
||||
In the CPC only Bit 4 is of interest, all other bits are always having the same value. In order to write to the PSG sound registers, a value of 82h must
|
||||
be written to this register. In order to read from the keyboard (through PSG register 0Eh), a value of 92h must be written to this register.
|
||||
|
||||
PPI Control with Bit7=0
|
||||
|
||||
Otherwise, if Bit 7 is "0" then the register is used to set or clear a single bit in Port C:
|
||||
|
||||
Bit 0 B New value for the specified bit (0=Clear, 1=Set)
|
||||
Bit 1-3 N0,N1,N2 Specifies the number of a bit (0-7) in Port C
|
||||
Bit 4-6 - Not Used
|
||||
Bit 7 SF Must be "0" in this case
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Deals with bytes written to the control
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
private void ControlHandler(int data)
|
||||
{
|
||||
byte val = (byte)data;
|
||||
|
||||
// Bit7 = 1
|
||||
if (val.Bit(7))
|
||||
{
|
||||
// Bit0 - Direction for Port C, lower bits
|
||||
PortCL.Input = val.Bit(0);
|
||||
|
||||
// Bit1 - Direction for Port B
|
||||
PortB.Input = val.Bit(1);
|
||||
|
||||
// Bit2 - Mode for Port B and Port Cl (CPC always 0)
|
||||
PortB.OpMode = 0;
|
||||
PortCL.OpMode = 0;
|
||||
|
||||
// Bit3 - Direction for Port C, upper bits
|
||||
PortCU.Input = val.Bit(3);
|
||||
|
||||
// Bit4 - Direction for Port A
|
||||
PortA.Input = val.Bit(4);
|
||||
|
||||
// Bits 5,6 - Mode for Port A and Port Ch (CPC always 0)
|
||||
PortA.OpMode = 0;
|
||||
PortCU.OpMode = 0;
|
||||
|
||||
// reset ports
|
||||
PortA.Data = 0x00;
|
||||
PortB.Data = 0x00;
|
||||
PortCL.Data = 0x00;
|
||||
PortCU.Data = 0x00;
|
||||
}
|
||||
// Bit7 = 0
|
||||
else
|
||||
{
|
||||
// Bit0 - New value for the specified bit (0=Clear, 1=Set)
|
||||
var newBit = val.Bit(0);
|
||||
|
||||
// Bits 1-3 - Specifies the number of a bit (0-7) in Port C
|
||||
var bit = (data >> 1) & 7;
|
||||
|
||||
if (newBit)
|
||||
{
|
||||
// set the bit
|
||||
PortCL.Data |= ~(1 << bit);
|
||||
PortCU.Data |= ~(1 << bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset the bit
|
||||
PortCL.Data &= ~(1 << bit);
|
||||
PortCU.Data &= ~(1 << bit);
|
||||
}
|
||||
|
||||
if (!PortCL.Input)
|
||||
{
|
||||
// keyboard set row
|
||||
}
|
||||
|
||||
if (!PortCU.Input)
|
||||
{
|
||||
// ay register set and write
|
||||
PSG.SelectedRegister = val;
|
||||
PSG.PortWrite(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPortIODevice
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadPort(ushort port, ref int result)
|
||||
{
|
||||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||||
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
|
||||
|
||||
// The 8255 responds to bit 11 reset
|
||||
bool accessed = !portBits[11];
|
||||
if (!accessed)
|
||||
return false;
|
||||
|
||||
if (!portBits[8] && !portBits[9])
|
||||
{
|
||||
// Port A Data
|
||||
// PSG (Sound/Keyboard/Joystick)
|
||||
result = INPortA();
|
||||
}
|
||||
|
||||
if (portBits[8] && !portBits[9])
|
||||
{
|
||||
// Port B Data
|
||||
// Vsync/Jumpers/PrinterBusy/CasIn/Exp
|
||||
result = INPortB();
|
||||
}
|
||||
|
||||
if (!portBits[8] && portBits[9])
|
||||
{
|
||||
// Port C Data
|
||||
// KeybRow/CasOut/PSG
|
||||
result = INPortC();
|
||||
}
|
||||
|
||||
if (portBits[8] && portBits[9])
|
||||
{
|
||||
// Control
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public bool WritePort(ushort port, int result)
|
||||
{
|
||||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||||
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
|
||||
|
||||
// The 8255 responds to bit 11 reset
|
||||
bool accessed = !portBits[11];
|
||||
if (!accessed)
|
||||
return false;
|
||||
|
||||
if (!portBits[8] && !portBits[9])
|
||||
{
|
||||
// Port A Data
|
||||
// PSG (Sound/Keyboard/Joystick)
|
||||
OUTPortA(result);
|
||||
}
|
||||
|
||||
if (portBits[8] && !portBits[9])
|
||||
{
|
||||
// Port B Data
|
||||
// Vsync/Jumpers/PrinterBusy/CasIn/Exp
|
||||
OUTPortB(result);
|
||||
}
|
||||
|
||||
if (!portBits[8] && portBits[9])
|
||||
{
|
||||
// Port C Data
|
||||
// KeybRow/CasOut/PSG
|
||||
OUTPortC(result);
|
||||
}
|
||||
|
||||
if (portBits[8] && portBits[9])
|
||||
{
|
||||
// Control
|
||||
ControlHandler(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("PPI");
|
||||
PortA.SyncState(ser);
|
||||
PortB.SyncState(ser);
|
||||
PortCU.SyncState(ser);
|
||||
PortCL.SyncState(ser);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using System.Text;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical object representing a standard +3 disk image
|
||||
/// </summary>
|
||||
public class CPCExtendedFloppyDisk : FloppyDisk
|
||||
{
|
||||
/// <summary>
|
||||
/// The format type
|
||||
/// </summary>
|
||||
public override DiskType DiskFormatType => DiskType.CPCExtended;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse incoming disk data
|
||||
/// </summary>
|
||||
/// <param name="diskData"></param>
|
||||
/// <returns>
|
||||
/// TRUE: disk parsed
|
||||
/// FALSE: unable to parse disk
|
||||
/// </returns>
|
||||
public override bool ParseDisk(byte[] data)
|
||||
{
|
||||
// look for standard magic string
|
||||
string ident = Encoding.ASCII.GetString(data, 0, 16);
|
||||
|
||||
if (!ident.ToUpper().Contains("EXTENDED CPC DSK"))
|
||||
{
|
||||
// incorrect format
|
||||
return false;
|
||||
}
|
||||
|
||||
// read the disk information block
|
||||
DiskHeader.DiskIdent = ident;
|
||||
DiskHeader.DiskCreatorString = Encoding.ASCII.GetString(data, 0x22, 14);
|
||||
DiskHeader.NumberOfTracks = data[0x30];
|
||||
DiskHeader.NumberOfSides = data[0x31];
|
||||
DiskHeader.TrackSizes = new int[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
|
||||
DiskTracks = new Track[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
|
||||
DiskData = data;
|
||||
int pos = 0x34;
|
||||
|
||||
if (DiskHeader.NumberOfSides > 1)
|
||||
{
|
||||
StringBuilder sbm = new StringBuilder();
|
||||
sbm.AppendLine();
|
||||
sbm.AppendLine();
|
||||
sbm.AppendLine("The detected disk image contains multiple sides.");
|
||||
sbm.AppendLine("This is NOT currently supported in CPCHawk.");
|
||||
sbm.AppendLine("Please find an alternate image/dump where each side has been saved as a separate *.dsk image (and use the mutli-disk bundler tool to load into Bizhawk).");
|
||||
throw new System.NotImplementedException(sbm.ToString());
|
||||
}
|
||||
|
||||
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
|
||||
{
|
||||
DiskHeader.TrackSizes[i] = data[pos++] * 256;
|
||||
}
|
||||
|
||||
// move to first track information block
|
||||
pos = 0x100;
|
||||
|
||||
// parse each track
|
||||
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
|
||||
{
|
||||
// check for unformatted track
|
||||
if (DiskHeader.TrackSizes[i] == 0)
|
||||
{
|
||||
DiskTracks[i] = new Track();
|
||||
DiskTracks[i].Sectors = new Sector[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
int p = pos;
|
||||
DiskTracks[i] = new Track();
|
||||
|
||||
// track info block
|
||||
DiskTracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12);
|
||||
p += 16;
|
||||
DiskTracks[i].TrackNumber = data[p++];
|
||||
DiskTracks[i].SideNumber = data[p++];
|
||||
DiskTracks[i].DataRate = data[p++];
|
||||
DiskTracks[i].RecordingMode = data[p++];
|
||||
DiskTracks[i].SectorSize = data[p++];
|
||||
DiskTracks[i].NumberOfSectors = data[p++];
|
||||
DiskTracks[i].GAP3Length = data[p++];
|
||||
DiskTracks[i].FillerByte = data[p++];
|
||||
|
||||
int dpos = pos + 0x100;
|
||||
|
||||
// sector info list
|
||||
DiskTracks[i].Sectors = new Sector[DiskTracks[i].NumberOfSectors];
|
||||
for (int s = 0; s < DiskTracks[i].NumberOfSectors; s++)
|
||||
{
|
||||
DiskTracks[i].Sectors[s] = new Sector();
|
||||
|
||||
DiskTracks[i].Sectors[s].TrackNumber = data[p++];
|
||||
DiskTracks[i].Sectors[s].SideNumber = data[p++];
|
||||
DiskTracks[i].Sectors[s].SectorID = data[p++];
|
||||
DiskTracks[i].Sectors[s].SectorSize = data[p++];
|
||||
DiskTracks[i].Sectors[s].Status1 = data[p++];
|
||||
DiskTracks[i].Sectors[s].Status2 = data[p++];
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = MediaConverter.GetWordValue(data, p);
|
||||
p += 2;
|
||||
|
||||
// sector data - begins at 0x100 offset from the start of the track info block (in this case dpos)
|
||||
DiskTracks[i].Sectors[s].SectorData = new byte[DiskTracks[i].Sectors[s].ActualDataByteLength];
|
||||
|
||||
// copy the data
|
||||
for (int b = 0; b < DiskTracks[i].Sectors[s].ActualDataByteLength; b++)
|
||||
{
|
||||
DiskTracks[i].Sectors[s].SectorData[b] = data[dpos + b];
|
||||
}
|
||||
|
||||
// check for multiple weak/random sectors stored
|
||||
if (DiskTracks[i].Sectors[s].SectorSize <= 7)
|
||||
{
|
||||
// sectorsize n=8 is equivilent to n=0 - FDC will use DTL for length
|
||||
int specifiedSize = 0x80 << DiskTracks[i].Sectors[s].SectorSize;
|
||||
|
||||
if (specifiedSize < DiskTracks[i].Sectors[s].ActualDataByteLength)
|
||||
{
|
||||
// more data stored than sectorsize defines
|
||||
// check for multiple weak/random copies
|
||||
if (DiskTracks[i].Sectors[s].ActualDataByteLength % specifiedSize != 0)
|
||||
{
|
||||
DiskTracks[i].Sectors[s].ContainsMultipleWeakSectors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move dpos to the next sector data postion
|
||||
dpos += DiskTracks[i].Sectors[s].ActualDataByteLength;
|
||||
}
|
||||
|
||||
// move to the next track info block
|
||||
pos += DiskHeader.TrackSizes[i];
|
||||
}
|
||||
|
||||
// run protection scheme detector
|
||||
ParseProtection();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State serlialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public override void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Plus3FloppyDisk");
|
||||
|
||||
ser.Sync("CylinderCount", ref CylinderCount);
|
||||
ser.Sync("SideCount", ref SideCount);
|
||||
ser.Sync("BytesPerTrack", ref BytesPerTrack);
|
||||
ser.Sync("WriteProtected", ref WriteProtected);
|
||||
ser.SyncEnum("Protection", ref Protection);
|
||||
|
||||
ser.Sync("DirtyData", ref DirtyData);
|
||||
if (DirtyData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// sync deterministic track and sector counters
|
||||
ser.Sync(" _randomCounter", ref _randomCounter);
|
||||
RandomCounter = _randomCounter;
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
using System.Text;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical object representing a standard +3 disk image
|
||||
/// </summary>
|
||||
public class CPCFloppyDisk : FloppyDisk
|
||||
{
|
||||
/// <summary>
|
||||
/// The format type
|
||||
/// </summary>
|
||||
public override DiskType DiskFormatType => DiskType.CPC;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse incoming disk data
|
||||
/// </summary>
|
||||
/// <param name="diskData"></param>
|
||||
/// <returns>
|
||||
/// TRUE: disk parsed
|
||||
/// FALSE: unable to parse disk
|
||||
/// </returns>
|
||||
public override bool ParseDisk(byte[] data)
|
||||
{
|
||||
// look for standard magic string
|
||||
string ident = Encoding.ASCII.GetString(data, 0, 16);
|
||||
|
||||
if (!ident.ToUpper().Contains("MV - CPC"))
|
||||
{
|
||||
// incorrect format
|
||||
return false;
|
||||
}
|
||||
|
||||
// read the disk information block
|
||||
DiskHeader.DiskIdent = ident;
|
||||
DiskHeader.DiskCreatorString = Encoding.ASCII.GetString(data, 0x22, 14);
|
||||
DiskHeader.NumberOfTracks = data[0x30];
|
||||
DiskHeader.NumberOfSides = data[0x31];
|
||||
DiskHeader.TrackSizes = new int[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
|
||||
DiskTracks = new Track[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
|
||||
DiskData = data;
|
||||
int pos = 0x32;
|
||||
|
||||
if (DiskHeader.NumberOfSides > 1)
|
||||
{
|
||||
StringBuilder sbm = new StringBuilder();
|
||||
sbm.AppendLine();
|
||||
sbm.AppendLine();
|
||||
sbm.AppendLine("The detected disk image contains multiple sides.");
|
||||
sbm.AppendLine("This is NOT currently supported in CPCHawk.");
|
||||
sbm.AppendLine("Please find an alternate image/dump where each side has been saved as a separate *.dsk image (and use the mutli-disk bundler tool to load into Bizhawk).");
|
||||
throw new System.NotImplementedException(sbm.ToString());
|
||||
}
|
||||
|
||||
// standard CPC format all track sizes are the same in the image
|
||||
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
|
||||
{
|
||||
DiskHeader.TrackSizes[i] = MediaConverter.GetWordValue(data, pos);
|
||||
}
|
||||
|
||||
// move to first track information block
|
||||
pos = 0x100;
|
||||
|
||||
// parse each track
|
||||
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
|
||||
{
|
||||
// check for unformatted track
|
||||
if (DiskHeader.TrackSizes[i] == 0)
|
||||
{
|
||||
DiskTracks[i] = new Track();
|
||||
DiskTracks[i].Sectors = new Sector[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
int p = pos;
|
||||
DiskTracks[i] = new Track();
|
||||
|
||||
// track info block
|
||||
DiskTracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12);
|
||||
p += 16;
|
||||
DiskTracks[i].TrackNumber = data[p++];
|
||||
DiskTracks[i].SideNumber = data[p++];
|
||||
p += 2;
|
||||
DiskTracks[i].SectorSize = data[p++];
|
||||
DiskTracks[i].NumberOfSectors = data[p++];
|
||||
DiskTracks[i].GAP3Length = data[p++];
|
||||
DiskTracks[i].FillerByte = data[p++];
|
||||
|
||||
int dpos = pos + 0x100;
|
||||
|
||||
// sector info list
|
||||
DiskTracks[i].Sectors = new Sector[DiskTracks[i].NumberOfSectors];
|
||||
for (int s = 0; s < DiskTracks[i].NumberOfSectors; s++)
|
||||
{
|
||||
DiskTracks[i].Sectors[s] = new Sector();
|
||||
|
||||
DiskTracks[i].Sectors[s].TrackNumber = data[p++];
|
||||
DiskTracks[i].Sectors[s].SideNumber = data[p++];
|
||||
DiskTracks[i].Sectors[s].SectorID = data[p++];
|
||||
DiskTracks[i].Sectors[s].SectorSize = data[p++];
|
||||
DiskTracks[i].Sectors[s].Status1 = data[p++];
|
||||
DiskTracks[i].Sectors[s].Status2 = data[p++];
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = MediaConverter.GetWordValue(data, p);
|
||||
p += 2;
|
||||
|
||||
// actualdatabytelength value is calculated now
|
||||
if (DiskTracks[i].Sectors[s].SectorSize == 0)
|
||||
{
|
||||
// no sectorsize specified - DTL will be used at runtime
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = DiskHeader.TrackSizes[i];
|
||||
}
|
||||
else if (DiskTracks[i].Sectors[s].SectorSize > 6)
|
||||
{
|
||||
// invalid - wrap around to 0
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = DiskHeader.TrackSizes[i];
|
||||
}
|
||||
else if (DiskTracks[i].Sectors[s].SectorSize == 6)
|
||||
{
|
||||
// only 0x1800 bytes are stored
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = 0x1800;
|
||||
}
|
||||
else
|
||||
{
|
||||
// valid sector size for this format
|
||||
DiskTracks[i].Sectors[s].ActualDataByteLength = 0x80 << DiskTracks[i].Sectors[s].SectorSize;
|
||||
}
|
||||
|
||||
// sector data - begins at 0x100 offset from the start of the track info block (in this case dpos)
|
||||
DiskTracks[i].Sectors[s].SectorData = new byte[DiskTracks[i].Sectors[s].ActualDataByteLength];
|
||||
|
||||
// copy the data
|
||||
for (int b = 0; b < DiskTracks[i].Sectors[s].ActualDataByteLength; b++)
|
||||
{
|
||||
DiskTracks[i].Sectors[s].SectorData[b] = data[dpos + b];
|
||||
}
|
||||
|
||||
// move dpos to the next sector data postion
|
||||
dpos += DiskTracks[i].Sectors[s].ActualDataByteLength;
|
||||
}
|
||||
|
||||
// move to the next track info block
|
||||
pos += DiskHeader.TrackSizes[i];
|
||||
}
|
||||
|
||||
// run protection scheme detector
|
||||
ParseProtection();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State serlialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public override void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Plus3FloppyDisk");
|
||||
|
||||
ser.Sync("CylinderCount", ref CylinderCount);
|
||||
ser.Sync("SideCount", ref SideCount);
|
||||
ser.Sync("BytesPerTrack", ref BytesPerTrack);
|
||||
ser.Sync("WriteProtected", ref WriteProtected);
|
||||
ser.SyncEnum("Protection", ref Protection);
|
||||
|
||||
ser.Sync("DirtyData", ref DirtyData);
|
||||
if (DirtyData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The different disk formats ZXHawk currently supports
|
||||
/// </summary>
|
||||
public enum DiskType
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard CPCEMU disk format (used in the built-in +3 disk drive)
|
||||
/// </summary>
|
||||
CPC,
|
||||
|
||||
/// <summary>
|
||||
/// Extended CPCEMU disk format (used in the built-in +3 disk drive)
|
||||
/// </summary>
|
||||
CPCExtended
|
||||
}
|
||||
}
|
|
@ -0,0 +1,750 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// This abstract class defines a logical floppy disk
|
||||
/// </summary>
|
||||
public abstract class FloppyDisk
|
||||
{
|
||||
/// <summary>
|
||||
/// The disk format type
|
||||
/// </summary>
|
||||
public abstract DiskType DiskFormatType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disk information header
|
||||
/// </summary>
|
||||
public Header DiskHeader = new Header();
|
||||
|
||||
/// <summary>
|
||||
/// Track array
|
||||
/// </summary>
|
||||
public Track[] DiskTracks = null;
|
||||
|
||||
/// <summary>
|
||||
/// No. of tracks per side
|
||||
/// </summary>
|
||||
public int CylinderCount;
|
||||
|
||||
/// <summary>
|
||||
/// The number of physical sides
|
||||
/// </summary>
|
||||
public int SideCount;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes per track
|
||||
/// </summary>
|
||||
public int BytesPerTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The write-protect tab on the disk
|
||||
/// </summary>
|
||||
public bool WriteProtected;
|
||||
|
||||
/// <summary>
|
||||
/// The detected protection scheme (if any)
|
||||
/// </summary>
|
||||
public ProtectionType Protection;
|
||||
|
||||
/// <summary>
|
||||
/// The actual disk image data
|
||||
/// </summary>
|
||||
public byte[] DiskData;
|
||||
|
||||
/// <summary>
|
||||
/// If TRUE then data on the disk has changed (been written to)
|
||||
/// This will be used to determine whether the disk data needs to be included
|
||||
/// in any SyncState operations
|
||||
/// </summary>
|
||||
protected bool DirtyData = false;
|
||||
|
||||
/// <summary>
|
||||
/// Used to deterministically choose a 'random' sector when dealing with weak reads
|
||||
/// </summary>
|
||||
public int RandomCounter
|
||||
{
|
||||
get { return _randomCounter; }
|
||||
set
|
||||
{
|
||||
_randomCounter = value;
|
||||
|
||||
foreach (var trk in DiskTracks)
|
||||
{
|
||||
foreach (var sec in trk.Sectors)
|
||||
{
|
||||
sec.RandSecCounter = _randomCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected int _randomCounter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse incoming disk data
|
||||
/// </summary>
|
||||
/// <param name="diskData"></param>
|
||||
/// <returns>
|
||||
/// TRUE: disk parsed
|
||||
/// FALSE: unable to parse disk
|
||||
/// </returns>
|
||||
public virtual bool ParseDisk(byte[] diskData)
|
||||
{
|
||||
// default result
|
||||
// override in inheriting class
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Examines the floppydisk data to work out what protection (if any) is present
|
||||
/// If possible it will also fix the disk data for this protection
|
||||
/// This should be run at the end of the ParseDisk() method
|
||||
/// </summary>
|
||||
public virtual void ParseProtection()
|
||||
{
|
||||
int[] weakArr = new int[2];
|
||||
|
||||
// speedlock
|
||||
if (DetectSpeedlock(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
|
||||
Sector sec = DiskTracks[0].Sectors[1];
|
||||
if (!sec.ContainsMultipleWeakSectors)
|
||||
{
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
for (int m = 0; m < 3; m++)
|
||||
{
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[i] + m + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
|
||||
byte nByte = (byte)n;
|
||||
|
||||
if (m == 0)
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < weakArr[0])
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
|
||||
else if (weakArr[1] > 0)
|
||||
{
|
||||
data.Add(nByte);
|
||||
weakArr[1]--;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
}
|
||||
}
|
||||
else if (DetectAlkatraz(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Alkatraz;
|
||||
}
|
||||
else if (DetectPaulOwens(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.PaulOwens;
|
||||
}
|
||||
else if (DetectHexagon(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Hexagon;
|
||||
}
|
||||
else if (DetectShadowOfTheBeast())
|
||||
{
|
||||
Protection = ProtectionType.ShadowOfTheBeast;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detection routine for shadow of the beast game
|
||||
/// Still cannot get this to work, but at least the game is detected
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool DetectShadowOfTheBeast()
|
||||
{
|
||||
if (DiskTracks[0].Sectors.Length != 9)
|
||||
return false;
|
||||
|
||||
var zeroSecs = DiskTracks[0].Sectors;
|
||||
if (zeroSecs[0].SectorID != 65 ||
|
||||
zeroSecs[1].SectorID != 66 ||
|
||||
zeroSecs[2].SectorID != 67 ||
|
||||
zeroSecs[3].SectorID != 68 ||
|
||||
zeroSecs[4].SectorID != 69 ||
|
||||
zeroSecs[5].SectorID != 70 ||
|
||||
zeroSecs[6].SectorID != 71 ||
|
||||
zeroSecs[7].SectorID != 72 ||
|
||||
zeroSecs[8].SectorID != 73)
|
||||
return false;
|
||||
|
||||
var oneSecs = DiskTracks[1].Sectors;
|
||||
|
||||
if (oneSecs.Length != 8)
|
||||
return false;
|
||||
|
||||
if (oneSecs[0].SectorID != 17 ||
|
||||
oneSecs[1].SectorID != 18 ||
|
||||
oneSecs[2].SectorID != 19 ||
|
||||
oneSecs[3].SectorID != 20 ||
|
||||
oneSecs[4].SectorID != 21 ||
|
||||
oneSecs[5].SectorID != 22 ||
|
||||
oneSecs[6].SectorID != 23 ||
|
||||
oneSecs[7].SectorID != 24)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect speedlock weak sector
|
||||
/// </summary>
|
||||
/// <param name="weak"></param>
|
||||
/// <returns></returns>
|
||||
public bool DetectSpeedlock(ref int[] weak)
|
||||
{
|
||||
// SPEEDLOCK NOTES (-asni 2018-05-01)
|
||||
// ---------------------------------
|
||||
// Speedlock is one of the more common +3 disk protections and there are a few different versions
|
||||
// Usually, track 0 sector 1 (ID 2) has data CRC errors that result in certain bytes returning a different value every time they are read
|
||||
// Speedlock will generally read this track a number of times during the load process
|
||||
// and if the correct bytes are not different between reads, the load fails
|
||||
|
||||
// always must have track 0 containing 9 sectors
|
||||
if (DiskTracks[0].Sectors.Length != 9)
|
||||
return false;
|
||||
|
||||
// check for SPEEDLOCK ident in sector 0
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
return false;
|
||||
|
||||
// check for correct sector 0 lengths
|
||||
if (DiskTracks[0].Sectors[0].SectorSize != 2 ||
|
||||
DiskTracks[0].Sectors[0].SectorData.Length < 0x200)
|
||||
return false;
|
||||
|
||||
// sector[1] (SectorID 2) contains the weak sectors
|
||||
Sector sec = DiskTracks[0].Sectors[1];
|
||||
|
||||
// check for correct sector 1 lengths
|
||||
if (sec.SectorSize != 2 ||
|
||||
sec.SectorData.Length < 0x200)
|
||||
return false;
|
||||
|
||||
// secID 2 needs a CRC error
|
||||
//if (!(sec.Status1.Bit(5) || sec.Status2.Bit(5)))
|
||||
//return false;
|
||||
|
||||
// check for filler
|
||||
bool startFillerFound = true;
|
||||
for (int i = 0; i < 250; i++)
|
||||
{
|
||||
if (sec.SectorData[i] != sec.SectorData[i + 1])
|
||||
{
|
||||
startFillerFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!startFillerFound)
|
||||
{
|
||||
weak[0] = 0;
|
||||
weak[1] = 0x200;
|
||||
}
|
||||
else
|
||||
{
|
||||
weak[0] = 0x150;
|
||||
weak[1] = 0x20;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Alkatraz
|
||||
/// </summary>
|
||||
/// <param name="weak"></param>
|
||||
/// <returns></returns>
|
||||
public bool DetectAlkatraz(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors[0].SectorData;
|
||||
var data2 = DiskTracks[0].Sectors[0].SectorData.Length;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for ALKATRAZ ident in sector 0
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("ALKATRAZ PROTECTION SYSTEM"))
|
||||
return false;
|
||||
|
||||
// ALKATRAZ NOTES (-asni 2018-05-01)
|
||||
// ---------------------------------
|
||||
// Alkatraz protection appears to revolve around a track on the disk with 18 sectors,
|
||||
// (track position is not consistent) with the sector ID info being incorrect:
|
||||
// TrackID is consistent between the sectors although is usually high (233, 237 etc)
|
||||
// SideID is fairly random looking but with all IDs being even
|
||||
// SectorID is also fairly random looking but contains both odd and even numbers
|
||||
//
|
||||
// There doesnt appear to be any CRC errors in this track, but the sector size is always 1 (256 bytes)
|
||||
// Each sector contains different filler byte
|
||||
// Once track 0 is loaded the CPU completely reads all the sectors in this track one-by-one.
|
||||
// Data transferred during execution must be correct, also result ST0, ST1 and ST2 must be 64, 128 and 0 respectively
|
||||
|
||||
// Immediately following this track are a number of tracks and sectors with a DAM set.
|
||||
// These are all read in sector by sector
|
||||
// Again, Alkatraz appears to require that ST0, ST1, and ST2 result bytes are set to 64, 128 and 0 respectively
|
||||
// (so the CM in ST2 needs to be reset)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Paul Owens
|
||||
/// </summary>
|
||||
/// <param name="weak"></param>
|
||||
/// <returns></returns>
|
||||
public bool DetectPaulOwens(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors[2].SectorData;
|
||||
var data2 = DiskTracks[0].Sectors[2].SectorData.Length;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for PAUL OWENS ident in sector 2
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[2].SectorData, 0, DiskTracks[0].Sectors[2].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("PAUL OWENS"))
|
||||
return false;
|
||||
|
||||
// Paul Owens Disk Protection Notes (-asni 2018-05-01)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// This scheme looks a little similar to Alkatraz with incorrect sector ID info in many places
|
||||
// and deleted address marks (although these do not seem to show the strict relience on removing the CM mark from ST2 result that Alkatraz does)
|
||||
// There are also data CRC errors but these dont look to be read more than once/checked for changes during load
|
||||
// Main identifiers:
|
||||
//
|
||||
// * There are more than 10 cylinders
|
||||
// * Cylinder 1 has no sector data
|
||||
// * The sector ID infomation in most cases contains incorrect track IDs
|
||||
// * Tracks 0 (boot) and 5 appear to be pretty much the only tracks that do not have incorrect sector ID marks
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Hexagon copy protection
|
||||
/// </summary>
|
||||
/// <param name="weak"></param>
|
||||
/// <returns></returns>
|
||||
public bool DetectHexagon(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors.Length;
|
||||
var data2 = DiskTracks[0].Sectors[8].ActualDataByteLength;
|
||||
var data3 = DiskTracks[0].Sectors[8].SectorData;
|
||||
var data4 = DiskTracks[0].Sectors[8].SectorData.Length;
|
||||
var data5 = DiskTracks[1].Sectors[0];
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DiskTracks[0].Sectors.Length != 10 || DiskTracks[0].Sectors[8].ActualDataByteLength != 512)
|
||||
return false;
|
||||
|
||||
// check for Hexagon ident in sector 8
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[8].SectorData, 0, DiskTracks[0].Sectors[8].SectorData.Length);
|
||||
if (ident.ToUpper().Contains("GON DISK PROT"))
|
||||
return true;
|
||||
|
||||
// hexagon protection may not be labelled as such
|
||||
var track = DiskTracks[1];
|
||||
var sector = track.Sectors[0];
|
||||
|
||||
if (sector.SectorSize == 6 && sector.Status1 == 0x20 && sector.Status2 == 0x60)
|
||||
{
|
||||
if (track.Sectors.Length == 1)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Hexagon Copy Protection Notes (-asni 2018-05-01)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
//
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Should be run at the end of the ParseDisk process
|
||||
/// If speedlock is detected the flag is set in the disk image
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual void SpeedlockDetection()
|
||||
{
|
||||
|
||||
if (DiskTracks.Length == 0)
|
||||
return;
|
||||
|
||||
// check for speedlock copyright notice
|
||||
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
{
|
||||
// speedlock not found
|
||||
return;
|
||||
}
|
||||
|
||||
// get cylinder 0
|
||||
var cyl = DiskTracks[0];
|
||||
|
||||
// get sector with ID=2
|
||||
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
|
||||
|
||||
if (sec == null)
|
||||
return;
|
||||
|
||||
// check for already multiple weak copies
|
||||
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
|
||||
return;
|
||||
|
||||
// check for invalid crcs in sector 2
|
||||
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we are going to create a total of 5 weak sector copies
|
||||
// keeping the original copy
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
//Random rnd = new Random();
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
for (int s = 0; s < origData.Length; s++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
data.Add(origData[s]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[s] + i + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
|
||||
byte nByte = (byte)n;
|
||||
|
||||
if (s < 336)
|
||||
{
|
||||
// non weak data
|
||||
data.Add(origData[s]);
|
||||
}
|
||||
else if (s < 511)
|
||||
{
|
||||
// weak data
|
||||
data.Add(nByte);
|
||||
}
|
||||
else if (s == 511)
|
||||
{
|
||||
// final sector byte
|
||||
data.Add(nByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// speedlock sector should not be more than 512 bytes
|
||||
// but in case it is just do non weak
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commit the sector data
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the track count for the disk
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual int GetTrackCount()
|
||||
{
|
||||
return DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the current sector ID info
|
||||
/// </summary>
|
||||
/// <param name="track"></param>
|
||||
/// <returns></returns>
|
||||
public virtual CHRN ReadID(byte trackIndex, byte side, int sectorIndex)
|
||||
{
|
||||
if (side != 0)
|
||||
return null;
|
||||
|
||||
if (DiskTracks.Length <= trackIndex || trackIndex < 0)
|
||||
{
|
||||
// invalid track - wrap around
|
||||
trackIndex = 0;
|
||||
}
|
||||
|
||||
var track = DiskTracks[trackIndex];
|
||||
|
||||
if (track.NumberOfSectors <= sectorIndex)
|
||||
{
|
||||
// invalid sector - wrap around
|
||||
sectorIndex = 0;
|
||||
}
|
||||
|
||||
var sector = track.Sectors[sectorIndex];
|
||||
|
||||
CHRN chrn = new CHRN();
|
||||
|
||||
chrn.C = sector.TrackNumber;
|
||||
chrn.H = sector.SideNumber;
|
||||
chrn.R = sector.SectorID;
|
||||
|
||||
// wrap around for N > 7
|
||||
if (sector.SectorSize > 7)
|
||||
{
|
||||
chrn.N = (byte)(sector.SectorSize - 7);
|
||||
}
|
||||
else if (sector.SectorSize < 0)
|
||||
{
|
||||
chrn.N = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
chrn.N = sector.SectorSize;
|
||||
}
|
||||
|
||||
chrn.Flag1 = (byte)(sector.Status1 & 0x25);
|
||||
chrn.Flag2 = (byte)(sector.Status2 & 0x61);
|
||||
|
||||
chrn.DataBytes = sector.ActualData;
|
||||
|
||||
return chrn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State serialization routines
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public abstract void SyncState(Serializer ser);
|
||||
|
||||
|
||||
public class Header
|
||||
{
|
||||
public string DiskIdent { get; set; }
|
||||
public string DiskCreatorString { get; set; }
|
||||
public byte NumberOfTracks { get; set; }
|
||||
public byte NumberOfSides { get; set; }
|
||||
public int[] TrackSizes { get; set; }
|
||||
}
|
||||
|
||||
public class Track
|
||||
{
|
||||
public string TrackIdent { get; set; }
|
||||
public byte TrackNumber { get; set; }
|
||||
public byte SideNumber { get; set; }
|
||||
public byte DataRate { get; set; }
|
||||
public byte RecordingMode { get; set; }
|
||||
public byte SectorSize { get; set; }
|
||||
public byte NumberOfSectors { get; set; }
|
||||
public byte GAP3Length { get; set; }
|
||||
public byte FillerByte { get; set; }
|
||||
public Sector[] Sectors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Presents a contiguous byte array of all sector data for this track
|
||||
/// (including any multiple weak/random data)
|
||||
/// </summary>
|
||||
public byte[] TrackSectorData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<byte> list = new List<byte>();
|
||||
|
||||
foreach (var sec in Sectors)
|
||||
{
|
||||
list.AddRange(sec.ActualData);
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Sector
|
||||
{
|
||||
public byte TrackNumber { get; set; }
|
||||
public byte SideNumber { get; set; }
|
||||
public byte SectorID { get; set; }
|
||||
public byte SectorSize { get; set; }
|
||||
public byte Status1 { get; set; }
|
||||
public byte Status2 { get; set; }
|
||||
public int ActualDataByteLength { get; set; }
|
||||
public byte[] SectorData { get; set; }
|
||||
public bool ContainsMultipleWeakSectors { get; set; }
|
||||
|
||||
public int WeakReadIndex = 0;
|
||||
|
||||
public void SectorReadCompleted()
|
||||
{
|
||||
if (ContainsMultipleWeakSectors)
|
||||
WeakReadIndex++;
|
||||
}
|
||||
|
||||
public int DataLen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ContainsMultipleWeakSectors)
|
||||
{
|
||||
return ActualDataByteLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int RandSecCounter = 0;
|
||||
|
||||
public byte[] ActualData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ContainsMultipleWeakSectors)
|
||||
{
|
||||
// check whether filler bytes are needed
|
||||
int size = 0x80 << SectorSize;
|
||||
if (size > ActualDataByteLength)
|
||||
{
|
||||
List<byte> l = new List<byte>();
|
||||
l.AddRange(SectorData);
|
||||
for (int i = 0; i < size - ActualDataByteLength; i++)
|
||||
{
|
||||
//l.Add(SectorData[i]);
|
||||
l.Add(SectorData.Last());
|
||||
}
|
||||
|
||||
return l.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SectorData;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// weak read neccessary
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
|
||||
// handle index wrap-around
|
||||
if (WeakReadIndex > copies - 1)
|
||||
WeakReadIndex = copies - 1;
|
||||
|
||||
// get the sector data based on the current weakreadindex
|
||||
int step = WeakReadIndex * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
|
||||
/*
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
Random rnd = new Random();
|
||||
int r = rnd.Next(0, copies - 1);
|
||||
int step = r * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CHRN SectorIDInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return new CHRN
|
||||
{
|
||||
C = TrackNumber,
|
||||
H = SideNumber,
|
||||
R = SectorID,
|
||||
N = SectorSize,
|
||||
Flag1 = Status1,
|
||||
Flag2 = Status2,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of speedlock detection found
|
||||
/// </summary>
|
||||
public enum ProtectionType
|
||||
{
|
||||
None,
|
||||
Speedlock,
|
||||
Alkatraz,
|
||||
Hexagon,
|
||||
Frontier,
|
||||
PaulOwens,
|
||||
ShadowOfTheBeast
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Abtract class that represents all Media Converters
|
||||
/// </summary>
|
||||
public abstract class MediaConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of serializer
|
||||
/// </summary>
|
||||
public abstract MediaConverterType FormatType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this class can be used to read the data format
|
||||
/// </summary>
|
||||
public virtual bool IsReader
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this class can be used to write the data format
|
||||
/// </summary>
|
||||
public virtual bool IsWriter
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialization method
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void Read(byte[] data)
|
||||
{
|
||||
throw new NotImplementedException(this.GetType().ToString() +
|
||||
"Read operation is not implemented for this converter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeSerialization method
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual void Write(byte[] data)
|
||||
{
|
||||
throw new NotImplementedException(this.GetType().ToString() +
|
||||
"Write operation is not implemented for this converter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer does a quick check, returns TRUE if file is detected as this type
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public virtual bool CheckType(byte[] data)
|
||||
{
|
||||
throw new NotImplementedException(this.GetType().ToString() +
|
||||
"Check type operation is not implemented for this converter");
|
||||
}
|
||||
|
||||
#region Static Tools
|
||||
|
||||
/// <summary>
|
||||
/// Converts an int32 value into a byte array
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] GetBytes(int value)
|
||||
{
|
||||
byte[] buf = new byte[4];
|
||||
buf[0] = (byte)value;
|
||||
buf[1] = (byte)(value >> 8);
|
||||
buf[2] = (byte)(value >> 16);
|
||||
buf[3] = (byte)(value >> 24);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an int32 from a byte array based on offset
|
||||
/// </summary>
|
||||
/// <param name="buf"></param>
|
||||
/// <param name="offsetIndex"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetInt32(byte[] buf, int offsetIndex)
|
||||
{
|
||||
return buf[offsetIndex] | buf[offsetIndex + 1] << 8 | buf[offsetIndex + 2] << 16 | buf[offsetIndex + 3] << 24;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an uint16 from a byte array based on offset
|
||||
/// </summary>
|
||||
/// <param name="buf"></param>
|
||||
/// <param name="offsetIndex"></param>
|
||||
/// <returns></returns>
|
||||
public static ushort GetWordValue(byte[] buf, int offsetIndex)
|
||||
{
|
||||
return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a byte array with a uint16 value based on offset
|
||||
/// </summary>
|
||||
/// <param name="buf"></param>
|
||||
/// <param name="offsetIndex"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetWordValue(byte[] buf, int offsetIndex, ushort value)
|
||||
{
|
||||
buf[offsetIndex] = (byte)value;
|
||||
buf[offsetIndex + 1] = (byte)(value >> 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a PauseInMilliseconds value and returns the value in T-States
|
||||
/// </summary>
|
||||
/// <param name="pauseInMS"></param>
|
||||
/// <returns></returns>
|
||||
public static int TranslatePause(int pauseInMS)
|
||||
{
|
||||
// t-states per millisecond
|
||||
var tspms = (69888 * 50) / 1000;
|
||||
// get value
|
||||
int res = pauseInMS * tspms;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a byte array that is Z-RLE compressed
|
||||
/// </summary>
|
||||
/// <param name="sourceBuffer"></param>
|
||||
/// <param name="destBuffer"></param>
|
||||
public static void DecompressZRLE(byte[] sourceBuffer, ref byte[] destBuffer)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
stream.Write(sourceBuffer, 0, sourceBuffer.Length);
|
||||
stream.Position = 0;
|
||||
stream.ReadByte();
|
||||
stream.ReadByte();
|
||||
DeflateStream ds = new DeflateStream(stream, CompressionMode.Decompress, false);
|
||||
ds.Read(destBuffer, 0, destBuffer.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the different types of media serializer avaiable
|
||||
/// </summary>
|
||||
public enum MediaConverterType
|
||||
{
|
||||
NONE,
|
||||
CDT,
|
||||
DSK
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the possible commands that can be raised from each tape block
|
||||
/// </summary>
|
||||
public enum TapeCommand
|
||||
{
|
||||
NONE,
|
||||
STOP_THE_TAPE,
|
||||
STOP_THE_TAPE_48K,
|
||||
BEGIN_GROUP,
|
||||
END_GROUP,
|
||||
SHOW_MESSAGE,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
using BizHawk.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a tape block
|
||||
/// </summary>
|
||||
public class TapeDataBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Either the TZX block ID, or -1 in the case of non-tzx blocks
|
||||
/// </summary>
|
||||
private int _blockID = -1;
|
||||
public int BlockID
|
||||
{
|
||||
get { return _blockID; }
|
||||
set {
|
||||
_blockID = value;
|
||||
|
||||
if (MetaData == null)
|
||||
MetaData = new Dictionary<BlockDescriptorTitle, string>();
|
||||
|
||||
AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The block type
|
||||
/// </summary>
|
||||
private BlockType _blockType;
|
||||
public BlockType BlockDescription
|
||||
{
|
||||
get { return _blockType; }
|
||||
set {
|
||||
_blockType = value;
|
||||
if (MetaData == null)
|
||||
MetaData = new Dictionary<BlockDescriptorTitle, string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte array containing the raw block data
|
||||
/// </summary>
|
||||
private byte[] _blockData;
|
||||
public byte[] BlockData
|
||||
{
|
||||
get { return _blockData; }
|
||||
set { _blockData = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
||||
/// Its basically tape information
|
||||
/// </summary>
|
||||
private byte[][] _tapeDescriptionData;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Tape Description Data in a human readable format
|
||||
/// </summary>
|
||||
public List<string> TapeDescriptionData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> data = new List<string>();
|
||||
|
||||
foreach (byte[] b in _tapeDescriptionData)
|
||||
{
|
||||
data.Add(Encoding.ASCII.GetString(b));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Block Meta Data
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of block related data
|
||||
/// </summary>
|
||||
public Dictionary<BlockDescriptorTitle, string> MetaData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single metadata item to the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
/// <param name="data"></param>
|
||||
public void AddMetaData(BlockDescriptorTitle descriptor, string data)
|
||||
{
|
||||
// check whether entry already exists
|
||||
bool check = MetaData.ContainsKey(descriptor);
|
||||
if (check)
|
||||
{
|
||||
// already exists - update
|
||||
MetaData[descriptor] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
// create new
|
||||
MetaData.Add(descriptor, data);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List containing the pulse timing values
|
||||
/// </summary>
|
||||
public List<int> DataPeriods = new List<int>();
|
||||
|
||||
public bool InitialPulseLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Command that is raised by this data block
|
||||
/// (that may or may not need to be acted on)
|
||||
/// </summary>
|
||||
private TapeCommand _command = TapeCommand.NONE;
|
||||
public TapeCommand Command
|
||||
{
|
||||
get { return _command; }
|
||||
set { _command = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The defined post-block pause
|
||||
/// </summary>
|
||||
private int _pauseInMS;
|
||||
public int PauseInMS
|
||||
{
|
||||
get { return _pauseInMS; }
|
||||
set { _pauseInMS = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data periods as an array
|
||||
/// (primarily to aid in bizhawk state serialization)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int[] GetDataPeriodsArray()
|
||||
{
|
||||
return DataPeriods.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts an array of data periods and updates the DataPeriods list accordingly
|
||||
/// (primarily to aid in bizhawk state serialization)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetDataPeriodsArray(int[] periodArray)
|
||||
{
|
||||
DataPeriods = new List<int>();
|
||||
|
||||
if (periodArray == null)
|
||||
return;
|
||||
|
||||
DataPeriods = periodArray.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bizhawk state serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
public void SyncState(Serializer ser, int blockPosition)
|
||||
{
|
||||
ser.BeginSection("DataBlock" + blockPosition);
|
||||
|
||||
ser.Sync("_blockID", ref _blockID);
|
||||
//ser.SyncFixedString("_blockDescription", ref _blockDescription, 200);
|
||||
ser.SyncEnum("_blockType", ref _blockType);
|
||||
ser.Sync("_blockData", ref _blockData, true);
|
||||
ser.SyncEnum("_command", ref _command);
|
||||
|
||||
int[] tempArray = null;
|
||||
|
||||
if (ser.IsWriter)
|
||||
{
|
||||
tempArray = GetDataPeriodsArray();
|
||||
ser.Sync("_periods", ref tempArray, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ser.Sync("_periods", ref tempArray, true);
|
||||
SetDataPeriodsArray(tempArray);
|
||||
}
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The types of TZX blocks
|
||||
/// </summary>
|
||||
public enum BlockType
|
||||
{
|
||||
Standard_Speed_Data_Block = 0x10,
|
||||
Turbo_Speed_Data_Block = 0x11,
|
||||
Pure_Tone = 0x12,
|
||||
Pulse_Sequence = 0x13,
|
||||
Pure_Data_Block = 0x14,
|
||||
Direct_Recording = 0x15,
|
||||
CSW_Recording = 0x18,
|
||||
Generalized_Data_Block = 0x19,
|
||||
Pause_or_Stop_the_Tape = 0x20,
|
||||
Group_Start = 0x21,
|
||||
Group_End = 0x22,
|
||||
Jump_to_Block = 0x23,
|
||||
Loop_Start = 0x24,
|
||||
Loop_End = 0x25,
|
||||
Call_Sequence = 0x26,
|
||||
Return_From_Sequence = 0x27,
|
||||
Select_Block = 0x28,
|
||||
Stop_the_Tape_48K = 0x2A,
|
||||
Set_Signal_Level = 0x2B,
|
||||
Text_Description = 0x30,
|
||||
Message_Block = 0x31,
|
||||
Archive_Info = 0x32,
|
||||
Hardware_Type = 0x33,
|
||||
Custom_Info_Block = 0x35,
|
||||
Glue_Block = 0x5A,
|
||||
|
||||
// depreciated blocks
|
||||
C64_ROM_Type_Data_Block = 0x16,
|
||||
C64_Turbo_Tape_Data_Block = 0x17,
|
||||
Emulation_Info = 0x34,
|
||||
Snapshot_Block = 0x40,
|
||||
|
||||
// unsupported / undetected
|
||||
Unsupported,
|
||||
|
||||
// PZX blocks
|
||||
PZXT,
|
||||
PULS,
|
||||
DATA,
|
||||
BRWS,
|
||||
PAUS,
|
||||
|
||||
// zxhawk proprietry
|
||||
PAUSE_BLOCK,
|
||||
|
||||
WAV_Recording
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Different title possibilities
|
||||
/// </summary>
|
||||
public enum BlockDescriptorTitle
|
||||
{
|
||||
Undefined,
|
||||
Block_ID,
|
||||
Program,
|
||||
Data_Bytes,
|
||||
Bytes,
|
||||
|
||||
Pilot_Pulse_Length,
|
||||
Pilot_Pulse_Count,
|
||||
First_Sync_Length,
|
||||
Second_Sync_Length,
|
||||
Zero_Bit_Length,
|
||||
One_Bit_Length,
|
||||
Data_Length,
|
||||
Bits_In_Last_Byte,
|
||||
Pause_After_Data,
|
||||
|
||||
Pulse_Length,
|
||||
Pulse_Count,
|
||||
|
||||
Text_Description,
|
||||
Title,
|
||||
Publisher,
|
||||
Author,
|
||||
Year,
|
||||
Language,
|
||||
Type,
|
||||
Price,
|
||||
Protection,
|
||||
Origin,
|
||||
Comments,
|
||||
|
||||
Needs_Parsing
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about Amstrad ROM
|
||||
/// </summary>
|
||||
public class RomData
|
||||
{
|
||||
/// <summary>
|
||||
/// ROM Contents
|
||||
/// </summary>
|
||||
public byte[] RomBytes
|
||||
{
|
||||
get { return _romBytes; }
|
||||
set { _romBytes = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Useful ROM addresses that are needed during tape operations
|
||||
/// </summary>
|
||||
public ushort SaveBytesRoutineAddress
|
||||
{
|
||||
get { return _saveBytesRoutineAddress; }
|
||||
set { _saveBytesRoutineAddress = value; }
|
||||
}
|
||||
public ushort LoadBytesRoutineAddress
|
||||
{
|
||||
get { return _loadBytesRoutineAddress; }
|
||||
set { _loadBytesRoutineAddress = value; }
|
||||
}
|
||||
public ushort SaveBytesResumeAddress
|
||||
{
|
||||
get { return _saveBytesResumeAddress; }
|
||||
set { _saveBytesResumeAddress = value; }
|
||||
}
|
||||
public ushort LoadBytesResumeAddress
|
||||
{
|
||||
get { return _loadBytesResumeAddress; }
|
||||
set { _loadBytesResumeAddress = value; }
|
||||
}
|
||||
public ushort LoadBytesInvalidHeaderAddress
|
||||
{
|
||||
get { return _loadBytesInvalidHeaderAddress; }
|
||||
set { _loadBytesInvalidHeaderAddress = value; }
|
||||
}
|
||||
|
||||
private byte[] _romBytes;
|
||||
private ushort _saveBytesRoutineAddress;
|
||||
private ushort _loadBytesRoutineAddress;
|
||||
private ushort _saveBytesResumeAddress;
|
||||
private ushort _loadBytesResumeAddress;
|
||||
private ushort _loadBytesInvalidHeaderAddress;
|
||||
|
||||
|
||||
public static RomData InitROM(MachineType machineType, byte[] rom)
|
||||
{
|
||||
RomData RD = new RomData();
|
||||
RD.RomBytes = new byte[rom.Length];
|
||||
RD.RomBytes = rom;
|
||||
|
||||
switch (machineType)
|
||||
{
|
||||
case MachineType.CPC464:
|
||||
RD.SaveBytesRoutineAddress = 0x04C2;
|
||||
RD.SaveBytesResumeAddress = 0x0000;
|
||||
RD.LoadBytesRoutineAddress = 0x0808; //0x0556; //0x056C;
|
||||
RD.LoadBytesResumeAddress = 0x05E2;
|
||||
RD.LoadBytesInvalidHeaderAddress = 0x05B6;
|
||||
break;
|
||||
}
|
||||
|
||||
return RD;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// My attempt at mixing multiple ISoundProvider sources together and outputting another ISoundProvider
|
||||
/// Currently only supports SyncSoundMode.Sync
|
||||
/// Attached ISoundProvider sources must already be stereo 44.1khz and ideally sound buffers should be the same length (882)
|
||||
/// (if not, only 882 samples of their buffer will be used)
|
||||
/// </summary>
|
||||
internal sealed class SoundProviderMixer : ISoundProvider
|
||||
{
|
||||
private class Provider
|
||||
{
|
||||
public ISoundProvider SoundProvider { get; set; }
|
||||
public string ProviderDescription { get; set; }
|
||||
public int MaxVolume { get; set; }
|
||||
public short[] Buffer { get; set; }
|
||||
public int NSamp { get; set; }
|
||||
}
|
||||
|
||||
private bool _stereo = true;
|
||||
public bool Stereo
|
||||
{
|
||||
get { return _stereo; }
|
||||
set { _stereo = value; }
|
||||
}
|
||||
|
||||
private readonly List<Provider> SoundProviders;
|
||||
|
||||
public SoundProviderMixer(params ISoundProvider[] soundProviders)
|
||||
{
|
||||
SoundProviders = new List<Provider>();
|
||||
|
||||
foreach (var s in soundProviders)
|
||||
{
|
||||
SoundProviders.Add(new Provider
|
||||
{
|
||||
SoundProvider = s,
|
||||
MaxVolume = short.MaxValue,
|
||||
});
|
||||
}
|
||||
|
||||
EqualizeVolumes();
|
||||
}
|
||||
|
||||
public SoundProviderMixer(short maxVolume, string description, params ISoundProvider[] soundProviders)
|
||||
{
|
||||
SoundProviders = new List<Provider>();
|
||||
|
||||
foreach (var s in soundProviders)
|
||||
{
|
||||
SoundProviders.Add(new Provider
|
||||
{
|
||||
SoundProvider = s,
|
||||
MaxVolume = maxVolume,
|
||||
ProviderDescription = description
|
||||
});
|
||||
}
|
||||
|
||||
EqualizeVolumes();
|
||||
}
|
||||
|
||||
public void AddSource(ISoundProvider source, string description)
|
||||
{
|
||||
SoundProviders.Add(new Provider
|
||||
{
|
||||
SoundProvider = source,
|
||||
MaxVolume = short.MaxValue,
|
||||
ProviderDescription = description
|
||||
});
|
||||
|
||||
EqualizeVolumes();
|
||||
}
|
||||
|
||||
public void AddSource(ISoundProvider source, short maxVolume, string description)
|
||||
{
|
||||
SoundProviders.Add(new Provider
|
||||
{
|
||||
SoundProvider = source,
|
||||
MaxVolume = maxVolume,
|
||||
ProviderDescription = description
|
||||
});
|
||||
|
||||
EqualizeVolumes();
|
||||
}
|
||||
|
||||
public void DisableSource(ISoundProvider source)
|
||||
{
|
||||
var sp = SoundProviders.Where(a => a.SoundProvider == source);
|
||||
if (sp.Count() == 1)
|
||||
SoundProviders.Remove(sp.First());
|
||||
else if (sp.Count() > 1)
|
||||
foreach (var s in sp)
|
||||
SoundProviders.Remove(s);
|
||||
|
||||
EqualizeVolumes();
|
||||
}
|
||||
|
||||
public void EqualizeVolumes()
|
||||
{
|
||||
if (SoundProviders.Count < 1)
|
||||
return;
|
||||
|
||||
int eachVolume = short.MaxValue / SoundProviders.Count;
|
||||
foreach (var source in SoundProviders)
|
||||
{
|
||||
source.MaxVolume = eachVolume;
|
||||
}
|
||||
}
|
||||
|
||||
#region ISoundProvider
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
foreach (var soundSource in SoundProviders)
|
||||
{
|
||||
soundSource.SoundProvider.DiscardSamples();
|
||||
}
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
samples = null;
|
||||
nsamp = 0;
|
||||
|
||||
// get samples from all the providers
|
||||
foreach (var sp in SoundProviders)
|
||||
{
|
||||
int sampCount;
|
||||
short[] samp;
|
||||
sp.SoundProvider.GetSamplesSync(out samp, out sampCount);
|
||||
sp.NSamp = sampCount;
|
||||
sp.Buffer = samp;
|
||||
}
|
||||
|
||||
// are all the sample lengths the same?
|
||||
var firstEntry = SoundProviders.First();
|
||||
bool sameCount = SoundProviders.All(s => s.NSamp == firstEntry.NSamp);
|
||||
|
||||
if (!sameCount)
|
||||
{
|
||||
// this is a bit hacky, really all ISoundProviders should be supplying 44100 with 882 samples per frame.
|
||||
// we will make sure this happens (no matter how it sounds)
|
||||
if (SoundProviders.Count > 1)
|
||||
{
|
||||
for (int i = 0; i < SoundProviders.Count; i++)
|
||||
{
|
||||
int ns = SoundProviders[i].NSamp;
|
||||
short[] buff = new short[882 * 2];
|
||||
|
||||
for (int b = 0; b < 882 * 2; b++)
|
||||
{
|
||||
if (b == SoundProviders[i].Buffer.Length - 1)
|
||||
{
|
||||
// end of source buffer
|
||||
break;
|
||||
}
|
||||
|
||||
buff[b] = SoundProviders[i].Buffer[b];
|
||||
}
|
||||
|
||||
// save back to the soundprovider
|
||||
SoundProviders[i].NSamp = 882;
|
||||
SoundProviders[i].Buffer = buff;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// just process what we have as-is
|
||||
}
|
||||
}
|
||||
|
||||
// mix the soundproviders together
|
||||
nsamp = 882;
|
||||
samples = new short[nsamp * 2];
|
||||
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
short sectorVal = 0;
|
||||
foreach (var sp in SoundProviders)
|
||||
{
|
||||
if (sp.Buffer[i] > sp.MaxVolume)
|
||||
sectorVal += (short)sp.MaxVolume;
|
||||
else
|
||||
sectorVal += sp.Buffer[i];
|
||||
}
|
||||
|
||||
samples[i] = sectorVal;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
## CPCHawk
|
||||
|
||||
This may or may not work out. But I figured I could at least start by building on the existing ZXHawk implementation (the machines do have CPU, tape format, PSG and disk drive in common).
|
||||
|
||||
We'll see how that goes...
|
||||
|
||||
#### Currently working:
|
||||
|
||||
* Nothing. Just starting to get a code outline in place
|
||||
|
||||
-Asnivor
|
|
@ -43,6 +43,7 @@ namespace BizHawk.Emulation.Cores
|
|||
|
||||
C64,
|
||||
ZXSpectrum,
|
||||
AmstradCPC,
|
||||
INT,
|
||||
A26, A52, A78, LNX,
|
||||
|
||||
|
|
|
@ -60,6 +60,16 @@ namespace BizHawk.Emulation.Cores.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] cpc464_rom {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("cpc464_rom", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
|
|
|
@ -151,4 +151,7 @@
|
|||
<data name="ZX_plus2_rom" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\plus2.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="cpc464_rom" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\cpc464.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
Binary file not shown.
Loading…
Reference in New Issue