Merge pull request #1150 from TASVideos/ZXSpectrum

New system let's go!
This commit is contained in:
alyosha-tas 2018-03-19 15:33:20 -04:00 committed by GitHub
commit 981a31d32b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 16913 additions and 384 deletions

View File

@ -461,6 +461,79 @@
"Key Cursor Up/Down": "DownArrow",
"Key Cursor Left/Right": "RightArrow",
"Key Space": "Space"
},
"ZXSpectrum Controller": {
"P1 Up": "NumberPad8, J1 POV1U, X1 DpadUp, X1 LStickUp",
"P1 Down": "NumberPad2, J1 POV1D, X1 DpadDown, X1 LStickDown",
"P1 Left": "NumberPad4, J1 POV1L, X1 DpadLeft, X1 LStickLeft",
"P1 Right": "NumberPad6, J1 POV1R, X1 DpadRight, X1 LStickRight",
"P1 Button": "NumberPad1, J1 B1, X1 X",
"Key True Video": "",
"Key Inv Video": "",
"Key 1": "D1",
"Key 2": "D2",
"Key 3": "D3",
"Key 4": "D4",
"Key 5": "D5",
"Key 6": "D6",
"Key 7": "D7",
"Key 8": "D8",
"Key 9": "D9",
"Key 0": "D0",
"Key Break": "Delete",
"Key Delete": "Backspace",
"Key Graph": "",
"Key Q": "Q",
"Key W": "W",
"Key E": "E",
"Key R": "R",
"Key T": "T",
"Key Y": "Y",
"Key U": "U",
"Key I": "I",
"Key O": "O",
"Key P": "P",
"Key Extend Mode": "",
"Key Edit": "",
"Key A": "A",
"Key S": "S",
"Key D": "D",
"Key F": "F",
"Key G": "G",
"Key H": "H",
"Key J": "J",
"Key K": "K",
"Key L": "L",
"Key Return": "Return",
"Key Caps Shift": "LeftShift, RightShift",
"Key Caps Lock": "",
"Key Z": "Z",
"Key X": "X",
"Key C": "C",
"Key V": "V",
"Key B": "B",
"Key N": "N",
"Key M": "M",
"Key Period": "Period",
"Key Symbol Shift": "LeftControl, RightControl",
"Key Semi-Colon": "Semicolon",
"Key Inverted-Comma": "",
"Key Left Cursor": "LeftArrow",
"Key Right Cursor": "RightArrow",
"Key Space": "Space",
"Key Up Cursor": "UpArrow",
"Key Down Cursor": "DownArrow",
"Key Comma": "Comma",
"Play Tape": "F1",
"Stop Tape": "F2",
"RTZ Tape": "F3",
"Record Tape": "",
"Key Quote": "Shift+D2",
"Insert Next Tape": "F6",
"Insert Previous Tape": "F5",
"Next Tape Block": "F8",
"Prev Tape Block": "F7",
"Get Tape Status": "F10"
},
"Intellivision Controller": {
"P1 Up": "UpArrow, J1 POV1U, X1 DpadUp, X1 LStickUp",

View File

@ -96,6 +96,9 @@ namespace BizHawk.Client.ApiHawk
case "WSWAN":
return CoreSystem.WonderSwan;
case "ZXSpectrum":
return CoreSystem.ZXSpectrum;
case "VB":
case "NGP":
case "DNGP":
@ -205,6 +208,9 @@ namespace BizHawk.Client.ApiHawk
case CoreSystem.WonderSwan:
return "WSWAN";
case CoreSystem.ZXSpectrum:
return "ZXSpectrum";
default:
throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value.ToString()));
}

View File

@ -427,11 +427,11 @@ namespace BizHawk.Client.ApiHawk
}
else
{
return SystemInfo.DualGB;
return SystemInfo.DualGB;
}
default:
return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId));
return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId));
}
}
}

View File

@ -29,6 +29,7 @@
WonderSwan,
Libretro,
VirtualBoy,
NeoGeoPocket
NeoGeoPocket,
ZXSpectrum
}
}

View File

@ -149,6 +149,8 @@ namespace BizHawk.Client.Common
return SystemInfo.VirtualBoy;
case "NGP":
return SystemInfo.NeoGeoPocket;
case "ZXSpectrum":
return SystemInfo.ZXSpectrum;
}
}
}

View File

@ -19,6 +19,7 @@ using BizHawk.Emulation.Cores.PCEngine;
using BizHawk.Emulation.Cores.Sega.Saturn;
using BizHawk.Emulation.Cores.Sony.PSP;
using BizHawk.Emulation.Cores.Sony.PSX;
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
using BizHawk.Emulation.DiscSystem;
using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
@ -657,6 +658,21 @@ namespace BizHawk.Client.Common
(C64.C64Settings)GetCoreSettings<C64>(),
(C64.C64SyncSettings)GetCoreSyncSettings<C64>());
break;
case "ZXSpectrum":
List<GameInfo> zxGI = new List<GameInfo>();
foreach (var a in xmlGame.Assets)
{
zxGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) });
}
nextEmulator = new ZXSpectrum(
nextComm,
xmlGame.Assets.Select(a => a.Value), //.First(),
zxGI, // GameInfo.NullInstance,
(ZXSpectrum.ZXSpectrumSettings)GetCoreSettings<ZXSpectrum>(),
(ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings<ZXSpectrum>());
break;
case "PSX":
var entries = xmlGame.AssetFullPaths;
var discs = new List<Disc>();
@ -990,6 +1006,10 @@ namespace BizHawk.Client.Common
var c64 = new C64(nextComm, Enumerable.Repeat(rom.FileData, 1), rom.GameInfo, GetCoreSettings<C64>(), GetCoreSyncSettings<C64>());
nextEmulator = c64;
break;
case "ZXSpectrum":
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":
if (Global.Config.GBA_UsemGBA)
{

View File

@ -188,6 +188,11 @@ namespace BizHawk.Client.Common
/// </summary>
public static SystemInfo NeoGeoPocket { get; } = new SystemInfo("Neo-Geo Pocket", CoreSystem.NeoGeoPocket, 1);
/// <summary>
/// Gets the <see cref="SystemInfo"/> instance for ZXSpectrum
/// </summary>
public static SystemInfo ZXSpectrum { get; } = new SystemInfo("ZX Spectrum", CoreSystem.ZXSpectrum, 2);
#endregion Get SystemInfo
/// <summary>

View File

@ -292,7 +292,13 @@ namespace BizHawk.Client.Common
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", 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 = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Base", Path = Path.Combine(".", "ZXSpectrum"), Ordinal = 0 },
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "ROM", Path = ".", Ordinal = 1 },
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
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 = "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 },
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 },

View File

@ -503,6 +503,24 @@
<Compile Include="config\TI83\TI83PaletteConfig.Designer.cs">
<DependentUpon>TI83PaletteConfig.cs</DependentUpon>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumNonSyncSettings.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumNonSyncSettings.Designer.cs">
<DependentUpon>ZXSpectrumNonSyncSettings.cs</DependentUpon>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumCoreEmulationSettings.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumCoreEmulationSettings.Designer.cs">
<DependentUpon>ZXSpectrumCoreEmulationSettings.cs</DependentUpon>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumJoystickSettings.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="config\ZXSpectrum\ZXSpectrumJoystickSettings.Designer.cs">
<DependentUpon>ZXSpectrumJoystickSettings.cs</DependentUpon>
</Compile>
<Compile Include="CoreFeatureAnalysis.cs">
<SubType>Form</SubType>
</Compile>
@ -1206,6 +1224,7 @@
<Compile Include="tools\VirtualPads\schema\SnesSchema.cs" />
<Compile Include="tools\VirtualPads\schema\VirtualBoySchema.cs" />
<Compile Include="tools\VirtualPads\schema\WonderSwanSchema.cs" />
<Compile Include="tools\VirtualPads\schema\ZXSpectrumSchema.cs" />
<Compile Include="tools\VirtualPads\VirtualPad.cs">
<SubType>UserControl</SubType>
</Compile>
@ -1403,6 +1422,15 @@
<EmbeddedResource Include="config\TI83\TI83PaletteConfig.resx">
<DependentUpon>TI83PaletteConfig.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="config\ZXSpectrum\ZXSpectrumNonSyncSettings.resx">
<DependentUpon>ZXSpectrumNonSyncSettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="config\ZXSpectrum\ZXSpectrumCoreEmulationSettings.resx">
<DependentUpon>ZXSpectrumCoreEmulationSettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="config\ZXSpectrum\ZXSpectrumJoystickSettings.resx">
<DependentUpon>ZXSpectrumJoystickSettings.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="CoreFeatureAnalysis.resx">
<DependentUpon>CoreFeatureAnalysis.cs</DependentUpon>
</EmbeddedResource>
@ -1794,6 +1822,7 @@
<None Include="config\ControllerImages\GENController.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\ZXSpectrumKeyboard.bmp" />
<None Include="images\WSW.png" />
<None Include="images\WNW.png" />
<None Include="images\SW.png" />
@ -2118,6 +2147,7 @@
<None Include="images\ENE.png" />
<None Include="images\ESE.png" />
<None Include="images\ControllerImages\NGPController.png" />
<Content Include="config\ControllerImages\ZXSpectrumKeyboards.png" />
<Content Include="images\logo.ico" />
<None Include="images\Paste.png" />
<None Include="images\reboot.png" />

View File

@ -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"
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX"
};
}

View File

@ -73,7 +73,7 @@
this.LoadCurrentSlotMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SaveRAMSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.FlushSaveRAMMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
this.MovieSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.ReadonlyMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator15 = new System.Windows.Forms.ToolStripSeparator();
@ -192,14 +192,13 @@
this.GbaCoreSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.VbaNextCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.MgbaCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SGBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem();
this.SgbBsnesMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SgbBsnesMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SgbSameBoyMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem();
this.GBGambatteMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBGBHawkMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem();
this.GBGambatteMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBGBHawkMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem16 = new System.Windows.Forms.ToolStripSeparator();
this.allowGameDBCoreOverridesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator8 = new System.Windows.Forms.ToolStripSeparator();
@ -239,9 +238,8 @@
this.coreToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.quickNESToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.nesHawkToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator34 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripSeparator35 = new System.Windows.Forms.ToolStripSeparator();
this.NESPPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator34 = new System.Windows.Forms.ToolStripSeparator();
this.NESPPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.NESNametableViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.NESGameGenieCodesMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.musicRipperToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@ -280,6 +278,12 @@
this.SMSdisplayNtscToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSdisplayPalToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSdisplayAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerStandardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerPaddleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerLightPhaserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerSportsPadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerKeyboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMStoolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
this.SMSenableBIOSToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSEnableFMChipMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@ -304,14 +308,14 @@
this.AtariSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.A7800SubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.A7800ControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.A7800FilterSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.A7800FilterSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.GBcoreSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.LoadGBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator28 = new System.Windows.Forms.ToolStripSeparator();
this.GBGPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBPrinterViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBGameGenieMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBGameGenieMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBPrinterViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.GBASubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.GBACoreSelectionSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.GBAmGBAMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@ -333,6 +337,7 @@
this.SnesOptionsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ColecoSubMenu = new System.Windows.Forms.ToolStripMenuItem();
this.ColecoControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator35 = new System.Windows.Forms.ToolStripSeparator();
this.ColecoSkipBiosMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ColecoUseSGMMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.N64SubMenu = new System.Windows.Forms.ToolStripMenuItem();
@ -376,6 +381,11 @@
this.ForumsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ZXSpectrumCoreEmulationSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ZXSpectrumNonSyncSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.MainStatusBar = new StatusStripEx();
this.DumpStatusButton = new System.Windows.Forms.ToolStripDropDownButton();
this.EmuStatus = new System.Windows.Forms.ToolStripStatusLabel();
@ -447,13 +457,7 @@
this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator();
this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.timerMouseIdle = new System.Windows.Forms.Timer(this.components);
this.SMSControllerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerStandardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerPaddleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerLightPhaserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerSportsPadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SMSControllerKeyboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.MainformMenu.SuspendLayout();
this.MainformMenu.SuspendLayout();
this.MainStatusBar.SuspendLayout();
this.MainFormContextMenu.SuspendLayout();
this.SuspendLayout();
@ -490,7 +494,8 @@
this.pCFXToolStripMenuItem,
this.virtualBoyToolStripMenuItem,
this.neoGeoPocketToolStripMenuItem,
this.HelpSubMenu});
this.HelpSubMenu,
this.zXSpectrumToolStripMenuItem});
this.MainformMenu.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow;
this.MainformMenu.Location = new System.Drawing.Point(0, 0);
this.MainformMenu.Name = "MainformMenu";
@ -912,26 +917,26 @@
this.LoadCurrentSlotMenuItem.Size = new System.Drawing.Size(178, 22);
this.LoadCurrentSlotMenuItem.Text = "Load Current Slot";
this.LoadCurrentSlotMenuItem.Click += new System.EventHandler(this.LoadCurrentSlotMenuItem_Click);
//
// SaveRAMSubMenu
//
this.SaveRAMSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.FlushSaveRAMMenuItem});
this.SaveRAMSubMenu.Name = "SaveRAMSubMenu";
this.SaveRAMSubMenu.Size = new System.Drawing.Size(159, 22);
this.SaveRAMSubMenu.Text = "Save &RAM";
this.SaveRAMSubMenu.DropDownOpened += new System.EventHandler(this.SaveRAMSubMenu_DropDownOpened);
//
// FlushSaveRAMMenuItem
//
this.FlushSaveRAMMenuItem.Name = "FlushSaveRAMMenuItem";
this.FlushSaveRAMMenuItem.Size = new System.Drawing.Size(156, 22);
this.FlushSaveRAMMenuItem.Text = "&Flush Save Ram";
this.FlushSaveRAMMenuItem.Click += new System.EventHandler(this.FlushSaveRAMMenuItem_Click);
// toolStripMenuItem2
//
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
//
// SaveRAMSubMenu
//
this.SaveRAMSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.FlushSaveRAMMenuItem});
this.SaveRAMSubMenu.Name = "SaveRAMSubMenu";
this.SaveRAMSubMenu.Size = new System.Drawing.Size(159, 22);
this.SaveRAMSubMenu.Text = "Save &RAM";
this.SaveRAMSubMenu.DropDownOpened += new System.EventHandler(this.SaveRAMSubMenu_DropDownOpened);
//
// FlushSaveRAMMenuItem
//
this.FlushSaveRAMMenuItem.Name = "FlushSaveRAMMenuItem";
this.FlushSaveRAMMenuItem.Size = new System.Drawing.Size(156, 22);
this.FlushSaveRAMMenuItem.Text = "&Flush Save Ram";
this.FlushSaveRAMMenuItem.Click += new System.EventHandler(this.FlushSaveRAMMenuItem_Click);
//
// toolStripMenuItem2
//
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
this.toolStripMenuItem2.Size = new System.Drawing.Size(156, 6);
//
// MovieSubMenu
@ -1126,7 +1131,7 @@
//
this.RecordAVMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.RecordHS;
this.RecordAVMenuItem.Name = "RecordAVMenuItem";
this.RecordAVMenuItem.Size = new System.Drawing.Size(223, 22);
this.RecordAVMenuItem.Size = new System.Drawing.Size(225, 22);
this.RecordAVMenuItem.Text = "&Record AVI/WAV";
this.RecordAVMenuItem.Click += new System.EventHandler(this.RecordAVMenuItem_Click);
//
@ -1134,7 +1139,7 @@
//
this.ConfigAndRecordAVMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.AVI;
this.ConfigAndRecordAVMenuItem.Name = "ConfigAndRecordAVMenuItem";
this.ConfigAndRecordAVMenuItem.Size = new System.Drawing.Size(223, 22);
this.ConfigAndRecordAVMenuItem.Size = new System.Drawing.Size(225, 22);
this.ConfigAndRecordAVMenuItem.Text = "Config and Record AVI/WAV";
this.ConfigAndRecordAVMenuItem.Click += new System.EventHandler(this.ConfigAndRecordAVMenuItem_Click);
//
@ -1142,26 +1147,26 @@
//
this.StopAVIMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.Stop;
this.StopAVIMenuItem.Name = "StopAVIMenuItem";
this.StopAVIMenuItem.Size = new System.Drawing.Size(223, 22);
this.StopAVIMenuItem.Size = new System.Drawing.Size(225, 22);
this.StopAVIMenuItem.Text = "&Stop AVI/WAV";
this.StopAVIMenuItem.Click += new System.EventHandler(this.StopAVMenuItem_Click);
//
// toolStripSeparator19
//
this.toolStripSeparator19.Name = "toolStripSeparator19";
this.toolStripSeparator19.Size = new System.Drawing.Size(220, 6);
this.toolStripSeparator19.Size = new System.Drawing.Size(222, 6);
//
// CaptureOSDMenuItem
//
this.CaptureOSDMenuItem.Name = "CaptureOSDMenuItem";
this.CaptureOSDMenuItem.Size = new System.Drawing.Size(223, 22);
this.CaptureOSDMenuItem.Size = new System.Drawing.Size(225, 22);
this.CaptureOSDMenuItem.Text = "Capture OSD";
this.CaptureOSDMenuItem.Click += new System.EventHandler(this.CaptureOSDMenuItem_Click);
//
// SynclessRecordingMenuItem
//
this.SynclessRecordingMenuItem.Name = "SynclessRecordingMenuItem";
this.SynclessRecordingMenuItem.Size = new System.Drawing.Size(223, 22);
this.SynclessRecordingMenuItem.Size = new System.Drawing.Size(225, 22);
this.SynclessRecordingMenuItem.Text = "S&yncless Recording Tools";
this.SynclessRecordingMenuItem.Click += new System.EventHandler(this.SynclessRecordingMenuItem_Click);
//
@ -1816,8 +1821,8 @@
this.CoreSNESSubMenu,
this.GbaCoreSubMenu,
this.SGBCoreSubmenu,
this.GBCoreSubmenu,
this.GBInSGBMenuItem,
this.GBCoreSubmenu,
this.GBInSGBMenuItem,
this.toolStripMenuItem16,
this.allowGameDBCoreOverridesToolStripMenuItem,
this.toolStripSeparator8,
@ -1900,12 +1905,6 @@
this.MgbaCoreMenuItem.Text = "mGBA";
this.MgbaCoreMenuItem.Click += new System.EventHandler(this.GbaCorePick_Click);
//
// Atari7800HawkCoreMenuItem
//
this.Atari7800HawkCoreMenuItem.Name = "Atari7800HawkCoreMenuItem";
this.Atari7800HawkCoreMenuItem.Size = new System.Drawing.Size(153, 22);
this.Atari7800HawkCoreMenuItem.Text = "Atari7800Hawk";
//
// SGBCoreSubmenu
//
this.SGBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
@ -1915,48 +1914,48 @@
this.SGBCoreSubmenu.Size = new System.Drawing.Size(239, 22);
this.SGBCoreSubmenu.Text = "SGB";
this.SGBCoreSubmenu.DropDownOpened += new System.EventHandler(this.SGBCoreSubmenu_DropDownOpened);
//
// GBCoreSubmenu
//
this.GBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.GBGambatteMenuItem,
this.GBGBHawkMenuItem});
this.GBCoreSubmenu.Name = "GBCoreSubmenu";
this.GBCoreSubmenu.Size = new System.Drawing.Size(239, 22);
this.GBCoreSubmenu.Text = "GB";
this.GBCoreSubmenu.DropDownOpened += new System.EventHandler(this.GBCoreSubmenu_DropDownOpened);
//
// SgbBsnesMenuItem
//
this.SgbBsnesMenuItem.Name = "SgbBsnesMenuItem";
this.SgbBsnesMenuItem.Size = new System.Drawing.Size(152, 22);
//
// SgbBsnesMenuItem
//
this.SgbBsnesMenuItem.Name = "SgbBsnesMenuItem";
this.SgbBsnesMenuItem.Size = new System.Drawing.Size(123, 22);
this.SgbBsnesMenuItem.Text = "BSNES";
this.SgbBsnesMenuItem.Click += new System.EventHandler(this.SgbCorePick_Click);
//
// SgbSameBoyMenuItem
//
this.SgbSameBoyMenuItem.Name = "SgbSameBoyMenuItem";
this.SgbSameBoyMenuItem.Size = new System.Drawing.Size(152, 22);
this.SgbSameBoyMenuItem.Size = new System.Drawing.Size(123, 22);
this.SgbSameBoyMenuItem.Text = "SameBoy";
this.SgbSameBoyMenuItem.Click += new System.EventHandler(this.SgbCorePick_Click);
//
// GBGambatteMenuItem
//
this.GBGambatteMenuItem.Name = "GBGambatteMenuItem";
this.GBGambatteMenuItem.Size = new System.Drawing.Size(152, 22);
this.GBGambatteMenuItem.Text = "Gambatte";
this.GBGambatteMenuItem.Click += new System.EventHandler(this.GBCorePick_Click);
//
// GBGBHawkMenuItem
//
this.GBGBHawkMenuItem.Name = "GBGBHawkMenuItem";
this.GBGBHawkMenuItem.Size = new System.Drawing.Size(152, 22);
this.GBGBHawkMenuItem.Text = "GBHawk";
this.GBGBHawkMenuItem.Click += new System.EventHandler(this.GBCorePick_Click);
//
// GBInSGBMenuItem
//
this.GBInSGBMenuItem.Name = "GBInSGBMenuItem";
//
// GBCoreSubmenu
//
this.GBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.GBGambatteMenuItem,
this.GBGBHawkMenuItem});
this.GBCoreSubmenu.Name = "GBCoreSubmenu";
this.GBCoreSubmenu.Size = new System.Drawing.Size(239, 22);
this.GBCoreSubmenu.Text = "GB";
this.GBCoreSubmenu.DropDownOpened += new System.EventHandler(this.GBCoreSubmenu_DropDownOpened);
//
// GBGambatteMenuItem
//
this.GBGambatteMenuItem.Name = "GBGambatteMenuItem";
this.GBGambatteMenuItem.Size = new System.Drawing.Size(126, 22);
this.GBGambatteMenuItem.Text = "Gambatte";
this.GBGambatteMenuItem.Click += new System.EventHandler(this.GBCorePick_Click);
//
// GBGBHawkMenuItem
//
this.GBGBHawkMenuItem.Name = "GBGBHawkMenuItem";
this.GBGBHawkMenuItem.Size = new System.Drawing.Size(126, 22);
this.GBGBHawkMenuItem.Text = "GBHawk";
this.GBGBHawkMenuItem.Click += new System.EventHandler(this.GBCorePick_Click);
//
// GBInSGBMenuItem
//
this.GBInSGBMenuItem.Name = "GBInSGBMenuItem";
this.GBInSGBMenuItem.Size = new System.Drawing.Size(239, 22);
this.GBInSGBMenuItem.Text = "GB in SGB";
this.GBInSGBMenuItem.Click += new System.EventHandler(this.GbInSgbMenuItem_Click);
@ -2053,7 +2052,7 @@
this.batchRunnerToolStripMenuItem,
this.ExperimentalToolsSubMenu});
this.ToolsSubMenu.Name = "ToolsSubMenu";
this.ToolsSubMenu.Size = new System.Drawing.Size(47, 19);
this.ToolsSubMenu.Size = new System.Drawing.Size(48, 19);
this.ToolsSubMenu.Text = "&Tools";
this.ToolsSubMenu.DropDownOpened += new System.EventHandler(this.ToolsSubMenu_DropDownOpened);
//
@ -2442,7 +2441,7 @@
//
this.PceControllerSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.GameController;
this.PceControllerSettingsMenuItem.Name = "PceControllerSettingsMenuItem";
this.PceControllerSettingsMenuItem.Size = new System.Drawing.Size(258, 22);
this.PceControllerSettingsMenuItem.Size = new System.Drawing.Size(259, 22);
this.PceControllerSettingsMenuItem.Text = "Controller Settings";
this.PceControllerSettingsMenuItem.Click += new System.EventHandler(this.PceControllerSettingsMenuItem_Click);
//
@ -2450,59 +2449,59 @@
//
this.PCEGraphicsSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.tvIcon;
this.PCEGraphicsSettingsMenuItem.Name = "PCEGraphicsSettingsMenuItem";
this.PCEGraphicsSettingsMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEGraphicsSettingsMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEGraphicsSettingsMenuItem.Text = "Graphics Settings";
this.PCEGraphicsSettingsMenuItem.Click += new System.EventHandler(this.PceGraphicsSettingsMenuItem_Click);
//
// toolStripSeparator32
//
this.toolStripSeparator32.Name = "toolStripSeparator32";
this.toolStripSeparator32.Size = new System.Drawing.Size(255, 6);
this.toolStripSeparator32.Size = new System.Drawing.Size(256, 6);
//
// PCEBGViewerMenuItem
//
this.PCEBGViewerMenuItem.Name = "PCEBGViewerMenuItem";
this.PCEBGViewerMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEBGViewerMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEBGViewerMenuItem.Text = "&BG Viewer";
this.PCEBGViewerMenuItem.Click += new System.EventHandler(this.PceBgViewerMenuItem_Click);
//
// PCEtileViewerToolStripMenuItem
//
this.PCEtileViewerToolStripMenuItem.Name = "PCEtileViewerToolStripMenuItem";
this.PCEtileViewerToolStripMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEtileViewerToolStripMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEtileViewerToolStripMenuItem.Text = "&Tile Viewer";
this.PCEtileViewerToolStripMenuItem.Click += new System.EventHandler(this.PceTileViewerMenuItem_Click);
//
// PceSoundDebuggerToolStripMenuItem
//
this.PceSoundDebuggerToolStripMenuItem.Name = "PceSoundDebuggerToolStripMenuItem";
this.PceSoundDebuggerToolStripMenuItem.Size = new System.Drawing.Size(258, 22);
this.PceSoundDebuggerToolStripMenuItem.Size = new System.Drawing.Size(259, 22);
this.PceSoundDebuggerToolStripMenuItem.Text = "&Sound Debugger";
this.PceSoundDebuggerToolStripMenuItem.Click += new System.EventHandler(this.PceSoundDebuggerMenuItem_Click);
//
// toolStripSeparator25
//
this.toolStripSeparator25.Name = "toolStripSeparator25";
this.toolStripSeparator25.Size = new System.Drawing.Size(255, 6);
this.toolStripSeparator25.Size = new System.Drawing.Size(256, 6);
//
// PCEAlwaysPerformSpriteLimitMenuItem
//
this.PCEAlwaysPerformSpriteLimitMenuItem.Name = "PCEAlwaysPerformSpriteLimitMenuItem";
this.PCEAlwaysPerformSpriteLimitMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEAlwaysPerformSpriteLimitMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEAlwaysPerformSpriteLimitMenuItem.Text = "Always Perform Sprite Limit";
this.PCEAlwaysPerformSpriteLimitMenuItem.Click += new System.EventHandler(this.PCEAlwaysPerformSpriteLimitMenuItem_Click);
//
// PCEAlwaysEqualizeVolumesMenuItem
//
this.PCEAlwaysEqualizeVolumesMenuItem.Name = "PCEAlwaysEqualizeVolumesMenuItem";
this.PCEAlwaysEqualizeVolumesMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEAlwaysEqualizeVolumesMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEAlwaysEqualizeVolumesMenuItem.Text = "Always Equalize Volumes (PCE-CD)";
this.PCEAlwaysEqualizeVolumesMenuItem.Click += new System.EventHandler(this.PCEAlwaysEqualizeVolumesMenuItem_Click);
//
// PCEArcadeCardRewindEnableMenuItem
//
this.PCEArcadeCardRewindEnableMenuItem.Name = "PCEArcadeCardRewindEnableMenuItem";
this.PCEArcadeCardRewindEnableMenuItem.Size = new System.Drawing.Size(258, 22);
this.PCEArcadeCardRewindEnableMenuItem.Size = new System.Drawing.Size(259, 22);
this.PCEArcadeCardRewindEnableMenuItem.Text = "Arcade Card Rewind-Enable Hack";
this.PCEArcadeCardRewindEnableMenuItem.Click += new System.EventHandler(this.PCEArcadeCardRewindEnableMenuItem_Click);
//
@ -2512,7 +2511,7 @@
this.SMSregionToolStripMenuItem,
this.SMSdisplayToolStripMenuItem,
this.SMSControllerToolStripMenuItem,
this.SMStoolStripMenuItem2,
this.SMStoolStripMenuItem2,
this.SMSenableBIOSToolStripMenuItem,
this.SMSEnableFMChipMenuItem,
this.SMSOverclockMenuItem,
@ -2539,7 +2538,7 @@
this.SMSregionKoreaToolStripMenuItem,
this.SMSregionAutoToolStripMenuItem});
this.SMSregionToolStripMenuItem.Name = "SMSregionToolStripMenuItem";
this.SMSregionToolStripMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSregionToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSregionToolStripMenuItem.Text = "Region";
//
// SMSregionExportToolStripMenuItem
@ -2577,7 +2576,7 @@
this.SMSdisplayPalToolStripMenuItem,
this.SMSdisplayAutoToolStripMenuItem});
this.SMSdisplayToolStripMenuItem.Name = "SMSdisplayToolStripMenuItem";
this.SMSdisplayToolStripMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSdisplayToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSdisplayToolStripMenuItem.Text = "Display Type";
//
// SMSdisplayNtscToolStripMenuItem
@ -2587,51 +2586,9 @@
this.SMSdisplayNtscToolStripMenuItem.Text = "NTSC";
this.SMSdisplayNtscToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayNTSC_Click);
//
// SMSControllerToolStripMenuItem
// SMSdisplayPalToolStripMenuItem
//
this.SMSControllerToolStripMenuItem.Name = "SMSControllerToolStripMenuItem";
this.SMSControllerToolStripMenuItem.Text = "&Controller Type";
this.SMSControllerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.SMSControllerStandardToolStripMenuItem,
this.SMSControllerPaddleToolStripMenuItem,
this.SMSControllerLightPhaserToolStripMenuItem,
this.SMSControllerSportsPadToolStripMenuItem,
this.SMSControllerKeyboardToolStripMenuItem});
//
// SMSControllerStandardToolStripMenuItem
//
this.SMSControllerStandardToolStripMenuItem.Name = "SMSControllerStandardToolStripMenuItem";
this.SMSControllerStandardToolStripMenuItem.Text = "Standard";
this.SMSControllerStandardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerStandardToolStripMenuItem_Click);
//
// SMSControllerPaddleToolStripMenuItem
//
this.SMSControllerPaddleToolStripMenuItem.Name = "SMSControllerPaddleToolStripMenuItem";
this.SMSControllerPaddleToolStripMenuItem.Text = "Paddle";
this.SMSControllerPaddleToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerPaddleToolStripMenuItem_Click);
//
// SMSControllerLightPhaserToolStripMenuItem
//
this.SMSControllerLightPhaserToolStripMenuItem.Name = "SMSControllerLightPhaserToolStripMenuItem";
this.SMSControllerLightPhaserToolStripMenuItem.Text = "Light Phaser";
this.SMSControllerLightPhaserToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerLightPhaserToolStripMenuItem_Click);
//
// SMSControllerSportsPadToolStripMenuItem
//
this.SMSControllerSportsPadToolStripMenuItem.Name = "SMSControllerSportsPadToolStripMenuItem";
this.SMSControllerSportsPadToolStripMenuItem.Text = "Sports Pad";
this.SMSControllerSportsPadToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerSportsPadToolStripMenuItem_Click);
//
// SMSControllerKeyboardToolStripMenuItem
//
this.SMSControllerKeyboardToolStripMenuItem.Name = "SMSControllerKeyboardToolStripMenuItem";
this.SMSControllerKeyboardToolStripMenuItem.Text = "Keyboard";
this.SMSControllerKeyboardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerKeyboardToolStripMenuItem_Click);
//
// SMSdisplayPalToolStripMenuItem
//
this.SMSdisplayPalToolStripMenuItem.Name = "SMSdisplayPalToolStripMenuItem";
this.SMSdisplayPalToolStripMenuItem.Name = "SMSdisplayPalToolStripMenuItem";
this.SMSdisplayPalToolStripMenuItem.Size = new System.Drawing.Size(104, 22);
this.SMSdisplayPalToolStripMenuItem.Text = "PAL";
this.SMSdisplayPalToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayPAL_Click);
@ -2643,97 +2600,144 @@
this.SMSdisplayAutoToolStripMenuItem.Text = "Auto";
this.SMSdisplayAutoToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayAuto_Click);
//
// SMSControllerToolStripMenuItem
//
this.SMSControllerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.SMSControllerStandardToolStripMenuItem,
this.SMSControllerPaddleToolStripMenuItem,
this.SMSControllerLightPhaserToolStripMenuItem,
this.SMSControllerSportsPadToolStripMenuItem,
this.SMSControllerKeyboardToolStripMenuItem});
this.SMSControllerToolStripMenuItem.Name = "SMSControllerToolStripMenuItem";
this.SMSControllerToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSControllerToolStripMenuItem.Text = "&Controller Type";
//
// SMSControllerStandardToolStripMenuItem
//
this.SMSControllerStandardToolStripMenuItem.Name = "SMSControllerStandardToolStripMenuItem";
this.SMSControllerStandardToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
this.SMSControllerStandardToolStripMenuItem.Text = "Standard";
this.SMSControllerStandardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerStandardToolStripMenuItem_Click);
//
// SMSControllerPaddleToolStripMenuItem
//
this.SMSControllerPaddleToolStripMenuItem.Name = "SMSControllerPaddleToolStripMenuItem";
this.SMSControllerPaddleToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
this.SMSControllerPaddleToolStripMenuItem.Text = "Paddle";
this.SMSControllerPaddleToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerPaddleToolStripMenuItem_Click);
//
// SMSControllerLightPhaserToolStripMenuItem
//
this.SMSControllerLightPhaserToolStripMenuItem.Name = "SMSControllerLightPhaserToolStripMenuItem";
this.SMSControllerLightPhaserToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
this.SMSControllerLightPhaserToolStripMenuItem.Text = "Light Phaser";
this.SMSControllerLightPhaserToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerLightPhaserToolStripMenuItem_Click);
//
// SMSControllerSportsPadToolStripMenuItem
//
this.SMSControllerSportsPadToolStripMenuItem.Name = "SMSControllerSportsPadToolStripMenuItem";
this.SMSControllerSportsPadToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
this.SMSControllerSportsPadToolStripMenuItem.Text = "Sports Pad";
this.SMSControllerSportsPadToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerSportsPadToolStripMenuItem_Click);
//
// SMSControllerKeyboardToolStripMenuItem
//
this.SMSControllerKeyboardToolStripMenuItem.Name = "SMSControllerKeyboardToolStripMenuItem";
this.SMSControllerKeyboardToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
this.SMSControllerKeyboardToolStripMenuItem.Text = "Keyboard";
this.SMSControllerKeyboardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerKeyboardToolStripMenuItem_Click);
//
// SMStoolStripMenuItem2
//
this.SMStoolStripMenuItem2.Name = "SMStoolStripMenuItem2";
this.SMStoolStripMenuItem2.Size = new System.Drawing.Size(274, 6);
this.SMStoolStripMenuItem2.Size = new System.Drawing.Size(275, 6);
//
// SMSenableBIOSToolStripMenuItem
//
this.SMSenableBIOSToolStripMenuItem.Name = "SMSenableBIOSToolStripMenuItem";
this.SMSenableBIOSToolStripMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSenableBIOSToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSenableBIOSToolStripMenuItem.Text = "Enable BIOS (Must be Enabled for TAS)";
this.SMSenableBIOSToolStripMenuItem.Click += new System.EventHandler(this.SmsBiosMenuItem_Click);
//
// SMSEnableFMChipMenuItem
//
this.SMSEnableFMChipMenuItem.Name = "SMSEnableFMChipMenuItem";
this.SMSEnableFMChipMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSEnableFMChipMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSEnableFMChipMenuItem.Text = "&Enable FM Chip";
this.SMSEnableFMChipMenuItem.Click += new System.EventHandler(this.SmsEnableFmChipMenuItem_Click);
//
// SMSOverclockMenuItem
//
this.SMSOverclockMenuItem.Name = "SMSOverclockMenuItem";
this.SMSOverclockMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSOverclockMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSOverclockMenuItem.Text = "&Overclock when Known Safe";
this.SMSOverclockMenuItem.Click += new System.EventHandler(this.SMSOverclockMenuItem_Click);
//
// SMSForceStereoMenuItem
//
this.SMSForceStereoMenuItem.Name = "SMSForceStereoMenuItem";
this.SMSForceStereoMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSForceStereoMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSForceStereoMenuItem.Text = "&Force Stereo Separation";
this.SMSForceStereoMenuItem.Click += new System.EventHandler(this.SMSForceStereoMenuItem_Click);
//
// SMSSpriteLimitMenuItem
//
this.SMSSpriteLimitMenuItem.Name = "SMSSpriteLimitMenuItem";
this.SMSSpriteLimitMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSSpriteLimitMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSSpriteLimitMenuItem.Text = "Sprite &Limit";
this.SMSSpriteLimitMenuItem.Click += new System.EventHandler(this.SMSSpriteLimitMenuItem_Click);
//
// SMSDisplayOverscanMenuItem
//
this.SMSDisplayOverscanMenuItem.Name = "SMSDisplayOverscanMenuItem";
this.SMSDisplayOverscanMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSDisplayOverscanMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSDisplayOverscanMenuItem.Text = "Display Overscan";
this.SMSDisplayOverscanMenuItem.Click += new System.EventHandler(this.SMSDisplayOverscanMenuItem_Click);
//
// SMSFix3DGameDisplayToolStripMenuItem
//
this.SMSFix3DGameDisplayToolStripMenuItem.Name = "SMSFix3DGameDisplayToolStripMenuItem";
this.SMSFix3DGameDisplayToolStripMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSFix3DGameDisplayToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSFix3DGameDisplayToolStripMenuItem.Text = "Fix 3D Game Display";
this.SMSFix3DGameDisplayToolStripMenuItem.Click += new System.EventHandler(this.SMSFix3DDisplayMenuItem_Click);
//
// ShowClippedRegionsMenuItem
//
this.ShowClippedRegionsMenuItem.Name = "ShowClippedRegionsMenuItem";
this.ShowClippedRegionsMenuItem.Size = new System.Drawing.Size(277, 22);
this.ShowClippedRegionsMenuItem.Size = new System.Drawing.Size(278, 22);
this.ShowClippedRegionsMenuItem.Text = "&Show Clipped Regions";
this.ShowClippedRegionsMenuItem.Click += new System.EventHandler(this.ShowClippedRegionsMenuItem_Click);
//
// HighlightActiveDisplayRegionMenuItem
//
this.HighlightActiveDisplayRegionMenuItem.Name = "HighlightActiveDisplayRegionMenuItem";
this.HighlightActiveDisplayRegionMenuItem.Size = new System.Drawing.Size(277, 22);
this.HighlightActiveDisplayRegionMenuItem.Size = new System.Drawing.Size(278, 22);
this.HighlightActiveDisplayRegionMenuItem.Text = "&Highlight Active Display Region";
this.HighlightActiveDisplayRegionMenuItem.Click += new System.EventHandler(this.HighlightActiveDisplayRegionMenuItem_Click);
//
// SMSGraphicsSettingsMenuItem
//
this.SMSGraphicsSettingsMenuItem.Name = "SMSGraphicsSettingsMenuItem";
this.SMSGraphicsSettingsMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSGraphicsSettingsMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSGraphicsSettingsMenuItem.Text = "&Graphics Settings...";
this.SMSGraphicsSettingsMenuItem.Click += new System.EventHandler(this.SMSGraphicsSettingsMenuItem_Click);
//
// toolStripSeparator24
//
this.toolStripSeparator24.Name = "toolStripSeparator24";
this.toolStripSeparator24.Size = new System.Drawing.Size(274, 6);
this.toolStripSeparator24.Size = new System.Drawing.Size(275, 6);
//
// SMSVDPViewerToolStripMenuItem
//
this.SMSVDPViewerToolStripMenuItem.Name = "SMSVDPViewerToolStripMenuItem";
this.SMSVDPViewerToolStripMenuItem.Size = new System.Drawing.Size(277, 22);
this.SMSVDPViewerToolStripMenuItem.Size = new System.Drawing.Size(278, 22);
this.SMSVDPViewerToolStripMenuItem.Text = "&VDP Viewer";
this.SMSVDPViewerToolStripMenuItem.Click += new System.EventHandler(this.SmsVdpViewerMenuItem_Click);
//
// GGGameGenieMenuItem
//
this.GGGameGenieMenuItem.Name = "GGGameGenieMenuItem";
this.GGGameGenieMenuItem.Size = new System.Drawing.Size(277, 22);
this.GGGameGenieMenuItem.Size = new System.Drawing.Size(278, 22);
this.GGGameGenieMenuItem.Text = "&Game Genie Encoder/Decoder";
this.GGGameGenieMenuItem.Click += new System.EventHandler(this.GGGameGenieMenuItem_Click);
//
@ -2805,7 +2809,7 @@
//
this.A7800SubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.A7800ControllerSettingsMenuItem,
this.A7800FilterSettingsMenuItem});
this.A7800FilterSettingsMenuItem});
this.A7800SubMenu.Name = "A7800SubMenu";
this.A7800SubMenu.Size = new System.Drawing.Size(51, 19);
this.A7800SubMenu.Text = "&A7800";
@ -2814,26 +2818,26 @@
// A7800ControllerSettingsMenuItem
//
this.A7800ControllerSettingsMenuItem.Name = "A7800ControllerSettingsMenuItem";
this.A7800ControllerSettingsMenuItem.Size = new System.Drawing.Size(125, 22);
this.A7800ControllerSettingsMenuItem.Size = new System.Drawing.Size(172, 22);
this.A7800ControllerSettingsMenuItem.Text = "Controller Settings";
this.A7800ControllerSettingsMenuItem.Click += new System.EventHandler(this.A7800ControllerSettingsToolStripMenuItem_Click);
//
// A7800FilterSettingsMenuItem
//
this.A7800FilterSettingsMenuItem.Name = "A7800FilterSettingsMenuItem";
this.A7800FilterSettingsMenuItem.Size = new System.Drawing.Size(125, 22);
this.A7800FilterSettingsMenuItem.Text = "Filter Settings";
this.A7800FilterSettingsMenuItem.Click += new System.EventHandler(this.A7800FilterSettingsToolStripMenuItem_Click);
//
// GBSubMenu
//
this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
//
// A7800FilterSettingsMenuItem
//
this.A7800FilterSettingsMenuItem.Name = "A7800FilterSettingsMenuItem";
this.A7800FilterSettingsMenuItem.Size = new System.Drawing.Size(172, 22);
this.A7800FilterSettingsMenuItem.Text = "Filter Settings";
this.A7800FilterSettingsMenuItem.Click += new System.EventHandler(this.A7800FilterSettingsToolStripMenuItem_Click);
//
// GBSubMenu
//
this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.GBcoreSettingsToolStripMenuItem,
this.LoadGBInSGBMenuItem,
this.toolStripSeparator28,
this.GBGPUViewerMenuItem,
this.GBGameGenieMenuItem,
this.GBPrinterViewerMenuItem});
this.GBPrinterViewerMenuItem});
this.GBSubMenu.Name = "GBSubMenu";
this.GBSubMenu.Size = new System.Drawing.Size(34, 19);
this.GBSubMenu.Text = "&GB";
@ -2871,17 +2875,17 @@
this.GBGameGenieMenuItem.Size = new System.Drawing.Size(233, 22);
this.GBGameGenieMenuItem.Text = "&Game Genie Encoder/Decoder";
this.GBGameGenieMenuItem.Click += new System.EventHandler(this.GBGameGenieMenuItem_Click);
//
// GBPrinterViewerMenuItem
//
this.GBPrinterViewerMenuItem.Name = "GBPrinterViewerMenuItem";
this.GBPrinterViewerMenuItem.Size = new System.Drawing.Size(233, 22);
this.GBPrinterViewerMenuItem.Text = "&Printer Viewer";
this.GBPrinterViewerMenuItem.Click += new System.EventHandler(this.GBPrinterViewerMenuItem_Click);
//
// GBASubMenu
//
this.GBASubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
//
// GBPrinterViewerMenuItem
//
this.GBPrinterViewerMenuItem.Name = "GBPrinterViewerMenuItem";
this.GBPrinterViewerMenuItem.Size = new System.Drawing.Size(233, 22);
this.GBPrinterViewerMenuItem.Text = "&Printer Viewer";
this.GBPrinterViewerMenuItem.Click += new System.EventHandler(this.GBPrinterViewerMenuItem_Click);
//
// GBASubMenu
//
this.GBASubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.GBACoreSelectionSubMenu,
this.GBAcoresettingsToolStripMenuItem1,
this.toolStripSeparator33,
@ -3390,6 +3394,44 @@
this.AboutMenuItem.Text = "&About";
this.AboutMenuItem.Click += new System.EventHandler(this.AboutMenuItem_Click);
//
// zXSpectrumToolStripMenuItem
//
this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.ZXSpectrumCoreEmulationSettingsMenuItem,
this.ZXSpectrumControllerConfigurationMenuItem,
this.ZXSpectrumNonSyncSettingsMenuItem});
this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem";
this.zXSpectrumToolStripMenuItem.Size = new System.Drawing.Size(87, 19);
this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum";
this.zXSpectrumToolStripMenuItem.DropDownOpened += new System.EventHandler(this.zXSpectrumToolStripMenuItem_DropDownOpened);
//
// ZXSpectrumCoreEmulationSettingsMenuItem
//
this.ZXSpectrumCoreEmulationSettingsMenuItem.Name = "ZXSpectrumCoreEmulationSettingsMenuItem";
this.ZXSpectrumCoreEmulationSettingsMenuItem.Size = new System.Drawing.Size(201, 22);
this.ZXSpectrumCoreEmulationSettingsMenuItem.Text = "Core Emulation Settings";
this.ZXSpectrumCoreEmulationSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumCoreEmulationSettingsMenuItem_Click);
//
// ZXSpectrumControllerConfigurationMenuItem
//
this.ZXSpectrumControllerConfigurationMenuItem.Name = "ZXSpectrumControllerConfigurationMenuItem";
this.ZXSpectrumControllerConfigurationMenuItem.Size = new System.Drawing.Size(201, 22);
this.ZXSpectrumControllerConfigurationMenuItem.Text = "Joystick Configuration";
this.ZXSpectrumControllerConfigurationMenuItem.Click += new System.EventHandler(this.ZXSpectrumControllerConfigurationMenuItem_Click);
//
// ZXSpectrumNonSyncSettingsMenuItem
//
this.ZXSpectrumNonSyncSettingsMenuItem.Name = "ZXSpectrumNonSyncSettingsMenuItem";
this.ZXSpectrumNonSyncSettingsMenuItem.Size = new System.Drawing.Size(201, 22);
this.ZXSpectrumNonSyncSettingsMenuItem.Text = "Non-Sync Settings";
this.ZXSpectrumNonSyncSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumNonSyncSettingsMenuItem_Click);
//
// Atari7800HawkCoreMenuItem
//
this.Atari7800HawkCoreMenuItem.Name = "Atari7800HawkCoreMenuItem";
this.Atari7800HawkCoreMenuItem.Size = new System.Drawing.Size(153, 22);
this.Atari7800HawkCoreMenuItem.Text = "Atari7800Hawk";
//
// MainStatusBar
//
this.MainStatusBar.ClickThrough = true;
@ -4474,5 +4516,9 @@
private System.Windows.Forms.ToolStripMenuItem SMSControllerLightPhaserToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem SMSControllerSportsPadToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem SMSControllerKeyboardToolStripMenuItem;
}
private System.Windows.Forms.ToolStripMenuItem zXSpectrumToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ZXSpectrumControllerConfigurationMenuItem;
private System.Windows.Forms.ToolStripMenuItem ZXSpectrumCoreEmulationSettingsMenuItem;
private System.Windows.Forms.ToolStripMenuItem ZXSpectrumNonSyncSettingsMenuItem;
}
}

View File

@ -2451,11 +2451,42 @@ namespace BizHawk.Client.EmuHawk
new IntvControllerSettings().ShowDialog();
}
#endregion
#endregion
#region Help
#region ZXSpectrum
private void HelpSubMenu_DropDownOpened(object sender, EventArgs e)
private void zXSpectrumToolStripMenuItem_DropDownOpened(object sender, EventArgs e)
{
}
private void preferencesToolStripMenuItem4_Click(object sender, EventArgs e)
{
GenericCoreConfig.DoDialog(this, "ZXSpectrum Settings");
}
private void ZXSpectrumControllerConfigurationMenuItem_Click(object sender, EventArgs e)
{
new ZXSpectrumJoystickSettings().ShowDialog();
}
private void ZXSpectrumCoreEmulationSettingsMenuItem_Click(object sender, EventArgs e)
{
new ZXSpectrumCoreEmulationSettings().ShowDialog();
}
private void ZXSpectrumNonSyncSettingsMenuItem_Click(object sender, EventArgs e)
{
new ZXSpectrumNonSyncSettings().ShowDialog();
}
#endregion
#region Help
private void HelpSubMenu_DropDownOpened(object sender, EventArgs e)
{
FeaturesMenuItem.Visible = VersionInfo.DeveloperBuild;
}

View File

@ -1719,6 +1719,7 @@ namespace BizHawk.Client.EmuHawk
sNESToolStripMenuItem.Visible = false;
neoGeoPocketToolStripMenuItem.Visible = false;
pCFXToolStripMenuItem.Visible = false;
zXSpectrumToolStripMenuItem.Visible = false;
switch (system)
{
@ -1816,6 +1817,9 @@ namespace BizHawk.Client.EmuHawk
case "PCFX":
pCFXToolStripMenuItem.Visible = true;
break;
case "ZXSpectrum":
zXSpectrumToolStripMenuItem.Visible = true;
break;
}
}
@ -2077,7 +2081,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;%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;%ARCH%",
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
@ -2105,6 +2109,7 @@ namespace BizHawk.Client.EmuHawk
"Apple II", "*.dsk;*.do;*.po;%ARCH%",
"Virtual Boy", "*.vb;%ARCH%",
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
"Sinclair ZX Spectrum", "*.tzx;*.tap;%ARCH%",
"All Files", "*.*");
}
@ -4294,7 +4299,7 @@ namespace BizHawk.Client.EmuHawk
GenericCoreConfig.DoDialog(this, "PC-FX Settings");
}
private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording)
private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording)
{
var isRewinding = false;

View File

@ -489,7 +489,7 @@ namespace BizHawk.Client.EmuHawk.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -3438,5 +3438,15 @@ namespace BizHawk.Client.EmuHawk.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ZXSpectrumKeyboards {
get {
object obj = ResourceManager.GetObject("ZXSpectrumKeyboards", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@ -1557,4 +1557,7 @@
<data name="NGPController" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\images\ControllerImages\NGPController.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>
<data name="ZXSpectrumKeyboards" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\config\controllerimages\zxspectrumkeyboards.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -172,15 +172,24 @@ namespace BizHawk.Client.EmuHawk
string tabname = cat.Key;
tt.TabPages.Add(tabname);
tt.TabPages[pageidx].Controls.Add(createpanel(settings, cat.Value, tt.Size));
}
// zxhawk hack - it uses multiple categoryLabels
if (Global.Emulator.SystemId == "ZXSpectrum")
pageidx++;
}
if (buckets[0].Count > 0)
{
string tabname = Global.Emulator.SystemId == "C64" ? "Keyboard" : "Console"; // hack
tt.TabPages.Add(tabname);
tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size));
}
}
// ZXHawk needs to skip this bit
if (Global.Emulator.SystemId == "ZXSpectrum")
return;
string tabname = (Global.Emulator.SystemId == "C64") ? "Keyboard" : "Console"; // hack
tt.TabPages.Add(tabname);
tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size));
}
}
}
public ControllerConfig(ControllerDefinition def)
@ -256,6 +265,13 @@ namespace BizHawk.Client.EmuHawk
pictureBox2.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
}
if (controlName == "ZXSpectrum 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -52,6 +52,7 @@ namespace BizHawk.Client.EmuHawk
{ "GBC", "Game Boy Color" },
{ "PCFX", "PC-FX" },
{ "32X", "32X" },
{ "ZXSpectrum", "ZX Spectrum" }
};
public string TargetSystem = null;

View File

@ -0,0 +1,187 @@
namespace BizHawk.Client.EmuHawk
{
partial class ZXSpectrumCoreEmulationSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumCoreEmulationSettings));
this.OkBtn = new System.Windows.Forms.Button();
this.CancelBtn = new System.Windows.Forms.Button();
this.label4 = new System.Windows.Forms.Label();
this.MachineSelectionComboBox = new System.Windows.Forms.ComboBox();
this.label1 = new System.Windows.Forms.Label();
this.lblMachineNotes = new System.Windows.Forms.Label();
this.determEmucheckBox1 = new System.Windows.Forms.CheckBox();
this.label2 = new System.Windows.Forms.Label();
this.borderTypecomboBox1 = new System.Windows.Forms.ComboBox();
this.lblBorderInfo = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// OkBtn
//
this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OkBtn.Location = new System.Drawing.Point(247, 393);
this.OkBtn.Name = "OkBtn";
this.OkBtn.Size = new System.Drawing.Size(60, 23);
this.OkBtn.TabIndex = 3;
this.OkBtn.Text = "&OK";
this.OkBtn.UseVisualStyleBackColor = true;
this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click);
//
// CancelBtn
//
this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelBtn.Location = new System.Drawing.Point(313, 393);
this.CancelBtn.Name = "CancelBtn";
this.CancelBtn.Size = new System.Drawing.Size(60, 23);
this.CancelBtn.TabIndex = 4;
this.CancelBtn.Text = "&Cancel";
this.CancelBtn.UseVisualStyleBackColor = true;
this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click);
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(12, 46);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(98, 13);
this.label4.TabIndex = 15;
this.label4.Text = "Emulated Machine:";
//
// MachineSelectionComboBox
//
this.MachineSelectionComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.MachineSelectionComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.MachineSelectionComboBox.FormattingEnabled = true;
this.MachineSelectionComboBox.Location = new System.Drawing.Point(12, 62);
this.MachineSelectionComboBox.Name = "MachineSelectionComboBox";
this.MachineSelectionComboBox.Size = new System.Drawing.Size(361, 21);
this.MachineSelectionComboBox.TabIndex = 13;
this.MachineSelectionComboBox.SelectionChangeCommitted += new System.EventHandler(this.MachineSelectionComboBox_SelectionChangeCommitted);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 14);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(159, 13);
this.label1.TabIndex = 17;
this.label1.Text = "ZX Spectrum Emulation Settings";
//
// lblMachineNotes
//
this.lblMachineNotes.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblMachineNotes.Location = new System.Drawing.Point(15, 95);
this.lblMachineNotes.Name = "lblMachineNotes";
this.lblMachineNotes.Size = new System.Drawing.Size(358, 204);
this.lblMachineNotes.TabIndex = 20;
this.lblMachineNotes.Text = "null\r\n";
//
// determEmucheckBox1
//
this.determEmucheckBox1.AutoSize = true;
this.determEmucheckBox1.Location = new System.Drawing.Point(15, 302);
this.determEmucheckBox1.Name = "determEmucheckBox1";
this.determEmucheckBox1.Size = new System.Drawing.Size(135, 17);
this.determEmucheckBox1.TabIndex = 21;
this.determEmucheckBox1.Text = "Deterministic Emulation";
this.determEmucheckBox1.UseVisualStyleBackColor = true;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 335);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(118, 13);
this.label2.TabIndex = 23;
this.label2.Text = "Rendered Border Type:";
//
// borderTypecomboBox1
//
this.borderTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.borderTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.borderTypecomboBox1.FormattingEnabled = true;
this.borderTypecomboBox1.Location = new System.Drawing.Point(12, 351);
this.borderTypecomboBox1.Name = "borderTypecomboBox1";
this.borderTypecomboBox1.Size = new System.Drawing.Size(157, 21);
this.borderTypecomboBox1.TabIndex = 22;
this.borderTypecomboBox1.SelectedIndexChanged += new System.EventHandler(this.borderTypecomboBox1_SelectedIndexChanged);
//
// lblBorderInfo
//
this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblBorderInfo.Location = new System.Drawing.Point(175, 351);
this.lblBorderInfo.Name = "lblBorderInfo";
this.lblBorderInfo.Size = new System.Drawing.Size(196, 21);
this.lblBorderInfo.TabIndex = 24;
this.lblBorderInfo.Text = "null";
this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// ZXSpectrumCoreEmulationSettings
//
this.AcceptButton = this.OkBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelBtn;
this.ClientSize = new System.Drawing.Size(385, 428);
this.Controls.Add(this.lblBorderInfo);
this.Controls.Add(this.label2);
this.Controls.Add(this.borderTypecomboBox1);
this.Controls.Add(this.determEmucheckBox1);
this.Controls.Add(this.lblMachineNotes);
this.Controls.Add(this.label1);
this.Controls.Add(this.label4);
this.Controls.Add(this.MachineSelectionComboBox);
this.Controls.Add(this.CancelBtn);
this.Controls.Add(this.OkBtn);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ZXSpectrumCoreEmulationSettings";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Core Emulation Settings";
this.Load += new System.EventHandler(this.IntvControllerSettings_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button OkBtn;
private System.Windows.Forms.Button CancelBtn;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.ComboBox MachineSelectionComboBox;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label lblMachineNotes;
private System.Windows.Forms.CheckBox determEmucheckBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox borderTypecomboBox1;
private System.Windows.Forms.Label lblBorderInfo;
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Linq;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
using System.Text;
namespace BizHawk.Client.EmuHawk
{
public partial class ZXSpectrumCoreEmulationSettings : Form
{
private ZXSpectrum.ZXSpectrumSyncSettings _syncSettings;
public ZXSpectrumCoreEmulationSettings()
{
InitializeComponent();
}
private void IntvControllerSettings_Load(object sender, EventArgs e)
{
_syncSettings = ((ZXSpectrum)Global.Emulator).GetSyncSettings().Clone();
// machine selection
var machineTypes = Enum.GetNames(typeof(MachineType));
foreach (var val in machineTypes)
{
MachineSelectionComboBox.Items.Add(val);
}
MachineSelectionComboBox.SelectedItem = _syncSettings.MachineType.ToString();
UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString()));
// border selecton
var borderTypes = Enum.GetNames(typeof(ZXSpectrum.BorderType));
foreach (var val in borderTypes)
{
borderTypecomboBox1.Items.Add(val);
}
borderTypecomboBox1.SelectedItem = _syncSettings.BorderType.ToString();
UpdateBorderNotes((ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), borderTypecomboBox1.SelectedItem.ToString()));
// deterministic emulation
determEmucheckBox1.Checked = _syncSettings.DeterministicEmulation;
}
private void OkBtn_Click(object sender, EventArgs e)
{
bool changed =
_syncSettings.MachineType.ToString() != MachineSelectionComboBox.SelectedItem.ToString()
|| _syncSettings.BorderType.ToString() != borderTypecomboBox1.SelectedItem.ToString()
|| _syncSettings.DeterministicEmulation != determEmucheckBox1.Checked;
if (changed)
{
_syncSettings.MachineType = (MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString());
_syncSettings.BorderType = (ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), borderTypecomboBox1.SelectedItem.ToString());
_syncSettings.DeterministicEmulation = determEmucheckBox1.Checked;
GlobalWin.MainForm.PutCoreSyncSettings(_syncSettings);
DialogResult = DialogResult.OK;
Close();
}
else
{
DialogResult = DialogResult.OK;
Close();
}
}
private void CancelBtn_Click(object sender, EventArgs e)
{
GlobalWin.OSD.AddMessage("Core emulator settings aborted");
DialogResult = DialogResult.Cancel;
Close();
}
private void MachineSelectionComboBox_SelectionChangeCommitted(object sender, EventArgs e)
{
ComboBox cb = sender as ComboBox;
UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), cb.SelectedItem.ToString()));
}
private void UpdateMachineNotes(MachineType type)
{
lblMachineNotes.Text = ZXMachineMetaData.GetMetaString(type);
}
private void borderTypecomboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cb = sender as ComboBox;
UpdateBorderNotes((ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), cb.SelectedItem.ToString()));
}
private void UpdateBorderNotes(ZXSpectrum.BorderType type)
{
switch (type)
{
case ZXSpectrum.BorderType.Full:
lblBorderInfo.Text = "Original border sizes";
break;
case ZXSpectrum.BorderType.Medium:
lblBorderInfo.Text = "All borders 24px";
break;
case ZXSpectrum.BorderType.None:
lblBorderInfo.Text = "No border at all";
break;
case ZXSpectrum.BorderType.Small:
lblBorderInfo.Text = "All borders 10px";
break;
case ZXSpectrum.BorderType.Widescreen:
lblBorderInfo.Text = "No top and bottom border (almost 16:9)";
break;
}
}
}
}

View File

@ -0,0 +1,624 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA
BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ
AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm
AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA
AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP//
/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA
AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw
AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA
AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ
AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA
AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE
AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3
dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH
x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI
cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI
h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA
AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH
eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA
AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC
AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/
AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA
AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA
AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA
AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI
h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA
yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA
AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB
/AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA//////////////////////////
//8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA
AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA
d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI
yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH
d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/
/wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP//
/wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA
AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI
iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA
AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP//
AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8
PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF
RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU
VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP
UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ
WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s
awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr
agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4
dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf
TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5
+gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC
ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh
oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP
kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj
jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk
owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1
swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr
9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme
VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE
AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc
XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55
eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg
YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51
dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz
dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz
dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM
5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO
jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A
gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud
iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc
mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer
qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv
rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2
tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV
3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa
mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc
tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA
AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882
AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP
z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb
orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR
l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH
///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA
AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA
AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2
dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15
eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+
fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek
VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P
jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK
iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ
mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi
oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8
ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf
8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA
AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy
NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA
PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM
mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ
hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv
YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP//
/wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA
BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA
AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw
cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K
igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS
kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay
sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS
0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA
AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb
Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5
AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA
AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS
U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP
T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY
V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw
cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4
uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA
AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6
ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8
vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA
ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT
kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck
pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6
OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk
ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br
auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0
c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg
n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA
AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA
vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg
nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA
AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO
zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv
rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH
RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF
RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+
vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB
vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX
V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i
pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/
vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv
L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z
sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9
uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e
nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6
t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV
lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6
u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC
gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej
hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y
sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T
k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn
JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC
QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK
StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/
QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+
PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L
S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ
SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ
Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2
NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km
Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af////
AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA
B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA
AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA
AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB//
AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP//
/////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA
AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q
av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw
cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1
dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4
ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+
Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA
AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA
AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc
HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A
f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z
sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui
of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP
z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB
v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8
vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ
x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O
Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK
yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz
dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc
9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI
zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw
t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il
o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX
V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc
XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6
OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD
///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4
D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA
ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv
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
</value>
</data>
</root>

View File

@ -0,0 +1,184 @@
namespace BizHawk.Client.EmuHawk
{
partial class ZXSpectrumJoystickSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumJoystickSettings));
this.OkBtn = new System.Windows.Forms.Button();
this.CancelBtn = new System.Windows.Forms.Button();
this.label5 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.Port2ComboBox = new System.Windows.Forms.ComboBox();
this.Port1ComboBox = new System.Windows.Forms.ComboBox();
this.label1 = new System.Windows.Forms.Label();
this.Port3ComboBox = new System.Windows.Forms.ComboBox();
this.label2 = new System.Windows.Forms.Label();
this.lblDoubleSize = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// OkBtn
//
this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OkBtn.Location = new System.Drawing.Point(170, 312);
this.OkBtn.Name = "OkBtn";
this.OkBtn.Size = new System.Drawing.Size(60, 23);
this.OkBtn.TabIndex = 3;
this.OkBtn.Text = "&OK";
this.OkBtn.UseVisualStyleBackColor = true;
this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click);
//
// CancelBtn
//
this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelBtn.Location = new System.Drawing.Point(236, 312);
this.CancelBtn.Name = "CancelBtn";
this.CancelBtn.Size = new System.Drawing.Size(60, 23);
this.CancelBtn.TabIndex = 4;
this.CancelBtn.Text = "&Cancel";
this.CancelBtn.UseVisualStyleBackColor = true;
this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click);
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(9, 207);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(57, 13);
this.label5.TabIndex = 16;
this.label5.Text = "Joystick 2:";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(12, 157);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(57, 13);
this.label4.TabIndex = 15;
this.label4.Text = "Joystick 1:";
//
// Port2ComboBox
//
this.Port2ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Port2ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.Port2ComboBox.FormattingEnabled = true;
this.Port2ComboBox.Location = new System.Drawing.Point(12, 223);
this.Port2ComboBox.Name = "Port2ComboBox";
this.Port2ComboBox.Size = new System.Drawing.Size(284, 21);
this.Port2ComboBox.TabIndex = 14;
//
// Port1ComboBox
//
this.Port1ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Port1ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.Port1ComboBox.FormattingEnabled = true;
this.Port1ComboBox.Location = new System.Drawing.Point(12, 173);
this.Port1ComboBox.Name = "Port1ComboBox";
this.Port1ComboBox.Size = new System.Drawing.Size(284, 21);
this.Port1ComboBox.TabIndex = 13;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 14);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(151, 13);
this.label1.TabIndex = 17;
this.label1.Text = "ZX Spectrum Joystick Settings";
//
// Port3ComboBox
//
this.Port3ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Port3ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.Port3ComboBox.FormattingEnabled = true;
this.Port3ComboBox.Location = new System.Drawing.Point(12, 275);
this.Port3ComboBox.Name = "Port3ComboBox";
this.Port3ComboBox.Size = new System.Drawing.Size(284, 21);
this.Port3ComboBox.TabIndex = 18;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 259);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(57, 13);
this.label2.TabIndex = 19;
this.label2.Text = "Joystick 3:";
//
// lblDoubleSize
//
this.lblDoubleSize.Location = new System.Drawing.Point(26, 40);
this.lblDoubleSize.Name = "lblDoubleSize";
this.lblDoubleSize.Size = new System.Drawing.Size(254, 117);
this.lblDoubleSize.TabIndex = 20;
this.lblDoubleSize.Text = resources.GetString("lblDoubleSize.Text");
//
// ZXSpectrumJoystickSettings
//
this.AcceptButton = this.OkBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelBtn;
this.ClientSize = new System.Drawing.Size(308, 347);
this.Controls.Add(this.lblDoubleSize);
this.Controls.Add(this.label2);
this.Controls.Add(this.Port3ComboBox);
this.Controls.Add(this.label1);
this.Controls.Add(this.label5);
this.Controls.Add(this.label4);
this.Controls.Add(this.Port2ComboBox);
this.Controls.Add(this.Port1ComboBox);
this.Controls.Add(this.CancelBtn);
this.Controls.Add(this.OkBtn);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ZXSpectrumJoystickSettings";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Joystick Settings";
this.Load += new System.EventHandler(this.IntvControllerSettings_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button OkBtn;
private System.Windows.Forms.Button CancelBtn;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.ComboBox Port2ComboBox;
private System.Windows.Forms.ComboBox Port1ComboBox;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ComboBox Port3ComboBox;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label lblDoubleSize;
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Linq;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
namespace BizHawk.Client.EmuHawk
{
public partial class ZXSpectrumJoystickSettings : Form
{
private ZXSpectrum.ZXSpectrumSyncSettings _syncSettings;
public ZXSpectrumJoystickSettings()
{
InitializeComponent();
}
private string[] possibleControllers;
private void IntvControllerSettings_Load(object sender, EventArgs e)
{
_syncSettings = ((ZXSpectrum)Global.Emulator).GetSyncSettings().Clone();
possibleControllers = Enum.GetNames(typeof(JoystickType));
foreach (var val in possibleControllers)
{
Port1ComboBox.Items.Add(val);
Port2ComboBox.Items.Add(val);
Port3ComboBox.Items.Add(val);
}
Port1ComboBox.SelectedItem = _syncSettings.JoystickType1.ToString();
Port2ComboBox.SelectedItem = _syncSettings.JoystickType2.ToString();
Port3ComboBox.SelectedItem = _syncSettings.JoystickType3.ToString();
}
private void OkBtn_Click(object sender, EventArgs e)
{
bool changed =
_syncSettings.JoystickType1.ToString() != Port1ComboBox.SelectedItem.ToString()
|| _syncSettings.JoystickType2.ToString() != Port2ComboBox.SelectedItem.ToString()
|| _syncSettings.JoystickType3.ToString() != Port3ComboBox.SelectedItem.ToString();
if (changed)
{
// enforce unique joystick selection
bool selectionValid = true;
var j1 = Port1ComboBox.SelectedItem.ToString();
if (j1 != possibleControllers.First())
{
if (j1 == Port2ComboBox.SelectedItem.ToString())
{
Port2ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
if (j1 == Port3ComboBox.SelectedItem.ToString())
{
Port3ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
}
var j2 = Port2ComboBox.SelectedItem.ToString();
if (j2 != possibleControllers.First())
{
if (j2 == Port1ComboBox.SelectedItem.ToString())
{
Port1ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
if (j2 == Port3ComboBox.SelectedItem.ToString())
{
Port3ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
}
var j3 = Port3ComboBox.SelectedItem.ToString();
if (j3 != possibleControllers.First())
{
if (j3 == Port1ComboBox.SelectedItem.ToString())
{
Port1ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
if (j3 == Port2ComboBox.SelectedItem.ToString())
{
Port2ComboBox.SelectedItem = possibleControllers.First();
selectionValid = false;
}
}
if (selectionValid)
{
_syncSettings.JoystickType1 = (JoystickType)Enum.Parse(typeof(JoystickType), Port1ComboBox.SelectedItem.ToString());
_syncSettings.JoystickType2 = (JoystickType)Enum.Parse(typeof(JoystickType), Port2ComboBox.SelectedItem.ToString());
_syncSettings.JoystickType3 = (JoystickType)Enum.Parse(typeof(JoystickType), Port3ComboBox.SelectedItem.ToString());
GlobalWin.MainForm.PutCoreSyncSettings(_syncSettings);
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show("Invalid joystick configuration. \nDuplicates have automatically been changed to NULL.\n\nPlease review the configuration");
}
}
else
{
DialogResult = DialogResult.OK;
Close();
}
}
private void CancelBtn_Click(object sender, EventArgs e)
{
GlobalWin.OSD.AddMessage("Joystick settings aborted");
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@ -0,0 +1,630 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="lblDoubleSize.Text" xml:space="preserve">
<value>ZXHawk is set up to allow 3 different unique joysticks to be attached at one time.
This is because the Kempston joystick had to be attached via a Kempton interface plugged into the single expansion port. The Sinclair and Cursor joysticks effectively mapped to different key presses on the keyboard.
</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA
BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ
AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm
AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA
AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP//
/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA
AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw
AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA
AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ
AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA
AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE
AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3
dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH
x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI
cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI
h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA
AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH
eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA
AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC
AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/
AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA
AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA
AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA
AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI
h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA
yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA
AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB
/AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA//////////////////////////
//8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA
AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA
d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI
yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH
d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/
/wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP//
/wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA
AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI
iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA
AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP//
AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8
PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF
RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU
VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP
UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ
WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s
awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr
agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4
dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf
TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5
+gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC
ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh
oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP
kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj
jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk
owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1
swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr
9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme
VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE
AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc
XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55
eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg
YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51
dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz
dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz
dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM
5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO
jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A
gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud
iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc
mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer
qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv
rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2
tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV
3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa
mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc
tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA
AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882
AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP
z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb
orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR
l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH
///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA
AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA
AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2
dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15
eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+
fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek
VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P
jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK
iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ
mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi
oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8
ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf
8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA
AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy
NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA
PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM
mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ
hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv
YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP//
/wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA
BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA
AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw
cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K
igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS
kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay
sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS
0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA
AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb
Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5
AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA
AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS
U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP
T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY
V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw
cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4
uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA
AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6
ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8
vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA
ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT
kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck
pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6
OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk
ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br
auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0
c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg
n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA
AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA
vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg
nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA
AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO
zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv
rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH
RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF
RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+
vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB
vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX
V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i
pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/
vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv
L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z
sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9
uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e
nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6
t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV
lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6
u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC
gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej
hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y
sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T
k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn
JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC
QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK
StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/
QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+
PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L
S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ
SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ
Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2
NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km
Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af////
AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA
B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA
AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA
AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB//
AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP//
/////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA
AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q
av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw
cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1
dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4
ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+
Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA
AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA
AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc
HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A
f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z
sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui
of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP
z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB
v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8
vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ
x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O
Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK
yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz
dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc
9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI
zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw
t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il
o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX
V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc
XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6
OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD
///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4
D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA
ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv
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
</value>
</data>
</root>

View File

@ -0,0 +1,162 @@
namespace BizHawk.Client.EmuHawk
{
partial class ZXSpectrumNonSyncSettings
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumNonSyncSettings));
this.OkBtn = new System.Windows.Forms.Button();
this.CancelBtn = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.autoLoadcheckBox1 = new System.Windows.Forms.CheckBox();
this.label2 = new System.Windows.Forms.Label();
this.panTypecomboBox1 = new System.Windows.Forms.ComboBox();
this.lblBorderInfo = new System.Windows.Forms.Label();
this.lblAutoLoadText = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// OkBtn
//
this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OkBtn.Location = new System.Drawing.Point(247, 158);
this.OkBtn.Name = "OkBtn";
this.OkBtn.Size = new System.Drawing.Size(60, 23);
this.OkBtn.TabIndex = 3;
this.OkBtn.Text = "&OK";
this.OkBtn.UseVisualStyleBackColor = true;
this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click);
//
// CancelBtn
//
this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelBtn.Location = new System.Drawing.Point(313, 158);
this.CancelBtn.Name = "CancelBtn";
this.CancelBtn.Size = new System.Drawing.Size(60, 23);
this.CancelBtn.TabIndex = 4;
this.CancelBtn.Text = "&Cancel";
this.CancelBtn.UseVisualStyleBackColor = true;
this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 14);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(185, 13);
this.label1.TabIndex = 17;
this.label1.Text = "ZX Spectrum Misc Non-Sync Settings";
//
// autoLoadcheckBox1
//
this.autoLoadcheckBox1.AutoSize = true;
this.autoLoadcheckBox1.Location = new System.Drawing.Point(15, 52);
this.autoLoadcheckBox1.Name = "autoLoadcheckBox1";
this.autoLoadcheckBox1.Size = new System.Drawing.Size(103, 17);
this.autoLoadcheckBox1.TabIndex = 21;
this.autoLoadcheckBox1.Text = "Auto-Load Tape";
this.autoLoadcheckBox1.UseVisualStyleBackColor = true;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 97);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(135, 13);
this.label2.TabIndex = 23;
this.label2.Text = "AY-3-8912 Panning Config:";
//
// panTypecomboBox1
//
this.panTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.panTypecomboBox1.FormattingEnabled = true;
this.panTypecomboBox1.Location = new System.Drawing.Point(12, 113);
this.panTypecomboBox1.Name = "panTypecomboBox1";
this.panTypecomboBox1.Size = new System.Drawing.Size(157, 21);
this.panTypecomboBox1.TabIndex = 22;
//
// lblBorderInfo
//
this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblBorderInfo.Location = new System.Drawing.Point(175, 106);
this.lblBorderInfo.Name = "lblBorderInfo";
this.lblBorderInfo.Size = new System.Drawing.Size(196, 37);
this.lblBorderInfo.TabIndex = 24;
this.lblBorderInfo.Text = "Selects a particular panning configuration for the 3ch AY-3-8912 Programmable Sou" +
"nd Generator (128K models only)";
this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lblAutoLoadText
//
this.lblAutoLoadText.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblAutoLoadText.Location = new System.Drawing.Point(175, 46);
this.lblAutoLoadText.Name = "lblAutoLoadText";
this.lblAutoLoadText.Size = new System.Drawing.Size(196, 30);
this.lblAutoLoadText.TabIndex = 25;
this.lblAutoLoadText.Text = "When enabled ZXHawk will attempt to control the tape device automatically when th" +
"e correct traps are detected";
this.lblAutoLoadText.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// ZXSpectrumNonSyncSettings
//
this.AcceptButton = this.OkBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelBtn;
this.ClientSize = new System.Drawing.Size(385, 193);
this.Controls.Add(this.lblAutoLoadText);
this.Controls.Add(this.lblBorderInfo);
this.Controls.Add(this.label2);
this.Controls.Add(this.panTypecomboBox1);
this.Controls.Add(this.autoLoadcheckBox1);
this.Controls.Add(this.label1);
this.Controls.Add(this.CancelBtn);
this.Controls.Add(this.OkBtn);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ZXSpectrumNonSyncSettings";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Other Non-Sync Settings";
this.Load += new System.EventHandler(this.IntvControllerSettings_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button OkBtn;
private System.Windows.Forms.Button CancelBtn;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox autoLoadcheckBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox panTypecomboBox1;
private System.Windows.Forms.Label lblBorderInfo;
private System.Windows.Forms.Label lblAutoLoadText;
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Linq;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
using System.Text;
namespace BizHawk.Client.EmuHawk
{
public partial class ZXSpectrumNonSyncSettings : Form
{
private ZXSpectrum.ZXSpectrumSettings _settings;
public ZXSpectrumNonSyncSettings()
{
InitializeComponent();
}
private void IntvControllerSettings_Load(object sender, EventArgs e)
{
_settings = ((ZXSpectrum)Global.Emulator).GetSettings().Clone();
// autoload tape
autoLoadcheckBox1.Checked = _settings.AutoLoadTape;
// AY panning config
var panTypes = Enum.GetNames(typeof(AYChip.AYPanConfig));
foreach (var val in panTypes)
{
panTypecomboBox1.Items.Add(val);
}
panTypecomboBox1.SelectedItem = _settings.AYPanConfig.ToString();
}
private void OkBtn_Click(object sender, EventArgs e)
{
bool changed =
_settings.AutoLoadTape != autoLoadcheckBox1.Checked
|| _settings.AYPanConfig.ToString() != panTypecomboBox1.SelectedItem.ToString();
if (changed)
{
_settings.AutoLoadTape = autoLoadcheckBox1.Checked;
_settings.AYPanConfig = (AYChip.AYPanConfig)Enum.Parse(typeof(AYChip.AYPanConfig), panTypecomboBox1.SelectedItem.ToString());
GlobalWin.MainForm.PutCoreSettings(_settings);
DialogResult = DialogResult.OK;
Close();
}
else
{
DialogResult = DialogResult.OK;
Close();
}
}
private void CancelBtn_Click(object sender, EventArgs e)
{
GlobalWin.OSD.AddMessage("Misc settings aborted");
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@ -0,0 +1,624 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA
BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ
AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm
AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA
AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP//
/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA
AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw
AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA
AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ
AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA
AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE
AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3
dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH
x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI
cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI
h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA
AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH
eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA
AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC
AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/
AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA
AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA
AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA
AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI
h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA
yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA
AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB
/AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA//////////////////////////
//8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA
AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA
d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI
yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH
d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/
/wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP//
/wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA
AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI
iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA
AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP//
AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8
PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF
RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU
VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP
UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ
WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s
awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr
agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4
dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf
TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5
+gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC
ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh
oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP
kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj
jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk
owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1
swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr
9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme
VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA////////
AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A
H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP////
AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA
AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/
AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE
AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc
XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55
eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg
YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51
dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz
dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz
dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM
5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO
jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A
gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud
iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc
mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer
qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv
rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2
tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV
3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa
mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc
tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA
AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882
AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP
z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb
orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR
l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH
///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA
AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA
AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2
dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15
eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+
fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek
VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P
jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK
iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ
mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi
oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8
ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf
8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA
AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy
NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA
PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM
mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ
hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv
YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP//
/wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA
BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA
AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw
cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K
igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS
kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay
sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS
0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA
AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb
Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5
AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA
AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS
U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP
T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY
V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw
cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4
uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA
AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6
ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8
vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA
ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT
kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck
pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6
OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk
ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br
auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0
c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg
n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA
AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA
vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg
nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA
AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO
zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv
rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH
RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF
RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+
vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB
vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX
V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i
pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/
vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv
L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z
sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9
uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e
nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6
t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV
lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6
u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC
gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej
hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y
sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T
k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn
JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC
QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK
StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/
QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+
PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L
S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ
SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ
Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2
NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km
Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP//
/////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af////
AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA
B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA
AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA
AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB//
AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP//
/////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA
AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q
av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw
cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1
dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4
ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+
Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA
AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA
AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc
HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A
f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z
sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui
of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP
z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB
v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8
vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ
x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O
Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK
yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz
dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc
9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI
zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw
t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il
o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX
V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc
XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6
OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD
///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4
D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA
ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv
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
</value>
</data>
</root>

View File

@ -143,7 +143,8 @@
"GB",
"PCFX",
"PSX",
"SAT"});
"SAT",
"ZXSpectrum"});
this.SystemDropDown.Location = new System.Drawing.Point(425, 75);
this.SystemDropDown.Name = "SystemDropDown";
this.SystemDropDown.Size = new System.Drawing.Size(69, 21);

View File

@ -0,0 +1,260 @@
using System.Collections.Generic;
using System.Drawing;
using BizHawk.Emulation.Common;
using System.Linq;
namespace BizHawk.Client.EmuHawk
{
[Schema("ZXSpectrum")]
class ZXSpectrumSchema : IVirtualPadSchema
{
public IEnumerable<PadSchema> GetPadSchemas(IEmulator core)
{
yield return Joystick(1);
yield return Joystick(2);
yield return Joystick(3);
yield return Keyboard();
//yield return TapeDevice();
}
private static PadSchema Joystick(int controller)
{
return new PadSchema
{
DisplayName = "Joystick " + controller,
IsConsole = false,
DefaultSize = new Size(174, 74),
MaxSize = new Size(174, 74),
Buttons = new[]
{
new PadSchema.ButtonSchema
{
Name = "P" + controller + " Up",
DisplayName = "",
Icon = Properties.Resources.BlueUp,
Location = new Point(23, 15),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "P" + controller + " Down",
DisplayName = "",
Icon = Properties.Resources.BlueDown,
Location = new Point(23, 36),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "P" + controller + " Left",
DisplayName = "",
Icon = Properties.Resources.Back,
Location = new Point(2, 24),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "P" + controller + " Right",
DisplayName = "",
Icon = Properties.Resources.Forward,
Location = new Point(44, 24),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "P" + controller + " Button",
DisplayName = "B",
Location = new Point(124, 24),
Type = PadSchema.PadInputType.Boolean
}
}
};
}
private class ButtonLayout
{
public string Name { get; set; }
public string DisName { get; set; }
public double WidthFactor { get; set; }
public int Row { get; set; }
public bool IsActive = true;
}
private static PadSchema Keyboard()
{
List<ButtonLayout> bls = new List<ButtonLayout>
{
new ButtonLayout { Name = "Key True Video", DisName = "TV", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key Inv Video", DisName = "IV", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 1", DisName = "1", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 2", DisName = "2", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 3", DisName = "3", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 4", DisName = "4", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 5", DisName = "5", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 6", DisName = "6", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 7", DisName = "7", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 8", DisName = "8", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 9", DisName = "9", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key 0", DisName = "0", Row = 0, WidthFactor = 1 },
new ButtonLayout { Name = "Key Break", DisName = "BREAK", Row = 0, WidthFactor = 1.5 },
new ButtonLayout { Name = "Key Delete", DisName = "DEL", Row = 1, WidthFactor = 1.5 },
new ButtonLayout { Name = "Key Graph", DisName = "GR", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key Q", DisName = "Q", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key W", DisName = "W", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key E", DisName = "E", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key R", DisName = "R", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key T", DisName = "T", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key Y", DisName = "Y", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key U", DisName = "U", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key I", DisName = "I", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key O", DisName = "O", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key P", DisName = "P", Row = 1, WidthFactor = 1 },
new ButtonLayout { Name = "Key Extend Mode", DisName = "EM", Row = 2, WidthFactor = 1.5 },
new ButtonLayout { Name = "Key Edit", DisName = "ED", Row = 2, WidthFactor = 1.25},
new ButtonLayout { Name = "Key A", DisName = "A", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key S", DisName = "S", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key D", DisName = "D", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key F", DisName = "F", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key G", DisName = "G", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key H", DisName = "H", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key J", DisName = "J", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key K", DisName = "K", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key L", DisName = "L", Row = 2, WidthFactor = 1 },
new ButtonLayout { Name = "Key Return", DisName = "ENTER", Row = 2, WidthFactor = 1.75 },
new ButtonLayout { Name = "Key Caps Shift", DisName = "CAPS-S", Row = 3, WidthFactor = 2.25 },
new ButtonLayout { Name = "Key Caps Lock", DisName = "CL", Row = 3, WidthFactor = 1},
new ButtonLayout { Name = "Key Z", DisName = "Z", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key X", DisName = "X", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key C", DisName = "C", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key V", DisName = "V", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key B", DisName = "B", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key N", DisName = "N", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key M", DisName = "M", Row = 3, WidthFactor = 1 },
new ButtonLayout { Name = "Key Period", DisName = ".", Row = 3, WidthFactor = 1},
new ButtonLayout { Name = "Key Caps Shift", DisName = "CAPS-S", Row = 3, WidthFactor = 2.25 },
new ButtonLayout { Name = "Key Symbol Shift", DisName = "SS", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Semi-Colon", DisName = ";", Row = 4, WidthFactor = 1},
new ButtonLayout { Name = "Key Quote", DisName = "\"", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Left Cursor", DisName = "←", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Right Cursor", DisName = "→", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Space", DisName = "SPACE", Row = 4, WidthFactor = 4.5 },
new ButtonLayout { Name = "Key Up Cursor", DisName = "↑", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Down Cursor", DisName = "↓", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Comma", DisName = ",", Row = 4, WidthFactor = 1 },
new ButtonLayout { Name = "Key Symbol Shift", DisName = "SS", Row = 4, WidthFactor = 1 },
};
PadSchema ps = new PadSchema
{
DisplayName = "Keyboard",
IsConsole = false,
DefaultSize = new Size(500, 170)
};
List<PadSchema.ButtonSchema> btns = new List<PadSchema.ButtonSchema>();
int rowHeight = 29; //24
int stdButtonWidth = 29; //24
int yPos = 18;
int xPos = 22;
int currRow = 0;
foreach (var b in bls)
{
if (b.Row > currRow)
{
currRow++;
yPos += rowHeight;
xPos = 22;
}
int txtLength = b.DisName.Length;
int btnSize = System.Convert.ToInt32((double)stdButtonWidth * b.WidthFactor);
string disp = b.DisName;
if (txtLength == 1)
disp = " " + disp;
switch(b.DisName)
{
case "SPACE": disp = " " + disp + " "; break;
case "I": disp = " " + disp + " "; break;
case "W": disp = b.DisName; break;
}
if (b.IsActive)
{
PadSchema.ButtonSchema btn = new PadSchema.ButtonSchema();
btn.Name = b.Name;
btn.DisplayName = disp;
btn.Location = new Point(xPos, yPos);
btn.Type = PadSchema.PadInputType.Boolean;
btns.Add(btn);
}
xPos += btnSize;
}
ps.Buttons = btns.ToArray();
return ps;
}
private static PadSchema TapeDevice()
{
return new PadSchema
{
DisplayName = "DATACORDER",
IsConsole = false,
DefaultSize = new Size(174, 74),
MaxSize = new Size(174, 74),
Buttons = new[]
{
new PadSchema.ButtonSchema
{
Name = "Play Tape",
Icon = Properties.Resources.Play,
Location = new Point(23, 22),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "Stop Tape",
Icon = Properties.Resources.Stop,
Location = new Point(53, 22),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "RTZ Tape",
Icon = Properties.Resources.BackMore,
Location = new Point(83, 22),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "Insert Next Tape",
DisplayName = "NEXT TAPE",
//Icon = Properties.Resources.MoveRight,
Location = new Point(23, 52),
Type = PadSchema.PadInputType.Boolean
},
new PadSchema.ButtonSchema
{
Name = "Insert Previous Tape",
DisplayName = "PREV TAPE",
//Icon = Properties.Resources.MoveLeft,
Location = new Point(100, 52),
Type = PadSchema.PadInputType.Boolean
},
}
};
}
}
}

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Threading;
using BizHawk.Common.BufferExtensions;
using System.Linq;
namespace BizHawk.Emulation.Common
{
@ -298,12 +299,24 @@ namespace BizHawk.Emulation.Common
case ".D64":
case ".T64":
case ".G64":
case ".CRT":
case ".TAP":
case ".CRT":
game.System = "C64";
break;
case ".Z64":
case ".TZX":
game.System = "ZXSpectrum";
break;
case ".TAP":
byte[] head = romData.Take(8).ToArray();
if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE"))
game.System = "C64";
else
game.System = "ZXSpectrum";
break;
case ".Z64":
case ".V64":
case ".N64":
game.System = "N64";

View File

@ -50,9 +50,17 @@ namespace BizHawk.Emulation.Common
FirmwareAndOption("AB16F56989B27D89BABE5F89C5A8CB3DA71A82F0", 16384, "C64", "Drive1541", "drive-1541.bin", "1541 Disk Drive Rom");
FirmwareAndOption("D3B78C3DBAC55F5199F33F3FE0036439811F7FB3", 16384, "C64", "Drive1541II", "drive-1541ii.bin", "1541-II Disk Drive Rom");
// for saturn, we think any bios region can pretty much run any iso
// so, we're going to lay this out carefully so that we choose things in a sensible order, but prefer the correct region
var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)");
// ZX Spectrum
/* These are now shipped with bizhawk
FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM");
FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM");
FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM");
FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65536, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM");
*/
// for saturn, we think any bios region can pretty much run any iso
// so, we're going to lay this out carefully so that we choose things in a sensible order, but prefer the correct region
var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)");
var ss_100_ue = File("FAA8EA183A6D7BBE5D4E03BB1332519800D3FBC3", 524288, "saturn-1.00-(U+E).bin", "Bios v1.00 (U+E)");
var ss_100a_ue = File("3BB41FEB82838AB9A35601AC666DE5AACFD17A58", 524288, "saturn-1.00a-(U+E).bin", "Bios v1.00a (U+E)"); // ?? is this size correct?
var ss_101_j = File("DF94C5B4D47EB3CC404D88B33A8FDA237EAF4720", 524288, "saturn-1.01-(J).bin", "Bios v1.01 (J)"); // ?? is this size correct?

View File

@ -32,7 +32,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 = "INTV", FullName = "Intellivision" },
new SystemInfo { SystemId = "ZXSpectrum", FullName = "Sinclair ZX Spectrum" }
};
public SystemInfo this[string systemId]

View File

@ -258,6 +258,61 @@
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
<Compile Include="Computers\Commodore64\SaveState.cs" />
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPortIODevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPSG.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\CursorJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick2.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick1.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\NullJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\AYChip.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\Buzzer.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Datacorder\DatacorderDevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IKeyboard.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\KempstonJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IBeeperDevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ULABase.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus2a\ZX128Plus2a.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus2a\ZX128Plus2a.Memory.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus2a\ZX128Plus2a.Port.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus2a\ZX128Plus2a.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus3\ZX128Plus3.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum16K\ZX16.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaSerializationType.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeCommand.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeDataBlock.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TzxSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\SoundProviderMixer.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Input.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Port.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Memory.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus2\ZX128Plus2.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus3\ZX128Plus3.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus3\ZX128Plus3.Memory.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128KPlus3\ZX128Plus3.Port.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.Memory.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.Port.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Memory.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\Pulse.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Rom\RomData.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Controllers.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IDebuggable.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IEmulator.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IInputPollable.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IMemoryDomains.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISettable.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IStatable.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Messaging.cs" />
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Util.cs" />
<Compile Include="Consoles\Atari\2600\Atari2600.cs" />
<Compile Include="Consoles\Atari\2600\Atari2600.Core.cs">
<DependentUpon>Atari2600.cs</DependentUpon>
@ -1338,16 +1393,29 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\StandardKeyboard.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Port.cs" />
<None Include="Computers\SinclairSpectrum\readme.md" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Media.cs" />
<None Include="Consoles\Atari\docs\stella.pdf" />
<None Include="Consoles\Coleco\docs\colecovision tech1.pdf" />
<None Include="Consoles\Coleco\docs\colecovision tech2.pdf" />
<None Include="Consoles\Nintendo\NES\Docs\BoardTable.xlsx" />
<None Include="Consoles\Nintendo\NES\Docs\MapperCompatibilityList.url" />
<None Include="Consoles\Nintendo\NES\Docs\nesasm.pdf" />
<None Include="Resources\128.ROM.gz" />
<None Include="Resources\48.ROM.gz" />
<None Include="Resources\cgb_boot.bin.gz" />
<None Include="Resources\dmg_boot.bin.gz" />
<None Include="Resources\plus2.rom.gz" />
<None Include="Resources\plus2a.rom.gz" />
<None Include="Resources\sgb-cart-present.spc.gz" />
<None Include="Resources\Spectrum3_V4-0_ROM0.bin.gz" />
<None Include="Resources\Spectrum3_V4-0_ROM1.bin.gz" />
<None Include="Resources\Spectrum3_V4-0_ROM2.bin.gz" />
<None Include="Resources\Spectrum3_V4-0_ROM3.bin.gz" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">"$(SolutionDir)subwcrev.bat" "$(ProjectDir)"</PreBuildEvent>

View File

@ -4,8 +4,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
{
public partial class Z80A
{
private int totalExecutedCycles;
public int TotalExecutedCycles { get { return totalExecutedCycles; } set { totalExecutedCycles = value; } }
public long TotalExecutedCycles;
private int EI_pending;

View File

@ -83,28 +83,27 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
}
// Interrupt mode 2 uses the I vector combined with a byte on the data bus
// Again for now we assume only a 0 on the data bus and jump to (0xI00)
private void INTERRUPT_2(ushort src)
private void INTERRUPT_2()
{
cur_instr = new ushort[]
{IDLE,
IDLE,
FTCH_DB,
TR, Z, DB,
TR, W, I,
IDLE,
DEC16, SPl, SPh,
WR, SPl, SPh, PCh,
IDLE,
DEC16, SPl, SPh,
WR, SPl, SPh, PCl,
IDLE,
ASGN, PCl, 0,
TR, PCh, I,
IDLE,
WR, SPl, SPh, PCl,
IDLE,
RD, Z, PCl, PCh,
INC16, PCl, PCh,
RD, PCl, Z, W,
INC16, Z, W,
IDLE,
RD, PCh, Z, W,
IDLE,
RD, W, PCl, PCh,
IDLE,
TR16, PCl, PCh, Z, W,
OP };
}

View File

@ -435,6 +435,12 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
addr += extra_inc;
size = addr - start_addr;
// handle case of addr wrapping around at 16 bit boundary
if (addr < start_addr)
{
size = (0x10000 + addr) - start_addr;
}
return temp;
}

View File

@ -8,31 +8,37 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public void Read_Func(ushort dest, ushort src_l, ushort src_h)
{
Regs[dest] = ReadMemory((ushort)(Regs[src_l] | (Regs[src_h]) << 8));
Regs[DB] = Regs[dest];
}
public void I_Read_Func(ushort dest, ushort src_l, ushort src_h, ushort inc)
{
Regs[dest] = ReadMemory((ushort)((Regs[src_l] | (Regs[src_h] << 8)) + inc));
Regs[DB] = Regs[dest];
}
public void Write_Func(ushort dest_l, ushort dest_h, ushort src)
{
Regs[DB] = Regs[src];
WriteMemory((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), (byte)Regs[src]);
}
public void I_Write_Func(ushort dest_l, ushort dest_h, ushort inc, ushort src)
{
Regs[DB] = Regs[src];
WriteMemory((ushort)((Regs[dest_l] | (Regs[dest_h] << 8)) + inc), (byte)Regs[src]);
}
public void OUT_Func(ushort dest, ushort src)
public void OUT_Func(ushort dest_l, ushort dest_h, ushort src)
{
WriteHardware(Regs[dest], (byte)(Regs[src]));
Regs[DB] = Regs[src];
WriteHardware((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), (byte)(Regs[src]));
}
public void IN_Func(ushort dest, ushort src)
public void IN_Func(ushort dest, ushort src_l, ushort src_h)
{
Regs[dest] = ReadHardware(Regs[src]);
Regs[dest] = ReadHardware((ushort)(Regs[src_l] | (Regs[src_h]) << 8));
Regs[DB] = Regs[dest];
}
public void TR_Func(ushort dest, ushort src)
@ -738,5 +744,10 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
Flag3 = (Regs[A] & 0x08) != 0;
}
}
public void FTCH_DB_Func()
{
Regs[DB] = FetchDB();
}
}
}

View File

@ -1,8 +1,7 @@
TODO:
Mode 0 and 2 interrupts
Mode 0
Check T-cycle level memory access timing
Check R register
new tests for WZ Registers
Memory refresh - IR is pushed onto the address bus at instruction start, does anything need this?
Data Bus - For mode zero and 2 interrupts, need a system that uses it to test

View File

@ -6,7 +6,6 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public partial class Z80A
{
// registers
// note these are not constants. When shadows are used, they will be changed accordingly
public ushort PCl = 0;
public ushort PCh = 1;
public ushort SPl = 2;
@ -40,6 +39,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public ushort E_s = 29;
public ushort H_s = 30;
public ushort L_s = 31;
public ushort DB = 32;
public ushort[] Regs = new ushort[36];

View File

@ -452,13 +452,13 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
{
cur_instr = new ushort[]
{IDLE,
RD, ALU, PCl, PCh,
RD, Z, PCl, PCh,
IDLE,
INC16, PCl, PCh,
TR, W, A,
OUT, ALU, A,
TR, Z, ALU,
INC16, Z, ALU,
OUT, Z, W, A,
IDLE,
INC16, Z, W,
IDLE,
IDLE,
OP};
@ -469,9 +469,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
cur_instr = new ushort[]
{IDLE,
IDLE,
OUT, dest, src,
IDLE,
TR16, Z, W, C, B,
OUT, Z, W, src,
IDLE,
INC16, Z, W,
IDLE,
OP};
@ -481,12 +481,12 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
{
cur_instr = new ushort[]
{IDLE,
RD, ALU, PCl, PCh,
RD, Z, PCl, PCh,
IDLE,
INC16, PCl, PCh,
TR, W, A,
IN, A, ALU,
TR, Z, ALU,
IN, A, Z, W,
IDLE,
INC16, Z, W,
IDLE,
IDLE,
@ -498,7 +498,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
cur_instr = new ushort[]
{IDLE,
IDLE,
IN, dest, src,
IN, dest, src, B,
IDLE,
TR16, Z, W, C, B,
INC16, Z, W,

View File

@ -419,7 +419,7 @@
private void IN_OP_R(ushort operation, ushort repeat_instr)
{
cur_instr = new ushort[]
{IN, ALU, C,
{IN, ALU, C, B,
IDLE,
WR, L, H, ALU,
IDLE,
@ -438,7 +438,7 @@
cur_instr = new ushort[]
{RD, ALU, L, H,
IDLE,
OUT, C, ALU,
OUT, C, B, ALU,
IDLE,
IDLE,
operation, L, H,

View File

@ -74,6 +74,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public const ushort SET_FL_IR = 59;
public const ushort I_BIT = 60;
public const ushort HL_BIT = 61;
public const ushort FTCH_DB = 62;
public byte temp_R;
@ -106,6 +107,11 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
public Func<ushort, byte> ReadHardware;
public Action<ushort, byte> WriteHardware;
// Data Bus
// Interrupting Devices are responsible for putting a value onto the data bus
// for as long as the interrupt is valid
public Func<byte> FetchDB;
//this only calls when the first byte of an instruction is fetched.
public Action<ushort> OnExecFetch;
@ -190,9 +196,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
INTERRUPT_1();
break;
case 2:
// Low byte of interrupt vector comes from data bus
// We'll assume it's zero for now
INTERRUPT_2(0);
INTERRUPT_2();
break;
}
IRQCallback();
@ -315,9 +319,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
INTERRUPT_1();
break;
case 2:
// Low byte of interrupt vector comes from data bus
// We'll assume it's zero for now
INTERRUPT_2(0);
INTERRUPT_2();
break;
}
IRQCallback();
@ -339,6 +341,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
case HALT:
halted = true;
// NOTE: Check how halt state effects the DB
Regs[DB] = 0xFF;
if (EI_pending > 0)
{
EI_pending--;
@ -382,9 +387,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
INTERRUPT_1();
break;
case 2:
// Low byte of interrupt vector comes from data bus
// We'll assume it's zero for now
INTERRUPT_2(0);
INTERRUPT_2();
break;
}
IRQCallback();
@ -569,10 +572,10 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
iff1 = iff2;
break;
case OUT:
OUT_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]);
OUT_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++], cur_instr[instr_pntr++]);
break;
case IN:
IN_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]);
IN_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++], cur_instr[instr_pntr++]);
break;
case NEG:
NEG_8_Func(cur_instr[instr_pntr++]);
@ -595,8 +598,11 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
case SET_FL_IR:
SET_FL_IR_Func(cur_instr[instr_pntr++]);
break;
case FTCH_DB:
FTCH_DB_Func();
break;
}
totalExecutedCycles++;
TotalExecutedCycles++;
}
// tracer stuff
@ -651,8 +657,8 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
FlagI ? "E" : "e")
};
}
// State Save/Load
// State Save/Load
public void SyncState(Serializer ser)
{
ser.BeginSection("Z80A");
@ -663,7 +669,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A
ser.Sync("IFF1", ref iff1);
ser.Sync("IFF2", ref iff2);
ser.Sync("Halted", ref halted);
ser.Sync("ExecutedCycles", ref totalExecutedCycles);
ser.Sync("ExecutedCycles", ref TotalExecutedCycles);
ser.Sync("EI_pending", ref EI_pending);
ser.Sync("instruction_pointer", ref instr_pntr);

View File

@ -12,35 +12,35 @@ namespace BizHawk.Emulation.Cores.Calculators
{
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,
["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
};
}
@ -49,85 +49,85 @@ namespace BizHawk.Emulation.Cores.Calculators
{
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);
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;
}
}
@ -145,6 +145,6 @@ namespace BizHawk.Emulation.Cores.Calculators
return false;
}
public int TotalExecutedCycles => _cpu.TotalExecutedCycles;
public int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles;
}
}

View File

@ -137,6 +137,8 @@ namespace BizHawk.Emulation.Cores.Calculators
private void WriteHardware(ushort addr, byte value)
{
addr &= 0xFF;
switch (addr)
{
case 0: // PORT_LINK
@ -232,6 +234,8 @@ namespace BizHawk.Emulation.Cores.Calculators
private byte ReadHardware(ushort addr)
{
addr &= 0xFF;
switch (addr)
{
case 0: // PORT_LINK

View File

@ -0,0 +1,24 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public interface IBeeperDevice
{
void Init(int sampleRate, int tStatesPerFrame);
void ProcessPulseValue(bool fromTape, bool earPulse);
void StartFrame();
void EndFrame();
void SetTapeMode(bool tapeMode);
void SyncState(Serializer ser);
}
}

View File

@ -0,0 +1,44 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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);
}
}

View File

@ -0,0 +1,84 @@

using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a spectrum keyboard
/// </summary>
public interface IKeyboard : IPortIODevice
{
/// <summary>
/// The calling spectrumbase class
/// </summary>
SpectrumBase _machine { get; }
/// <summary>
/// The keyboard matrix for a particular spectrum 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>
int[] KeyLine { get; set; }
/// <summary>
/// Resets the line status
/// </summary>
void ResetLineStatus();
/// <summary>
/// There are some slight differences in how PortIN and PortOUT functions
/// between Issue2 and Issue3 keyboards (16k/48k spectrum only)
/// It is possible that some very old games require Issue2 emulation
/// </summary>
bool IsIssue2Keyboard { get; set; }
/// <summary>
/// Sets the spectrum key status
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
void SetKeyStatus(string key, bool isPressed);
/// <summary>
/// Gets the status of a spectrum key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool GetKeyStatus(string key);
/// <summary>
/// Returns the query byte
/// </summary>
/// <param name="lines"></param>
/// <returns></returns>
byte GetLineStatus(byte lines);
/// <summary>
/// Reads a keyboard byte
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
byte ReadKeyboardByte(ushort addr);
/// <summary>
/// Looks up a key in the keyboard matrix and returns the relevent byte value
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
byte GetByteFromKeyMatrix(string key);
void SyncState(Serializer ser);
}
}

View File

@ -0,0 +1,67 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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>
/// 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);
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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);
}
}

View File

@ -0,0 +1,876 @@
using BizHawk.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents the tape device (or build-in datacorder as it was called +2 and above)
/// </summary>
public class DatacorderDevice : IPortIODevice
{
#region Construction
private SpectrumBase _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(SpectrumBase machine)
{
_machine = machine;
_cpu = _machine.CPU;
_buzzer = machine.BuzzerDevice;
}
#endregion
#region State Information
/// <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;
public bool AutoPlay
{
get { return _machine.Spectrum.Settings.AutoLoadTape; }
set { _autoPlay = value; MonitorReset(); }
}
#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()
{
//if (TapeIsPlaying && AutoPlay)
//FlashLoad();
}
#endregion
#region Tape Controls
/// <summary>
/// Starts the tape playing from the beginning of the current block
/// </summary>
public void Play()
{
if (_tapeIsPlaying)
return;
_machine.BuzzerDevice.SetTapeMode(true);
_machine.Spectrum.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
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.BuzzerDevice.SetTapeMode(false);
_machine.Spectrum.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.Spectrum.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.Spectrum.OSD_TapeNextBlock(sbd.ToString());
else
_machine.Spectrum.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)
{
// attempt TZX deserialization
TzxSerializer tzxSer = new TzxSerializer(this);
try
{
tzxSer.DeSerialize(tapeData);
return;
}
catch (Exception ex)
{
// TAP format not detected
var e = ex;
}
// attempt TAP deserialization
TapSerializer tapSer = new TapSerializer(this);
try
{
tapSer.DeSerialize(tapeData);
return;
}
catch (Exception ex)
{
// TAP format not detected
var e = ex;
}
}
/// <summary>
/// Resets the tape
/// </summary>
public void Reset()
{
RTZ();
}
#endregion
#region Tape Device Methods
/// <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;
// flip the current state
currentState = !currentState;
if (_position == 0 && _tapeIsPlaying)
{
// start of block
// 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.Spectrum.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.Spectrum.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)
{
// 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.Spectrum.OSD_TapeStoppedAuto();
if (_currentDataBlockIndex >= _dataBlocks.Count())
RTZ();
else
{
Stop();
}
_monitorTimeOut = 2000;
break;
case TapeCommand.STOP_THE_TAPE_48K:
if (is48k)
{
_machine.Spectrum.OSD_TapeStoppedAuto();
if (_currentDataBlockIndex >= _dataBlocks.Count())
RTZ();
else
{
Stop();
}
_monitorTimeOut = 2000;
}
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];
}
// update lastCycle and return currentstate
_lastCycle = cpuCycle - (long)cycles;
// play the buzzer
_buzzer.ProcessPulseValue(false, currentState);
return 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.Spectrum;
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.Spectrum.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 && _cpu.RegPC == 1523 && _machine.Spectrum.Settings.AutoLoadTape)
{
if (!_tapeIsPlaying)
{
Play();
_machine.Spectrum.OSD_TapePlayingAuto();
}
_monitorTimeOut = 250;
}
}
else
{
_monitorCount = 0;
}
}
_monitorLastRegs = nRegs;
_monitorLastPC = _cpu.RegPC;
}
public void AutoStopTape()
{
if (_tapeIsPlaying)
return;
if (!_machine.Spectrum.Settings.AutoLoadTape)
return;
Stop();
_machine.Spectrum.OSD_TapeStoppedAuto();
}
public void AutoStartTape()
{
if (_tapeIsPlaying)
return;
if (!_machine.Spectrum.Settings.AutoLoadTape)
return;
Play();
_machine.Spectrum.OSD_TapePlayingAuto();
}
private void MonitorFrame()
{
if (_tapeIsPlaying && _machine.Spectrum.Settings.AutoLoadTape)
{
_monitorTimeOut--;
if (_monitorTimeOut < 0)
{
// does not work properly - disabled for now (handled elsewhere)
//Stop();
//_machine.Spectrum.OSD_TapeStoppedAuto();
}
}
}
#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;
}
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("_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
}
}

View File

@ -0,0 +1,116 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Cursor joystick
/// Maps to a combination of 0xf7fe and 0xeffe
/// </summary>
public class CursorJoystick : IJoystick
{
private int _joyLine;
private SpectrumBase _machine;
#region Construction
public CursorJoystick(SpectrumBase machine, int playerNumber)
{
_machine = machine;
_joyLine = 0;
_playerNumber = playerNumber;
ButtonCollection = new List<string>
{
"P" + _playerNumber + " Left",
"P" + _playerNumber + " Right",
"P" + _playerNumber + " Down",
"P" + _playerNumber + " Up",
"P" + _playerNumber + " Button",
}.ToArray();
}
private List<string> btnLookups = new List<string>
{
"Key 5", // left
"Key 8", // right
"Key 6", // down
"Key 7", // up
"Key 0", // fire
};
#endregion
#region IJoystick
public JoystickType JoyType => JoystickType.Cursor;
public string[] ButtonCollection { get; set; }
private int _playerNumber;
public int PlayerNumber
{
get { return _playerNumber; }
set { _playerNumber = value; }
}
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
public void SetJoyInput(string key, bool isPressed)
{
var pos = GetBitPos(key);
if (isPressed)
{
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true);
}
else
{
if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]))
{
// key is already pressed elswhere - leave it as is
}
else
{
// key is safe to unpress
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false);
}
}
}
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetJoyInput(string key)
{
var pos = GetBitPos(key);
if (_machine == null)
return false;
var l = _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]);
return l;
}
#endregion
/// <summary>
/// Gets the bit position of a particular joystick binding from the matrix
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetBitPos(string key)
{
int index = Array.IndexOf(ButtonCollection, key);
return index;
}
}
}

View File

@ -0,0 +1,108 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class KempstonJoystick : IJoystick
{
private int _joyLine;
private SpectrumBase _machine;
#region Construction
public KempstonJoystick(SpectrumBase machine, int playerNumber)
{
_machine = machine;
_joyLine = 0;
_playerNumber = playerNumber;
ButtonCollection = new List<string>
{
"P" + _playerNumber + " Right",
"P" + _playerNumber + " Left",
"P" + _playerNumber + " Down",
"P" + _playerNumber + " Up",
"P" + _playerNumber + " Button",
}.ToArray();
}
#endregion
#region IJoystick
public JoystickType JoyType => JoystickType.Kempston;
public string[] ButtonCollection { get; set; }
private int _playerNumber;
public int PlayerNumber
{
get { return _playerNumber; }
set { _playerNumber = value; }
}
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
public void SetJoyInput(string key, bool isPressed)
{
var pos = GetBitPos(key);
if (isPressed)
_joyLine |= (1 << pos);
else
_joyLine &= ~(1 << pos);
}
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetJoyInput(string key)
{
var pos = GetBitPos(key);
return (_joyLine & (1 << pos)) != 0;
}
#endregion
/// <summary>
/// Active bits high
/// 0 0 0 F U D L R
/// </summary>
public int JoyLine
{
get { return _joyLine; }
set { _joyLine = value; }
}
/// <summary>
/// Gets the bit position of a particular joystick binding from the matrix
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetBitPos(string key)
{
int index = Array.IndexOf(ButtonCollection, key);
return index;
}
/*
public readonly string[] _bitPos = new string[]
{
"P1 Right",
"P1 Left",
"P1 Down",
"P1 Up",
"P1 Button"
};
*/
}
}

View File

@ -0,0 +1,107 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// A null joystick object
/// </summary>
public class NullJoystick : IJoystick
{
private int _joyLine;
private SpectrumBase _machine;
#region Construction
public NullJoystick(SpectrumBase machine, int playerNumber)
{
_machine = machine;
_joyLine = 0;
_playerNumber = playerNumber;
ButtonCollection = new List<string>
{
}.ToArray();
}
#endregion
#region IJoystick
public JoystickType JoyType => JoystickType.NULL;
public string[] ButtonCollection { get; set; }
private int _playerNumber;
public int PlayerNumber
{
get { return _playerNumber; }
set { _playerNumber = value; }
}
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
public void SetJoyInput(string key, bool isPressed)
{
var pos = GetBitPos(key);
if (isPressed)
_joyLine |= (1 << pos);
else
_joyLine &= ~(1 << pos);
}
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetJoyInput(string key)
{
var pos = GetBitPos(key);
return (_joyLine & (1 << pos)) != 0;
}
#endregion
/// <summary>
/// Active bits high
/// 0 0 0 F U D L R
/// </summary>
public int JoyLine
{
get { return _joyLine; }
set { _joyLine = value; }
}
/// <summary>
/// Gets the bit position of a particular joystick binding from the matrix
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetBitPos(string key)
{
int index = Array.IndexOf(ButtonCollection, key);
return index;
}
/*
public readonly string[] _bitPos = new string[]
{
"P1 Right",
"P1 Left",
"P1 Down",
"P1 Up",
"P1 Button"
};
*/
}
}

View File

@ -0,0 +1,115 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Sinclair Joystick LEFT
/// Just maps to the standard keyboard and is read the same (from port 0xf7fe)
/// </summary>
public class SinclairJoystick1 : IJoystick
{
private int _joyLine;
private SpectrumBase _machine;
#region Construction
public SinclairJoystick1(SpectrumBase machine, int playerNumber)
{
_machine = machine;
_joyLine = 0;
_playerNumber = playerNumber;
ButtonCollection = new List<string>
{
"P" + _playerNumber + " Left",
"P" + _playerNumber + " Right",
"P" + _playerNumber + " Down",
"P" + _playerNumber + " Up",
"P" + _playerNumber + " Button",
}.ToArray();
}
private List<string> btnLookups = new List<string>
{
"Key 1", // left
"Key 2", // right
"Key 3", // down
"Key 4", // up
"Key 5", // fire
};
#endregion
#region IJoystick
public JoystickType JoyType => JoystickType.SinclairLEFT;
public string[] ButtonCollection { get; set; }
private int _playerNumber;
public int PlayerNumber
{
get { return _playerNumber; }
set { _playerNumber = value; }
}
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
public void SetJoyInput(string key, bool isPressed)
{
var pos = GetBitPos(key);
if (isPressed)
{
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true);
}
else
{
if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]))
{
// key is already pressed elswhere - leave it as is
}
else
{
// key is safe to unpress
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false);
}
}
}
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetJoyInput(string key)
{
var pos = GetBitPos(key);
if (_machine == null)
return false;
return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]);
}
#endregion
/// <summary>
/// Gets the bit position of a particular joystick binding from the matrix
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetBitPos(string key)
{
int index = Array.IndexOf(ButtonCollection, key);
return index;
}
}
}

View File

@ -0,0 +1,115 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Sinclair Joystick RIGHT
/// Just maps to the standard keyboard and is read the same (from port 0xeffe)
/// </summary>
public class SinclairJoystick2 : IJoystick
{
private int _joyLine;
private SpectrumBase _machine;
#region Construction
public SinclairJoystick2(SpectrumBase machine, int playerNumber)
{
_machine = machine;
_joyLine = 0;
_playerNumber = playerNumber;
ButtonCollection = new List<string>
{
"P" + _playerNumber + " Left",
"P" + _playerNumber + " Right",
"P" + _playerNumber + " Down",
"P" + _playerNumber + " Up",
"P" + _playerNumber + " Button",
}.ToArray();
}
private List<string> btnLookups = new List<string>
{
"Key 6", // left
"Key 7", // right
"Key 8", // down
"Key 9", // up
"Key 0", // fire
};
#endregion
#region IJoystick
public JoystickType JoyType => JoystickType.SinclairRIGHT;
public string[] ButtonCollection { get; set; }
private int _playerNumber;
public int PlayerNumber
{
get { return _playerNumber; }
set { _playerNumber = value; }
}
/// <summary>
/// Sets the joystick line based on key pressed
/// </summary>
/// <param name="key"></param>
/// <param name="isPressed"></param>
public void SetJoyInput(string key, bool isPressed)
{
var pos = GetBitPos(key);
if (isPressed)
{
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true);
}
else
{
if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]))
{
// key is already pressed elswhere - leave it as is
}
else
{
// key is safe to unpress
_machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false);
}
}
}
/// <summary>
/// Gets the state of a particular joystick binding
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetJoyInput(string key)
{
var pos = GetBitPos(key);
if (_machine == null)
return false;
return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]);
}
#endregion
/// <summary>
/// Gets the bit position of a particular joystick binding from the matrix
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetBitPos(string key)
{
int index = Array.IndexOf(ButtonCollection, key);
return index;
}
}
}

View File

@ -0,0 +1,416 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The 48k keyboard device
/// </summary>
public class StandardKeyboard : IKeyboard
{
public SpectrumBase _machine { get; set; }
private byte[] LineStatus;
private string[] _keyboardMatrix;
private int[] _keyLine;
private bool _isIssue2Keyboard;
private string[] _nonMatrixKeys;
public bool IsIssue2Keyboard
{
get { return _isIssue2Keyboard; }
set { _isIssue2Keyboard = value; }
}
public int[] KeyLine
{
get { return _keyLine; }
set { _keyLine = value; }
}
public string[] KeyboardMatrix
{
get { return _keyboardMatrix; }
set { _keyboardMatrix = value; }
}
public string[] NonMatrixKeys
{
get { return _nonMatrixKeys; }
set { _nonMatrixKeys = value; }
}
public StandardKeyboard(SpectrumBase machine)
{
_machine = machine;
KeyboardMatrix = new string[]
{
// 0xfefe - 0 - 4
"Key Caps Shift", "Key Z", "Key X", "Key C", "Key V",
// 0xfdfe - 5 - 9
"Key A", "Key S", "Key D", "Key F", "Key G",
// 0xfbfe - 10 - 14
"Key Q", "Key W", "Key E", "Key R", "Key T",
// 0xf7fe - 15 - 19
"Key 1", "Key 2", "Key 3", "Key 4", "Key 5",
// 0xeffe - 20 - 24
"Key 0", "Key 9", "Key 8", "Key 7", "Key 6",
// 0xdffe - 25 - 29
"Key P", "Key O", "Key I", "Key U", "Key Y",
// 0xbffe - 30 - 34
"Key Return", "Key L", "Key K", "Key J", "Key H",
// 0x7ffe - 35 - 39
"Key Space", "Key Symbol Shift", "Key M", "Key N", "Key B"
};
var nonMatrix = new List<string>();
foreach (var key in _machine.Spectrum.ZXSpectrumControllerDefinition.BoolButtons)
{
if (!KeyboardMatrix.Any(s => s == key))
nonMatrix.Add(key);
}
NonMatrixKeys = nonMatrix.ToArray();
LineStatus = new byte[8];
_keyLine = new int[] { 255, 255, 255, 255, 255, 255, 255, 255 };
IsIssue2Keyboard = true;
}
public void SetKeyStatus(string key, bool isPressed)
{
int k = GetByteFromKeyMatrix(key);
if (k != 255)
{
var lineIndex = k / 5;
var lineMask = 1 << k % 5;
_keyLine[lineIndex] = isPressed
? (byte)(_keyLine[lineIndex] & ~lineMask)
: (byte)(_keyLine[lineIndex] | lineMask);
}
/*
if (isPressed)
{
switch (k)
{
// 0xfefe - 0 - 4
case 0: _keyLine[0] = (_keyLine[0] & ~(0x1)); break;
case 1: _keyLine[0] = (_keyLine[0] & ~(0x02)); break;
case 2: _keyLine[0] = (_keyLine[0] & ~(0x04)); break;
case 3: _keyLine[0] = (_keyLine[0] & ~(0x08)); break;
case 4: _keyLine[0] = (_keyLine[0] & ~(0x10)); break;
// 0xfdfe - 5 - 9
case 5: _keyLine[1] = (_keyLine[1] & ~(0x1)); break;
case 6: _keyLine[1] = (_keyLine[1] & ~(0x02)); break;
case 7: _keyLine[1] = (_keyLine[1] & ~(0x04)); break;
case 8: _keyLine[1] = (_keyLine[1] & ~(0x08)); break;
case 9: _keyLine[1] = (_keyLine[1] & ~(0x10)); break;
// 0xfbfe - 10 - 14
case 10: _keyLine[2] = (_keyLine[2] & ~(0x1)); break;
case 11: _keyLine[2] = (_keyLine[2] & ~(0x02)); break;
case 12: _keyLine[2] = (_keyLine[2] & ~(0x04)); break;
case 13: _keyLine[2] = (_keyLine[2] & ~(0x08)); break;
case 14: _keyLine[2] = (_keyLine[2] & ~(0x10)); break;
// 0xf7fe - 15 - 19
case 15: _keyLine[3] = (_keyLine[3] & ~(0x1)); break;
case 16: _keyLine[3] = (_keyLine[3] & ~(0x02)); break;
case 17: _keyLine[3] = (_keyLine[3] & ~(0x04)); break;
case 18: _keyLine[3] = (_keyLine[3] & ~(0x08)); break;
case 19: _keyLine[3] = (_keyLine[3] & ~(0x10)); break;
// 0xeffe - 20 - 24
case 20: _keyLine[4] = (_keyLine[4] & ~(0x1)); break;
case 21: _keyLine[4] = (_keyLine[4] & ~(0x02)); break;
case 22: _keyLine[4] = (_keyLine[4] & ~(0x04)); break;
case 23: _keyLine[4] = (_keyLine[4] & ~(0x08)); break;
case 24: _keyLine[4] = (_keyLine[4] & ~(0x10)); break;
// 0xdffe - 25 - 29
case 25: _keyLine[5] = (_keyLine[5] & ~(0x1)); break;
case 26: _keyLine[5] = (_keyLine[5] & ~(0x02)); break;
case 27: _keyLine[5] = (_keyLine[5] & ~(0x04)); break;
case 28: _keyLine[5] = (_keyLine[5] & ~(0x08)); break;
case 29: _keyLine[5] = (_keyLine[5] & ~(0x10)); break;
// 0xbffe - 30 - 34
case 30: _keyLine[6] = (_keyLine[6] & ~(0x1)); break;
case 31: _keyLine[6] = (_keyLine[6] & ~(0x02)); break;
case 32: _keyLine[6] = (_keyLine[6] & ~(0x04)); break;
case 33: _keyLine[6] = (_keyLine[6] & ~(0x08)); break;
case 34: _keyLine[6] = (_keyLine[6] & ~(0x10)); break;
// 0x7ffe - 35 - 39
case 35: _keyLine[7] = (_keyLine[7] & ~(0x1)); break;
case 36: _keyLine[7] = (_keyLine[7] & ~(0x02)); break;
case 37: _keyLine[7] = (_keyLine[7] & ~(0x04)); break;
case 38: _keyLine[7] = (_keyLine[7] & ~(0x08)); break;
case 39: _keyLine[7] = (_keyLine[7] & ~(0x10)); break;
}
}
else
{
switch (k)
{
// 0xfefe - 0 - 4
case 0: _keyLine[0] = (_keyLine[0] | (0x1)); break;
case 1: _keyLine[0] = (_keyLine[0] | (0x02)); break;
case 2: _keyLine[0] = (_keyLine[0] | (0x04)); break;
case 3: _keyLine[0] = (_keyLine[0] | (0x08)); break;
case 4: _keyLine[0] = (_keyLine[0] | (0x10)); break;
// 0xfdfe - 5 - 9
case 5: _keyLine[1] = (_keyLine[1] | (0x1)); break;
case 6: _keyLine[1] = (_keyLine[1] | (0x02)); break;
case 7: _keyLine[1] = (_keyLine[1] | (0x04)); break;
case 8: _keyLine[1] = (_keyLine[1] | (0x08)); break;
case 9: _keyLine[1] = (_keyLine[1] | (0x10)); break;
// 0xfbfe - 10 - 14
case 10: _keyLine[2] = (_keyLine[2] | (0x1)); break;
case 11: _keyLine[2] = (_keyLine[2] | (0x02)); break;
case 12: _keyLine[2] = (_keyLine[2] | (0x04)); break;
case 13: _keyLine[2] = (_keyLine[2] | (0x08)); break;
case 14: _keyLine[2] = (_keyLine[2] | (0x10)); break;
// 0xf7fe - 15 - 19
case 15: _keyLine[3] = (_keyLine[3] | (0x1)); break;
case 16: _keyLine[3] = (_keyLine[3] | (0x02)); break;
case 17: _keyLine[3] = (_keyLine[3] | (0x04)); break;
case 18: _keyLine[3] = (_keyLine[3] | (0x08)); break;
case 19: _keyLine[3] = (_keyLine[3] | (0x10)); break;
// 0xeffe - 20 - 24
case 20: _keyLine[4] = (_keyLine[4] | (0x1)); break;
case 21: _keyLine[4] = (_keyLine[4] | (0x02)); break;
case 22: _keyLine[4] = (_keyLine[4] | (0x04)); break;
case 23: _keyLine[4] = (_keyLine[4] | (0x08)); break;
case 24: _keyLine[4] = (_keyLine[4] | (0x10)); break;
// 0xdffe - 25 - 29
case 25: _keyLine[5] = (_keyLine[5] | (0x1)); break;
case 26: _keyLine[5] = (_keyLine[5] | (0x02)); break;
case 27: _keyLine[5] = (_keyLine[5] | (0x04)); break;
case 28: _keyLine[5] = (_keyLine[5] | (0x08)); break;
case 29: _keyLine[5] = (_keyLine[5] | (0x10)); break;
// 0xbffe - 30 - 34
case 30: _keyLine[6] = (_keyLine[6] | (0x1)); break;
case 31: _keyLine[6] = (_keyLine[6] | (0x02)); break;
case 32: _keyLine[6] = (_keyLine[6] | (0x04)); break;
case 33: _keyLine[6] = (_keyLine[6] | (0x08)); break;
case 34: _keyLine[6] = (_keyLine[6] | (0x10)); break;
// 0x7ffe - 35 - 39
case 35: _keyLine[7] = (_keyLine[7] | (0x1)); break;
case 36: _keyLine[7] = (_keyLine[7] | (0x02)); break;
case 37: _keyLine[7] = (_keyLine[7] | (0x04)); break;
case 38: _keyLine[7] = (_keyLine[7] | (0x08)); break;
case 39: _keyLine[7] = (_keyLine[7] | (0x10)); break;
}
}
*/
// Combination keys that are not in the keyboard matrix
// but are available on the Spectrum+, 128k +2 & +3
// (GetByteFromKeyMatrix() should return 255)
// Processed after the matrix keys - only presses handled (unpressed get done above)
if (k == 255)
{
if (isPressed)
{
switch (key)
{
// Delete key (simulates Caps Shift + 0)
case "Key Delete":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x1);
break;
// Cursor left (simulates Caps Shift + 5)
case "Key Left Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[3] = _keyLine[3] & ~(0x10);
break;
// Cursor right (simulates Caps Shift + 8)
case "Key Right Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x04);
break;
// Cursor up (simulates Caps Shift + 7)
case "Key Up Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x08);
break;
// Cursor down (simulates Caps Shift + 6)
case "Key Down Cursor":
_keyLine[0] = _keyLine[0] & ~(0x1);
_keyLine[4] = _keyLine[4] & ~(0x10);
break;
}
}
}
}
public bool GetKeyStatus(string key)
{
byte keyByte = GetByteFromKeyMatrix(key);
var lineIndex = keyByte / 5;
var lineMask = 1 << keyByte % 5;
return (_keyLine[lineIndex] & lineMask) == 0;
}
public void ResetLineStatus()
{
lock (this)
{
for (int i = 0; i < KeyLine.Length; i++)
KeyLine[i] = 255;
}
}
public byte GetLineStatus(byte lines)
{
lock(this)
{
byte status = 0;
lines = (byte)~lines;
var lineIndex = 0;
while (lines > 0)
{
if ((lines & 0x01) != 0)
status |= (byte)_keyLine[lineIndex];
lineIndex++;
lines >>= 1;
}
var result = (byte)status;
return result;
}
/*
switch (lines)
{
case 0xfe: return (byte)KeyLine[0];
case 0xfd: return (byte)KeyLine[1];
case 0xfb: return (byte)KeyLine[2];
case 0xf7: return (byte)KeyLine[3];
case 0xef: return (byte)KeyLine[4];
case 0xdf: return (byte)KeyLine[5];
case 0xbf: return (byte)KeyLine[6];
case 0x7f: return (byte)KeyLine[7];
default: return 0;
}
*/
}
public byte ReadKeyboardByte(ushort addr)
{
return GetLineStatus((byte)(addr >> 8));
}
public byte GetByteFromKeyMatrix(string key)
{
int index = Array.IndexOf(KeyboardMatrix, key);
return (byte)index;
}
#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)
{
/*
The high byte indicates which half-row of keys is being polled
A zero on one of these lines selects a particular half-row of five keys:
IN: Reads keys (bit 0 to bit 4 inclusive)
0xfefe SHIFT, Z, X, C, V 0xeffe 0, 9, 8, 7, 6
0xfdfe A, S, D, F, G 0xdffe P, O, I, U, Y
0xfbfe Q, W, E, R, T 0xbffe ENTER, L, K, J, H
0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B
A zero in one of the five lowest bits means that the corresponding key is pressed. If more than one address line
is made low, the result is the logical AND of all single inputs, so a zero in a bit means that at least one of the
appropriate keys is pressed. For example, only if each of the five lowest bits of the result from reading from Port 00FE
(for instance by XOR A/IN A,(FE)) is one, no key is pressed
*/
if ((port & 0x0001) != 0)
return false;
if ((port & 0x8000) == 0)
{
result &= KeyLine[7];
}
if ((port & 0x4000) == 0)
{
result &= KeyLine[6];
}
if ((port & 0x2000) == 0)
{
result &= KeyLine[5];
}
if ((port & 0x1000) == 0)
{
result &= KeyLine[4];
}
if ((port & 0x800) == 0)
{
result &= KeyLine[3];
}
if ((port & 0x400) == 0)
{
result &= KeyLine[2];
}
if ((port & 0x200) == 0)
{
result &= KeyLine[1];
}
if ((port & 0x100) == 0)
{
result &= KeyLine[0];
}
// mask out lower 4 bits
result = result & 0x1f;
// set bit 5 & 7 to 1
result = result | 0xa0;
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)
{
// not implemented
return false;
}
#endregion
public void SyncState(Serializer ser)
{
ser.BeginSection("Keyboard");
ser.Sync("LineStatus", ref LineStatus, false);
ser.Sync("_keyLine", ref _keyLine, false);
ser.EndSection();
}
}
}

View File

@ -0,0 +1,99 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
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.ZXSpectrum48:
RD.SaveBytesRoutineAddress = 0x04C2;
RD.SaveBytesResumeAddress = 0x0000;
RD.LoadBytesRoutineAddress = 0x0808; //0x0556; //0x056C;
RD.LoadBytesResumeAddress = 0x05E2;
RD.LoadBytesInvalidHeaderAddress = 0x05B6;
break;
case MachineType.ZXSpectrum128:
RD.SaveBytesRoutineAddress = 0x04C2;
RD.SaveBytesResumeAddress = 0x0000;
RD.LoadBytesRoutineAddress = 0x0808; //0x0556; //0x056C;
RD.LoadBytesResumeAddress = 0x05E2;
RD.LoadBytesInvalidHeaderAddress = 0x05B6;
break;
}
return RD;
}
public void SyncState(Serializer ser)
{
ser.BeginSection("RomData");
ser.Sync("RomBytes", ref _romBytes, false);
ser.Sync("_saveBytesRoutineAddress", ref _saveBytesRoutineAddress);
ser.Sync("_loadBytesRoutineAddress", ref _loadBytesRoutineAddress);
ser.Sync("_saveBytesResumeAddress", ref _saveBytesResumeAddress);
ser.Sync("_loadBytesResumeAddress", ref _loadBytesResumeAddress);
ser.Sync("_loadBytesInvalidHeaderAddress", ref _loadBytesInvalidHeaderAddress);
ser.EndSection();
}
}
}

View File

@ -0,0 +1,802 @@

using BizHawk.Common;
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// AY-3-8912 Emulated Device
///
/// 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 AYChip : IPSG
{
#region Device Fields
/// <summary>
/// The emulated machine (passed in via constructor)
/// </summary>
private SpectrumBase _machine;
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 AYChip(SpectrumBase 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()
{
return _registers[_activeRegister];
}
/// <summary>
/// Writes to the currently selected register
/// </summary>
/// <param name="value"></param>
public void PortWrite(int value)
{
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[] { 100,100, 100,100, 100,100 },
// 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 = 50;
/// <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()
{
var vol = (ulong)0xFFFF * (ulong)_volume / 100UL;
_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;
_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.ULADevice.ClockSpeed;
var aytickspercputick = (double)_machine.ULADevice.ClockSpeed / (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);
}
ser.EndSection();
}
#endregion
}
}

View File

@ -0,0 +1,330 @@

using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents the piezoelectric buzzer used in the Spectrum to produce sound
/// The beeper is controlled by rapidly toggling bit 4 of port &FE
///
/// For the purposes of emulation this devices is locked to a frame
/// a list of Pulses is built up over the course of the frame and outputted at the end of the frame
/// </summary>
public class Buzzer : ISoundProvider, IBeeperDevice
{
/// <summary>
/// Supplied values are right for 48K spectrum
/// These will deviate for 128k and up (as there are more T-States per frame)
/// </summary>
//public int SampleRate = 44100; //35000;
//public int SamplesPerFrame = 882; //699;
//public int TStatesPerSample = 79; //100;
/// <summary>
/// Sample Rate
/// This usually has to be 44100 for ISoundProvider
/// </summary>
public int SampleRate
{
get { return _sampleRate; }
set { _sampleRate = value; }
}
/// <summary>
/// Number of samples in one frame
/// </summary>
public int SamplesPerFrame
{
get { return _samplesPerFrame; }
set { _samplesPerFrame = value; }
}
/// <summary>
/// Number of TStates in each sample
/// </summary>
public int TStatesPerSample
{
get { return _tStatesPerSample; }
set { _tStatesPerSample = value; }
}
private SpectrumBase _machine;
/// <summary>
/// State fields
/// </summary>
private long _frameStart;
private bool _tapeMode;
private long _tStatesPerFrame;
private int _sampleRate;
private int _samplesPerFrame;
private int _tStatesPerSample;
/// <summary>
/// Pulses collected during the last frame
/// </summary>
public List<Pulse> Pulses { get; private set; }
/// <summary>
/// The last pulse
/// </summary>
public bool LastPulse { get; set; }
/// <summary>
/// The last T-State (cpu cycle) that the last pulse was received
/// </summary>
public long LastPulseTState { get; set; }
#region Construction & Initialisation
public Buzzer(SpectrumBase machine)
{
_machine = machine;
}
/// <summary>
/// Initialises the buzzer
/// </summary>
public void Init(int sampleRate, int tStatesPerFrame)
{
_sampleRate = sampleRate;
_tStatesPerFrame = tStatesPerFrame;
_tStatesPerSample = 79;
_samplesPerFrame = (int)_tStatesPerFrame / _tStatesPerSample;
/*
// set the tstatesperframe
_tStatesPerFrame = tStatesPerFrame;
// calculate actual refresh rate
double refresh = (double)_machine.ULADevice.ClockSpeed / (double)_tStatesPerFrame;
// how many samples per frame are expected by ISoundProvider (at 44.1KHz)
_samplesPerFrame = 880;// (int)((double)sampleRate / (double)refresh);
// set the sample rate
_sampleRate = sampleRate;
// calculate samples per frame (what ISoundProvider will be expecting at 44100)
//_samplesPerFrame = (int)((double)_tStatesPerFrame / (double)refresh);
// calculate tstates per sameple
_tStatesPerSample = 79;// _tStatesPerFrame / _samplesPerFrame;
/*
// get divisors
var divs = from a in Enumerable.Range(2, _tStatesPerFrame / 2)
where _tStatesPerFrame % a == 0
select a;
// get the highest int value under 120 (this will be TStatesPerSample)
_tStatesPerSample = divs.Where(a => a < 100).Last();
// get _samplesPerFrame
_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
*/
Pulses = new List<Pulse>(1000);
}
#endregion
/// <summary>
/// When the pulse value from the EAR output changes it is processed here
/// </summary>
/// <param name="fromTape"></param>
/// <param name="earPulse"></param>
public void ProcessPulseValue(bool fromTape, bool earPulse)
{
if (!_machine._renderSound)
return;
if (!fromTape && _tapeMode)
{
// tape mode is active but the pulse value came from an OUT instruction
// do not process the value
//return;
}
if (earPulse == LastPulse)
{
// no change detected
return;
}
// set the lastpulse
LastPulse = earPulse;
// get where we are in the frame
var currentULACycle = _machine.CurrentFrameCycle;
var currentBuzzerCycle = currentULACycle <= _tStatesPerFrame ? currentULACycle : _tStatesPerFrame;
var length = currentBuzzerCycle - LastPulseTState;
if (length == 0)
{
// the first T-State has changed the pulse
// do not add it
}
else if (length > 0)
{
// add the pulse
Pulse p = new Pulse
{
State = !earPulse,
Length = length
};
Pulses.Add(p);
}
// set the last pulse tstate
LastPulseTState = currentBuzzerCycle;
}
/// <summary>
/// New frame starts
/// </summary>
public void StartFrame()
{
//DiscardSamples();
Pulses.Clear();
LastPulseTState = 0;
}
/// <summary>
/// Frame is completed
/// </summary>
public void EndFrame()
{
// store the last pulse information
if (LastPulseTState <= _tStatesPerFrame - 1)
{
Pulse p = new Pulse
{
State = LastPulse,
Length = _tStatesPerFrame - LastPulseTState
};
Pulses.Add(p);
}
// create the sample array
var firstSampleOffset = _frameStart % TStatesPerSample == 0 ? 0 : TStatesPerSample - (_frameStart + TStatesPerSample) % TStatesPerSample;
var samplesInFrame = (_tStatesPerFrame - firstSampleOffset - 1) / TStatesPerSample + 1;
var samples = new short[samplesInFrame];
// convert pulses to samples
var sampleIndex = 0;
var currentEnd = _frameStart;
foreach (var pulse in Pulses)
{
var firstSample = currentEnd % TStatesPerSample == 0
? currentEnd : currentEnd + TStatesPerSample - currentEnd % TStatesPerSample;
for (var i = firstSample; i < currentEnd + pulse.Length; i += TStatesPerSample)
{
if (_tapeMode)
samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 6) : (short)0;
else
samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 3) : (short)0;
}
currentEnd += pulse.Length;
}
// fill the _sampleBuffer for ISoundProvider
soundBufferContains = (int)samplesInFrame;
if (soundBuffer.Length != soundBufferContains)
soundBuffer = new short[soundBufferContains];
samples.CopyTo(soundBuffer, 0);
_frameStart += _tStatesPerFrame;
}
/// <summary>
/// When the spectrum is set to receive tape input, the EAR output on the ULA is disabled
/// (so no buzzer sound is emitted)
/// </summary>
/// <param name="tapeMode"></param>
public void SetTapeMode(bool tapeMode)
{
_tapeMode = tapeMode;
}
#region ISoundProvider
private short[] soundBuffer = new short[882];
private int soundBufferContains = 0;
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()
{
soundBufferContains = 0;
soundBuffer = new short[SamplesPerFrame];
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
// convert to stereo
short[] stereoBuffer = new short[soundBufferContains * 2];
int index = 0;
for (int i = 0; i < soundBufferContains; i++)
{
stereoBuffer[index++] = soundBuffer[i];
stereoBuffer[index++] = soundBuffer[i];
}
samples = stereoBuffer;
nsamp = _samplesPerFrame; // soundBufferContains;
}
#endregion
public void SyncState(Serializer ser)
{
ser.BeginSection("Buzzer");
ser.Sync("_frameStart", ref _frameStart);
ser.Sync("_tapeMode", ref _tapeMode);
ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
ser.Sync("_sampleRate", ref _sampleRate);
ser.Sync("_samplesPerFrame", ref _samplesPerFrame);
ser.Sync("_tStatesPerSample", ref _tStatesPerSample);
ser.Sync("soundBuffer", ref soundBuffer, false);
ser.Sync("soundBufferContains", ref soundBufferContains);
ser.EndSection();
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The MIC and EAR pins in the spectrum deal in on/off pulses of varying lengths
/// This struct therefore represents 1 of these pulses
/// </summary>
public struct Pulse
{
/// <summary>
/// True: High State
/// False: Low State
/// </summary>
public bool State { get; set; }
/// <summary>
/// Pulse length in Z80 T-States (cycles)
/// </summary>
public long Length { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public enum MachineType
{
/// <summary>
/// Original Sinclair Spectrum 16K model
/// </summary>
ZXSpectrum16,
/// <summary>
/// Sinclair Spectrum 48K model
/// </summary>
ZXSpectrum48,
/// <summary>
/// Sinclair Spectrum 128K model
/// </summary>
ZXSpectrum128,
/// <summary>
/// Sinclair Spectrum 128 +2 model
/// </summary>
ZXSpectrum128Plus2,
/// <summary>
/// Sinclair Spectrum 128 +2a model (same as the +3 just without disk drive)
/// </summary>
ZXSpectrum128Plus2a,
/// <summary>
/// Sinclair Spectrum 128 +3 model
/// </summary>
ZXSpectrum128Plus3
}
}

View File

@ -0,0 +1,292 @@

using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Handles all ZX-level input
/// </summary>
public abstract partial class SpectrumBase
{
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 HardResetStr = "Hard Reset";
string SoftResetStr = "Soft 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_HardReset = false;
bool pressed_SoftReset = false;
/// <summary>
/// Cycles through all the input callbacks
/// This should be done once per frame
/// </summary>
public void PollInput()
{
Spectrum.InputCallbacks.Call();
lock (this)
{
// parse single keyboard matrix keys
for (var i = 0; i < KeyboardDevice.KeyboardMatrix.Length; i++)
{
string key = KeyboardDevice.KeyboardMatrix[i];
bool prevState = KeyboardDevice.GetKeyStatus(key);
bool currState = Spectrum._controller.IsPressed(key);
if (currState != prevState)
KeyboardDevice.SetKeyStatus(key, currState);
}
// non matrix keys
foreach (string k in KeyboardDevice.NonMatrixKeys)
{
if (!k.StartsWith("Key"))
continue;
bool currState = Spectrum._controller.IsPressed(k);
KeyboardDevice.SetKeyStatus(k, currState);
}
// J1
foreach (string j in JoystickCollection[0].ButtonCollection)
{
bool prevState = JoystickCollection[0].GetJoyInput(j);
bool currState = Spectrum._controller.IsPressed(j);
if (currState != prevState)
JoystickCollection[0].SetJoyInput(j, currState);
}
// J2
foreach (string j in JoystickCollection[1].ButtonCollection)
{
bool prevState = JoystickCollection[1].GetJoyInput(j);
bool currState = Spectrum._controller.IsPressed(j);
if (currState != prevState)
JoystickCollection[1].SetJoyInput(j, currState);
}
// J3
foreach (string j in JoystickCollection[2].ButtonCollection)
{
bool prevState = JoystickCollection[2].GetJoyInput(j);
bool currState = Spectrum._controller.IsPressed(j);
if (currState != prevState)
JoystickCollection[2].SetJoyInput(j, currState);
}
}
// Tape control
if (Spectrum._controller.IsPressed(Play))
{
if (!pressed_Play)
{
Spectrum.OSD_FireInputMessage(Play);
TapeDevice.Play();
pressed_Play = true;
}
}
else
pressed_Play = false;
if (Spectrum._controller.IsPressed(Stop))
{
if (!pressed_Stop)
{
Spectrum.OSD_FireInputMessage(Stop);
TapeDevice.Stop();
pressed_Stop = true;
}
}
else
pressed_Stop = false;
if (Spectrum._controller.IsPressed(RTZ))
{
if (!pressed_RTZ)
{
Spectrum.OSD_FireInputMessage(RTZ);
TapeDevice.RTZ();
pressed_RTZ = true;
}
}
else
pressed_RTZ = false;
if (Spectrum._controller.IsPressed(Record))
{
}
if (Spectrum._controller.IsPressed(NextTape))
{
if (!pressed_NextTape)
{
Spectrum.OSD_FireInputMessage(NextTape);
TapeMediaIndex++;
pressed_NextTape = true;
}
}
else
pressed_NextTape = false;
if (Spectrum._controller.IsPressed(PrevTape))
{
if (!pressed_PrevTape)
{
Spectrum.OSD_FireInputMessage(PrevTape);
TapeMediaIndex--;
pressed_PrevTape = true;
}
}
else
pressed_PrevTape = false;
if (Spectrum._controller.IsPressed(NextBlock))
{
if (!pressed_NextBlock)
{
Spectrum.OSD_FireInputMessage(NextBlock);
TapeDevice.SkipBlock(true);
pressed_NextBlock = true;
}
}
else
pressed_NextBlock = false;
if (Spectrum._controller.IsPressed(PrevBlock))
{
if (!pressed_PrevBlock)
{
Spectrum.OSD_FireInputMessage(PrevBlock);
TapeDevice.SkipBlock(false);
pressed_PrevBlock = true;
}
}
else
pressed_PrevBlock = false;
if (Spectrum._controller.IsPressed(TapeStatus))
{
if (!pressed_TapeStatus)
{
//Spectrum.OSD_FireInputMessage(TapeStatus);
Spectrum.OSD_ShowTapeStatus();
pressed_TapeStatus = true;
}
}
else
pressed_TapeStatus = false;
if (Spectrum._controller.IsPressed(HardResetStr))
{
if (!pressed_HardReset)
{
HardReset();
pressed_HardReset = true;
}
}
else
pressed_HardReset = false;
if (Spectrum._controller.IsPressed(SoftResetStr))
{
if (!pressed_SoftReset)
{
SoftReset();
pressed_SoftReset = true;
}
}
else
pressed_SoftReset = false;
}
/// <summary>
/// Instantiates the joysticks array
/// </summary>
/// <param name="joys"></param>
protected void InitJoysticks(List<JoystickType> joys)
{
List<IJoystick> jCollection = new List<IJoystick>();
for (int i = 0; i < joys.Count(); i++)
{
jCollection.Add(InstantiateJoystick(joys[i], i + 1));
}
JoystickCollection = jCollection.ToArray();
for (int i = 0; i < JoystickCollection.Length; i++)
{
Spectrum.OSD_FireInputMessage("Joystick " + (i + 1) + ": " + JoystickCollection[i].JoyType.ToString());
}
}
/// <summary>
/// Instantiates a new IJoystick object
/// </summary>
/// <param name="type"></param>
/// <param name="playerNumber"></param>
/// <returns></returns>
public IJoystick InstantiateJoystick(JoystickType type, int playerNumber)
{
switch (type)
{
case JoystickType.Kempston:
return new KempstonJoystick(this, playerNumber);
case JoystickType.Cursor:
return new CursorJoystick(this, playerNumber);
case JoystickType.SinclairLEFT:
return new SinclairJoystick1(this, playerNumber);
case JoystickType.SinclairRIGHT:
return new SinclairJoystick2(this, playerNumber);
case JoystickType.NULL:
return new NullJoystick(this, playerNumber);
}
return null;
}
/// <summary>
/// Returns a IJoystick object depending on the type (or null if not found)
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
protected IJoystick LocateUniqueJoystick(JoystickType type)
{
return JoystickCollection.Where(a => a.JoyType == type).FirstOrDefault();
}
/// <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; }
}
}
}

View File

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public abstract partial class SpectrumBase
{
// until +3 disk drive is emulated, we assume that incoming files are tape images
/// <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>
protected List<byte[]> tapeImages { get; set; }
/// <summary>
/// Disk images
/// </summary>
protected 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;
LoadDiskMedia();
}
}
/// <summary>
/// Called on first instantiation (and subsequent core reboots)
/// </summary>
/// <param name="files"></param>
protected void InitializeMedia(List<byte[]> files)
{
mediaImages = files;
LoadAllMedia();
Spectrum.OSD_TapeInit();
}
/// <summary>
/// Attempts to load all media into the relevant structures
/// </summary>
protected void LoadAllMedia()
{
tapeImages = new List<byte[]>();
diskImages = new List<byte[]>();
foreach (var m in mediaImages)
{
switch (IdentifyMedia(m))
{
case SpectrumMediaType.Tape:
tapeImages.Add(m);
break;
case SpectrumMediaType.Disk:
diskImages.Add(m);
break;
}
}
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()
{
throw new NotImplementedException("+3 disk drive device not yet implemented");
}
/// <summary>
/// Identifies and sorts the various media types
/// </summary>
/// <returns></returns>
private SpectrumMediaType 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"))
{
// spectrum .dsk disk file
return SpectrumMediaType.Disk;
}
if (hdr.ToUpper().StartsWith("FDI"))
{
// spectrum .fdi disk file
return SpectrumMediaType.Disk;
}
// tape checking
if (hdr.ToUpper().StartsWith("ZXTAPE!"))
{
// spectrum .tzx tape file
return SpectrumMediaType.Tape;
}
// if we get this far, assume a .tap file
return SpectrumMediaType.Tape;
}
}
public enum SpectrumMediaType
{
None,
Tape,
Disk
}
}

View File

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The abstract class that all emulated models will inherit from
/// * Memory *
/// </summary>
public abstract partial class SpectrumBase
{
#region Memory Fields & Properties
/// <summary>
/// ROM Banks
/// </summary>
public byte[] ROM0 = new byte[0x4000];
public byte[] ROM1 = new byte[0x4000];
public byte[] ROM2 = new byte[0x4000];
public byte[] ROM3 = 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
public byte[] RAM4 = new byte[0x4000]; // Bank 4
public byte[] RAM5 = new byte[0x4000]; // Bank 5
public byte[] RAM6 = new byte[0x4000]; // Bank 6
public byte[] RAM7 = new byte[0x4000]; // Bank 7
/// <summary>
/// Signs that the shadow screen is now displaying
/// Note: normal screen memory in RAM5 is not altered, the ULA just outputs Screen1 instead (RAM7)
/// </summary>
protected bool SHADOWPaged;
/// <summary>
/// Index of the current RAM page
/// /// 128k, +2/2a and +3 only
/// </summary>
public int RAMPaged;
/// <summary>
/// Signs that all paging is disabled
/// If this is TRUE, then 128k and above machines need a hard reset before paging is allowed again
/// </summary>
protected bool PagingDisabled;
/// <summary>
/// Index of the currently paged ROM
/// 128k, +2/2a and +3 only
/// </summary>
protected int ROMPaged;
public virtual int _ROMpaged
{
get { return ROMPaged; }
set { ROMPaged = value; }
}
/*
* +3/+2A only
*/
/// <summary>
/// High bit of the ROM selection (in normal paging mode)
/// </summary>
protected bool ROMhigh = false;
/// <summary>
/// Low bit of the ROM selection (in normal paging mode)
/// </summary>
protected bool ROMlow = false;
/// <summary>
/// Signs that the +2a/+3 special paging mode is activated
/// </summary>
protected bool SpecialPagingMode;
/// <summary>
/// Index of the current special paging mode (0-3)
/// </summary>
protected int PagingConfiguration;
#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 virtual void WriteBus(ushort addr, byte value)
{
throw new NotImplementedException("Must be overriden");
}
/// <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));
return value;
}
#endregion
#region Helper Methods
/// <summary>
/// Detects whether this is a 48k machine (or a 128k in 48k mode)
/// </summary>
/// <returns></returns>
public virtual bool IsIn48kMode()
{
if (this.GetType() == typeof(ZX48) ||
this.GetType() == typeof(ZX16) ||
PagingDisabled)
{
return true;
}
else
return false;
}
/// <summary>
/// Monitors ROM access
/// Used to auto start/stop the tape device when appropriate
/// </summary>
/// <param name="addr"></param>
public virtual void TestForTapeTraps(int addr)
{
if (TapeDevice.TapeIsPlaying)
{
// THE 'ERROR' RESTART
if (addr == 8)
{
TapeDevice?.AutoStopTape();
return;
}
// THE 'ED-ERROR' SUBROUTINE
if (addr == 4223)
{
TapeDevice?.AutoStopTape();
return;
}
// THE 'ERROR-2' ROUTINE
if (addr == 83)
{
TapeDevice?.AutoStopTape();
return;
}
// THE 'MASKABLE INTERRUPT' ROUTINE
if (addr == 56)
{
//TapeDevice?.AutoStopTape();
return;
}
}
else
{
// THE 'LD-BYTES' SUBROUTINE
if (addr == 1366)
{
TapeDevice?.AutoStartTape();
return;
}
// THE 'LD-EDGE-2' AND 'LD-EDGE-1' SUBROUTINES
if (addr == 1507)
{
TapeDevice?.AutoStartTape();
return;
}
}
}
#endregion
}
}

View File

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The abstract class that all emulated models will inherit from
/// * Port Access *
/// </summary>
public abstract partial class SpectrumBase
{
/// <summary>
/// The last OUT data that was sent to the ULA
/// </summary>
protected byte LastULAOutByte;
public byte LASTULAOutByte
{
get { return LastULAOutByte; }
set { LastULAOutByte = value; }
}
/// <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);
/// <summary>
/// Increments the CPU totalCycles counter by the tStates value specified
/// </summary>
/// <param name="tStates"></param>
public virtual void PortContention(int tStates)
{
CPU.TotalExecutedCycles += tStates;
}
/// <summary>
/// Simulates IO port contention based on the supplied address
/// This method is for 48k and 128k/+2 machines only and should be overridden for other models
/// </summary>
/// <param name="addr"></param>
public virtual void ContendPortAddress(ushort addr)
{
/*
It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port. As is the case with memory access,
this can be lengthened by the ULA. There are two effects which occur here:
If the port address being accessed has its low bit reset, the ULA is required to supply the result, which leads to a delay if it is
currently busy handling the screen.
The address of the port being accessed is placed on the data bus. If this is in the range 0x4000 to 0x7fff, the ULA treats this as an
attempted access to contended memory and therefore introduces a delay. If the port being accessed is between 0xc000 and 0xffff,
this effect does not apply, even on a 128K machine if a contended memory bank is paged into the range 0xc000 to 0xffff.
These two effects combine to lead to the following contention patterns:
High byte | |
in 40 - 7F? | Low bit | Contention pattern
------------+---------+-------------------
No | Reset | N:1, C:3
No | Set | N:4
Yes | Reset | C:1, C:3
Yes | Set | C:1, C:1, C:1, C:1
The 'Contention pattern' column should be interpreted from left to right. An "N:n" entry means that no delay is applied at this cycle, and the Z80 continues uninterrupted for 'n' T states. A "C:n" entry means that the ULA halts the Z80; the delay is exactly the same as would occur for a contended memory access at this cycle (eg 6 T states at cycle 14335, 5 at 14336, etc on the 48K machine). After this delay, the Z80 then continues for 'n' cycles.
*/
// is the low bit reset (i.e. is this addressing the ULA)?
bool lowBit = (addr & 0x0001) != 0;
if ((addr & 0xc000) == 0x4000 || (addr & 0xc000) == 0xC000)
{
// high byte is in 40 - 7F
if (lowBit)
{
// lowbit is set
// C:1, C:1, C:1, C:1
for (int i = 0; i < 4; i++)
{
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles++;
}
}
else
{
// low bit is reset
// C:1, C:3
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles++;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles += 3;
}
}
else
{
// high byte is NOT in 40 - 7F
if (lowBit)
{
// lowbit is set
// C:1, C:1, C:1, C:1
CPU.TotalExecutedCycles += 4;
}
else
{
// lowbit is reset
// N:1, C:3
CPU.TotalExecutedCycles++;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles += 3;
}
}
}
}
}

View File

@ -0,0 +1,325 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The abstract class that all emulated models will inherit from
/// * Main properties / fields / contruction*
/// </summary>
public abstract partial class SpectrumBase
{
#region Devices
/// <summary>
/// The calling ZXSpectrum class (piped in via constructor)
/// </summary>
public ZXSpectrum Spectrum { 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 emulated ULA device
/// </summary>
public ULABase ULADevice { get; set; }
/// <summary>
/// The spectrum buzzer/beeper
/// </summary>
public IBeeperDevice BuzzerDevice { get; set; }
/// <summary>
/// Device representing the AY-3-8912 chip found in the 128k and up spectrums
/// </summary>
public IPSG AYDevice { get; set; }
/// <summary>
/// The spectrum keyboard
/// </summary>
public virtual IKeyboard KeyboardDevice { get; set; }
/// <summary>
/// The spectrum datacorder device
/// </summary>
public virtual DatacorderDevice TapeDevice { get; set; }
/// <summary>
/// Holds the currently selected joysticks
/// </summary>
public virtual IJoystick[] JoystickCollection { get; set; }
/// <summary>
/// Signs whether the disk motor is on or off
/// </summary>
protected bool DiskMotorState;
/// <summary>
/// +3/2a printer port strobe
/// </summary>
protected bool PrinterPortStrobe;
#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)
{
InputRead = false;
_render = render;
_renderSound = renderSound;
FrameCompleted = false;
TapeDevice.StartFrame();
if (_renderSound)
{
BuzzerDevice.StartFrame();
if (AYDevice != null)
AYDevice.StartFrame();
}
PollInput();
while (CurrentFrameCycle < ULADevice.FrameLength)
{
// check for interrupt
ULADevice.CheckForInterrupt(CurrentFrameCycle);
// run a single CPU instruction
CPU.ExecuteOne();
// update AY
int ayCnt = 0;
if (_renderSound)
{
if (AYDevice != null)
{
//AYDevice.UpdateSound(CurrentFrameCycle);
if (ayCnt > 10)
{
//AYDevice.UpdateSound(CurrentFrameCycle);
ayCnt = 0;
}
}
}
}
// we have reached the end of a frame
LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow;
// paint the buffer if needed
if (ULADevice.needsPaint && _render)
ULADevice.UpdateScreenBuffer(ULADevice.FrameLength);
if (_renderSound)
BuzzerDevice.EndFrame();
if (AYDevice != null)
AYDevice.EndFrame();
FrameCount++;
// setup for next frame
ULADevice.ResetInterrupt();
TapeDevice.EndFrame();
FrameCompleted = true;
// is this a lag frame?
Spectrum.IsLagFrame = !InputRead;
}
#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();
}
/// <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();
}
#endregion
#region IStatable
public void SyncState(Serializer ser)
{
ser.BeginSection("ZXMachine");
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("LastULAOutByte", ref LastULAOutByte);
ser.Sync("ROM0", ref ROM0, false);
ser.Sync("ROM1", ref ROM1, false);
ser.Sync("ROM2", ref ROM2, false);
ser.Sync("ROM3", ref ROM3, 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);
ser.Sync("RAM4", ref RAM4, false);
ser.Sync("RAM5", ref RAM5, false);
ser.Sync("RAM6", ref RAM6, false);
ser.Sync("RAM7", ref RAM7, false);
ser.Sync("ROMPaged", ref ROMPaged);
ser.Sync("SHADOWPaged", ref SHADOWPaged);
ser.Sync("RAMPaged", ref RAMPaged);
ser.Sync("PagingDisabled", ref PagingDisabled);
ser.Sync("SpecialPagingMode", ref SpecialPagingMode);
ser.Sync("PagingConfiguration", ref PagingConfiguration);
ser.Sync("ROMhigh", ref ROMhigh);
ser.Sync("ROMlow", ref ROMlow);
RomData.SyncState(ser);
KeyboardDevice.SyncState(ser);
BuzzerDevice.SyncState(ser);
ULADevice.SyncState(ser);
if (AYDevice != null)
AYDevice.SyncState(ser);
ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
TapeMediaIndex = tapeMediaIndex;
ser.Sync("diskMediaIndex", ref diskMediaIndex);
DiskMediaIndex = diskMediaIndex;
TapeDevice.SyncState(ser);
ser.EndSection();
}
#endregion
}
}

View File

@ -0,0 +1,760 @@

using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// ULA (Uncommitted Logic Array) implementation
/// </summary>
public abstract class ULABase : IVideoProvider
{
#region General
/// <summary>
/// Length of the frame in T-States
/// </summary>
public int FrameLength;
/// <summary>
/// Emulated clock speed
/// </summary>
public int ClockSpeed;
/// <summary>
/// Whether machine is late or early timing model
/// </summary>
public bool LateTiming; //currently not implemented
/// <summary>
/// The current cycle within the current frame
/// </summary>
public int CurrentTStateInFrame;
protected SpectrumBase _machine;
#endregion
#region Palettes
/// <summary>
/// The standard ULA palette
/// </summary>
private static readonly int[] ULAPalette =
{
Colors.ARGB(0x00, 0x00, 0x00), // Black
Colors.ARGB(0x00, 0x00, 0xD7), // Blue
Colors.ARGB(0xD7, 0x00, 0x00), // Red
Colors.ARGB(0xD7, 0x00, 0xD7), // Magenta
Colors.ARGB(0x00, 0xD7, 0x00), // Green
Colors.ARGB(0x00, 0xD7, 0xD7), // Cyan
Colors.ARGB(0xD7, 0xD7, 0x00), // Yellow
Colors.ARGB(0xD7, 0xD7, 0xD7), // White
Colors.ARGB(0x00, 0x00, 0x00), // Bright Black
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
};
#endregion
#region Contention
/// <summary>
/// T-State at which to start applying contention
/// </summary>
public int contentionStartPeriod;
/// <summary>
/// T-State at which to end applying contention
/// </summary>
public int contentionEndPeriod;
/// <summary>
/// T-State memory contention delay mapping
/// </summary>
public byte[] contentionTable;
#endregion
#region Screen Rendering
/// <summary>
/// Video output buffer
/// </summary>
public int[] ScreenBuffer;
/// <summary>
/// Display memory
/// </summary>
protected byte[] screen;
/// <summary>
/// Attribute memory lookup (mapped 1:1 to screen for convenience)
/// </summary>
protected short[] attr;
/// <summary>
/// T-State display mapping
/// </summary>
protected short[] tstateToDisp;
/// <summary>
/// Table that stores T-State to screen/attribute address values
/// </summary>
public short[] floatingBusTable;
/// <summary>
/// Cycle at which the last render update took place
/// </summary>
protected long lastTState;
/// <summary>
/// T-States elapsed since last render update
/// </summary>
protected long elapsedTStates;
/// <summary>
/// T-State of top left raster pixel
/// </summary>
protected int actualULAStart;
/// <summary>
/// Offset into display memory based on current T-State
/// </summary>
protected int screenByteCtr;
/// <summary>
/// Offset into current pixel of rasterizer
/// </summary>
protected int ULAByteCtr;
/// <summary>
/// The current border colour
/// </summary>
public int borderColour;
/// <summary>
/// Signs whether the colour flash is ON or OFF
/// </summary>
protected bool flashOn = false;
protected int flashCounter;
public int FlashCounter
{
get { return flashCounter; }
set
{
flashCounter = value;
}
}
/// <summary>
/// Internal frame counter used for flasher operations
/// </summary>
protected int frameCounter = 0;
/// <summary>
/// Last 8-bit bitmap read from display memory
/// (Floating bus implementation)
/// </summary>
protected int lastPixelValue;
/// <summary>
/// Last 8-bit attr val read from attribute memory
/// (Floating bus implementation)
/// </summary>
protected int lastAttrValue;
/// <summary>
/// Last 8-bit bitmap read from display memory+1
/// (Floating bus implementation)
/// </summary>
protected int lastPixelValuePlusOne;
/// <summary>
/// Last 8-bit attr val read from attribute memory+1
/// (Floating bus implementation)
/// </summary>
protected int lastAttrValuePlusOne;
/// <summary>
/// Used to create the non-border display area
/// </summary>
protected int TtateAtLeft;
protected int TstateWidth;
protected int TstateAtTop;
protected int TstateHeight;
protected int TstateAtRight;
protected int TstateAtBottom;
/// <summary>
/// Total T-States in one scanline
/// </summary>
protected int TstatesPerScanline;
/// <summary>
/// Total pixels in one scanline
/// </summary>
protected int ScanLineWidth;
/// <summary>
/// Total chars in one PRINT row
/// </summary>
protected int CharRows;
/// <summary>
/// Total chars in one PRINT column
/// </summary>
protected int CharCols;
/// <summary>
/// Total pixels in one display row
/// </summary>
protected int ScreenWidth;
/// <summary>
/// Total pixels in one display column
/// </summary>
protected int ScreenHeight;
/// <summary>
/// Total pixels in top border
/// </summary>
protected int BorderTopHeight;
/// <summary>
/// Total pixels in bottom border
/// </summary>
protected int BorderBottomHeight;
/// <summary>
/// Total pixels in left border width
/// </summary>
protected int BorderLeftWidth;
/// <summary>
/// Total pixels in right border width
/// </summary>
protected int BorderRightWidth;
/// <summary>
/// Memory address of display start
/// </summary>
protected int DisplayStart;
/// <summary>
/// Total number of bytes of display memory
/// </summary>
protected int DisplayLength;
/// <summary>
/// Memory address of attribute start
/// </summary>
protected int AttributeStart;
/// <summary>
/// Total number of bytes of attribute memory
/// </summary>
protected int AttributeLength;
/// <summary>
/// Raised when ULA has finished painting the entire screen
/// </summary>
public bool needsPaint = false;
#endregion
#region Interrupt
/// <summary>
/// The number of T-States that the INT pin is simulated to be held low
/// </summary>
public int InterruptPeriod;
/// <summary>
/// The longest instruction cycle count
/// </summary>
protected int LongestOperationCycles = 23;
/// <summary>
/// Signs that an interrupt has been raised in this frame.
/// </summary>
protected bool InterruptRaised;
/// <summary>
/// Signs that the interrupt signal has been revoked
/// </summary>
protected bool InterruptRevoked;
/// <summary>
/// Resets the interrupt - this should happen every frame in order to raise
/// the VBLANK interrupt in the proceding frame
/// </summary>
public virtual void ResetInterrupt()
{
InterruptRaised = false;
InterruptRevoked = false;
}
/// <summary>
/// Generates an interrupt in the current phase if needed
/// </summary>
/// <param name="currentCycle"></param>
public virtual void CheckForInterrupt(long currentCycle)
{
if (InterruptRevoked)
{
// interrupt has already been handled
return;
}
if (currentCycle < LongestOperationCycles)// InterruptPeriod)
{
// interrupt does not need to be raised yet
return;
}
if (currentCycle >= InterruptPeriod + LongestOperationCycles)
{
// interrupt should have already been raised and the cpu may or
// may not have caught it. The time has passed so revoke the signal
InterruptRevoked = true;
_machine.CPU.FlagI = false;
return;
}
if (InterruptRaised)
{
// INT is raised but not yet revoked
// CPU has NOT handled it yet
return;
}
// Raise the interrupt
InterruptRaised = true;
_machine.CPU.FlagI = true;
// Signal the start of ULA processing
if (_machine._render)
ULAUpdateStart();
CalcFlashCounter();
}
#endregion
#region Construction & Initialisation
public ULABase(SpectrumBase machine)
{
_machine = machine;
borderType = _machine.Spectrum.SyncSettings.BorderType;
}
#endregion
#region Methods
/// <summary>
/// Resets the ULA chip
/// </summary>
public abstract void Reset();
/// <summary>
/// Builds the contention table for the emulated model
/// </summary>
public abstract void BuildContentionTable();
/// <summary>
/// Returns true if the given memory address should be contended
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public abstract bool IsContended(int addr);
/// <summary>
/// Contends the machine for a given address
/// </summary>
/// <param name="addr"></param>
public virtual void Contend(ushort addr)
{
if (IsContended(addr) && !(_machine is ZX128Plus3))
{
_machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame];
}
}
public virtual void Contend(int addr, int time, int count)
{
if (IsContended(addr) && !(_machine is ZX128Plus3))
{
for (int f = 0; f < count; f++)
{
_machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame] + time;
}
}
else
_machine.CPU.TotalExecutedCycles += count * time;
}
/// <summary>
/// Resets render state once interrupt is generated
/// </summary>
public void ULAUpdateStart()
{
ULAByteCtr = 0;
screenByteCtr = DisplayStart;
lastTState = actualULAStart;
needsPaint = true;
}
/// <summary>
/// Flash processing
/// </summary>
public void CalcFlashCounter()
{
flashCounter++;
if (flashCounter > 15)
{
flashOn = !flashOn;
flashCounter = 0;
}
}
/// <summary>
/// Builds the T-State to attribute map used with the floating bus
/// </summary>
public void BuildAttributeMap()
{
int start = DisplayStart;
for (int f = 0; f < DisplayLength; f++, start++)
{
int addrH = start >> 8; //div by 256
int addrL = start % 256;
int pixelY = (addrH & 0x07);
pixelY |= (addrL & (0xE0)) >> 2;
pixelY |= (addrH & (0x18)) << 3;
int attrIndex_Y = AttributeStart + ((pixelY >> 3) << 5);// pixel/8 * 32
addrL = start % 256;
int pixelX = addrL & (0x1F);
attr[f] = (short)(attrIndex_Y + pixelX);
}
}
/// <summary>
/// Updates the screen buffer based on the number of T-States supplied
/// </summary>
/// <param name="_tstates"></param>
public virtual void UpdateScreenBuffer(long _tstates)
{
if (_tstates < actualULAStart)
{
return;
}
else if (_tstates >= FrameLength)
{
_tstates = FrameLength - 1;
needsPaint = true;
}
//the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna
elapsedTStates = (_tstates + 1 - lastTState) - 1;
//It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state.
long numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0);
int pixelData;
int pixel2Data = 0xff;
int attrData;
int attr2Data;
int bright;
int ink;
int paper;
int flash;
for (int i = 0; i < numBytes; i++)
{
if (tstateToDisp[lastTState] > 1)
{
screenByteCtr = tstateToDisp[lastTState] - 16384; //adjust for actual screen offset
pixelData = _machine.FetchScreenMemory((ushort)screenByteCtr); //screen[screenByteCtr];
attrData = _machine.FetchScreenMemory((ushort)(attr[screenByteCtr] - 16384)); //screen[attr[screenByteCtr] - 16384];
lastPixelValue = pixelData;
lastAttrValue = attrData;
bright = (attrData & 0x40) >> 3;
flash = (attrData & 0x80) >> 7;
ink = (attrData & 0x07);
paper = ((attrData >> 3) & 0x7);
int paletteInk = ULAPalette[ink + bright];
int palettePaper = ULAPalette[paper + bright];
if (flashOn && (flash != 0)) //swap paper and ink when flash is on
{
int temp = paletteInk;
paletteInk = palettePaper;
palettePaper = temp;
}
for (int a = 0; a < 8; ++a)
{
if ((pixelData & 0x80) != 0)
{
ScreenBuffer[ULAByteCtr++] = paletteInk;
lastAttrValue = ink;
//pixelIsPaper = false;
}
else
{
ScreenBuffer[ULAByteCtr++] = palettePaper;
lastAttrValue = paper;
}
pixelData <<= 1;
}
}
else if (tstateToDisp[lastTState] == 1)
{
int bor = ULAPalette[borderColour];
for (int g = 0; g < 8; g++)
ScreenBuffer[ULAByteCtr++] = bor;
}
lastTState += 4;
}
}
#endregion
#region IVideoProvider
private int _virtualWidth;
private int _virtualHeight;
private int _bufferWidth;
private int _bufferHeight;
public int BackgroundColor
{
get { return ULAPalette[7]; } //ULAPalette[borderColour]; }
}
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 ClockSpeed; }
set { }
}
public int VsyncDenominator
{
get { return FrameLength; }
}
public int[] GetVideoBuffer()
{
switch (borderType)
{
// Full side borders, no top or bottom border (giving *almost* 16:9 output)
case ZXSpectrum.BorderType.Widescreen:
// we are cropping out the top and bottom borders
var startPixelsToCrop = ScanLineWidth * BorderTopHeight;
var endPixelsToCrop = ScanLineWidth * BorderBottomHeight;
int index = 0;
for (int i = startPixelsToCrop; i < ScreenBuffer.Length - endPixelsToCrop; i++)
{
croppedBuffer[index++] = ScreenBuffer[i];
}
return croppedBuffer;
// The full spectrum border
case ZXSpectrum.BorderType.Full:
return ScreenBuffer;
case ZXSpectrum.BorderType.Medium:
// all border sizes now 24
var lR = BorderLeftWidth - 24;
var rR = BorderRightWidth - 24;
var tR = BorderTopHeight - 24;
var bR = BorderBottomHeight - 24;
var startP = ScanLineWidth * tR;
var endP = ScanLineWidth * bR;
int index2 = 0;
// line by line
for (int i = startP; i < ScreenBuffer.Length - endP; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
{
// each pixel in each line
for (int p = lR; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR; p++)
{
if (index2 == croppedBuffer.Length)
break;
croppedBuffer[index2++] = ScreenBuffer[i + p];
}
}
return croppedBuffer;
case ZXSpectrum.BorderType.Small:
// all border sizes now 24
var lR_ = BorderLeftWidth - 10;
var rR_ = BorderRightWidth - 10;
var tR_ = BorderTopHeight - 10;
var bR_ = BorderBottomHeight - 10;
var startP_ = ScanLineWidth * tR_;
var endP_ = ScanLineWidth * bR_;
int index2_ = 0;
// line by line
for (int i = startP_; i < ScreenBuffer.Length - endP_; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
{
// each pixel in each line
for (int p = lR_; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR_; p++)
{
if (index2_ == croppedBuffer.Length)
break;
croppedBuffer[index2_++] = ScreenBuffer[i + p];
}
}
return croppedBuffer;
case ZXSpectrum.BorderType.None:
// all border sizes now 24
var lR__ = BorderLeftWidth;
var rR__ = BorderRightWidth;
var tR__ = BorderTopHeight;
var bR__ = BorderBottomHeight;
var startP__ = ScanLineWidth * tR__;
var endP__ = ScanLineWidth * bR__;
int index2__ = 0;
// line by line
for (int i = startP__; i < ScreenBuffer.Length - endP__; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
{
// each pixel in each line
for (int p = lR__; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR__; p++)
{
if (index2__ == croppedBuffer.Length)
break;
croppedBuffer[index2__++] = ScreenBuffer[i + p];
}
}
return croppedBuffer;
}
return ScreenBuffer;
}
protected void SetupScreenSize()
{
switch (borderType)
{
case ZXSpectrum.BorderType.Full:
BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
ScreenBuffer = new int[BufferWidth * BufferHeight];
break;
case ZXSpectrum.BorderType.Widescreen:
BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
BufferHeight = ScreenHeight;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
croppedBuffer = new int[BufferWidth * BufferHeight];
break;
case ZXSpectrum.BorderType.Medium:
BufferWidth = ScreenWidth + (24) + (24);
BufferHeight = ScreenHeight + (24) + (24);
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
croppedBuffer = new int[BufferWidth * BufferHeight];
break;
case ZXSpectrum.BorderType.Small:
BufferWidth = ScreenWidth + (10) + (10);
BufferHeight = ScreenHeight + (10) + (10);
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
croppedBuffer = new int[BufferWidth * BufferHeight];
break;
case ZXSpectrum.BorderType.None:
BufferWidth = ScreenWidth;
BufferHeight = ScreenHeight;
VirtualHeight = BufferHeight;
VirtualWidth = BufferWidth;
croppedBuffer = new int[BufferWidth * BufferHeight];
break;
}
}
protected int[] croppedBuffer;
private ZXSpectrum.BorderType _borderType;
public ZXSpectrum.BorderType borderType
{
get { return _borderType; }
set { _borderType = value; }
}
#endregion
#region IStatable
public void SyncState(Serializer ser)
{
ser.BeginSection("ULA");
ser.Sync("ScreenBuffer", ref ScreenBuffer, false);
ser.Sync("FrameLength", ref FrameLength);
ser.Sync("ClockSpeed", ref ClockSpeed);
ser.Sync("LateTiming", ref LateTiming);
ser.Sync("borderColour", ref borderColour);
ser.EndSection();
}
#endregion
#region Attribution
/*
* Based on code from ArjunNair's Zero emulator (MIT Licensed)
* https://github.com/ArjunNair/Zero-Emulator
The MIT License (MIT)
Copyright (c) 2009 Arjun Nair
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#endregion
}
}

View File

@ -0,0 +1,259 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128 : SpectrumBase
{
/* 128k paging controlled by writes to port 0x7ffd
*
*
#7FFD (32765) - decoded as A15=0, A1=0 and /IORQ=0. Bits 0..5 are latched. Bits 0..2 select RAM bank in secton D. Bit 3 selects RAM bank to dispay screen (0 - RAM5, 1 - RAM7). Bit 4 selects ROM bank (0 - ROM0, 1 - ROM1). Bit 5, when set locks future writing to #7FFD port until reset. Reading #7FFD port is the same as writing #FF into it.
#BFFD (49149) - write data byte into AY-3-8912 chip.
#FFFD (65533) - select AY-3-8912 addres (D4..D7 ignored) and reading data byte.
* 0xffff +--------+--------+--------+--------+--------+--------+--------+--------+
| Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 |
| | |(also at| | |(also at| | |
| | | 0x8000)| | | 0x4000)| | |
| | | | | | screen | | screen |
0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+
| Bank 2 | Any one of these pages may be switched in.
| |
| |
| |
0x8000 +--------+
| Bank 5 |
| |
| |
| screen |
0x4000 +--------+--------+
| ROM 0 | ROM 1 | Either ROM may be switched in.
| | |
| | |
| | |
0x0000 +--------+--------+
*/
/// <summary>
/// Simulates reading from the bus (no contention)
/// 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)
{
// ROM 0x000
case 0:
TestForTapeTraps(addr % 0x4000);
if (ROMPaged == 0)
result = ROM0[addr % 0x4000];
else
result = ROM1[addr % 0x4000];
break;
// RAM 0x4000 (RAM5 - Bank5)
case 1:
result = RAM5[addr % 0x4000];
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
result = RAM2[addr % 0x4000];
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
result = RAM1[addr % 0x4000];
break;
case 2:
result = RAM2[addr % 0x4000];
break;
case 3:
result = RAM3[addr % 0x4000];
break;
case 4:
result = RAM4[addr % 0x4000];
break;
case 5:
result = RAM5[addr % 0x4000];
break;
case 6:
result = RAM6[addr % 0x4000];
break;
case 7:
result = RAM7[addr % 0x4000];
break;
}
break;
default:
break;
}
return result;
}
/// <summary>
/// Simulates writing to the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public override void WriteBus(ushort addr, byte value)
{
int divisor = addr / 0x4000;
switch (divisor)
{
// ROM 0x000
case 0:
// cannot write to ROMs
/*
if (ROMPaged == 0)
ROM0[addr % 0x4000] = value;
else
ROM1[addr % 0x4000] = value;
*/
break;
// RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7)
case 1:
RAM5[addr % 0x4000] = value;
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
RAM2[addr % 0x4000] = value;
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
RAM0[addr % 0x4000] = value;
break;
case 1:
RAM1[addr % 0x4000] = value;
break;
case 2:
RAM2[addr % 0x4000] = value;
break;
case 3:
RAM3[addr % 0x4000] = value;
break;
case 4:
RAM4[addr % 0x4000] = value;
break;
case 5:
RAM5[addr % 0x4000] = value;
break;
case 6:
RAM6[addr % 0x4000] = value;
break;
case 7:
RAM7[addr % 0x4000] = value;
break;
}
break;
default:
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
/// Reads a byte of data from a specified memory address
/// (with memory contention if appropriate)
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
WriteBus(addr, value);
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)
/// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte FetchScreenMemory(ushort addr)
{
byte value = new byte();
if (SHADOWPaged && !PagingDisabled)
{
// shadow screen should be outputted
// this lives in RAM7
value = RAM7[addr & 0x3FFF];
}
else
{
// shadow screen is not set to display or paging is disabled (probably in 48k mode)
// (use screen0 at RAM5)
value = RAM5[addr & 0x3FFF];
}
return value;
}
/// <summary>
/// Sets up the ROM
/// </summary>
/// <param name="buffer"></param>
/// <param name="startAddress"></param>
public override void InitROM(RomData romData)
{
RomData = romData;
// 128k uses ROM0 and ROM1
// 128k loader is in ROM0, and fallback 48k rom is in ROM1
for (int i = 0; i < 0x4000; i++)
{
ROM0[i] = RomData.RomBytes[i];
if (RomData.RomBytes.Length > 0x4000)
ROM1[i] = RomData.RomBytes[i + 0x4000];
}
}
}
}

View File

@ -0,0 +1,253 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128 : SpectrumBase
{
/// <summary>
/// Reads a byte of data from a specified port address
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public override byte ReadPort(ushort port)
{
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
// check AY
if (AYDevice.ReadPort(port, ref result))
return (byte)result;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
InputRead = true;
}
else
{
if (KeyboardDevice.ReadPort(port, ref result))
{
// not a lagframe
InputRead = true;
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else
deviceAddressed = false;
}
if (!deviceAddressed)
{
// If this is an unused port the floating memory bus should be returned
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
result = 0xff;
}
else
{
if (ULADevice.floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
}
/*
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = (port & 0x0001) == 0;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
InputRead = true;
}
else if (lowBitReset)
{
// Even I/O address so get input from keyboard
KeyboardDevice.ReadPort(port, ref result);
// not a lagframe
InputRead = true;
// tape loading monitor cycle
TapeDevice.MonitorRead();
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else if ((port & 0xc002) == 0xc000)
{
// AY sound chip
result = (int)AYDevice.PortRead();
}
else
{
// devices other than the ULA will respond here
// Kempston Mouse (not implemented yet)
// If this is an unused port the floating memory bus should be returned
// Floating bus is read on the previous cycle
int _tStates = CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
result = 0xff;
}
else
{
if (ULADevice.floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
}
*/
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)
{
// process IO contention
ContendPortAddress(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
// get a BitArray of the value byte
BitArray bits = new BitArray(new byte[] { value });
long currT = CPU.TotalExecutedCycles;
AYDevice.WritePort(port, value);
// paging
if (port == 0x7ffd)
{
//if (PagingDisabled)
//return;
// Bits 0, 1, 2 select the RAM page
var rp = value & 0x07;
if (rp < 8)
RAMPaged = rp;
// bit 3 controls shadow screen
SHADOWPaged = bits[3];
if (SHADOWPaged == false)
{
}
else
{
}
// ROM page
if (bits[4])
{
// 48k basic rom
ROMPaged = 1;
}
else
{
// 128k editor and menu system
ROMPaged = 0;
}
// Bit 5 set signifies that paging is disabled until next reboot
PagingDisabled = bits[5];
}
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = !portBits[0]; // (port & 0x01) == 0;
// Only even addresses address the ULA
if (lowBitReset)
{
// store the last OUT byte
LastULAOutByte = value;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
/*
Bit 7 6 5 4 3 2 1 0
+-------------------------------+
| | | | E | M | Border |
+-------------------------------+
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0);
TapeDevice.WritePort(port, value);
// Tape
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
/*
// Active AY Register
if ((port & 0xc002) == 0xc000)
{
var reg = value & 0x0f;
AYDevice.SelectedRegister = reg;
CPU.TotalExecutedCycles += 3;
}
// AY Write
if ((port & 0xc002) == 0x8000)
{
AYDevice.PortWrite(value);
CPU.TotalExecutedCycles += 3;
}
*/
}
}
}

View File

@ -0,0 +1,192 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
class ULA128 : ULABase
{
#region Construction
public ULA128(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
contentionTable = new byte[70930];
floatingBusTable = new short[70930];
for (int f = 0; f < 70930; f++)
floatingBusTable[f] = -1;
CharRows = 24;
CharCols = 32;
ScreenWidth = 256;
ScreenHeight = 192;
BorderTopHeight = 48;
BorderBottomHeight = 56;
BorderLeftWidth = 48;
BorderRightWidth = 48;
DisplayStart = 16384;
DisplayLength = 6144;
AttributeStart = 22528;
AttributeLength = 768;
borderColour = 7;
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
TstatesPerScanline = 228;
TstateAtTop = BorderTopHeight * TstatesPerScanline;
TstateAtBottom = BorderBottomHeight * TstatesPerScanline;
tstateToDisp = new short[FrameLength];
ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border
+ ScanLineWidth * ScreenHeight //border + main + border of 192 lines
+ ScanLineWidth * BorderBottomHeight]; //56 lines of border
attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped
SetupScreenSize();
Reset();
}
#endregion
#region Misc Operations
public override void Reset()
{
contentionStartPeriod = 14361; // + LateTiming;
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
screen = _machine.RAM5;
screenByteCtr = DisplayStart;
ULAByteCtr = 0;
actualULAStart = 14366 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming;
lastTState = actualULAStart;
BuildAttributeMap();
BuildContentionTable();
}
#endregion
#region Contention Methods
public override bool IsContended(int addr)
{
addr = addr & 0xc000;
if (addr == 0x4000)
{
// low port contention
return true;
}
if (addr == 0xc000)
{
// high port contention - check for contended bank paged in
switch (_machine.RAMPaged)
{
case 1:
case 3:
case 5:
case 7:
return true;
}
}
return false;
}
public override void BuildContentionTable()
{
int t = contentionStartPeriod;
while (t < contentionEndPeriod)
{
//for 128 t-states
for (int i = 0; i < 128; i += 8)
{
contentionTable[t++] = 6;
contentionTable[t++] = 5;
contentionTable[t++] = 4;
contentionTable[t++] = 3;
contentionTable[t++] = 2;
contentionTable[t++] = 1;
contentionTable[t++] = 0;
contentionTable[t++] = 0;
}
t += (TstatesPerScanline - 128);
}
//build top half of tstateToDisp table
//vertical retrace period
for (t = 0; t < actualULAStart; t++)
tstateToDisp[t] = 0;
//next 48 are actual border
while (t < actualULAStart + (TstateAtTop))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
//build middle half
int _x = 0;
int _y = 0;
int scrval = 2;
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline))
{
for (int g = 0; g < 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24; g < 24 + 128; g++)
{
//Map screenaddr to tstate
if (g % 4 == 0)
{
scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2));
_x += 8;
}
tstateToDisp[t++] = (short)scrval;
}
_y++;
for (int g = 24 + 128; g < 24 + 128 + 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++)
tstateToDisp[t++] = 0;
}
int h = contentionStartPeriod + 3;
while (h < contentionEndPeriod + 3)
{
for (int j = 0; j < 128; j += 8)
{
floatingBusTable[h] = tstateToDisp[h + 2];
floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)];
floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4];
floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)];
h += 8;
}
h += TstatesPerScanline - 128;
}
//build bottom half
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
}
#endregion
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BizHawk.Emulation.Cores.Components.Z80A;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128 : SpectrumBase
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
{
Spectrum = spectrum;
CPU = cpu;
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULA128(this);
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
AYDevice = new AYChip(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
InitJoysticks(joysticks);
TapeDevice = new DatacorderDevice();
TapeDevice.Init(this);
InitializeMedia(files);
}
#endregion
}
}

View File

@ -0,0 +1,31 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// The +2 is almost identical to the 128k from an emulation point of view
/// There are just a few small changes in the ROMs
/// </summary>
public partial class ZX128Plus2 : ZX128
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
: base(spectrum, cpu, borderType, files, joysticks)
{
}
#endregion
}
}

View File

@ -0,0 +1,429 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus2a : SpectrumBase
{
/* http://www.worldofspectrum.org/faq/reference/128kreference.htm
*
* Port 0x7ffd behaves in the almost exactly the same way as on the 128K/+2, with two exceptions:
Bit 4 is now the low bit of the ROM selection.
The partial decoding used is now slightly different: the hardware will respond only to those port addresses with bit 1 reset, bit 14 set and bit 15 reset (as opposed to just bits 1 and 15 reset on the 128K/+2).
The extra paging features of the +2A/+3 are controlled by port 0x1ffd (again, partial decoding applies here: the hardware will respond to all port addresses with bit 1 reset, bit 12 set and bits 13, 14 and 15 reset). This port is also write-only, and its last value should be saved at 0x5b67 (23399).
Port 0x1ffd responds as follows:
Bit 0: Paging mode. 0=normal, 1=special
Bit 1: In normal mode, ignored.
Bit 2: In normal mode, high bit of ROM selection. The four ROMs are:
ROM 0: 128k editor, menu system and self-test program
ROM 1: 128k syntax checker
ROM 2: +3DOS
ROM 3: 48 BASIC
Bit 3: Disk motor; 1=on, 0=off
Bit 4: Printer port strobe.
When special mode is selected, the memory map changes to one of four configurations specified in bits 1 and 2 of port 0x1ffd:
Bit 2 =0 Bit 2 =0 Bit 2 =1 Bit 2 =1
Bit 1 =0 Bit 1 =1 Bit 1 =0 Bit 1 =1
0xffff +--------+ +--------+ +--------+ +--------+
| Bank 3 | | Bank 7 | | Bank 3 | | Bank 3 |
| | | | | | | |
| | | | | | | |
| | | screen | | | | |
0xc000 +--------+ +--------+ +--------+ +--------+
| Bank 2 | | Bank 6 | | Bank 6 | | Bank 6 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x8000 +--------+ +--------+ +--------+ +--------+
| Bank 1 | | Bank 5 | | Bank 5 | | Bank 7 |
| | | | | | | |
| | | | | | | |
| | | screen | | screen | | screen |
0x4000 +--------+ +--------+ +--------+ +--------+
| Bank 0 | | Bank 4 | | Bank 4 | | Bank 4 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x0000 +--------+ +--------+ +--------+ +--------+
RAM banks 1,3,4 and 6 are used for the disc cache and RAMdisc, while Bank 7 contains editor scratchpads and +3DOS workspace.
*/
/// <summary>
/// Simulates reading from the bus (no contention)
/// 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;
// special paging
if (SpecialPagingMode)
{
switch (divisor)
{
case 0:
switch (PagingConfiguration)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
case 2:
case 3:
result = RAM4[addr % 0x4000];
break;
}
break;
case 1:
switch (PagingConfiguration)
{
case 0:
result = RAM1[addr % 0x4000];
break;
case 1:
case 2:
result = RAM5[addr % 0x4000];
break;
case 3:
result = RAM7[addr % 0x4000];
break;
}
break;
case 2:
switch (PagingConfiguration)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
case 2:
case 3:
result = RAM6[addr % 0x4000];
break;
}
break;
case 3:
switch (PagingConfiguration)
{
case 0:
case 2:
case 3:
result = RAM3[addr % 0x4000];
break;
case 1:
result = RAM7[addr % 0x4000];
break;
}
break;
}
}
else
{
switch (divisor)
{
// ROM 0x000
case 0:
switch (_ROMpaged)
{
case 0:
result = ROM0[addr % 0x4000];
TestForTapeTraps(addr % 0x4000);
break;
case 1:
result = ROM1[addr % 0x4000];
TestForTapeTraps(addr % 0x4000);
break;
case 2:
result = ROM2[addr % 0x4000];
break;
case 3:
result = ROM3[addr % 0x4000];
break;
}
break;
// RAM 0x4000 (RAM5 - Bank5 always)
case 1:
result = RAM5[addr % 0x4000];
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
result = RAM2[addr % 0x4000];
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
result = RAM1[addr % 0x4000];
break;
case 2:
result = RAM2[addr % 0x4000];
break;
case 3:
result = RAM3[addr % 0x4000];
break;
case 4:
result = RAM4[addr % 0x4000];
break;
case 5:
result = RAM5[addr % 0x4000];
break;
case 6:
result = RAM6[addr % 0x4000];
break;
case 7:
result = RAM7[addr % 0x4000];
break;
}
break;
default:
break;
}
}
return result;
}
/// <summary>
/// Simulates writing to the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public override void WriteBus(ushort addr, byte value)
{
int divisor = addr / 0x4000;
// special paging
if (SpecialPagingMode)
{
switch (divisor)
{
case 0:
switch (PagingConfiguration)
{
case 0:
RAM0[addr % 0x4000] = value;
break;
case 1:
case 2:
case 3:
RAM4[addr % 0x4000] = value;
break;
}
break;
case 1:
switch (PagingConfiguration)
{
case 0:
RAM1[addr % 0x4000] = value;
break;
case 1:
case 2:
RAM5[addr % 0x4000] = value;
break;
case 3:
RAM7[addr % 0x4000] = value;
break;
}
break;
case 2:
switch (PagingConfiguration)
{
case 0:
RAM2[addr % 0x4000] = value;
break;
case 1:
case 2:
case 3:
RAM6[addr % 0x4000] = value;
break;
}
break;
case 3:
switch (PagingConfiguration)
{
case 0:
case 2:
case 3:
RAM3[addr % 0x4000] = value;
break;
case 1:
RAM7[addr % 0x4000] = value;
break;
}
break;
}
}
else
{
switch (divisor)
{
// ROM 0x000
case 0:
/*
switch (_ROMpaged)
{
// cannot write to ROMs
case 0:
ROM0[addr % 0x4000] = value;
break;
case 1:
ROM1[addr % 0x4000] = value;
break;
case 2:
ROM2[addr % 0x4000] = value;
break;
case 3:
ROM3[addr % 0x4000] = value;
break;
}
*/
break;
// RAM 0x4000 (RAM5 - Bank5 only)
case 1:
RAM5[addr % 0x4000] = value;
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
RAM2[addr % 0x4000] = value;
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
RAM0[addr % 0x4000] = value;
break;
case 1:
RAM1[addr % 0x4000] = value;
break;
case 2:
RAM2[addr % 0x4000] = value;
break;
case 3:
RAM3[addr % 0x4000] = value;
break;
case 4:
RAM4[addr % 0x4000] = value;
break;
case 5:
RAM5[addr % 0x4000] = value;
break;
case 6:
RAM6[addr % 0x4000] = value;
break;
case 7:
RAM7[addr % 0x4000] = value;
break;
}
break;
default:
break;
}
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
/// Reads a byte of data from a specified memory address
/// (with memory contention if appropriate)
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
WriteBus(addr, value);
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)
/// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte FetchScreenMemory(ushort addr)
{
byte value = new byte();
if (SHADOWPaged && !PagingDisabled)
{
// shadow screen should be outputted
// this lives in RAM7
value = RAM7[addr & 0x3FFF];
}
else
{
// shadow screen is not set to display or paging is disabled (probably in 48k mode)
// (use screen0 at RAM5)
value = RAM5[addr & 0x3FFF];
}
return value;
}
/// <summary>
/// Sets up the ROM
/// </summary>
/// <param name="buffer"></param>
/// <param name="startAddress"></param>
public override void InitROM(RomData romData)
{
RomData = romData;
// +3 uses ROM0, ROM1, ROM2 & ROM3
/* ROM 0: 128k editor, menu system and self-test program
ROM 1: 128k syntax checker
ROM 2: +3DOS
ROM 3: 48 BASIC
*/
Stream stream = new MemoryStream(RomData.RomBytes);
stream.Read(ROM0, 0, 16384);
stream.Read(ROM1, 0, 16384);
stream.Read(ROM2, 0, 16384);
stream.Read(ROM3, 0, 16384);
stream.Dispose();
}
}
}

View File

@ -0,0 +1,296 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus2a : SpectrumBase
{
/// <summary>
/// Reads a byte of data from a specified port address
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public override byte ReadPort(ushort port)
{
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
// check AY
if (AYDevice.ReadPort(port, ref result))
return (byte)result;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
InputRead = true;
}
else
{
if (KeyboardDevice.ReadPort(port, ref result))
{
// not a lagframe
InputRead = true;
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else
deviceAddressed = false;
}
if (!deviceAddressed)
{
// If this is an unused port the floating memory bus should be returned
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
result = 0xff;
}
else
{
if (ULADevice.floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
}
/*
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = (port & 0x0001) == 0;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
InputRead = true;
}
else if (lowBitReset)
{
// Even I/O address so get input from keyboard
KeyboardDevice.ReadPort(port, ref result);
TapeDevice.MonitorRead();
// not a lagframe
InputRead = true;
// tape loading monitor cycle
//TapeDevice.MonitorRead();
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else
{
// devices other than the ULA will respond here
// (e.g. the AY sound chip in a 128k spectrum
// AY register activate - on +3/2a both FFFD and BFFD active AY
if ((port & 0xc002) == 0xc000)
{
result = (int)AYDevice.PortRead();
}
else if ((port & 0xc002) == 0x8000)
{
result = (int)AYDevice.PortRead();
}
// Kempston Mouse
/*
else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset?
{
//result = udpDrive.DiskStatusRead();
// disk drive is not yet implemented - return a max status byte for the menu to load
result = 255;
}
else if ((port & 0xF002) == 0x3000)
{
//result = udpDrive.DiskReadByte();
result = 0;
}
else if ((port & 0xF002) == 0x0)
{
if (PagingDisabled)
result = 0x1;
else
result = 0xff;
}
*//*
// if unused port the floating memory bus should be returned (still todo)
}
*/
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)
{
// process IO contention
ContendPortAddress(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
// get a BitArray of the value byte
BitArray bits = new BitArray(new byte[] { value });
// Check whether the low bit is reset
bool lowBitReset = !portBits[0]; // (port & 0x01) == 0;
AYDevice.WritePort(port, value);
// port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
if (port == 0x7ffd)
{
if (!PagingDisabled)
{
// bits 0, 1, 2 select the RAM page
var rp = value & 0x07;
if (rp < 8)
RAMPaged = rp;
// bit 3 controls shadow screen
SHADOWPaged = bits[3];
// Bit 5 set signifies that paging is disabled until next reboot
PagingDisabled = bits[5];
// portbit 4 is the LOW BIT of the ROM selection
ROMlow = bits[4];
}
}
// port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set
if (port == 0x1ffd)
{
if (!PagingDisabled)
{
if (!bits[0])
{
// special paging is not enabled - get the ROMpage high byte
ROMhigh = bits[2];
// set the special paging mode flag
SpecialPagingMode = false;
}
else
{
// special paging is enabled
// this is decided based on combinations of bits 1 & 2
// Config 0 = Bit1-0 Bit2-0
// Config 1 = Bit1-1 Bit2-0
// Config 2 = Bit1-0 Bit2-1
// Config 3 = Bit1-1 Bit2-1
BitArray confHalfNibble = new BitArray(2);
confHalfNibble[0] = bits[1];
confHalfNibble[1] = bits[2];
// set special paging configuration
PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble);
// set the special paging mode flag
SpecialPagingMode = true;
}
}
// bit 3 controls the disk motor (1=on, 0=off)
DiskMotorState = bits[3];
// bit 4 is the printer port strobe
PrinterPortStrobe = bits[4];
}
// Only even addresses address the ULA
if (lowBitReset)
{
// store the last OUT byte
LastULAOutByte = value;
/*
Bit 7 6 5 4 3 2 1 0
+-------------------------------+
| | | | E | M | Border |
+-------------------------------+
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0);
// Tape
TapeDevice.WritePort(port, value);
// Tape
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
LastULAOutByte = value;
}
/// <summary>
/// +3 and 2a overidden method
/// </summary>
public override int _ROMpaged
{
get
{
// calculate the ROMpage from the high and low bits
var rp = ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh }));
if (rp != 0)
{
}
return rp;
}
set { ROMPaged = value; }
}
/// <summary>
/// Override port contention
/// +3/2a does not have the same ULA IO contention
/// </summary>
/// <param name="addr"></param>
public override void ContendPortAddress(ushort addr)
{
CPU.TotalExecutedCycles += 4;
}
}
}

View File

@ -0,0 +1,196 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
class ULAPlus2a : ULABase
{
#region Construction
public ULAPlus2a(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
contentionTable = new byte[70930];
floatingBusTable = new short[70930];
for (int f = 0; f < 70930; f++)
floatingBusTable[f] = -1;
CharRows = 24;
CharCols = 32;
ScreenWidth = 256;
ScreenHeight = 192;
BorderTopHeight = 48;
BorderBottomHeight = 56;
BorderLeftWidth = 48;
BorderRightWidth = 48;
DisplayStart = 16384;
DisplayLength = 6144;
AttributeStart = 22528;
AttributeLength = 768;
borderColour = 7;
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
TstatesPerScanline = 228;
TstateAtTop = BorderTopHeight * TstatesPerScanline;
TstateAtBottom = BorderBottomHeight * TstatesPerScanline;
tstateToDisp = new short[FrameLength];
ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border
+ ScanLineWidth * ScreenHeight //border + main + border of 192 lines
+ ScanLineWidth * BorderBottomHeight]; //56 lines of border
attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped
SetupScreenSize();
Reset();
}
#endregion
#region Misc Operations
public override void Reset()
{
contentionStartPeriod = 14361; // + LateTiming;
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
screen = _machine.RAM5;
screenByteCtr = DisplayStart;
ULAByteCtr = 0;
actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming;
lastTState = actualULAStart;
BuildAttributeMap();
BuildContentionTable();
}
#endregion
#region Contention Methods
public override bool IsContended(int addr)
{
addr = addr & 0xc000;
if (addr == 0x4000)
{
// low port contention
return true;
}
if (addr == 0xc000)
{
// high port contention - check for contended bank paged in
switch (_machine.RAMPaged)
{
case 4:
case 5:
case 6:
case 7:
return true;
}
}
return false;
}
public override void BuildContentionTable()
{
int t = contentionStartPeriod;
while (t < contentionEndPeriod)
{
contentionTable[t++] = 1;
contentionTable[t++] = 0;
//for 128 t-states
for (int i = 0; i < 128; i += 8)
{
contentionTable[t++] = 7;
contentionTable[t++] = 6;
contentionTable[t++] = 5;
contentionTable[t++] = 4;
contentionTable[t++] = 3;
contentionTable[t++] = 2;
contentionTable[t++] = 1;
contentionTable[t++] = 0;
}
t += (TstatesPerScanline - 128) - 2;
}
//build top half of tstateToDisp table
//vertical retrace period
for (t = 0; t < actualULAStart; t++)
tstateToDisp[t] = 0;
//next 48 are actual border
while (t < actualULAStart + (TstateAtTop))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
//build middle half
int _x = 0;
int _y = 0;
int scrval = 2;
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline))
{
for (int g = 0; g < 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24; g < 24 + 128; g++)
{
//Map screenaddr to tstate
if (g % 4 == 0)
{
scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2));
_x += 8;
}
tstateToDisp[t++] = (short)scrval;
}
_y++;
for (int g = 24 + 128; g < 24 + 128 + 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++)
tstateToDisp[t++] = 0;
}
int h = contentionStartPeriod + 3;
while (h < contentionEndPeriod + 3)
{
for (int j = 0; j < 128; j += 8)
{
floatingBusTable[h] = tstateToDisp[h + 2];
floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)];
floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4];
floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)];
h += 8;
}
h += TstatesPerScanline - 128;
}
//build bottom half
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
}
#endregion
}
}

View File

@ -0,0 +1,49 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus2a : SpectrumBase
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX128Plus2a(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
{
Spectrum = spectrum;
CPU = cpu;
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULAPlus2a(this);
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
AYDevice = new AYChip(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
InitJoysticks(joysticks);
TapeDevice = new DatacorderDevice();
TapeDevice.Init(this);
InitializeMedia(files);
}
#endregion
}
}

View File

@ -0,0 +1,429 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus3 : SpectrumBase
{
/* http://www.worldofspectrum.org/faq/reference/128kreference.htm
*
* Port 0x7ffd behaves in the almost exactly the same way as on the 128K/+2, with two exceptions:
Bit 4 is now the low bit of the ROM selection.
The partial decoding used is now slightly different: the hardware will respond only to those port addresses with bit 1 reset, bit 14 set and bit 15 reset (as opposed to just bits 1 and 15 reset on the 128K/+2).
The extra paging features of the +2A/+3 are controlled by port 0x1ffd (again, partial decoding applies here: the hardware will respond to all port addresses with bit 1 reset, bit 12 set and bits 13, 14 and 15 reset). This port is also write-only, and its last value should be saved at 0x5b67 (23399).
Port 0x1ffd responds as follows:
Bit 0: Paging mode. 0=normal, 1=special
Bit 1: In normal mode, ignored.
Bit 2: In normal mode, high bit of ROM selection. The four ROMs are:
ROM 0: 128k editor, menu system and self-test program
ROM 1: 128k syntax checker
ROM 2: +3DOS
ROM 3: 48 BASIC
Bit 3: Disk motor; 1=on, 0=off
Bit 4: Printer port strobe.
When special mode is selected, the memory map changes to one of four configurations specified in bits 1 and 2 of port 0x1ffd:
Bit 2 =0 Bit 2 =0 Bit 2 =1 Bit 2 =1
Bit 1 =0 Bit 1 =1 Bit 1 =0 Bit 1 =1
0xffff +--------+ +--------+ +--------+ +--------+
| Bank 3 | | Bank 7 | | Bank 3 | | Bank 3 |
| | | | | | | |
| | | | | | | |
| | | screen | | | | |
0xc000 +--------+ +--------+ +--------+ +--------+
| Bank 2 | | Bank 6 | | Bank 6 | | Bank 6 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x8000 +--------+ +--------+ +--------+ +--------+
| Bank 1 | | Bank 5 | | Bank 5 | | Bank 7 |
| | | | | | | |
| | | | | | | |
| | | screen | | screen | | screen |
0x4000 +--------+ +--------+ +--------+ +--------+
| Bank 0 | | Bank 4 | | Bank 4 | | Bank 4 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x0000 +--------+ +--------+ +--------+ +--------+
RAM banks 1,3,4 and 6 are used for the disc cache and RAMdisc, while Bank 7 contains editor scratchpads and +3DOS workspace.
*/
/// <summary>
/// Simulates reading from the bus (no contention)
/// 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;
// special paging
if (SpecialPagingMode)
{
switch (divisor)
{
case 0:
switch (PagingConfiguration)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
case 2:
case 3:
result = RAM4[addr % 0x4000];
break;
}
break;
case 1:
switch (PagingConfiguration)
{
case 0:
result = RAM1[addr % 0x4000];
break;
case 1:
case 2:
result = RAM5[addr % 0x4000];
break;
case 3:
result = RAM7[addr % 0x4000];
break;
}
break;
case 2:
switch (PagingConfiguration)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
case 2:
case 3:
result = RAM6[addr % 0x4000];
break;
}
break;
case 3:
switch (PagingConfiguration)
{
case 0:
case 2:
case 3:
result = RAM3[addr % 0x4000];
break;
case 1:
result = RAM7[addr % 0x4000];
break;
}
break;
}
}
else
{
switch (divisor)
{
// ROM 0x000
case 0:
switch (_ROMpaged)
{
case 0:
result = ROM0[addr % 0x4000];
TestForTapeTraps(addr % 0x4000);
break;
case 1:
result = ROM1[addr % 0x4000];
TestForTapeTraps(addr % 0x4000);
break;
case 2:
result = ROM2[addr % 0x4000];
break;
case 3:
result = ROM3[addr % 0x4000];
break;
}
break;
// RAM 0x4000 (RAM5 - Bank5 always)
case 1:
result = RAM5[addr % 0x4000];
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
result = RAM2[addr % 0x4000];
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
result = RAM0[addr % 0x4000];
break;
case 1:
result = RAM1[addr % 0x4000];
break;
case 2:
result = RAM2[addr % 0x4000];
break;
case 3:
result = RAM3[addr % 0x4000];
break;
case 4:
result = RAM4[addr % 0x4000];
break;
case 5:
result = RAM5[addr % 0x4000];
break;
case 6:
result = RAM6[addr % 0x4000];
break;
case 7:
result = RAM7[addr % 0x4000];
break;
}
break;
default:
break;
}
}
return result;
}
/// <summary>
/// Simulates writing to the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public override void WriteBus(ushort addr, byte value)
{
int divisor = addr / 0x4000;
// special paging
if (SpecialPagingMode)
{
switch (divisor)
{
case 0:
switch (PagingConfiguration)
{
case 0:
RAM0[addr % 0x4000] = value;
break;
case 1:
case 2:
case 3:
RAM4[addr % 0x4000] = value;
break;
}
break;
case 1:
switch (PagingConfiguration)
{
case 0:
RAM1[addr % 0x4000] = value;
break;
case 1:
case 2:
RAM5[addr % 0x4000] = value;
break;
case 3:
RAM7[addr % 0x4000] = value;
break;
}
break;
case 2:
switch (PagingConfiguration)
{
case 0:
RAM2[addr % 0x4000] = value;
break;
case 1:
case 2:
case 3:
RAM6[addr % 0x4000] = value;
break;
}
break;
case 3:
switch (PagingConfiguration)
{
case 0:
case 2:
case 3:
RAM3[addr % 0x4000] = value;
break;
case 1:
RAM7[addr % 0x4000] = value;
break;
}
break;
}
}
else
{
switch (divisor)
{
// ROM 0x000
case 0:
/*
switch (_ROMpaged)
{
// cannot write to ROMs
case 0:
ROM0[addr % 0x4000] = value;
break;
case 1:
ROM1[addr % 0x4000] = value;
break;
case 2:
ROM2[addr % 0x4000] = value;
break;
case 3:
ROM3[addr % 0x4000] = value;
break;
}
*/
break;
// RAM 0x4000 (RAM5 - Bank5 only)
case 1:
RAM5[addr % 0x4000] = value;
break;
// RAM 0x8000 (RAM2 - Bank2)
case 2:
RAM2[addr % 0x4000] = value;
break;
// RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0)
case 3:
switch (RAMPaged)
{
case 0:
RAM0[addr % 0x4000] = value;
break;
case 1:
RAM1[addr % 0x4000] = value;
break;
case 2:
RAM2[addr % 0x4000] = value;
break;
case 3:
RAM3[addr % 0x4000] = value;
break;
case 4:
RAM4[addr % 0x4000] = value;
break;
case 5:
RAM5[addr % 0x4000] = value;
break;
case 6:
RAM6[addr % 0x4000] = value;
break;
case 7:
RAM7[addr % 0x4000] = value;
break;
}
break;
default:
break;
}
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
/// Reads a byte of data from a specified memory address
/// (with memory contention if appropriate)
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
WriteBus(addr, value);
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)
/// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte FetchScreenMemory(ushort addr)
{
byte value = new byte();
if (SHADOWPaged && !PagingDisabled)
{
// shadow screen should be outputted
// this lives in RAM7
value = RAM7[addr & 0x3FFF];
}
else
{
// shadow screen is not set to display or paging is disabled (probably in 48k mode)
// (use screen0 at RAM5)
value = RAM5[addr & 0x3FFF];
}
return value;
}
/// <summary>
/// Sets up the ROM
/// </summary>
/// <param name="buffer"></param>
/// <param name="startAddress"></param>
public override void InitROM(RomData romData)
{
RomData = romData;
// +3 uses ROM0, ROM1, ROM2 & ROM3
/* ROM 0: 128k editor, menu system and self-test program
ROM 1: 128k syntax checker
ROM 2: +3DOS
ROM 3: 48 BASIC
*/
Stream stream = new MemoryStream(RomData.RomBytes);
stream.Read(ROM0, 0, 16384);
stream.Read(ROM1, 0, 16384);
stream.Read(ROM2, 0, 16384);
stream.Read(ROM3, 0, 16384);
stream.Dispose();
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus3 : SpectrumBase
{
/// <summary>
/// Reads a byte of data from a specified port address
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public override byte ReadPort(ushort port)
{
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
// check AY
if (AYDevice.ReadPort(port, ref result))
return (byte)result;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
InputRead = true;
}
else
{
if (KeyboardDevice.ReadPort(port, ref result))
{
// not a lagframe
InputRead = true;
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else
deviceAddressed = false;
}
if (!deviceAddressed)
{
// If this is an unused port the floating memory bus should be returned
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
result = 0xff;
}
else
{
if (ULADevice.floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
}
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)
{
// process IO contention
ContendPortAddress(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
// get a BitArray of the value byte
BitArray bits = new BitArray(new byte[] { value });
// Check whether the low bit is reset
bool lowBitReset = !portBits[0]; // (port & 0x01) == 0;
AYDevice.WritePort(port, value);
// port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
if (port == 0x7ffd)
{
if (!PagingDisabled)
{
// bits 0, 1, 2 select the RAM page
var rp = value & 0x07;
if (rp < 8)
RAMPaged = rp;
// bit 3 controls shadow screen
SHADOWPaged = bits[3];
// Bit 5 set signifies that paging is disabled until next reboot
PagingDisabled = bits[5];
// portbit 4 is the LOW BIT of the ROM selection
ROMlow = bits[4];
}
}
// port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set
if (port == 0x1ffd)
{
if (!PagingDisabled)
{
if (!bits[0])
{
// special paging is not enabled - get the ROMpage high byte
ROMhigh = bits[2];
// set the special paging mode flag
SpecialPagingMode = false;
}
else
{
// special paging is enabled
// this is decided based on combinations of bits 1 & 2
// Config 0 = Bit1-0 Bit2-0
// Config 1 = Bit1-1 Bit2-0
// Config 2 = Bit1-0 Bit2-1
// Config 3 = Bit1-1 Bit2-1
BitArray confHalfNibble = new BitArray(2);
confHalfNibble[0] = bits[1];
confHalfNibble[1] = bits[2];
// set special paging configuration
PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble);
// set the special paging mode flag
SpecialPagingMode = true;
}
}
// bit 3 controls the disk motor (1=on, 0=off)
DiskMotorState = bits[3];
// bit 4 is the printer port strobe
PrinterPortStrobe = bits[4];
}
// Only even addresses address the ULA
if (lowBitReset)
{
// store the last OUT byte
LastULAOutByte = value;
/*
Bit 7 6 5 4 3 2 1 0
+-------------------------------+
| | | | E | M | Border |
+-------------------------------+
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0);
// Tape
TapeDevice.WritePort(port, value);
// Tape
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
LastULAOutByte = value;
}
/// <summary>
/// +3 and 2a overidden method
/// </summary>
public override int _ROMpaged
{
get
{
// calculate the ROMpage from the high and low bits
var rp = ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh }));
if (rp != 0)
{
}
return rp;
}
set { ROMPaged = value; }
}
/// <summary>
/// Override port contention
/// +3/2a does not have the same ULA IO contention
/// </summary>
/// <param name="addr"></param>
public override void ContendPortAddress(ushort addr)
{
CPU.TotalExecutedCycles += 4;
}
}
}

View File

@ -0,0 +1,196 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
class ULAPlus3 : ULABase
{
#region Construction
public ULAPlus3(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
contentionTable = new byte[70930];
floatingBusTable = new short[70930];
for (int f = 0; f < 70930; f++)
floatingBusTable[f] = -1;
CharRows = 24;
CharCols = 32;
ScreenWidth = 256;
ScreenHeight = 192;
BorderTopHeight = 48;
BorderBottomHeight = 56;
BorderLeftWidth = 48;
BorderRightWidth = 48;
DisplayStart = 16384;
DisplayLength = 6144;
AttributeStart = 22528;
AttributeLength = 768;
borderColour = 7;
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
TstatesPerScanline = 228;
TstateAtTop = BorderTopHeight * TstatesPerScanline;
TstateAtBottom = BorderBottomHeight * TstatesPerScanline;
tstateToDisp = new short[FrameLength];
ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border
+ ScanLineWidth * ScreenHeight //border + main + border of 192 lines
+ ScanLineWidth * BorderBottomHeight]; //56 lines of border
attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped
SetupScreenSize();
Reset();
}
#endregion
#region Misc Operations
public override void Reset()
{
contentionStartPeriod = 14361; // + LateTiming;
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
screen = _machine.RAM5;
screenByteCtr = DisplayStart;
ULAByteCtr = 0;
actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming;
lastTState = actualULAStart;
BuildAttributeMap();
BuildContentionTable();
}
#endregion
#region Contention Methods
public override bool IsContended(int addr)
{
addr = addr & 0xc000;
if (addr == 0x4000)
{
// low port contention
return true;
}
if (addr == 0xc000)
{
// high port contention - check for contended bank paged in
switch (_machine.RAMPaged)
{
case 4:
case 5:
case 6:
case 7:
return true;
}
}
return false;
}
public override void BuildContentionTable()
{
int t = contentionStartPeriod;
while (t < contentionEndPeriod)
{
contentionTable[t++] = 1;
contentionTable[t++] = 0;
//for 128 t-states
for (int i = 0; i < 128; i += 8)
{
contentionTable[t++] = 7;
contentionTable[t++] = 6;
contentionTable[t++] = 5;
contentionTable[t++] = 4;
contentionTable[t++] = 3;
contentionTable[t++] = 2;
contentionTable[t++] = 1;
contentionTable[t++] = 0;
}
t += (TstatesPerScanline - 128) - 2;
}
//build top half of tstateToDisp table
//vertical retrace period
for (t = 0; t < actualULAStart; t++)
tstateToDisp[t] = 0;
//next 48 are actual border
while (t < actualULAStart + (TstateAtTop))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
//build middle half
int _x = 0;
int _y = 0;
int scrval = 2;
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline))
{
for (int g = 0; g < 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24; g < 24 + 128; g++)
{
//Map screenaddr to tstate
if (g % 4 == 0)
{
scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2));
_x += 8;
}
tstateToDisp[t++] = (short)scrval;
}
_y++;
for (int g = 24 + 128; g < 24 + 128 + 24; g++)
tstateToDisp[t++] = 1;
for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++)
tstateToDisp[t++] = 0;
}
int h = contentionStartPeriod + 3;
while (h < contentionEndPeriod + 3)
{
for (int j = 0; j < 128; j += 8)
{
floatingBusTable[h] = tstateToDisp[h + 2];
floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)];
floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4];
floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)];
h += 8;
}
h += TstatesPerScanline - 128;
}
//build bottom half
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
{
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
}
#endregion
}
}

View File

@ -0,0 +1,49 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX128Plus3 : SpectrumBase
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
{
Spectrum = spectrum;
CPU = cpu;
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULAPlus3(this);
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
AYDevice = new AYChip(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
InitJoysticks(joysticks);
TapeDevice = new DatacorderDevice();
TapeDevice.Init(this);
InitializeMedia(files);
}
#endregion
}
}

View File

@ -0,0 +1,142 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class ZX16 : ZX48
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
: base(spectrum, cpu, borderType, files, joysticks)
{
}
#endregion
#region Memory
/* 48K Spectrum has NO memory paging
*
*
| Bank 0 |
| |
| |
| screen |
0x4000 +--------+
| ROM 0 |
| |
| |
| |
0x0000 +--------+
*/
/// <summary>
/// Simulates reading from the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadBus(ushort addr)
{
int divisor = addr / 0x4000;
var index = addr % 0x4000;
// paging logic goes here
switch (divisor)
{
case 0:
TestForTapeTraps(addr % 0x4000);
return ROM0[index];
case 1: return RAM0[index];
default:
// memory does not exist
return 0xff;
}
}
/// <summary>
/// Simulates writing to the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public override void WriteBus(ushort addr, byte value)
{
int divisor = addr / 0x4000;
var index = addr % 0x4000;
// paging logic goes here
switch (divisor)
{
case 0:
// cannot write to ROM
break;
case 1:
RAM0[index] = value;
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
/// Reads a byte of data from a specified memory address
/// (with memory contention if appropriate)
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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 16/48k machines only ROM0 is used (no paging)
RomData.RomBytes?.CopyTo(ROM0, 0);
}
#endregion
}
}

View File

@ -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.SinclairSpectrum
{
public partial class ZX48 : SpectrumBase
{
/* 48K Spectrum has NO memory paging
*
* 0xffff +--------+
| Bank 2 |
| |
| |
| |
0xc000 +--------+
| Bank 1 |
| |
| |
| |
0x8000 +--------+
| Bank 0 |
| |
| |
| screen |
0x4000 +--------+
| ROM 0 |
| |
| |
| |
0x0000 +--------+
*/
/// <summary>
/// Simulates reading from the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadBus(ushort addr)
{
int divisor = addr / 0x4000;
var index = addr % 0x4000;
// paging logic goes here
switch (divisor)
{
case 0: return ROM0[index];
case 1: return RAM0[index];
case 2: return RAM1[index];
case 3: return RAM2[index];
default: return 0;
}
}
/// <summary>
/// Simulates writing to the bus (no contention)
/// Paging should be handled here
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public override void WriteBus(ushort addr, byte value)
{
int divisor = addr / 0x4000;
var index = addr % 0x4000;
// paging logic goes here
switch (divisor)
{
case 0:
// cannot write to ROM
break;
case 1:
RAM0[index] = value;
break;
case 2:
RAM1[index] = value;
break;
case 3:
RAM2[index] = value;
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
/// Reads a byte of data from a specified memory address
/// (with memory contention if appropriate)
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
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 16/48k machines only ROM0 is used (no paging)
RomData.RomBytes?.CopyTo(ROM0, 0);
}
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX48 : SpectrumBase
{
/// <summary>
/// Reads a byte of data from a specified port address
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public override byte ReadPort(ushort port)
{
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = (port & 0x0001) == 0;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
if ((port & 0xe0) == 0 || (port & 0x20) == 0)
{
if (LocateUniqueJoystick(JoystickType.Kempston) != null)
return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
// not a lag frame
InputRead = true;
}
else if (lowBitReset)
{
// Even I/O address so get input from keyboard
KeyboardDevice.ReadPort(port, ref result);
// not a lagframe
InputRead = true;
// process tape INs
TapeDevice.ReadPort(port, ref result);
}
else
{
// devices other than the ULA will respond here
// (e.g. the AY sound chip in a 128k spectrum
// AY register activate - no AY chip in a 48k spectrum
// Kemptson Mouse (not implemented yet)
// If this is an unused port the floating memory bus should be returned
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
result = 0xff;
}
else
{
if (ULADevice.floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
}
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)
{
// process IO contention
ContendPortAddress(port);
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
if ((port & 0x0001) != 0)
return;
// store the last OUT byte
LastULAOutByte = value;
/*
Bit 7 6 5 4 3 2 1 0
+-------------------------------+
| | | | E | M | Border |
+-------------------------------+
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
{
// border value has changed - update the screen buffer
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
ULADevice.borderColour = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0);
// Tape
TapeDevice.WritePort(port, value);
// Tape mic processing (not implemented yet)
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
}
}

View File

@ -0,0 +1,180 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
class ULA48 : ULABase
{
#region Construction
public ULA48(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 32;
LongestOperationCycles = 64;
FrameLength = 69888;
ClockSpeed = 3500000;
contentionTable = new byte[70930];
floatingBusTable = new short[70930];
for (int f = 0; f < 70930; f++)
floatingBusTable[f] = -1;
CharRows = 24;
CharCols = 32;
ScreenWidth = 256;
ScreenHeight = 192;
BorderTopHeight = 48;
BorderBottomHeight = 56;
BorderLeftWidth = 48;
BorderRightWidth = 48;
DisplayStart = 16384;
DisplayLength = 6144;
AttributeStart = 22528;
AttributeLength = 768;
borderColour = 7;
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
TstatesPerScanline = 224;
TstateAtTop = BorderTopHeight * TstatesPerScanline;
TstateAtBottom = BorderBottomHeight * TstatesPerScanline;
tstateToDisp = new short[FrameLength];
ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border
+ ScanLineWidth * ScreenHeight //border + main + border of 192 lines
+ ScanLineWidth * BorderBottomHeight]; //56 lines of border
attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped
SetupScreenSize();
Reset();
}
#endregion
#region Misc Operations
public override void Reset()
{
contentionStartPeriod = 14335; // + LateTiming;
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
screen = _machine.RAM0;
screenByteCtr = DisplayStart;
ULAByteCtr = 0;
actualULAStart = 14340 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming;
lastTState = actualULAStart;
BuildAttributeMap();
BuildContentionTable();
}
#endregion
#region Contention Methods
public override bool IsContended(int addr)
{
if ((addr & 49152) == 16384)
return true;
return false;
}
public override void BuildContentionTable()
{
int t = contentionStartPeriod;
while (t < contentionEndPeriod)
{
//for 128 t-states
for (int i = 0; i < 128; i += 8)
{
contentionTable[t++] = 6;
contentionTable[t++] = 5;
contentionTable[t++] = 4;
contentionTable[t++] = 3;
contentionTable[t++] = 2;
contentionTable[t++] = 1;
contentionTable[t++] = 0;
contentionTable[t++] = 0;
}
t += (TstatesPerScanline - 128); //24 tstates of right border + left border + 48 tstates of retrace
}
//build top half of tstateToDisp table
//vertical retrace period
for (t = 0; t < actualULAStart; t++)
tstateToDisp[t] = 0;
//next 48 are actual border
while (t < actualULAStart + (TstateAtTop))
{
//border(24t) + screen (128t) + border(24t) = 176 tstates
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
//horizontal retrace
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
//build middle half of display
int _x = 0;
int _y = 0;
int scrval = 2;
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline))
{
//left border
for (int g = 0; g < 24; g++)
tstateToDisp[t++] = 1;
//screen
for (int g = 24; g < 24 + 128; g++)
{
//Map screenaddr to tstate
if (g % 4 == 0)
{
scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2));
_x += 8;
}
tstateToDisp[t++] = (short)scrval;
}
_y++;
//right border
for (int g = 24 + 128; g < 24 + 128 + 24; g++)
tstateToDisp[t++] = 1;
//horizontal retrace
for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 48; g++)
tstateToDisp[t++] = 0;
}
int h = contentionStartPeriod + 3;
while (h < contentionEndPeriod + 3)
{
for (int j = 0; j < 128; j += 8)
{
floatingBusTable[h] = tstateToDisp[h + 2]; //screen address
floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; //attr address
floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; //screen address + 1
floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; //attr address + 1
h += 8;
}
h += TstatesPerScanline - 128;
}
//build bottom border
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
{
//border(24t) + screen (128t) + border(24t) = 176 tstates
for (int g = 0; g < 176; g++)
tstateToDisp[t++] = 1;
//horizontal retrace
for (int g = 176; g < TstatesPerScanline; g++)
tstateToDisp[t++] = 0;
}
}
#endregion
}
}

View File

@ -0,0 +1,41 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZX48 : SpectrumBase
{
#region Construction
/// <summary>
/// Main constructor
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List<byte[]> files, List<JoystickType> joysticks)
{
Spectrum = spectrum;
CPU = cpu;
ULADevice = new ULA48(this);
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
InitJoysticks(joysticks);
TapeDevice = new DatacorderDevice();
TapeDevice.Init(this);
InitializeMedia(files);
}
#endregion
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents the different types of media serializer avaiable
/// </summary>
public enum MediaSerializationType
{
NONE,
TZX,
TAP
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Abtract class that represents all Media Serializers
/// </summary>
public abstract class MediaSerializer
{
/// <summary>
/// The type of serializer
/// </summary>
public abstract MediaSerializationType FormatType { get; }
/// <summary>
/// Signs whether this class can be used to serialize
/// </summary>
public virtual bool IsSerializer
{
get
{
return false;
}
}
/// <summary>
/// Signs whether this class can be used to de-serialize
/// </summary>
public virtual bool IsDeSerializer
{
get
{
return false;
}
}
/// <summary>
/// Serialization method
/// </summary>
/// <param name="data"></param>
public virtual void Serialize(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
"Serialize operation is not implemented for this serializer");
}
/// <summary>
/// DeSerialization method
/// </summary>
/// <param name="data"></param>
public virtual void DeSerialize(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
"DeSerialize operation is not implemented for this serializer");
}
#region Static Tools
/// <summary>
/// Converts an int32 value into a byte array
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected 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>
protected 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>
protected 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>
protected static void SetWordValue(byte[] buf, int offsetIndex, ushort value)
{
buf[offsetIndex] = (byte)value;
buf[offsetIndex + 1] = (byte)(value >> 8);
}
#endregion
}
}

View File

@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Reponsible for TAP format serializaton
/// </summary>
public class TapSerializer : MediaSerializer
{
/// <summary>
/// The type of serializer
/// </summary>
private MediaSerializationType _formatType = MediaSerializationType.TAP;
public override MediaSerializationType FormatType
{
get
{
return _formatType;
}
}
/// <summary>
/// Signs whether this class can be used to serialize
/// </summary>
public override bool IsSerializer { get { return false; } }
/// <summary>
/// Signs whether this class can be used to de-serialize
/// </summary>
public override bool IsDeSerializer { get { return true; } }
#region Construction
private DatacorderDevice _datacorder;
public TapSerializer(DatacorderDevice _tapeDevice)
{
_datacorder = _tapeDevice;
}
#endregion
#region TAP Format Constants
/// <summary>
/// Pilot pulse length
/// </summary>
public const int PILOT_PL = 2168;
/// <summary>
/// Pilot pulses in the ROM header block
/// </summary>
public const int HEADER_PILOT_COUNT = 8063;
/// <summary>
/// Pilot pulses in the ROM data block
/// </summary>
public const int DATA_PILOT_COUNT = 3223;
/// <summary>
/// Sync 1 pulse length
/// </summary>
public const int SYNC_1_PL = 667;
/// <summary>
/// Sync 2 pulse lenth
/// </summary>
public const int SYNC_2_PL = 735;
/// <summary>
/// Bit 0 pulse length
/// </summary>
public const int BIT_0_PL = 855;
/// <summary>
/// Bit 1 pulse length
/// </summary>
public const int BIT_1_PL = 1710;
/// <summary>
/// End sync pulse length
/// </summary>
public const int TERM_SYNC = 947;
/// <summary>
/// 1 millisecond pause
/// </summary>
public const int PAUSE_MS = 3500;
/// <summary>
/// Used bit count in last byte
/// </summary>
public const int BIT_COUNT_IN_LAST = 8;
#endregion
/// <summary>
/// DeSerialization method
/// </summary>
/// <param name="data"></param>
public override void DeSerialize(byte[] data)
{
/*
The .TAP files contain blocks of tape-saved data. All blocks start with two bytes specifying how many bytes will follow (not counting the two length bytes). Then raw tape data follows, including the flag and checksum bytes. The checksum is the bitwise XOR of all bytes including the flag byte. For example, when you execute the line SAVE "ROM" CODE 0,2 this will result:
|------ Spectrum-generated data -------| |---------|
13 00 00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 04 00 ff f3 af a3
^^^^^...... first block is 19 bytes (17 bytes+flag+checksum)
^^... flag byte (A reg, 00 for headers, ff for data blocks)
^^ first byte of header, indicating a code block
file name ..^^^^^^^^^^^^^
header info ..............^^^^^^^^^^^^^^^^^
checksum of header .........................^^
length of second block ........................^^^^^
flag byte ............................................^^
first two bytes of rom .................................^^^^^
checksum (checkbittoggle would be a better name!).............^^
*/
// clear existing tape blocks
_datacorder.DataBlocks.Clear();
// convert bytearray to memory stream
MemoryStream stream = new MemoryStream(data);
// the first 2 bytes of the TAP file designate the length of the first data block
// this (I think) should always be 17 bytes (as this is the tape header)
byte[] blockLengthData = new byte[2];
// we are now going to stream through the entire file processing a block at a time
while (stream.Position < stream.Length)
{
// read and calculate the length of the block
stream.Read(blockLengthData, 0, 2);
int blockSize = BitConverter.ToUInt16(blockLengthData, 0);
if (blockSize == 0)
{
// block size is 0 - this is probably invalid (but I guess could be EoF in some situations)
break;
}
// copy the entire block into a new bytearray
byte[] blockdata = new byte[blockSize];
stream.Read(blockdata, 0, blockSize);
// create and populate a new tapedatablock object
TapeDataBlock tdb = new TapeDataBlock();
// ascertain the block description
string description = string.Empty;
byte crc = 0;
byte crcValue = 0;
byte crcFile = 0;
byte[] programData = new byte[10];
// calculate block checksum value
for (int i = 0; i < blockSize; i++)
{
crc ^= blockdata[i];
if (i < blockSize - 1)
{
crcValue = crc;
}
else
{
crcFile = blockdata[i];
}
}
// process the type byte
/* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file.
A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal.
If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given)
and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds
the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.)
*/
if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3)
{
// This is the PROGRAM header
// take the 10 filename bytes (that start at offset 2)
programData = blockdata.Skip(2).Take(10).ToArray();
// get the filename as a string (with padding removed)
string fileName = Encoding.ASCII.GetString(programData).Trim();
// get the type
string type = "";
if (blockdata[0] == 0x00)
{
type = "Program";
}
else
{
type = "Bytes";
}
// now build the description string
StringBuilder sb = new StringBuilder();
sb.Append(type + ": ");
sb.Append(fileName + " ");
sb.Append(GetWordValue(blockdata, 14));
sb.Append(":");
sb.Append(GetWordValue(blockdata, 12));
description = sb.ToString();
}
else if (blockdata[0] == 0xFF)
{
// this is a data block
description = "Data Block " + (blockSize - 2) + "bytes";
}
else
{
// other type
description = string.Format("#{0} block, {1} bytes", blockdata[0].ToString("X2"), blockSize - 2);
description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok"));
}
tdb.BlockDescription = BlockType.Standard_Speed_Data_Block;
// calculate the data periods for this block
int pilotLength = 0;
// work out pilot length
if (blockdata[0] < 4)
{
pilotLength = 8064;
}
else
{
pilotLength = 3220;
}
// create a list to hold the data periods
List<int> dataPeriods = new List<int>();
// generate pilot pulses
for (int i = 0; i < pilotLength; i++)
{
dataPeriods.Add(PILOT_PL);
}
// add syncro pulses
dataPeriods.Add(SYNC_1_PL);
dataPeriods.Add(SYNC_2_PL);
int pos = 0;
// add bit0 and bit1 periods
for (int i = 0; i < blockSize - 1; i++, pos++)
{
for (byte b = 0x80; b != 0; b >>= 1)
{
if ((blockdata[i] & b) != 0)
dataPeriods.Add(BIT_1_PL);
else
dataPeriods.Add(BIT_0_PL);
if ((blockdata[i] & b) != 0)
dataPeriods.Add(BIT_1_PL);
else
dataPeriods.Add(BIT_0_PL);
}
}
// add the last byte
for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1)
{
if ((blockdata[pos] & c) != 0)
dataPeriods.Add(BIT_1_PL);
else
dataPeriods.Add(BIT_0_PL);
if ((blockdata[pos] & c) != 0)
dataPeriods.Add(BIT_1_PL);
else
dataPeriods.Add(BIT_0_PL);
}
// add block pause
int actualPause = PAUSE_MS * 1000;
dataPeriods.Add(actualPause);
// add to the tapedatablock object
tdb.DataPeriods = dataPeriods;
// add the raw data
tdb.BlockData = blockdata;
// add block to the tape
_datacorder.DataBlocks.Add(tdb);
}
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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,
}
}

View File

@ -0,0 +1,264 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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>();
/// <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>
/// 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
}
/// <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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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
/// </summary>
internal sealed class SoundProviderMixer : ISoundProvider
{
private class Provider
{
public ISoundProvider SoundProvider { 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, params ISoundProvider[] soundProviders)
{
SoundProviders = new List<Provider>();
foreach (var s in soundProviders)
{
SoundProviders.Add(new Provider
{
SoundProvider = s,
MaxVolume = maxVolume,
});
}
EqualizeVolumes();
}
public void AddSource(ISoundProvider source)
{
SoundProviders.Add(new Provider
{
SoundProvider = source,
MaxVolume = short.MaxValue
});
EqualizeVolumes();
}
public void AddSource(ISoundProvider source, short maxVolume)
{
SoundProviders.Add(new Provider
{
SoundProvider = source,
MaxVolume = maxVolume
});
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)
{
nsamp = firstEntry.NSamp;
samples = new short[nsamp * 2];
if (_stereo)
{
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;
}
}
else
{
// convert to mono
for (int i = 0; i < samples.Length; i += 2)
{
short s = 0;
foreach (var sp in SoundProviders)
{
s += (short)((sp.Buffer[i] + sp.Buffer[i + 1]) / 2);
}
samples[i] = s;
samples[i + 1] = s;
}
}
}
else if (!sameCount)
{
// this is a pretty poor implementation that doesnt work very well
// ideally soundproviders should ensure that their number of samples is identical
int divisor = 1;
int highestCount = 0;
// get the lowest divisor of all the soundprovider nsamps
for (int d = 2; d < 999; d++)
{
bool divFound = false;
foreach (var sp in SoundProviders)
{
if (sp.NSamp > highestCount)
highestCount = sp.NSamp;
if (sp.NSamp % d == 0)
divFound = true;
else
divFound = false;
}
if (divFound)
{
divisor = d;
break;
}
}
// now we have the largest current number of samples among the providers
// along with a common divisor for all of them
nsamp = highestCount * divisor;
samples = new short[nsamp * 2];
// take a pass at populating the samples array for each provider
foreach (var sp in SoundProviders)
{
short sectorVal = 0;
int pos = 0;
for (int i = 0; i < sp.Buffer.Length; i++)
{
if (sp.Buffer[i] > sp.MaxVolume)
sectorVal = (short)sp.MaxVolume;
else
sectorVal = sp.Buffer[i];
for (int s = 0; s < divisor; s++)
{
samples[pos++] += sectorVal;
}
}
}
/*
// get the highest number of samples
int max = SoundProviders.Aggregate((i, j) => i.Buffer.Length > j.Buffer.Length ? i : j).Buffer.Length;
nsamp = max;
samples = new short[nsamp * 2];
// take a pass at populating the samples array for each provider
foreach (var sp in SoundProviders)
{
short sectorVal = 0;
int pos = 0;
for (int i = 0; i < sp.Buffer.Length; i++)
{
if (sp.Buffer[i] > sp.MaxVolume)
sectorVal = (short)sp.MaxVolume;
else
{
if (sp.SoundProvider is AY38912)
{
// boost audio
sectorVal += (short)(sp.Buffer[i] * 2);
}
else
{
sectorVal += sp.Buffer[i];
}
}
samples[pos++] += sectorVal;
}
}
*/
}
}
#endregion
}
}

View File

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Common;
using BizHawk.Common.ReflectionExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum
{
/// <summary>
/// The one ZX Hawk ControllerDefinition
/// </summary>
public ControllerDefinition ZXSpectrumControllerDefinition
{
get
{
ControllerDefinition definition = new ControllerDefinition();
definition.Name = "ZXSpectrum Controller";
// joysticks
List<string> joys1 = new List<string>
{
// P1 Joystick
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button",
};
foreach (var s in joys1)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "J1 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType1.ToString() + ")";
}
List<string> joys2 = new List<string>
{
// P2 Joystick
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Button",
};
foreach (var s in joys2)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "J2 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType2.ToString() + ")";
}
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>
{
/// Controller mapping includes all keyboard keys from the following models:
/// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg
/// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg
// Keyboard - row 1
"Key True Video", "Key Inv Video", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Break",
// Keyboard - row 2
"Key Delete", "Key Graph", "Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P",
// Keyboard - row 3
"Key Extend Mode", "Key Edit", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return",
// Keyboard - row 4
"Key Caps Shift", "Key Caps Lock", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Period",
// Keyboard - row 5
"Key Symbol Shift", "Key Semi-Colon", "Key Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma",
};
foreach (var s in keys)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "Keyboard";
}
// Datacorder (tape device)
List<string> power = new List<string>
{
// Tape functions
"Soft Reset", "Hard Reset"
};
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";
}
return definition;
}
}
/*
/// <summary>
/// Controller mapping includes all keyboard keys from the following models:
/// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg
/// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg
/// </summary>
public static readonly ControllerDefinition ZXSpectrumControllerDefinition = new ControllerDefinition
{
Name = "ZXSpectrum Controller",
BoolButtons =
{
// Kempston Joystick (P1)
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button",
// Keyboard - row 1
"Key True Video", "Key Inv Video", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Break",
// Keyboard - row 2
"Key Delete", "Key Graph", "Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P",
// Keyboard - row 3
"Key Extend Mode", "Key Edit", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return",
// Keyboard - row 4
"Key Caps Shift", "Key Caps Lock", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Period",
// Keyboard - row 5
"Key Symbol Shift", "Key Semi-Colon", "Key Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma",
// Tape functions
"Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape", "Next Tape Block", "Prev Tape Block"
}
};
*/
}
/// <summary>
/// The possible joystick types
/// </summary>
public enum JoystickType
{
NULL,
Kempston,
SinclairLEFT,
SinclairRIGHT,
Cursor
}
}

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum : 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 int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles;
}
}

View File

@ -0,0 +1,82 @@
using BizHawk.Emulation.Common;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum : 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 => "ZXSpectrum";
private bool deterministicEmulation;
public bool DeterministicEmulation
{
get { return deterministicEmulation; }
}
//public bool DeterministicEmulation => true;
public void ResetCounters()
{
_machine.FrameCount = 0;
_lagCount = 0;
_isLag = false;
}
public CoreComm CoreComm { get; }
public void Dispose()
{
if (_machine != null)
{
_machine = null;
}
}
}
}

View File

@ -0,0 +1,26 @@

using System;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum : 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;
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum //: IMemoryDomains
{
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("ROM2", _machine.ROM2);
SyncByteArrayDomain("ROM3", _machine.ROM3);
SyncByteArrayDomain("RAM0", _machine.RAM0);
SyncByteArrayDomain("RAM1", _machine.RAM1);
SyncByteArrayDomain("RAM2", _machine.RAM2);
SyncByteArrayDomain("RAM3", _machine.RAM3);
SyncByteArrayDomain("RAM4", _machine.RAM4);
SyncByteArrayDomain("RAM5", _machine.RAM5);
SyncByteArrayDomain("RAM6", _machine.RAM6);
SyncByteArrayDomain("RAM7", _machine.RAM7);
}
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);
}
}
}
}

View File

@ -0,0 +1,349 @@
using System;
using Newtonsoft.Json;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using System.ComponentModel;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum : ISettable<ZXSpectrum.ZXSpectrumSettings, ZXSpectrum.ZXSpectrumSyncSettings>
{
internal ZXSpectrumSettings Settings = new ZXSpectrumSettings();
internal ZXSpectrumSyncSettings SyncSettings = new ZXSpectrumSyncSettings();
public ZXSpectrumSettings GetSettings()
{
return Settings.Clone();
}
public ZXSpectrumSyncSettings GetSyncSettings()
{
return SyncSettings.Clone();
}
public bool PutSettings(ZXSpectrumSettings o)
{
//if (SoundMixer != null)
//SoundMixer.Stereo = o.StereoSound;
if (_machine != null && _machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
{
((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig;
}
Settings = o;
return false;
}
public bool PutSyncSettings(ZXSpectrumSyncSettings o)
{
bool ret = ZXSpectrumSyncSettings.NeedsReboot(SyncSettings, o);
SyncSettings = o;
return ret;
}
public class ZXSpectrumSettings
{
[DisplayName("Auto-load/stop tape")]
[Description("Auto or manual tape operation. Auto will attempt to detect CPU tape traps and automatically Stop/Start the tape")]
[DefaultValue(true)]
public bool AutoLoadTape { get; set; }
/*
[DisplayName("Stereo Sound")]
[Description("Turn stereo sound on or off")]
[DefaultValue(true)]
public bool StereoSound { get; set; }
*/
[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(AYChip.AYPanConfig.ABC)]
public AYChip.AYPanConfig AYPanConfig { 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; }
public ZXSpectrumSettings Clone()
{
return (ZXSpectrumSettings)MemberwiseClone();
}
public ZXSpectrumSettings()
{
BizHawk.Common.SettingsUtil.SetDefaultValues(this);
}
}
public class ZXSpectrumSyncSettings
{
[DisplayName("Deterministic Emulation")]
[Description("If true, the core agrees to behave in a completely deterministic manner")]
[DefaultValue(true)]
public bool DeterministicEmulation { get; set; }
[DisplayName("Spectrum model")]
[Description("The model of spectrum to be emulated")]
[DefaultValue(MachineType.ZXSpectrum48)]
public MachineType MachineType { get; set; }
[DisplayName("Border type")]
[Description("Select how to show the border area")]
[DefaultValue(BorderType.Full)]
public BorderType BorderType { get; set; }
[DisplayName("Tape Load Speed")]
[Description("Select how fast the spectrum loads the game from tape")]
[DefaultValue(TapeLoadSpeed.Accurate)]
public TapeLoadSpeed TapeLoadSpeed { get; set; }
[DisplayName("Joystick 1")]
[Description("The emulated joystick assigned to P1 (SHOULD BE UNIQUE TYPE!)")]
[DefaultValue(JoystickType.Kempston)]
public JoystickType JoystickType1 { get; set; }
[DisplayName("Joystick 2")]
[Description("The emulated joystick assigned to P2 (SHOULD BE UNIQUE TYPE!)")]
[DefaultValue(JoystickType.SinclairLEFT)]
public JoystickType JoystickType2 { get; set; }
[DisplayName("Joystick 3")]
[Description("The emulated joystick assigned to P3 (SHOULD BE UNIQUE TYPE!)")]
[DefaultValue(JoystickType.SinclairRIGHT)]
public JoystickType JoystickType3 { get; set; }
public ZXSpectrumSyncSettings Clone()
{
return (ZXSpectrumSyncSettings)MemberwiseClone();
}
public ZXSpectrumSyncSettings()
{
SettingsUtil.SetDefaultValues(this);
}
public static bool NeedsReboot(ZXSpectrumSyncSettings x, ZXSpectrumSyncSettings y)
{
return !DeepEquality.DeepEquals(x, y);
}
}
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
}
/// <summary>
/// The size of the Spectrum border
/// </summary>
public enum BorderType
{
/// <summary>
/// How it was originally back in the day
/// </summary>
Full,
/// <summary>
/// All borders 24px
/// </summary>
Medium,
/// <summary>
/// All borders 10px
/// </summary>
Small,
/// <summary>
/// No border at all
/// </summary>
None,
/// <summary>
/// Top and bottom border removed so that the result is *almost* 16:9
/// </summary>
Widescreen,
}
/// <summary>
/// The speed at which the tape is loaded
/// NOT IN USE YET
/// </summary>
public enum TapeLoadSpeed
{
Accurate,
//Fast,
//Fastest
}
}
/// <summary>
/// Provides information on each emulated machine
/// </summary>
public class ZXMachineMetaData
{
public MachineType MachineType { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Released { get; set; }
public string CPU { get; set; }
public string Memory { get; set; }
public string Video { get; set; }
public string Audio { get; set; }
public string Media { get; set; }
public string OtherMisc { get; set; }
public static ZXMachineMetaData GetMetaObject(MachineType type)
{
ZXMachineMetaData m = new ZXMachineMetaData();
m.MachineType = type;
switch (type)
{
case MachineType.ZXSpectrum16:
m.Name = "Sinclair ZX Spectrum 16K";
m.Description = "The original ZX Spectrum 16K RAM version. Aside from available RAM this machine is technically identical to the 48K machine that was released at the same time. ";
m.Description += "Due to the small amount of RAM, very few games were actually made to run on this model.";
m.Released = "1982";
m.CPU = "Zilog Z80A @ 3.5MHz";
m.Memory = "16KB ROM / 16KB RAM";
m.Video = "ULA @ 7MHz - PAL (50.08Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) - Internal Speaker";
m.Media = "Cassette Tape (via 3rd party external tape player)";
break;
case MachineType.ZXSpectrum48:
m.Name = "Sinclair ZX Spectrum 48K / 48K+";
m.Description = "The original ZX Spectrum 48K RAM version. 2 years later a 'plus' version was released that had a better keyboard. ";
m.Description += "Electronically both the 48K and + are identical, so ZXHawk treats them as the same emulated machine. ";
m.Description += "These machines dominated the UK 8-bit home computer market throughout the 1980's so most non-128k only games are compatible.";
m.Released = "1982 (48K) / 1984 (48K+)";
m.CPU = "Zilog Z80A @ 3.5MHz";
m.Memory = "16KB ROM / 48KB RAM";
m.Video = "ULA @ 7MHz - PAL (50.08Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) - Internal Speaker";
m.Media = "Cassette Tape (via 3rd party external tape player)";
break;
case MachineType.ZXSpectrum128:
m.Name = "Sinclair ZX Spectrum 128";
m.Description = "The first Spectrum 128K machine released in Spain in 1985 and later UK in 1986. ";
m.Description += "With an updated ROM and new memory paging system to work around the Z80's 16-bit address bus. ";
m.Description += "The 128 shipped with a copy of the 48k ROM (that is paged in when required) and a new startup menu with the option of dropping into a '48k mode'. ";
m.Description += "Even so, there were some compatibility issues with older Spectrum games that were written to utilise some of the previous model's intricacies. ";
m.Description += "Many games released after 1985 supported the new AY-3-8912 PSG chip making for far superior audio. The extra memory also enabled many games to be loaded in all at once (rather than loading each level from tape when needed).";
m.Released = "1985 / 1986";
m.CPU = "Zilog Z80A @ 3.5469 MHz";
m.Memory = "32KB ROM / 128KB RAM";
m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output";
m.Media = "Cassette Tape (via 3rd party external tape player)";
break;
case MachineType.ZXSpectrum128Plus2:
m.Name = "Sinclair ZX Spectrum +2";
m.Description = "The first Sinclair Spectrum 128K machine that was released after Amstrad purchased Sinclair in 1986. ";
m.Description += "Electronically it was almost identical to the 128, but with the addition of a built-in tape deck and 2 Sinclair Joystick ports.";
m.Released = "1986";
m.CPU = "Zilog Z80A @ 3.5469 MHz";
m.Memory = "32KB ROM / 128KB RAM";
m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output";
m.Media = "Cassette Tape (via built-in Datacorder)";
break;
case MachineType.ZXSpectrum128Plus2a:
m.Name = "Sinclair ZX Spectrum +2a";
m.Description = "The +2a looks almost identical to the +2 but is a variant of the +3 machine that was released the same year (except with the same built-in datacorder that the +2 had rather than a floppy drive). ";
m.Description += "Memory paging again changed significantly and this (along with memory contention timing changes) caused more compatibility issues with some older games. ";
m.Description += "Although functionally identical to the +3, it does not contain floppy disk controller.";
m.Released = "1987";
m.CPU = "Zilog Z80A @ 3.5469 MHz";
m.Memory = "64KB ROM / 128KB RAM";
m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output";
m.Media = "Cassette Tape (via built-in Datacorder)";
break;
case MachineType.ZXSpectrum128Plus3:
m.Name = "Sinclair ZX Spectrum +3";
m.Description = "Amstrad released the +3 the same year as the +2a, but it featured a built-in floppy drive rather than a datacorder. An external cassette player could still be connected though as in the older 48k models. ";
m.Description += "Memory paging again changed significantly and this (along with memory contention timing changes) caused more compatibility issues with some older games. ";
m.Description += "Currently ZXHawk does not emulate the floppy drive or floppy controller so the machine reports as a +2a on boot.";
m.Released = "1987";
m.CPU = "Zilog Z80A @ 3.5469 MHz";
m.Memory = "64KB ROM / 128KB RAM";
m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)";
m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output";
m.Media = "3\" Floppy Disk (via built-in Floppy Drive)";
break;
}
return m;
}
public static string GetMetaString(MachineType type)
{
var m = GetMetaObject(type);
StringBuilder sb = new StringBuilder();
sb.Append(m.Name);
sb.Append("\n");
sb.Append("-----------------------------------------------------------------\n");
// Release
sb.Append("Released:");
sb.Append(" ");
sb.Append(m.Released);
sb.Append("\n");
// CPU
sb.Append("CPU:");
sb.Append(" ");
sb.Append(m.CPU);
sb.Append("\n");
// Memory
sb.Append("Memory:");
sb.Append(" ");
sb.Append(m.Memory);
sb.Append("\n");
// Video
sb.Append("Video:");
sb.Append(" ");
sb.Append(m.Video);
sb.Append("\n");
// Audio
sb.Append("Audio:");
sb.Append(" ");
sb.Append(m.Audio);
sb.Append("\n");
// Audio
sb.Append("Media:");
sb.Append(" ");
sb.Append(m.Media);
sb.Append("\n");
sb.Append("-----------------------------------------------------------------\n");
// description
sb.Append(m.Description);
if (m.OtherMisc != null)
sb.Append("\n" + m.OtherMisc);
return sb.ToString();
}
}
}

View File

@ -0,0 +1,97 @@
using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public partial class ZXSpectrum : 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("ZXSpectrum");
_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("ZXSpectrum");
_machine.SyncState(ser);
ser.Sync("Frame", ref _machine.FrameCount);
ser.Sync("LagCount", ref _lagCount);
ser.Sync("IsLag", ref _isLag);
ser.EndSection();
SyncAllByteArrayDomains();
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More