diff --git a/Assets/Lua/UnitTests/TestCommunication_All.lua b/Assets/Lua/UnitTests/TestCommunication_All.lua new file mode 100644 index 0000000000..4e1a7e95b3 --- /dev/null +++ b/Assets/Lua/UnitTests/TestCommunication_All.lua @@ -0,0 +1,149 @@ +function round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function get_baseline() + i = 100 + client.reboot_core() + t = os.clock() + + while i > 0 do + emu.frameadvance() + i = i - 1 + end + baseline = os.clock() - t + print('Baseline: ' .. round(baseline, 3) .. " secs") + return baseline +end + +function test_mmf() + i = 100 + client.reboot_core() + t = os.clock() + while i > 0 do + emu.frameadvance() + comm.mmfScreenshot() + i = i - 1 + end + print('Memory mapped files: ' .. round((os.clock() - t - baseline), 3) .. " secs") +end + +function test_http() + print("Testing HTTP server") + client.reboot_core() + i = 100 + t = os.clock() + + while i > 0 do + emu.frameadvance() + comm.httpTestGet() + i = i - 1 + end + print('HTTP get: ' .. round((os.clock() - t - baseline), 3) .. " secs") + + client.reboot_core() + i = 100 + t = os.clock() + + while i > 0 do + emu.frameadvance() + comm.httpPostScreenshot() + i = i - 1 + end + print('HTTP post: ' .. round((os.clock() - t - baseline), 3) .. " secs") + +end + +function test_socket() + + i = 100 + client.reboot_core() + t = os.clock() + while i > 0 do + emu.frameadvance() + comm.socketServerScreenShot() + i = i - 1 + end + print('Socket server: ' .. round((os.clock() - t - baseline), 3) .. " secs") +end + +function test_socketresponse() + best_time = -100 + timeouts = {1, 2, 3, 4, 5, 10, 20, 25, 50, 100, 250, 500, 1000} + comm.socketServerSetTimeout(1000) + resp = comm.socketServerScreenShotResponse() + for t, timeout in ipairs(timeouts) do + comm.socketServerSetTimeout(timeout) + client.reboot_core() + print("Trying to find minimal timeout for Socket server") + i = 100 + t = os.clock() + while i > 0 do + emu.frameadvance() + resp = comm.socketServerScreenShotResponse() + if resp ~= 'ack' then + i = -100 + print(resp) + print("Failed to a get a proper response") + end + i = i - 1 + end + if i > -100 then + print("Best timeout: " .. timeout .. " msecs") + print("Best time: " .. round((os.clock() - t - baseline), 3) .. " secs") + break + end + end + +end + +function test_http_response() + err = false + print("Testing HTTP server response") + client.reboot_core() + i = 100 + + while i > 0 do + emu.frameadvance() + resp = comm.httpTestGet() + if resp ~= "

hi!

" then + print("Failed to get correct HTTP get response") + print(resp) + i = 0 + err = true + end + i = i - 1 + end + if not err then + print("HTTP GET looks fine: No errors occurred") + end + + client.reboot_core() + i = 100 + err = false + while i > 0 do + emu.frameadvance() + resp = comm.httpPostScreenshot() + if resp ~= "OK" then + print("Failed to get correct HTTP post response") + print(resp) + i = 0 + err = true + end + i = i - 1 + end + if not err then + print("HTTP POST looks fine: No errors occurred") + end +end + +baseline = get_baseline() +test_socket() +test_mmf() +test_http() +print("#####################") +test_http_response() +test_socketresponse() +print() + diff --git a/Assets/Lua/UnitTests/TestCommunication_Simple.lua b/Assets/Lua/UnitTests/TestCommunication_Simple.lua new file mode 100644 index 0000000000..b7b5fac647 --- /dev/null +++ b/Assets/Lua/UnitTests/TestCommunication_Simple.lua @@ -0,0 +1,91 @@ +print("##########################################################") +getUrl = comm.httpGetGetUrl() +print("GET URL: " .. getUrl) + +postUrl = comm.httpGetPostUrl() +print("POST URL: " .. postUrl) + +print("\nChecking GET URL change") +error = false +comm.httpSetGetUrl('a') +if (getUrl ~= comm.httpGetGetUrl()) then + comm.httpSetGetUrl(getUrl) + error = (getUrl ~= comm.httpGetGetUrl()) +else + error = true +end + +if error == false then + print("Get URL was successfully changed") +else + print("Error while changing Get URL") +end + + +print("\nChecking POST URL change") +error = false +comm.httpSetPostUrl('a') +if (postUrl ~= comm.httpGetPostUrl()) then + comm.httpSetPostUrl(postUrl) + error = (postUrl ~= comm.httpGetPostUrl()) +else + error = true +end + +if error == false then + print("Post URL was successfully changed") +else + print("Error while changing Post URL") +end + +print("\nChecking GET request") +getResponse = comm.httpGet("http://tasvideos.org/BizHawk.html") +if string.find(getResponse, "Bizhawk") then + print("GET seems to work") +else + print("Either the Bizhawk site is down or the GET does not work") +end + +print("\nChecking memory mapped filed") + +size = comm.mmfScreenshot() +if size > 0 then + print("Memory mapped file was successfully written") +else + print("Failed to write memory mapped file") +end + +mmf_filename = comm.mmfGetFilename() +print("MMF filename: " .. mmf_filename) +comm.mmfSetFilename("deleteme.tmp") +error = false +if (mmf_filename ~= comm.mmfGetFilename()) then + comm.mmfSetFilename(mmf_filename) + error = (mmf_filename ~= comm.mmfGetFilename()) +else + error = true +end +if error == false then + print("MMF filename successfully changed") +else + print("MMF filename change failed") +end + +print("Writing to MMF") + +message = "ABC" +resp_n = tonumber(comm.mmfWrite(mmf_filename, message)) +if (resp_n ~= string.len(message)) then + print("Failed to write to MMF") +else + resp = comm.mmfRead(mmf_filename, string.len(message)) + if (resp ~= message) then + print("Failed to read from MMF") + else + print("MMF read and read OK") + end +end + + +print("\nTests finished") +print("Please run TestCommunication_All.lua with the supplied Python server for a more comprehensive test") diff --git a/Assets/defctrl.json b/Assets/defctrl.json index 42f8b4cee1..dace0123e0 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -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", diff --git a/Assets/gamedb/gamedb_nes.txt b/Assets/gamedb/gamedb_nes.txt index d53e90e7f9..256a27a55b 100644 --- a/Assets/gamedb/gamedb_nes.txt +++ b/Assets/gamedb/gamedb_nes.txt @@ -273,6 +273,9 @@ sha1:3C72706AF5998133EC6BE703994C10466A094EAB Xing Ji Zheng Ba (China) (Unl) NE sha1:18DF013DB350787D0F3D83ADE33EA92B097BD54B Mahjan Samit Kabukicho Hen (Asia) (Unl) NES board=MAPPER146;PRG=64;CHR=64;WRAM=0;VRAM=0;PAD_V=1;PAD_H=0 sha1:6AAA5521F91F101448E77C996C9802015578400C Dooly_Bravo_Land NES board=MAPPER002;PRG=256;CHR=0;WRAM=0;VRAM=8;PAD_V=0;PAD_H=1 sha1:4EBC1ED9665C36913D0F05129E6A54787BAD3165 Dragon Ball 3 - Gokuu Den (Japan) (Rev 1) NES board=BANDAI-FCG-2;PRG=128;CHR=256;WRAM=0;VRAM=0;PAD_V=0;PAD_H=1 +sha1:5A6DFDD8A2D62EBE313A6FDB986C3585077BB348 Final Combat (Asia) (NTSC) (Unl) NES board=MAPPER139 +sha1:DFAF6D81280ADBEB2ADF3DAB38E536B0F2FDFC76 Final Combat (Asia) (PAL) (Unl) NES board=MAPPER139;system=NES-PAL +sha1:433CEC30E71DCA31E32B8A44A0D534DBFE7039CA BoogerMan II (RexSoft) [!] NES board=UNIF_UNL-KOF97 ;;;;;;;;;;;;;;;;;;;----------------------------------------------------------------------- @@ -300,6 +303,8 @@ sha1:FFB4706E49B826C6CDD12E502E8AE94FC9810B7F Monty no Doki Doki Daisassou (FDS sha1:17473C223453D2D80FCB9DCFA317947287DC5C52 Xing He Zhan Shi (China) (Unl) NES board=WAIXINGMAPPER176 sha1:B1C74236FD17FAB4AB9AA6AB28E38864C66D6255 Pocahontus (UNL) NES board=MAPPER182;PRG=256;CHR=256;WRAM=8;PAD_H=1 sha1:5FA23F88432006DCF6874EA36E9E7DA8934427BE Super Donkey Kong (Unl) NES board=MAPPER182;PRG=128;CHR=128;WRAM=8;PAD_H=1 +sha1:8A7DAB8B78DA1C5EA159BA9EEC00FF97742245F1 B Super Donkey Kong (Unl) [b1] NES board=MAPPER182;PRG=128;CHR=128;WRAM=8;PAD_H=1 +sha1:8A7DAB8B78DA1C5EA159BA9EEC00FF97742245F1 O Super Donkey Kong (Unl) [o1] NES board=MAPPER182;PRG=128;CHR=128;WRAM=8;PAD_H=1 ;wrong vram info sha1:32D71DD6C5A8D78A918FE1B9D6D6C4A570D9652D Oeka Kids Anpanman no Hiragana Daisuki (J) NES board=MAPPER096;VRAM=32 diff --git a/Assets/gamedb/gamedb_sega_sms.txt b/Assets/gamedb/gamedb_sega_sms.txt index b3a7102c79..6701daef48 100644 --- a/Assets/gamedb/gamedb_sega_sms.txt +++ b/Assets/gamedb/gamedb_sega_sms.txt @@ -264,7 +264,7 @@ C0A9A2261EA7EF93BF8120F5328DEAEC Great Golf (J) SMS Sports;Golf FM Japan BE6EAC7CE416C86A818FF13B228B39C5 Great Tennis (J) SMS Sports;Tennis Japan 382B627EFA06C958B8EC5C85E964BF10 Great Volleyball (UE) SMS Sports;Volleyball USA;Europe 03D6E8450A74AC09E47AE9BF210CFE17 Great Volleyball (J) SMS Sports;Volleyball Japan -B54989C58520516F4C10CE4E7A446725 Haja no Fuuin (J) SMS RPG SRAM=8192 Japan +B54989C58520516F4C10CE4E7A446725 Haja no Fuuin (J) SMS RPG SRAM=8192;FM Japan 3701CB59DFD137246D163462D18E8DD4 Hang-On & Astro Warrior (U) SMS USA 84284C327B07C200C16F4E13B2E8DE79 Hang-On & Safari Hunt (U) SMS Light Gun;Arcade Phaser USA 16D870BF1A5A23A9F2993D8786F5BC74 Hang-On & Safari Hunt (U) (Beta) SMS Light Gun;Arcade Phaser USA @@ -564,7 +564,7 @@ E7F86C049E4BD8B26844FF62BD067D57 Wonder Boy III - The Dragon's Trap (UE) SMS Wo 16BE61F4DE705CDD636D1FB1662E24DE Wonder Boy in Monster Land (UE) (Beta) SMS Wonder Boy Series;Arcade FM USA;Europe 5837764C172C8C43C8C7B21F2144CF27 Wonder Boy in Monster World (E) SMS Wonder Boy Series Europe 311AA03DCAB8EDCF9891397AC3297B72 Wonder Boy in Monster World (E) (Beta) SMS Wonder Boy Series Europe -8883790D3B3D4FED9A845D63AA251786 Wonder Boy in Monster World (J) SMS Wonder Boy Series Japan +8883790D3B3D4FED9A845D63AA251786 Wonder Boy in Monster World (J) SMS Wonder Boy Series FM Japan 7E805AA51BFB5F206C950A32EBCDAB7C Wonder Boy (UE) SMS Wonder Boy Series USA;Europe A4E48850BF8799CFAC74B1D33F5900B5 Wonder Boy (JE) SMS Wonder Boy Series Europe;Japan C53091E60B5BD473142CA231DD96F6EB Wonsiin (K) SMS MSXMapper Korea diff --git a/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj b/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj index 6d4363c34a..9205707a7e 100644 --- a/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj +++ b/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj @@ -7,7 +7,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\output\dll\ @@ -17,7 +18,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset @@ -93,4 +95,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs index 31b65c5df5..fcce1118cf 100644 --- a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs +++ b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs @@ -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())); } diff --git a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs index 12ee67277c..767d755105 100644 --- a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs +++ b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs @@ -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)); } } } diff --git a/BizHawk.Client.Common/Api/CoreSystem.cs b/BizHawk.Client.Common/Api/CoreSystem.cs index 132a832b98..9bdc7d8232 100644 --- a/BizHawk.Client.Common/Api/CoreSystem.cs +++ b/BizHawk.Client.Common/Api/CoreSystem.cs @@ -29,6 +29,7 @@ WonderSwan, Libretro, VirtualBoy, - NeoGeoPocket + NeoGeoPocket, + ZXSpectrum } } diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj index ad62cb179f..9816bf2bfb 100644 --- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -23,7 +23,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -35,7 +36,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -182,6 +184,7 @@ + diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index b592f1696b..edd287ef49 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -149,6 +149,8 @@ namespace BizHawk.Client.Common return SystemInfo.VirtualBoy; case "NGP": return SystemInfo.NeoGeoPocket; + case "ZXSpectrum": + return SystemInfo.ZXSpectrum; } } } diff --git a/BizHawk.Client.Common/PathManager.cs b/BizHawk.Client.Common/PathManager.cs index c77af2cca0..51826e93f5 100644 --- a/BizHawk.Client.Common/PathManager.cs +++ b/BizHawk.Client.Common/PathManager.cs @@ -336,6 +336,12 @@ namespace BizHawk.Client.Common name += "." + Global.Emulator.Attributes().CoreName; } + // Gambatte and GBHawk have incompatible savestates, store the name to keep them separate + if (Global.Emulator.SystemId == "GB") + { + name += "." + Global.Emulator.Attributes().CoreName; + } + if (Global.Emulator is Snes9x) // Keep snes9x savestate away from libsnes, we want to not be too tedious so bsnes names will just have the profile name not the core name { name += "." + Global.Emulator.Attributes().CoreName; diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 99e355081e..9458897687 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -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.C64SyncSettings)GetCoreSyncSettings()); break; + case "ZXSpectrum": + + List zxGI = new List(); + 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.ZXSpectrumSyncSettings)GetCoreSyncSettings()); + break; case "PSX": var entries = xmlGame.AssetFullPaths; var discs = new List(); @@ -987,9 +1003,13 @@ namespace BizHawk.Client.Common nextEmulator = new A7800Hawk(nextComm, game, rom.RomData, gamedbpath, GetCoreSettings(), GetCoreSyncSettings()); break; case "C64": - var c64 = new C64(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); + var c64 = new C64(nextComm, Enumerable.Repeat(rom.FileData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); nextEmulator = c64; break; + case "ZXSpectrum": + var zx = new ZXSpectrum(nextComm, Enumerable.Repeat(rom.RomData, 1), Enumerable.Repeat(rom.GameInfo, 1).ToList(), GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = zx; + break; case "GBA": if (Global.Config.GBA_UsemGBA) { diff --git a/BizHawk.Client.Common/SystemInfo.cs b/BizHawk.Client.Common/SystemInfo.cs index 82414adc50..2d65f1729c 100644 --- a/BizHawk.Client.Common/SystemInfo.cs +++ b/BizHawk.Client.Common/SystemInfo.cs @@ -188,6 +188,11 @@ namespace BizHawk.Client.Common /// public static SystemInfo NeoGeoPocket { get; } = new SystemInfo("Neo-Geo Pocket", CoreSystem.NeoGeoPocket, 1); + /// + /// Gets the instance for ZXSpectrum + /// + public static SystemInfo ZXSpectrum { get; } = new SystemInfo("ZX Spectrum", CoreSystem.ZXSpectrum, 2); + #endregion Get SystemInfo /// diff --git a/BizHawk.Client.Common/config/Binding.cs b/BizHawk.Client.Common/config/Binding.cs index d5ff054c57..c2ec1c1738 100644 --- a/BizHawk.Client.Common/config/Binding.cs +++ b/BizHawk.Client.Common/config/Binding.cs @@ -125,6 +125,7 @@ namespace BizHawk.Client.Common Bind("General", "Hard Reset"), Bind("General", "Quick Load", "P"), Bind("General", "Quick Save", "I"), + Bind("General", "Autofire"), Bind("General", "Autohold"), Bind("General", "Clear Autohold"), Bind("General", "Screenshot", "F12"), @@ -148,7 +149,6 @@ namespace BizHawk.Client.Common Bind("General", "Increase Speed", "Equals"), Bind("General", "Decrease Speed", "Minus"), Bind("General", "Reboot Core", "Ctrl+R"), - Bind("General", "Autofire"), Bind("General", "Toggle Sound"), Bind("General", "Exit Program"), Bind("General", "Screen Raw to Clipboard", "Ctrl+C"), diff --git a/BizHawk.Client.Common/config/PathEntry.cs b/BizHawk.Client.Common/config/PathEntry.cs index 0be34e26c7..d8e070f950 100644 --- a/BizHawk.Client.Common/config/PathEntry.cs +++ b/BizHawk.Client.Common/config/PathEntry.cs @@ -155,6 +155,8 @@ namespace BizHawk.Client.Common public string MoviesPathFragment => Global.Config.PathEntries["Global", "Movies"].Path; + public string MoviesBackupsPathFragment => Global.Config.PathEntries["Global", "Movie backups"].Path; + public string LuaPathFragment => Global.Config.PathEntries["Global", "Lua"].Path; public string FirmwaresPathFragment => Global.Config.PathEntries["Global", "Firmware"].Path; @@ -290,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 }, diff --git a/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs b/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs index 4987babdff..69e2ae8de8 100644 --- a/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs +++ b/BizHawk.Client.Common/inputAdapters/BitwiseAdapters.cs @@ -27,6 +27,32 @@ namespace BizHawk.Client.Common internal IController SourceAnd { get; set; } } + public class XorAdapter : IController + { + public ControllerDefinition Definition => Source.Definition; + + public bool IsPressed(string button) + { + if (Source != null && SourceXor != null) + { + return Source.IsPressed(button) ^ SourceXor.IsPressed(button); + } + + return false; + } + + // pass floats solely from the original source + // this works in the code because SourceOr is the autofire controller + public float GetFloat(string name) + { + return Source.GetFloat(name); + } + + internal IController Source { get; set; } + internal IController SourceXor { get; set; } + } + + public class ORAdapter : IController { public ControllerDefinition Definition => Source.Definition; diff --git a/BizHawk.Client.Common/inputAdapters/InputAdapterExtensions.cs b/BizHawk.Client.Common/inputAdapters/InputAdapterExtensions.cs index 6df7308f66..8fb59c3509 100644 --- a/BizHawk.Client.Common/inputAdapters/InputAdapterExtensions.cs +++ b/BizHawk.Client.Common/inputAdapters/InputAdapterExtensions.cs @@ -16,6 +16,19 @@ namespace BizHawk.Client.Common.InputAdapterExtensions }; } + /// + /// Creates a new IController that is in a state of a bitwise Xor of the source and target controllers + /// + public static IController Xor(this IController source, IController target) + { + return new XorAdapter + { + Source = source, + SourceXor = target + }; + } + + /// /// Creates a new IController that is in a state of a bitwise Or of the source and target controllers /// diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Bit.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Bit.cs index ef2158cafc..e5f8a0e37b 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Bit.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Bit.cs @@ -16,84 +16,98 @@ namespace BizHawk.Client.Common public override string Name => "bit"; + [LuaMethodExample("local uibitban = bit.band( 1000, 4 );")] [LuaMethod("band", "Bitwise AND of 'val' against 'amt'")] public static uint Band(uint val, uint amt) { return val & amt; } + [LuaMethodExample("local uibitbno = bit.bnot( 1000 );")] [LuaMethod("bnot", "Bitwise NOT of 'val' against 'amt'")] public static uint Bnot(uint val) { return ~val; } + [LuaMethodExample("local uibitbor = bit.bor( 1000, 4 );")] [LuaMethod("bor", "Bitwise OR of 'val' against 'amt'")] public static uint Bor(uint val, uint amt) { return val | amt; } + [LuaMethodExample("local uibitbxo = bit.bxor( 1000, 4 );")] [LuaMethod("bxor", "Bitwise XOR of 'val' against 'amt'")] public static uint Bxor(uint val, uint amt) { return val ^ amt; } + [LuaMethodExample("local uibitlsh = bit.lshift( 1000, 4 );")] [LuaMethod("lshift", "Logical shift left of 'val' by 'amt' bits")] public static uint Lshift(uint val, int amt) { return val << amt; } + [LuaMethodExample("local uibitrol = bit.rol( 1000, 4 );")] [LuaMethod("rol", "Left rotate 'val' by 'amt' bits")] public static uint Rol(uint val, int amt) { return (val << amt) | (val >> (32 - amt)); } + [LuaMethodExample("local uibitror = bit.ror( 1000, 4 );")] [LuaMethod("ror", "Right rotate 'val' by 'amt' bits")] public static uint Ror(uint val, int amt) { return (val >> amt) | (val << (32 - amt)); } + [LuaMethodExample("local uibitrsh = bit.rshift( 1000, 4 );")] [LuaMethod("rshift", "Logical shift right of 'val' by 'amt' bits")] public static uint Rshift(uint val, int amt) { return (uint)(val >> amt); } + [LuaMethodExample("local inbitars = bit.arshift( -1000, 4 );")] [LuaMethod("arshift", "Arithmetic shift right of 'val' by 'amt' bits")] public static int Arshift(int val, int amt) { return val >> amt; } + [LuaMethodExample("if ( bit.check( -12345, 35 ) ) then\r\n\tconsole.log( \"Returns result of bit 'pos' being set in 'num'\" );\r\nend;")] [LuaMethod("check", "Returns result of bit 'pos' being set in 'num'")] public static bool Check(long num, int pos) { return (num & (1 << pos)) != 0; } + [LuaMethodExample("local uibitset = bit.set( 25, 35 );")] [LuaMethod("set", "Sets the bit 'pos' in 'num'")] public static uint Set(uint num, int pos) { return (uint)(num | (uint)1 << pos); } + [LuaMethodExample("local lobitcle = bit.clear( 25, 35 );")] [LuaMethod("clear", "Clears the bit 'pos' in 'num'")] public static long Clear(uint num, int pos) { return num & ~(1 << pos); } + [LuaMethodExample("local usbitbyt = bit.byteswap_16( 100 );")] [LuaMethod("byteswap_16", "Byte swaps 'short', i.e. bit.byteswap_16(0xFF00) would return 0x00FF")] public static ushort Byteswap16(ushort val) { return (ushort)((val & 0xFFU) << 8 | (val & 0xFF00U) >> 8); } + [LuaMethodExample("local uibitbyt = bit.byteswap_32( 1000 );")] [LuaMethod("byteswap_32", "Byte swaps 'dword'")] public static uint Byteswap32(uint val) { @@ -101,6 +115,7 @@ namespace BizHawk.Client.Common (val & 0x00FF0000U) >> 8 | (val & 0xFF000000U) >> 24; } + [LuaMethodExample("local ulbitbyt = bit.byteswap_64( 10000 );")] [LuaMethod("byteswap_64", "Byte swaps 'long'")] public static ulong Byteswap64(ulong val) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Emu.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Emu.cs index 1b29884878..60c0eb6164 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Emu.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Emu.cs @@ -48,24 +48,28 @@ namespace BizHawk.Client.Common public override string Name => "emu"; + [LuaMethodExample("emu.displayvsync( true );")] [LuaMethod("displayvsync", "Sets the display vsync property of the emulator")] public static void DisplayVsync(bool enabled) { Global.Config.VSync = enabled; } + [LuaMethodExample("emu.frameadvance( );")] [LuaMethod("frameadvance", "Signals to the emulator to resume emulation. Necessary for any lua script while loop or else the emulator will freeze!")] public void FrameAdvance() { FrameAdvanceCallback(); } + [LuaMethodExample("local inemufra = emu.framecount( );")] [LuaMethod("framecount", "Returns the current frame count")] public int FrameCount() { return Emulator.Frame; } + [LuaMethodExample("local obemudis = emu.disassemble( 0x8000 );")] [LuaMethod("disassemble", "Returns the disassembly object (disasm string and length int) for the given PC address. Uses System Bus domain if no domain name provided")] public object Disassemble(uint pc, string name = "") { @@ -95,6 +99,7 @@ namespace BizHawk.Client.Common } // TODO: what about 64 bit registers? + [LuaMethodExample("local inemuget = emu.getregister( emu.getregisters( )[ 0 ] );")] [LuaMethod("getregister", "returns the value of a cpu register or flag specified by name. For a complete list of possible registers or flags for a given core, use getregisters")] public int GetRegister(string name) { @@ -117,6 +122,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("local nlemuget = emu.getregisters( );")] [LuaMethod("getregisters", "returns the complete set of available flags and registers for a given core")] public LuaTable GetRegisters() { @@ -142,6 +148,7 @@ namespace BizHawk.Client.Common return table; } + [LuaMethodExample("emu.setregister( emu.getregisters( )[ 0 ], -1000 );")] [LuaMethod("setregister", "sets the given register name to the given value")] public void SetRegister(string register, int value) { @@ -160,6 +167,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("local inemutot = emu.totalexecutedcycles( );")] [LuaMethod("totalexecutedcycles", "gets the total number of executed cpu cycles")] public int TotalExecutedycles() { @@ -180,12 +188,14 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("local stemuget = emu.getsystemid( );")] [LuaMethod("getsystemid", "Returns the ID string of the current core loaded. Note: No ROM loaded will return the string NULL")] public static string GetSystemId() { return Global.Game.System; } + [LuaMethodExample("if ( emu.islagged( ) ) then\r\n\tconsole.log( \"Returns whether or not the current frame is a lag frame\" );\r\nend;")] [LuaMethod("islagged", "Returns whether or not the current frame is a lag frame")] public bool IsLagged() { @@ -198,6 +208,7 @@ namespace BizHawk.Client.Common return false; } + [LuaMethodExample("emu.setislagged( true );")] [LuaMethod("setislagged", "Sets the lag flag for the current frame. If no value is provided, it will default to true")] public void SetIsLagged(bool value = true) { @@ -211,6 +222,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("local inemulag = emu.lagcount( );")] [LuaMethod("lagcount", "Returns the current lag count")] public int LagCount() { @@ -223,6 +235,7 @@ namespace BizHawk.Client.Common return 0; } + [LuaMethodExample("emu.setlagcount( 50 );")] [LuaMethod("setlagcount", "Sets the current lag count")] public void SetLagCount(int count) { @@ -236,18 +249,21 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("emu.limitframerate( true );")] [LuaMethod("limitframerate", "sets the limit framerate property of the emulator")] public static void LimitFramerate(bool enabled) { Global.Config.ClockThrottle = enabled; } + [LuaMethodExample("emu.minimizeframeskip( true );")] [LuaMethod("minimizeframeskip", "Sets the autominimizeframeskip value of the emulator")] public static void MinimizeFrameskip(bool enabled) { Global.Config.AutoMinimizeSkipping = enabled; } + [LuaMethodExample("emu.setrenderplanes( true, false );")] [LuaMethod("setrenderplanes", "Toggles the drawing of sprites and background planes. Set to false or nil to disable a pane, anything else will draw them")] public void SetRenderPlanes(params bool[] luaParam) { @@ -322,12 +338,14 @@ namespace BizHawk.Client.Common return true; } + [LuaMethodExample("emu.yield( );")] [LuaMethod("yield", "allows a script to run while emulation is paused and interact with the gui/main window in realtime ")] public void Yield() { YieldCallback(); } + [LuaMethodExample("local stemuget = emu.getdisplaytype();")] [LuaMethod("getdisplaytype", "returns the display type (PAL vs NTSC) that the emulator is currently running in")] public string GetDisplayType() { @@ -339,6 +357,7 @@ namespace BizHawk.Client.Common return ""; } + [LuaMethodExample("local stemuget = emu.getboardname();")] [LuaMethod("getboardname", "returns (if available) the board name of the loaded ROM")] public string GetBoardName() { @@ -349,5 +368,11 @@ namespace BizHawk.Client.Common return ""; } + + [LuaMethod("getluacore", "returns the name of the Lua core currently in use")] + public string GetLuaBackend() + { + return Lua.WhichLua; + } } } diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Events.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Events.cs index b9e5695128..f8d94f10e8 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Events.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Events.cs @@ -159,6 +159,8 @@ namespace BizHawk.Client.Common #endregion + + [LuaMethodExample("local steveonf = event.onframeend(\r\n\tfunction()\r\n\t\tconsole.log( \"Calls the given lua function at the end of each frame, after all emulation and drawing has completed. Note: this is the default behavior of lua scripts\" );\r\n\tend\r\n\t, \"Frame name\" );")] [LuaMethod("onframeend", "Calls the given lua function at the end of each frame, after all emulation and drawing has completed. Note: this is the default behavior of lua scripts")] public string OnFrameEnd(LuaFunction luaf, string name = null) { @@ -167,6 +169,7 @@ namespace BizHawk.Client.Common return nlf.Guid.ToString(); } + [LuaMethodExample("local steveonf = event.onframestart(\r\n\tfunction()\r\n\t\tconsole.log( \"Calls the given lua function at the beginning of each frame before any emulation and drawing occurs\" );\r\n\tend\r\n\t, \"Frame name\" );")] [LuaMethod("onframestart", "Calls the given lua function at the beginning of each frame before any emulation and drawing occurs")] public string OnFrameStart(LuaFunction luaf, string name = null) { @@ -175,6 +178,7 @@ namespace BizHawk.Client.Common return nlf.Guid.ToString(); } + [LuaMethodExample("local steveoni = event.oninputpoll(\r\n\tfunction()\r\n\t\tconsole.log( \"Calls the given lua function after each time the emulator core polls for input\" );\r\n\tend\r\n\t, \"Frame name\" );")] [LuaMethod("oninputpoll", "Calls the given lua function after each time the emulator core polls for input")] public string OnInputPoll(LuaFunction luaf, string name = null) { @@ -204,6 +208,7 @@ namespace BizHawk.Client.Common Log($"Error: {Emulator.Attributes().CoreName} does not yet implement input polling callbacks"); } + [LuaMethodExample("local steveonl = event.onloadstate(\r\n\tfunction()\r\n\tconsole.log( \"Fires after a state is loaded. Receives a lua function name, and registers it to the event immediately following a successful savestate event\" );\r\nend\", \"Frame name\" );")] [LuaMethod("onloadstate", "Fires after a state is loaded. Receives a lua function name, and registers it to the event immediately following a successful savestate event")] public string OnLoadState(LuaFunction luaf, string name = null) { @@ -212,6 +217,7 @@ namespace BizHawk.Client.Common return nlf.Guid.ToString(); } + [LuaMethodExample("local steveonm = event.onmemoryexecute(\r\n\tfunction()\r\n\t\tconsole.log( \"Fires after the given address is executed by the core\" );\r\n\tend\r\n\t, 0x200, \"Frame name\", \"System Bus\" );")] [LuaMethod("onmemoryexecute", "Fires after the given address is executed by the core")] public string OnMemoryExecute(LuaFunction luaf, uint address, string name = null, string domain = null) { @@ -251,6 +257,7 @@ namespace BizHawk.Client.Common return Guid.Empty.ToString(); } + [LuaMethodExample("local steveonm = event.onmemoryread(\r\n\tfunction()\r\n\t\tconsole.log( \"Fires after the given address is read by the core. If no address is given, it will attach to every memory read\" );\r\n\tend\r\n\t, 0x200, \"Frame name\" );")] [LuaMethod("onmemoryread", "Fires after the given address is read by the core. If no address is given, it will attach to every memory read")] public string OnMemoryRead(LuaFunction luaf, uint? address = null, string name = null, string domain = null) { @@ -289,6 +296,7 @@ namespace BizHawk.Client.Common return Guid.Empty.ToString(); } + [LuaMethodExample("local steveonm = event.onmemorywrite(\r\n\tfunction()\r\n\t\tconsole.log( \"Fires after the given address is written by the core. If no address is given, it will attach to every memory write\" );\r\n\tend\r\n\t, 0x200, \"Frame name\" );")] [LuaMethod("onmemorywrite", "Fires after the given address is written by the core. If no address is given, it will attach to every memory write")] public string OnMemoryWrite(LuaFunction luaf, uint? address = null, string name = null, string domain = null) { @@ -327,6 +335,7 @@ namespace BizHawk.Client.Common return Guid.Empty.ToString(); } + [LuaMethodExample("local steveons = event.onsavestate(\r\n\tfunction()\r\n\t\tconsole.log( \"Fires after a state is saved\" );\r\n\tend\r\n\t, \"Frame name\" );")] [LuaMethod("onsavestate", "Fires after a state is saved")] public string OnSaveState(LuaFunction luaf, string name = null) { @@ -335,6 +344,7 @@ namespace BizHawk.Client.Common return nlf.Guid.ToString(); } + [LuaMethodExample("local steveone = event.onexit(\r\n\tfunction()\r\n\t\tconsole.log( \"Fires after the calling script has stopped\" );\r\n\tend\r\n\t, \"Frame name\" );")] [LuaMethod("onexit", "Fires after the calling script has stopped")] public string OnExit(LuaFunction luaf, string name = null) { @@ -343,6 +353,7 @@ namespace BizHawk.Client.Common return nlf.Guid.ToString(); } + [LuaMethodExample("if ( event.unregisterbyid( \"4d1810b7 - 0d28 - 4acb - 9d8b - d87721641551\" ) ) then\r\n\tconsole.log( \"Removes the registered function that matches the guid.If a function is found and remove the function will return true.If unable to find a match, the function will return false.\" );\r\nend;")] [LuaMethod("unregisterbyid", "Removes the registered function that matches the guid. If a function is found and remove the function will return true. If unable to find a match, the function will return false.")] public bool UnregisterById(string guid) { @@ -355,6 +366,7 @@ namespace BizHawk.Client.Common return false; } + [LuaMethodExample("if ( event.unregisterbyname( \"Function name\" ) ) then\r\n\tconsole.log( \"Removes the first registered function that matches Name.If a function is found and remove the function will return true.If unable to find a match, the function will return false.\" );\r\nend;")] [LuaMethod("unregisterbyname", "Removes the first registered function that matches Name. If a function is found and remove the function will return true. If unable to find a match, the function will return false.")] public bool UnregisterByName(string name) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.GameInfo.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.GameInfo.cs index a7223074b4..c098594f37 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.GameInfo.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.GameInfo.cs @@ -18,6 +18,7 @@ namespace BizHawk.Client.Common public override string Name => "gameinfo"; + [LuaMethodExample("local stgamget = gameinfo.getromname( );")] [LuaMethod("getromname", "returns the path of the currently loaded rom, if a rom is loaded")] public string GetRomName() { @@ -29,6 +30,7 @@ namespace BizHawk.Client.Common return ""; } + [LuaMethodExample("local stgamget = gameinfo.getromhash( );")] [LuaMethod("getromhash", "returns the hash of the currently loaded rom, if a rom is loaded")] public string GetRomHash() { @@ -40,6 +42,7 @@ namespace BizHawk.Client.Common return ""; } + [LuaMethodExample("if ( gameinfo.indatabase( ) ) then\r\n\tconsole.log( \"returns whether or not the currently loaded rom is in the game database\" );\r\nend;")] [LuaMethod("indatabase", "returns whether or not the currently loaded rom is in the game database")] public bool InDatabase() { @@ -51,6 +54,7 @@ namespace BizHawk.Client.Common return false; } + [LuaMethodExample("local stgamget = gameinfo.getstatus( );")] [LuaMethod("getstatus", "returns the game database status of the currently loaded rom. Statuses are for example: GoodDump, BadDump, Hack, Unknown, NotInDatabase")] public string GetStatus() { @@ -62,6 +66,7 @@ namespace BizHawk.Client.Common return ""; } + [LuaMethodExample("if ( gameinfo.isstatusbad( ) ) then\r\n\tconsole.log( \"returns the currently loaded rom's game database status is considered 'bad'\" );\r\nend;")] [LuaMethod("isstatusbad", "returns the currently loaded rom's game database status is considered 'bad'")] public bool IsStatusBad() { @@ -73,12 +78,14 @@ namespace BizHawk.Client.Common return true; } + [LuaMethodExample("local stgamget = gameinfo.getboardtype( );")] [LuaMethod("getboardtype", "returns identifying information about the 'mapper' or similar capability used for this game. empty if no such useful distinction can be drawn")] public string GetBoardType() { return BoardInfo?.BoardName ?? ""; } + [LuaMethodExample("local nlgamget = gameinfo.getoptions( );")] [LuaMethod("getoptions", "returns the game options for the currently loaded rom. Options vary per platform")] public LuaTable GetOptions() { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Genesis.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Genesis.cs index 761abae41b..de469a25d7 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Genesis.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Genesis.cs @@ -37,24 +37,28 @@ namespace BizHawk.Client.Common Genesis?.PutSettings(settings); } + [LuaMethodExample("if ( genesis.getlayer_bga( ) ) then\r\n\tconsole.log( \"Returns whether the bg layer A is displayed\" );\r\nend;")] [LuaMethod("getlayer_bga", "Returns whether the bg layer A is displayed")] public bool GetLayerBgA() { return GetSettings().DrawBGA; } + [LuaMethodExample("if ( genesis.getlayer_bgb( ) ) then\r\n\tconsole.log( \"Returns whether the bg layer B is displayed\" );\r\nend;")] [LuaMethod("getlayer_bgb", "Returns whether the bg layer B is displayed")] public bool GetLayerBgB() { return GetSettings().DrawBGB; } + [LuaMethodExample("if ( genesis.getlayer_bgw( ) ) then\r\n\tconsole.log( \"Returns whether the bg layer W is displayed\" );\r\nend;")] [LuaMethod("getlayer_bgw", "Returns whether the bg layer W is displayed")] public bool GetLayerBgW() { return GetSettings().DrawBGW; } + [LuaMethodExample("genesis.setlayer_bga( true );")] [LuaMethod("setlayer_bga", "Sets whether the bg layer A is displayed")] public void SetLayerBgA(bool value) { @@ -63,6 +67,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("genesis.setlayer_bgb( true );")] [LuaMethod("setlayer_bgb", "Sets whether the bg layer B is displayed")] public void SetLayerBgB(bool value) { @@ -71,6 +76,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("genesis.setlayer_bgw( true );")] [LuaMethod("setlayer_bgw", "Sets whether the bg layer W is displayed")] public void SetLayerBgW(bool value) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Joypad.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Joypad.cs index eec7823d45..e5bc81d9be 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Joypad.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Joypad.cs @@ -13,6 +13,7 @@ namespace BizHawk.Client.Common public override string Name => "joypad"; + [LuaMethodExample("local nljoyget = joypad.get( 1 );")] [LuaMethod("get", "returns a lua table of the controller buttons pressed. If supplied, it will only return a table of buttons for the given controller")] public LuaTable Get(int? controller = null) { @@ -50,6 +51,7 @@ namespace BizHawk.Client.Common } // TODO: what about float controls? + [LuaMethodExample("local nljoyget = joypad.getimmediate( );")] [LuaMethod("getimmediate", "returns a lua table of any controller buttons currently pressed by the user")] public LuaTable GetImmediate() { @@ -62,6 +64,7 @@ namespace BizHawk.Client.Common return buttons; } + [LuaMethodExample("joypad.setfrommnemonicstr( \"| 0, 0, 0, 100,...R..B....|\" );")] [LuaMethod("setfrommnemonicstr", "sets the given buttons to their provided values for the current frame, string will be interpretted the same way an entry from a movie input log would be")] public void SetFromMnemonicStr(string inputLogEntry) { @@ -86,6 +89,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("joypad.set( { [\"Left\"] = true, [ \"A\" ] = true, [ \"B\" ] = true } );")] [LuaMethod("set", "sets the given buttons to their provided values for the current frame")] public void Set(LuaTable buttons, int? controller = null) { @@ -146,10 +150,11 @@ namespace BizHawk.Client.Common } catch { - /*Eat it*/ + /*Eat it*/ } } + [LuaMethodExample("joypad.setanalog( { [ \"Tilt X\" ] = true, [ \"Tilt Y\" ] = false } );")] [LuaMethod("setanalog", "sets the given analog controls to their provided values for the current frame. Note that unlike set() there is only the logic of overriding with the given value.")] public void SetAnalog(LuaTable controls, object controller = null) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.MainMemory.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.MainMemory.cs index 9956c3a2dd..1d41611cde 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.MainMemory.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.MainMemory.cs @@ -37,12 +37,14 @@ namespace BizHawk.Client.Common #region Unique Library Methods + [LuaMethodExample("local stmaiget = mainmemory.getname( );")] [LuaMethod("getname", "returns the name of the domain defined as main memory for the given core")] public string GetName() { return Domain.Name; } + [LuaMethodExample("local uimaiget = mainmemory.getcurrentmemorydomainsize( );")] [LuaMethod("getcurrentmemorydomainsize", "Returns the number of bytes of the domain defined as main memory")] public uint GetSize() { @@ -53,36 +55,42 @@ namespace BizHawk.Client.Common #region Common Special and Legacy Methods + [LuaMethodExample("local uimairea = mainmemory.readbyte( 0x100 );")] [LuaMethod("readbyte", "gets the value from the given address as an unsigned byte")] public uint ReadByte(int addr) { return ReadUnsignedByte(addr); } + [LuaMethodExample("mainmemory.writebyte( 0x100, 1000 );")] [LuaMethod("writebyte", "Writes the given value to the given address as an unsigned byte")] public void WriteByte(int addr, uint value) { WriteUnsignedByte(addr, value); } + [LuaMethodExample("local nlmairea = mainmemory.readbyterange( 0x100, 64 );")] [LuaMethod("readbyterange", "Reads the address range that starts from address, and is length long. Returns the result into a table of key value pairs (where the address is the key).")] public LuaTable ReadByteRange(int addr, int length) { return base.ReadByteRange(addr, length); } + [LuaMethodExample("")] [LuaMethod("writebyterange", "Writes the given values to the given addresses as unsigned bytes")] public void WriteByteRange(LuaTable memoryblock) { base.WriteByteRange(memoryblock); } + [LuaMethodExample("local simairea = mainmemory.readfloat(0x100, false);")] [LuaMethod("readfloat", "Reads the given address as a 32-bit float value from the main memory domain with th e given endian")] public float ReadFloat(int addr, bool bigendian) { return base.ReadFloat(addr, bigendian); } + [LuaMethodExample("mainmemory.writefloat( 0x100, 10.0, false );")] [LuaMethod("writefloat", "Writes the given 32-bit float value to the given address and endian")] public void WriteFloat(int addr, double value, bool bigendian) { @@ -93,24 +101,28 @@ namespace BizHawk.Client.Common #region 1 Byte + [LuaMethodExample("local inmairea = mainmemory.read_s8( 0x100 );")] [LuaMethod("read_s8", "read signed byte")] public int ReadS8(int addr) { return (sbyte)ReadUnsignedByte(addr); } + [LuaMethodExample("mainmemory.write_s8( 0x100, 1000 );")] [LuaMethod("write_s8", "write signed byte")] public void WriteS8(int addr, uint value) { WriteUnsignedByte(addr, value); } + [LuaMethodExample("local uimairea = mainmemory.read_u8( 0x100 );")] [LuaMethod("read_u8", "read unsigned byte")] public uint ReadU8(int addr) { return ReadUnsignedByte(addr); } + [LuaMethodExample("mainmemory.write_u8( 0x100, 1000 );")] [LuaMethod("write_u8", "write unsigned byte")] public void WriteU8(int addr, uint value) { @@ -121,48 +133,56 @@ namespace BizHawk.Client.Common #region 2 Byte + [LuaMethodExample("local inmairea = mainmemory.read_s16_le( 0x100 );")] [LuaMethod("read_s16_le", "read signed 2 byte value, little endian")] public int ReadS16Little(int addr) { return ReadSignedLittleCore(addr, 2); } + [LuaMethodExample("mainmemory.write_s16_le( 0x100, -1000 );")] [LuaMethod("write_s16_le", "write signed 2 byte value, little endian")] public void WriteS16Little(int addr, int value) { WriteSignedLittle(addr, value, 2); } + [LuaMethodExample("local inmairea = mainmemory.read_s16_be( 0x100 );")] [LuaMethod("read_s16_be", "read signed 2 byte value, big endian")] public int ReadS16Big(int addr) { return ReadSignedBig(addr, 2); } + [LuaMethodExample("mainmemory.write_s16_be( 0x100, -1000 );")] [LuaMethod("write_s16_be", "write signed 2 byte value, big endian")] public void WriteS16Big(int addr, int value) { WriteSignedBig(addr, value, 2); } + [LuaMethodExample("local uimairea = mainmemory.read_u16_le( 0x100 );")] [LuaMethod("read_u16_le", "read unsigned 2 byte value, little endian")] public uint ReadU16Little(int addr) { return ReadSignedLittle(addr, 2); } + [LuaMethodExample("mainmemory.write_u16_le( 0x100, 1000 );")] [LuaMethod("write_u16_le", "write unsigned 2 byte value, little endian")] public void WriteU16Little(int addr, uint value) { WriteUnsignedLittle(addr, value, 2); } + [LuaMethodExample("local uimairea = mainmemory.read_u16_be( 0x100 );")] [LuaMethod("read_u16_be", "read unsigned 2 byte value, big endian")] public uint ReadU16Big(int addr) { return ReadUnsignedBig(addr, 2); } + [LuaMethodExample("mainmemory.write_u16_be( 0x100, 1000 );")] [LuaMethod("write_u16_be", "write unsigned 2 byte value, big endian")] public void WriteU16Big(int addr, uint value) { @@ -173,48 +193,56 @@ namespace BizHawk.Client.Common #region 3 Byte + [LuaMethodExample("local inmairea = mainmemory.read_s24_le( 0x100 );")] [LuaMethod("read_s24_le", "read signed 24 bit value, little endian")] public int ReadS24Little(int addr) { return ReadSignedLittleCore(addr, 3); } + [LuaMethodExample("mainmemory.write_s24_le( 0x100, -1000 );")] [LuaMethod("write_s24_le", "write signed 24 bit value, little endian")] public void WriteS24Little(int addr, int value) { WriteSignedLittle(addr, value, 3); } + [LuaMethodExample("local inmairea = mainmemory.read_s24_be( 0x100 );")] [LuaMethod("read_s24_be", "read signed 24 bit value, big endian")] public int ReadS24Big(int addr) { return ReadSignedBig(addr, 3); } + [LuaMethodExample("mainmemory.write_s24_be( 0x100, -1000 );")] [LuaMethod("write_s24_be", "write signed 24 bit value, big endian")] public void WriteS24Big(int addr, int value) { WriteSignedBig(addr, value, 3); } + [LuaMethodExample("local uimairea = mainmemory.read_u24_le( 0x100 );")] [LuaMethod("read_u24_le", "read unsigned 24 bit value, little endian")] public uint ReadU24Little(int addr) { return ReadSignedLittle(addr, 3); } + [LuaMethodExample("mainmemory.write_u24_le( 0x100, 1000 );")] [LuaMethod("write_u24_le", "write unsigned 24 bit value, little endian")] public void WriteU24Little(int addr, uint value) { WriteUnsignedLittle(addr, value, 3); } + [LuaMethodExample("local uimairea = mainmemory.read_u24_be( 0x100 );")] [LuaMethod("read_u24_be", "read unsigned 24 bit value, big endian")] public uint ReadU24Big(int addr) { return ReadUnsignedBig(addr, 3); } + [LuaMethodExample("mainmemory.write_u24_be( 0x100, 1000 );")] [LuaMethod("write_u24_be", "write unsigned 24 bit value, big endian")] public void WriteU24Big(int addr, uint value) { @@ -225,48 +253,56 @@ namespace BizHawk.Client.Common #region 4 Byte + [LuaMethodExample("local inmairea = mainmemory.read_s32_le( 0x100 );")] [LuaMethod("read_s32_le", "read signed 4 byte value, little endian")] public int ReadS32Little(int addr) { return ReadSignedLittleCore(addr, 4); } + [LuaMethodExample("mainmemory.write_s32_le( 0x100, -1000 );")] [LuaMethod("write_s32_le", "write signed 4 byte value, little endian")] public void WriteS32Little(int addr, int value) { WriteSignedLittle(addr, value, 4); } + [LuaMethodExample("local inmairea = mainmemory.read_s32_be( 0x100 );")] [LuaMethod("read_s32_be", "read signed 4 byte value, big endian")] public int ReadS32Big(int addr) { return ReadSignedBig(addr, 4); } + [LuaMethodExample("mainmemory.write_s32_be( 0x100, -1000 );")] [LuaMethod("write_s32_be", "write signed 4 byte value, big endian")] public void WriteS32Big(int addr, int value) { WriteSignedBig(addr, value, 4); } + [LuaMethodExample("local uimairea = mainmemory.read_u32_le( 0x100 );")] [LuaMethod("read_u32_le", "read unsigned 4 byte value, little endian")] public uint ReadU32Little(int addr) { return ReadSignedLittle(addr, 4); } + [LuaMethodExample("mainmemory.write_u32_le( 0x100, 1000 );")] [LuaMethod("write_u32_le", "write unsigned 4 byte value, little endian")] public void WriteU32Little(int addr, uint value) { WriteUnsignedLittle(addr, value, 4); } + [LuaMethodExample("local uimairea = mainmemory.read_u32_be( 0x100 );")] [LuaMethod("read_u32_be", "read unsigned 4 byte value, big endian")] public uint ReadU32Big(int addr) { return ReadUnsignedBig(addr, 4); } + [LuaMethodExample("mainmemory.write_u32_be( 0x100, 1000 );")] [LuaMethod("write_u32_be", "write unsigned 4 byte value, big endian")] public void WriteU32Big(int addr, uint value) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Memory.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Memory.cs index 69a04d4028..71395b4737 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Memory.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Memory.cs @@ -49,6 +49,7 @@ namespace BizHawk.Client.Common #region Unique Library Methods + [LuaMethodExample("local nlmemget = memory.getmemorydomainlist();")] [LuaMethod("getmemorydomainlist", "Returns a string of the memory domains for the loaded platform core. List will be a single string delimited by line feeds")] public LuaTable GetMemoryDomainList() { @@ -64,6 +65,7 @@ namespace BizHawk.Client.Common return table; } + [LuaMethodExample("local uimemget = memory.getmemorydomainsize( mainmemory.getname( ) );")] [LuaMethod("getmemorydomainsize", "Returns the number of bytes of the specified memory domain. If no domain is specified, or the specified domain doesn't exist, returns the current domain size")] public uint GetMemoryDomainSize(string name = "") { @@ -75,18 +77,21 @@ namespace BizHawk.Client.Common return (uint)DomainList[VerifyMemoryDomain(name)].Size; } + [LuaMethodExample("local stmemget = memory.getcurrentmemorydomain( );")] [LuaMethod("getcurrentmemorydomain", "Returns a string name of the current memory domain selected by Lua. The default is Main memory")] public string GetCurrentMemoryDomain() { return Domain.Name; } + [LuaMethodExample("local uimemget = memory.getcurrentmemorydomainsize( );")] [LuaMethod("getcurrentmemorydomainsize", "Returns the number of bytes of the current memory domain selected by Lua. The default is Main memory")] public uint GetCurrentMemoryDomainSize() { return (uint)Domain.Size; } + [LuaMethodExample("if ( memory.usememorydomain( mainmemory.getname( ) ) ) then\r\n\tconsole.log( \"Attempts to set the current memory domain to the given domain. If the name does not match a valid memory domain, the function returns false, else it returns true\" );\r\nend;")] [LuaMethod("usememorydomain", "Attempts to set the current memory domain to the given domain. If the name does not match a valid memory domain, the function returns false, else it returns true")] public bool UseMemoryDomain(string domain) { @@ -109,6 +114,7 @@ namespace BizHawk.Client.Common return false; } + [LuaMethodExample("local stmemhas = memory.hash_region( 0x100, 50, mainmemory.getname( ) );")] [LuaMethod("hash_region", "Returns a hash as a string of a region of memory, starting from addr, through count bytes. If the domain is unspecified, it uses the current region.")] public string HashRegion(int addr, int count, string domain = null) { @@ -144,36 +150,42 @@ namespace BizHawk.Client.Common #region Common Special and Legacy Methods + [LuaMethodExample("local uimemrea = memory.readbyte( 0x100, mainmemory.getname( ) );")] [LuaMethod("readbyte", "gets the value from the given address as an unsigned byte")] public uint ReadByte(int addr, string domain = null) { return ReadUnsignedByte(addr, domain); } + [LuaMethodExample("memory.writebyte( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("writebyte", "Writes the given value to the given address as an unsigned byte")] public void WriteByte(int addr, uint value, string domain = null) { WriteUnsignedByte(addr, value, domain); } + [LuaMethodExample("local nlmemrea = memory.readbyterange( 0x100, 30, mainmemory.getname( ) );")] [LuaMethod("readbyterange", "Reads the address range that starts from address, and is length long. Returns the result into a table of key value pairs (where the address is the key).")] public new LuaTable ReadByteRange(int addr, int length, string domain = null) { return base.ReadByteRange(addr, length, domain); } + [LuaMethodExample("")] [LuaMethod("writebyterange", "Writes the given values to the given addresses as unsigned bytes")] public new void WriteByteRange(LuaTable memoryblock, string domain = null) { base.WriteByteRange(memoryblock, domain); } + [LuaMethodExample("local simemrea = memory.readfloat( 0x100, false, mainmemory.getname( ) );")] [LuaMethod("readfloat", "Reads the given address as a 32-bit float value from the main memory domain with th e given endian")] public new float ReadFloat(int addr, bool bigendian, string domain = null) { return base.ReadFloat(addr, bigendian, domain); } + [LuaMethodExample("memory.writefloat( 0x100, 10.0, false, mainmemory.getname( ) );")] [LuaMethod("writefloat", "Writes the given 32-bit float value to the given address and endian")] public new void WriteFloat(int addr, double value, bool bigendian, string domain = null) { @@ -184,24 +196,28 @@ namespace BizHawk.Client.Common #region 1 Byte + [LuaMethodExample("local inmemrea = memory.read_s8( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s8", "read signed byte")] public int ReadS8(int addr, string domain = null) { return (sbyte)ReadUnsignedByte(addr, domain); } + [LuaMethodExample("memory.write_s8( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_s8", "write signed byte")] public void WriteS8(int addr, uint value, string domain = null) { WriteUnsignedByte(addr, value, domain); } + [LuaMethodExample("local uimemrea = memory.read_u8( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u8", "read unsigned byte")] public uint ReadU8(int addr, string domain = null) { return ReadUnsignedByte(addr, domain); } + [LuaMethodExample("memory.write_u8( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u8", "write unsigned byte")] public void WriteU8(int addr, uint value, string domain = null) { @@ -212,48 +228,56 @@ namespace BizHawk.Client.Common #region 2 Byte + [LuaMethodExample("local inmemrea = memory.read_s16_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s16_le", "read signed 2 byte value, little endian")] public int ReadS16Little(int addr, string domain = null) { return ReadSignedLittleCore(addr, 2, domain); } + [LuaMethodExample("memory.write_s16_le( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s16_le", "write signed 2 byte value, little endian")] public void WriteS16Little(int addr, int value, string domain = null) { WriteSignedLittle(addr, value, 2, domain); } + [LuaMethodExample("local inmemrea = memory.read_s16_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s16_be", "read signed 2 byte value, big endian")] public int ReadS16Big(int addr, string domain = null) { return ReadSignedBig(addr, 2, domain); } + [LuaMethodExample("memory.write_s16_be( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s16_be", "write signed 2 byte value, big endian")] public void WriteS16Big(int addr, int value, string domain = null) { WriteSignedBig(addr, value, 2, domain); } + [LuaMethodExample("local uimemrea = memory.read_u16_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u16_le", "read unsigned 2 byte value, little endian")] public uint ReadU16Little(int addr, string domain = null) { return ReadUnsignedLittle(addr, 2, domain); } + [LuaMethodExample("memory.write_u16_le( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u16_le", "write unsigned 2 byte value, little endian")] public void WriteU16Little(int addr, uint value, string domain = null) { WriteUnsignedLittle(addr, value, 2, domain); } + [LuaMethodExample("local uimemrea = memory.read_u16_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u16_be", "read unsigned 2 byte value, big endian")] public uint ReadU16Big(int addr, string domain = null) { return ReadUnsignedBig(addr, 2, domain); } + [LuaMethodExample("memory.write_u16_be( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u16_be", "write unsigned 2 byte value, big endian")] public void WriteU16Big(int addr, uint value, string domain = null) { @@ -264,48 +288,56 @@ namespace BizHawk.Client.Common #region 3 Byte + [LuaMethodExample("local inmemrea = memory.read_s24_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s24_le", "read signed 24 bit value, little endian")] public int ReadS24Little(int addr, string domain = null) { return ReadSignedLittleCore(addr, 3, domain); } + [LuaMethodExample("memory.write_s24_le( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s24_le", "write signed 24 bit value, little endian")] public void WriteS24Little(int addr, int value, string domain = null) { WriteSignedLittle(addr, value, 3, domain); } + [LuaMethodExample("local inmemrea = memory.read_s24_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s24_be", "read signed 24 bit value, big endian")] public int ReadS24Big(int addr, string domain = null) { return ReadSignedBig(addr, 3, domain); } + [LuaMethodExample("memory.write_s24_be( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s24_be", "write signed 24 bit value, big endian")] public void WriteS24Big(int addr, int value, string domain = null) { WriteSignedBig(addr, value, 3, domain); } + [LuaMethodExample("local uimemrea = memory.read_u24_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u24_le", "read unsigned 24 bit value, little endian")] public uint ReadU24Little(int addr, string domain = null) { return ReadUnsignedLittle(addr, 3, domain); } + [LuaMethodExample("memory.write_u24_le( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u24_le", "write unsigned 24 bit value, little endian")] public void WriteU24Little(int addr, uint value, string domain = null) { WriteUnsignedLittle(addr, value, 3, domain); } + [LuaMethodExample("local uimemrea = memory.read_u24_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u24_be", "read unsigned 24 bit value, big endian")] public uint ReadU24Big(int addr, string domain = null) { return ReadUnsignedBig(addr, 3, domain); } + [LuaMethodExample("memory.write_u24_be( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u24_be", "write unsigned 24 bit value, big endian")] public void WriteU24Big(int addr, uint value, string domain = null) { @@ -316,48 +348,56 @@ namespace BizHawk.Client.Common #region 4 Byte + [LuaMethodExample("local inmemrea = memory.read_s32_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s32_le", "read signed 4 byte value, little endian")] public int ReadS32Little(int addr, string domain = null) { return ReadSignedLittleCore(addr, 4, domain); } + [LuaMethodExample("memory.write_s32_le( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s32_le", "write signed 4 byte value, little endian")] public void WriteS32Little(int addr, int value, string domain = null) { WriteSignedLittle(addr, value, 4, domain); } + [LuaMethodExample("local inmemrea = memory.read_s32_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_s32_be", "read signed 4 byte value, big endian")] public int ReadS32Big(int addr, string domain = null) { return ReadSignedBig(addr, 4, domain); } + [LuaMethodExample("memory.write_s32_be( 0x100, -1000, mainmemory.getname( ) );")] [LuaMethod("write_s32_be", "write signed 4 byte value, big endian")] public void WriteS32Big(int addr, int value, string domain = null) { WriteSignedBig(addr, value, 4, domain); } + [LuaMethodExample("local uimemrea = memory.read_u32_le( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u32_le", "read unsigned 4 byte value, little endian")] public uint ReadU32Little(int addr, string domain = null) { return ReadUnsignedLittle(addr, 4, domain); } + [LuaMethodExample("memory.write_u32_le( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u32_le", "write unsigned 4 byte value, little endian")] public void WriteU32Little(int addr, uint value, string domain = null) { WriteUnsignedLittle(addr, value, 4, domain); } + [LuaMethodExample("local uimemrea = memory.read_u32_be( 0x100, mainmemory.getname( ) );")] [LuaMethod("read_u32_be", "read unsigned 4 byte value, big endian")] public uint ReadU32Big(int addr, string domain = null) { return ReadUnsignedBig(addr, 4, domain); } + [LuaMethodExample("memory.write_u32_be( 0x100, 1000, mainmemory.getname( ) );")] [LuaMethod("write_u32_be", "write unsigned 4 byte value, big endian")] public void WriteU32Big(int addr, uint value, string domain = null) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.MemorySavestate.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.MemorySavestate.cs index 28534715a8..b8a52a6122 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.MemorySavestate.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.MemorySavestate.cs @@ -23,6 +23,7 @@ namespace BizHawk.Client.Common private readonly Dictionary _memorySavestates = new Dictionary(); + [LuaMethodExample("local mmsvstsvcst = memorysavestate.savecorestate( );")] [LuaMethod("savecorestate", "creates a core savestate and stores it in memory. Note: a core savestate is only the raw data from the core, and not extras such as movie input logs, or framebuffers. Returns a unique identifer for the savestate")] public string SaveCoreStateToMemory() { @@ -34,6 +35,7 @@ namespace BizHawk.Client.Common return guid.ToString(); } + [LuaMethodExample("memorysavestate.loadcorestate( \"3fcf120f-0778-43fd-b2c5-460fb7d34184\" );")] [LuaMethod("loadcorestate", "loads an in memory state with the given identifier")] public void LoadCoreStateFromMemory(string identifier) { @@ -55,6 +57,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("memorysavestate.removestate( \"3fcf120f-0778-43fd-b2c5-460fb7d34184\" );")] [LuaMethod("removestate", "removes the savestate with the given identifier from memory")] public void DeleteState(string identifier) { @@ -62,6 +65,7 @@ namespace BizHawk.Client.Common _memorySavestates.Remove(guid); } + [LuaMethodExample("memorysavestate.clearstatesfrommemory( );")] [LuaMethod("clearstatesfrommemory", "clears all savestates stored in memory")] public void ClearInMemoryStates() { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.Movie.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.Movie.cs index d3cdbfe77f..4131788a70 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.Movie.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.Movie.cs @@ -14,24 +14,28 @@ namespace BizHawk.Client.Common public override string Name => "movie"; + [LuaMethodExample("if ( movie.startsfromsavestate( ) ) then\r\n\tconsole.log( \"Returns whether or not the movie is a savestate-anchored movie\" );\r\nend;")] [LuaMethod("startsfromsavestate", "Returns whether or not the movie is a savestate-anchored movie")] public bool StartsFromSavestate() { return Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.StartsFromSavestate; } + [LuaMethodExample("if ( movie.startsfromsaveram( ) ) then\r\n\tconsole.log( \"Returns whether or not the movie is a saveram-anchored movie\" );\r\nend;")] [LuaMethod("startsfromsaveram", "Returns whether or not the movie is a saveram-anchored movie")] public bool StartsFromSaveram() { return Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.StartsFromSaveRam; } + [LuaMethodExample("local stmovfil = movie.filename( );")] [LuaMethod("filename", "Returns the file name including path of the currently loaded movie")] public static string Filename() { return Global.MovieSession.Movie.Filename; } + [LuaMethodExample("local nlmovget = movie.getinput( 500 );")] [LuaMethod("getinput", "Returns a table of buttons pressed on a given frame of the loaded movie")] public LuaTable GetInput(int frame) { @@ -63,6 +67,7 @@ namespace BizHawk.Client.Common return input; } + [LuaMethodExample("local stmovget = movie.getinputasmnemonic( 500 );")] [LuaMethod("getinputasmnemonic", "Returns the input of a given frame of the loaded movie in a raw inputlog string")] public string GetInputAsMnemonic(int frame) { @@ -76,36 +81,42 @@ namespace BizHawk.Client.Common return ""; } + [LuaMethodExample("if ( movie.getreadonly( ) ) then\r\n\tconsole.log( \"Returns true if the movie is in read-only mode, false if in read+write\" );\r\nend;")] [LuaMethod("getreadonly", "Returns true if the movie is in read-only mode, false if in read+write")] public static bool GetReadOnly() { return Global.MovieSession.ReadOnly; } + [LuaMethodExample("local ulmovget = movie.getrerecordcount();")] [LuaMethod("getrerecordcount", "Gets the rerecord count of the current movie.")] public static ulong GetRerecordCount() { return Global.MovieSession.Movie.Rerecords; } + [LuaMethodExample("if ( movie.getrerecordcounting( ) ) then\r\n\tconsole.log( \"Returns whether or not the current movie is incrementing rerecords on loadstate\" );\r\nend;")] [LuaMethod("getrerecordcounting", "Returns whether or not the current movie is incrementing rerecords on loadstate")] public static bool GetRerecordCounting() { return Global.MovieSession.Movie.IsCountingRerecords; } + [LuaMethodExample("if ( movie.isloaded( ) ) then\r\n\tconsole.log( \"Returns true if a movie is loaded in memory ( play, record, or finished modes ), false if not ( inactive mode )\" );\r\nend;")] [LuaMethod("isloaded", "Returns true if a movie is loaded in memory (play, record, or finished modes), false if not (inactive mode)")] public static bool IsLoaded() { return Global.MovieSession.Movie.IsActive; } + [LuaMethodExample("local domovlen = movie.length( );")] [LuaMethod("length", "Returns the total number of frames of the loaded movie")] public static double Length() { return Global.MovieSession.Movie.FrameCount; } + [LuaMethodExample("local stmovmod = movie.mode( );")] [LuaMethod("mode", "Returns the mode of the current movie. Possible modes: \"PLAY\", \"RECORD\", \"FINISHED\", \"INACTIVE\"")] public static string Mode() { @@ -113,20 +124,21 @@ namespace BizHawk.Client.Common { return "FINISHED"; } - + if (Global.MovieSession.Movie.IsPlaying) { return "PLAY"; } - + if (Global.MovieSession.Movie.IsRecording) { return "RECORD"; } - + return "INACTIVE"; } + [LuaMethodExample("movie.save( \"C:\\moviename.ext\" );")] [LuaMethod("save", "Saves the current movie to the disc. If the filename is provided (no extension or path needed), the movie is saved under the specified name to the current movie directory. The filename may contain a subdirectory, it will be created if it doesn't exist. Existing files won't get overwritten.")] public void Save(string filename = "") { @@ -151,12 +163,14 @@ namespace BizHawk.Client.Common Global.MovieSession.Movie.Save(); } + [LuaMethodExample("movie.setreadonly( false );")] [LuaMethod("setreadonly", "Sets the read-only state to the given value. true for read only, false for read+write")] public static void SetReadOnly(bool readOnly) { Global.MovieSession.ReadOnly = readOnly; } + [LuaMethodExample("movie.setrerecordcount( 20.0 );")] [LuaMethod("setrerecordcount", "Sets the rerecord count of the current movie.")] public static void SetRerecordCount(double count) { @@ -172,18 +186,21 @@ namespace BizHawk.Client.Common Global.MovieSession.Movie.Rerecords = (ulong)count; } + [LuaMethodExample("movie.setrerecordcounting( true );")] [LuaMethod("setrerecordcounting", "Sets whether or not the current movie will increment the rerecord counter on loadstate")] public static void SetRerecordCounting(bool counting) { Global.MovieSession.Movie.IsCountingRerecords = counting; } + [LuaMethodExample("movie.stop( );")] [LuaMethod("stop", "Stops the current movie")] public static void Stop() { Global.MovieSession.Movie.Stop(); } + [LuaMethodExample("local domovget = movie.getfps( );")] [LuaMethod("getfps", "If a movie is loaded, gets the frames per second used by the movie to determine the movie length time")] public static double GetFps() { @@ -200,6 +217,7 @@ namespace BizHawk.Client.Common return 0.0; } + [LuaMethodExample("local nlmovget = movie.getheader( );")] [LuaMethod("getheader", "If a movie is active, will return the movie header as a lua table")] public LuaTable GetHeader() { @@ -215,6 +233,7 @@ namespace BizHawk.Client.Common return luaTable; } + [LuaMethodExample("local nlmovget = movie.getcomments( );")] [LuaMethod("getcomments", "If a movie is active, will return the movie comments as a lua table")] public LuaTable GetComments() { @@ -230,6 +249,7 @@ namespace BizHawk.Client.Common return luaTable; } + [LuaMethodExample("local nlmovget = movie.getsubtitles( );")] [LuaMethod("getsubtitles", "If a movie is active, will return the movie subtitles as a lua table")] public LuaTable GetSubtitles() { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.NES.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.NES.cs index ddafda1dfc..d1e4ea11b1 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.NES.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.NES.cs @@ -38,6 +38,7 @@ namespace BizHawk.Client.Common public override string Name => "nes"; + [LuaMethodExample("nes.addgamegenie( \"GXXZZLVI\" );")] [LuaMethod("addgamegenie", "Adds the specified game genie code. If an NES game is not currently loaded or the code is not a valid game genie code, this will have no effect")] public void AddGameGenie(string code) { @@ -59,6 +60,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("if ( nes.getallowmorethaneightsprites( ) ) then\r\n\tconsole.log( \"Gets the NES setting 'Allow more than 8 sprites per scanline' value\" );\r\nend;")] [LuaMethod("getallowmorethaneightsprites", "Gets the NES setting 'Allow more than 8 sprites per scanline' value")] public bool GetAllowMoreThanEightSprites() { @@ -75,6 +77,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("local innesget = nes.getbottomscanline( false );")] [LuaMethod("getbottomscanline", "Gets the current value for the bottom scanline value")] public int GetBottomScanline(bool pal = false) { @@ -93,6 +96,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("if ( nes.getclipleftandright( ) ) then\r\n\tconsole.log( \"Gets the current value for the Clip Left and Right sides option\" );\r\nend;")] [LuaMethod("getclipleftandright", "Gets the current value for the Clip Left and Right sides option")] public bool GetClipLeftAndRight() { @@ -109,6 +113,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("if ( nes.getdispbackground( ) ) then\r\n\tconsole.log( \"Indicates whether or not the bg layer is being displayed\" );\r\nend;")] [LuaMethod("getdispbackground", "Indicates whether or not the bg layer is being displayed")] public bool GetDisplayBackground() { @@ -125,6 +130,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("if ( nes.getdispsprites( ) ) then\r\n\tconsole.log( \"Indicates whether or not sprites are being displayed\" );\r\nend;")] [LuaMethod("getdispsprites", "Indicates whether or not sprites are being displayed")] public bool GetDisplaySprites() { @@ -141,6 +147,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("local innesget = nes.gettopscanline(false);")] [LuaMethod("gettopscanline", "Gets the current value for the top scanline value")] public int GetTopScanline(bool pal = false) { @@ -159,6 +166,7 @@ namespace BizHawk.Client.Common throw new InvalidOperationException(); } + [LuaMethodExample("nes.removegamegenie( \"GXXZZLVI\" );")] [LuaMethod("removegamegenie", "Removes the specified game genie code. If an NES game is not currently loaded or the code is not a valid game genie code, this will have no effect")] public void RemoveGameGenie(string code) { @@ -170,6 +178,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("nes.setallowmorethaneightsprites( true );")] [LuaMethod("setallowmorethaneightsprites", "Sets the NES setting 'Allow more than 8 sprites per scanline'")] public void SetAllowMoreThanEightSprites(bool allow) { @@ -187,6 +196,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("nes.setclipleftandright( true );")] [LuaMethod("setclipleftandright", "Sets the Clip Left and Right sides option")] public void SetClipLeftAndRight(bool leftandright) { @@ -204,6 +214,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("nes.setdispbackground( true );")] [LuaMethod("setdispbackground", "Sets whether or not the background layer will be displayed")] public void SetDisplayBackground(bool show) { @@ -215,6 +226,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("nes.setdispsprites( true );")] [LuaMethod("setdispsprites", "Sets whether or not sprites will be displayed")] public void SetDisplaySprites(bool show) { @@ -232,6 +244,7 @@ namespace BizHawk.Client.Common } } + [LuaMethodExample("nes.setscanlines( 10, 20, false );")] [LuaMethod("setscanlines", "sets the top and bottom scanlines to be drawn (same values as in the graphics options dialog). Top must be in the range of 0 to 127, bottom must be between 128 and 239. Not supported in the Quick Nes core")] public void SetScanlines(int top, int bottom, bool pal = false) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.SNES.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.SNES.cs index 3c6e23ca7c..3a5bbdc617 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.SNES.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.SNES.cs @@ -35,54 +35,63 @@ namespace BizHawk.Client.Common Snes?.PutSettings(settings); } + [LuaMethodExample("if ( snes.getlayer_bg_1( ) ) then\r\n\tconsole.log( \"Returns whether the bg 1 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_bg_1", "Returns whether the bg 1 layer is displayed")] public bool GetLayerBg1() { return GetSettings().ShowBG1_1; } + [LuaMethodExample("if ( snes.getlayer_bg_2( ) ) then\r\n\tconsole.log( \"Returns whether the bg 2 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_bg_2", "Returns whether the bg 2 layer is displayed")] public bool GetLayerBg2() { return GetSettings().ShowBG2_1; } + [LuaMethodExample("if ( snes.getlayer_bg_3( ) ) then\r\n\tconsole.log( \"Returns whether the bg 3 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_bg_3", "Returns whether the bg 3 layer is displayed")] public bool GetLayerBg3() { return GetSettings().ShowBG3_1; } + [LuaMethodExample("if ( snes.getlayer_bg_4( ) ) then\r\n\tconsole.log( \"Returns whether the bg 4 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_bg_4", "Returns whether the bg 4 layer is displayed")] public bool GetLayerBg4() { return GetSettings().ShowBG4_1; } + [LuaMethodExample("if ( snes.getlayer_obj_1( ) ) then\r\n\tconsole.log( \"Returns whether the obj 1 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_obj_1", "Returns whether the obj 1 layer is displayed")] public bool GetLayerObj1() { return GetSettings().ShowOBJ_0; } + [LuaMethodExample("if ( snes.getlayer_obj_2( ) ) then\r\n\tconsole.log( \"Returns whether the obj 2 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_obj_2", "Returns whether the obj 2 layer is displayed")] public bool GetLayerObj2() { return GetSettings().ShowOBJ_1; } + [LuaMethodExample("if ( snes.getlayer_obj_3( ) ) then\r\n\tconsole.log( \"Returns whether the obj 3 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_obj_3", "Returns whether the obj 3 layer is displayed")] public bool GetLayerObj3() { return GetSettings().ShowOBJ_2; } + [LuaMethodExample("if ( snes.getlayer_obj_4( ) ) then\r\n\tconsole.log( \"Returns whether the obj 4 layer is displayed\" );\r\nend;")] [LuaMethod("getlayer_obj_4", "Returns whether the obj 4 layer is displayed")] public bool GetLayerObj4() { return GetSettings().ShowOBJ_3; } + [LuaMethodExample("snes.setlayer_bg_1( true );")] [LuaMethod("setlayer_bg_1", "Sets whether the bg 1 layer is displayed")] public void SetLayerBg1(bool value) { @@ -91,6 +100,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_bg_2( true );")] [LuaMethod("setlayer_bg_2", "Sets whether the bg 2 layer is displayed")] public void SetLayerBg2(bool value) { @@ -99,6 +109,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_bg_3( true );")] [LuaMethod("setlayer_bg_3", "Sets whether the bg 3 layer is displayed")] public void SetLayerBg3(bool value) { @@ -107,6 +118,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_bg_4( true );")] [LuaMethod("setlayer_bg_4", "Sets whether the bg 4 layer is displayed")] public void SetLayerBg4(bool value) { @@ -115,6 +127,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_obj_1( true );")] [LuaMethod("setlayer_obj_1", "Sets whether the obj 1 layer is displayed")] public void SetLayerObj1(bool value) { @@ -123,6 +136,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_obj_2( true );")] [LuaMethod("setlayer_obj_2", "Sets whether the obj 2 layer is displayed")] public void SetLayerObj2(bool value) { @@ -131,6 +145,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_obj_3( true );")] [LuaMethod("setlayer_obj_3", "Sets whether the obj 3 layer is displayed")] public void SetLayerObj3(bool value) { @@ -139,6 +154,7 @@ namespace BizHawk.Client.Common PutSettings(s); } + [LuaMethodExample("snes.setlayer_obj_4( true );")] [LuaMethod("setlayer_obj_4", "Sets whether the obj 4 layer is displayed")] public void SetLayerObj4(bool value) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs index e34baf4958..5738e975db 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs @@ -21,7 +21,8 @@ namespace BizHawk.Client.Common SQLiteConnection m_dbConnection; string connectionString; - [LuaMethod("createdatabase","Creates a SQLite Database. Name should end with .db")] + [LuaMethodExample("local stSQLcre = SQL.createdatabase( \"eg_db\" );")] + [LuaMethod("createdatabase", "Creates a SQLite Database. Name should end with .db")] public string CreateDatabase(string name) { try @@ -37,6 +38,7 @@ namespace BizHawk.Client.Common } + [LuaMethodExample("local stSQLope = SQL.opendatabase( \"eg_db\" );")] [LuaMethod("opendatabase", "Opens a SQLite database. Name should end with .db")] public string OpenDatabase(string name) { @@ -54,15 +56,16 @@ namespace BizHawk.Client.Common m_dbConnection.Close(); return "Database Opened Successfully"; } - catch(SQLiteException sqlEX) + catch (SQLiteException sqlEX) { return sqlEX.Message; } } + [LuaMethodExample("local stSQLwri = SQL.writecommand( \"CREATE TABLE eg_tab ( eg_tab_id integer PRIMARY KEY, eg_tab_row_name text NOT NULL ); INSERT INTO eg_tab ( eg_tab_id, eg_tab_row_name ) VALUES ( 1, 'Example table row' );\" );")] [LuaMethod("writecommand", "Runs a SQLite write command which includes CREATE,INSERT, UPDATE. " + "Ex: create TABLE rewards (ID integer PRIMARY KEY, action VARCHAR(20)) ")] - public string WriteCommand(string query="") + public string WriteCommand(string query = "") { if (query == "") { @@ -83,18 +86,19 @@ namespace BizHawk.Client.Common { return "Database not open."; } - catch(SQLiteException sqlEX) + catch (SQLiteException sqlEX) { m_dbConnection.Close(); return sqlEX.Message; } } + [LuaMethodExample("local obSQLrea = SQL.readcommand( \"SELECT * FROM eg_tab WHERE eg_tab_id = 1;\" );")] [LuaMethod("readcommand", "Run a SQLite read command which includes Select. Returns all rows into a LuaTable." + "Ex: select * from rewards")] - public dynamic ReadCommand(string query="") + public dynamic ReadCommand(string query = "") { - if (query=="") + if (query == "") { return "query is empty"; } @@ -102,10 +106,10 @@ namespace BizHawk.Client.Common { var table = Lua.NewTable(); m_dbConnection.Open(); - string sql = "PRAGMA read_uncommitted =1;"+query; + string sql = "PRAGMA read_uncommitted =1;" + query; SQLiteCommand command = new SQLiteCommand(sql, m_dbConnection); SQLiteDataReader reader = command.ExecuteReader(); - bool rows=reader.HasRows; + bool rows = reader.HasRows; long rowCount = 0; var columns = new List(); for (int i = 0; i < reader.FieldCount; ++i) //Add all column names into list @@ -113,16 +117,16 @@ namespace BizHawk.Client.Common columns.Add(reader.GetName(i)); } while (reader.Read()) - { + { for (int i = 0; i < reader.FieldCount; ++i) { - table[columns[i]+" "+rowCount.ToString()] = reader.GetValue(i); + table[columns[i] + " " + rowCount.ToString()] = reader.GetValue(i); } rowCount += 1; } reader.Close(); m_dbConnection.Close(); - if (rows==false) + if (rows == false) { return "No rows found"; } diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.String.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.String.cs index be10855597..d73936aa17 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.String.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.String.cs @@ -17,6 +17,7 @@ namespace BizHawk.Client.Common public StringLuaLibrary(Lua lua, Action logOutputCallback) : base(lua, logOutputCallback) { } + [LuaMethodExample("local stbizhex = bizstring.hex( -12345 );")] [LuaMethod("hex", "Converts the number to a string representation of the hexadecimal value of the given number")] public static string Hex(long num) { @@ -29,6 +30,7 @@ namespace BizHawk.Client.Common return hex; } + [LuaMethodExample("local stbizbin = bizstring.binary( -12345 );")] [LuaMethod("binary", "Converts the number to a string representation of the binary value of the given number")] public static string Binary(long num) { @@ -37,6 +39,7 @@ namespace BizHawk.Client.Common return binary; } + [LuaMethodExample("local stbizoct = bizstring.octal( -12345 );")] [LuaMethod("octal", "Converts the number to a string representation of the octal value of the given number")] public static string Octal(long num) { @@ -49,6 +52,7 @@ namespace BizHawk.Client.Common return octal; } + [LuaMethodExample("local stbiztri = bizstring.trim( \"Some trim string\t \" );")] [LuaMethod("trim", "returns a string that trims whitespace on the left and right ends of the string")] public static string Trim(string str) { @@ -60,6 +64,7 @@ namespace BizHawk.Client.Common return str.Trim(); } + [LuaMethodExample("local stbizrep = bizstring.replace( \"Some string\", \"Some\", \"Replaced\" );")] [LuaMethod("replace", "Returns a string that replaces all occurances of str2 in str1 with the value of replace")] public static string Replace(string str, string str2, string replace) { @@ -71,6 +76,7 @@ namespace BizHawk.Client.Common return str.Replace(str2, replace); } + [LuaMethodExample("local stbiztou = bizstring.toupper( \"Some string\" );")] [LuaMethod("toupper", "Returns an uppercase version of the given string")] public static string ToUpper(string str) { @@ -82,6 +88,7 @@ namespace BizHawk.Client.Common return str.ToUpper(); } + [LuaMethodExample("local stbiztol = bizstring.tolower( \"Some string\" );")] [LuaMethod("tolower", "Returns an lowercase version of the given string")] public static string ToLower(string str) { @@ -93,6 +100,7 @@ namespace BizHawk.Client.Common return str.ToLower(); } + [LuaMethodExample("local stbizsub = bizstring.substring( \"Some string\", 6, 3 );")] [LuaMethod("substring", "Returns a string that represents a substring of str starting at position for the specified length")] public static string SubString(string str, int position, int length) { @@ -104,6 +112,7 @@ namespace BizHawk.Client.Common return str.Substring(position, length); } + [LuaMethodExample("local stbizrem = bizstring.remove( \"Some string\", 4, 5 );")] [LuaMethod("remove", "Returns a string that represents str with the given position and count removed")] public static string Remove(string str, int position, int count) { @@ -115,6 +124,7 @@ namespace BizHawk.Client.Common return str.Remove(position, count); } + [LuaMethodExample("if ( bizstring.contains( \"Some string\", \"Some\") ) then\r\n\tconsole.log( \"Returns whether or not str contains str2\" );\r\nend;")] [LuaMethod("contains", "Returns whether or not str contains str2")] public static bool Contains(string str, string str2) { @@ -126,6 +136,7 @@ namespace BizHawk.Client.Common return str.Contains(str2); } + [LuaMethodExample("if ( bizstring.startswith( \"Some string\", \"Some\") ) then\r\n\tconsole.log( \"Returns whether str starts with str2\" );\r\nend;")] [LuaMethod("startswith", "Returns whether str starts with str2")] public static bool StartsWith(string str, string str2) { @@ -137,6 +148,7 @@ namespace BizHawk.Client.Common return str.StartsWith(str2); } + [LuaMethodExample("if ( bizstring.endswith( \"Some string\", \"string\") ) then\r\n\tconsole.log( \"Returns whether str ends wth str2\" );\r\nend;")] [LuaMethod("endswith", "Returns whether str ends wth str2")] public static bool EndsWith(string str, string str2) { @@ -148,6 +160,7 @@ namespace BizHawk.Client.Common return str.EndsWith(str2); } + [LuaMethodExample("local nlbizspl = bizstring.split( \"Some, string\", \", \" );")] [LuaMethod("split", "Splits str based on separator into a LuaTable. Separator must be one character!. Same functionality as .NET string.Split() using the RemoveEmptyEntries option")] public LuaTable Split(string str, string separator) { diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.UserData.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.UserData.cs index 3e280ace40..101674a187 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.UserData.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.UserData.cs @@ -18,9 +18,10 @@ namespace BizHawk.Client.EmuHawk public override string Name => "userdata"; + [LuaMethodExample("userdata.set(\"Unique key\", \"Current key data\");")] [LuaMethod("set", "adds or updates the data with the given key with the given value")] public void Set(string name, object value) - { + { if (value != null) { var t = value.GetType(); @@ -33,6 +34,7 @@ namespace BizHawk.Client.EmuHawk Global.UserBag[name] = value; } + [LuaMethodExample("local obuseget = userdata.get( \"Unique key\" );")] [LuaMethod("get", "gets the data with the given key, if the key does not exist it will return nil")] public object Get(string key) { @@ -44,18 +46,21 @@ namespace BizHawk.Client.EmuHawk return null; } + [LuaMethodExample("userdata.clear( );")] [LuaMethod("clear", "clears all user data")] public void Clear() { Global.UserBag.Clear(); } + [LuaMethodExample("if ( userdata.remove( \"Unique key\" ) ) then\r\n\tconsole.log( \"remove the data with the given key.Returns true if the element is successfully found and removed; otherwise, false.\" );\r\nend;")] [LuaMethod("remove", "remove the data with the given key. Returns true if the element is successfully found and removed; otherwise, false.")] public bool Remove(string key) { return Global.UserBag.Remove(key); } + [LuaMethodExample("if ( userdata.containskey( \"Unique key\" ) ) then\r\n\tconsole.log( \"returns whether or not there is an entry for the given key\" );\r\nend;")] [LuaMethod("containskey", "returns whether or not there is an entry for the given key")] public bool ContainsKey(string key) { diff --git a/BizHawk.Client.Common/lua/LuaAttributes.cs b/BizHawk.Client.Common/lua/LuaAttributes.cs index ebfb733a93..398046f01a 100644 --- a/BizHawk.Client.Common/lua/LuaAttributes.cs +++ b/BizHawk.Client.Common/lua/LuaAttributes.cs @@ -15,6 +15,18 @@ namespace BizHawk.Client.Common public string Description { get; } } + [AttributeUsage(AttributeTargets.Method)] + public class LuaMethodExampleAttribute : Attribute + { + public LuaMethodExampleAttribute(string example) + { + Example = example; + } + + public string Name { get; } + public string Example { get; } + } + [AttributeUsage(AttributeTargets.Class)] public class LuaLibraryAttribute : Attribute { diff --git a/BizHawk.Client.Common/lua/LuaDocumentation.cs b/BizHawk.Client.Common/lua/LuaDocumentation.cs index 34f0271d7e..9487336989 100644 --- a/BizHawk.Client.Common/lua/LuaDocumentation.cs +++ b/BizHawk.Client.Common/lua/LuaDocumentation.cs @@ -162,12 +162,13 @@ __Types and notation__ public class LibraryFunction { private readonly LuaMethodAttribute _luaAttributes; + private readonly LuaMethodExampleAttribute _luaExampleAttribute; private readonly MethodInfo _method; public LibraryFunction(string library, string libraryDescription, MethodInfo method) { - _luaAttributes = method.GetCustomAttributes(typeof(LuaMethodAttribute), false) - .First() as LuaMethodAttribute; + _luaAttributes = method.GetCustomAttribute(false); + _luaExampleAttribute = method.GetCustomAttribute(false); _method = method; Library = library; @@ -183,6 +184,8 @@ __Types and notation__ public string Description => _luaAttributes.Description; + public string Example => _luaExampleAttribute?.Example; + private string _paramterList = null; public string ParameterList diff --git a/BizHawk.Client.Common/lua/LuaExamples.cs b/BizHawk.Client.Common/lua/LuaExamples.cs new file mode 100644 index 0000000000..ff39999bad --- /dev/null +++ b/BizHawk.Client.Common/lua/LuaExamples.cs @@ -0,0 +1,28 @@ +using System; + +namespace BizHawk.Client.Common +{ + [AttributeUsage(AttributeTargets.Method)] + public class LuaMethodExample : Attribute + { + public LuaMethodExample(string name, string example) + { + Name = name; + Example = example; + } + + public string Name { get; } + public string Example { get; } + } + + [AttributeUsage(AttributeTargets.Class)] + public class LuaLibraryExample : Attribute + { + public LuaLibraryExample(bool released) + { + Released = released; + } + + public bool Released { get; } + } +} diff --git a/BizHawk.Client.Common/lua/LuaMemoryBase.cs b/BizHawk.Client.Common/lua/LuaMemoryBase.cs index 683ee68cfe..b0b814983c 100644 --- a/BizHawk.Client.Common/lua/LuaMemoryBase.cs +++ b/BizHawk.Client.Common/lua/LuaMemoryBase.cs @@ -177,7 +177,7 @@ namespace BizHawk.Client.Common var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; var lastAddr = length + addr; var table = Lua.NewTable(); - if (lastAddr < d.Size) + if (lastAddr <= d.Size) { for (var i = 0; i < length; i++) { diff --git a/BizHawk.Client.Common/movie/bk2/Bk2ControllerAdapter.cs b/BizHawk.Client.Common/movie/bk2/Bk2ControllerAdapter.cs index 157da701e7..026bfbcfb7 100644 --- a/BizHawk.Client.Common/movie/bk2/Bk2ControllerAdapter.cs +++ b/BizHawk.Client.Common/movie/bk2/Bk2ControllerAdapter.cs @@ -143,6 +143,12 @@ namespace BizHawk.Client.Common { _myBoolButtons[button] = Global.AutofireStickyXORAdapter.IsSticky(button); } + + // float controls don't have sticky logic. but old values remain in MovieOutputHardpoint if we don't update this here + foreach (var name in Definition.FloatControls) + { + _myFloatControls[name] = Global.AutofireStickyXORAdapter.GetFloat(name); + } } /// diff --git a/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 59fa396131..f56331b533 100644 --- a/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -20,9 +20,10 @@ namespace BizHawk.Client.Common return; } + var backupDir = PathManager.MakeAbsolutePath(Global.Config.PathEntries.MoviesBackupsPathFragment, null); var backupName = Filename; backupName = backupName.Insert(Filename.LastIndexOf("."), $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}"); - backupName = Path.Combine(Global.Config.PathEntries["Global", "Movie backups"].Path, Path.GetFileName(backupName)); + backupName = Path.Combine(backupDir, Path.GetFileName(backupName)); var directoryInfo = new FileInfo(backupName).Directory; if (directoryInfo != null) diff --git a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs index bc52d1ded0..ca8492c3e7 100644 --- a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs +++ b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs @@ -157,7 +157,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions // States can't be easily moved over, because they contain the frame number. // TODO? I'm not sure how this would be done. tas.TasStateManager.MountWriteAccess(); - old.TasStateManager.Clear(); + old.TasStateManager.ClearStateHistory(); // Lag Log tas.TasLagLog.FromLagLog(old.TasLagLog); @@ -221,7 +221,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions } var tas = new TasMovie(newFilename, true) { SaveRam = saveRam }; - tas.TasStateManager.Clear(); + tas.TasStateManager.ClearStateHistory(); tas.ClearLagLog(); var entries = old.GetLogEntries(); diff --git a/BizHawk.Client.Common/movie/import/PJMImport.cs b/BizHawk.Client.Common/movie/import/PJMImport.cs index 6c997698cc..0ced2f3f15 100644 --- a/BizHawk.Client.Common/movie/import/PJMImport.cs +++ b/BizHawk.Client.Common/movie/import/PJMImport.cs @@ -121,13 +121,13 @@ namespace BizHawk.Client.Common { case 0: case 8: - info.Player1Type = OctoshockDll.ePeripheralType.None; + info.Player2Type = OctoshockDll.ePeripheralType.None; break; case 4: - info.Player1Type = OctoshockDll.ePeripheralType.Pad; + info.Player2Type = OctoshockDll.ePeripheralType.Pad; break; case 7: - info.Player1Type = OctoshockDll.ePeripheralType.DualShock; + info.Player2Type = OctoshockDll.ePeripheralType.DualShock; break; default: Result.Errors.Add("Movie has unrecognised controller type for Player 2."); diff --git a/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs b/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs new file mode 100644 index 0000000000..7cf5fbdccd --- /dev/null +++ b/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs @@ -0,0 +1,203 @@ +/**************************************************************************************** + + Algorithm by r57shell & feos, 2018 + + _zeros is the key to GREENZONE DECAY PATTERN. + + In a 16 element example, we evaluate these bitwise numbers to count zeros on the right. + First element is always assumed to be 16, which has all 4 bits set to 0. Each right zero + means that we lower the priority of a state that goes at that index. Priority changes + depending on current frame and amount of states. States with biggest priority get erased + first. With a 4-bit battern and no initial gap between states, total frame coverage is + about 5 times state count. Initial state gap can screw up our patterns, so do all + calculations like gap isn't there, and take it back into account afterwards. + + _zeros values are essentialy the values of rshiftby here: + bitwise view frame rshiftby priority + 00010000 0 4 1 + 00000001 1 0 15 + 00000010 2 1 7 + 00000011 3 0 13 + 00000100 4 2 3 + 00000101 5 0 11 + 00000110 6 1 5 + 00000111 7 0 9 + 00001000 8 3 1 + 00001001 9 0 7 + 00001010 10 1 3 + 00001011 11 0 5 + 00001100 12 2 1 + 00001101 13 0 3 + 00001110 14 1 1 + 00001111 15 0 1 + +*****************************************************************************************/ +using System.Collections.Generic; + +namespace BizHawk.Client.Common +{ + internal class StateManagerDecay + { + private TasStateManager _tsm; // access tsm methods to make life easier + private List _zeros; // amount of least significant zeros in bitwise view (also max pattern step) + private int _bits; // size of _zeros is 2 raised to the power of _bits + private int _mask; // for remainder calculation using bitwise instead of division + private int _base; // repeat count (like fceux's capacity). only used by aligned formula + private int _capacity; // total amount of savestates + private int _step; // initial memory state gap + private bool _align; // extra care about fine alignment. TODO: do we want it? + + public StateManagerDecay(TasStateManager tsm) + { + _tsm = tsm; + _align = false; + } + + // todo: go through all states once, remove as many as we need. refactor to not need goto + public void Trigger(int decayStates) + { + for (; decayStates > 0 && _tsm.StateCount > 1;) + { + int baseStateIndex = _tsm.GetStateIndexByFrame(Global.Emulator.Frame); + int baseStateFrame = _tsm.GetStateFrameByIndex(baseStateIndex) / _step; + int forwardPriority = -1000000; + int backwardPriority = -1000000; + int forwardFrame = -1; + int backwardFrame = -1; + + for (int currentStateIndex = 1; currentStateIndex < baseStateIndex; currentStateIndex++) + { + int currentFrame = _tsm.GetStateFrameByIndex(currentStateIndex); + + if (_tsm.StateIsMarker(currentFrame)) + { + continue; + } + else if (currentFrame % _step > 0) + { + // ignore the pattern if the state doesn't belong already, drop it blindly and skip everything + _tsm.RemoveState(currentFrame); + decayStates--; + + // this is the kind of highly complex loops that might justify goto + goto next_state; + } + else + { + currentFrame /= _step; + } + + int zeroCount = _zeros[currentFrame & _mask]; + int priority = ((baseStateFrame - currentFrame) >> zeroCount); + + if (_align) + { + priority -= ((_base * ((1 << zeroCount) * 2 - 1)) >> zeroCount); + } + + if (priority > forwardPriority) + { + forwardPriority = priority; + forwardFrame = currentFrame; + } + } + + for (int currentStateIndex = _tsm.StateCount - 1; currentStateIndex > baseStateIndex; currentStateIndex--) + { + int currentFrame = _tsm.GetStateFrameByIndex(currentStateIndex) / _step; + + if (_tsm.StateIsMarker(currentFrame)) + { + continue; + } + else if (currentFrame % _step > 0) + { + // ignore the pattern if the state doesn't belong already, drop it blindly and skip everything + _tsm.RemoveState(currentFrame); + decayStates--; + + // this is the kind of highly complex loops that might justify goto + goto next_state; + } + else + { + currentFrame /= _step; + } + + int zeroCount = _zeros[currentFrame & _mask]; + int priority = ((currentFrame - baseStateFrame) >> zeroCount); + + if (_align) + { + priority -= ((_base * ((1 << zeroCount) * 2 - 1)) >> zeroCount); + } + + if (priority > backwardPriority) + { + backwardPriority = priority; + backwardFrame = currentFrame; + } + } + + if (forwardFrame > -1 && backwardFrame > -1) + { + if (baseStateFrame - forwardFrame > backwardFrame - baseStateFrame) + { + _tsm.RemoveState(forwardFrame * _step); + } + else + { + _tsm.RemoveState(backwardFrame * _step); + } + + decayStates--; + } + else if (forwardFrame > -1) + { + _tsm.RemoveState(forwardFrame * _step); + decayStates--; + } + else if (backwardFrame > -1) + { + _tsm.RemoveState(backwardFrame * _step); + decayStates--; + } + else + { + // we're very sorry about failing to find states to remove, but we can't go beyond capacity, so remove at least something + // this shouldn't happen, but if we don't do it here, nothing good will happen either + _tsm.RemoveState(_tsm.GetStateFrameByIndex(1)); + } + + // this is the kind of highly complex loops that might justify goto + next_state:; + } + } + + public void UpdateSettings(int capacity, int step, int bits) + { + _capacity = capacity; + _step = step; + _bits = bits; + _mask = (1 << _bits) - 1; + _base = (_capacity + _bits / 2) / (_bits + 1); + _zeros = new List(); + _zeros.Add(_bits); + + for (int i = 1; i < (1 << _bits); i++) + { + _zeros.Add(0); + + for (int j = i; j > 0; j >>= 1) + { + if ((j & 1) > 0) + { + break; + } + + _zeros[i]++; + } + } + } + } +} diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index 4aab52a4e2..bd70407858 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -372,7 +372,7 @@ namespace BizHawk.Client.Common } Changes = true; - InvalidateAfter(frame - 1); + InvalidateAfter(frame); ChangeLog.SetGeneralRedo(); if (endBatch) diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 5c52afb7c8..9cbc90ebcb 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -62,15 +62,11 @@ namespace BizHawk.Client.Common if (Branches.Any()) { Branches.Save(bs); - if (_stateManager.Settings.BranchStatesInTasproj) - { - bs.PutLump(BinaryStateLump.BranchStateHistory, (BinaryWriter bw) => _stateManager.SaveBranchStates(bw)); - } } bs.PutLump(BinaryStateLump.Session, tw => tw.WriteLine(Session.ToString())); - if (_stateManager.Settings.SaveStateHistory) + if (_stateManager.Settings.SaveStateHistory && !backup) { bs.PutLump(BinaryStateLump.StateHistory, (BinaryWriter bw) => _stateManager.Save(bw)); } @@ -245,13 +241,6 @@ namespace BizHawk.Client.Common }); Branches.Load(bl, this); - if (_stateManager.Settings.BranchStatesInTasproj) - { - bl.GetLump(BinaryStateLump.BranchStateHistory, false, delegate(BinaryReader br, long length) - { - _stateManager.LoadBranchStates(br); - }); - } bl.GetLump(BinaryStateLump.Session, false, delegate(TextReader tr) { @@ -283,7 +272,7 @@ namespace BizHawk.Client.Common private void ClearTasprojExtras() { _lagLog.Clear(); - _stateManager.Clear(); + _stateManager.ClearStateHistory(); Markers.Clear(); ChangeLog.ClearLog(); } diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 4b5c27aa04..e500a1e833 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -16,29 +16,24 @@ namespace BizHawk.Client.Common private readonly TasStateManager _stateManager; private readonly TasLagLog _lagLog = new TasLagLog(); private readonly Dictionary _inputStateCache = new Dictionary(); - private BackgroundWorker _progressReportWorker; - public new const string Extension = "tasproj"; - public const string DefaultProjectName = "default"; - - public bool LastPositionStable { get; set; } = true; - public string NewBranchText { get; set; } = ""; - public readonly IStringLog VerificationLog = StringLogUtil.MakeStringLog(); // For movies that do not begin with power-on, this is the input required to get into the initial state public readonly TasBranchCollection Branches = new TasBranchCollection(); public readonly TasSession Session; - public TasLagLog TasLagLog => _lagLog; - - public IStringLog InputLog => Log; - + public new const string Extension = "tasproj"; + public const string DefaultProjectName = "default"; + public string NewBranchText { get; set; } = ""; + public int LastEditedFrame { get; set; } = -1; + public bool LastPositionStable { get; set; } = true; public TasMovieMarkerList Markers { get; private set; } - public bool BindMarkersToInput { get; set; } public bool UseInputCache { get; set; } public int CurrentBranch { get; set; } + public TasLagLog TasLagLog => _lagLog; + public IStringLog InputLog => Log; public int BranchCount => Branches.Count; public int LastValidFrame => _lagLog.LastValidFrame; public override string PreferredExtension => Extension; @@ -155,7 +150,8 @@ namespace BizHawk.Client.Common { var anyInvalidated = _lagLog.RemoveFrom(frame); _stateManager.Invalidate(frame + 1); - Changes = true; // TODO check if this actually removed anything before flagging changes + Changes = anyInvalidated; + LastEditedFrame = frame; if (anyInvalidated && Global.MovieSession.Movie.IsCountingRerecords) { @@ -239,7 +235,7 @@ namespace BizHawk.Client.Common if (!_stateManager.HasState(Global.Emulator.Frame)) { - _stateManager.Capture(); + _stateManager.Capture(Global.Emulator.Frame == LastEditedFrame - 1); } } @@ -497,13 +493,11 @@ namespace BizHawk.Client.Common public void AddBranch(TasBranch branch) { Branches.Add(branch); - TasStateManager.AddBranch(); Changes = true; } public void RemoveBranch(TasBranch branch) { - TasStateManager.RemoveBranch(Branches.IndexOf(branch)); Branches.Remove(branch); Changes = true; } @@ -527,9 +521,6 @@ namespace BizHawk.Client.Common { _stateManager.Invalidate(branch.InputLog.Count); } - - _stateManager.LoadBranch(Branches.IndexOf(branch)); - _stateManager.SetState(branch.Frame, branch.CoreData); if (BindMarkersToInput) // pretty critical not to erase them { @@ -549,7 +540,6 @@ namespace BizHawk.Client.Common } Branches[index] = newBranch; - TasStateManager.UpdateBranch(index); Changes = true; } diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index af3006e4e4..cd1ff55c1c 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Drawing; using BizHawk.Common; +using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Common.IEmulatorExtensions; @@ -26,7 +27,6 @@ namespace BizHawk.Client.Common InvalidateCallback?.Invoke(index); } - private readonly List _lowPriorityStates = new List(); internal NDBDatabase NdbDatabase { get; set; } private Guid _guid = Guid.NewGuid(); private SortedList _states = new SortedList(); @@ -42,45 +42,27 @@ namespace BizHawk.Client.Common private bool _isMountedForWrite; private readonly TasMovie _movie; + + private StateManagerDecay _decay; private ulong _expectedStateSize; - + private int _stateFrequency; private readonly int _minFrequency = 1; - private const int MaxFrequency = 16; - private int MaxStates => (int)(Settings.Cap / _expectedStateSize) + (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize); - private int FileStateGap => 1 << Settings.FileStateGap; + private readonly int _maxFrequency = 16; + private int _maxStates => (int)(Settings.Cap / _expectedStateSize) + + (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize); + private int _fileStateGap => 1 << Settings.FileStateGap; - private int StateFrequency - { - get - { - int freq = (int)_expectedStateSize / Settings.MemStateGapDivider / 1024; - - if (freq < _minFrequency) - { - return _minFrequency; - } - - if (freq > MaxFrequency) - { - return MaxFrequency; - } - - return freq; - } - } - public TasStateManager(TasMovie movie) { _movie = movie; - Settings = new TasStateManagerSettings(Global.Config.DefaultTasProjSettings); - _accessed = new List(); - if (_movie.StartsFromSavestate) { SetState(0, _movie.BinarySavestate); } + + _decay = new StateManagerDecay(this); } public void Dispose() @@ -89,6 +71,15 @@ namespace BizHawk.Client.Common NdbDatabase?.Dispose(); } + public void UpdateStateFrequency() + { + _stateFrequency = NumberExtensions.Clamp( + ((int)_expectedStateSize / Settings.MemStateGapDivider / 1024), + _minFrequency, _maxFrequency); + + _decay.UpdateSettings(_maxStates, _stateFrequency, 4); + } + /// /// Mounts this instance for write access. Prior to that it's read-only /// @@ -99,15 +90,14 @@ namespace BizHawk.Client.Common return; } - _isMountedForWrite = true; - int limit = 0; - + _isMountedForWrite = true; _expectedStateSize = (ulong)Core.SaveStateBinary().Length; + UpdateStateFrequency(); if (_expectedStateSize > 0) { - limit = MaxStates; + limit = _maxStates; } _states = new SortedList(limit); @@ -119,7 +109,7 @@ namespace BizHawk.Client.Common NdbDatabase = new NDBDatabase(StatePath, Settings.DiskCapacitymb * 1024 * 1024, (int)_expectedStateSize); } - + public TasStateManagerSettings Settings { get; set; } /// @@ -138,7 +128,6 @@ namespace BizHawk.Client.Common if (_states.ContainsKey(frame)) { - StateAccessed(frame); return new KeyValuePair(frame, _states[frame].State); } @@ -146,8 +135,6 @@ namespace BizHawk.Client.Common } } - private readonly List _accessed; - public byte[] InitialState { get @@ -168,8 +155,8 @@ namespace BizHawk.Client.Common public void Capture(bool force = false) { bool shouldCapture; - int frame = Global.Emulator.Frame; + if (_movie.StartsFromSavestate && frame == 0) // Never capture frame 0 on savestate anchored movies since we have it anyway { shouldCapture = false; @@ -182,13 +169,13 @@ namespace BizHawk.Client.Common { shouldCapture = true; } - else if (_movie.Markers.IsMarker(frame + 1)) + else if (StateIsMarker(frame)) { shouldCapture = true; // Markers shoudl always get priority } else { - shouldCapture = frame % StateFrequency == 0; + shouldCapture = frame % _stateFrequency == 0; } if (shouldCapture) @@ -197,159 +184,6 @@ namespace BizHawk.Client.Common } } - private void MaybeRemoveStates() - { - // Loop, because removing a state that has a duplicate won't save any space - while (Used + _expectedStateSize > Settings.Cap || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024) - { - Point shouldRemove = StateToRemove(); - RemoveState(shouldRemove.X, shouldRemove.Y); - } - - if (Used > Settings.Cap) - { - int lastMemState = -1; - do - { - lastMemState++; - } - while (_states[_accessed[lastMemState].Frame] == null); - MoveStateToDisk(_accessed[lastMemState].Frame); - } - } - - /// - /// X is the frame of the state, Y is the branch (-1 for current). - /// - private Point StateToRemove() - { - // X is frame, Y is branch - Point shouldRemove = new Point(-1, -1); - - if (_branchStates.Any() && Settings.EraseBranchStatesFirst) - { - var kvp = _branchStates.Count > 1 ? _branchStates.ElementAt(1) : _branchStates.ElementAt(0); - shouldRemove.X = kvp.Key; - shouldRemove.Y = kvp.Value.Keys[0]; - - return shouldRemove; - } - - int i = 0; - int markerSkips = MaxStates / 2; - - // lowPrioritySates (e.g. states with only lag frames between them) - do - { - if (_lowPriorityStates.Count > i) - { - shouldRemove = FindState(_lowPriorityStates[i]); - } - else - { - break; - } - - // Keep marker states - markerSkips--; - if (markerSkips < 0) - { - shouldRemove.X = -1; - } - - i++; - } - while ((StateIsMarker(shouldRemove.X, shouldRemove.Y) && markerSkips > -1) || shouldRemove.X == 0); - - // by last accessed - markerSkips = MaxStates / 2; - if (shouldRemove.X < 1) - { - i = 0; - do - { - if (_accessed.Count > i) - { - shouldRemove = FindState(_accessed[i]); - } - else - { - break; - } - - // Keep marker states - markerSkips--; - if (markerSkips < 0) - { - shouldRemove.X = -1; - } - - i++; - } - while ((StateIsMarker(shouldRemove.X, shouldRemove.Y) && markerSkips > -1) || shouldRemove.X == 0); - } - - if (shouldRemove.X < 1) // only found marker states above - { - if (_branchStates.Any() && !Settings.EraseBranchStatesFirst) - { - var kvp = _branchStates.Count > 1 ? _branchStates.ElementAt(1) : _branchStates.ElementAt(0); - shouldRemove.X = kvp.Key; - shouldRemove.Y = kvp.Value.Keys[0]; - } - else - { - StateManagerState s = _states.Values[1]; - shouldRemove.X = s.Frame; - shouldRemove.Y = -1; - } - } - - return shouldRemove; - } - - private bool StateIsMarker(int frame, int branch) - { - if (frame == -1) - { - return false; - } - - if (branch == -1) - { - return _movie.Markers.IsMarker(_states[frame].Frame + 1); - } - - if (_movie.GetBranch(_movie.BranchIndexByHash(branch)).Markers == null) - { - return _movie.Markers.IsMarker(_states[frame].Frame + 1); - } - - return _movie.GetBranch(branch).Markers.Any(m => m.Frame + 1 == frame); - } - - private bool AllLag(int from, int upTo) - { - if (upTo >= Global.Emulator.Frame) - { - upTo = Global.Emulator.Frame - 1; - if (!Global.Emulator.AsInputPollable().IsLagFrame) - { - return false; - } - } - - for (int i = from; i < upTo; i++) - { - if (_movie[i].Lagged == false) - { - return false; - } - } - - return true; - } - private void MoveStateToDisk(int index) { Used -= (ulong)_states[index].Length; @@ -366,16 +200,11 @@ namespace BizHawk.Client.Common { if (!skipRemoval) // skipRemoval: false only when capturing new states { - MaybeRemoveStates(); // Remove before adding so this state won't be removed. + LimitStateCount(); // Remove before adding so this state won't be removed. } if (_states.ContainsKey(frame)) { - if (StateHasDuplicate(frame, -1) != -2) - { - Used += (ulong)state.Length; - } - _states[frame].State = state; } else @@ -383,91 +212,6 @@ namespace BizHawk.Client.Common Used += (ulong)state.Length; _states.Add(frame, new StateManagerState(this, state, frame)); } - - StateAccessed(frame); - - int i = _states.IndexOfKey(frame); - if (i > 0 && AllLag(_states.Keys[i - 1], _states.Keys[i])) - { - _lowPriorityStates.Add(_states[frame]); - } - } - - private void RemoveState(int frame, int branch = -1) - { - if (branch == -1) - { - _accessed.Remove(_states[frame]); - } - else if (_accessed.Contains(_branchStates[frame][branch]) && !Settings.EraseBranchStatesFirst) - { - _accessed.Remove(_branchStates[frame][branch]); - } - - StateManagerState state; - bool hasDuplicate = StateHasDuplicate(frame, branch) != -2; - if (branch == -1) - { - state = _states[frame]; - if (_states[frame].IsOnDisk) - { - _states[frame].Dispose(); - } - else - { - Used -= (ulong)_states[frame].Length; - } - - _states.RemoveAt(_states.IndexOfKey(frame)); - } - else - { - state = _branchStates[frame][branch]; - - if (_branchStates[frame][branch].IsOnDisk) - { - _branchStates[frame][branch].Dispose(); - } - - _branchStates[frame].RemoveAt(_branchStates[frame].IndexOfKey(branch)); - - if (_branchStates[frame].Count == 0) - { - _branchStates.Remove(frame); - } - } - - if (!hasDuplicate) - { - _lowPriorityStates.Remove(state); - } - } - - private void StateAccessed(int frame) - { - if (frame == 0 && _movie.StartsFromSavestate) - { - return; - } - - StateManagerState state = _states[frame]; - bool removed = _accessed.Remove(state); - _accessed.Add(state); - - if (_states[frame].IsOnDisk) - { - if (!_states[_accessed[0].Frame].IsOnDisk) - { - MoveStateToDisk(_accessed[0].Frame); - } - - MoveStateToMemory(frame); - } - - if (!removed && _accessed.Count > MaxStates) - { - _accessed.RemoveAt(0); - } } public bool HasState(int frame) @@ -489,14 +233,12 @@ namespace BizHawk.Client.Common if (Any()) { - if (!_movie.StartsFromSavestate && frame == 0) // Never invalidate frame 0 on a non-savestate-anchored movie + if (frame == 0) // Never invalidate frame 0 { frame = 1; } - List> statesToRemove = - _states.Where(s => s.Key >= frame).ToList(); - + List> statesToRemove = _states.Where(s => s.Key >= frame).ToList(); anyInvalidated = statesToRemove.Any(); foreach (var state in statesToRemove) @@ -510,66 +252,48 @@ namespace BizHawk.Client.Common return anyInvalidated; } - /// - /// Clears all state information - /// - public void Clear() + public bool StateIsMarker(int frame) { - _states.Clear(); - _accessed.Clear(); - Used = 0; - ClearDiskStates(); - } - - public void ClearStateHistory() - { - if (_states.Any()) + if (frame == -1) { - StateManagerState power = _states.Values.First(s => s.Frame == 0); - StateAccessed(power.Frame); - - _states.Clear(); - _accessed.Clear(); - - SetState(0, power.State); - Used = (ulong)power.State.Length; - - ClearDiskStates(); + return false; } + + return _movie.Markers.IsMarker(frame + 1); } - private void ClearDiskStates() + public void RemoveState(int frame) { - NdbDatabase?.Clear(); + int index = _states.IndexOfKey(frame); + + if (frame < 1 || index < 1) + { + return; + } + + StateManagerState state = _states.ElementAt(index).Value; + + if (state.IsOnDisk) + { + state.Dispose(); + } + else + { + Used -= (ulong)state.Length; + } + + _states.RemoveAt(index); } /// - /// Deletes/moves states to follow the state storage size limits. - /// Used after changing the settings. + /// Deletes states to follow the state storage size limits. + /// Used after changing the settings too. /// public void LimitStateCount() { - while (Used + DiskUsed > Settings.CapTotal) + if (StateCount + 1 > _maxStates || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024) { - Point s = StateToRemove(); - RemoveState(s.X, s.Y); - } - - int index = -1; - while (DiskUsed > (ulong)Settings.DiskCapacitymb * 1024uL * 1024uL) - { - do - { - index++; - } - while (!_accessed[index].IsOnDisk); - - _accessed[index].MoveToRAM(); - } - - if (Used > Settings.Cap) - { - MaybeRemoveStates(); + _decay.Trigger(StateCount + 1 - _maxStates); } } @@ -582,21 +306,22 @@ namespace BizHawk.Client.Common // still leave marker states for (int i = 1; i < _states.Count; i++) { - if (_movie.Markers.IsMarker(_states.ElementAt(i).Key + 1) - || _states.ElementAt(i).Key % FileStateGap == 0) + int frame = GetStateFrameByIndex(i); + + if (StateIsMarker(frame) || frame % _fileStateGap < _stateFrequency) { continue; } ret.Add(i); - if (_states.ElementAt(i).Value.IsOnDisk) + if (_states.Values[i].IsOnDisk) { saveUsed -= _expectedStateSize; } else { - saveUsed -= (ulong)_states.ElementAt(i).Value.Length; + saveUsed -= (ulong)_states.Values[i].Length; } } @@ -607,13 +332,12 @@ namespace BizHawk.Client.Common { do { - index++; - if (index >= _states.Count) + if (++index >= _states.Count) { break; } } - while (_movie.Markers.IsMarker(_states.ElementAt(index).Key + 1)); + while (StateIsMarker(GetStateFrameByIndex(index))); if (index >= _states.Count) { @@ -622,13 +346,13 @@ namespace BizHawk.Client.Common ret.Add(index); - if (_states.ElementAt(index).Value.IsOnDisk) + if (_states.Values[index].IsOnDisk) { saveUsed -= _expectedStateSize; } else { - saveUsed -= (ulong)_states.ElementAt(index).Value.Length; + saveUsed -= (ulong)_states.Values[index].Length; } } @@ -636,30 +360,47 @@ namespace BizHawk.Client.Common index = 0; while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024) { - index++; - if (!ret.Contains(index)) + if (!ret.Contains(++index)) { ret.Add(index); } - if (_states.ElementAt(index).Value.IsOnDisk) + if (_states.Values[index].IsOnDisk) { saveUsed -= _expectedStateSize; } else { - saveUsed -= (ulong)_states.ElementAt(index).Value.Length; + saveUsed -= (ulong)_states.Values[index].Length; } } return ret; } + public void ClearStateHistory() + { + if (_states.Any()) + { + StateManagerState power = _states.Values.First(s => s.Frame == 0); + _states.Clear(); + SetState(0, power.State); + Used = (ulong)power.State.Length; + NdbDatabase?.Clear(); + } + } + + // Map: + // 4 bytes - total savestate count + // [Foreach state] + // 4 bytes - frame + // 4 bytes - length of savestate + // 0 - n savestate public void Save(BinaryWriter bw) { List noSave = ExcludeStates(); - bw.Write(_states.Count - noSave.Count); + for (int i = 0; i < _states.Count; i++) { if (noSave.Contains(i)) @@ -667,21 +408,21 @@ namespace BizHawk.Client.Common continue; } - StateAccessed(_states.ElementAt(i).Key); KeyValuePair kvp = _states.ElementAt(i); bw.Write(kvp.Key); bw.Write(kvp.Value.Length); bw.Write(kvp.Value.State); - ////_movie.ReportProgress(100d / States.Count * i); } } public void Load(BinaryReader br) { _states.Clear(); + try { int nstates = br.ReadInt32(); + for (int i = 0; i < nstates; i++) { int frame = br.ReadInt32(); @@ -691,8 +432,6 @@ namespace BizHawk.Client.Common // whether we should allow state removal check here is an interesting question // nothing was edited yet, so it might make sense to show the project untouched first SetState(frame, data); - ////States.Add(frame, data); - ////Used += len; } } catch (EndOfStreamException) @@ -700,50 +439,6 @@ namespace BizHawk.Client.Common } } - public void SaveBranchStates(BinaryWriter bw) - { - bw.Write(_branchStates.Count); - foreach (var s in _branchStates) - { - bw.Write(s.Key); - bw.Write(s.Value.Count); - foreach (var t in s.Value) - { - bw.Write(t.Key); - t.Value.Write(bw); - } - } - } - - public void LoadBranchStates(BinaryReader br) - { - try - { - int c = br.ReadInt32(); - _branchStates = new SortedList>(c); - while (c > 0) - { - int key = br.ReadInt32(); - int c2 = br.ReadInt32(); - var list = new SortedList(c2); - while (c2 > 0) - { - int key2 = br.ReadInt32(); - var state = StateManagerState.Read(br, this); - list.Add(key2, state); - c2--; - } - - _branchStates.Add(key, list); - c--; - } - } - catch (EndOfStreamException) - { - // Bad! - } - } - public KeyValuePair GetStateClosestToFrame(int frame) { var s = _states.LastOrDefault(state => state.Key < frame); @@ -751,12 +446,28 @@ namespace BizHawk.Client.Common return this[s.Key]; } - // Map: - // 4 bytes - total savestate count - // [Foreach state] - // 4 bytes - frame - // 4 bytes - length of savestate - // 0 - n savestate + /// + /// Returns index of the state right above the given frame + /// + /// + /// + public int GetStateIndexByFrame(int frame) + { + return _states.IndexOfKey(GetStateClosestToFrame(frame).Key); + } + + /// + /// Returns frame of the state at the given index + /// + /// + /// + public int GetStateFrameByIndex(int index) + { + // feos: this is called super often by decay + // this method is hundred times faster than _states.ElementAt(index).Key + return _states.Keys[index]; + } + private ulong _used; private ulong Used { @@ -817,7 +528,7 @@ namespace BizHawk.Client.Common } } - public int LastEmulatedFrame + public int LastStatedFrame { get { @@ -830,219 +541,14 @@ namespace BizHawk.Client.Common } } - #region Branches - - private SortedList> _branchStates = new SortedList>(); - - /// - /// Checks if the state at frame in the given branch (-1 for current) has any duplicates. - /// - /// Index of the branch (-1 for current) of the first match. If no match, returns -2. - private int StateHasDuplicate(int frame, int branchHash) + private int FindState(StateManagerState s) { - StateManagerState stateToMatch; - - // Get the state instance - if (branchHash == -1) - { - stateToMatch = _states[frame]; - } - else - { - if (!_branchStates[frame].ContainsKey(branchHash)) - { - return -2; - } - - stateToMatch = _branchStates[frame][branchHash]; - - // Check the current branch for duplicate. - if (_states.ContainsKey(frame) && _states[frame] == stateToMatch) - { - return -1; - } - } - - // Check if there are any branch states for the given frame. - if (!_branchStates.ContainsKey(frame) || _branchStates[frame] == null || branchHash == -1) - { - return -2; - } - - // Loop through branch states for the given frame. - SortedList stateList = _branchStates[frame]; - for (int i = 0; i < stateList.Count; i++) - { - // Don't check the branch containing the state to match. - if (i == _movie.BranchIndexByHash(branchHash)) - { - continue; - } - - if (stateList.Values[i] == stateToMatch) - { - return i; - } - } - - return -2; // No match. - } - - private Point FindState(StateManagerState s) - { - Point ret = new Point(0, -1); - ret.X = s.Frame; if (!_states.ContainsValue(s)) { - if (_branchStates.ContainsKey(s.Frame)) - { - int index = _branchStates[s.Frame].Values.IndexOf(s); - ret.Y = _branchStates[s.Frame].Keys.ElementAt(index); - } - - if (ret.Y == -1) - { - return new Point(-1, -2); - } + return -1; } - return ret; + return s.Frame; } - - public void AddBranch() - { - int branchHash = _movie.BranchHashByIndex(_movie.BranchCount - 1); - - foreach (KeyValuePair kvp in _states) - { - if (!_branchStates.ContainsKey(kvp.Key)) - { - _branchStates.Add(kvp.Key, new SortedList()); - } - - SortedList stateList = _branchStates[kvp.Key]; - - if (stateList == null) // when does this happen? - { - stateList = new SortedList(); - _branchStates[kvp.Key] = stateList; - } - - // We aren't creating any new states, just adding a reference to an already-existing one; so Used doesn't need to be updated. - stateList.Add(branchHash, kvp.Value); - } - } - - public void RemoveBranch(int index) - { - int branchHash = _movie.BranchHashByIndex(index); - - foreach (KeyValuePair> kvp in _branchStates.ToList()) - { - SortedList stateList = kvp.Value; - if (stateList == null) - { - continue; - } - - /* - if (stateList.ContainsKey(branchHash)) - { - if (stateHasDuplicate(kvp.Key, branchHash) == -2) - { - if (!stateList[branchHash].IsOnDisk) - Used -= (ulong)stateList[branchHash].Length; - } - } - */ - stateList.Remove(branchHash); - if (stateList.Count == 0) - { - _branchStates.Remove(kvp.Key); - } - } - } - - public void UpdateBranch(int index) - { - if (index == -1) // backup branch is outsider - { - return; - } - - int branchHash = _movie.BranchHashByIndex(index); - - // RemoveBranch - foreach (KeyValuePair> kvp in _branchStates.ToList()) - { - SortedList stateList = kvp.Value; - if (stateList == null) - { - continue; - } - - /* - if (stateList.ContainsKey(branchHash)) - { - if (stateHasDuplicate(kvp.Key, branchHash) == -2) - { - if (!stateList[branchHash].IsOnDisk) - Used -= (ulong)stateList[branchHash].Length; - } - } - */ - stateList.Remove(branchHash); - if (stateList.Count == 0) - { - _branchStates.Remove(kvp.Key); - } - } - - // AddBranch - foreach (KeyValuePair kvp in _states) - { - if (!_branchStates.ContainsKey(kvp.Key)) - { - _branchStates.Add(kvp.Key, new SortedList()); - } - - SortedList stateList = _branchStates[kvp.Key]; - - if (stateList == null) - { - stateList = new SortedList(); - _branchStates[kvp.Key] = stateList; - } - - stateList.Add(branchHash, kvp.Value); - } - } - - public void LoadBranch(int index) - { - if (index == -1) // backup branch is outsider - { - return; - } - - int branchHash = _movie.BranchHashByIndex(index); - - ////Invalidate(0); // Not a good way of doing it? - - foreach (KeyValuePair> kvp in _branchStates) - { - if (kvp.Key == 0 && _states.ContainsKey(0)) - { - continue; // TODO: It might be a better idea to just not put state 0 in BranchStates. - } - - if (kvp.Value.ContainsKey(branchHash)) - { - SetState(kvp.Key, kvp.Value[branchHash].State); - } - } - } - - #endregion } } diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs index 24aff44e13..7a7f07ea14 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs @@ -14,8 +14,6 @@ namespace BizHawk.Client.Common DiskCapacitymb = 1; // not working yet MemStateGapDivider = 64; FileStateGap = 4; - BranchStatesInTasproj = false; - EraseBranchStatesFirst = true; } public TasStateManagerSettings(TasStateManagerSettings settings) @@ -25,8 +23,6 @@ namespace BizHawk.Client.Common DiskCapacitymb = settings.DiskCapacitymb; MemStateGapDivider = settings.MemStateGapDivider; FileStateGap = settings.FileStateGap; - BranchStatesInTasproj = settings.BranchStatesInTasproj; - EraseBranchStatesFirst = settings.EraseBranchStatesFirst; } /// @@ -71,20 +67,6 @@ namespace BizHawk.Client.Common [Description("The actual state gap in frames is calculated as Nth power on 2")] public int FileStateGap { get; set; } - /// - /// Gets or sets a value indicating whether or not to save branch states into the movie file - /// - [DisplayName("Put branch states to .tasproj")] - [Description("Put branch states to .tasproj")] - public bool BranchStatesInTasproj { get; set; } - - /// - /// Gets or sets a value indicating whether or not to erase branch states before greenzone states when capacity is met - /// - [DisplayName("Erase branch states first")] - [Description("Erase branch states before greenzone states when capacity is met")] - public bool EraseBranchStatesFirst { get; set; } - /// /// The total state capacity in bytes. /// @@ -106,8 +88,6 @@ namespace BizHawk.Client.Common sb.AppendLine(DiskSaveCapacitymb.ToString()); sb.AppendLine(Capacitymb.ToString()); sb.AppendLine(DiskCapacitymb.ToString()); - sb.AppendLine(BranchStatesInTasproj.ToString()); - sb.AppendLine(EraseBranchStatesFirst.ToString()); sb.AppendLine(FileStateGap.ToString()); sb.AppendLine(MemStateGapDivider.ToString()); @@ -136,10 +116,8 @@ namespace BizHawk.Client.Common int i = 2; DiskCapacitymb = lines.Length > i ? int.Parse(lines[i++]) : 1; - BranchStatesInTasproj = lines.Length > i && bool.Parse(lines[i++]); - EraseBranchStatesFirst = lines.Length <= i || bool.Parse(lines[i++]); FileStateGap = lines.Length > i ? int.Parse(lines[i++]) : 4; - FileStateGap = lines.Length > i ? int.Parse(lines[i++]) : 64; + MemStateGapDivider = lines.Length > i ? int.Parse(lines[i++]) : 64; } catch (Exception) // TODO: this is bad { diff --git a/BizHawk.Client.Common/tools/Watch/DwordWatch.cs b/BizHawk.Client.Common/tools/Watch/DWordWatch.cs similarity index 100% rename from BizHawk.Client.Common/tools/Watch/DwordWatch.cs rename to BizHawk.Client.Common/tools/Watch/DWordWatch.cs diff --git a/BizHawk.Client.DBMan/BizHawk.Client.DBMan.csproj b/BizHawk.Client.DBMan/BizHawk.Client.DBMan.csproj index 558926c28d..7235dc706c 100644 --- a/BizHawk.Client.DBMan/BizHawk.Client.DBMan.csproj +++ b/BizHawk.Client.DBMan/BizHawk.Client.DBMan.csproj @@ -12,7 +12,8 @@ AnyCPU false prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\output\ @@ -22,7 +23,8 @@ AnyCPU false prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset @@ -36,6 +38,7 @@ v4.6.1 512 + true @@ -47,7 +50,7 @@ - + @@ -114,4 +117,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj b/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj index 012636db6e..19809199c9 100644 --- a/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj +++ b/BizHawk.Client.DiscoHawk/BizHawk.Client.DiscoHawk.csproj @@ -51,7 +51,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset ..\output\ @@ -61,7 +62,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset false false @@ -165,4 +167,4 @@ - \ No newline at end of file + diff --git a/BizHawk.Client.EmuHawk/AVOut/FFmpegWriterForm.cs b/BizHawk.Client.EmuHawk/AVOut/FFmpegWriterForm.cs index bf1af2c5c0..88cc16cb36 100644 --- a/BizHawk.Client.EmuHawk/AVOut/FFmpegWriterForm.cs +++ b/BizHawk.Client.EmuHawk/AVOut/FFmpegWriterForm.cs @@ -48,17 +48,30 @@ namespace BizHawk.Client.EmuHawk { return new[] { - new FormatPreset("Uncompressed AVI", "AVI file with uncompressed audio and video. Very large.", "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"), - new FormatPreset("Xvid", "AVI file with xvid video and mp3 audio.", "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"), - //new FormatPreset("Lossless Compressed AVI", "AVI file with zlib video and uncompressed audio.", "-c:a pcm_s16le -c:v zlib -f avi", false, "avi"), - new FormatPreset("FLV", "avc+aac in flash container.", "-c:a libvo_aacenc -c:v libx264 -f flv", false, "flv"), - new FormatPreset("Matroska Lossless", "MKV file with lossless video and audio", "-c:a pcm_s16le -c:v libx264rgb -crf 0 -f matroska", false, "mkv"), - new FormatPreset("Matroska", "MKV file with h264 + vorbis", "-c:a libvorbis -c:v libx264 -f matroska", false, "mkv"), - new FormatPreset("QuickTime", "MOV file with avc+aac", "-c:a libvo_aacenc -c:v libx264 -f mov", false, "mov"), - new FormatPreset("Ogg", "Theora + Vorbis in OGG", "-c:a libvorbis -c:v libtheora -f ogg", false, "ogg"), - new FormatPreset("WebM", "Vp8 + Vorbis in WebM", "-c:a libvorbis -c:v libvpx -f webm", false, "webm"), - new FormatPreset("mp4", "ISO mp4 with AVC+AAC", "-c:a libvo_aacenc -c:v libx264 -f mp4", false, "mp4"), - new FormatPreset("[Custom]", "Write your own ffmpeg command. For advanced users only", "-c:a foo -c:v bar -f baz", true, "foobar"), + new FormatPreset("AVI Uncompressed", "Uncompressed video and audio in an AVI container. Very large.", + "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"), + new FormatPreset("AVI Lossless", "Lossless FFV1 video and uncompressed audio in an AVI container. Compatible with AVISource, if ffmpeg based decoder is installed.", + "-c:a pcm_s16le -c:v ffv1 -pix_fmt bgr0 -level 1 -g 1 -coder 1 -context 1 -f avi", false, "avi"), + //new FormatPreset("AVI Lossless", "Lossless zlib video and uncompressed audio in an AVI container.", + // "-c:a pcm_s16le -c:v zlib -f avi", false, "avi"), + new FormatPreset("Matroska Lossless", "Lossless AVC video and uncompressed audio in a Matroska container.", + "-c:a pcm_s16le -c:v libx264rgb -crf 0 -f matroska", false, "mkv"), + new FormatPreset("Matroska", "AVC video and Vorbis audio in a Matroska container.", + "-c:a libvorbis -c:v libx264 -f matroska", false, "mkv"), + new FormatPreset("MP4", "AVC video and AAC audio in an MP4 container.", + "-c:a libvo_aacenc -c:v libx264 -f mp4", false, "mp4"), + new FormatPreset("WebM", "VP8 video and Vorbis audio in a WebM container.", + "-c:a libvorbis -c:v libvpx -f webm", false, "webm"), + new FormatPreset("Ogg", "Theora video and Vorbis audio in an Ogg contrainer.", + "-c:a libvorbis -c:v libtheora -f ogg", false, "ogg"), + new FormatPreset("Xvid", "Xvid video and MP3 audio in an AVI container.", + "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"), + new FormatPreset("QuickTime", "AVC video and AAC audio in a QuickTime container.", + "-c:a libvo_aacenc -c:v libx264 -f mov", false, "mov"), + new FormatPreset("FLV", "AVC video and AAC audio in a Flash Video container.", + "-c:a libvo_aacenc -c:v libx264 -f flv", false, "flv"), + new FormatPreset("[Custom]", "Write your own ffmpeg command. For advanced users only.", + "-c:a foo -c:v bar -f baz", true, "foobar"), }; } diff --git a/BizHawk.Client.EmuHawk/ArgParser.cs b/BizHawk.Client.EmuHawk/ArgParser.cs index 9e4c5ab1b9..0912c68ba9 100644 --- a/BizHawk.Client.EmuHawk/ArgParser.cs +++ b/BizHawk.Client.EmuHawk/ArgParser.cs @@ -27,11 +27,14 @@ namespace BizHawk.Client.EmuHawk public bool luaConsole = false; public int socket_port = 9999; public string socket_ip = null; + public string mmf_filename = null; + public string URL_get = null; + public string URL_post = null; + + public void ParseArguments(string[] args) - public void parseArguments(string[] args) - { - for (int i = 0; i") @@ -50,11 +53,11 @@ namespace BizHawk.Client.EmuHawk if (arg.StartsWith("--load-state=")) { - cmdLoadState = arg.Substring(arg.IndexOf('=') + 1); + cmdLoadState = args[i].Substring(args[i].IndexOf('=') + 1); } else if (arg.StartsWith("--movie=")) { - cmdMovie = arg.Substring(arg.IndexOf('=') + 1); + cmdMovie = args[i].Substring(args[i].IndexOf('=') + 1); } else if (arg.StartsWith("--dump-type=")) { @@ -62,8 +65,8 @@ namespace BizHawk.Client.EmuHawk } else if (arg.StartsWith("--dump-frames=")) { - var list = arg.Substring(arg.IndexOf('=') + 1); - var items = list.Split(','); + string list = arg.Substring(arg.IndexOf('=') + 1); + string[] items = list.Split(','); _currAviWriterFrameList = new HashSet(); foreach (string item in items) { @@ -75,7 +78,7 @@ namespace BizHawk.Client.EmuHawk } else if (arg.StartsWith("--dump-name=")) { - cmdDumpName = arg.Substring(arg.IndexOf('=') + 1); + cmdDumpName = args[i].Substring(args[i].IndexOf('=') + 1); } else if (arg.StartsWith("--dump-length=")) { @@ -95,7 +98,7 @@ namespace BizHawk.Client.EmuHawk } else if (arg.StartsWith("--lua=")) { - luaScript = arg.Substring(arg.IndexOf('=') + 1); + luaScript = args[i].Substring(args[i].IndexOf('=') + 1); luaConsole = true; } else if (arg.StartsWith("--luaconsole")) @@ -110,11 +113,55 @@ namespace BizHawk.Client.EmuHawk { socket_ip = arg.Substring(arg.IndexOf('=') + 1); } + else if (arg.StartsWith("--mmf=")) + { + mmf_filename = args[i].Substring(args[i].IndexOf('=') + 1); + } + else if (arg.StartsWith("--url_get=")) + { + URL_get = args[i].Substring(args[i].IndexOf('=') + 1); + } + else if (arg.StartsWith("--url_post=")) + { + URL_post = args[i].Substring(args[i].IndexOf('=') + 1); + } else { cmdRom = args[i]; } } + ////initialize HTTP communication + if (URL_get != null || URL_post != null) + { + if (URL_get != null) + { + GlobalWin.httpCommunication.initialized = true; + GlobalWin.httpCommunication.SetGetUrl(URL_get); + } + if (URL_post != null) + { + GlobalWin.httpCommunication.initialized = true; + GlobalWin.httpCommunication.SetPostUrl(URL_post); + } + } + //inititalize socket server + if (socket_ip != null && socket_port > -1) + { + GlobalWin.socketServer.initialized = true; + GlobalWin.socketServer.SetIp(socket_ip, socket_port); + } + else if (socket_ip != null) + { + GlobalWin.socketServer.initialized = true; + GlobalWin.socketServer.SetIp(socket_ip); + } + + //initialize mapped memory files + if (mmf_filename != null) + { + GlobalWin.memoryMappedFiles.initialized = true; + GlobalWin.memoryMappedFiles.SetFilename(mmf_filename); + } } } } diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 2a860dd082..5b51df2137 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -37,6 +37,7 @@ false true None + true true @@ -50,7 +51,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset ..\output\ @@ -61,7 +63,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset @@ -71,7 +74,8 @@ ..\References\ICSharpCode.SharpZipLib.dll - + + ..\References\Newtonsoft.Json.dll @@ -92,6 +96,7 @@ 3.5 + @@ -193,6 +198,7 @@ BizBoxInfoControl.cs + Component @@ -497,6 +503,30 @@ TI83PaletteConfig.cs + + Form + + + ZXSpectrumAudioSettings.cs + + + Form + + + ZXSpectrumNonSyncSettings.cs + + + Form + + + ZXSpectrumCoreEmulationSettings.cs + + + Form + + + ZXSpectrumJoystickSettings.cs + Form @@ -598,7 +628,8 @@ - + + @@ -625,7 +656,8 @@ EditCommentsForm.cs - + + Form @@ -871,6 +903,7 @@ NewHexEditor.cs + @@ -1197,6 +1230,7 @@ + UserControl @@ -1394,6 +1428,18 @@ TI83PaletteConfig.cs + + ZXSpectrumAudioSettings.cs + + + ZXSpectrumNonSyncSettings.cs + + + ZXSpectrumCoreEmulationSettings.cs + + + ZXSpectrumJoystickSettings.cs + CoreFeatureAnalysis.cs @@ -1785,6 +1831,7 @@ + @@ -2109,6 +2156,7 @@ + @@ -2173,4 +2221,4 @@ - + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/Communication.cs b/BizHawk.Client.EmuHawk/Communication.cs new file mode 100644 index 0000000000..ba61cd1671 --- /dev/null +++ b/BizHawk.Client.EmuHawk/Communication.cs @@ -0,0 +1,445 @@ +using System; +using System.Text; +using System.Net; +using System.Net.Sockets; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Net.Http; +using System.IO.MemoryMappedFiles; +using BizHawk.Bizware.BizwareGL; +using System.Drawing; +using BizHawk.Emulation.Common; +using BizHawk.Client.Common; +using BizHawk.Emulation.Common.IEmulatorExtensions; +using System.Windows.Forms; +using System.IO; + +namespace BizHawk.Client.EmuHawk +{ + + public class Communication + { + + public class HttpCommunication + { + private static HttpClient client = new HttpClient(); + private string PostUrl = "http://localhost:9876/post/"; + private string GetUrl = "http://localhost:9876/index"; + public bool initialized = false; + private ScreenShot screenShot = new ScreenShot(); + public int timeout = 0; + public int default_timeout = 500; + + public void SetTimeout(int _timeout) + { + //timeout = _timeout.TotalMilliseconds; + if (timeout == 0 && _timeout == 0) + { + timeout = default_timeout; + } + if (_timeout != 0) + { + client.Timeout = new TimeSpan(0, 0, 0, _timeout / 1000, _timeout % 1000); + timeout = _timeout; + } + + } + public void SetPostUrl(string url) + { + PostUrl = url; + } + public void SetGetUrl(string url) + { + GetUrl = url; + } + + public string GetGetUrl() + { + return GetUrl; + } + public string GetPostUrl() + { + return PostUrl; + } + + public async Task Get(string url) + { + client.DefaultRequestHeaders.ConnectionClose = false; + HttpResponseMessage response = await client.GetAsync(url).ConfigureAwait(false); + if (response.IsSuccessStatusCode) { + return await response.Content.ReadAsStringAsync(); + } + else + { + return null; + } + } + + public async Task Post(string url, FormUrlEncodedContent content) + { + client.DefaultRequestHeaders.ConnectionClose = true; + HttpResponseMessage response = null; + try + { + response = await client.PostAsync(url, content).ConfigureAwait(false); + + } + catch (Exception e) + { + MessageBox.Show(e.ToString()); + return e.ToString(); + + } + if (!response.IsSuccessStatusCode) + { + return null; + } + return await response.Content.ReadAsStringAsync(); + } + + public string TestGet() + { + Task getResponse = Get(GetUrl); + return getResponse.Result; + } + + public string SendScreenshot(string url, string parameter) + { + int trials = 5; + var values = new Dictionary + { + {parameter, screenShot.GetScreenShotAsString()}, + }; + FormUrlEncodedContent content = new FormUrlEncodedContent(values); + + Task postResponse = null; + while (postResponse == null && trials > 0) + { + postResponse = Post(PostUrl, content); + trials -= 1; + } + return postResponse.Result; + } + + public string SendScreenshot() + { + return SendScreenshot(PostUrl, "screenshot"); + } + + public string SendScreenshot(string url) + { + return SendScreenshot(url, "screenshot"); + } + + public string ExecGet(string url) + { + return Get(url).Result; + } + + public string ExecGet() + { + return Get(GetUrl).Result; + } + + public string ExecPost(string url, string payload) + { + var values = new Dictionary + { + {"payload", payload}, + }; + FormUrlEncodedContent content = new FormUrlEncodedContent(values); + return Post(url, content).Result; + } + + public string ExecPost(string payload) + { + var values = new Dictionary + { + {"payload", payload}, + }; + FormUrlEncodedContent content = new FormUrlEncodedContent(values); + return Post(PostUrl, content).Result; + } + } + public class SocketServer + { + + public string ip = "192.168.178.21"; + public int port = 9999; + public Decoder decoder = Encoding.UTF8.GetDecoder(); + public Socket soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + public IPAddress ipAdd; + public IPEndPoint remoteEP; + public IVideoProvider currentVideoProvider = null; + public bool connected = false; + public bool initialized = false; + public int retries = 10; + public bool success = false; //indicates whether the last command was executed succesfully + + public void Initialize(IVideoProvider _currentVideoProvider) + { + currentVideoProvider = _currentVideoProvider; + SetIp(ip, port); + initialized = true; + + } + public void Connect() + { + if (!initialized) + { + Initialize(currentVideoProvider); + } + remoteEP = new IPEndPoint(ipAdd, port); + soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + soc.Connect(remoteEP); + connected = true; + soc.ReceiveTimeout = 5; + + } + public void SetIp(string ip_) + { + ip = ip_; + ipAdd = System.Net.IPAddress.Parse(ip); + remoteEP = new IPEndPoint(ipAdd, port); + } + public void SetIp(string ip_, int port_) + { + ip = ip_; + port = port_; + ipAdd = System.Net.IPAddress.Parse(ip); + remoteEP = new IPEndPoint(ipAdd, port); + + } + public void SetTimeout(int timeout) + { + soc.ReceiveTimeout = timeout; + } + public void SocketConnected() + { + bool part1 = soc.Poll(1000, SelectMode.SelectRead); + bool part2 = (soc.Available == 0); + if (part1 && part2) + connected = false; + else + connected = true; + } + public int SendString(string SendString) + { + + int sentBytes = SendBytes(Encoding.ASCII.GetBytes(SendString)); + success = sentBytes > 0; + return sentBytes; + } + public int SendBytes(byte[] SendBytes) + { + int sentBytes = 0; + try + { + sentBytes = soc.Send(SendBytes); + } + catch + { + sentBytes = -1; + } + return sentBytes; + } + + public string SendScreenshot() + { + return SendScreenshot(0); + } + public string SendScreenshot(int waitingTime) + { + if (!connected) + { + Connect(); + } + ScreenShot screenShot = new ScreenShot(); + using (BitmapBuffer bb = screenShot.MakeScreenShotImage()) + { + using (var img = bb.ToSysdrawingBitmap()) + { + byte[] bmpBytes = screenShot.ImageToByte(img); + int sentBytes = 0; + int tries = 0; + while (sentBytes <= 0 && tries < retries) + { + try + { + tries++; + sentBytes = SendBytes(bmpBytes); + } + catch (SocketException) + { + Connect(); + sentBytes = 0; + } + if (sentBytes == -1) + { + Connect(); + } + } + success = (tries < retries); + } + } + String resp = ""; + if (!success) + { + resp = "Screenshot could not be sent"; + } else + { + resp = "Screenshot was sent"; + } + if (waitingTime == 0) + { + return resp; + } + resp = ""; + resp = ReceiveMessage(); + if (resp == "") + { + resp = "Failed to get a response"; + } + return resp; + } + public string ReceiveMessage() + { + if (!connected) + { + Connect(); + } + string resp = ""; + byte[] receivedBytes = new byte[256]; + int receivedLength = 1; + + while (receivedLength > 0) + { + try + { + receivedLength = soc.Receive(receivedBytes, receivedBytes.Length, 0); + resp += Encoding.ASCII.GetString(receivedBytes); + } catch + { + receivedLength = 0; + } + } + + return resp; + } + public bool Successful() + { + return success; + } + } + + public class MemoryMappedFiles + { + public string filename_main = "BizhawkTemp_main"; + public Dictionary mmf_files = new Dictionary(); + public int index = 0; + public bool initialized = false; + public int main_size = 10 ^ 5; + ScreenShot screenShot = new ScreenShot(); + + public void SetFilename(string filename) + { + filename_main = filename; + } + public string GetFilename() + { + return filename_main; + } + + public int ScreenShotToFile() + { + ScreenShot screenShot = new ScreenShot(); + var bb = screenShot.MakeScreenShotImage(); + var img = bb.ToSysdrawingBitmap(); + byte[] bmpBytes = screenShot.ImageToByte(img); + return WriteToFile(@filename_main, bmpBytes); + } + + public int WriteToFile(string filename, byte[] outputBytes) + { + MemoryMappedFile mmf_file; + int bytesWritten = -1; + if (mmf_files.TryGetValue(filename, out mmf_file) == false) + { + mmf_file = MemoryMappedFile.CreateOrOpen(filename, outputBytes.Length); + mmf_files[filename] = mmf_file; + } + try + { + using (MemoryMappedViewAccessor accessor = mmf_file.CreateViewAccessor(0, outputBytes.Length, MemoryMappedFileAccess.Write)) + { + accessor.WriteArray(0, outputBytes, 0, outputBytes.Length); + bytesWritten = outputBytes.Length; + } + } + catch (UnauthorizedAccessException) + { + try + { + mmf_file.Dispose(); + } + catch (Exception) + { + + } + + mmf_file = MemoryMappedFile.CreateOrOpen(filename, outputBytes.Length); + mmf_files[filename] = mmf_file; + using (MemoryMappedViewAccessor accessor = mmf_file.CreateViewAccessor(0, outputBytes.Length, MemoryMappedFileAccess.Write)) + { + accessor.WriteArray(0, outputBytes, 0, outputBytes.Length); + bytesWritten = outputBytes.Length; + } + } + return bytesWritten; + } + + public string ReadFromFile(string filename, int expectedSize) + { + MemoryMappedFile mmf_file = mmf_file = MemoryMappedFile.OpenExisting(@filename); + using (MemoryMappedViewAccessor viewAccessor = mmf_file.CreateViewAccessor()) + { + byte[] bytes = new byte[expectedSize]; + viewAccessor.ReadArray(0, bytes, 0, bytes.Length); + string text = Encoding.UTF8.GetString(bytes); + return text; + } + } + + } + + class ScreenShot + //makes all functionalities for providing screenshots available + { + private IVideoProvider currentVideoProvider = null; + private ImageConverter converter = new ImageConverter(); + public BitmapBuffer MakeScreenShotImage() + { + if (currentVideoProvider == null) + { + currentVideoProvider = Global.Emulator.AsVideoProviderOrDefault(); + } + return GlobalWin.DisplayManager.RenderVideoProvider(currentVideoProvider); + } + public byte[] ImageToByte(Image img) + { + return (byte[])converter.ConvertTo(img, typeof(byte[])); + } + public string ImageToString(Image img) + { + return Convert.ToBase64String(ImageToByte(img)); + } + public string GetScreenShotAsString() + { + BitmapBuffer bb = MakeScreenShotImage(); + byte[] imgBytes = ImageToByte(bb.ToSysdrawingBitmap()); + return Convert.ToBase64String(imgBytes); + } + } + } +} + diff --git a/BizHawk.Client.EmuHawk/CustomControls/ExceptionBox.Designer.cs b/BizHawk.Client.EmuHawk/CustomControls/ExceptionBox.designer.cs similarity index 100% rename from BizHawk.Client.EmuHawk/CustomControls/ExceptionBox.Designer.cs rename to BizHawk.Client.EmuHawk/CustomControls/ExceptionBox.designer.cs diff --git a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs index f0ac656f36..ff955f51d6 100644 --- a/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs +++ b/BizHawk.Client.EmuHawk/CustomControls/InputRoll.cs @@ -1106,7 +1106,7 @@ namespace BizHawk.Client.EmuHawk { // do marker drag here } - else if (ModifierKeys == Keys.Shift) + else if (ModifierKeys == Keys.Shift && (CurrentCell.Column.Name == "FrameColumn" || CurrentCell.Column.Type == RollColumn.InputType.Text)) { if (_selectedItems.Any()) { @@ -1175,11 +1175,11 @@ namespace BizHawk.Client.EmuHawk SelectCell(CurrentCell); } } - else if (ModifierKeys == Keys.Control) + else if (ModifierKeys == Keys.Control && (CurrentCell.Column.Name == "FrameColumn" || CurrentCell.Column.Type == RollColumn.InputType.Text)) { SelectCell(CurrentCell, toggle: true); } - else + else if (ModifierKeys != Keys.Shift) { var hadIndex = _selectedItems.Any(); _selectedItems.Clear(); @@ -1319,6 +1319,7 @@ namespace BizHawk.Client.EmuHawk { HorizontalOrientation ^= true; } + // Scroll else if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.PageUp) // Page Up { if (FirstVisibleRow > 0) @@ -1352,9 +1353,26 @@ namespace BizHawk.Client.EmuHawk LastVisibleRow = RowCount; Refresh(); } + else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Up + { + if (FirstVisibleRow > 0) + { + FirstVisibleRow--; + Refresh(); + } + } + else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Down + { + if (FirstVisibleRow < RowCount - 1) + { + FirstVisibleRow++; + Refresh(); + } + } + // Selection courser else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Ctrl + Up { - if (SelectedRows.Any() && LetKeysModifySelection) + if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.First() > 0) { foreach (var row in SelectedRows.ToList()) { @@ -1374,36 +1392,70 @@ namespace BizHawk.Client.EmuHawk } } } - else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Shift + Up + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Left) // Ctrl + Left { if (SelectedRows.Any() && LetKeysModifySelection) { - SelectRow(SelectedRows.First() - 1, true); + SelectRow(SelectedRows.Last(), false); } } - else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Shift + Down + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Right) // Ctrl + Right { - if (SelectedRows.Any() && LetKeysModifySelection) + if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.Last() < _rowCount - 1) { SelectRow(SelectedRows.Last() + 1, true); } } - else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Up + else if (e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Left) // Ctrl + Shift + Left { - if (FirstVisibleRow > 0) + if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.First() > 0) { - FirstVisibleRow--; - Refresh(); + SelectRow(SelectedRows.First() - 1, true); } } - else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Down + else if (e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Right) // Ctrl + Shift + Right { - if (FirstVisibleRow < RowCount - 1) + if (SelectedRows.Any() && LetKeysModifySelection) { - FirstVisibleRow++; - Refresh(); + SelectRow(SelectedRows.First(), false); } } + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.PageUp) // Ctrl + Page Up + { + //jump to above marker with selection courser + if (LetKeysModifySelection) + { + + } + } + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.PageDown) // Ctrl + Page Down + { + //jump to below marker with selection courser + if (LetKeysModifySelection) + { + + } + + } + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Home) // Ctrl + Home + { + //move selection courser to frame 0 + if (LetKeysModifySelection) + { + DeselectAll(); + SelectRow(0, true); + } + } + else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.End) // Ctrl + End + { + //move selection courser to end of movie + if (LetKeysModifySelection) + { + DeselectAll(); + SelectRow(RowCount-1, true); + } + } + } base.OnKeyDown(e); diff --git a/BizHawk.Client.EmuHawk/DisplayManager/OSDManager.cs b/BizHawk.Client.EmuHawk/DisplayManager/OSDManager.cs index 5d6ea34df4..01107e0435 100644 --- a/BizHawk.Client.EmuHawk/DisplayManager/OSDManager.cs +++ b/BizHawk.Client.EmuHawk/DisplayManager/OSDManager.cs @@ -274,9 +274,13 @@ namespace BizHawk.Client.EmuHawk SourceStickyOr = Global.AutofireStickyXORAdapter }; - var lg = Global.MovieSession.LogGeneratorInstance(); - lg.SetSource(stickyOr); + return MakeStringFor(stickyOr); + } + private string MakeStringFor(IController controller) + { + var lg = Global.MovieSession.LogGeneratorInstance(); + lg.SetSource(controller); return lg.GenerateInputDisplay(); } @@ -345,28 +349,41 @@ namespace BizHawk.Client.EmuHawk else // TODO: message config -- allow setting of "previous", "mixed", and "auto" { + var previousColor = Color.Orange; + Color immediateColor = Color.FromArgb(Global.Config.MessagesColor); + var autoColor = Color.Pink; + var changedColor = Color.PeachPuff; + + //we need some kind of string for calculating position when right-anchoring, of something like that var bgStr = InputStrOrAll(); var x = GetX(g, Global.Config.DispInpx, Global.Config.DispInpanchor, bgStr); var y = GetY(g, Global.Config.DispInpy, Global.Config.DispInpanchor, bgStr); - g.DrawString(bgStr, MessageFont, Color.Black, x + 1, y + 1); - + //now, we're going to render these repeatedly, with higher-priority things overriding + //first display previous frame's input. + //note: that's only available in case we're working on a movie var previousStr = InputPrevious(); - var pColor = Color.Orange; - g.DrawString(previousStr, MessageFont, pColor, x, y); - + g.DrawString(previousStr, MessageFont, previousColor, x, y); + //next, draw the immediate input. + //that is, whatever's being held down interactively right this moment even if the game is paused + //this includes things held down due to autohold or autofire + //I know, this is all really confusing var immediate = InputStrImmediate(); - Color immediateColor = Color.FromArgb(Global.Config.MessagesColor); g.DrawString(immediate, MessageFont, immediateColor, x, y); - var immediateOverlay = MakeIntersectImmediatePrevious(); - var oColor = Color.PeachPuff; - g.DrawString(immediateOverlay, MessageFont, oColor, x, y); + //next draw anything that's pressed because it's sticky. + //this applies to autofire and autohold both. somehow. I dont understand it. + //basically we're tinting whatever's pressed because it's sticky specially + //in order to achieve this we want to avoid drawing anything pink that isnt actually held down right now + //so we make an AND adapter and combine it using immediate & sticky + var autoString = MakeStringFor(Global.StickyXORAdapter.Source.Xor(Global.AutofireStickyXORAdapter).And(Global.AutofireStickyXORAdapter)); + g.DrawString(autoString, MessageFont, autoColor, x, y); - var autoString = InputStrSticky(); - g.DrawString(autoString, MessageFont, Color.Pink, x, y); + //recolor everything that's changed from the previous input + var immediateOverlay = MakeIntersectImmediatePrevious(); + g.DrawString(immediateOverlay, MessageFont, changedColor, x, y); } } diff --git a/BizHawk.Client.EmuHawk/FileLoader.cs b/BizHawk.Client.EmuHawk/FileLoader.cs index 0009646fb4..db3c00d76d 100644 --- a/BizHawk.Client.EmuHawk/FileLoader.cs +++ b/BizHawk.Client.EmuHawk/FileLoader.cs @@ -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" }; } diff --git a/BizHawk.Client.EmuHawk/GlobalWin.cs b/BizHawk.Client.EmuHawk/GlobalWin.cs index e0137c6e4a..5df3f651bf 100644 --- a/BizHawk.Client.EmuHawk/GlobalWin.cs +++ b/BizHawk.Client.EmuHawk/GlobalWin.cs @@ -24,5 +24,8 @@ namespace BizHawk.Client.EmuHawk public static GLManager GLManager; public static int ExitCode; + public static Communication.HttpCommunication httpCommunication = new Communication.HttpCommunication(); + public static Communication.SocketServer socketServer = new Communication.SocketServer(); + public static Communication.MemoryMappedFiles memoryMappedFiles = new Communication.MemoryMappedFiles(); } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index d2c636448a..57d6c30767 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -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(); @@ -274,11 +272,18 @@ this.SMSregionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSregionExportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSregionJapanToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSregionKoreaToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSregionAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSdisplayToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 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(); @@ -303,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(); @@ -332,7 +337,9 @@ 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(); this.N64PluginSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.N64ControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -374,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(); @@ -445,13 +457,8 @@ 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.ZXSpectrumAudioSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); this.SuspendLayout(); @@ -488,7 +495,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"; @@ -910,26 +918,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 @@ -1124,7 +1132,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); // @@ -1132,7 +1140,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); // @@ -1140,26 +1148,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); // @@ -1814,8 +1822,8 @@ this.CoreSNESSubMenu, this.GbaCoreSubMenu, this.SGBCoreSubmenu, - this.GBCoreSubmenu, - this.GBInSGBMenuItem, + this.GBCoreSubmenu, + this.GBInSGBMenuItem, this.toolStripMenuItem16, this.allowGameDBCoreOverridesToolStripMenuItem, this.toolStripSeparator8, @@ -1898,12 +1906,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[] { @@ -1913,48 +1915,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); @@ -2051,7 +2053,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); // @@ -2440,7 +2442,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); // @@ -2448,59 +2450,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); // @@ -2510,7 +2512,7 @@ this.SMSregionToolStripMenuItem, this.SMSdisplayToolStripMenuItem, this.SMSControllerToolStripMenuItem, - this.SMStoolStripMenuItem2, + this.SMStoolStripMenuItem2, this.SMSenableBIOSToolStripMenuItem, this.SMSEnableFMChipMenuItem, this.SMSOverclockMenuItem, @@ -2534,9 +2536,10 @@ this.SMSregionToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.SMSregionExportToolStripMenuItem, this.SMSregionJapanToolStripMenuItem, + 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 @@ -2553,6 +2556,13 @@ this.SMSregionJapanToolStripMenuItem.Text = "Japan"; this.SMSregionJapanToolStripMenuItem.Click += new System.EventHandler(this.SMS_RegionJapan_Click); // + // SMSregionKoreaToolStripMenuItem + // + this.SMSregionKoreaToolStripMenuItem.Name = "SMSregionKoreaToolStripMenuItem"; + this.SMSregionKoreaToolStripMenuItem.Size = new System.Drawing.Size(107, 22); + this.SMSregionKoreaToolStripMenuItem.Text = "Korea"; + this.SMSregionKoreaToolStripMenuItem.Click += new System.EventHandler(this.SMS_RegionKorea_Click); + // // SMSregionAutoToolStripMenuItem // this.SMSregionAutoToolStripMenuItem.Name = "SMSregionAutoToolStripMenuItem"; @@ -2567,7 +2577,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 @@ -2577,51 +2587,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); @@ -2633,97 +2601,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); // @@ -2795,7 +2810,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"; @@ -2804,26 +2819,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"; @@ -2861,17 +2876,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, @@ -3025,7 +3040,8 @@ this.ColecoSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ColecoControllerSettingsMenuItem, this.toolStripSeparator35, - this.ColecoSkipBiosMenuItem}); + this.ColecoSkipBiosMenuItem, + this.ColecoUseSGMMenuItem}); this.ColecoSubMenu.Name = "ColecoSubMenu"; this.ColecoSubMenu.Size = new System.Drawing.Size(56, 19); this.ColecoSubMenu.Text = "&Coleco"; @@ -3051,6 +3067,13 @@ this.ColecoSkipBiosMenuItem.Text = "&Skip BIOS intro (When Applicable)"; this.ColecoSkipBiosMenuItem.Click += new System.EventHandler(this.ColecoSkipBiosMenuItem_Click); // + // ColecoUseSGMMenuItem + // + this.ColecoUseSGMMenuItem.Name = "ColecoUseSGMMenuItem"; + this.ColecoUseSGMMenuItem.Size = new System.Drawing.Size(253, 22); + this.ColecoUseSGMMenuItem.Text = "&Use the Super Game Module"; + this.ColecoUseSGMMenuItem.Click += new System.EventHandler(this.ColecoUseSGMMenuItem_Click); + // // N64SubMenu // this.N64SubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -3372,6 +3395,45 @@ 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.ZXSpectrumAudioSettingsMenuItem, + 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; @@ -3992,6 +4054,13 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // + // ZXSpectrumAudioSettingsMenuItem + // + this.ZXSpectrumAudioSettingsMenuItem.Name = "ZXSpectrumAudioSettingsMenuItem"; + this.ZXSpectrumAudioSettingsMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumAudioSettingsMenuItem.Text = "Audio Settings"; + this.ZXSpectrumAudioSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumAudioSettingsMenuItem_Click); + // // MainForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -4248,6 +4317,7 @@ private System.Windows.Forms.ToolStripSeparator toolStripSeparator28; private System.Windows.Forms.ToolStripMenuItem ColecoSubMenu; private System.Windows.Forms.ToolStripMenuItem ColecoSkipBiosMenuItem; + private System.Windows.Forms.ToolStripMenuItem ColecoUseSGMMenuItem; private System.Windows.Forms.ToolStripMenuItem ColecoControllerSettingsMenuItem; private System.Windows.Forms.ToolStripStatusLabel LedLightStatusLabel; private System.Windows.Forms.ToolStripMenuItem GBASubMenu; @@ -4298,6 +4368,7 @@ private System.Windows.Forms.ToolStripMenuItem SMSregionToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSregionExportToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSregionJapanToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem SMSregionKoreaToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSregionAutoToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSdisplayToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSdisplayNtscToolStripMenuItem; @@ -4454,5 +4525,10 @@ 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; + private System.Windows.Forms.ToolStripMenuItem ZXSpectrumAudioSettingsMenuItem; + } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 3eb36838f1..810dfb3567 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1757,6 +1757,7 @@ namespace BizHawk.Client.EmuHawk var ss = ((SMS)Emulator).GetSyncSettings(); SMSregionExportToolStripMenuItem.Checked = ss.ConsoleRegion == "Export"; SMSregionJapanToolStripMenuItem.Checked = ss.ConsoleRegion == "Japan"; + SMSregionKoreaToolStripMenuItem.Checked = ss.ConsoleRegion == "Korea"; SMSregionAutoToolStripMenuItem.Checked = ss.ConsoleRegion == "Auto"; SMSdisplayNtscToolStripMenuItem.Checked = ss.DisplayType == "NTSC"; SMSdisplayPalToolStripMenuItem.Checked = ss.DisplayType == "PAL"; @@ -1814,6 +1815,13 @@ namespace BizHawk.Client.EmuHawk PutCoreSyncSettings(ss); } + private void SMS_RegionKorea_Click(object sender, EventArgs e) + { + var ss = ((SMS)Emulator).GetSyncSettings(); + ss.ConsoleRegion = "Korea"; + PutCoreSyncSettings(ss); + } + private void SMS_RegionAuto_Click(object sender, EventArgs e) { var ss = ((SMS)Emulator).GetSyncSettings(); @@ -2198,6 +2206,7 @@ namespace BizHawk.Client.EmuHawk { var ss = ((ColecoVision)Emulator).GetSyncSettings(); ColecoSkipBiosMenuItem.Checked = ss.SkipBiosIntro; + ColecoUseSGMMenuItem.Checked = ss.UseSGM; ColecoControllerSettingsMenuItem.Enabled = !Global.MovieSession.Movie.IsActive; } @@ -2208,6 +2217,13 @@ namespace BizHawk.Client.EmuHawk PutCoreSyncSettings(ss); } + private void ColecoUseSGMMenuItem_Click(object sender, EventArgs e) + { + var ss = ((ColecoVision)Emulator).GetSyncSettings(); + ss.UseSGM ^= true; + PutCoreSyncSettings(ss); + } + private void ColecoControllerSettingsMenuItem_Click(object sender, EventArgs e) { new ColecoControllerSettings().ShowDialog(); @@ -2435,11 +2451,47 @@ 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(); + } + + private void ZXSpectrumAudioSettingsMenuItem_Click(object sender, EventArgs e) + { + new ZXSpectrumAudioSettings().ShowDialog(); + } + + #endregion + + #region Help + + private void HelpSubMenu_DropDownOpened(object sender, EventArgs e) { FeaturesMenuItem.Visible = VersionInfo.DeveloperBuild; } diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index fa6977cd9f..3f7a9ea1df 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -155,7 +155,7 @@ namespace BizHawk.Client.EmuHawk } }; - argParse.parseArguments(args); + argParse.ParseArguments(args); Database.LoadDatabase(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "gamedb.txt")); @@ -609,7 +609,7 @@ namespace BizHawk.Client.EmuHawk private bool IsTurboSeeking => PauseOnFrame.HasValue && Global.Config.TurboSeek; - private bool IsTurboing => Global.ClientControls["Turbo"] || IsTurboSeeking; + public bool IsTurboing => Global.ClientControls["Turbo"] || IsTurboSeeking; #endregion @@ -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", "*.*"); } @@ -2969,11 +2974,6 @@ namespace BizHawk.Client.EmuHawk Global.CheatList.Pulse(); - if (!PauseAvi) - { - AvFrameAdvance(); - } - if (IsLagFrame && Global.Config.AutofireLagFrames) { Global.AutoFireController.IncrementStarts(); @@ -2990,13 +2990,18 @@ namespace BizHawk.Client.EmuHawk if (IsTurboing) { - GlobalWin.Tools.FastUpdateAfter(); + GlobalWin.Tools.FastUpdateAfter(SuppressLua); } else { UpdateToolsAfter(SuppressLua); } + if (!PauseAvi) + { + AvFrameAdvance(); + } + if (GlobalWin.Tools.IsLoaded() && GlobalWin.Tools.TAStudio.LastPositionFrame == Emulator.Frame) { @@ -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; diff --git a/BizHawk.Client.EmuHawk/Program.cs b/BizHawk.Client.EmuHawk/Program.cs index 94b397b4a2..a6a5e5a538 100644 --- a/BizHawk.Client.EmuHawk/Program.cs +++ b/BizHawk.Client.EmuHawk/Program.cs @@ -30,19 +30,22 @@ namespace BizHawk.Client.EmuHawk //but oddly it lets us proceed and we'll then catch it here var d3dx9 = Win32.LoadLibrary("d3dx9_43.dll"); var vc2015 = Win32.LoadLibrary("vcruntime140.dll"); + var vc2012 = Win32.LoadLibrary("msvcr120.dll"); //TODO - check version? var vc2010 = Win32.LoadLibrary("msvcr100.dll"); //TODO - check version? var vc2010p = Win32.LoadLibrary("msvcp100.dll"); bool fail = false, warn = false; warn |= d3dx9 == IntPtr.Zero; fail |= vc2015 == IntPtr.Zero; fail |= vc2010 == IntPtr.Zero; + fail |= vc2012 == IntPtr.Zero; fail |= vc2010p == IntPtr.Zero; if (fail || warn) { var sw = new System.IO.StringWriter(); - sw.WriteLine("[ OK ] .Net 4.0 (You couldn't even get here without it)"); + sw.WriteLine("[ OK ] .Net 4.6.1 (You couldn't even get here without it)"); sw.WriteLine("[{0}] Direct3d 9", d3dx9 == IntPtr.Zero ? "FAIL" : " OK "); sw.WriteLine("[{0}] Visual C++ 2010 SP1 Runtime", (vc2010 == IntPtr.Zero || vc2010p == IntPtr.Zero) ? "FAIL" : " OK "); + sw.WriteLine("[{0}] Visual C++ 2012 Runtime", (vc2012 == IntPtr.Zero) ? "FAIL" : " OK "); sw.WriteLine("[{0}] Visual C++ 2015 Runtime", (vc2015 == IntPtr.Zero) ? "FAIL" : " OK "); var str = sw.ToString(); var box = new BizHawk.Client.EmuHawk.CustomControls.PrereqsAlert(!fail); @@ -55,6 +58,7 @@ namespace BizHawk.Client.EmuHawk Win32.FreeLibrary(d3dx9); Win32.FreeLibrary(vc2015); + Win32.FreeLibrary(vc2012); Win32.FreeLibrary(vc2010); Win32.FreeLibrary(vc2010p); diff --git a/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs b/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs index 72c965441f..7504818e28 100644 --- a/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs +++ b/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs @@ -489,7 +489,7 @@ namespace BizHawk.Client.EmuHawk.Properties { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -3438,5 +3438,15 @@ namespace BizHawk.Client.EmuHawk.Properties { return ((System.Drawing.Bitmap)(obj)); } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ZXSpectrumKeyboards { + get { + object obj = ResourceManager.GetObject("ZXSpectrumKeyboards", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/BizHawk.Client.EmuHawk/Properties/Resources.resx b/BizHawk.Client.EmuHawk/Properties/Resources.resx index 8d9fb1b714..38184f2b35 100644 --- a/BizHawk.Client.EmuHawk/Properties/Resources.resx +++ b/BizHawk.Client.EmuHawk/Properties/Resources.resx @@ -1300,7 +1300,7 @@ ..\images\alt_about_image.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\images\controllerimages\saturncontroller.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\images\ControllerImages\SaturnController.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\images\tastudio\ts_v_piano_02_green_blue.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -1557,4 +1557,7 @@ ..\images\ControllerImages\NGPController.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - + + ..\config\controllerimages\zxspectrumkeyboards.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp b/BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp new file mode 100644 index 0000000000..55516c7aaa Binary files /dev/null and b/BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp differ diff --git a/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs b/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs index 42af277c0e..10e9cef191 100644 --- a/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs +++ b/BizHawk.Client.EmuHawk/Sound/Output/DirectSoundSoundOutput.cs @@ -117,6 +117,8 @@ namespace BizHawk.Client.EmuHawk public int CalculateSamplesNeeded() { + if (_deviceBuffer.Status == BufferStatus.BufferLost) return 0; + long currentWriteTime = Stopwatch.GetTimestamp(); int playCursor = _deviceBuffer.CurrentPlayPosition; int writeCursor = _deviceBuffer.CurrentWritePosition; diff --git a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index ab488b1724..f17a7e1afe 100644 --- a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -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 diff --git a/BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png b/BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png new file mode 100644 index 0000000000..1af85ebea9 Binary files /dev/null and b/BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png differ diff --git a/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs b/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs index 5b2e5eca92..7b016a8cab 100644 --- a/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs +++ b/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs @@ -52,6 +52,7 @@ namespace BizHawk.Client.EmuHawk { "GBC", "Game Boy Color" }, { "PCFX", "PC-FX" }, { "32X", "32X" }, + { "ZXSpectrum", "ZX Spectrum" } }; public string TargetSystem = null; diff --git a/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs b/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs index d8c19d9644..f5891e284a 100644 --- a/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs +++ b/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs @@ -1,5 +1,7 @@ using System; using System.Windows.Forms; +using System.Reflection; +using System.ComponentModel; using BizHawk.Client.Common; using BizHawk.Emulation.Common; @@ -31,6 +33,7 @@ namespace BizHawk.Client.EmuHawk if (_s != null) { propertyGrid1.SelectedObject = _s; + ChangeDescriptionHeight(propertyGrid1); } else { @@ -40,6 +43,7 @@ namespace BizHawk.Client.EmuHawk if (_ss != null) { propertyGrid2.SelectedObject = _ss; + ChangeDescriptionHeight(propertyGrid2); } else { @@ -57,6 +61,36 @@ namespace BizHawk.Client.EmuHawk { } + private static void ChangeDescriptionHeight(PropertyGrid grid) + { + if (grid == null) + throw new ArgumentNullException("grid"); + + int maxlen = 0; + string desc = ""; + + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(grid.SelectedObject)) + { + if (property.Description.Length > maxlen) + { + maxlen = property.Description.Length; + desc = property.Description; + } + } + + foreach (Control control in grid.Controls) + { + if (control.GetType().Name == "DocComment") + { + FieldInfo field = control.GetType().GetField("userSized", BindingFlags.Instance | BindingFlags.NonPublic); + field.SetValue(control, true); + int height = (int)System.Drawing.Graphics.FromHwnd(control.Handle).MeasureString(desc, control.Font, grid.Width).Height; + control.Height = Math.Max(20, height) + 16; // magic for now + return; + } + } + } + private void OkBtn_Click(object sender, EventArgs e) { if (_s != null && settingschanged) diff --git a/BizHawk.Client.EmuHawk/config/N64/N64VideoPluginconfig.Designer.cs b/BizHawk.Client.EmuHawk/config/N64/N64VideoPluginconfig.Designer.cs index 327588c724..ebc228d4f9 100644 --- a/BizHawk.Client.EmuHawk/config/N64/N64VideoPluginconfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/N64/N64VideoPluginconfig.Designer.cs @@ -520,7 +520,7 @@ this.label47.Name = "label47"; this.label47.Size = new System.Drawing.Size(275, 13); this.label47.TabIndex = 14; - this.label47.Text = "(Glide64mk2 is newer and is recommended over Glide64)"; + this.label47.Text = "(GLideN64 is the newest pluging and has the highest compatibility)"; // // label2 // diff --git a/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.Designer.cs index 4047b8680b..7f8e659c38 100644 --- a/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.Designer.cs @@ -28,123 +28,238 @@ /// private void InitializeComponent() { - this.OK = new System.Windows.Forms.Button(); - this.Cancel = new System.Windows.Forms.Button(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.DispBG2 = new System.Windows.Forms.CheckBox(); - this.DispOBJ2 = new System.Windows.Forms.CheckBox(); - this.DispBG1 = new System.Windows.Forms.CheckBox(); - this.DispOBJ1 = new System.Windows.Forms.CheckBox(); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); - // - // OK - // - this.OK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OK.DialogResult = System.Windows.Forms.DialogResult.OK; - this.OK.Location = new System.Drawing.Point(205, 96); - this.OK.Name = "OK"; - this.OK.Size = new System.Drawing.Size(75, 23); - this.OK.TabIndex = 4; - this.OK.Text = "&OK"; - this.OK.UseVisualStyleBackColor = true; - this.OK.Click += new System.EventHandler(this.Ok_Click); - // - // Cancel - // - this.Cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.Cancel.Location = new System.Drawing.Point(286, 96); - this.Cancel.Name = "Cancel"; - this.Cancel.Size = new System.Drawing.Size(75, 23); - this.Cancel.TabIndex = 5; - this.Cancel.Text = "&Cancel"; - this.Cancel.UseVisualStyleBackColor = true; - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.DispBG2); - this.groupBox1.Controls.Add(this.DispOBJ2); - this.groupBox1.Controls.Add(this.DispBG1); - this.groupBox1.Controls.Add(this.DispOBJ1); - this.groupBox1.Location = new System.Drawing.Point(9, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(352, 73); - this.groupBox1.TabIndex = 2; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Background and Sprites"; - // - // DispBG2 - // - this.DispBG2.AutoSize = true; - this.DispBG2.Checked = true; - this.DispBG2.CheckState = System.Windows.Forms.CheckState.Checked; - this.DispBG2.Location = new System.Drawing.Point(108, 43); - this.DispBG2.Name = "DispBG2"; - this.DispBG2.Size = new System.Drawing.Size(84, 17); - this.DispBG2.TabIndex = 3; - this.DispBG2.Text = "Display BG2"; - this.DispBG2.UseVisualStyleBackColor = true; - // - // DispOBJ2 - // - this.DispOBJ2.AutoSize = true; - this.DispOBJ2.Checked = true; - this.DispOBJ2.CheckState = System.Windows.Forms.CheckState.Checked; - this.DispOBJ2.Location = new System.Drawing.Point(108, 21); - this.DispOBJ2.Name = "DispOBJ2"; - this.DispOBJ2.Size = new System.Drawing.Size(89, 17); - this.DispOBJ2.TabIndex = 2; - this.DispOBJ2.Text = "Display OBJ2"; - this.DispOBJ2.UseVisualStyleBackColor = true; - // - // DispBG1 - // - this.DispBG1.AutoSize = true; - this.DispBG1.Checked = true; - this.DispBG1.CheckState = System.Windows.Forms.CheckState.Checked; - this.DispBG1.Location = new System.Drawing.Point(9, 43); - this.DispBG1.Name = "DispBG1"; - this.DispBG1.Size = new System.Drawing.Size(84, 17); - this.DispBG1.TabIndex = 1; - this.DispBG1.Text = "Display BG1"; - this.DispBG1.UseVisualStyleBackColor = true; - // - // DispOBJ1 - // - this.DispOBJ1.AutoSize = true; - this.DispOBJ1.Checked = true; - this.DispOBJ1.CheckState = System.Windows.Forms.CheckState.Checked; - this.DispOBJ1.Location = new System.Drawing.Point(9, 21); - this.DispOBJ1.Name = "DispOBJ1"; - this.DispOBJ1.Size = new System.Drawing.Size(89, 17); - this.DispOBJ1.TabIndex = 0; - this.DispOBJ1.Text = "Display OBJ1"; - this.DispOBJ1.UseVisualStyleBackColor = true; - // - // PCEGraphicsConfig - // - this.AcceptButton = this.OK; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.Cancel; - this.ClientSize = new System.Drawing.Size(373, 128); - this.Controls.Add(this.groupBox1); - this.Controls.Add(this.Cancel); - this.Controls.Add(this.OK); - this.MaximizeBox = false; - this.MaximumSize = new System.Drawing.Size(389, 433); - this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(389, 166); - this.Name = "PCEGraphicsConfig"; - this.ShowIcon = false; - this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "PC Engine Graphics Settings"; - this.Load += new System.EventHandler(this.PCEGraphicsConfig_Load); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.ResumeLayout(false); + this.OK = new System.Windows.Forms.Button(); + this.Cancel = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.DispBG2 = new System.Windows.Forms.CheckBox(); + this.DispOBJ2 = new System.Windows.Forms.CheckBox(); + this.DispBG1 = new System.Windows.Forms.CheckBox(); + this.DispOBJ1 = new System.Windows.Forms.CheckBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.label5 = new System.Windows.Forms.Label(); + this.btnAreaFull = new System.Windows.Forms.Button(); + this.btnAreaStandard = new System.Windows.Forms.Button(); + this.label4 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.NTSC_LastLineNumeric = new System.Windows.Forms.NumericUpDown(); + this.NTSC_FirstLineNumeric = new System.Windows.Forms.NumericUpDown(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.NTSC_LastLineNumeric)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.NTSC_FirstLineNumeric)).BeginInit(); + this.SuspendLayout(); + // + // OK + // + this.OK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.OK.Location = new System.Drawing.Point(205, 279); + this.OK.Name = "OK"; + this.OK.Size = new System.Drawing.Size(75, 23); + this.OK.TabIndex = 4; + this.OK.Text = "&OK"; + this.OK.UseVisualStyleBackColor = true; + this.OK.Click += new System.EventHandler(this.Ok_Click); + // + // Cancel + // + this.Cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.Cancel.Location = new System.Drawing.Point(286, 279); + this.Cancel.Name = "Cancel"; + this.Cancel.Size = new System.Drawing.Size(75, 23); + this.Cancel.TabIndex = 5; + this.Cancel.Text = "&Cancel"; + this.Cancel.UseVisualStyleBackColor = true; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.DispBG2); + this.groupBox1.Controls.Add(this.DispOBJ2); + this.groupBox1.Controls.Add(this.DispBG1); + this.groupBox1.Controls.Add(this.DispOBJ1); + this.groupBox1.Location = new System.Drawing.Point(9, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(352, 73); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Background and Sprites"; + // + // DispBG2 + // + this.DispBG2.AutoSize = true; + this.DispBG2.Checked = true; + this.DispBG2.CheckState = System.Windows.Forms.CheckState.Checked; + this.DispBG2.Location = new System.Drawing.Point(108, 43); + this.DispBG2.Name = "DispBG2"; + this.DispBG2.Size = new System.Drawing.Size(84, 17); + this.DispBG2.TabIndex = 3; + this.DispBG2.Text = "Display BG2"; + this.DispBG2.UseVisualStyleBackColor = true; + // + // DispOBJ2 + // + this.DispOBJ2.AutoSize = true; + this.DispOBJ2.Checked = true; + this.DispOBJ2.CheckState = System.Windows.Forms.CheckState.Checked; + this.DispOBJ2.Location = new System.Drawing.Point(108, 21); + this.DispOBJ2.Name = "DispOBJ2"; + this.DispOBJ2.Size = new System.Drawing.Size(89, 17); + this.DispOBJ2.TabIndex = 2; + this.DispOBJ2.Text = "Display OBJ2"; + this.DispOBJ2.UseVisualStyleBackColor = true; + // + // DispBG1 + // + this.DispBG1.AutoSize = true; + this.DispBG1.Checked = true; + this.DispBG1.CheckState = System.Windows.Forms.CheckState.Checked; + this.DispBG1.Location = new System.Drawing.Point(9, 43); + this.DispBG1.Name = "DispBG1"; + this.DispBG1.Size = new System.Drawing.Size(84, 17); + this.DispBG1.TabIndex = 1; + this.DispBG1.Text = "Display BG1"; + this.DispBG1.UseVisualStyleBackColor = true; + // + // DispOBJ1 + // + this.DispOBJ1.AutoSize = true; + this.DispOBJ1.Checked = true; + this.DispOBJ1.CheckState = System.Windows.Forms.CheckState.Checked; + this.DispOBJ1.Location = new System.Drawing.Point(9, 21); + this.DispOBJ1.Name = "DispOBJ1"; + this.DispOBJ1.Size = new System.Drawing.Size(89, 17); + this.DispOBJ1.TabIndex = 0; + this.DispOBJ1.Text = "Display OBJ1"; + this.DispOBJ1.UseVisualStyleBackColor = true; + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.label5); + this.groupBox2.Controls.Add(this.btnAreaFull); + this.groupBox2.Controls.Add(this.btnAreaStandard); + this.groupBox2.Controls.Add(this.label4); + this.groupBox2.Controls.Add(this.label3); + this.groupBox2.Controls.Add(this.NTSC_LastLineNumeric); + this.groupBox2.Controls.Add(this.NTSC_FirstLineNumeric); + this.groupBox2.Location = new System.Drawing.Point(9, 100); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(352, 150); + this.groupBox2.TabIndex = 6; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Drawing Area"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(62, 22); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(36, 13); + this.label5.TabIndex = 41; + this.label5.Text = "NTSC"; + // + // btnAreaFull + // + this.btnAreaFull.Location = new System.Drawing.Point(6, 115); + this.btnAreaFull.Name = "btnAreaFull"; + this.btnAreaFull.Size = new System.Drawing.Size(100, 23); + this.btnAreaFull.TabIndex = 40; + this.btnAreaFull.Text = "Full [0,262]"; + this.btnAreaFull.UseVisualStyleBackColor = true; + this.btnAreaFull.Click += new System.EventHandler(this.BtnAreaFull_Click); + // + // btnAreaStandard + // + this.btnAreaStandard.Location = new System.Drawing.Point(6, 92); + this.btnAreaStandard.Name = "btnAreaStandard"; + this.btnAreaStandard.Size = new System.Drawing.Size(100, 23); + this.btnAreaStandard.TabIndex = 35; + this.btnAreaStandard.Text = "Standard [18,252]"; + this.btnAreaStandard.UseVisualStyleBackColor = true; + this.btnAreaStandard.Click += new System.EventHandler(this.BtnAreaStandard_Click); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(4, 69); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(49, 13); + this.label4.TabIndex = 24; + this.label4.Text = "Last line:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(5, 43); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(48, 13); + this.label3.TabIndex = 23; + this.label3.Text = "First line:"; + // + // NTSC_LastLineNumeric + // + this.NTSC_LastLineNumeric.Location = new System.Drawing.Point(59, 67); + this.NTSC_LastLineNumeric.Maximum = new decimal(new int[] { + 262, + 0, + 0, + 0}); + this.NTSC_LastLineNumeric.Minimum = new decimal(new int[] { + 128, + 0, + 0, + 0}); + this.NTSC_LastLineNumeric.Name = "NTSC_LastLineNumeric"; + this.NTSC_LastLineNumeric.Size = new System.Drawing.Size(47, 20); + this.NTSC_LastLineNumeric.TabIndex = 28; + this.NTSC_LastLineNumeric.Value = new decimal(new int[] { + 128, + 0, + 0, + 0}); + // + // NTSC_FirstLineNumeric + // + this.NTSC_FirstLineNumeric.Location = new System.Drawing.Point(59, 41); + this.NTSC_FirstLineNumeric.Maximum = new decimal(new int[] { + 127, + 0, + 0, + 0}); + this.NTSC_FirstLineNumeric.Name = "NTSC_FirstLineNumeric"; + this.NTSC_FirstLineNumeric.Size = new System.Drawing.Size(47, 20); + this.NTSC_FirstLineNumeric.TabIndex = 21; + // + // PCEGraphicsConfig + // + this.AcceptButton = this.OK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.Cancel; + this.ClientSize = new System.Drawing.Size(373, 311); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.Cancel); + this.Controls.Add(this.OK); + this.MaximizeBox = false; + this.MaximumSize = new System.Drawing.Size(389, 433); + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(389, 166); + this.Name = "PCEGraphicsConfig"; + this.ShowIcon = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "PC Engine Graphics Settings"; + this.Load += new System.EventHandler(this.PCEGraphicsConfig_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.NTSC_LastLineNumeric)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.NTSC_FirstLineNumeric)).EndInit(); + this.ResumeLayout(false); } @@ -157,5 +272,13 @@ private System.Windows.Forms.CheckBox DispOBJ2; private System.Windows.Forms.CheckBox DispBG1; private System.Windows.Forms.CheckBox DispOBJ1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Button btnAreaFull; + private System.Windows.Forms.Button btnAreaStandard; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.NumericUpDown NTSC_LastLineNumeric; + private System.Windows.Forms.NumericUpDown NTSC_FirstLineNumeric; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.cs b/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.cs index 13eaae966c..fd08ec4724 100644 --- a/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.cs +++ b/BizHawk.Client.EmuHawk/config/PCE/PCEGraphicsConfig.cs @@ -21,6 +21,8 @@ namespace BizHawk.Client.EmuHawk DispBG1.Checked = s.ShowBG1; DispOBJ2.Checked = s.ShowOBJ2; DispBG2.Checked = s.ShowBG2; + NTSC_FirstLineNumeric.Value = s.Top_Line; + NTSC_LastLineNumeric.Value = s.Bottom_Line; } private void Ok_Click(object sender, EventArgs e) @@ -31,8 +33,22 @@ namespace BizHawk.Client.EmuHawk s.ShowBG1 = DispBG1.Checked; s.ShowOBJ2 = DispOBJ2.Checked; s.ShowBG2 = DispBG2.Checked; + s.Top_Line = (int)NTSC_FirstLineNumeric.Value; + s.Bottom_Line = (int)NTSC_LastLineNumeric.Value; pce.PutSettings(s); Close(); } + + private void BtnAreaStandard_Click(object sender, EventArgs e) + { + NTSC_FirstLineNumeric.Value = 18; + NTSC_LastLineNumeric.Value = 252; + } + + private void BtnAreaFull_Click(object sender, EventArgs e) + { + NTSC_FirstLineNumeric.Value = 0; + NTSC_LastLineNumeric.Value = 262; + } } } diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.Designer.cs new file mode 100644 index 0000000000..21b2122306 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.Designer.cs @@ -0,0 +1,210 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumAudioSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumAudioSettings)); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.panTypecomboBox1 = new System.Windows.Forms.ComboBox(); + this.lblBorderInfo = new System.Windows.Forms.Label(); + this.tapeVolumetrackBar = new System.Windows.Forms.TrackBar(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.earVolumetrackBar = new System.Windows.Forms.TrackBar(); + this.label5 = new System.Windows.Forms.Label(); + this.ayVolumetrackBar = new System.Windows.Forms.TrackBar(); + ((System.ComponentModel.ISupportInitialize)(this.tapeVolumetrackBar)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.earVolumetrackBar)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.ayVolumetrackBar)).BeginInit(); + 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, 298); + 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, 298); + 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(140, 13); + this.label1.TabIndex = 17; + this.label1.Text = "ZX Spectrum Audio Settings"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 236); + 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, 252); + 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, 245); + 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; + // + // tapeVolumetrackBar + // + this.tapeVolumetrackBar.Location = new System.Drawing.Point(12, 60); + this.tapeVolumetrackBar.Maximum = 100; + this.tapeVolumetrackBar.Name = "tapeVolumetrackBar"; + this.tapeVolumetrackBar.Size = new System.Drawing.Size(359, 45); + this.tapeVolumetrackBar.TabIndex = 25; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(12, 44); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(73, 13); + this.label3.TabIndex = 26; + this.label3.Text = "Tape Volume:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 108); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(80, 13); + this.label4.TabIndex = 28; + this.label4.Text = "Buzzer Volume:"; + // + // earVolumetrackBar + // + this.earVolumetrackBar.Location = new System.Drawing.Point(12, 124); + this.earVolumetrackBar.Maximum = 100; + this.earVolumetrackBar.Name = "earVolumetrackBar"; + this.earVolumetrackBar.Size = new System.Drawing.Size(359, 45); + this.earVolumetrackBar.TabIndex = 27; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(12, 172); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(98, 13); + this.label5.TabIndex = 30; + this.label5.Text = "AY-3-8912 Volume:"; + // + // ayVolumetrackBar + // + this.ayVolumetrackBar.Location = new System.Drawing.Point(12, 188); + this.ayVolumetrackBar.Maximum = 100; + this.ayVolumetrackBar.Name = "ayVolumetrackBar"; + this.ayVolumetrackBar.Size = new System.Drawing.Size(359, 45); + this.ayVolumetrackBar.TabIndex = 29; + // + // ZXSpectrumAudioSettings + // + 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, 333); + this.Controls.Add(this.label5); + this.Controls.Add(this.ayVolumetrackBar); + this.Controls.Add(this.label4); + this.Controls.Add(this.earVolumetrackBar); + this.Controls.Add(this.label3); + this.Controls.Add(this.tapeVolumetrackBar); + this.Controls.Add(this.lblBorderInfo); + this.Controls.Add(this.label2); + this.Controls.Add(this.panTypecomboBox1); + 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 = "ZXSpectrumAudioSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Audio Settings"; + this.Load += new System.EventHandler(this.IntvControllerSettings_Load); + ((System.ComponentModel.ISupportInitialize)(this.tapeVolumetrackBar)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.earVolumetrackBar)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.ayVolumetrackBar)).EndInit(); + 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.Label label2; + private System.Windows.Forms.ComboBox panTypecomboBox1; + private System.Windows.Forms.Label lblBorderInfo; + private System.Windows.Forms.TrackBar tapeVolumetrackBar; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TrackBar earVolumetrackBar; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TrackBar ayVolumetrackBar; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs new file mode 100644 index 0000000000..204d406d7f --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs @@ -0,0 +1,79 @@ +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 ZXSpectrumAudioSettings : Form + { + private ZXSpectrum.ZXSpectrumSettings _settings; + + public ZXSpectrumAudioSettings() + { + InitializeComponent(); + } + + private void IntvControllerSettings_Load(object sender, EventArgs e) + { + _settings = ((ZXSpectrum)Global.Emulator).GetSettings().Clone(); + + // AY panning config + var panTypes = Enum.GetNames(typeof(AYChip.AYPanConfig)); + foreach (var val in panTypes) + { + panTypecomboBox1.Items.Add(val); + } + panTypecomboBox1.SelectedItem = _settings.AYPanConfig.ToString(); + + // tape volume + tapeVolumetrackBar.Value = _settings.TapeVolume; + + // ear volume + earVolumetrackBar.Value = _settings.EarVolume; + + // ay volume + ayVolumetrackBar.Value = _settings.AYVolume; + + + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _settings.AYPanConfig.ToString() != panTypecomboBox1.SelectedItem.ToString() + || _settings.TapeVolume != tapeVolumetrackBar.Value + || _settings.EarVolume != earVolumetrackBar.Value + || _settings.AYVolume != ayVolumetrackBar.Value; + + if (changed) + { + _settings.AYPanConfig = (AYChip.AYPanConfig)Enum.Parse(typeof(AYChip.AYPanConfig), panTypecomboBox1.SelectedItem.ToString()); + + _settings.TapeVolume = tapeVolumetrackBar.Value; + _settings.EarVolume = earVolumetrackBar.Value; + _settings.AYVolume = ayVolumetrackBar.Value; + + 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(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAoIJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAACYXODs4BCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + KTNDQ0M7OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALllbYmJZQBcAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYYWNwcHBwWy8mAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFLanBwcHBwYz0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAABpqcHBwcHBwZVkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl11w + cHBwcHBwcGcSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIXdwcHBwcHBwcGkSAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPXBwcHBwcHBwd2wYAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAACXbnBwdXB5dXl0eW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAid3R5eXl5eXl5q6wzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9eXV5 + i7CxsbGxsblLKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABndYuwsbm8uby5vMFnHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJt3q7G3vMHB1cLBwdWuEgAAAAAAAAAAAAAAAAAA + AAAAAAAeEhMSCiUAAAAAAAAAAEexsbm/1dXZ2dnZ1da5ZgwAAAAAAAAAAAAAAAAAAAAjEjNZaW5qXRMl + AAAAAAAAADW5s7/V2N7i4uLi3dzZrQQPAAAAAAAAAAAAAAAAHxhZbm5uaWltd6ASAAAAAAAAAEmzvMLZ + 3uP29/fw4uTkuUAWCy0AAAAAAAAAAB4YYXd3gG13vbm5vb8zAAAAAAAAAE6xwdXd4/b6+/r38OTl1Vlc + OAMIFAweFBQSM2mtrYB3vdXT0NXExNU1AAAAAAAAAE65wtXe8Pr7/Pz79+fn1WphZ25pXV1mbHetrXd3 + tdXT4vXw49nZ3NYgAAAAAAAAAEu3wdje9vv7/Pz79+fn34B3d2xtoHeud66uudXT4vD39/Dj49zk5G0A + AAAAAAAAAD2xwcwoH0/L/Pukyenp5K27u7m5uczM0Nve4vb3+vr56OPl5eXl1igAAAAAAAAAADWxwQgB + BQYNmveZK/Dp6cG/wcTV2eP3+vr6+/r6+ejm5ufn5+nkIgAAAAAAAAAAAJmruR4sjC2WLFCdDd3p6dXW + 1tXI3vn67pCO9Ojp6efo5+fm59wiAAAAAAAAAAAAAABLsZ0FmC0qKgHMRcjp6dzc1Y2KiO3RlfKTj+np + 5ubm5eXk1SIAAAAAAAAAAAAAAACdab/Lp5aWnEfV1cHm6ebk6pGSiabZ8fOU0uXl5eTk3NyuRQAAAAAA + AAAAAAAAAAAAn0ux0KFTaMHBv7nC6efp3Ovv7OTm3OPl3Nzc3NfW1U6fAAAAAAAAAAAAAAAAAAAAAABF + Wa25t7yxs7Gw5+fn5Obk18XG3NyBfHvD1cSgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAFUzarGwsHl5sefn + 39zEgoZ/hL19fnqirj2jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj09ZXV0cLzn3NXChYeDub+1pbQ9 + VQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0rXj+rpInTBDcHCz5NW/ucG5u7GAM1QAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXjU1h6NnZv/Fc3T/x3Bx/8dw + cf/HcHH/x3Bx/8dwcf/HcHH/w3Jz/6ZoZ/9ZMzPTAQAAPQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyFxccektK0b12 + dv/HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xXR0/69wb/9jOjneBwMDSQAAAAUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNKSlNlmBf9sh3d//HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xnd3/7Z4d/9sQUDnDgcHVQAA + AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABkOjqKsXFw/8lyc//HcXL/yHJz/8l0df/JdXb/yXV2/8l1dv/JdHX/ynt7/7+B + f/94SknvFgsLZQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACILCxB7TUzDwXd3/8lyc//KdXb/y3h5/8x7fP/NfX7/zX5+/819 + fv/NfH3/zoOC/8iJiP+GVVX3Hg8QegAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMiIi+SXl3oynp7/8t4ef/NfX7/z4GC/9GE + hf/Sh4j/04iJ/9KIiP/Rhof/04uK/8+RkP+XY2L9KxcXlwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAA + AA0AAAAPAAAACwAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUvL1enbW37zn5+/85/ + gP/Rhob/1IuM/9aPkP/XkpP/2JOU/9iTlP/XkZH/15OT/9eZl/+rdHP/QSUlvAAAADwAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACQAA + ABgAAAAvAgEBSwcDA2EFAgJoAAAAWAAAADYAAAARAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGU8 + O4W5eXn/0IKD/9KIif/Wj5D/2ZWW/9ubm//dnp//3qCg/92foP/cnZ3/3Jyc/9+in//CiYf/Zj8/4wYC + AnAAAAAbAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AA4AAAAnCQQEUCISEoQ+IiKzVzEx1mU6OuZiOTnmRigo0hgNDZsAAABMAAAAEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAABnVJSK/HhIP/04eI/9aQkf/amJn/3qCh/+Gmp//jq6v/5Kyt/+OsrP/iqan/4aal/+ap + p//Umpj/nmxr/C8ZGboAAABXAAAAGAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAIAAAAOAQAALRkNDWY+IiKpZDo63YZRUfigZGP/sHBv/7V0c/+xcnH/oWZm/2k+PvEfEBCcAAAAMQAA + 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 + cP/Gb3D/oF9e/hMKCmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4QECynZmX7xnBx/sdwcf/HcHH/tG1t/h8REYMAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAx + MIzFc3T+xm9w/sdwcf7HcHH+vHR0/jAcHJkAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ4OAYVSUtfIcnP/yXZ3/st5ef/LeHn/xoB//kQq + KrEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAJxYWGrNvb/7Nfn//0oeI/tSNjf/UjI3/1ZOS/mE+PtQAAAAXAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAIAAAARAAAALQAAADUAAAARAAAAAAAAAAAAAAAAQyYmUM6Ghv/Wj5D/3J2e/uCl + pf/fpKT/4KOi/qRycPkHBARlAAAABQAAAAAAAAAAAAAAAAAAAAAAAAADAQAAJh8REYBYNTXMhVJR8XxM + TO8gEhKeAAAAEAAAAAAAAAAAbUVEe9aPkP7doKD+5rKz/uu9vv7rvLz+6rKx/tqfnf5iNzfnCAQEcwAA + ACoAAAAbAAAAIQIBATorGBiQhFNT67Z3dv68fn3+wYSD/siKiP6aZmX2AQAAKQAAAAAAAAAAd05Ni9eT + lP/jq6z/7cLC/vXS0v/zz9D/8b69/uyxrv+samr/l15d+2tDQ+NkPz7bdkxL451nZve+gYD/yY2M/tWg + n//jtrT/46+t/uOmpP+mdHPwBQMDFAAAAAAAAAAAdkpJh9iUlf7Hl5f+tJeX/uzOzv7lyMj+57y6/vS6 + t/7HhoX+xYaE/saJh/7MkpD+0ZmY/tejov7mt7X+7cXD/vDFxP7vvLr+8Le0/u2zsf5PMzOMDQcHAQAA + AAAAAAAAYTg4X9OOj/9aUlL/YGJi/nh2dv+skJD/qo2M/vnAvf/dn53/4KKg/+Cnp/7vxsT/u8PM/sHI + 0P/1xsT/9sG+/ve+u//3vrv/87q3/ntVVLkkFhYIAAAAAAAAAAAAAAAAVC8wD6BkZOWjhIT/jo6O/n1+ + fv+eenv/xpGR/vi/vP/wtbL/mZPP/0Z2+v69nrr/gd/x/nfD2v/2vLr/9Lq3/vG2tP/lq6j/elJRrjQg + IAoAAAAAAAAAAAAAAAAAAAAAAAAAAGc7OyeOWVnGv4eH/r2Fhf7YlZb+1Y6P/uinpv74v7z+3ay3/seo + w/7srZ/+7LGv/qmyjv63qI7+5Kel/r2GhPZ1S0p1QCcmAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAd0pKOpReXtKxb3D/yXl6/sx5ev/ws7D/6q6s/+Ked/7npFb/2ZiP/ny7gP+OjW/9h1dWr2I7 + OiMAAAAAAAAAAAAAAAAAAAAAAAAAALSCggSqcXIbo2dnN61xcVS/h4eIzp2c2cKWle2OY2OGbz4+Y4xN + Tr6zaWn84Jyb/9aXlv7Ji4r/p25t9INTUqZlPDw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJg + YASjcnMorH9/a6h7e4yabm6Df1NTU3VKSgwAAAAAAAAAAAAAAABgNDQgcj8/bntHR4ZnPDxTVTExDQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////APx//wD4P/8A8D//AOA//wDgH/8A4B//AMAf + /wDAH8EAwA8AAMAAAADAAAAAwAAAAMAAAQDAAAMA4AAHAPgAHwAAAH8AAcH/AP///wD///8A////ACgA + AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQc + HA5LKSlUNBwcSAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsO + DgV/SkqHm1hY+X5HR90tGRkuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB4SEhCr2Zm7sZwcf+oYWL5UC8vUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACnl9fnMRwcf/IcXL/tmxs/mI8PGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAa0NCGbRsbdbMenv/zn5//8R9ff9ySkmCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA + AAkAAAAAAAAAAItYWDvFfn/y2ZWW/92fn//anJv/jWFgvwAAAB0AAAAAAAAAAAAAAAIzHBwiYjs7a3pM + S6pqQkKjLBoaMwAAAACeZ2dZ05KS/em0tP/vxMT/77u6/8CHhfpmPDyvRysqYlExMV1ySEiGnWdn07qB + gPzLkI//w4iG/HJLS3YAAAAAomloXsyRkf/DoKD/48bG/+jAv//hpKL/vX17/7h/fPu/iYj7z5qZ/+Gw + rv/rvLr/77q3/9ScmuR9U1I+AAAAAJZbWz2ndnbxdG9v/4yCgv+4lJP/77Wy/86erP+6nsH/tsXR/8PH + 0P/4wsD/9b26/+Cppu2peXdiAAAAAQAAAABYKCgHn2lqe6eCguSsgoL90pKS//Cxrv/TrcP/s5y+/8i3 + s/+quab/26mh/82UktSgbm1TBAAAAwAAAACud3cEvYGBC7N6ehyyfHtyt39+3bNub9vLgYH05qak/+Kg + g//OlH39jZR04Zd0aYmDT1EiAAAAAAAAAAAAAAAAr3t7D7aCgki5h4Z8uImJgah+fUltPz8ajU1ORq1s + bI6vdHOgm2RkaYxJUiZgCygCAAAAAAAAAAAAAAAAAAAAAGo9PQF9UVEHcEdHCTodHQIAAAAAAAAAAAAA + AAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADh/wAAwf8AAMH/ + AACB/wAAgfkAAIDAAACAAAAAgAAAAIAAAACAAQAAAAcAAAAPAAAOfwAA//8AAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs new file mode 100644 index 0000000000..d7e1cf1e63 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs @@ -0,0 +1,187 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumCoreEmulationSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs new file mode 100644 index 0000000000..bb8ba92d50 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs @@ -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; + } + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAoIJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAACYXODs4BCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + KTNDQ0M7OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALllbYmJZQBcAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYYWNwcHBwWy8mAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFLanBwcHBwYz0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAABpqcHBwcHBwZVkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl11w + cHBwcHBwcGcSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIXdwcHBwcHBwcGkSAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPXBwcHBwcHBwd2wYAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAACXbnBwdXB5dXl0eW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAid3R5eXl5eXl5q6wzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9eXV5 + i7CxsbGxsblLKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABndYuwsbm8uby5vMFnHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJt3q7G3vMHB1cLBwdWuEgAAAAAAAAAAAAAAAAAA + AAAAAAAeEhMSCiUAAAAAAAAAAEexsbm/1dXZ2dnZ1da5ZgwAAAAAAAAAAAAAAAAAAAAjEjNZaW5qXRMl + AAAAAAAAADW5s7/V2N7i4uLi3dzZrQQPAAAAAAAAAAAAAAAAHxhZbm5uaWltd6ASAAAAAAAAAEmzvMLZ + 3uP29/fw4uTkuUAWCy0AAAAAAAAAAB4YYXd3gG13vbm5vb8zAAAAAAAAAE6xwdXd4/b6+/r38OTl1Vlc + OAMIFAweFBQSM2mtrYB3vdXT0NXExNU1AAAAAAAAAE65wtXe8Pr7/Pz79+fn1WphZ25pXV1mbHetrXd3 + tdXT4vXw49nZ3NYgAAAAAAAAAEu3wdje9vv7/Pz79+fn34B3d2xtoHeud66uudXT4vD39/Dj49zk5G0A + AAAAAAAAAD2xwcwoH0/L/Pukyenp5K27u7m5uczM0Nve4vb3+vr56OPl5eXl1igAAAAAAAAAADWxwQgB + BQYNmveZK/Dp6cG/wcTV2eP3+vr6+/r6+ejm5ufn5+nkIgAAAAAAAAAAAJmruR4sjC2WLFCdDd3p6dXW + 1tXI3vn67pCO9Ojp6efo5+fm59wiAAAAAAAAAAAAAABLsZ0FmC0qKgHMRcjp6dzc1Y2KiO3RlfKTj+np + 5ubm5eXk1SIAAAAAAAAAAAAAAACdab/Lp5aWnEfV1cHm6ebk6pGSiabZ8fOU0uXl5eTk3NyuRQAAAAAA + AAAAAAAAAAAAn0ux0KFTaMHBv7nC6efp3Ovv7OTm3OPl3Nzc3NfW1U6fAAAAAAAAAAAAAAAAAAAAAABF + Wa25t7yxs7Gw5+fn5Obk18XG3NyBfHvD1cSgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAFUzarGwsHl5sefn + 39zEgoZ/hL19fnqirj2jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj09ZXV0cLzn3NXChYeDub+1pbQ9 + VQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0rXj+rpInTBDcHCz5NW/ucG5u7GAM1QAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXjU1h6NnZv/Fc3T/x3Bx/8dw + cf/HcHH/x3Bx/8dwcf/HcHH/w3Jz/6ZoZ/9ZMzPTAQAAPQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyFxccektK0b12 + dv/HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xXR0/69wb/9jOjneBwMDSQAAAAUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNKSlNlmBf9sh3d//HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xnd3/7Z4d/9sQUDnDgcHVQAA + AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABkOjqKsXFw/8lyc//HcXL/yHJz/8l0df/JdXb/yXV2/8l1dv/JdHX/ynt7/7+B + f/94SknvFgsLZQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACILCxB7TUzDwXd3/8lyc//KdXb/y3h5/8x7fP/NfX7/zX5+/819 + fv/NfH3/zoOC/8iJiP+GVVX3Hg8QegAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMiIi+SXl3oynp7/8t4ef/NfX7/z4GC/9GE + hf/Sh4j/04iJ/9KIiP/Rhof/04uK/8+RkP+XY2L9KxcXlwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAA + AA0AAAAPAAAACwAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUvL1enbW37zn5+/85/ + gP/Rhob/1IuM/9aPkP/XkpP/2JOU/9iTlP/XkZH/15OT/9eZl/+rdHP/QSUlvAAAADwAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACQAA + ABgAAAAvAgEBSwcDA2EFAgJoAAAAWAAAADYAAAARAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGU8 + O4W5eXn/0IKD/9KIif/Wj5D/2ZWW/9ubm//dnp//3qCg/92foP/cnZ3/3Jyc/9+in//CiYf/Zj8/4wYC + AnAAAAAbAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AA4AAAAnCQQEUCISEoQ+IiKzVzEx1mU6OuZiOTnmRigo0hgNDZsAAABMAAAAEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAABnVJSK/HhIP/04eI/9aQkf/amJn/3qCh/+Gmp//jq6v/5Kyt/+OsrP/iqan/4aal/+ap + p//Umpj/nmxr/C8ZGboAAABXAAAAGAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAIAAAAOAQAALRkNDWY+IiKpZDo63YZRUfigZGP/sHBv/7V0c/+xcnH/oWZm/2k+PvEfEBCcAAAAMQAA + 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 + cP/Gb3D/oF9e/hMKCmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4QECynZmX7xnBx/sdwcf/HcHH/tG1t/h8REYMAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAx + MIzFc3T+xm9w/sdwcf7HcHH+vHR0/jAcHJkAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ4OAYVSUtfIcnP/yXZ3/st5ef/LeHn/xoB//kQq + KrEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAJxYWGrNvb/7Nfn//0oeI/tSNjf/UjI3/1ZOS/mE+PtQAAAAXAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAIAAAARAAAALQAAADUAAAARAAAAAAAAAAAAAAAAQyYmUM6Ghv/Wj5D/3J2e/uCl + pf/fpKT/4KOi/qRycPkHBARlAAAABQAAAAAAAAAAAAAAAAAAAAAAAAADAQAAJh8REYBYNTXMhVJR8XxM + TO8gEhKeAAAAEAAAAAAAAAAAbUVEe9aPkP7doKD+5rKz/uu9vv7rvLz+6rKx/tqfnf5iNzfnCAQEcwAA + ACoAAAAbAAAAIQIBATorGBiQhFNT67Z3dv68fn3+wYSD/siKiP6aZmX2AQAAKQAAAAAAAAAAd05Ni9eT + lP/jq6z/7cLC/vXS0v/zz9D/8b69/uyxrv+samr/l15d+2tDQ+NkPz7bdkxL451nZve+gYD/yY2M/tWg + n//jtrT/46+t/uOmpP+mdHPwBQMDFAAAAAAAAAAAdkpJh9iUlf7Hl5f+tJeX/uzOzv7lyMj+57y6/vS6 + t/7HhoX+xYaE/saJh/7MkpD+0ZmY/tejov7mt7X+7cXD/vDFxP7vvLr+8Le0/u2zsf5PMzOMDQcHAQAA + AAAAAAAAYTg4X9OOj/9aUlL/YGJi/nh2dv+skJD/qo2M/vnAvf/dn53/4KKg/+Cnp/7vxsT/u8PM/sHI + 0P/1xsT/9sG+/ve+u//3vrv/87q3/ntVVLkkFhYIAAAAAAAAAAAAAAAAVC8wD6BkZOWjhIT/jo6O/n1+ + fv+eenv/xpGR/vi/vP/wtbL/mZPP/0Z2+v69nrr/gd/x/nfD2v/2vLr/9Lq3/vG2tP/lq6j/elJRrjQg + IAoAAAAAAAAAAAAAAAAAAAAAAAAAAGc7OyeOWVnGv4eH/r2Fhf7YlZb+1Y6P/uinpv74v7z+3ay3/seo + w/7srZ/+7LGv/qmyjv63qI7+5Kel/r2GhPZ1S0p1QCcmAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAd0pKOpReXtKxb3D/yXl6/sx5ev/ws7D/6q6s/+Ked/7npFb/2ZiP/ny7gP+OjW/9h1dWr2I7 + OiMAAAAAAAAAAAAAAAAAAAAAAAAAALSCggSqcXIbo2dnN61xcVS/h4eIzp2c2cKWle2OY2OGbz4+Y4xN + Tr6zaWn84Jyb/9aXlv7Ji4r/p25t9INTUqZlPDw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJg + YASjcnMorH9/a6h7e4yabm6Df1NTU3VKSgwAAAAAAAAAAAAAAABgNDQgcj8/bntHR4ZnPDxTVTExDQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////APx//wD4P/8A8D//AOA//wDgH/8A4B//AMAf + /wDAH8EAwA8AAMAAAADAAAAAwAAAAMAAAQDAAAMA4AAHAPgAHwAAAH8AAcH/AP///wD///8A////ACgA + AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQc + HA5LKSlUNBwcSAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsO + DgV/SkqHm1hY+X5HR90tGRkuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB4SEhCr2Zm7sZwcf+oYWL5UC8vUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACnl9fnMRwcf/IcXL/tmxs/mI8PGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAa0NCGbRsbdbMenv/zn5//8R9ff9ySkmCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA + AAkAAAAAAAAAAItYWDvFfn/y2ZWW/92fn//anJv/jWFgvwAAAB0AAAAAAAAAAAAAAAIzHBwiYjs7a3pM + S6pqQkKjLBoaMwAAAACeZ2dZ05KS/em0tP/vxMT/77u6/8CHhfpmPDyvRysqYlExMV1ySEiGnWdn07qB + gPzLkI//w4iG/HJLS3YAAAAAomloXsyRkf/DoKD/48bG/+jAv//hpKL/vX17/7h/fPu/iYj7z5qZ/+Gw + rv/rvLr/77q3/9ScmuR9U1I+AAAAAJZbWz2ndnbxdG9v/4yCgv+4lJP/77Wy/86erP+6nsH/tsXR/8PH + 0P/4wsD/9b26/+Cppu2peXdiAAAAAQAAAABYKCgHn2lqe6eCguSsgoL90pKS//Cxrv/TrcP/s5y+/8i3 + s/+quab/26mh/82UktSgbm1TBAAAAwAAAACud3cEvYGBC7N6ehyyfHtyt39+3bNub9vLgYH05qak/+Kg + g//OlH39jZR04Zd0aYmDT1EiAAAAAAAAAAAAAAAAr3t7D7aCgki5h4Z8uImJgah+fUltPz8ajU1ORq1s + bI6vdHOgm2RkaYxJUiZgCygCAAAAAAAAAAAAAAAAAAAAAGo9PQF9UVEHcEdHCTodHQIAAAAAAAAAAAAA + AAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADh/wAAwf8AAMH/ + AACB/wAAgfkAAIDAAACAAAAAgAAAAIAAAACAAQAAAAcAAAAPAAAOfwAA//8AAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs new file mode 100644 index 0000000000..342e833dc1 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs @@ -0,0 +1,184 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumJoystickSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs new file mode 100644 index 0000000000..fc151c843c --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs @@ -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(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx new file mode 100644 index 0000000000..c45473925d --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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. + + + + + + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAoIJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAACYXODs4BCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + KTNDQ0M7OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALllbYmJZQBcAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYYWNwcHBwWy8mAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFLanBwcHBwYz0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAABpqcHBwcHBwZVkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl11w + cHBwcHBwcGcSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIXdwcHBwcHBwcGkSAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPXBwcHBwcHBwd2wYAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAACXbnBwdXB5dXl0eW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAid3R5eXl5eXl5q6wzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9eXV5 + i7CxsbGxsblLKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABndYuwsbm8uby5vMFnHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJt3q7G3vMHB1cLBwdWuEgAAAAAAAAAAAAAAAAAA + AAAAAAAeEhMSCiUAAAAAAAAAAEexsbm/1dXZ2dnZ1da5ZgwAAAAAAAAAAAAAAAAAAAAjEjNZaW5qXRMl + AAAAAAAAADW5s7/V2N7i4uLi3dzZrQQPAAAAAAAAAAAAAAAAHxhZbm5uaWltd6ASAAAAAAAAAEmzvMLZ + 3uP29/fw4uTkuUAWCy0AAAAAAAAAAB4YYXd3gG13vbm5vb8zAAAAAAAAAE6xwdXd4/b6+/r38OTl1Vlc + OAMIFAweFBQSM2mtrYB3vdXT0NXExNU1AAAAAAAAAE65wtXe8Pr7/Pz79+fn1WphZ25pXV1mbHetrXd3 + tdXT4vXw49nZ3NYgAAAAAAAAAEu3wdje9vv7/Pz79+fn34B3d2xtoHeud66uudXT4vD39/Dj49zk5G0A + AAAAAAAAAD2xwcwoH0/L/Pukyenp5K27u7m5uczM0Nve4vb3+vr56OPl5eXl1igAAAAAAAAAADWxwQgB + BQYNmveZK/Dp6cG/wcTV2eP3+vr6+/r6+ejm5ufn5+nkIgAAAAAAAAAAAJmruR4sjC2WLFCdDd3p6dXW + 1tXI3vn67pCO9Ojp6efo5+fm59wiAAAAAAAAAAAAAABLsZ0FmC0qKgHMRcjp6dzc1Y2KiO3RlfKTj+np + 5ubm5eXk1SIAAAAAAAAAAAAAAACdab/Lp5aWnEfV1cHm6ebk6pGSiabZ8fOU0uXl5eTk3NyuRQAAAAAA + AAAAAAAAAAAAn0ux0KFTaMHBv7nC6efp3Ovv7OTm3OPl3Nzc3NfW1U6fAAAAAAAAAAAAAAAAAAAAAABF + Wa25t7yxs7Gw5+fn5Obk18XG3NyBfHvD1cSgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAFUzarGwsHl5sefn + 39zEgoZ/hL19fnqirj2jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj09ZXV0cLzn3NXChYeDub+1pbQ9 + VQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0rXj+rpInTBDcHCz5NW/ucG5u7GAM1QAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXjU1h6NnZv/Fc3T/x3Bx/8dw + cf/HcHH/x3Bx/8dwcf/HcHH/w3Jz/6ZoZ/9ZMzPTAQAAPQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyFxccektK0b12 + dv/HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xXR0/69wb/9jOjneBwMDSQAAAAUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNKSlNlmBf9sh3d//HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xnd3/7Z4d/9sQUDnDgcHVQAA + AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABkOjqKsXFw/8lyc//HcXL/yHJz/8l0df/JdXb/yXV2/8l1dv/JdHX/ynt7/7+B + f/94SknvFgsLZQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACILCxB7TUzDwXd3/8lyc//KdXb/y3h5/8x7fP/NfX7/zX5+/819 + fv/NfH3/zoOC/8iJiP+GVVX3Hg8QegAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMiIi+SXl3oynp7/8t4ef/NfX7/z4GC/9GE + hf/Sh4j/04iJ/9KIiP/Rhof/04uK/8+RkP+XY2L9KxcXlwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAA + AA0AAAAPAAAACwAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUvL1enbW37zn5+/85/ + gP/Rhob/1IuM/9aPkP/XkpP/2JOU/9iTlP/XkZH/15OT/9eZl/+rdHP/QSUlvAAAADwAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACQAA + ABgAAAAvAgEBSwcDA2EFAgJoAAAAWAAAADYAAAARAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGU8 + O4W5eXn/0IKD/9KIif/Wj5D/2ZWW/9ubm//dnp//3qCg/92foP/cnZ3/3Jyc/9+in//CiYf/Zj8/4wYC + AnAAAAAbAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AA4AAAAnCQQEUCISEoQ+IiKzVzEx1mU6OuZiOTnmRigo0hgNDZsAAABMAAAAEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAABnVJSK/HhIP/04eI/9aQkf/amJn/3qCh/+Gmp//jq6v/5Kyt/+OsrP/iqan/4aal/+ap + p//Umpj/nmxr/C8ZGboAAABXAAAAGAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAIAAAAOAQAALRkNDWY+IiKpZDo63YZRUfigZGP/sHBv/7V0c/+xcnH/oWZm/2k+PvEfEBCcAAAAMQAA + 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 + cP/Gb3D/oF9e/hMKCmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4QECynZmX7xnBx/sdwcf/HcHH/tG1t/h8REYMAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAx + MIzFc3T+xm9w/sdwcf7HcHH+vHR0/jAcHJkAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ4OAYVSUtfIcnP/yXZ3/st5ef/LeHn/xoB//kQq + KrEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAJxYWGrNvb/7Nfn//0oeI/tSNjf/UjI3/1ZOS/mE+PtQAAAAXAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAIAAAARAAAALQAAADUAAAARAAAAAAAAAAAAAAAAQyYmUM6Ghv/Wj5D/3J2e/uCl + pf/fpKT/4KOi/qRycPkHBARlAAAABQAAAAAAAAAAAAAAAAAAAAAAAAADAQAAJh8REYBYNTXMhVJR8XxM + TO8gEhKeAAAAEAAAAAAAAAAAbUVEe9aPkP7doKD+5rKz/uu9vv7rvLz+6rKx/tqfnf5iNzfnCAQEcwAA + ACoAAAAbAAAAIQIBATorGBiQhFNT67Z3dv68fn3+wYSD/siKiP6aZmX2AQAAKQAAAAAAAAAAd05Ni9eT + lP/jq6z/7cLC/vXS0v/zz9D/8b69/uyxrv+samr/l15d+2tDQ+NkPz7bdkxL451nZve+gYD/yY2M/tWg + n//jtrT/46+t/uOmpP+mdHPwBQMDFAAAAAAAAAAAdkpJh9iUlf7Hl5f+tJeX/uzOzv7lyMj+57y6/vS6 + t/7HhoX+xYaE/saJh/7MkpD+0ZmY/tejov7mt7X+7cXD/vDFxP7vvLr+8Le0/u2zsf5PMzOMDQcHAQAA + AAAAAAAAYTg4X9OOj/9aUlL/YGJi/nh2dv+skJD/qo2M/vnAvf/dn53/4KKg/+Cnp/7vxsT/u8PM/sHI + 0P/1xsT/9sG+/ve+u//3vrv/87q3/ntVVLkkFhYIAAAAAAAAAAAAAAAAVC8wD6BkZOWjhIT/jo6O/n1+ + fv+eenv/xpGR/vi/vP/wtbL/mZPP/0Z2+v69nrr/gd/x/nfD2v/2vLr/9Lq3/vG2tP/lq6j/elJRrjQg + IAoAAAAAAAAAAAAAAAAAAAAAAAAAAGc7OyeOWVnGv4eH/r2Fhf7YlZb+1Y6P/uinpv74v7z+3ay3/seo + w/7srZ/+7LGv/qmyjv63qI7+5Kel/r2GhPZ1S0p1QCcmAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAd0pKOpReXtKxb3D/yXl6/sx5ev/ws7D/6q6s/+Ked/7npFb/2ZiP/ny7gP+OjW/9h1dWr2I7 + OiMAAAAAAAAAAAAAAAAAAAAAAAAAALSCggSqcXIbo2dnN61xcVS/h4eIzp2c2cKWle2OY2OGbz4+Y4xN + Tr6zaWn84Jyb/9aXlv7Ji4r/p25t9INTUqZlPDw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJg + YASjcnMorH9/a6h7e4yabm6Df1NTU3VKSgwAAAAAAAAAAAAAAABgNDQgcj8/bntHR4ZnPDxTVTExDQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////APx//wD4P/8A8D//AOA//wDgH/8A4B//AMAf + /wDAH8EAwA8AAMAAAADAAAAAwAAAAMAAAQDAAAMA4AAHAPgAHwAAAH8AAcH/AP///wD///8A////ACgA + AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQc + HA5LKSlUNBwcSAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsO + DgV/SkqHm1hY+X5HR90tGRkuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB4SEhCr2Zm7sZwcf+oYWL5UC8vUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACnl9fnMRwcf/IcXL/tmxs/mI8PGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAa0NCGbRsbdbMenv/zn5//8R9ff9ySkmCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA + AAkAAAAAAAAAAItYWDvFfn/y2ZWW/92fn//anJv/jWFgvwAAAB0AAAAAAAAAAAAAAAIzHBwiYjs7a3pM + S6pqQkKjLBoaMwAAAACeZ2dZ05KS/em0tP/vxMT/77u6/8CHhfpmPDyvRysqYlExMV1ySEiGnWdn07qB + gPzLkI//w4iG/HJLS3YAAAAAomloXsyRkf/DoKD/48bG/+jAv//hpKL/vX17/7h/fPu/iYj7z5qZ/+Gw + rv/rvLr/77q3/9ScmuR9U1I+AAAAAJZbWz2ndnbxdG9v/4yCgv+4lJP/77Wy/86erP+6nsH/tsXR/8PH + 0P/4wsD/9b26/+Cppu2peXdiAAAAAQAAAABYKCgHn2lqe6eCguSsgoL90pKS//Cxrv/TrcP/s5y+/8i3 + s/+quab/26mh/82UktSgbm1TBAAAAwAAAACud3cEvYGBC7N6ehyyfHtyt39+3bNub9vLgYH05qak/+Kg + g//OlH39jZR04Zd0aYmDT1EiAAAAAAAAAAAAAAAAr3t7D7aCgki5h4Z8uImJgah+fUltPz8ajU1ORq1s + bI6vdHOgm2RkaYxJUiZgCygCAAAAAAAAAAAAAAAAAAAAAGo9PQF9UVEHcEdHCTodHQIAAAAAAAAAAAAA + AAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADh/wAAwf8AAMH/ + AACB/wAAgfkAAIDAAACAAAAAgAAAAIAAAACAAQAAAAcAAAAPAAAOfwAA//8AAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs new file mode 100644 index 0000000000..a519b00e94 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs @@ -0,0 +1,162 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumNonSyncSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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.lblAutoLoadText = new System.Windows.Forms.Label(); + this.lblOSDVerbinfo = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.osdMessageVerbositycomboBox1 = new System.Windows.Forms.ComboBox(); + 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, 145); + 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, 145); + 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; + // + // 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; + // + // lblOSDVerbinfo + // + this.lblOSDVerbinfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblOSDVerbinfo.Location = new System.Drawing.Point(175, 107); + this.lblOSDVerbinfo.Name = "lblOSDVerbinfo"; + this.lblOSDVerbinfo.Size = new System.Drawing.Size(196, 21); + this.lblOSDVerbinfo.TabIndex = 28; + this.lblOSDVerbinfo.Text = "null"; + this.lblOSDVerbinfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 91); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(125, 13); + this.label4.TabIndex = 27; + this.label4.Text = "OSD Message Verbosity:"; + // + // osdMessageVerbositycomboBox1 + // + this.osdMessageVerbositycomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.osdMessageVerbositycomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.osdMessageVerbositycomboBox1.FormattingEnabled = true; + this.osdMessageVerbositycomboBox1.Location = new System.Drawing.Point(12, 107); + this.osdMessageVerbositycomboBox1.Name = "osdMessageVerbositycomboBox1"; + this.osdMessageVerbositycomboBox1.Size = new System.Drawing.Size(157, 21); + this.osdMessageVerbositycomboBox1.TabIndex = 26; + this.osdMessageVerbositycomboBox1.SelectionChangeCommitted += new System.EventHandler(this.OSDComboBox_SelectionChangeCommitted); + // + // 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, 180); + this.Controls.Add(this.lblOSDVerbinfo); + this.Controls.Add(this.label4); + this.Controls.Add(this.osdMessageVerbositycomboBox1); + this.Controls.Add(this.lblAutoLoadText); + 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 lblAutoLoadText; + private System.Windows.Forms.Label lblOSDVerbinfo; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox osdMessageVerbositycomboBox1; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs new file mode 100644 index 0000000000..921cee6b8b --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs @@ -0,0 +1,89 @@ +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; + + // OSD Message Verbosity + var osdTypes = Enum.GetNames(typeof(ZXSpectrum.OSDVerbosity)); + foreach (var val in osdTypes) + { + osdMessageVerbositycomboBox1.Items.Add(val); + } + osdMessageVerbositycomboBox1.SelectedItem = _settings.OSDMessageVerbosity.ToString(); + UpdateOSDNotes((ZXSpectrum.OSDVerbosity)Enum.Parse(typeof(ZXSpectrum.OSDVerbosity), osdMessageVerbositycomboBox1.SelectedItem.ToString())); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _settings.AutoLoadTape != autoLoadcheckBox1.Checked + || _settings.OSDMessageVerbosity.ToString() != osdMessageVerbositycomboBox1.SelectedItem.ToString(); + + if (changed) + { + _settings.AutoLoadTape = autoLoadcheckBox1.Checked; + _settings.OSDMessageVerbosity = (ZXSpectrum.OSDVerbosity)Enum.Parse(typeof(ZXSpectrum.OSDVerbosity), osdMessageVerbositycomboBox1.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(); + } + + private void UpdateOSDNotes(ZXSpectrum.OSDVerbosity type) + { + switch (type) + { + case ZXSpectrum.OSDVerbosity.Full: + lblOSDVerbinfo.Text = "Show all OSD messages"; + break; + case ZXSpectrum.OSDVerbosity.Medium: + lblOSDVerbinfo.Text = "Only show machine/device generated messages"; + break; + case ZXSpectrum.OSDVerbosity.None: + lblOSDVerbinfo.Text = "No core-driven OSD messages"; + break; + } + } + + private void OSDComboBox_SelectionChangeCommitted(object sender, EventArgs e) + { + ComboBox cb = sender as ComboBox; + UpdateOSDNotes((ZXSpectrum.OSDVerbosity)Enum.Parse(typeof(ZXSpectrum.OSDVerbosity), cb.SelectedItem.ToString())); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAoIJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAACYXODs4BCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + KTNDQ0M7OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALllbYmJZQBcAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYYWNwcHBwWy8mAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFLanBwcHBwYz0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAABpqcHBwcHBwZVkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl11w + cHBwcHBwcGcSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIXdwcHBwcHBwcGkSAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPXBwcHBwcHBwd2wYAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAACXbnBwdXB5dXl0eW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAid3R5eXl5eXl5q6wzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9eXV5 + i7CxsbGxsblLKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABndYuwsbm8uby5vMFnHgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJt3q7G3vMHB1cLBwdWuEgAAAAAAAAAAAAAAAAAA + AAAAAAAeEhMSCiUAAAAAAAAAAEexsbm/1dXZ2dnZ1da5ZgwAAAAAAAAAAAAAAAAAAAAjEjNZaW5qXRMl + AAAAAAAAADW5s7/V2N7i4uLi3dzZrQQPAAAAAAAAAAAAAAAAHxhZbm5uaWltd6ASAAAAAAAAAEmzvMLZ + 3uP29/fw4uTkuUAWCy0AAAAAAAAAAB4YYXd3gG13vbm5vb8zAAAAAAAAAE6xwdXd4/b6+/r38OTl1Vlc + OAMIFAweFBQSM2mtrYB3vdXT0NXExNU1AAAAAAAAAE65wtXe8Pr7/Pz79+fn1WphZ25pXV1mbHetrXd3 + tdXT4vXw49nZ3NYgAAAAAAAAAEu3wdje9vv7/Pz79+fn34B3d2xtoHeud66uudXT4vD39/Dj49zk5G0A + AAAAAAAAAD2xwcwoH0/L/Pukyenp5K27u7m5uczM0Nve4vb3+vr56OPl5eXl1igAAAAAAAAAADWxwQgB + BQYNmveZK/Dp6cG/wcTV2eP3+vr6+/r6+ejm5ufn5+nkIgAAAAAAAAAAAJmruR4sjC2WLFCdDd3p6dXW + 1tXI3vn67pCO9Ojp6efo5+fm59wiAAAAAAAAAAAAAABLsZ0FmC0qKgHMRcjp6dzc1Y2KiO3RlfKTj+np + 5ubm5eXk1SIAAAAAAAAAAAAAAACdab/Lp5aWnEfV1cHm6ebk6pGSiabZ8fOU0uXl5eTk3NyuRQAAAAAA + AAAAAAAAAAAAn0ux0KFTaMHBv7nC6efp3Ovv7OTm3OPl3Nzc3NfW1U6fAAAAAAAAAAAAAAAAAAAAAABF + Wa25t7yxs7Gw5+fn5Obk18XG3NyBfHvD1cSgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAFUzarGwsHl5sefn + 39zEgoZ/hL19fnqirj2jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj09ZXV0cLzn3NXChYeDub+1pbQ9 + VQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0rXj+rpInTBDcHCz5NW/ucG5u7GAM1QAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXjU1h6NnZv/Fc3T/x3Bx/8dw + cf/HcHH/x3Bx/8dwcf/HcHH/w3Jz/6ZoZ/9ZMzPTAQAAPQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyFxccektK0b12 + dv/HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xXR0/69wb/9jOjneBwMDSQAAAAUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNKSlNlmBf9sh3d//HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xnd3/7Z4d/9sQUDnDgcHVQAA + AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABkOjqKsXFw/8lyc//HcXL/yHJz/8l0df/JdXb/yXV2/8l1dv/JdHX/ynt7/7+B + f/94SknvFgsLZQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACILCxB7TUzDwXd3/8lyc//KdXb/y3h5/8x7fP/NfX7/zX5+/819 + fv/NfH3/zoOC/8iJiP+GVVX3Hg8QegAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMiIi+SXl3oynp7/8t4ef/NfX7/z4GC/9GE + hf/Sh4j/04iJ/9KIiP/Rhof/04uK/8+RkP+XY2L9KxcXlwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAA + AA0AAAAPAAAACwAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUvL1enbW37zn5+/85/ + gP/Rhob/1IuM/9aPkP/XkpP/2JOU/9iTlP/XkZH/15OT/9eZl/+rdHP/QSUlvAAAADwAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACQAA + ABgAAAAvAgEBSwcDA2EFAgJoAAAAWAAAADYAAAARAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGU8 + O4W5eXn/0IKD/9KIif/Wj5D/2ZWW/9ubm//dnp//3qCg/92foP/cnZ3/3Jyc/9+in//CiYf/Zj8/4wYC + AnAAAAAbAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AA4AAAAnCQQEUCISEoQ+IiKzVzEx1mU6OuZiOTnmRigo0hgNDZsAAABMAAAAEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAABnVJSK/HhIP/04eI/9aQkf/amJn/3qCh/+Gmp//jq6v/5Kyt/+OsrP/iqan/4aal/+ap + p//Umpj/nmxr/C8ZGboAAABXAAAAGAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAIAAAAOAQAALRkNDWY+IiKpZDo63YZRUfigZGP/sHBv/7V0c/+xcnH/oWZm/2k+PvEfEBCcAAAAMQAA + 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 + cP/Gb3D/oF9e/hMKCmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4QECynZmX7xnBx/sdwcf/HcHH/tG1t/h8REYMAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAx + MIzFc3T+xm9w/sdwcf7HcHH+vHR0/jAcHJkAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ4OAYVSUtfIcnP/yXZ3/st5ef/LeHn/xoB//kQq + KrEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAJxYWGrNvb/7Nfn//0oeI/tSNjf/UjI3/1ZOS/mE+PtQAAAAXAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAIAAAARAAAALQAAADUAAAARAAAAAAAAAAAAAAAAQyYmUM6Ghv/Wj5D/3J2e/uCl + pf/fpKT/4KOi/qRycPkHBARlAAAABQAAAAAAAAAAAAAAAAAAAAAAAAADAQAAJh8REYBYNTXMhVJR8XxM + TO8gEhKeAAAAEAAAAAAAAAAAbUVEe9aPkP7doKD+5rKz/uu9vv7rvLz+6rKx/tqfnf5iNzfnCAQEcwAA + ACoAAAAbAAAAIQIBATorGBiQhFNT67Z3dv68fn3+wYSD/siKiP6aZmX2AQAAKQAAAAAAAAAAd05Ni9eT + lP/jq6z/7cLC/vXS0v/zz9D/8b69/uyxrv+samr/l15d+2tDQ+NkPz7bdkxL451nZve+gYD/yY2M/tWg + n//jtrT/46+t/uOmpP+mdHPwBQMDFAAAAAAAAAAAdkpJh9iUlf7Hl5f+tJeX/uzOzv7lyMj+57y6/vS6 + t/7HhoX+xYaE/saJh/7MkpD+0ZmY/tejov7mt7X+7cXD/vDFxP7vvLr+8Le0/u2zsf5PMzOMDQcHAQAA + AAAAAAAAYTg4X9OOj/9aUlL/YGJi/nh2dv+skJD/qo2M/vnAvf/dn53/4KKg/+Cnp/7vxsT/u8PM/sHI + 0P/1xsT/9sG+/ve+u//3vrv/87q3/ntVVLkkFhYIAAAAAAAAAAAAAAAAVC8wD6BkZOWjhIT/jo6O/n1+ + fv+eenv/xpGR/vi/vP/wtbL/mZPP/0Z2+v69nrr/gd/x/nfD2v/2vLr/9Lq3/vG2tP/lq6j/elJRrjQg + IAoAAAAAAAAAAAAAAAAAAAAAAAAAAGc7OyeOWVnGv4eH/r2Fhf7YlZb+1Y6P/uinpv74v7z+3ay3/seo + w/7srZ/+7LGv/qmyjv63qI7+5Kel/r2GhPZ1S0p1QCcmAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAd0pKOpReXtKxb3D/yXl6/sx5ev/ws7D/6q6s/+Ked/7npFb/2ZiP/ny7gP+OjW/9h1dWr2I7 + OiMAAAAAAAAAAAAAAAAAAAAAAAAAALSCggSqcXIbo2dnN61xcVS/h4eIzp2c2cKWle2OY2OGbz4+Y4xN + Tr6zaWn84Jyb/9aXlv7Ji4r/p25t9INTUqZlPDw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJg + YASjcnMorH9/a6h7e4yabm6Df1NTU3VKSgwAAAAAAAAAAAAAAABgNDQgcj8/bntHR4ZnPDxTVTExDQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////APx//wD4P/8A8D//AOA//wDgH/8A4B//AMAf + /wDAH8EAwA8AAMAAAADAAAAAwAAAAMAAAQDAAAMA4AAHAPgAHwAAAH8AAcH/AP///wD///8A////ACgA + AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQc + HA5LKSlUNBwcSAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsO + DgV/SkqHm1hY+X5HR90tGRkuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB4SEhCr2Zm7sZwcf+oYWL5UC8vUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACnl9fnMRwcf/IcXL/tmxs/mI8PGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAa0NCGbRsbdbMenv/zn5//8R9ff9ySkmCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA + AAkAAAAAAAAAAItYWDvFfn/y2ZWW/92fn//anJv/jWFgvwAAAB0AAAAAAAAAAAAAAAIzHBwiYjs7a3pM + S6pqQkKjLBoaMwAAAACeZ2dZ05KS/em0tP/vxMT/77u6/8CHhfpmPDyvRysqYlExMV1ySEiGnWdn07qB + gPzLkI//w4iG/HJLS3YAAAAAomloXsyRkf/DoKD/48bG/+jAv//hpKL/vX17/7h/fPu/iYj7z5qZ/+Gw + rv/rvLr/77q3/9ScmuR9U1I+AAAAAJZbWz2ndnbxdG9v/4yCgv+4lJP/77Wy/86erP+6nsH/tsXR/8PH + 0P/4wsD/9b26/+Cppu2peXdiAAAAAQAAAABYKCgHn2lqe6eCguSsgoL90pKS//Cxrv/TrcP/s5y+/8i3 + s/+quab/26mh/82UktSgbm1TBAAAAwAAAACud3cEvYGBC7N6ehyyfHtyt39+3bNub9vLgYH05qak/+Kg + g//OlH39jZR04Zd0aYmDT1EiAAAAAAAAAAAAAAAAr3t7D7aCgki5h4Z8uImJgah+fUltPz8ajU1ORq1s + bI6vdHOgm2RkaYxJUiZgCygCAAAAAAAAAAAAAAAAAAAAAGo9PQF9UVEHcEdHCTodHQIAAAAAAAAAAAAA + AAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADh/wAAwf8AAMH/ + AACB/wAAgfkAAIDAAACAAAAAgAAAAIAAAACAAQAAAAcAAAAPAAAOfwAA//8AAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/movie/SubtitleMaker.Designer.cs b/BizHawk.Client.EmuHawk/movie/SubtitleMaker.Designer.cs index df23542d0d..f9640b8f53 100644 --- a/BizHawk.Client.EmuHawk/movie/SubtitleMaker.Designer.cs +++ b/BizHawk.Client.EmuHawk/movie/SubtitleMaker.Designer.cs @@ -179,7 +179,7 @@ // this.FrameNumeric.Location = new System.Drawing.Point(78, 19); this.FrameNumeric.Maximum = new decimal(new int[] { - 999999, + 2147483647, 0, 0, 0}); diff --git a/BizHawk.Client.EmuHawk/tools/CDL.Designer.cs b/BizHawk.Client.EmuHawk/tools/CDL.designer.cs similarity index 100% rename from BizHawk.Client.EmuHawk/tools/CDL.Designer.cs rename to BizHawk.Client.EmuHawk/tools/CDL.designer.cs diff --git a/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs b/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs index a509b57639..82b3f53de1 100644 --- a/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs +++ b/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs @@ -235,6 +235,28 @@ namespace BizHawk.Client.EmuHawk return str.Select(Convert.ToByte).ToArray(); } + public byte[] ConvertHexStringToByteArray(string str) + { + if (string.IsNullOrWhiteSpace(str)) { + return new byte[0]; + } + + // TODO: Better method of handling this? + if (str.Length % 2 == 1) + { + str += "0"; + } + + byte[] bytes = new byte[str.Length / 2]; + + for (int i = 0; i < str.Length; i += 2) + { + bytes[i / 2] = Convert.ToByte(str.Substring(i, 2), 16); + } + + return bytes; + } + public void FindNext(string value, bool wrap) { long found = -1; @@ -261,16 +283,20 @@ namespace BizHawk.Client.EmuHawk startByte = _addressHighlighted + DataSize; } + byte[] searchBytes = ConvertHexStringToByteArray(search); for (var i = startByte; i < (_domain.Size - numByte); i++) { - var ramblock = new StringBuilder(); + bool differenceFound = false; for (var j = 0; j < numByte; j++) { - ramblock.Append(string.Format("{0:X2}", (int)_domain.PeekByte(i + j))); + if (_domain.PeekByte(i + j) != searchBytes[j]) + { + differenceFound = true; + break; + } } - var block = ramblock.ToString().ToUpper(); - if (search == block) + if (!differenceFound) { found = i; break; @@ -313,16 +339,19 @@ namespace BizHawk.Client.EmuHawk startByte = _addressHighlighted - 1; } + byte[] searchBytes = ConvertHexStringToByteArray(search); for (var i = startByte; i >= 0; i--) { - var ramblock = new StringBuilder(); + bool differenceFound = false; for (var j = 0; j < numByte; j++) { - ramblock.Append(string.Format("{0:X2}", (int)_domain.PeekByte(i + j))); + if (_domain.PeekByte(i + j) != searchBytes[j]) { + differenceFound = true; + break; + } } - var block = ramblock.ToString().ToUpper(); - if (search == block) + if (!differenceFound) { found = i; break; diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs index 7a20625169..d59f34b9c8 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs @@ -36,18 +36,21 @@ namespace BizHawk.Client.EmuHawk public override string Name => "client"; + [LuaMethodExample("client.exit( );")] [LuaMethod("exit", "Closes the emulator")] public void CloseEmulator() { GlobalWin.MainForm.CloseEmulator(); } + [LuaMethodExample("client.exitCode( 0 );")] [LuaMethod("exitCode", "Closes the emulator and returns the provided code")] public void CloseEmulatorWithCode(int exitCode) { GlobalWin.MainForm.CloseEmulator(exitCode); } + [LuaMethodExample("local inclibor = client.borderheight( );")] [LuaMethod("borderheight", "Gets the current height in pixels of the letter/pillarbox area (top side only) around the emu display surface, excluding the gameExtraPadding you've set. This function (the whole lot of them) should be renamed or refactored since the padding areas have got more complex.")] public static int BorderHeight() { @@ -55,6 +58,7 @@ namespace BizHawk.Client.EmuHawk return GlobalWin.DisplayManager.TransformPoint(point).Y; } + [LuaMethodExample("local inclibor = client.borderwidth( );")] [LuaMethod("borderwidth", "Gets the current width in pixels of the letter/pillarbox area (left side only) around the emu display surface, excluding the gameExtraPadding you've set. This function (the whole lot of them) should be renamed or refactored since the padding areas have got more complex.")] public static int BorderWidth() { @@ -62,36 +66,42 @@ namespace BizHawk.Client.EmuHawk return GlobalWin.DisplayManager.TransformPoint(point).X; } + [LuaMethodExample("local inclibuf = client.bufferheight( );")] [LuaMethod("bufferheight", "Gets the visible height of the emu display surface (the core video output). This excludes the gameExtraPadding you've set.")] public int BufferHeight() { return VideoProvider.BufferHeight; } + [LuaMethodExample("local inclibuf = client.bufferwidth( );")] [LuaMethod("bufferwidth", "Gets the visible width of the emu display surface (the core video output). This excludes the gameExtraPadding you've set.")] public int BufferWidth() { return VideoProvider.BufferWidth; } + [LuaMethodExample("client.clearautohold( );")] [LuaMethod("clearautohold", "Clears all autohold keys")] public void ClearAutohold() { GlobalWin.MainForm.ClearHolds(); } + [LuaMethodExample("client.closerom( );")] [LuaMethod("closerom", "Closes the loaded Rom")] public static void CloseRom() { GlobalWin.MainForm.CloseRom(); } + [LuaMethodExample("client.enablerewind( true );")] [LuaMethod("enablerewind", "Sets whether or not the rewind feature is enabled")] public void EnableRewind(bool enabled) { GlobalWin.MainForm.EnableRewind(enabled); } + [LuaMethodExample("client.frameskip( 8 );")] [LuaMethod("frameskip", "Sets the frame skip value of the client UI")] public void FrameSkip(int numFrames) { @@ -106,18 +116,21 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local incliget = client.gettargetscanlineintensity( );")] [LuaMethod("gettargetscanlineintensity", "Gets the current scanline intensity setting, used for the scanline display filter")] public static int GetTargetScanlineIntensity() { return Global.Config.TargetScanlineFilterIntensity; } + [LuaMethodExample("local incliget = client.getwindowsize( );")] [LuaMethod("getwindowsize", "Gets the main window's size Possible values are 1, 2, 3, 4, 5, and 10")] public int GetWindowSize() { return Global.Config.TargetZoomFactors[Emulator.SystemId]; } + [LuaMethodExample("client.SetGameExtraPadding( 5, 10, 15, 20 );")] [LuaMethod("SetGameExtraPadding", "Sets the extra padding added to the 'emu' surface so that you can draw HUD elements in predictable placements")] public static void SetGameExtraPadding(int left, int top, int right, int bottom) { @@ -125,18 +138,21 @@ namespace BizHawk.Client.EmuHawk GlobalWin.MainForm.FrameBufferResized(); } + [LuaMethodExample("client.SetSoundOn( true );")] [LuaMethod("SetSoundOn", "Sets the state of the Sound On toggle")] public static void SetSoundOn(bool enable) { Global.Config.SoundEnabled = enable; } + [LuaMethodExample("if ( client.GetSoundOn( ) ) then\r\n\tconsole.log( \"Gets the state of the Sound On toggle\" );\r\nend;")] [LuaMethod("GetSoundOn", "Gets the state of the Sound On toggle")] public static bool GetSoundOn() { return Global.Config.SoundEnabled; } + [LuaMethodExample("client.SetClientExtraPadding( 5, 10, 15, 20 );")] [LuaMethod("SetClientExtraPadding", "Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements")] public static void SetClientExtraPadding(int left, int top, int right, int bottom) { @@ -144,72 +160,99 @@ namespace BizHawk.Client.EmuHawk GlobalWin.MainForm.FrameBufferResized(); } + [LuaMethodExample("if ( client.ispaused( ) ) then\r\n\tconsole.log( \"Returns true if emulator is paused, otherwise, false\" );\r\nend;")] [LuaMethod("ispaused", "Returns true if emulator is paused, otherwise, false")] public static bool IsPaused() { return GlobalWin.MainForm.EmulatorPaused; } + [LuaMethodExample("if ( client.client.isturbo( ) ) then\r\n\tconsole.log( \"Returns true if emulator is in turbo mode, otherwise, false\" );\r\nend;")] + [LuaMethod("isturbo", "Returns true if emulator is in turbo mode, otherwise, false")] + public static bool IsTurbo() + { + return GlobalWin.MainForm.IsTurboing; + } + + [LuaMethodExample("if ( client.isseeking( ) ) then\r\n\tconsole.log( \"Returns true if emulator is seeking, otherwise, false\" );\r\nend;")] + [LuaMethod("isseeking", "Returns true if emulator is seeking, otherwise, false")] + public static bool IsSeeking() + { + return GlobalWin.MainForm.IsSeeking; + } + + [LuaMethodExample("client.opencheats( );")] [LuaMethod("opencheats", "opens the Cheats dialog")] public static void OpenCheats() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.openhexeditor( );")] [LuaMethod("openhexeditor", "opens the Hex Editor dialog")] public static void OpenHexEditor() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.openramwatch( );")] [LuaMethod("openramwatch", "opens the RAM Watch dialog")] public static void OpenRamWatch() { GlobalWin.Tools.LoadRamWatch(loadDialog: true); } + [LuaMethodExample("client.openramsearch( );")] [LuaMethod("openramsearch", "opens the RAM Search dialog")] public static void OpenRamSearch() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.openrom( \"C:\\\" );")] [LuaMethod("openrom", "opens the Open ROM dialog")] public static void OpenRom(string path) { - GlobalWin.MainForm.LoadRom(path, new MainForm.LoadRomArgs { OpenAdvanced = new OpenAdvanced_OpenRom() }); + var ioa = OpenAdvancedSerializer.ParseWithLegacy(path); + GlobalWin.MainForm.LoadRom(path, new MainForm.LoadRomArgs { OpenAdvanced = ioa }); } + [LuaMethodExample("client.opentasstudio( );")] [LuaMethod("opentasstudio", "opens the TAStudio dialog")] public static void OpenTasStudio() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.opentoolbox( );")] [LuaMethod("opentoolbox", "opens the Toolbox Dialog")] public static void OpenToolBox() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.opentracelogger( );")] [LuaMethod("opentracelogger", "opens the tracelogger if it is available for the given core")] public static void OpenTraceLogger() { GlobalWin.Tools.Load(); } + [LuaMethodExample("client.pause( );")] [LuaMethod("pause", "Pauses the emulator")] public static void Pause() { GlobalWin.MainForm.PauseEmulator(); } + [LuaMethodExample("client.pause_av( );")] [LuaMethod("pause_av", "If currently capturing Audio/Video, this will suspend the record. Frames will not be captured into the AV until client.unpause_av() is called")] public static void PauseAv() { GlobalWin.MainForm.PauseAvi = true; } + [LuaMethodExample("client.reboot_core( );")] [LuaMethod("reboot_core", "Reboots the currently loaded core")] public static void RebootCore() { @@ -218,12 +261,14 @@ namespace BizHawk.Client.EmuHawk ((LuaConsole)GlobalWin.Tools.Get()).LuaImp.IsRebootingCore = false; } + [LuaMethodExample("local incliscr = client.screenheight( );")] [LuaMethod("screenheight", "Gets the current height in pixels of the emulator's drawing area")] public static int ScreenHeight() { return GlobalWin.MainForm.PresentationPanel.NativeSize.Height; } + [LuaMethodExample("client.screenshot( \"C:\\\" );")] [LuaMethod("screenshot", "if a parameter is passed it will function as the Screenshot As menu item of EmuHawk, else it will function as the Screenshot menu item")] public static void Screenshot(string path = null) { @@ -237,30 +282,35 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("client.screenshottoclipboard( );")] [LuaMethod("screenshottoclipboard", "Performs the same function as EmuHawk's Screenshot To Clipboard menu item")] public static void ScreenshotToClipboard() { GlobalWin.MainForm.TakeScreenshotToClipboard(); } + [LuaMethodExample("client.settargetscanlineintensity( -1000 );")] [LuaMethod("settargetscanlineintensity", "Sets the current scanline intensity setting, used for the scanline display filter")] public static void SetTargetScanlineIntensity(int val) { Global.Config.TargetScanlineFilterIntensity = val; } + [LuaMethodExample("client.setscreenshotosd( true );")] [LuaMethod("setscreenshotosd", "Sets the screenshot Capture OSD property of the client")] public static void SetScreenshotOSD(bool value) { Global.Config.Screenshot_CaptureOSD = value; } + [LuaMethodExample("local incliscr = client.screenwidth( );")] [LuaMethod("screenwidth", "Gets the current width in pixels of the emulator's drawing area")] public static int ScreenWidth() { return GlobalWin.MainForm.PresentationPanel.NativeSize.Width; } + [LuaMethodExample("client.setwindowsize( 100 );")] [LuaMethod("setwindowsize", "Sets the main window's size to the give value. Accepted values are 1, 2, 3, 4, 5, and 10")] public void SetWindowSize(int size) { @@ -276,6 +326,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("client.speedmode( 75 );")] [LuaMethod("speedmode", "Sets the speed of the emulator (in terms of percent)")] public void SpeedMode(int percent) { @@ -289,12 +340,14 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("client.togglepause( );")] [LuaMethod("togglepause", "Toggles the current pause state")] public static void TogglePause() { GlobalWin.MainForm.TogglePause(); } - + + [LuaMethodExample("local inclitra = client.transformPointX( 16 );")] [LuaMethod("transformPointX", "Transforms an x-coordinate in emulator space to an x-coordinate in client space")] public static int TransformPointX(int x) { @@ -302,6 +355,7 @@ namespace BizHawk.Client.EmuHawk return GlobalWin.DisplayManager.TransformPoint(point).X; } + [LuaMethodExample("local inclitra = client.transformPointY( 32 );")] [LuaMethod("transformPointY", "Transforms an y-coordinate in emulator space to an y-coordinate in client space")] public static int TransformPointY(int y) { @@ -309,30 +363,35 @@ namespace BizHawk.Client.EmuHawk return GlobalWin.DisplayManager.TransformPoint(point).Y; } + [LuaMethodExample("client.unpause( );")] [LuaMethod("unpause", "Unpauses the emulator")] public static void Unpause() { GlobalWin.MainForm.UnpauseEmulator(); } + [LuaMethodExample("client.unpause_av( );")] [LuaMethod("unpause_av", "If currently capturing Audio/Video this resumes capturing")] public static void UnpauseAv() { GlobalWin.MainForm.PauseAvi = false; } + [LuaMethodExample("local inclixpo = client.xpos( );")] [LuaMethod("xpos", "Returns the x value of the screen position where the client currently sits")] public static int Xpos() { return GlobalWin.MainForm.DesktopLocation.X; } + [LuaMethodExample("local incliypo = client.ypos( );")] [LuaMethod("ypos", "Returns the y value of the screen position where the client currently sits")] public static int Ypos() { return GlobalWin.MainForm.DesktopLocation.Y; } + [LuaMethodExample("local nlcliget = client.getavailabletools( );")] [LuaMethod("getavailabletools", "Returns a list of the tools currently open")] public LuaTable GetAvailableTools() { @@ -346,10 +405,11 @@ namespace BizHawk.Client.EmuHawk return t; } + [LuaMethodExample("local nlcliget = client.gettool( \"Tool name\" );")] [LuaMethod("gettool", "Returns an object that represents a tool of the given name (not case sensitive). If the tool is not open, it will be loaded if available. Use gettools to get a list of names")] public LuaTable GetTool(string name) { - var toolType = ReflectionUtil.GetTypeByName(name) + var toolType = ReflectionUtil.GetTypeByName(name) .FirstOrDefault(x => typeof(IToolForm).IsAssignableFrom(x) && !x.IsInterface); if (toolType != null) @@ -368,6 +428,7 @@ namespace BizHawk.Client.EmuHawk return null; } + [LuaMethodExample("local nlclicre = client.createinstance( \"objectname\" );")] [LuaMethod("createinstance", "returns a default instance of the given type of object if it exists (not case sensitive). Note: This will only work on objects which have a parameterless constructor. If no suitable type is found, or the type does not have a parameterless constructor, then nil is returned")] public LuaTable CreateInstance(string name) { @@ -382,12 +443,14 @@ namespace BizHawk.Client.EmuHawk return null; } + [LuaMethodExample("client.displaymessages( true );")] [LuaMethod("displaymessages", "sets whether or not on screen messages will display")] public void DisplayMessages(bool value) { Global.Config.DisplayMessages = value; } + [LuaMethodExample("client.saveram( );")] [LuaMethod("saveram", "flushes save ram to disk")] public void SaveRam() { diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Communication.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Communication.cs new file mode 100644 index 0000000000..fdae9f132d --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Communication.cs @@ -0,0 +1,161 @@ +using System; +using System.ComponentModel; +using NLua; +using BizHawk.Emulation.Common; +using BizHawk.Client.Common; +using System.Text; +using System.Collections.Generic; +using System.Net.Http; +using System.Windows.Forms; + + +namespace BizHawk.Client.EmuHawk +{ + [Description("A library for communicating with other programs")] + public sealed class CommunicationLuaLibrary : LuaLibraryBase + { + [RequiredService] + private IEmulator Emulator { get; set; } + + [RequiredService] + private IVideoProvider VideoProvider { get; set; } + + public CommunicationLuaLibrary(Lua lua) + : base(lua) { } + + public CommunicationLuaLibrary(Lua lua, Action logOutputCallback) + : base(lua, logOutputCallback) { } + + public override string Name => "comm"; + + //TO DO: not fully working yet! + [LuaMethod("getluafunctionslist", "returns a list of implemented functions")] + public static string GetLuaFunctionsList() + { + var list = new StringBuilder(); + foreach (var function in typeof(CommunicationLuaLibrary).GetMethods()) + { + list.AppendLine(function.ToString()); + } + + return list.ToString(); + } + + [LuaMethod("socketServerScreenShot", "sends a screenshot to the Socket server")] + public string SocketServerScreenShot() + { + return GlobalWin.socketServer.SendScreenshot(); + } + [LuaMethod("socketServerScreenShotResponse", "sends a screenshot to the Socket server and retrieves the response")] + public string SocketServerScreenShotResponse() + { + return GlobalWin.socketServer.SendScreenshot(1000).ToString(); + } + + [LuaMethod("socketServerSend", "sends a string to the Socket server")] + public string SocketServerSend(string SendString) + { + return "Sent : " + GlobalWin.socketServer.SendString(SendString).ToString() + " bytes"; + } + [LuaMethod("socketServerResponse", "receives a message from the Socket server")] + public string SocketServerResponse() + { + return GlobalWin.socketServer.ReceiveMessage(); + } + + [LuaMethod("socketServerSuccessful", "returns the status of the last Socket server action")] + public bool SocketServerSuccessful() + { + return GlobalWin.socketServer.Successful(); + } + [LuaMethod("socketServerSetTimeout", "sets the timeout in milliseconds for receiving messages")] + public void SocketServerSetTimeout(int timeout) + { + GlobalWin.socketServer.SetTimeout(timeout); + } + // All MemoryMappedFile related methods + [LuaMethod("mmfSetFilename", "Sets the filename for the screenshots")] + public void MmfSetFilename(string filename) + { + GlobalWin.memoryMappedFiles.SetFilename(filename); + } + [LuaMethod("mmfGetFilename", "Gets the filename for the screenshots")] + public string MmfSetFilename() + { + return GlobalWin.memoryMappedFiles.GetFilename(); + } + + [LuaMethod("mmfScreenshot", "Saves screenshot to memory mapped file")] + public int MmfScreenshot() + { + return GlobalWin.memoryMappedFiles.ScreenShotToFile(); + } + + [LuaMethod("mmfWrite", "Writes a string to a memory mapped file")] + public int MmfWrite(string mmf_filename, string outputString) + { + return GlobalWin.memoryMappedFiles.WriteToFile(mmf_filename, Encoding.ASCII.GetBytes(outputString)); + } + [LuaMethod("mmfRead", "Reads a string from a memory mapped file")] + public string MmfRead(string mmf_filename, int expectedSize) + { + return GlobalWin.memoryMappedFiles.ReadFromFile(mmf_filename, expectedSize).ToString(); + } + // All HTTP related methods + [LuaMethod("httpTest", "tests HTTP connections")] + public string HttpTest() + { + var list = new StringBuilder(); + list.AppendLine(GlobalWin.httpCommunication.TestGet()); + list.AppendLine(GlobalWin.httpCommunication.SendScreenshot()); + list.AppendLine("done testing"); + return list.ToString(); + } + [LuaMethod("httpTestGet", "tests the HTTP GET connection")] + public string HttpTestGet() + { + return GlobalWin.httpCommunication.TestGet(); + } + [LuaMethod("httpGet", "makes a HTTP GET request")] + public string HttpGet(string url) + { + return GlobalWin.httpCommunication.ExecGet(url); + } + + [LuaMethod("httpPost", "makes a HTTP POST request")] + public string HttpPost(string url, string payload) + { + return GlobalWin.httpCommunication.ExecPost(url, payload); + } + [LuaMethod("httpPostScreenshot", "HTTP POST screenshot")] + public string HttpPostScreenshot() + { + return GlobalWin.httpCommunication.SendScreenshot(); + } + [LuaMethod("httpSetTimeout", "Sets HTTP timeout in milliseconds")] + public void HttpSetTimeout(int timeout) + { + GlobalWin.httpCommunication.SetTimeout(timeout); + } + [LuaMethod("httpSetPostUrl", "Sets HTTP POST URL")] + public void HttpSetPostUrl(string url) + { + GlobalWin.httpCommunication.SetPostUrl(url); + } + [LuaMethod("httpSetGetUrl", "Sets HTTP GET URL")] + public void HttpSetGetUrl(string url) + { + GlobalWin.httpCommunication.SetGetUrl(url); + } + [LuaMethod("httpGetPostUrl", "Gets HTTP POST URL")] + public string HttpGetPostUrl() + { + return GlobalWin.httpCommunication.GetPostUrl(); + } + [LuaMethod("httpGetGetUrl", "Gets HTTP GET URL")] + public string HttpGetGetUrl() + { + return GlobalWin.httpCommunication.GetGetUrl(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Console.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Console.cs index 8541e6fc28..93aa46f4fa 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Console.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Console.cs @@ -18,6 +18,7 @@ namespace BizHawk.Client.EmuHawk public override string Name => "console"; + [LuaMethodExample("console.clear( );")] [LuaMethod("clear", "clears the output box of the Lua Console window")] public static void Clear() { @@ -27,6 +28,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local stconget = console.getluafunctionslist( );")] [LuaMethod("getluafunctionslist", "returns a list of implemented functions")] public static string GetLuaFunctionsList() { @@ -39,6 +41,7 @@ namespace BizHawk.Client.EmuHawk return list.ToString(); } + [LuaMethodExample("console.log( \"New log.\" );")] [LuaMethod("log", "Outputs the given object to the output box on the Lua Console dialog. Note: Can accept a LuaTable")] public static void Log(params object[] outputs) { @@ -57,6 +60,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("console.writeline( \"New log line.\" );")] [LuaMethod("writeline", "Outputs the given object to the output box on the Lua Console dialog. Note: Can accept a LuaTable")] public static void WriteLine(params object[] outputs) { @@ -66,6 +70,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("console.write( \"New log message.\" );")] [LuaMethod("write", "Outputs the given object to the output box on the Lua Console dialog. Note: Can accept a LuaTable")] public static void Write(params object[] outputs) { diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs index a6f7933ac3..e73c85da03 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs @@ -62,6 +62,7 @@ namespace BizHawk.Client.EmuHawk #endregion + [LuaMethodExample("forms.addclick( 332, function()\r\n\tconsole.log( \"adds the given lua function as a click event to the given control\" );\r\nend );")] [LuaMethod("addclick", "adds the given lua function as a click event to the given control")] public void AddClick(int handle, LuaFunction clickEvent) { @@ -78,6 +79,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local inforbut = forms.button( 333, \"Caption\", function()\r\n\tconsole.log( \"Creates a button control on the given form. The caption property will be the text value on the button. clickEvent is the name of a Lua function that will be invoked when the button is clicked. x, and y are the optional location parameters for the position of the button within the given form. The function returns the handle of the created button. Width and Height are optional, if not specified they will be a default size\" );\r\nend, 2, 48, 18, 24 );")] [LuaMethod( "button", "Creates a button control on the given form. The caption property will be the text value on the button. clickEvent is the name of a Lua function that will be invoked when the button is clicked. x, and y are the optional location parameters for the position of the button within the given form. The function returns the handle of the created button. Width and Height are optional, if not specified they will be a default size")] public int Button( @@ -113,6 +115,7 @@ namespace BizHawk.Client.EmuHawk return (int)button.Handle; } + [LuaMethodExample("local inforche = forms.checkbox( 333, \"Caption\", 2, 48 );")] [LuaMethod( "checkbox", "Creates a checkbox control on the given form. The caption property will be the text of the checkbox. x and y are the optional location parameters for the position of the checkbox within the form")] public int Checkbox(int formHandle, string caption, int? x = null, int? y = null) @@ -135,6 +138,7 @@ namespace BizHawk.Client.EmuHawk return (int)checkbox.Handle; } + [LuaMethodExample("forms.clearclicks( 332 );")] [LuaMethod("clearclicks", "Removes all click events from the given widget at the specified handle")] public void ClearClicks(int handle) { @@ -155,6 +159,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("if ( forms.destroy( 332 ) ) then\r\n\tconsole.log( \"Closes and removes a Lua created form with the specified handle. If a dialog was found and removed true is returned, else false\" );\r\nend;")] [LuaMethod("destroy", "Closes and removes a Lua created form with the specified handle. If a dialog was found and removed true is returned, else false")] public bool Destroy(int handle) { @@ -172,6 +177,7 @@ namespace BizHawk.Client.EmuHawk return false; } + [LuaMethodExample("forms.destroyall();")] [LuaMethod("destroyall", "Closes and removes all Lua created dialogs")] public void DestroyAll() { @@ -181,6 +187,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local infordro = forms.dropdown(333, { \"item 1\", \"item2\" }, 2, 48, 18, 24);")] [LuaMethod( "dropdown", "Creates a dropdown (with a ComboBoxStyle of DropDownList) control on the given form. Dropdown items are passed via a lua table. Only the values will be pulled for the dropdown items, the keys are irrelevant. Items will be sorted alphabetically. x and y are the optional location parameters, and width and height are the optional size parameters.")] public int Dropdown( @@ -216,6 +223,7 @@ namespace BizHawk.Client.EmuHawk return (int)dropdown.Handle; } + [LuaMethodExample("local stforget = forms.getproperty(332, \"Property\");")] [LuaMethod("getproperty", "returns a string representation of the value of a property of the widget at the given handle")] public string GetProperty(int handle, string property) { @@ -246,6 +254,7 @@ namespace BizHawk.Client.EmuHawk return ""; } + [LuaMethodExample("local stforget = forms.gettext(332);")] [LuaMethod("gettext", "Returns the text property of a given form or control")] public string GetText(int handle) { @@ -281,6 +290,7 @@ namespace BizHawk.Client.EmuHawk return ""; } + [LuaMethodExample("if ( forms.ischecked( 332 ) ) then\r\n\tconsole.log( \"Returns the given checkbox's checked property\" );\r\nend;")] [LuaMethod("ischecked", "Returns the given checkbox's checked property")] public bool IsChecked(int handle) { @@ -309,6 +319,7 @@ namespace BizHawk.Client.EmuHawk return false; } + [LuaMethodExample("local inforlab = forms.label( 333, \"Caption\", 2, 48, 18, 24, false );")] [LuaMethod( "label", "Creates a label control on the given form. The caption property is the text of the label. x, and y are the optional location parameters for the position of the label within the given form. The function returns the handle of the created label. Width and Height are optional, if not specified they will be a default size.")] public int Label( @@ -348,6 +359,7 @@ namespace BizHawk.Client.EmuHawk return (int)label.Handle; } + [LuaMethodExample("local infornew = forms.newform( 18, 24, \"Title\", function()\r\n\tconsole.log( \"creates a new default dialog, if both width and height are specified it will create a dialog of the specified size. If title is specified it will be the caption of the dialog, else the dialog caption will be 'Lua Dialog'. The function will return an int representing the handle of the dialog created.\" );\r\nend );")] [LuaMethod( "newform", "creates a new default dialog, if both width and height are specified it will create a dialog of the specified size. If title is specified it will be the caption of the dialog, else the dialog caption will be 'Lua Dialog'. The function will return an int representing the handle of the dialog created.")] public int NewForm(int? width = null, int? height = null, string title = null, LuaFunction onClose = null) @@ -383,6 +395,7 @@ namespace BizHawk.Client.EmuHawk return (int)form.Handle; } + [LuaMethodExample("local stforope = forms.openfile( \"C:\\filename.bin\", \"C:\\\", \"All files ( *.* )|*.*\");")] [LuaMethod( "openfile", "Creates a standard openfile dialog with optional parameters for the filename, directory, and filter. The return value is the directory that the user picked. If they chose to cancel, it will return an empty string")] public string OpenFile(string fileName = null, string initialDirectory = null, string filter = "All files (*.*)|*.*") @@ -413,6 +426,7 @@ namespace BizHawk.Client.EmuHawk return ""; } + [LuaMethodExample("local inforpic = forms.pictureBox( 333, 2, 48, 18, 24 );")] [LuaMethod( "pictureBox", "Creates a new drawing area in the form. Optionally the location in the form as well as the size of the drawing area can be specified. Returns the handle the component can be refered to with.")] @@ -444,6 +458,7 @@ namespace BizHawk.Client.EmuHawk #region LuaPictureBox Methods + [LuaMethodExample("forms.clear( 334, 0x000000FF );")] [LuaMethod( "clear", "Clears the canvas")] @@ -475,6 +490,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.refresh( 334 );")] [LuaMethod( "refresh", "Redraws the canvas")] @@ -506,6 +522,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.setDefaultForegroundColor( 334, 0xFFFFFFFF );")] [LuaMethod( "setDefaultForegroundColor", "Sets the default foreground color to use in drawing methods, white by default")] @@ -537,6 +554,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.setDefaultBackgroundColor( 334, 0x000000FF );")] [LuaMethod( "setDefaultBackgroundColor", "Sets the default background color to use in drawing methods, transparent by default")] @@ -568,6 +586,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.setDefaultTextBackground( 334, 0x000000FF );")] [LuaMethod( "setDefaultTextBackground", "Sets the default backgroiund color to use in text drawing methods, half-transparent black by default")] @@ -599,6 +618,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawBezier( 334, { { 5, 10 }, { 10, 10 }, { 10, 20 }, { 5, 20 } }, 0x000000FF );")] [LuaMethod( "drawBezier", "Draws a Bezier curve using the table of coordinates provided in the given color")] @@ -630,6 +650,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawBox( 334, 16, 32, 162, 322, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawBox", "Draws a rectangle on screen from x1/y1 to x2/y2. Same as drawRectangle except it receives two points intead of a point and width/height")] @@ -661,6 +682,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawEllipse( 334, 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawEllipse", "Draws an ellipse at the given coordinates and the given width and height. Line is the color of the ellipse. Background is the optional fill color")] @@ -692,6 +714,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawIcon( 334, \"C:\\icon.ico\", 16, 32, 18, 24 );")] [LuaMethod( "drawIcon", "draws an Icon (.ico) file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] @@ -723,6 +746,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawImage( 334, \"C:\\image.png\", 16, 32, 18, 24, false );")] [LuaMethod( "drawImage", "draws an image file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] @@ -759,6 +783,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.clearImageCache( 334 );")] [LuaMethod( "clearImageCache", "clears the image cache that is built up by using gui.drawImage, also releases the file handle for cached images")] @@ -790,6 +815,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawImageRegion( 334, \"C:\\image.bmp\", 11, 22, 33, 44, 21, 43, 34, 45 );")] [LuaMethod( "drawImageRegion", "draws a given region of an image file from the given path at the given coordinate, and optionally with the given size")] @@ -826,6 +852,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawLine( 334, 161, 321, 162, 322, 0xFFFFFFFF );")] [LuaMethod( "drawLine", "Draws a line from the first coordinate pair to the 2nd. Color is optional (if not specified it will be drawn black)")] @@ -857,6 +884,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawAxis( 334, 16, 32, int size, 0xFFFFFFFF );")] [LuaMethod( "drawAxis", "Draws an axis of the specified size at the coordinate pair.)")] @@ -888,6 +916,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawArc( 334, 16, 32, 77, 99, 180, 90, 0x007F00FF );")] [LuaMethod( "drawArc", "draws a Arc shape at the given coordinates and the given width and height" @@ -920,6 +949,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawPie( 334, 16, 32, 77, 99, 180, 90, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawPie", "draws a Pie shape at the given coordinates and the given width and height")] @@ -960,6 +990,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawPixel( 334, 16, 32, 0xFFFFFFFF );")] [LuaMethod( "drawPixel", "Draws a single pixel at the given coordinates in the given color. Color is optional (if not specified it will be drawn black)")] @@ -991,6 +1022,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawPolygon( 334, { { 5, 10 }, { 10, 10 }, { 10, 20 }, { 5, 20 } }, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawPolygon", "Draws a polygon using the table of coordinates specified in points. This should be a table of tables(each of size 2). Line is the color of the polygon. Background is the optional fill color")] @@ -1023,6 +1055,7 @@ namespace BizHawk.Client.EmuHawk } + [LuaMethodExample("forms.drawRectangle( 334, 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawRectangle", "Draws a rectangle at the given coordinate and the given width and height. Line is the color of the box. Background is the optional fill color")] @@ -1054,6 +1087,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawString( 334, 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod( "drawString", "Alias of DrawText()")] @@ -1096,6 +1130,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.drawText( 334, 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod( "drawText", "Draws the given message at the given x,y coordinates and the given color. The default color is white. A fontfamily can be specified and is monospace generic if none is specified (font family options are the same as the .NET FontFamily class). The fontsize default is 12. The default font style is regular. Font style options are regular, bold, italic, strikethrough, underline. Horizontal alignment options are left (default), center, or right. Vertical alignment options are bottom (default), middle, or top. Alignment options specify which ends of the text will be drawn at the x and y coordinates.")] @@ -1139,6 +1174,7 @@ namespace BizHawk.Client.EmuHawk } // It'd be great if these were simplified into 1 function, but I cannot figure out how to return a LuaTable from this class + [LuaMethodExample("local inforget = forms.getMouseX( 334 );")] [LuaMethod( "getMouseX", "Returns an integer representation of the mouse X coordinate relative to the PictureBox.")] @@ -1173,6 +1209,7 @@ namespace BizHawk.Client.EmuHawk return 0; } + [LuaMethodExample("local inforget = forms.getMouseY( 334 );")] [LuaMethod( "getMouseY", "Returns an integer representation of the mouse Y coordinate relative to the PictureBox.")] @@ -1209,6 +1246,7 @@ namespace BizHawk.Client.EmuHawk #endregion + [LuaMethodExample("forms.setdropdownitems( 332, { \"item1\", \"item2\" } );")] [LuaMethod("setdropdownitems", "Sets the items for a given dropdown box")] public void SetDropdownItems(int handle, LuaTable items) { @@ -1244,6 +1282,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.setlocation( 332, 16, 32 );")] [LuaMethod("setlocation", "Sets the location of a control or form by passing in the handle of the created object")] public void SetLocation(int handle, int x, int y) { @@ -1267,6 +1306,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.setproperty( 332, \"Property\", \"Property value\" );")] [LuaMethod("setproperty", "Attempts to set the given property of the widget with the given value. Note: not all properties will be able to be represented for the control to accept")] public void SetProperty(int handle, string property, object value) { @@ -1322,12 +1362,14 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local coforcre = forms.createcolor( 0x7F, 0x3F, 0x1F, 0xCF );")] [LuaMethod("createcolor", "Creates a color object useful with setproperty")] public Color CreateColor(int r, int g, int b, int a) { return Color.FromArgb(a, r, g, b); } + [LuaMethodExample("forms.setsize( 332, 77, 99 );")] [LuaMethod("setsize", "TODO")] public void SetSize(int handle, int width, int height) { @@ -1351,6 +1393,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("forms.settext( 332, \"Caption\" );")] [LuaMethod("settext", "Sets the text property of a control or form by passing in the handle of the created object")] public void Settext(int handle, string caption) { @@ -1374,6 +1417,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local infortex = forms.textbox( 333, \"Caption\", 18, 24, \"HEX\", 2, 48, true, false, \"Both\" );")] [LuaMethod( "textbox", "Creates a textbox control on the given form. The caption property will be the initial value of the textbox (default is empty). Width and Height are option, if not specified they will be a default size of 100, 20. Type is an optional property to restrict the textbox input. The available options are HEX, SIGNED, and UNSIGNED. Passing it null or any other value will set it to no restriction. x, and y are the optional location parameters for the position of the textbox within the given form. The function returns the handle of the created textbox. If true, the multiline will enable the standard winform multi-line property. If true, the fixedWidth options will create a fixed width font. Scrollbars is an optional property to specify which scrollbars to display. The available options are Vertical, Horizontal, Both, and None. Scrollbars are only shown on a multiline textbox")] public int Textbox( diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Gui.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Gui.cs index 96685e56af..105ac74ee9 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Gui.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Gui.cs @@ -45,6 +45,7 @@ namespace BizHawk.Client.EmuHawk public bool SurfaceIsNull => _luaSurface == null; + [LuaMethodExample("gui.DrawNew( \"native\", false );")] [LuaMethod("DrawNew", "Changes drawing target to the specified lua surface name. This may clobber any previous drawing to this surface (pass false if you don't want it to)")] public void DrawNew(string name, bool? clear = true) { @@ -59,6 +60,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.DrawFinish( );")] [LuaMethod("DrawFinish", "Finishes drawing to the current lua surface and causes it to get displayed.")] public void DrawFinish() { @@ -126,12 +128,14 @@ namespace BizHawk.Client.EmuHawk #endregion + [LuaMethodExample("gui.addmessage( \"Some message\" );")] [LuaMethod("addmessage", "Adds a message to the OSD's message area")] public void AddMessage(string message) { GlobalWin.OSD.AddMessage(message); } + [LuaMethodExample("gui.clearGraphics( );")] [LuaMethod("clearGraphics", "clears all lua drawn graphics from the screen")] public void ClearGraphics() { @@ -139,30 +143,35 @@ namespace BizHawk.Client.EmuHawk DrawFinish(); } + [LuaMethodExample("gui.cleartext( );")] [LuaMethod("cleartext", "clears all text created by gui.text()")] public static void ClearText() { GlobalWin.OSD.ClearGUIText(); } + [LuaMethodExample("gui.defaultForeground( 0x000000FF );")] [LuaMethod("defaultForeground", "Sets the default foreground color to use in drawing methods, white by default")] public void SetDefaultForegroundColor(Color color) { _defaultForeground = color; } + [LuaMethodExample("gui.defaultBackground( 0xFFFFFFFF );")] [LuaMethod("defaultBackground", "Sets the default background color to use in drawing methods, transparent by default")] public void SetDefaultBackgroundColor(Color color) { _defaultBackground = color; } + [LuaMethodExample("gui.defaultTextBackground( 0x000000FF );")] [LuaMethod("defaultTextBackground", "Sets the default backgroiund color to use in text drawing methods, half-transparent black by default")] public void SetDefaultTextBackground(Color color) { _defaultTextBackground = color; } + [LuaMethodExample("gui.defaultPixelFont( \"Arial Narrow\");")] [LuaMethod("defaultPixelFont", "Sets the default font to use in gui.pixelText(). Two font families are available, \"fceux\" and \"gens\" (or \"0\" and \"1\" respectively), \"gens\" is used by default")] public void SetDefaultTextBackground(string fontfamily) { @@ -182,6 +191,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawBezier( { { 5, 10 }, { 10, 10 }, { 10, 20 }, { 5, 20 } }, 0x000000FF );")] [LuaMethod("drawBezier", "Draws a Bezier curve using the table of coordinates provided in the given color")] public void DrawBezier(LuaTable points, Color color) { @@ -190,7 +200,7 @@ namespace BizHawk.Client.EmuHawk try { var pointsArr = new Point[4]; - + var i = 0; foreach (LuaTable point in points.Values) { @@ -211,6 +221,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawBox( 16, 32, 162, 322, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod("drawBox", "Draws a rectangle on screen from x1/y1 to x2/y2. Same as drawRectangle except it receives two points intead of a point and width/height")] public void DrawBox(int x, int y, int x2, int y2, Color? line = null, Color? background = null) { @@ -254,6 +265,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawEllipse( 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod("drawEllipse", "Draws an ellipse at the given coordinates and the given width and height. Line is the color of the ellipse. Background is the optional fill color")] public void DrawEllipse(int x, int y, int width, int height, Color? line = null, Color? background = null) { @@ -278,6 +290,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawIcon( \"C:\\sample.ico\", 16, 32, 18, 24 );")] [LuaMethod("drawIcon", "draws an Icon (.ico) file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] public void DrawIcon(string path, int x, int y, int? width = null, int? height = null) { @@ -304,6 +317,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawImage( \"C:\\sample.bmp\", 16, 32, 18, 24, false );")] [LuaMethod("drawImage", "draws an image file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] public void DrawImage(string path, int x, int y, int? width = null, int? height = null, bool cache = true) { @@ -333,6 +347,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.clearImageCache( );")] [LuaMethod("clearImageCache", "clears the image cache that is built up by using gui.drawImage, also releases the file handle for cached images")] public void ClearImageCache() { @@ -344,6 +359,7 @@ namespace BizHawk.Client.EmuHawk _imageCache.Clear(); } + [LuaMethodExample("gui.drawImageRegion( \"C:\\sample.png\", 11, 22, 33, 44, 21, 43, 34, 45 );")] [LuaMethod("drawImageRegion", "draws a given region of an image file from the given path at the given coordinate, and optionally with the given size")] public void DrawImageRegion(string path, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int? dest_width = null, int? dest_height = null) { @@ -372,6 +388,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawLine( 161, 321, 162, 322, 0xFFFFFFFF );")] [LuaMethod("drawLine", "Draws a line from the first coordinate pair to the 2nd. Color is optional (if not specified it will be drawn black)")] public void DrawLine(int x1, int y1, int x2, int y2, Color? color = null) { @@ -381,6 +398,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawAxis( 16, 32, 15, 0xFFFFFFFF );")] [LuaMethod("drawAxis", "Draws an axis of the specified size at the coordinate pair.)")] public void DrawAxis(int x, int y, int size, Color? color = null) { @@ -388,6 +406,7 @@ namespace BizHawk.Client.EmuHawk DrawLine(x, y + size, x, y - size, color); } + [LuaMethodExample("gui.drawPie( 16, 32, 77, 99, 180, 90, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod("drawPie", "draws a Pie shape at the given coordinates and the given width and height")] public void DrawPie( int x, @@ -412,6 +431,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawPixel( 16, 32, 0xFFFFFFFF );")] [LuaMethod("drawPixel", "Draws a single pixel at the given coordinates in the given color. Color is optional (if not specified it will be drawn black)")] public void DrawPixel(int x, int y, Color? color = null) { @@ -428,6 +448,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawPolygon( { { 5, 10 }, { 10, 10 }, { 10, 20 }, { 5, 20 } }, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod("drawPolygon", "Draws a polygon using the table of coordinates specified in points. This should be a table of tables(each of size 2). Line is the color of the polygon. Background is the optional fill color")] public void DrawPolygon(LuaTable points, Color? line = null, Color? background = null) { @@ -457,6 +478,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawRectangle( 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod("drawRectangle", "Draws a rectangle at the given coordinate and the given width and height. Line is the color of the box. Background is the optional fill color")] public void DrawRectangle(int x, int y, int width, int height, Color? line = null, Color? background = null) { @@ -471,6 +493,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.drawString( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod("drawString", "Alias of gui.drawText()")] public void DrawString( int x, @@ -487,6 +510,7 @@ namespace BizHawk.Client.EmuHawk DrawText(x, y, message, forecolor, backcolor, fontsize, fontfamily, fontstyle, horizalign, vertalign); } + [LuaMethodExample("gui.drawText( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod("drawText", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. A fontfamily can be specified and is monospace generic if none is specified (font family options are the same as the .NET FontFamily class). The fontsize default is 12. The default font style is regular. Font style options are regular, bold, italic, strikethrough, underline. Horizontal alignment options are left (default), center, or right. Vertical alignment options are bottom (default), middle, or top. Alignment options specify which ends of the text will be drawn at the x and y coordinates.")] public void DrawText( int x, @@ -582,6 +606,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.pixelText( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, \"Arial Narrow\" );")] [LuaMethod("pixelText", "Draws the given message in the emulator screen space (like all draw functions) at the given x,y coordinates and the given color. The default color is white. Two font families are available, \"fceux\" and \"gens\" (or \"0\" and \"1\" respectively), both are monospace and have the same size as in the emulaors they've been taken from. If no font family is specified, it uses \"gens\" font, unless that's overridden via gui.defaultPixelFont()")] public void DrawText( int x, @@ -636,6 +661,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("gui.text( 16, 32, \"Some message\", 0x7F0000FF, \"bottomleft\" );")] [LuaMethod("text", "Displays the given text on the screen at the given coordinates. Optional Foreground color. The optional anchor flag anchors the text to one of the four corners. Anchor flag parameters: topleft, topright, bottomleft, bottomright")] public void Text( int x, @@ -677,6 +703,7 @@ namespace BizHawk.Client.EmuHawk GlobalWin.OSD.AddGUIText(message, x, y, Color.Black, forecolor ?? Color.White, a); } + [LuaMethodExample("local nlguicre = gui.createcanvas( 77, 99, 2, 48 );")] [LuaMethod("createcanvas", "Creates a canvas of the given size and, if specified, the given coordinates.")] public LuaTable Text(int width, int height, int? x = null, int? y = null) { diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Input.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Input.cs index 2b60e2ef4d..91e8a5553f 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Input.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Input.cs @@ -17,6 +17,7 @@ namespace BizHawk.Client.EmuHawk public override string Name => "input"; + [LuaMethodExample("local nlinpget = input.get( );")] [LuaMethod("get", "Returns a lua table of all the buttons the user is currently pressing on their keyboard and gamepads\nAll buttons that are pressed have their key values set to true; all others remain nil.")] public LuaTable Get() { @@ -29,6 +30,7 @@ namespace BizHawk.Client.EmuHawk return buttons; } + [LuaMethodExample("local nlinpget = input.getmouse( );")] [LuaMethod("getmouse", "Returns a lua table of the mouse X/Y coordinates and button states. Table keys are X, Y, Left, Middle, Right, XButton1, XButton2, Wheel.")] public LuaTable GetMouse() { diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Savestate.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Savestate.cs index 0867c7de0d..b07919010d 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Savestate.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Savestate.cs @@ -17,6 +17,7 @@ namespace BizHawk.Client.EmuHawk public override string Name => "savestate"; + [LuaMethodExample("savestate.load( \"C:\\state.bin\" );")] [LuaMethod("load", "Loads a savestate with the given path")] public void Load(string path) { @@ -30,6 +31,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("savestate.loadslot( 7 );")] [LuaMethod("loadslot", "Loads the savestate at the given slot number (must be an integer between 0 and 9)")] public void LoadSlot(int slotNum) { @@ -39,12 +41,14 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("savestate.save( \"C:\\state.bin\" );")] [LuaMethod("save", "Saves a state at the given path")] public void Save(string path) { GlobalWin.MainForm.SaveState(path, path, true); } + [LuaMethodExample("savestate.saveslot( 7 );")] [LuaMethod("saveslot", "Saves a state at the given save slot (must be an integer between 0 and 9)")] public void SaveSlot(int slotNum) { diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Tastudio.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Tastudio.cs index 831cc599e8..bdba64d3a1 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Tastudio.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Tastudio.cs @@ -23,18 +23,47 @@ namespace BizHawk.Client.EmuHawk private TAStudio Tastudio => GlobalWin.Tools.Get() as TAStudio; + private struct PendingChanges + { + public LuaChangeTypes type; + public InputChangeTypes inputType; + public int frame; + public int number; + public string button; + public bool valueBool; + public float valueFloat; + }; + + public enum LuaChangeTypes + { + InputChange, + InsertFrames, + DeleteFrames, + }; + + public enum InputChangeTypes + { + Bool, + Float, + }; + + private List changeList = new List(); //TODO: Initialize it to empty list on a script reload, and have each script have it's own list + + [LuaMethodExample("if ( tastudio.engaged( ) ) then\r\n\tconsole.log( \"returns whether or not tastudio is currently engaged ( active )\" );\r\nend;")] [LuaMethod("engaged", "returns whether or not tastudio is currently engaged (active)")] public bool Engaged() { return GlobalWin.Tools.Has(); // TODO: eventually tastudio should have an engaged flag } + [LuaMethodExample("if ( tastudio.getrecording( ) ) then\r\n\tconsole.log( \"returns whether or not TAStudio is in recording mode\" );\r\nend;")] [LuaMethod("getrecording", "returns whether or not TAStudio is in recording mode")] public bool GetRecording() { return Tastudio.TasPlaybackBox.RecordingMode; } + [LuaMethodExample("tastudio.setrecording( true );")] [LuaMethod("setrecording", "sets the recording mode on/off depending on the parameter")] public void SetRecording(bool val) { @@ -44,12 +73,14 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.togglerecording( );")] [LuaMethod("togglerecording", "toggles tastudio recording mode on/off depending on its current state")] public void SetRecording() { Tastudio.ToggleReadOnly(); } + [LuaMethodExample("tastudio.setbranchtext( \"Some text\", 1 );")] [LuaMethod("setbranchtext", "adds the given message to the existing branch, or to the branch that will be created next if branch index is not specified")] public void SetBranchText(string text, int? index = null) { @@ -63,6 +94,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local sttasget = tastudio.getmarker( 500 );")] [LuaMethod("getmarker", "returns the marker text at the given frame, or an empty string if there is no marker for the given frame")] public string GetMarker(int frame) { @@ -74,10 +106,11 @@ namespace BizHawk.Client.EmuHawk return marker.Message; } } - + return ""; } + [LuaMethodExample("tastudio.removemarker( 500 );")] [LuaMethod("removemarker", "if there is a marker for the given frame, it will be removed")] public void RemoveMarker(int frame) { @@ -92,6 +125,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.setmarker( 500, \"Some message\" );")] [LuaMethod("setmarker", "Adds or sets a marker at the given frame, with an optional message")] public void SetMarker(int frame, string message = null) { @@ -110,6 +144,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("local botasisl = tastudio.islag( 500 );")] [LuaMethod("islag", "Returns whether or not the given frame was a lag frame, null if unknown")] public bool? IsLag(int frame) { @@ -124,6 +159,7 @@ namespace BizHawk.Client.EmuHawk return null; } + [LuaMethodExample("tastudio.setlag( 500, true );")] [LuaMethod("setlag", "Sets the lag information for the given frame, if the frame does not exist in the lag log, it will be added. If the value is null, the lag information for that frame will be removed")] public void SetLag(int frame, bool? value) { @@ -133,6 +169,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("if ( tastudio.hasstate( 500 ) ) then\r\n\tconsole.log( \"Returns whether or not the given frame has a savestate associated with it\" );\r\nend;")] [LuaMethod("hasstate", "Returns whether or not the given frame has a savestate associated with it")] public bool HasState(int frame) { @@ -147,6 +184,7 @@ namespace BizHawk.Client.EmuHawk return false; } + [LuaMethodExample("tastudio.setplayback( 1500 );")] [LuaMethod("setplayback", "Seeks the given frame (a number) or marker (a string)")] public void SetPlayback(object frame) { @@ -175,6 +213,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.onqueryitembg( function( currentindex, itemname )\r\n\tconsole.log( \"called during the background draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)\" );\r\nend );")] [LuaMethod("onqueryitembg", "called during the background draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)")] public void OnQueryItemBg(LuaFunction luaf) { @@ -195,6 +234,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.onqueryitemtext( function( currentindex, itemname )\r\n\tconsole.log( \"called during the text draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)\" );\r\nend );")] [LuaMethod("onqueryitemtext", "called during the text draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)")] public void OnQueryItemText(LuaFunction luaf) { @@ -209,6 +249,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.onqueryitemicon( function( currentindex, itemname )\r\n\tconsole.log( \"called during the icon draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)\" );\r\nend );")] [LuaMethod("onqueryitemicon", "called during the icon draw event of the tastudio listview. luaf must be a function that takes 2 params: index, column. The first is the integer row index of the listview, and the 2nd is the string column name. luaf should return a value that can be parsed into a .NET Color object (string color name, or integer value)")] public void OnQueryItemIcon(LuaFunction luaf) { @@ -229,6 +270,7 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.ongreenzoneinvalidated( function( currentindex, itemname )\r\n\tconsole.log( \"called whenever the greenzone is invalidated and returns the first frame that was invalidated\" );\r\nend );")] [LuaMethod("ongreenzoneinvalidated", "called whenever the greenzone is invalidated and returns the first frame that was invalidated")] public void OnGreenzoneInvalidated(LuaFunction luaf) { @@ -241,6 +283,46 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample("tastudio.ongreenzoneinvalidated( function( currentindex, itemname )\r\n\tconsole.log( \"called whenever the greenzone is invalidated and returns the first frame that was invalidated\" );\r\nend );")] + [LuaMethod("onbranchload", "called whenever a branch is loaded. luaf must be a function that takes the integer branch index as a parameter")] + public void OnBranchLoad(LuaFunction luaf) + { + if (Engaged()) + { + Tastudio.BranchLoadedCallback = (int index) => + { + luaf.Call(index); + }; + } + } + + [LuaMethodExample("")] + [LuaMethod("onbranchsave", "called whenever a branch is created or updated. luaf must be a function that takes the integer branch index as a parameter")] + public void OnBranchSave(LuaFunction luaf) + { + if (Engaged()) + { + Tastudio.BranchSavedCallback = (int index) => + { + luaf.Call(index); + }; + } + } + + [LuaMethodExample("")] + [LuaMethod("onbranchremove", "called whenever a branch is removed. luaf must be a function that takes the integer branch index as a parameter")] + public void OnBranchRemove(LuaFunction luaf) + { + if (Engaged()) + { + Tastudio.BranchRemovedCallback = (int index) => + { + luaf.Call(index); + }; + } + } + + [LuaMethodExample("local nltasget = tastudio.getselection( );")] [LuaMethod("getselection", "gets the currently selected frames")] public LuaTable GetSelection() { @@ -259,38 +341,6 @@ namespace BizHawk.Client.EmuHawk return table; } - [LuaMethod("insertframes", "inserts the given number of blank frames at the given insertion frame")] - public void InsertNumFrames(int insertionFrame, int numberOfFrames) - { - if (Engaged()) - { - if (insertionFrame < Tastudio.CurrentTasMovie.InputLogLength) - { - Tastudio.InsertNumFrames(insertionFrame, numberOfFrames); - } - else - { - Log(insertionFrame + " is out of range"); - } - } - } - - [LuaMethod("deleteframes", "deletes the given number of blank frames beginning at the given frame")] - public void DeleteFrames(int beginningFrame, int numberOfFrames) - { - if (Engaged()) - { - if (beginningFrame < Tastudio.CurrentTasMovie.InputLogLength) - { - Tastudio.DeleteFrames(beginningFrame, numberOfFrames); - } - else - { - Log(beginningFrame + " is out of range"); - } - } - } - public class TastudioBranchInfo { public string Id { get; set; } @@ -298,6 +348,7 @@ namespace BizHawk.Client.EmuHawk public string Text { get; set; } } + [LuaMethodExample("local nltasget = tastudio.getbranches( );")] [LuaMethod("getbranches", "Returns a list of the current tastudio branches. Each entry will have the Id, Frame, and Text properties of the branch")] public LuaTable GetBranches() { @@ -323,6 +374,7 @@ namespace BizHawk.Client.EmuHawk } + [LuaMethodExample("local nltasget = tastudio.getbranchinput( \"97021544-2454-4483-824f-47f75e7fcb6a\", 500 );")] [LuaMethod("getbranchinput", "Gets the controller state of the given frame with the given branch identifier")] public LuaTable GetBranchInput(string branchId, int frame) { @@ -336,7 +388,7 @@ namespace BizHawk.Client.EmuHawk if (frame < branch.InputLog.Count) { var input = branch.InputLog[frame]; - + var adapter = new Bk2ControllerAdapter { Definition = Global.MovieSession.MovieControllerAdapter.Definition @@ -359,5 +411,184 @@ namespace BizHawk.Client.EmuHawk return table; } + + [LuaMethodExample("")] + [LuaMethod("submitinputchange", "")] + public void SubmitInputChange(int frame, string button, bool value) + { + if (Engaged()) + { + if (frame >= 0) + { + PendingChanges newChange = new PendingChanges(); + + if (frame < Tastudio.CurrentTasMovie.InputLogLength) + { + if (Tastudio.CurrentTasMovie.BoolIsPressed(frame, button) != value) //Check if the button state is not already in the state the user set in the lua script + { + newChange.type = LuaChangeTypes.InputChange; + newChange.inputType = InputChangeTypes.Bool; + newChange.frame = frame; + newChange.button = button; + newChange.valueBool = value; + + changeList.Add(newChange); + } + else + { + //Nothing to do here + } + } + else + { + newChange.type = LuaChangeTypes.InputChange; + newChange.inputType = InputChangeTypes.Bool; + newChange.frame = frame; + newChange.button = button; + newChange.valueBool = value; + + changeList.Add(newChange); + } + } + } + } + + [LuaMethodExample("")] + [LuaMethod("submitanalogchange", "")] + public void SubmitAnalogChange(int frame, string button, float value) + { + if (Engaged()) + { + if (frame >= 0) + { + PendingChanges newChange = new PendingChanges(); + + if (frame < Tastudio.CurrentTasMovie.InputLogLength) + { + if (Tastudio.CurrentTasMovie.GetFloatState(frame, button) != value) //Check if the button state is not already in the state the user set in the lua script + { + newChange.type = LuaChangeTypes.InputChange; + newChange.inputType = InputChangeTypes.Float; + newChange.frame = frame; + newChange.button = button; + newChange.valueFloat = value; + + changeList.Add(newChange); + } + else + { + //Nothing to do here + } + } + else + { + newChange.type = LuaChangeTypes.InputChange; + newChange.inputType = InputChangeTypes.Float; + newChange.frame = frame; + newChange.button = button; + newChange.valueFloat = value; + + changeList.Add(newChange); + } + } + } + } + + [LuaMethodExample("")] + [LuaMethod("submitinsertframes", "")] + public void SubmitInsertFrames(int frame, int number) + { + if (Engaged()) + { + if (frame >= 0 && frame < Tastudio.CurrentTasMovie.InputLogLength && number > 0) + { + PendingChanges newChange = new PendingChanges(); + + newChange.type = LuaChangeTypes.InsertFrames; + newChange.frame = frame; + newChange.number = number; + + changeList.Add(newChange); + } + } + } + + [LuaMethodExample("")] + [LuaMethod("submitdeleteframes", "")] + public void SubmitDeleteFrames(int frame, int number) + { + if (Engaged()) + { + if (frame >= 0 && frame < Tastudio.CurrentTasMovie.InputLogLength && number > 0) + { + PendingChanges newChange = new PendingChanges(); + + newChange.type = LuaChangeTypes.DeleteFrames; + newChange.frame = frame; + newChange.number = number; + + changeList.Add(newChange); + } + } + } + + [LuaMethodExample("")] + [LuaMethod("applyinputchanges", "")] + public void ApplyInputChanges() + { + if (Engaged()) + { + if (changeList.Count > 0) + { + int size = changeList.Count; + + for (int i = 0; i < size; i++) + { + switch (changeList[i].type) + { + case LuaChangeTypes.InputChange: + switch (changeList[i].inputType) + { + case InputChangeTypes.Bool: + Tastudio.CurrentTasMovie.SetBoolState(changeList[i].frame, changeList[i].button, changeList[i].valueBool); + break; + case InputChangeTypes.Float: + Tastudio.CurrentTasMovie.SetFloatState(changeList[i].frame, changeList[i].button, changeList[i].valueFloat); + break; + } + break; + case LuaChangeTypes.InsertFrames: + Tastudio.InsertNumFrames(changeList[i].frame, changeList[i].number); + break; + case LuaChangeTypes.DeleteFrames: + Tastudio.DeleteFrames(changeList[i].frame, changeList[i].number); + break; + } + } + changeList.Clear(); + Tastudio.JumpToGreenzone(); + Tastudio.DoAutoRestore(); + } + + + } + } + + [LuaMethodExample("")] + [LuaMethod("clearinputchanges", "")] + public void ClearInputChanges() + { + if (Engaged()) + changeList.Clear(); + } + + [LuaMethod("addcolumn", "")] + public void AddColumn(string name, string text, int width) + { + if (Engaged()) + { + Tastudio.AddColumn(name, text, width, InputRoll.RollColumn.InputType.Text); + } + } } } diff --git a/BizHawk.Client.EmuHawk/tools/Lua/LuaCanvas.cs b/BizHawk.Client.EmuHawk/tools/Lua/LuaCanvas.cs index dba8c9079f..b0d900a8e4 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/LuaCanvas.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/LuaCanvas.cs @@ -33,6 +33,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.setTitle( \"Title\" );")] [LuaMethod( "setTitle", "Sets the canvas window title")] @@ -41,6 +43,8 @@ namespace BizHawk.Client.EmuHawk Text = title; } + [LuaMethodExample( + "LuaCanvas.setLocation( 16, 32 );")] [LuaMethod( "setLocation", "Sets the location of the canvas window")] @@ -51,6 +55,8 @@ namespace BizHawk.Client.EmuHawk Top = (int)y; } + [LuaMethodExample( + "LuaCanvas.clear( 0x000000FF );")] [LuaMethod( "clear", "Clears the canvas")] @@ -59,6 +65,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.Clear(color); } + [LuaMethodExample( + "LuaCanvas.refresh( );")] [LuaMethod( "refresh", "Redraws the canvas")] @@ -67,6 +75,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.Refresh(); } + [LuaMethodExample( + "LuaCanvas.setDefaultForegroundColor( 0x000000FF );")] [LuaMethod( "setDefaultForegroundColor", "Sets the default foreground color to use in drawing methods, white by default")] @@ -75,6 +85,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.SetDefaultForegroundColor(color); } + [LuaMethodExample( + "LuaCanvas.setDefaultBackgroundColor( 0x000000FF );")] [LuaMethod( "setDefaultBackgroundColor", "Sets the default background color to use in drawing methods, transparent by default")] @@ -83,6 +95,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.SetDefaultBackgroundColor(color); } + [LuaMethodExample( + "LuaCanvas.setDefaultTextBackground( 0x000000FF );")] [LuaMethod( "setDefaultTextBackground", "Sets the default backgroiund color to use in text drawing methods, half-transparent black by default")] @@ -91,6 +105,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.SetDefaultTextBackground(color); } + [LuaMethodExample( + "LuaCanvas.drawBezier( { { 5, 10 }, { 10, 10 }, { 10, 20 }, { 5, 20 } }, 0x000000FF );")] [LuaMethod( "drawBezier", "Draws a Bezier curve using the table of coordinates provided in the given color")] @@ -107,6 +123,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.drawBox( 16, 32, 162, 322, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawBox", "Draws a rectangle on screen from x1/y1 to x2/y2. Same as drawRectangle except it receives two points intead of a point and width/height")] @@ -123,6 +141,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.drawEllipse( 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawEllipse", "Draws an ellipse at the given coordinates and the given width and height. Line is the color of the ellipse. Background is the optional fill color")] @@ -139,6 +159,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.drawIcon( \"C:\\icon.ico\", 16, 32, 18, 24 );")] [LuaMethod( "drawIcon", "draws an Icon (.ico) file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] @@ -155,6 +177,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.drawImage( \"C:\\image.bmp\", 16, 32, 18, 24, false );")] [LuaMethod( "drawImage", "draws an image file from the given path at the given coordinate. width and height are optional. If specified, it will resize the image accordingly")] @@ -169,6 +193,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawImage(path, x, y, width, height, cache); } + [LuaMethodExample( + "LuaCanvas.clearImageCache( );")] [LuaMethod( "clearImageCache", "clears the image cache that is built up by using gui.drawImage, also releases the file handle for cached images")] @@ -177,6 +203,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.ClearImageCache(); } + [LuaMethodExample( + "LuaCanvas.drawImageRegion( \"C:\\image.png\", 11, 22, 33, 44, 21, 43, 34, 45 );")] [LuaMethod( "drawImageRegion", "draws a given region of an image file from the given path at the given coordinate, and optionally with the given size")] @@ -191,6 +219,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawImageRegion(path, source_x, source_y, source_width, source_height, dest_x, dest_y, dest_width, dest_height); } + [LuaMethodExample( + "LuaCanvas.drawLine( 161, 321, 162, 322, 0xFFFFFFFF );")] [LuaMethod( "drawLine", "Draws a line from the first coordinate pair to the 2nd. Color is optional (if not specified it will be drawn black)")] @@ -199,6 +229,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawLine(x1, y1, x2, y2, color); } + [LuaMethodExample( + "LuaCanvas.drawAxis( 16, 32, int size, 0xFFFFFFFF );")] [LuaMethod( "drawAxis", "Draws an axis of the specified size at the coordinate pair.)")] @@ -207,6 +239,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawAxis(x, y, size, color); } + [LuaMethodExample( + "LuaCanvas.drawArc( 16, 32, 77, 99, 180, 90, 0x007F00FF );")] [LuaMethod( "drawArc", "draws a Arc shape at the given coordinates and the given width and height" @@ -216,6 +250,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawArc(x, y, width, height, startangle, sweepangle, line); } + [LuaMethodExample( + "LuaCanvas.drawPie( 16, 32, 77, 99, 180, 90, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawPie", "draws a Pie shape at the given coordinates and the given width and height")] @@ -232,6 +268,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawPie(x, y, width, height, startangle, sweepangle, line, background); } + [LuaMethodExample( + "LuaCanvas.drawPixel( 16, 32, 0xFFFFFFFF );")] [LuaMethod( "drawPixel", "Draws a single pixel at the given coordinates in the given color. Color is optional (if not specified it will be drawn black)")] @@ -248,6 +286,8 @@ namespace BizHawk.Client.EmuHawk } } + [LuaMethodExample( + "LuaCanvas.drawPolygon( { 10, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawPolygon", "Draws a polygon using the table of coordinates specified in points. This should be a table of tables(each of size 2). Line is the color of the polygon. Background is the optional fill color")] @@ -265,6 +305,8 @@ namespace BizHawk.Client.EmuHawk } + [LuaMethodExample( + "LuaCanvas.drawRectangle( 16, 32, 77, 99, 0x007F00FF, 0x7F7F7FFF );")] [LuaMethod( "drawRectangle", "Draws a rectangle at the given coordinate and the given width and height. Line is the color of the box. Background is the optional fill color")] @@ -273,6 +315,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawRectangle(x, y, width, height, line, background); } + [LuaMethodExample( + "LuaCanvas.drawString( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod( "drawString", "Alias of DrawText()")] @@ -291,6 +335,8 @@ namespace BizHawk.Client.EmuHawk luaPictureBox.DrawText(x, y, message, forecolor, backcolor, fontsize, fontfamily, fontstyle, horizalign, vertalign); } + [LuaMethodExample( + "LuaCanvas.drawText( 16, 32, \"Some message\", 0x7F0000FF, 0x00007FFF, 8, \"Arial Narrow\", \"bold\", \"center\", \"middle\" );")] [LuaMethod( "drawText", "Draws the given message at the given x,y coordinates and the given color. The default color is white. A fontfamily can be specified and is monospace generic if none is specified (font family options are the same as the .NET FontFamily class). The fontsize default is 12. The default font style is regular. Font style options are regular, bold, italic, strikethrough, underline. Horizontal alignment options are left (default), center, or right. Vertical alignment options are bottom (default), middle, or top. Alignment options specify which ends of the text will be drawn at the x and y coordinates.")] @@ -311,6 +357,8 @@ namespace BizHawk.Client.EmuHawk // It'd be great if these were simplified into 1 function, but I cannot figure out how to return a LuaTable from this class + [LuaMethodExample( + "local inLuaget = LuaCanvas.getMouseX( );")] [LuaMethod( "getMouseX", "Returns an integer representation of the mouse X coordinate relative to the canvas window.")] @@ -320,6 +368,8 @@ namespace BizHawk.Client.EmuHawk return position.X; } + [LuaMethodExample( + "local inLuaget = LuaCanvas.getMouseY( );")] [LuaMethod( "getMouseY", "Returns an integer representation of the mouse Y coordinate relative to the canvas window.")] diff --git a/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.Designer.cs b/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.Designer.cs index 8819b2558c..4805891afc 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.Designer.cs @@ -82,6 +82,14 @@ this.ToWikiMarkupButton.Text = "Wiki markup to Clipboard"; this.ToWikiMarkupButton.UseVisualStyleBackColor = true; this.ToWikiMarkupButton.Click += new System.EventHandler(this.ToWikiMarkupButton_Click); + // + // CopyMenu + // + this.CopyMenu = new System.Windows.Forms.ContextMenu( + new System.Windows.Forms.MenuItem[] { + new System.Windows.Forms.MenuItem("Copy") + }); + this.CopyMenu.MenuItems[0].Click += new System.EventHandler(this.FunctionView_Copy); // // FunctionView // @@ -109,6 +117,7 @@ this.FunctionView.VirtualMode = true; this.FunctionView.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.FunctionView_ColumnClick); this.FunctionView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.FunctionView_KeyDown); + this.FunctionView.ContextMenu = this.CopyMenu; // // LibraryReturn // @@ -170,5 +179,6 @@ private System.Windows.Forms.TextBox FilterBox; private System.Windows.Forms.Label label1; private System.Windows.Forms.Button ToWikiMarkupButton; + private System.Windows.Forms.ContextMenu CopyMenu; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.cs b/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.cs index 96e03a5d60..81c9c696b6 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/LuaFunctionsForm.cs @@ -6,6 +6,7 @@ using System.Text; using System.Windows.Forms; using BizHawk.Client.Common; +using BizHawk.Common; namespace BizHawk.Client.EmuHawk { @@ -182,6 +183,10 @@ namespace BizHawk.Client.EmuHawk { var indexes = FunctionView.SelectedIndices; + //TODO - duplicated code with FunctionView_Copy + //also -- this list is more compact (the examples would fill space) + //it isn't clear whether we should copy the examples here. So maybe this should stay distinct (and more compact?) + if (indexes.Count > 0) { var sb = new StringBuilder(); @@ -193,13 +198,26 @@ namespace BizHawk.Client.EmuHawk } if (sb.Length > 0) - { Clipboard.SetDataObject(sb.ToString()); - } } } } + //FREVBHFYL? + private void FunctionView_Copy(object sender, EventArgs e) + { + var itm = _filteredList[FunctionView.selectedItem]; + var sb = new StringBuilder($"//{itm.Library}.{itm.Name}{itm.ParameterList}"); //comment style not an accident: the 'declaration' is not legal lua, so use of -- to comment it shouldn't suggest it. right? + if (itm.Example != null) + { + sb.AppendLine(); + sb.Append(itm.Example); + } + + if (sb.Length > 0) + Clipboard.SetText(sb.ToString()); + } + private void UpdateList() { GenerateFilteredList(); diff --git a/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs b/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs index 5044b98271..d20fea3c50 100644 --- a/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs +++ b/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs @@ -295,7 +295,7 @@ namespace BizHawk.Client.EmuHawk { return PathManager.MakeAbsolutePath(Path.Combine( Global.Config.PathEntries["Global", "Macros"].Path, - Global.Game.Name), null); + PathManager.FilesystemSafeName(Global.Game)), null); } #endregion diff --git a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs index 3c1e2f2e9e..5616d71e02 100644 --- a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs @@ -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); diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 96497bced7..aed7b087ef 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -28,6 +28,27 @@ namespace BizHawk.Client.EmuHawk Load, Update, Text, Remove, None } + public Action LoadedCallback { get; set; } + + private void CallLoadedCallback(int index) + { + LoadedCallback?.Invoke(index); + } + + public Action SavedCallback { get; set; } + + private void CallSavedCallback(int index) + { + SavedCallback?.Invoke(index); + } + + public Action RemovedCallback { get; set; } + + private void CallRemovedCallback(int index) + { + RemovedCallback?.Invoke(index); + } + public TAStudio Tastudio { get; set; } public int HoverInterval @@ -135,6 +156,7 @@ namespace BizHawk.Client.EmuHawk Movie.AddBranch(branch); BranchView.RowCount = Movie.BranchCount; Movie.CurrentBranch = Movie.BranchCount - 1; + BranchView.ScrollToIndex(Movie.CurrentBranch); BranchView.Refresh(); Tastudio.RefreshDialog(); } @@ -209,6 +231,7 @@ namespace BizHawk.Client.EmuHawk private void AddBranchToolStripMenuItem_Click(object sender, EventArgs e) { Branch(); + CallSavedCallback(Tastudio.CurrentTasMovie.BranchCount - 1); GlobalWin.OSD.AddMessage("Added branch " + Movie.CurrentBranch.ToString()); } @@ -216,6 +239,7 @@ namespace BizHawk.Client.EmuHawk { Branch(); EditBranchTextPopUp(Movie.CurrentBranch); + CallSavedCallback(Tastudio.CurrentTasMovie.BranchCount - 1); GlobalWin.OSD.AddMessage("Added branch " + Movie.CurrentBranch.ToString()); } @@ -236,6 +260,7 @@ namespace BizHawk.Client.EmuHawk _branchUndo = BranchUndo.Load; LoadSelectedBranch(); + CallLoadedCallback(BranchView.SelectedRows.First()); } private void UpdateBranchToolStripMenuItem_Click(object sender, EventArgs e) @@ -251,6 +276,7 @@ namespace BizHawk.Client.EmuHawk _branchUndo = BranchUndo.Update; UpdateBranch(SelectedBranch); + CallSavedCallback(Movie.CurrentBranch); GlobalWin.OSD.AddMessage("Saved branch " + Movie.CurrentBranch); } } @@ -315,6 +341,7 @@ namespace BizHawk.Client.EmuHawk BranchView.SelectRow(Movie.BranchCount - 1, true); } + CallRemovedCallback(index); Tastudio.RefreshDialog(); GlobalWin.OSD.AddMessage("Removed branch " + index.ToString()); } @@ -325,11 +352,13 @@ namespace BizHawk.Client.EmuHawk if (_branchUndo == BranchUndo.Load) { LoadBranch(_backupBranch); + CallLoadedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Load canceled"); } else if (_branchUndo == BranchUndo.Update) { Movie.UpdateBranch(Movie.GetBranch(_backupBranch.UniqueIdentifier), _backupBranch); + CallSavedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Update canceled"); } else if (_branchUndo == BranchUndo.Text) @@ -341,6 +370,7 @@ namespace BizHawk.Client.EmuHawk { Movie.AddBranch(_backupBranch); BranchView.RowCount = Movie.BranchCount; + CallSavedCallback(Tastudio.CurrentTasMovie.Branches.IndexOf(_backupBranch)); GlobalWin.OSD.AddMessage("Branch Removal canceled"); } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs index 9a15b7e7e3..7c77f6670b 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs @@ -53,8 +53,6 @@ namespace BizHawk.Client.EmuHawk this.label8 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label(); this.NumSaveStatesLabel = new System.Windows.Forms.Label(); - this.BranchStatesInTasproj = new System.Windows.Forms.CheckBox(); - this.EraseBranchStatesFirst = new System.Windows.Forms.CheckBox(); this.FileStateGapNumeric = new System.Windows.Forms.NumericUpDown(); this.label10 = new System.Windows.Forms.Label(); this.label11 = new System.Windows.Forms.Label(); @@ -79,7 +77,7 @@ namespace BizHawk.Client.EmuHawk // 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(242, 259); + this.CancelBtn.Location = new System.Drawing.Point(242, 225); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(60, 23); this.CancelBtn.TabIndex = 0; @@ -90,7 +88,7 @@ namespace BizHawk.Client.EmuHawk // 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(176, 259); + this.OkBtn.Location = new System.Drawing.Point(176, 225); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(60, 23); this.OkBtn.TabIndex = 1; @@ -177,10 +175,11 @@ namespace BizHawk.Client.EmuHawk // // DiskCapacityNumeric // + this.DiskCapacityNumeric.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.DiskCapacityNumeric.Enabled = false; - this.DiskCapacityNumeric.Location = new System.Drawing.Point(24, 241); + this.DiskCapacityNumeric.Location = new System.Drawing.Point(24, 215); this.DiskCapacityNumeric.Maximum = new decimal(new int[] { - 1, + 16384, 0, 0, 0}); @@ -201,9 +200,10 @@ namespace BizHawk.Client.EmuHawk // // label5 // + this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label5.AutoSize = true; this.label5.Enabled = false; - this.label5.Location = new System.Drawing.Point(79, 244); + this.label5.Location = new System.Drawing.Point(79, 218); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(23, 13); this.label5.TabIndex = 4; @@ -211,9 +211,10 @@ namespace BizHawk.Client.EmuHawk // // label6 // + this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label6.AutoSize = true; this.label6.Enabled = false; - this.label6.Location = new System.Drawing.Point(21, 224); + this.label6.Location = new System.Drawing.Point(21, 198); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(72, 13); this.label6.TabIndex = 5; @@ -273,32 +274,6 @@ namespace BizHawk.Client.EmuHawk this.NumSaveStatesLabel.TabIndex = 9; this.NumSaveStatesLabel.Text = "1"; // - // BranchStatesInTasproj - // - this.BranchStatesInTasproj.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.BranchStatesInTasproj.AutoSize = true; - this.BranchStatesInTasproj.Location = new System.Drawing.Point(12, 153); - this.BranchStatesInTasproj.Name = "BranchStatesInTasproj"; - this.BranchStatesInTasproj.Size = new System.Drawing.Size(118, 17); - this.BranchStatesInTasproj.TabIndex = 10; - this.BranchStatesInTasproj.Text = "Save branch states"; - this.BranchStatesInTasproj.UseVisualStyleBackColor = true; - this.BranchStatesInTasproj.CheckedChanged += new System.EventHandler(this.BranchStatesInTasproj_CheckedChanged); - // - // EraseBranchStatesFirst - // - this.EraseBranchStatesFirst.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.EraseBranchStatesFirst.AutoSize = true; - this.EraseBranchStatesFirst.Checked = true; - this.EraseBranchStatesFirst.CheckState = System.Windows.Forms.CheckState.Checked; - this.EraseBranchStatesFirst.Location = new System.Drawing.Point(6, 153); - this.EraseBranchStatesFirst.Name = "EraseBranchStatesFirst"; - this.EraseBranchStatesFirst.Size = new System.Drawing.Size(139, 17); - this.EraseBranchStatesFirst.TabIndex = 11; - this.EraseBranchStatesFirst.Text = "Erase branch states first"; - this.EraseBranchStatesFirst.UseVisualStyleBackColor = true; - this.EraseBranchStatesFirst.CheckedChanged += new System.EventHandler(this.EraseBranchStatesFIrst_CheckedChanged); - // // FileStateGapNumeric // this.FileStateGapNumeric.Location = new System.Drawing.Point(12, 100); @@ -351,13 +326,12 @@ namespace BizHawk.Client.EmuHawk this.groupBox1.Controls.Add(this.label13); this.groupBox1.Controls.Add(this.MemStateGapDividerNumeric); this.groupBox1.Controls.Add(this.label12); - this.groupBox1.Controls.Add(this.EraseBranchStatesFirst); this.groupBox1.Controls.Add(this.label2); this.groupBox1.Controls.Add(this.MemCapacityNumeric); this.groupBox1.Controls.Add(this.label1); this.groupBox1.Location = new System.Drawing.Point(12, 34); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(143, 176); + this.groupBox1.Size = new System.Drawing.Size(143, 147); this.groupBox1.TabIndex = 16; this.groupBox1.TabStop = false; this.groupBox1.Text = "Memory Usage"; @@ -427,7 +401,6 @@ namespace BizHawk.Client.EmuHawk this.groupBox2.Controls.Add(this.FileCapacityNumeric); this.groupBox2.Controls.Add(this.FileNumFramesLabel); this.groupBox2.Controls.Add(this.label7); - this.groupBox2.Controls.Add(this.BranchStatesInTasproj); this.groupBox2.Controls.Add(this.label11); this.groupBox2.Controls.Add(this.label9); this.groupBox2.Controls.Add(this.label10); @@ -435,7 +408,7 @@ namespace BizHawk.Client.EmuHawk this.groupBox2.Controls.Add(this.FileStateGapNumeric); this.groupBox2.Location = new System.Drawing.Point(163, 34); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(138, 176); + this.groupBox2.Size = new System.Drawing.Size(138, 147); this.groupBox2.TabIndex = 17; this.groupBox2.TabStop = false; this.groupBox2.Text = "Project File"; @@ -446,7 +419,7 @@ namespace BizHawk.Client.EmuHawk 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(315, 294); + this.ClientSize = new System.Drawing.Size(315, 260); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.DiskCapacityNumeric); @@ -495,8 +468,6 @@ namespace BizHawk.Client.EmuHawk private Label label8; private Label label9; private Label NumSaveStatesLabel; - private CheckBox BranchStatesInTasproj; - private CheckBox EraseBranchStatesFirst; private NumericUpDown FileStateGapNumeric; private Label label10; private Label label11; diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs index e45a2a6f3f..612f788cce 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using BizHawk.Emulation.Common; using BizHawk.Client.Common; +using BizHawk.Common.NumberExtensions; namespace BizHawk.Client.EmuHawk { @@ -24,25 +25,20 @@ namespace BizHawk.Client.EmuHawk _stateSizeMb = Statable.SaveStateBinary().Length / (decimal)1024 / (decimal)1024; MemCapacityNumeric.Maximum = 1024 * 8; - - MemCapacityNumeric.Value = _settings.Capacitymb < MemCapacityNumeric.Maximum ? - _settings.Capacitymb : MemCapacityNumeric.Maximum; - DiskCapacityNumeric.Value = _settings.DiskCapacitymb < MemCapacityNumeric.Maximum ? - _settings.DiskCapacitymb : MemCapacityNumeric.Maximum; - FileCapacityNumeric.Value = _settings.DiskSaveCapacitymb < MemCapacityNumeric.Maximum ? - _settings.DiskSaveCapacitymb : MemCapacityNumeric.Maximum; + MemCapacityNumeric.Minimum = _stateSizeMb + 1; MemStateGapDividerNumeric.Maximum = Statable.SaveStateBinary().Length / 1024 / 2 + 1; - MemStateGapDividerNumeric.Minimum = Statable.SaveStateBinary().Length / 1024 / 16; - MemStateGapDividerNumeric.Value = _settings.MemStateGapDivider < MemStateGapDividerNumeric.Minimum ? - MemStateGapDividerNumeric.Minimum : _settings.MemStateGapDivider; + MemStateGapDividerNumeric.Minimum = Math.Max(Statable.SaveStateBinary().Length / 1024 / 16, 1); + + MemCapacityNumeric.Value = NumberExtensions.Clamp(_settings.Capacitymb, MemCapacityNumeric.Minimum, MemCapacityNumeric.Maximum); + DiskCapacityNumeric.Value = NumberExtensions.Clamp(_settings.DiskCapacitymb, MemCapacityNumeric.Minimum, MemCapacityNumeric.Maximum); + FileCapacityNumeric.Value = NumberExtensions.Clamp(_settings.DiskSaveCapacitymb, MemCapacityNumeric.Minimum, MemCapacityNumeric.Maximum); + MemStateGapDividerNumeric.Value = NumberExtensions.Clamp(_settings.MemStateGapDivider, MemStateGapDividerNumeric.Minimum, MemStateGapDividerNumeric.Maximum); FileStateGapNumeric.Value = _settings.FileStateGap; SavestateSizeLabel.Text = Math.Round(_stateSizeMb, 2).ToString() + " MB"; CapacityNumeric_ValueChanged(null, null); SaveCapacityNumeric_ValueChanged(null, null); - BranchStatesInTasproj.Checked = _settings.BranchStatesInTasproj; - EraseBranchStatesFirst.Checked = _settings.EraseBranchStatesFirst; } private int MaxStatesInCapacity => (int)Math.Floor(MemCapacityNumeric.Value / _stateSizeMb) @@ -77,16 +73,6 @@ namespace BizHawk.Client.EmuHawk NumSaveStatesLabel.Text = ((int)Math.Floor(FileCapacityNumeric.Value / _stateSizeMb)).ToString(); } - private void BranchStatesInTasproj_CheckedChanged(object sender, EventArgs e) - { - _settings.BranchStatesInTasproj = BranchStatesInTasproj.Checked; - } - - private void EraseBranchStatesFIrst_CheckedChanged(object sender, EventArgs e) - { - _settings.EraseBranchStatesFirst = EraseBranchStatesFirst.Checked; - } - private void FileStateGap_ValueChanged(object sender, EventArgs e) { FileNumFramesLabel.Text = FileStateGapNumeric.Value == 0 diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs index 9646808d86..f24533e8d0 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/MarkerControl.cs @@ -98,10 +98,12 @@ namespace BizHawk.Client.EmuHawk private void MarkerContextMenu_Opening(object sender, CancelEventArgs e) { EditMarkerToolStripMenuItem.Enabled = - RemoveMarkerToolStripMenuItem.Enabled = - JumpToMarkerToolStripMenuItem.Enabled = - ScrollToMarkerToolStripMenuItem.Enabled = + RemoveMarkerToolStripMenuItem.Enabled = MarkerInputRoll.AnyRowsSelected && MarkerView.SelectedRows.First() != 0; + + JumpToMarkerToolStripMenuItem.Enabled = + ScrollToMarkerToolStripMenuItem.Enabled = + MarkerInputRoll.AnyRowsSelected; } private void ScrollToMarkerToolStripMenuItem_Click(object sender, EventArgs e) @@ -150,12 +152,28 @@ namespace BizHawk.Client.EmuHawk if (MarkerView.AnyRowsSelected) { SelectedMarkers.ForEach(i => Markers.Remove(i)); - MarkerInputRoll.DeselectAll(); + ShrinkSelection(); Tastudio.RefreshDialog(); MarkerView_SelectedIndexChanged(null, null); } } + // feos: not the same as InputRoll.TruncateSelection(), since multiple selection of markers is forbidden + // moreover, when the last marker is removed, we need its selection to move to the previous marker + // still iterate, so things don't break if multiple selection is allowed someday + public void ShrinkSelection() + { + if (MarkerView.AnyRowsSelected) + { + while (MarkerView.SelectedRows.Last() > Markers.Count() - 1) + { + MarkerView.SelectRow(Markers.Count(), false); + MarkerView.SelectRow(Markers.Count() - 1, true); + } + } + + } + public void AddMarker(bool editText = false, int? frame = null) { // feos: we specify the selected frame if we call this from TasView, otherwise marker should be added to the emulated frame @@ -190,6 +208,7 @@ namespace BizHawk.Client.EmuHawk UpdateValues(); } + MarkerView.ScrollToIndex(Markers.Count() - 1); Tastudio.RefreshDialog(); } @@ -242,17 +261,20 @@ namespace BizHawk.Client.EmuHawk private void MarkerView_SelectedIndexChanged(object sender, EventArgs e) { EditMarkerButton.Enabled = - RemoveMarkerButton.Enabled = - JumpToMarkerButton.Enabled = - ScrollToMarkerButton.Enabled = + RemoveMarkerButton.Enabled = MarkerInputRoll.AnyRowsSelected && MarkerView.SelectedRows.First() != 0; + + JumpToMarkerButton.Enabled = + ScrollToMarkerButton.Enabled = + MarkerInputRoll.AnyRowsSelected; } private List SelectedMarkers { get { - return MarkerView.SelectedRows + return MarkerView + .SelectedRows .Select(index => Markers[index]) .ToList(); } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.Designer.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.Designer.cs index aa1cfd04e6..2319cf7923 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.Designer.cs @@ -28,6 +28,7 @@ /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.PlaybackGroupBox = new System.Windows.Forms.GroupBox(); this.RecordingModeCheckbox = new System.Windows.Forms.CheckBox(); this.AutoRestoreCheckbox = new System.Windows.Forms.CheckBox(); @@ -38,6 +39,7 @@ this.PauseButton = new System.Windows.Forms.Button(); this.RewindButton = new BizHawk.Client.EmuHawk.RepeatButton(); this.PreviousMarkerButton = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); this.PlaybackGroupBox.SuspendLayout(); this.SuspendLayout(); // @@ -125,6 +127,7 @@ this.FrameAdvanceButton.Size = new System.Drawing.Size(38, 23); this.FrameAdvanceButton.TabIndex = 3; this.FrameAdvanceButton.Text = ">"; + this.toolTip1.SetToolTip(this.FrameAdvanceButton, "Right Mouse Button + Wheel Down"); this.FrameAdvanceButton.UseVisualStyleBackColor = true; this.FrameAdvanceButton.MouseDown += new System.Windows.Forms.MouseEventHandler(this.FrameAdvanceButton_MouseDown); this.FrameAdvanceButton.MouseLeave += new System.EventHandler(this.FrameAdvanceButton_MouseLeave); @@ -137,6 +140,7 @@ this.PauseButton.Size = new System.Drawing.Size(38, 23); this.PauseButton.TabIndex = 2; this.PauseButton.Text = "| |"; + this.toolTip1.SetToolTip(this.PauseButton, "Middle Mouse Button"); this.PauseButton.UseVisualStyleBackColor = true; this.PauseButton.Click += new System.EventHandler(this.PauseButton_Click); // @@ -149,6 +153,7 @@ this.RewindButton.Size = new System.Drawing.Size(38, 23); this.RewindButton.TabIndex = 1; this.RewindButton.Text = "<"; + this.toolTip1.SetToolTip(this.RewindButton, "Right Mouse Button + Wheel Up"); this.RewindButton.UseVisualStyleBackColor = true; this.RewindButton.MouseDown += new System.Windows.Forms.MouseEventHandler(this.RewindButton_MouseDown); this.RewindButton.MouseLeave += new System.EventHandler(this.RewindButton_MouseLeave); @@ -188,5 +193,6 @@ private System.Windows.Forms.CheckBox TurboSeekCheckbox; private System.Windows.Forms.CheckBox FollowCursorCheckbox; private System.Windows.Forms.CheckBox RecordingModeCheckbox; + private System.Windows.Forms.ToolTip toolTip1; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.resx b/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.resx index 29dcb1b3a3..65a871b69c 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.resx +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/PlaybackBox.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs index 1cc4ba50b5..b93e0f2a1a 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs @@ -11,6 +11,9 @@ namespace BizHawk.Client.EmuHawk public Func QueryItemIconCallback { get; set; } public Action GreenzoneInvalidatedCallback { get; set; } + public Action BranchLoadedCallback { get; set; } + public Action BranchSavedCallback { get; set; } + public Action BranchRemovedCallback { get; set; } private Color? GetColorOverride(int index, InputRoll.RollColumn column) { @@ -31,5 +34,20 @@ namespace BizHawk.Client.EmuHawk { GreenzoneInvalidatedCallback?.Invoke(index); } + + private void BranchLoaded(int index) + { + BranchLoadedCallback?.Invoke(index); + } + + private void BranchSaved(int index) + { + BranchSavedCallback?.Invoke(index); + } + + private void BranchRemoved(int index) + { + BranchRemovedCallback?.Invoke(index); + } } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index bb3d0089d8..2ed27e86df 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -23,6 +23,7 @@ namespace BizHawk.Client.EmuHawk private bool _startSelectionDrag; private bool _selectionDragState; private bool _supressContextMenu; + private int _startrow; // Editing analog input private string _floatEditColumn = ""; @@ -63,7 +64,7 @@ namespace BizHawk.Client.EmuHawk public AutoPatternBool[] BoolPatterns; public AutoPatternFloat[] FloatPatterns; - private void JumpToGreenzone() + public void JumpToGreenzone() { if (Emulator.Frame > CurrentTasMovie.LastValidFrame) { @@ -367,7 +368,6 @@ namespace BizHawk.Client.EmuHawk if (columnName == FrameColumnName) { CurrentTasMovie.Markers.Add(TasView.LastSelectedIndex.Value, ""); - RefreshDialog(); } else if (columnName != CursorColumnName) { @@ -376,18 +376,29 @@ namespace BizHawk.Client.EmuHawk if (Global.MovieSession.MovieControllerAdapter.Definition.BoolButtons.Contains(buttonName)) { - // nifty taseditor logic - bool allPressed = true; - foreach (var index in TasView.SelectedRows) + if (Control.ModifierKeys != Keys.Alt) { - if ((index == CurrentTasMovie.FrameCount) // last movie frame can't have input, but can be selected - || (!CurrentTasMovie.BoolIsPressed(index, buttonName))) + // nifty taseditor logic + bool allPressed = true; + foreach (var index in TasView.SelectedRows) { - allPressed = false; - break; + if ((index == CurrentTasMovie.FrameCount) // last movie frame can't have input, but can be selected + || (!CurrentTasMovie.BoolIsPressed(index, buttonName))) + { + allPressed = false; + break; + } + } + CurrentTasMovie.SetBoolStates(frame, TasView.SelectedRows.Count(), buttonName, !allPressed); + } + else + { + BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); + foreach (var index in TasView.SelectedRows) + { + CurrentTasMovie.SetBoolState(index, buttonName, BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].GetNextValue()); } } - CurrentTasMovie.SetBoolStates(frame, TasView.SelectedRows.Count(), buttonName, !allPressed); } else { @@ -397,8 +408,8 @@ namespace BizHawk.Client.EmuHawk _triggerAutoRestore = true; JumpToGreenzone(); - RefreshDialog(); } + RefreshDialog(); } } @@ -582,32 +593,63 @@ namespace BizHawk.Client.EmuHawk _selectionDragState = TasView.SelectedRows.Contains(frame); } } - else // User changed input + else if (TasView.CurrentCell.Column.Type != InputRoll.RollColumn.InputType.Text)// User changed input { bool wasPaused = Mainform.EmulatorPaused; if (Global.MovieSession.MovieControllerAdapter.Definition.BoolButtons.Contains(buttonName)) { - CurrentTasMovie.ChangeLog.BeginNewBatch("Paint Bool " + buttonName + " from frame " + frame); - - CurrentTasMovie.ToggleBoolState(TasView.CurrentCell.RowIndex.Value, buttonName); - _triggerAutoRestore = true; - JumpToGreenzone(); - RefreshDialog(); - + _patternPaint = false; _startBoolDrawColumn = buttonName; - _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - if (applyPatternToPaintedInputToolStripMenuItem.Checked && (!onlyOnAutoFireColumnsToolStripMenuItem.Checked - || TasView.CurrentCell.Column.Emphasis)) + if ((Control.ModifierKeys == Keys.Alt && Control.ModifierKeys != Keys.Shift) || (applyPatternToPaintedInputToolStripMenuItem.Checked && (!onlyOnAutoFireColumnsToolStripMenuItem.Checked + || TasView.CurrentCell.Column.Emphasis))) { BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].Reset(); - BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].GetNextValue(); + //BoolPatterns[ControllerType.BoolButtons.IndexOf(buttonName)].GetNextValue(); _patternPaint = true; + _startrow = TasView.CurrentCell.RowIndex.Value; + _boolPaintState = !CurrentTasMovie.BoolIsPressed(frame, buttonName); + } + else if (Control.ModifierKeys == Keys.Shift && Control.ModifierKeys != Keys.Alt) + { + int firstSel = TasView.SelectedRows.First(); + + if (frame <= firstSel) + { + firstSel = frame; + frame = TasView.SelectedRows.First(); + } + + bool allPressed = true; + for (int i = firstSel; i <= frame; i++) + { + if ((i == CurrentTasMovie.FrameCount) // last movie frame can't have input, but can be selected + || (!CurrentTasMovie.BoolIsPressed(i, buttonName))) + { + allPressed = false; + break; + } + } + CurrentTasMovie.SetBoolStates(firstSel, (frame - firstSel) + 1, buttonName, !allPressed); + _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); + _triggerAutoRestore = true; + JumpToGreenzone(); + RefreshDialog(); + } + else if (Control.ModifierKeys == Keys.Shift && Control.ModifierKeys == Keys.Alt) //Does not work? + { + // TODO: Pattern drawing from selection to current cell } else { - _patternPaint = false; + CurrentTasMovie.ChangeLog.BeginNewBatch("Paint Bool " + buttonName + " from frame " + frame); + + CurrentTasMovie.ToggleBoolState(TasView.CurrentCell.RowIndex.Value, buttonName); + _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); + _triggerAutoRestore = true; + JumpToGreenzone(); + RefreshDialog(); } if (!Settings.AutoRestoreOnMouseUpOnly) @@ -888,6 +930,11 @@ namespace BizHawk.Client.EmuHawk return; } + if (!MouseButtonHeld) + { + return; + } + // skip rerecord counting on drawing entirely, mouse down is enough // avoid introducing another global bool wasCountingRerecords = Global.MovieSession.Movie.IsCountingRerecords; @@ -899,11 +946,19 @@ namespace BizHawk.Client.EmuHawk { startVal = e.OldCell.RowIndex.Value; endVal = e.NewCell.RowIndex.Value; + if (_patternPaint) + { + endVal--; + } } else { startVal = e.NewCell.RowIndex.Value; endVal = e.OldCell.RowIndex.Value; + if(_patternPaint) + { + endVal = _startrow; + } } if (_startCursorDrag && !Mainform.IsSeeking) @@ -1050,9 +1105,12 @@ namespace BizHawk.Client.EmuHawk if (e.OldCell.RowIndex.HasValue && e.NewCell.RowIndex.HasValue) { + + for (int i = startVal; i <= endVal; i++) // Inclusive on both ends (drawing up or down) { bool setVal = _boolPaintState; + if (_patternPaint && _boolPaintState) { if (CurrentTasMovie[frame].Lagged.HasValue && CurrentTasMovie[frame].Lagged.Value) @@ -1065,7 +1123,7 @@ namespace BizHawk.Client.EmuHawk } } - CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column + CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column JumpToGreenzone(); } @@ -1123,39 +1181,43 @@ namespace BizHawk.Client.EmuHawk private void TasView_MouseMove(object sender, MouseEventArgs e) { // For float editing - int increment = (_floatEditYPos - e.Y) / 4; - if (_floatEditYPos == -1) - return; - - float value = _floatPaintState + increment; - ControllerDefinition.FloatRange range = Global.MovieSession.MovieControllerAdapter.Definition.FloatRanges - [Global.MovieSession.MovieControllerAdapter.Definition.FloatControls.IndexOf(_floatEditColumn)]; - - // Range for N64 Y axis has max -128 and min 127. That should probably be fixed in ControllerDefinition.cs. - // SuuperW: I really don't think changing it would break anything, but adelikat isn't so sure. - float rMax = range.Max; - float rMin = range.Min; - if (rMax < rMin) + if (FloatEditingMode) { - rMax = range.Min; - rMin = range.Max; - } + int increment = (_floatEditYPos - e.Y) / 4; + if (_floatEditYPos == -1) + return; - if (value > rMax) - { - value = rMax; - } - else if (value < rMin) - { - value = rMin; - } + float value = _floatPaintState + increment; + ControllerDefinition.FloatRange range = Global.MovieSession.MovieControllerAdapter.Definition.FloatRanges + [Global.MovieSession.MovieControllerAdapter.Definition.FloatControls.IndexOf(_floatEditColumn)]; - CurrentTasMovie.SetFloatState(_floatEditRow, _floatEditColumn, value); - _floatTypedValue = value.ToString(); + // Range for N64 Y axis has max -128 and min 127. That should probably be fixed in ControllerDefinition.cs. + // SuuperW: I really don't think changing it would break anything, but adelikat isn't so sure. + float rMax = range.Max; + float rMin = range.Min; + if (rMax < rMin) + { + rMax = range.Min; + rMin = range.Max; + } - _triggerAutoRestore = true; - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + if (value > rMax) + { + value = rMax; + } + else if (value < rMin) + { + value = rMin; + } + + CurrentTasMovie.SetFloatState(_floatEditRow, _floatEditColumn, value); + _floatTypedValue = value.ToString(); + + _triggerAutoRestore = true; + JumpToGreenzone(); + DoTriggeredAutoRestoreIfNeeded(); + + } RefreshDialog(); } @@ -1411,16 +1473,34 @@ namespace BizHawk.Client.EmuHawk private void TasView_KeyDown(object sender, KeyEventArgs e) { - if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Left) // Ctrl + Left + //taseditor uses Ctrl for selection and Shift for framecourser + if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.PageUp) // Shift + Page Up { GoToPreviousMarker(); } - else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Right) // Ctrl + Right + else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.PageDown) // Shift + Page Down { GoToNextMarker(); } + else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Home) // Shift + Home + { + GoToFrame(0); + } + else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.End) // Shift + End + { + GoToFrame(CurrentTasMovie.InputLogLength-1); + } + else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Shift + Up + { + //GoToPreviousFrame(); + } + else if (!e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Shift + Down + { + //GoToNextFrame(); + } - if (FloatEditingMode && e.KeyCode != Keys.Right + if (FloatEditingMode + && e.KeyCode != Keys.Right && e.KeyCode != Keys.Left && e.KeyCode != Keys.Up && e.KeyCode != Keys.Down) diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 63be9aa051..2c2ba34562 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -25,6 +25,10 @@ namespace BizHawk.Client.EmuHawk SaveTASMenuItem.Enabled = !string.IsNullOrWhiteSpace(CurrentTasMovie.Filename) && (CurrentTasMovie.Filename != DefaultTasProjName()); + + saveSelectionToMacroToolStripMenuItem.Enabled = + placeMacroAtSelectionToolStripMenuItem.Enabled = + TasView.AnyRowsSelected; } private void RecentSubMenu_DropDownOpened(object sender, EventArgs e) @@ -289,18 +293,21 @@ namespace BizHawk.Client.EmuHawk private void EditSubMenu_DropDownOpened(object sender, EventArgs e) { DeselectMenuItem.Enabled = - SelectBetweenMarkersMenuItem.Enabled = - CopyMenuItem.Enabled = - CutMenuItem.Enabled = - ClearFramesMenuItem.Enabled = - DeleteFramesMenuItem.Enabled = - CloneFramesMenuItem.Enabled = - TruncateMenuItem.Enabled = + SelectBetweenMarkersMenuItem.Enabled = + CopyMenuItem.Enabled = + CutMenuItem.Enabled = + ClearFramesMenuItem.Enabled = + DeleteFramesMenuItem.Enabled = + CloneFramesMenuItem.Enabled = + TruncateMenuItem.Enabled = + InsertFrameMenuItem.Enabled = + InsertNumFramesMenuItem.Enabled = TasView.AnyRowsSelected; + ReselectClipboardMenuItem.Enabled = PasteMenuItem.Enabled = PasteInsertMenuItem.Enabled = - _tasClipboard.Any(); + Clipboard.GetDataObject().GetDataPresent(DataFormats.StringFormat) && TasView.AnyRowsSelected; ClearGreenzoneMenuItem.Enabled = CurrentTasMovie != null && CurrentTasMovie.TasStateManager.Any(); @@ -447,45 +454,48 @@ namespace BizHawk.Client.EmuHawk private void PasteMenuItem_Click(object sender, EventArgs e) { - // TODO: if highlighting 2 rows and pasting 3, only paste 2 of them - // FCEUX Taseditor does't do this, but I think it is the expected behavior in editor programs - var wasPaused = Mainform.EmulatorPaused; - - // copypaste from PasteInsertMenuItem_Click! - IDataObject data = Clipboard.GetDataObject(); - if (data.GetDataPresent(DataFormats.StringFormat)) + if (TasView.AnyRowsSelected) { - string input = (string)data.GetData(DataFormats.StringFormat); - if (!string.IsNullOrWhiteSpace(input)) + // TODO: if highlighting 2 rows and pasting 3, only paste 2 of them + // FCEUX Taseditor does't do this, but I think it is the expected behavior in editor programs + var wasPaused = Mainform.EmulatorPaused; + + // copypaste from PasteInsertMenuItem_Click! + IDataObject data = Clipboard.GetDataObject(); + if (data.GetDataPresent(DataFormats.StringFormat)) { - string[] lines = input.Split('\n'); - if (lines.Length > 0) + string input = (string)data.GetData(DataFormats.StringFormat); + if (!string.IsNullOrWhiteSpace(input)) { - _tasClipboard.Clear(); - for (int i = 0; i < lines.Length - 1; i++) + string[] lines = input.Split('\n'); + if (lines.Length > 0) { - var line = TasClipboardEntry.SetFromMnemonicStr(lines[i]); - if (line == null) + _tasClipboard.Clear(); + for (int i = 0; i < lines.Length - 1; i++) { - return; + var line = TasClipboardEntry.SetFromMnemonicStr(lines[i]); + if (line == null) + { + return; + } + else + { + _tasClipboard.Add(new TasClipboardEntry(i, line)); + } + } + + var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; + CurrentTasMovie.CopyOverInput(TasView.FirstSelectedIndex.Value, _tasClipboard.Select(x => x.ControllerState)); + if (needsToRollback) + { + GoToLastEmulatedFrameIfNecessary(TasView.FirstSelectedIndex.Value); + DoAutoRestore(); } else { - _tasClipboard.Add(new TasClipboardEntry(i, line)); + RefreshDialog(); } } - - var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; - CurrentTasMovie.CopyOverInput(TasView.FirstSelectedIndex.Value, _tasClipboard.Select(x => x.ControllerState)); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(TasView.FirstSelectedIndex.Value); - DoAutoRestore(); - } - else - { - RefreshDialog(); - } } } } @@ -493,43 +503,46 @@ namespace BizHawk.Client.EmuHawk private void PasteInsertMenuItem_Click(object sender, EventArgs e) { - var wasPaused = Mainform.EmulatorPaused; - - // copypaste from PasteMenuItem_Click! - IDataObject data = Clipboard.GetDataObject(); - if (data.GetDataPresent(DataFormats.StringFormat)) + if (TasView.AnyRowsSelected) { - string input = (string)data.GetData(DataFormats.StringFormat); - if (!string.IsNullOrWhiteSpace(input)) + var wasPaused = Mainform.EmulatorPaused; + + // copypaste from PasteMenuItem_Click! + IDataObject data = Clipboard.GetDataObject(); + if (data.GetDataPresent(DataFormats.StringFormat)) { - string[] lines = input.Split('\n'); - if (lines.Length > 0) + string input = (string)data.GetData(DataFormats.StringFormat); + if (!string.IsNullOrWhiteSpace(input)) { - _tasClipboard.Clear(); - for (int i = 0; i < lines.Length - 1; i++) + string[] lines = input.Split('\n'); + if (lines.Length > 0) { - var line = TasClipboardEntry.SetFromMnemonicStr(lines[i]); - if (line == null) + _tasClipboard.Clear(); + for (int i = 0; i < lines.Length - 1; i++) { - return; + var line = TasClipboardEntry.SetFromMnemonicStr(lines[i]); + if (line == null) + { + return; + } + else + { + _tasClipboard.Add(new TasClipboardEntry(i, line)); + } + } + + var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; + CurrentTasMovie.InsertInput(TasView.FirstSelectedIndex.Value, _tasClipboard.Select(x => x.ControllerState)); + if (needsToRollback) + { + GoToLastEmulatedFrameIfNecessary(TasView.FirstSelectedIndex.Value); + DoAutoRestore(); } else { - _tasClipboard.Add(new TasClipboardEntry(i, line)); + RefreshDialog(); } } - - var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; - CurrentTasMovie.InsertInput(TasView.FirstSelectedIndex.Value, _tasClipboard.Select(x => x.ControllerState)); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(TasView.FirstSelectedIndex.Value); - DoAutoRestore(); - } - else - { - RefreshDialog(); - } } } } @@ -663,32 +676,37 @@ namespace BizHawk.Client.EmuHawk private void InsertFrameMenuItem_Click(object sender, EventArgs e) { - var wasPaused = Mainform.EmulatorPaused; - var insertionFrame = TasView.AnyRowsSelected ? TasView.FirstSelectedIndex.Value : 0; - var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; - - CurrentTasMovie.InsertEmptyFrame(insertionFrame); - - if (needsToRollback) + if (TasView.AnyRowsSelected) { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - else - { - RefreshDialog(); + var wasPaused = Mainform.EmulatorPaused; + var insertionFrame = TasView.AnyRowsSelected ? TasView.FirstSelectedIndex.Value : 0; + var needsToRollback = TasView.FirstSelectedIndex < Emulator.Frame; + + CurrentTasMovie.InsertEmptyFrame(insertionFrame); + + if (needsToRollback) + { + GoToLastEmulatedFrameIfNecessary(insertionFrame); + DoAutoRestore(); + } + else + { + RefreshDialog(); + } } } private void InsertNumFramesMenuItem_Click(object sender, EventArgs e) { - int insertionFrame = TasView.AnyRowsSelected ? TasView.FirstSelectedIndex.Value : 0; - - var framesPrompt = new FramesPrompt(); - DialogResult result = framesPrompt.ShowDialog(); - if (result == DialogResult.OK) + if (TasView.AnyRowsSelected) { - InsertNumFrames(insertionFrame, framesPrompt.Frames); + int insertionFrame = TasView.FirstSelectedIndex.Value; + var framesPrompt = new FramesPrompt(); + DialogResult result = framesPrompt.ShowDialog(); + if (result == DialogResult.OK) + { + InsertNumFrames(insertionFrame, framesPrompt.Frames); + } } } @@ -742,7 +760,7 @@ namespace BizHawk.Client.EmuHawk { CurrentTasMovie.Markers.Remove(m); } - + MarkerControl.ShrinkSelection(); RefreshDialog(); } @@ -764,7 +782,7 @@ namespace BizHawk.Client.EmuHawk GoToFrame(0); int lastState = 0; - int goToFrame = CurrentTasMovie.TasStateManager.LastEmulatedFrame; + int goToFrame = CurrentTasMovie.TasStateManager.LastStatedFrame; do { Mainform.FrameAdvance(); @@ -1014,6 +1032,7 @@ namespace BizHawk.Client.EmuHawk Location = this.ChildPointToScreen(TasView), Statable = this.StatableEmulator }.ShowDialog(); + CurrentTasMovie.TasStateManager.UpdateStateFrequency(); CurrentTasMovie.TasStateManager.LimitStateCount(); UpdateChangesIndicator(); } @@ -1296,6 +1315,10 @@ namespace BizHawk.Client.EmuHawk TruncateContextMenuItem.Enabled = TasView.AnyRowsSelected; + pasteToolStripMenuItem.Enabled = + pasteInsertToolStripMenuItem.Enabled = + Clipboard.GetDataObject().GetDataPresent(DataFormats.StringFormat) && TasView.AnyRowsSelected; + StartNewProjectFromNowMenuItem.Visible = TasView.SelectedRows.Count() == 1 && TasView.SelectedRows.Contains(Emulator.Frame) diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 7514d05f9c..e6ec6e7615 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -111,6 +111,9 @@ namespace BizHawk.Client.EmuHawk /// public void SetVisibleIndex(int? indexThatMustBeVisible = null) { + if (TasView.AlwaysScroll && _leftButtonHeld) + return; + if (!indexThatMustBeVisible.HasValue) { indexThatMustBeVisible = Emulator.Frame; diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 84405c107b..f4c55250d8 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -479,7 +479,7 @@ namespace BizHawk.Client.EmuHawk SetUpToolStripColumns(); } - private void AddColumn(string columnName, string columnText, int columnWidth, InputRoll.RollColumn.InputType columnType = InputRoll.RollColumn.InputType.Boolean) + public void AddColumn(string columnName, string columnText, int columnWidth, InputRoll.RollColumn.InputType columnType = InputRoll.RollColumn.InputType.Boolean) { if (TasView.AllColumns[columnName] == null) { @@ -534,6 +534,10 @@ namespace BizHawk.Client.EmuHawk newMovie.TasStateManager.InvalidateCallback = GreenzoneInvalidated; newMovie.Filename = file.FullName; + BookMarkControl.LoadedCallback = BranchLoaded; + BookMarkControl.SavedCallback = BranchSaved; + BookMarkControl.RemovedCallback = BranchRemoved; + if (!HandleMovieLoadStuff(newMovie)) { return false; @@ -582,8 +586,14 @@ namespace BizHawk.Client.EmuHawk { Global.MovieSession.Movie = new TasMovie(false, _seekBackgroundWorker); var stateManager = ((TasMovie)Global.MovieSession.Movie).TasStateManager; + stateManager.MountWriteAccess(); stateManager.InvalidateCallback = GreenzoneInvalidated; + + BookMarkControl.LoadedCallback = BranchLoaded; + BookMarkControl.SavedCallback = BranchSaved; + BookMarkControl.RemovedCallback = BranchRemoved; + CurrentTasMovie.PropertyChanged += TasMovie_OnPropertyChanged; CurrentTasMovie.Filename = DefaultTasProjName(); // TODO don't do this, take over any mainform actions that can crash without a filename CurrentTasMovie.PopulateWithDefaultHeaderValues(); @@ -797,13 +807,14 @@ namespace BizHawk.Client.EmuHawk TasView.Refresh(); + SetSplicer(); CurrentTasMovie.FlushInputCache(); CurrentTasMovie.UseInputCache = false; _lastRefresh = Emulator.Frame; } - private void DoAutoRestore() + public void DoAutoRestore() { if (Settings.AutoRestoreLastPosition && LastPositionFrame != -1) { @@ -929,6 +940,7 @@ namespace BizHawk.Client.EmuHawk SplicerStatusLabel.Text = "Selected: " + TasView.SelectedRows.Count() + " frame" + (TasView.SelectedRows.Count() == 1 ? "" : "s") + + ", States: " + CurrentTasMovie.TasStateManager.StateCount.ToString() + ", Clipboard: " + (_tasClipboard.Any() ? _tasClipboard.Count + " frame" + (_tasClipboard.Count == 1 ? "" : "s") : "empty"); } diff --git a/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/BizHawk.Client.EmuHawk/tools/ToolManager.cs index bd5e0c9bdd..1a95aba1fe 100644 --- a/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -694,9 +694,9 @@ namespace BizHawk.Client.EmuHawk } } - public void FastUpdateAfter() + public void FastUpdateAfter(bool fromLua = false) { - if (Global.Config.RunLuaDuringTurbo && Has()) + if (!fromLua && Global.Config.RunLuaDuringTurbo && Has()) { LuaConsole.ResumeScripts(true); } diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A26Schema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A26Schema.cs index 6369a8a1b5..c915c4015d 100644 --- a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A26Schema.cs +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A26Schema.cs @@ -39,6 +39,8 @@ namespace BizHawk.Client.EmuHawk return StandardController(controller); case Atari2600ControllerTypes.Paddle: return PaddleController(controller); + case Atari2600ControllerTypes.Driving: + return DrivingController(controller); } } @@ -143,6 +145,47 @@ namespace BizHawk.Client.EmuHawk }; } + private static PadSchema DrivingController(int controller) + { + return new PadSchema + { + DisplayName = "Player " + controller, + IsConsole = false, + DefaultSize = new Size(334, 94), + MaxSize = new Size(334, 94), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Button", + DisplayName = "B1", + Location = new Point(5, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Wheel X 1", + DisplayName = "Wheel X 1", + Location = new Point(55, 17), + Type = PadSchema.PadInputType.FloatSingle, + TargetSize = new Size(128, 69), + MaxValue = 127, + MinValue = -127 + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Wheel X 2", + DisplayName = "Wheel X 2", + Location = new Point(193, 17), + Type = PadSchema.PadInputType.FloatSingle, + TargetSize = new Size(128, 69), + MaxValue = 127, + MinValue = -127 + }, + } + }; + } + private static PadSchema ConsoleButtons() { return new PadSchema diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs new file mode 100644 index 0000000000..cdbf9228c0 --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs @@ -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 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 bls = new List + { + 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 btns = new List(); + + 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 + }, + + } + }; + } + } +} diff --git a/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs b/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs index 8d017a8d14..a208731d81 100644 --- a/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs +++ b/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs @@ -79,7 +79,7 @@ namespace BizHawk.Client.EmuHawk }; } - public ColumnList Columns { get; } + public ColumnList Columns { get; set; } } private IEnumerable SelectedIndices => WatchListView.SelectedIndices.Cast(); diff --git a/BizHawk.Client.MultiHawk/BizHawk.Client.MultiHawk.csproj b/BizHawk.Client.MultiHawk/BizHawk.Client.MultiHawk.csproj index 210c22e89e..daf4e4ecc3 100644 --- a/BizHawk.Client.MultiHawk/BizHawk.Client.MultiHawk.csproj +++ b/BizHawk.Client.MultiHawk/BizHawk.Client.MultiHawk.csproj @@ -8,7 +8,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\output\ @@ -18,7 +19,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset @@ -33,6 +35,7 @@ 512 None + true @@ -244,4 +247,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.Common/BizHawk.Common.csproj b/BizHawk.Common/BizHawk.Common.csproj index 04d322b879..8b9b8cac99 100644 --- a/BizHawk.Common/BizHawk.Common.csproj +++ b/BizHawk.Common/BizHawk.Common.csproj @@ -22,7 +22,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\output\dll\ @@ -32,7 +33,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false @@ -107,4 +109,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj index af786ec123..d6d61bbf3b 100644 --- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj +++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj @@ -8,7 +8,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -20,7 +21,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -141,4 +143,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index df1a2562c7..d106a5e91a 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -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"; diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index dde7623f46..e279f00c5d 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -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? @@ -124,13 +132,16 @@ namespace BizHawk.Emulation.Common var sms_jp_21 = File("A8C1B39A2E41137835EDA6A5DE6D46DD9FADBAF2", 8192, "sms_jp_2.1.sms", "SMS BIOS 2.1 (Japan)"); var sms_us_1b = File("29091FF60EF4C22B1EE17AA21E0E75BAC6B36474", 8192, "sms_us_1.0b.sms", "SMS BIOS 1.0 (USA) (Proto)"); // ?? is this size correct? var sms_m404 = File("4A06C8E66261611DCE0305217C42138B71331701", 8192, "sms_m404.sms", "SMS BIOS (USA) (M404) (Proto)"); // ?? is this size correct? + var sms_kr = File("2FEAFD8F1C40FDF1BD5668F8C5C02E5560945B17", 131072, "sms_kr.sms", "SMS BIOS (Kr)"); // ?? is this size correct? Firmware("SMS", "Export", "SMS Bios (USA/Export)"); Firmware("SMS", "Japan", "SMS Bios (Japan)"); + Firmware("SMS", "Korea", "SMS Bios (Korea)"); Option("SMS", "Export", sms_us_13); Option("SMS", "Export", sms_us_1b); Option("SMS", "Export", sms_m404); Option("SMS", "Japan", sms_jp_21); + Option("SMS", "Korea", sms_kr); // PSX // http://forum.fobby.net/index.php?t=msg&goto=2763 [f] diff --git a/BizHawk.Emulation.Common/SystemLookup.cs b/BizHawk.Emulation.Common/SystemLookup.cs index f8e1a0de3c..4c6c692e46 100644 --- a/BizHawk.Emulation.Common/SystemLookup.cs +++ b/BizHawk.Emulation.Common/SystemLookup.cs @@ -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] diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 4a00050be4..0faaa3c4f6 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -42,7 +42,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset false false @@ -55,7 +56,8 @@ AnyCPU false prompt - AllRules.ruleset + + AllRules.ruleset false false @@ -256,6 +258,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Atari2600.cs @@ -413,6 +470,8 @@ ColecoVision.cs + + @@ -664,6 +723,8 @@ + + @@ -859,6 +920,7 @@ + @@ -1331,16 +1393,29 @@ + + + + + + + + + + + + + "$(SolutionDir)subwcrev.bat" "$(ProjectDir)" diff --git a/BizHawk.Emulation.Cores/CPUs/CP1610/CP1610.Execute.cs b/BizHawk.Emulation.Cores/CPUs/CP1610/CP1610.Execute.cs index c4f3db942d..c30c140bff 100644 --- a/BizHawk.Emulation.Cores/CPUs/CP1610/CP1610.Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/CP1610/CP1610.Execute.cs @@ -245,7 +245,10 @@ namespace BizHawk.Emulation.Cores.Components.CP1610 Interruptible = true; break; case 0x005: // TCI - throw new ArgumentException(UNEXPECTED_TCI); + cycles = 4; + Interruptible = false; + Console.WriteLine(UNEXPECTED_TCI); + break; case 0x006: // CLRC FlagC = false; cycles = 4; diff --git a/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs b/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs index 401f1fe397..1b475ef337 100644 --- a/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs +++ b/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs @@ -137,17 +137,6 @@ namespace BizHawk.Emulation.Common.Components.LR35902 // call interrupt processor // lowest bit set is highest priority - - if (interrupt_src.Bit(0) && interrupt_enable.Bit(0)) { int_src = 0; } - else if (interrupt_src.Bit(1) && interrupt_enable.Bit(1)) { int_src = 1; } - else if (interrupt_src.Bit(2) && interrupt_enable.Bit(2)) { int_src = 2; } - else if (interrupt_src.Bit(3) && interrupt_enable.Bit(3)) { int_src = 3; } - else if (interrupt_src.Bit(4) && interrupt_enable.Bit(4)) { int_src = 4; } - else { /*Console.WriteLine("No source"); }*/ throw new Exception("Interrupt without Source"); } - - - if ((interrupt_src & interrupt_enable) == 0) { FlagI = false; } - INTERRUPT_(); } else @@ -252,7 +241,7 @@ namespace BizHawk.Emulation.Common.Components.LR35902 SET_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; case EI: - EI_pending = 2; + if (EI_pending == 0) { EI_pending = 2; } break; case DI: interrupts_enabled = false; @@ -286,18 +275,6 @@ namespace BizHawk.Emulation.Common.Components.LR35902 } halted = false; // call interrupt processor - instr_pntr = 0; - // lowest bit set is highest priority - - if (interrupt_src.Bit(0) && interrupt_enable.Bit(0)) { int_src = 0; } - else if (interrupt_src.Bit(1) && interrupt_enable.Bit(1)) { int_src = 1; } - else if (interrupt_src.Bit(2) && interrupt_enable.Bit(2)) { int_src = 2; } - else if (interrupt_src.Bit(3) && interrupt_enable.Bit(3)) { int_src = 3; } - else if (interrupt_src.Bit(4) && interrupt_enable.Bit(4)) { int_src = 4; } - else { /*Console.WriteLine("No source"); } */throw new Exception("Interrupt without Source"); } - - if ((interrupt_src & interrupt_enable) == 0) { FlagI = false; } - INTERRUPT_(); } else if (FlagI) @@ -315,17 +292,16 @@ namespace BizHawk.Emulation.Common.Components.LR35902 if (OnExecFetch != null) OnExecFetch(RegPC); if (TraceCallback != null && !CB_prefix) TraceCallback(State()); FetchInstruction(ReadMemory(RegPC++)); - instr_pntr = 0; } else { - instr_pntr = 0; cur_instr = new ushort[] {IDLE, IDLE, IDLE, HALT }; } + instr_pntr = 0; break; case STOP: stopped = true; @@ -387,43 +363,14 @@ namespace BizHawk.Emulation.Common.Components.LR35902 case INT_GET: // check if any interrupts got cancelled along the way // interrupt src = 5 sets the PC to zero as observed - if (int_src == 0) - { - if (interrupt_enable.Bit(0)) { interrupt_src -= 1; } - else { int_src = 5; } - } - if (int_src == 1) - { - if (interrupt_enable.Bit(1)) { interrupt_src -= 2; } - else { int_src = 5; } - } - if (int_src == 2) - { - if (interrupt_enable.Bit(2)) { interrupt_src -= 4; } - else { int_src = 5; } - } - if (int_src == 3) - { - if (interrupt_enable.Bit(3)) { interrupt_src -= 8; } - else { int_src = 5; } - } - if (int_src == 4) - { - if (interrupt_enable.Bit(4)) { interrupt_src -= 16; } - else { int_src = 5; } - } - - // if we lost the interrupt, find the next highest interrupt, if any - if (int_src == 5) - { - if (interrupt_src.Bit(0) && interrupt_enable.Bit(0)) { int_src = 0; interrupt_src -= 1; } - else if (interrupt_src.Bit(1) && interrupt_enable.Bit(1)) { int_src = 1; interrupt_src -= 2; } - else if (interrupt_src.Bit(2) && interrupt_enable.Bit(2)) { int_src = 2; interrupt_src -= 4; } - else if (interrupt_src.Bit(3) && interrupt_enable.Bit(3)) { int_src = 3; interrupt_src -= 8; } - else if (interrupt_src.Bit(4) && interrupt_enable.Bit(4)) { int_src = 4; interrupt_src -= 16; } - else { int_src = 5; } - } + if (interrupt_src.Bit(0) && interrupt_enable.Bit(0)) { int_src = 0; interrupt_src -= 1; } + else if (interrupt_src.Bit(1) && interrupt_enable.Bit(1)) { int_src = 1; interrupt_src -= 2; } + else if (interrupt_src.Bit(2) && interrupt_enable.Bit(2)) { int_src = 2; interrupt_src -= 4; } + else if (interrupt_src.Bit(3) && interrupt_enable.Bit(3)) { int_src = 3; interrupt_src -= 8; } + else if (interrupt_src.Bit(4) && interrupt_enable.Bit(4)) { int_src = 4; interrupt_src -= 16; } + else { int_src = 5; } + if ((interrupt_src & interrupt_enable) == 0) { FlagI = false; } Regs[cur_instr[instr_pntr++]] = INT_vectors[int_src]; diff --git a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs index b3e30a9a30..bcfa5a80a3 100644 --- a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs +++ b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs @@ -48,23 +48,31 @@ namespace BizHawk.Emulation.Cores.Components.M6502 public TraceInfo State(bool disassemble = true) { - int notused; + if (!disassemble) + { + return new TraceInfo { + Disassembly = "", + RegisterInfo = "" + }; + } + + int length; + string rawbytes = ""; + string disasm = Disassemble(PC, out length); + + for (int i = 0; i < length; i++) + { + rawbytes += string.Format(" {0:X2}", PeekMemory((ushort)(PC + i))); + } return new TraceInfo { Disassembly = string.Format( - "{0:X4}: {1:X2} {2} ", - PC, - PeekMemory(PC), - disassemble ? Disassemble(PC, out notused) : "---").PadRight(26), + "{0:X4}: {1,-9} {2} ", + PC, rawbytes, disasm).PadRight(32), RegisterInfo = string.Format( - "A:{0:X2} X:{1:X2} Y:{2:X2} P:{3:X2} SP:{4:X2} Cy:{5} {6}{7}{8}{9}{10}{11}{12}{13}", - A, - X, - Y, - P, - S, - TotalExecutedCycles, + "A:{0:X2} X:{1:X2} Y:{2:X2} SP:{4:X2} P:{3:X2} {6}{7}{8}{9}{10}{11}{12}{13} Cy:{5}", + A, X, Y, P, S, TotalExecutedCycles, FlagN ? "N" : "n", FlagV ? "V" : "v", FlagT ? "T" : "t", diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs index c70e4aa503..eddf74d0a4 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs index 8dc1cd24ce..2c4f5c5204 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs @@ -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 }; } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs index a312975d78..8bc3938c7d 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs @@ -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; } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs index cfaf517ec7..adf7452c29 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs @@ -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(); + } } } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt b/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt index d11f79b637..10804fde94 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt @@ -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 diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs index 628243c19e..14e447bc65 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs @@ -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]; diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs index 71b39d6018..e708b02bac 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs @@ -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, diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs index 4a1c46cc03..c53b013a86 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs @@ -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, diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs index 46abee9809..f26582dd41 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs @@ -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 ReadHardware; public Action 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 FetchDB; + //this only calls when the first byte of an instruction is fetched. public Action 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); diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs b/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs index c5d795fcd5..d4e292ad18 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs @@ -12,35 +12,35 @@ namespace BizHawk.Emulation.Cores.Calculators { return new Dictionary { - ["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; } } diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.cs b/BizHawk.Emulation.Cores/Calculator/TI83.cs index cfe82f74eb..403f0fa7b8 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.cs @@ -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 diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs index 9f7dab9541..b89b26709b 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs @@ -34,7 +34,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 }); } - if (tapeDriveEnabled) + if (tapeDriveEnabled && (_board.TapeDrive.TapeDataDomain != null)) { domains.AddRange(new[] { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs index a1c4c358a1..a87e50610a 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs @@ -52,22 +52,32 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 public class C64SyncSettings { [DisplayName("VIC type")] - [Description("Set the type of video chip to use")] + [Description("Set the type of video chip to use\n" + + "PAL: ~50hz. All PAL games will expect this configuration.\n" + + "NTSC: ~60hz.This is the most common NTSC configuration. Every NTSC game should work with this configuration.\n" + + "NTSCOld: ~60hz.This was used in the very earliest systems and will exhibit problems on most modern games.\n" + + "Drean: ~60hz.This was manufactured for a very specific market and is not very compatible with timing sensitive games.\n")] [DefaultValue(VicType.Pal)] public VicType VicType { get; set; } [DisplayName("SID type")] - [Description("Set the type of sound chip to use")] + [Description("Set the type of sound chip to use\n" + + "OldR2, OldR3, OldR4AR: Original 6581 SID chip.\n" + + "NewR5: Updated 8580 SID chip.\n" + + "")] [DefaultValue(SidType.OldR2)] public SidType SidType { get; set; } [DisplayName("Tape drive type")] - [Description("Set the type of tape drive attached")] + [Description("Set the type of tape drive attached\n" + + "1531: Original Datasette device.")] [DefaultValue(TapeDriveType.None)] public TapeDriveType TapeDriveType { get; set; } [DisplayName("Disk drive type")] - [Description("Set the type of disk drive attached")] + [Description("Set the type of disk drive attached\n" + + "1541: Original disk drive and ROM.\n" + + "1541 - II: Improved model with some ROM bugfixes.")] [DefaultValue(DiskDriveType.None)] public DiskDriveType DiskDriveType { get; set; } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.MotherboardInterface.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.MotherboardInterface.cs index 71b0e0a029..37b606fb74 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.MotherboardInterface.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.MotherboardInterface.cs @@ -1,4 +1,6 @@ -namespace BizHawk.Emulation.Cores.Computers.Commodore64 +using System; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64 { public sealed partial class Motherboard { @@ -155,12 +157,12 @@ private int Sid_ReadPotX() { - return 0; + return 255; } private int Sid_ReadPotY() { - return 0; + return 255; } private int Vic_ReadMemory(int addr) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper000F.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper000F.cs index 755254ae39..ae623445af 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper000F.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper000F.cs @@ -119,6 +119,13 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge _currentBank = _banks[_bankNumber]; } + public override int ReadDE00(int addr) + { + BankSet(0); + + return 0; + } + public override void WriteDE00(int addr, int val) { BankSet(addr); @@ -126,6 +133,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge public override void SyncState(Serializer ser) { + ser.Sync("_bankMask", ref _bankMask); + ser.Sync("_bankNumber", ref _bankNumber); base.SyncState(ser); if (ser.IsReader) { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Cassette/TapeDrive.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Cassette/TapeDrive.cs index e5ef8a1e67..f41258ce44 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Cassette/TapeDrive.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Cassette/TapeDrive.cs @@ -32,7 +32,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cassette public override void SyncState(Serializer ser) { - _tape.SyncState(ser); + + if (_tape != null) { _tape.SyncState(ser); } } public void Insert(Tape tape) @@ -46,6 +47,19 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cassette } // Exposed for memory domains, should not be used for actual emulation implementation - public override byte[] TapeDataDomain => _tape.TapeDataDomain; + public override byte[] TapeDataDomain + { + get + { + if (_tape != null) + { + return _tape.TapeDataDomain; + } + else + { + return null; + } + } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.Registers.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.Registers.cs index 1c55076712..0ba58e1ceb 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.Registers.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.Registers.cs @@ -1,4 +1,7 @@ -namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS +using BizHawk.Common.NumberExtensions; +using System; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { public sealed partial class Cia { @@ -154,7 +157,7 @@ break; case 0xC: WriteRegister(addr, val); - TriggerInterrupt(8); + // TriggerInterrupt(8); break; case 0xD: if ((val & 0x80) != 0) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.cs index ad74ed207b..dccf7bab2f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Cia.cs @@ -15,6 +15,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS http://frodo.cebix.net/ */ + + // operation of the interrupt at the serial data port occurs in 2 instances: + // 1. Being in output mode and having a complete transfer as defined by clocking of timer A + // 2. Being in input mode and receiving 8 clocks from the /CNT pin + // This is TODO + private enum TimerState { Stop = 0, diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.Registers.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.Registers.cs index fe1fbc9d56..71f65cf74e 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.Registers.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.Registers.cs @@ -111,8 +111,15 @@ break; case 0x19: result = _potX; break; case 0x1A: result = _potY; break; - case 0x1B: result = _voiceOutput2 >> 4; break; - case 0x1C: result = _envelopeOutput2; break; + // these two registers are reading the sound output in real time, so we need to flush the output here + case 0x1B: + Flush(false); + result = _voiceOutput2 >> 4; + break; + case 0x1C: + Flush(false); + result = _envelopeOutput2; + break; } return result; @@ -138,12 +145,13 @@ // we want to only flush the filter when the filter is actually changed, that way // the FFT will not be impacted by small sample sizes from other changes - if (addr == 15 || addr == 16 || addr==17) + if ((addr == 0x15) || (addr == 0x16) || (addr == 0x17)) { Flush(true); } - else if (addr==18) + else if (addr == 0x18) { + // note: we only want to flush the filter here if the filter components are changing bool temp1 = (val & 0x10) != 0; bool temp2 = (val & 0x20) != 0; diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs index b5a431a523..d5d4d337dd 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs @@ -49,8 +49,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { _mixer = _outputBuffer_not_filtered[i] + _outputBuffer_filtered[i]; _mixer = _mixer >> 7; - _mixer = (_mixer * _volume) >> 4; - _mixer -= _volume << 8; + _mixer = (_mixer * _volume_at_sample_time[i]) >> 4; + _mixer -= _volume_at_sample_time[i] << 8; if (_mixer > 0x7FFF) { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs index f31731177a..781cfa569c 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs @@ -41,6 +41,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS private short[] _outputBuffer; private int[] _outputBuffer_filtered; private int[] _outputBuffer_not_filtered; + private int[] _volume_at_sample_time; private int _outputBufferIndex; private int filter_index; private int last_filtered_value; @@ -93,6 +94,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS _outputBuffer_filtered = new int[sampleRate]; _outputBuffer_not_filtered = new int[sampleRate]; + _volume_at_sample_time = new int[sampleRate]; } // ------------------------------------ @@ -190,6 +192,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { _outputBuffer_not_filtered[_outputBufferIndex] = temp_not_filtered; _outputBuffer_filtered[_outputBufferIndex] = temp_filtered; + _volume_at_sample_time[_outputBufferIndex] = _volume; _outputBufferIndex++; } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Registers.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Registers.cs index 0aca8d65f6..0e63f4c16f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Registers.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Registers.cs @@ -99,7 +99,7 @@ return 0x01 | ((_pointerVm & 0x3C00) >> 6) | ((_pointerCb & 0x7) << 1); case 0x19: - return 0x70 | (_intRaster ? 0x01 : 0x00) | + return 0x70 | (_rasterInterruptTriggered ? 0x01 : 0x00) | (_intSpriteDataCollision ? 0x02 : 0x00) | (_intSpriteCollision ? 0x04 : 0x00) | (_intLightPen ? 0x08 : 0x00) | @@ -207,7 +207,11 @@ case 0x19: // interrupts are cleared by writing a 1 if ((val & 0x01) != 0) + { _intRaster = false; + _rasterInterruptTriggered = false; + } + if ((val & 0x02) != 0) _intSpriteDataCollision = false; if ((val & 0x04) != 0) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.VideoProvider.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.VideoProvider.cs index 2dc768401e..3adf339c76 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.VideoProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.VideoProvider.cs @@ -21,24 +21,28 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS private int _pixBufferBorderIndex; // palette + // feos: these are the colors that come from pepto's final render at http://www.pepto.de/projects/colorvic/ + // these colors are also default at http://www.colodore.com/ + // colors from Vice's colodore.vpl, that were used here since the recent update, are somehow different + // I'm using the colors from pepto's render. long term, this should have some adjustment options private static readonly int[] Palette = { Colors.ARGB(0x00, 0x00, 0x00), - Colors.ARGB(0xFF, 0xFF, 0xFF), - Colors.ARGB(0x68, 0x37, 0x2B), - Colors.ARGB(0x70, 0xA4, 0xB2), - Colors.ARGB(0x6F, 0x3D, 0x86), - Colors.ARGB(0x58, 0x8D, 0x43), - Colors.ARGB(0x35, 0x28, 0x79), - Colors.ARGB(0xB8, 0xC7, 0x6F), - Colors.ARGB(0x6F, 0x4F, 0x25), - Colors.ARGB(0x43, 0x39, 0x00), - Colors.ARGB(0x9A, 0x67, 0x59), - Colors.ARGB(0x44, 0x44, 0x44), - Colors.ARGB(0x6C, 0x6C, 0x6C), - Colors.ARGB(0x9A, 0xD2, 0x84), - Colors.ARGB(0x6C, 0x5E, 0xB5), - Colors.ARGB(0x95, 0x95, 0x95) + Colors.ARGB(0xff, 0xff, 0xff), + Colors.ARGB(0x81, 0x33, 0x38), + Colors.ARGB(0x75, 0xce, 0xc8), + Colors.ARGB(0x8e, 0x3c, 0x97), + Colors.ARGB(0x56, 0xac, 0x4d), + Colors.ARGB(0x2e, 0x2c, 0x9b), + Colors.ARGB(0xed, 0xf1, 0x71), + Colors.ARGB(0x8e, 0x50, 0x29), + Colors.ARGB(0x55, 0x38, 0x00), + Colors.ARGB(0xc4, 0x6c, 0x71), + Colors.ARGB(0x4a, 0x4a, 0x4a), + Colors.ARGB(0x7b, 0x7b, 0x7b), + Colors.ARGB(0xa9, 0xff, 0x9f), + Colors.ARGB(0x70, 0x6d, 0xeb), + Colors.ARGB(0xb2, 0xb2, 0xb2) }; public int BackgroundColor => BgColor; diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs index 8bbc4d4d71..0d136ba9d8 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs @@ -242,23 +242,16 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS // start of rasterline if ((_cycle == RasterIrqLineXCycle && _rasterLine > 0) || (_cycle == RasterIrqLine0Cycle && _rasterLine == 0)) { - _rasterInterruptTriggered = false; + //_rasterInterruptTriggered = false; if (_rasterLine == LastDmaLine) _badlineEnable = false; - } - // rasterline IRQ compare - if (_rasterLine != _rasterInterruptLine) - { - _rasterInterruptTriggered = false; - } - else - { - if (!_rasterInterruptTriggered) + // IRQ compares are done here + if (_rasterLine == _rasterInterruptLine) { _rasterInterruptTriggered = true; - + // interrupt needs to be enabled to be set to true if (_enableIntRaster) { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs index e6540b972b..ddd47146a7 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/Serial/Drive1541.cs @@ -54,7 +54,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial public override void SyncState(Serializer ser) { ser.BeginSection("Disk"); - _disk.SyncState(ser); + if (_disk != null) { _disk.SyncState(ser); } ser.EndSection(); ser.Sync("BitHistory", ref _bitHistory); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IBeeperDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IBeeperDevice.cs new file mode 100644 index 0000000000..7367e3948f --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IBeeperDevice.cs @@ -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); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IJoystick.cs new file mode 100644 index 0000000000..6421e99af4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IJoystick.cs @@ -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 +{ + /// + /// Represents a spectrum joystick + /// + public interface IJoystick + { + /// + /// The type of joystick + /// + JoystickType JoyType { get; } + + /// + /// Array of all the possibly button press names + /// + string[] ButtonCollection { get; set; } + + /// + /// The player number that this controller is currently assigned to + /// + int PlayerNumber { get; set; } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + void SetJoyInput(string key, bool isPressed); + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + bool GetJoyInput(string key); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs new file mode 100644 index 0000000000..77282f1dfa --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs @@ -0,0 +1,84 @@ + + +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a spectrum keyboard + /// + public interface IKeyboard : IPortIODevice + { + /// + /// The calling spectrumbase class + /// + SpectrumBase _machine { get; } + + /// + /// The keyboard matrix for a particular spectrum model + /// + string[] KeyboardMatrix { get; set; } + + /// + /// Other keyboard keys that are not in the matrix + /// (usually keys derived from key combos) + /// + string[] NonMatrixKeys { get; set; } + + /// + /// Represents the spectrum key state + /// + int[] KeyLine { get; set; } + + /// + /// Resets the line status + /// + void ResetLineStatus(); + + /// + /// 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 + /// + bool IsIssue2Keyboard { get; set; } + + /// + /// Sets the spectrum key status + /// + /// + /// + void SetKeyStatus(string key, bool isPressed); + + /// + /// Gets the status of a spectrum key + /// + /// + /// + bool GetKeyStatus(string key); + + /// + /// Returns the query byte + /// + /// + /// + byte GetLineStatus(byte lines); + + /// + /// Reads a keyboard byte + /// + /// + /// + byte ReadKeyboardByte(ushort addr); + + /// + /// Looks up a key in the keyboard matrix and returns the relevent byte value + /// + /// + /// + byte GetByteFromKeyMatrix(string key); + + + + void SyncState(Serializer ser); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs new file mode 100644 index 0000000000..d359103d27 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs @@ -0,0 +1,72 @@ +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 +{ + /// + /// Represents a PSG device (in this case an AY-3-891x) + /// + public interface IPSG : ISoundProvider, IPortIODevice + { + /// + /// Initlization routine + /// + /// + /// + void Init(int sampleRate, int tStatesPerFrame); + + /// + /// Activates a register + /// + int SelectedRegister { get; set; } + + /// + /// Writes to the PSG + /// + /// + void PortWrite(int value); + + /// + /// Reads from the PSG + /// + int PortRead(); + + + /// + /// Resets the PSG + /// + void Reset(); + + /// + /// The volume of the AY chip + /// + int Volume { get; set; } + + /// + /// Called at the start of a frame + /// + void StartFrame(); + + /// + /// called at the end of a frame + /// + void EndFrame(); + + /// + /// Updates the sound based on number of frame cycles + /// + /// + void UpdateSound(int frameCycle); + + /// + /// IStatable serialization + /// + /// + void SyncState(Serializer ser); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs new file mode 100644 index 0000000000..2fbd89a0db --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs @@ -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 +{ + /// + /// Represents a device that utilizes port IN & OUT + /// + public interface IPortIODevice + { + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + bool ReadPort(ushort port, ref int result); + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + bool WritePort(ushort port, int result); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs new file mode 100644 index 0000000000..93b91348ac --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -0,0 +1,908 @@ +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 +{ + /// + /// Represents the tape device (or build-in datacorder as it was called +2 and above) + /// + public class DatacorderDevice : IPortIODevice + { + #region Construction + + private SpectrumBase _machine { get; set; } + private Z80A _cpu { get; set; } + private IBeeperDevice _buzzer { get; set; } + + /// + /// Default constructor + /// + public DatacorderDevice() + { + + } + + /// + /// Initializes the datacorder device + /// + /// + public void Init(SpectrumBase machine) + { + _machine = machine; + _cpu = _machine.CPU; + _buzzer = machine.BuzzerDevice; + } + + #endregion + + #region State Information + + /// + /// The index of the current tape data block that is loaded + /// + 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; + } + } + } + + /// + /// The current position within the current data block + /// + private int _position = 0; + public int Position + { + get + { + if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count) { return 0; } + else { return _position; } + } + } + + /// + /// Signs whether the tape is currently playing or not + /// + private bool _tapeIsPlaying = false; + public bool TapeIsPlaying + { + get { return _tapeIsPlaying; } + } + + /// + /// A list of the currently loaded data blocks + /// + private List _dataBlocks = new List(); + public List DataBlocks + { + get { return _dataBlocks; } + set { _dataBlocks = value; } + } + + /// + /// Stores the last CPU t-state value + /// + private long _lastCycle = 0; + + /// + /// Edge + /// + private int _waitEdge = 0; + + /// + /// Current tapebit state + /// + private bool currentState = false; + + #endregion + + #region Datacorder Device Settings + + /// + /// Signs whether the device should autodetect when the Z80 has entered into + /// 'load' mode and auto-play the tape if neccesary + /// + private bool _autoPlay; + public bool AutoPlay + { + get { return _machine.Spectrum.Settings.AutoLoadTape; } + set { _autoPlay = value; MonitorReset(); } + } + + + #endregion + + #region Emulator + + /// + /// 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) + /// + public void EndFrame() + { + MonitorFrame(); + } + + public void StartFrame() + { + //if (TapeIsPlaying && AutoPlay) + //FlashLoad(); + } + + #endregion + + #region Tape Controls + + /// + /// Starts the tape playing from the beginning of the current block + /// + 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 + AutoStopTape(); + RTZ(); + return; + } + + // update waitEdge with the current position in the current block + _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position]; + + // sign that the tape is now playing + _tapeIsPlaying = true; + } + } + + /// + /// Stops the tape + /// (should move to the beginning of the next block) + /// + 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; + } + + /// + /// Rewinds the tape to it's beginning (return to zero) + /// + public void RTZ() + { + Stop(); + _machine.Spectrum.OSD_TapeRTZ(); + _currentDataBlockIndex = 0; + } + + /// + /// Performs a block skip operation on the current tape + /// TRUE: skip forward + /// FALSE: skip backward + /// + /// + 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; + } + + /// + /// Inserts a new tape and sets up the tape device accordingly + /// + /// + 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; + } + } + + /// + /// Resets the tape + /// + public void Reset() + { + RTZ(); + } + + #endregion + + #region Tape Device Methods + + /// + /// Simulates the spectrum 'EAR' input reading data from the tape + /// + /// + /// + 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; + } + + /// + /// Flash loading implementation + /// (Deterministic Emulation must be FALSE) + /// + 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]; + + /// + /// Resets the TapeMonitor + /// + private void MonitorReset() + { + _lastINCycle = 0; + _monitorCount = 0; + _monitorLastPC = 0; + _monitorLastRegs = null; + } + + /// + /// An iteration of the monitor process + /// + 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 && _machine.Spectrum.Settings.AutoLoadTape) + { + if (!_tapeIsPlaying) + { + Play(); + _machine.Spectrum.OSD_TapePlayingAuto(); + } + + _monitorTimeOut = 50; + } + } + 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(); + } + + public int MaskableInterruptCount = 0; + + private void MonitorFrame() + { + if (_tapeIsPlaying && _machine.Spectrum.Settings.AutoLoadTape) + { + _monitorTimeOut--; + + if (_monitorTimeOut < 0) + { + AutoStopTape(); + return; + } + + // fallback in case usual monitor detection methods do not work + + // number of t-states since last IN operation + long diff = _machine.CPU.TotalExecutedCycles - _lastINCycle; + + // get current datablock + var block = DataBlocks[_currentDataBlockIndex]; + + // pause in ms at the end of the current block + int blockPause = block.PauseInMS; + + // timeout in t-states (equiv. to blockpause) + int timeout = ((_machine.ULADevice.FrameLength * 50) / 1000) * blockPause; + + // dont use autostop detection if block has no pause at the end + if (timeout == 0) + return; + + if (diff >= timeout * 2) + { + // There have been no attempted tape reads by the CPU within the double timeout period + // Autostop the tape + AutoStopTape(); + _lastCycle = _cpu.TotalExecutedCycles; + } + } + } + + #endregion + + #region IPortIODevice + + /// + /// Mask constants + /// + private const int TAPE_BIT = 0x40; + private const int EAR_BIT = 0x10; + private const int MIC_BIT = 0x08; + + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + public bool ReadPort(ushort port, ref int result) + { + if (TapeIsPlaying) + { + GetEarBit(_cpu.TotalExecutedCycles); + } + if (currentState) + { + result |= TAPE_BIT; + } + else + { + result &= ~TAPE_BIT; + } + + if (!TapeIsPlaying) + { + MonitorRead(); + } + + 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; + } + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + public bool WritePort(ushort port, int result) + { + if (!TapeIsPlaying) + { + currentState = ((byte)result & 0x10) != 0; + } + + return true; + } + + #endregion + + #region State Serialization + + /// + /// Bizhawk state serialization + /// + /// + 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs new file mode 100644 index 0000000000..edd54dd9b1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs @@ -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 +{ + /// + /// Cursor joystick + /// Maps to a combination of 0xf7fe and 0xeffe + /// + 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 + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "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; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + 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); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + var l = _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + return l; + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs new file mode 100644 index 0000000000..880e388f3a --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs @@ -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 + { + "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; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + if (isPressed) + _joyLine |= (1 << pos); + else + _joyLine &= ~(1 << pos); + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + return (_joyLine & (1 << pos)) != 0; + } + + #endregion + + /// + /// Active bits high + /// 0 0 0 F U D L R + /// + public int JoyLine + { + get { return _joyLine; } + set { _joyLine = value; } + } + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + 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" + }; + */ + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs new file mode 100644 index 0000000000..ba2d3ea467 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs @@ -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 +{ + /// + /// A null joystick object + /// + 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 + { + + }.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; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + if (isPressed) + _joyLine |= (1 << pos); + else + _joyLine &= ~(1 << pos); + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + return (_joyLine & (1 << pos)) != 0; + } + + #endregion + + /// + /// Active bits high + /// 0 0 0 F U D L R + /// + public int JoyLine + { + get { return _joyLine; } + set { _joyLine = value; } + } + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + 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" + }; + */ + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs new file mode 100644 index 0000000000..a3789b9e5d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs @@ -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 +{ + /// + /// Sinclair Joystick LEFT + /// Just maps to the standard keyboard and is read the same (from port 0xf7fe) + /// + 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 + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "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; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + 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); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs new file mode 100644 index 0000000000..82d9fd9857 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs @@ -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 +{ + /// + /// Sinclair Joystick RIGHT + /// Just maps to the standard keyboard and is read the same (from port 0xeffe) + /// + 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 + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "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; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + 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); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs new file mode 100644 index 0000000000..0616003465 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs @@ -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 +{ + /// + /// The 48k keyboard device + /// + 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(); + + 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 + + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + 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; + } + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Rom/RomData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Rom/RomData.cs new file mode 100644 index 0000000000..0f0f12110b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Rom/RomData.cs @@ -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 + { + /// + /// ROM Contents + /// + public byte[] RomBytes + { + get { return _romBytes; } + set { _romBytes = value; } + } + + /// + /// Useful ROM addresses that are needed during tape operations + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs new file mode 100644 index 0000000000..d2d015ff53 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs @@ -0,0 +1,805 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// 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 + /// + public class AYChip : IPSG + { + #region Device Fields + + /// + /// The emulated machine (passed in via constructor) + /// + 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 + + /// + /// Main constructor + /// + public AYChip(SpectrumBase machine) + { + _machine = machine; + } + + /// + /// Initialises the AY chip + /// + 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 + + /// + /// AY mixer panning configuration + /// + [Flags] + public enum AYPanConfig + { + MONO = 0, + ABC = 1, + ACB = 2, + BAC = 3, + BCA = 4, + CAB = 5, + CBA = 6, + } + + /// + /// The AY panning configuration + /// + public AYPanConfig PanningConfiguration + { + get + { + return _currentPanTab; + } + set + { + if (value != _currentPanTab) + { + _currentPanTab = value; + UpdateVolume(); + } + } + } + + /// + /// The AY chip output volume + /// (0 - 100) + /// + public int Volume + { + get + { + return _volume; + } + set + { + //value = Math.Max(0, value); + //value = Math.Max(100, value); + if (_volume == value) + { + return; + } + _volume = value; + UpdateVolume(); + } + } + + /// + /// The currently selected register + /// + public int SelectedRegister + { + get { return _activeRegister; } + set + { + _activeRegister = (byte)value; + } + } + + #endregion + + #region Public Methods + + /// + /// Resets the PSG + /// + 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); + */ + } + + /// + /// Reads the value from the currently selected register + /// + /// + public int PortRead() + { + return _registers[_activeRegister]; + } + + /// + /// Writes to the currently selected register + /// + /// + 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; + } + } + + /// + /// Start of frame + /// + public void StartFrame() + { + _audioBufferIndex = 0; + BufferUpdate(0); + } + + /// + /// End of frame + /// + public void EndFrame() + { + BufferUpdate(_tStatesPerFrame); + } + + /// + /// Updates the audiobuffer based on the current frame t-state + /// + /// + public void UpdateSound(int frameCycle) + { + BufferUpdate(frameCycle); + } + + #endregion + + #region Private Fields + + /// + /// Register indicies + /// + 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; + + /// + /// 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. + */ + /// + private int[] _registers = new int[16]; + + /// + /// The currently selected register + /// + private byte _activeRegister; + + /// + /// The frequency of the AY chip + /// + private static int _chipFrequency = 1773400; + + /// + /// The rendering resolution of the chip + /// + private double _resolution = 50D * 8D / _chipFrequency; + + /// + /// Channel generator state + /// + private int _bitA; + private int _bitB; + private int _bitC; + + /// + /// Envelope state + /// + private int _eState; + + /// + /// Envelope direction + /// + private int _eDirection; + + /// + /// Noise seed + /// + private int _noiseSeed; + + /// + /// Mixer state + /// + private int _bit0; + private int _bit1; + private int _bit2; + private int _bit3; + private int _bit4; + private int _bit5; + + /// + /// Noise generator state + /// + private int _bitN; + + /// + /// Envelope masks + /// + private int _eMaskA; + private int _eMaskB; + private int _eMaskC; + + /// + /// Amplitudes + /// + private int _vA; + private int _vB; + private int _vC; + + /// + /// Channel gen counters + /// + private int _countA; + private int _countB; + private int _countC; + + /// + /// Envelope gen counter + /// + private int _countE; + + /// + /// Noise gen counter + /// + private int _countN; + + /// + /// Channel gen dividers + /// + private int _dividerA; + private int _dividerB; + private int _dividerC; + + /// + /// Envelope gen divider + /// + private int _dividerE; + + /// + /// Noise gen divider + /// + private int _dividerN; + + /// + /// Panning table list + /// + private static List PanTabs = new List + { + // MONO + new uint[] { 50,50, 50,50, 50,50 }, + // ABC + new uint[] { 100,10, 66,66, 10,100 }, + // ACB + new uint[] { 100,10, 10,100, 66,66 }, + // BAC + new uint[] { 66,66, 100,10, 10,100 }, + // BCA + new uint[] { 10,100, 100,10, 66,66 }, + // CAB + new uint[] { 66,66, 10,100, 100,10 }, + // CBA + new uint[] { 10,100, 66,66, 100,10 } + }; + + /// + /// The currently selected panning configuration + /// + private AYPanConfig _currentPanTab = AYPanConfig.ABC; + + /// + /// The current volume + /// + private int _volume = 50; + + /// + /// Volume tables state + /// + private uint[][] _volumeTables; + + /// + /// Volume table to be used + /// + 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 + + /// + /// Forces an update of the volume tables + /// + private void UpdateVolume() + { + var vol = ((ulong)0xFFFF * (ulong)_volume / 100UL) - 20000 ; + _volumeTables = new uint[6][]; + + // parent array + for (int j = 0; j < _volumeTables.Length; j++) + { + _volumeTables[j] = new uint[32]; + + // child array + for (int i = 0; i < _volumeTables[j].Length; i++) + { + _volumeTables[j][i] = (uint)( + (PanTabs[(int)_currentPanTab][j] * AYVolumes[i] * vol) / + (3 * 65535 * 100)); + } + } + } + + private int mult_const; + + /// + /// Initializes timing information for the frame + /// + /// + /// + 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); + } + + /// + /// Updates the audiobuffer based on the current frame t-state + /// + /// + 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; + + /// + /// State serialization + /// + /// + public void SyncState(Serializer ser) + { + ser.BeginSection("PSG-AY"); + + ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); + ser.Sync("_sampleRate", ref _sampleRate); + ser.Sync("_samplesPerFrame", ref _samplesPerFrame); + ser.Sync("_tStatesPerSample", ref _tStatesPerSample); + ser.Sync("_audioBufferIndex", ref _audioBufferIndex); + ser.Sync("_audioBuffer", ref _audioBuffer, false); + + ser.Sync("_registers", ref _registers, false); + ser.Sync("_activeRegister", ref _activeRegister); + ser.Sync("_bitA", ref _bitA); + ser.Sync("_bitB", ref _bitB); + ser.Sync("_bitC", ref _bitC); + ser.Sync("_eState", ref _eState); + ser.Sync("_eDirection", ref _eDirection); + ser.Sync("_noiseSeed", ref _noiseSeed); + ser.Sync("_bit0", ref _bit0); + ser.Sync("_bit1", ref _bit1); + ser.Sync("_bit2", ref _bit2); + ser.Sync("_bit3", ref _bit3); + ser.Sync("_bit4", ref _bit4); + ser.Sync("_bit5", ref _bit5); + ser.Sync("_bitN", ref _bitN); + ser.Sync("_eMaskA", ref _eMaskA); + ser.Sync("_eMaskB", ref _eMaskB); + ser.Sync("_eMaskC", ref _eMaskC); + ser.Sync("_vA", ref _vA); + ser.Sync("_vB", ref _vB); + ser.Sync("_vC", ref _vC); + ser.Sync("_countA", ref _countA); + ser.Sync("_countB", ref _countB); + ser.Sync("_countC", ref _countC); + ser.Sync("_countE", ref _countE); + ser.Sync("_countN", ref _countN); + ser.Sync("_dividerA", ref _dividerA); + ser.Sync("_dividerB", ref _dividerB); + ser.Sync("_dividerC", ref _dividerC); + ser.Sync("_dividerE", ref _dividerE); + ser.Sync("_dividerN", ref _dividerN); + ser.SyncEnum("_currentPanTab", ref _currentPanTab); + ser.Sync("_volume", ref nullDump); + + for (int i = 0; i < 6; i++) + { + ser.Sync("volTable" + i, ref _volumeTables[i], false); + } + + if (ser.IsReader) + _volume = _machine.Spectrum.Settings.AYVolume; + + ser.EndSection(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs new file mode 100644 index 0000000000..3a9516a63b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs @@ -0,0 +1,393 @@ + +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 +{ + /// + /// 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 + /// + public class Buzzer : ISoundProvider, IBeeperDevice + { + /// + /// Supplied values are right for 48K spectrum + /// These will deviate for 128k and up (as there are more T-States per frame) + /// + //public int SampleRate = 44100; //35000; + //public int SamplesPerFrame = 882; //699; + //public int TStatesPerSample = 79; //100; + + /// + /// Sample Rate + /// This usually has to be 44100 for ISoundProvider + /// + public int SampleRate + { + get { return _sampleRate; } + set { _sampleRate = value; } + } + + /// + /// Number of samples in one frame + /// + public int SamplesPerFrame + { + get { return _samplesPerFrame; } + set { _samplesPerFrame = value; } + } + + /// + /// Number of TStates in each sample + /// + public int TStatesPerSample + { + get { return _tStatesPerSample; } + set { _tStatesPerSample = value; } + } + + /// + /// The tape loading volume + /// Accepts an int 0-100 value + /// + private int _tapeVolume; + public int TapeVolume + { + get + { + return VolumeConverterOut(_tapeVolume); + } + set + { + _tapeVolume = VolumeConverterIn(value); + } + } + + /// + /// The EAR beeper volume + /// + private int _earVolume; + public int EarVolume + { + get + { + return VolumeConverterOut(_earVolume); + } + set + { + _earVolume = VolumeConverterIn(value); + } + } + + /// + /// Takes an int 0-100 and returns the relevant short volume to output + /// + /// + /// + private int VolumeConverterIn(int vol) + { + int maxLimit = short.MaxValue / 3; + int increment = maxLimit / 100; + + return vol * increment; + } + + /// + /// Takes an short volume and returns the relevant int value 0-100 + /// + /// + /// + private int VolumeConverterOut(int shortvol) + { + int maxLimit = short.MaxValue / 3; + int increment = maxLimit / 100; + + if (shortvol > maxLimit) + shortvol = maxLimit; + + return shortvol / increment; + } + + + private SpectrumBase _machine; + + /// + /// State fields + /// + private long _frameStart; + private bool _tapeMode; + private long _tStatesPerFrame; + private int _sampleRate; + private int _samplesPerFrame; + private int _tStatesPerSample; + + /// + /// Pulses collected during the last frame + /// + public List Pulses { get; private set; } + + /// + /// The last pulse + /// + public bool LastPulse { get; set; } + + /// + /// The last T-State (cpu cycle) that the last pulse was received + /// + public long LastPulseTState { get; set; } + + #region Construction & Initialisation + + public Buzzer(SpectrumBase machine) + { + _machine = machine; + } + + /// + /// Initialises the buzzer + /// + 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(1000); + } + + #endregion + + /// + /// When the pulse value from the EAR output changes it is processed here + /// + /// + /// + 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; + } + + /// + /// New frame starts + /// + public void StartFrame() + { + //DiscardSamples(); + Pulses.Clear(); + LastPulseTState = 0; + } + + /// + /// Frame is completed + /// + 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)(_tapeVolume) : (short)0; + else + samples[sampleIndex++] = pulse.State ? (short)(_earVolume) : (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; + } + + /// + /// When the spectrum is set to receive tape input, the EAR output on the ULA is disabled + /// (so no buzzer sound is emitted) + /// + /// + 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(); + } + + + } + + +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs new file mode 100644 index 0000000000..140c1d136a --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs @@ -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 +{ + /// + /// The MIC and EAR pins in the spectrum deal in on/off pulses of varying lengths + /// This struct therefore represents 1 of these pulses + /// + public struct Pulse + { + /// + /// True: High State + /// False: Low State + /// + public bool State { get; set; } + + /// + /// Pulse length in Z80 T-States (cycles) + /// + public long Length { get; set; } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs new file mode 100644 index 0000000000..cee9f524da --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -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 + { + /// + /// Original Sinclair Spectrum 16K model + /// + ZXSpectrum16, + + /// + /// Sinclair Spectrum 48K model + /// + ZXSpectrum48, + + /// + /// Sinclair Spectrum 128K model + /// + ZXSpectrum128, + + /// + /// Sinclair Spectrum 128 +2 model + /// + ZXSpectrum128Plus2, + + /// + /// Sinclair Spectrum 128 +2a model (same as the +3 just without disk drive) + /// + ZXSpectrum128Plus2a, + + /// + /// Sinclair Spectrum 128 +3 model + /// + ZXSpectrum128Plus3 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs new file mode 100644 index 0000000000..978d815c52 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -0,0 +1,292 @@ + +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Handles all ZX-level input + /// + 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; + + /// + /// Cycles through all the input callbacks + /// This should be done once per frame + /// + 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; + } + + /// + /// Instantiates the joysticks array + /// + /// + protected void InitJoysticks(List joys) + { + List jCollection = new List(); + + 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()); + } + } + + /// + /// Instantiates a new IJoystick object + /// + /// + /// + /// + 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; + } + + /// + /// Returns a IJoystick object depending on the type (or null if not found) + /// + /// + /// + protected IJoystick LocateUniqueJoystick(JoystickType type) + { + return JoystickCollection.Where(a => a.JoyType == type).FirstOrDefault(); + } + + /// + /// Signs whether input read has been requested + /// This forms part of the IEmulator LagFrame implementation + /// + private bool inputRead; + public bool InputRead + { + get { return inputRead; } + set { inputRead = value; } + } + + } +} + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs new file mode 100644 index 0000000000..de2f5206d7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs @@ -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 + + /// + /// The tape or disk image(s) that are passed in from the main ZXSpectrum class + /// + protected List mediaImages { get; set; } + + /// + /// Tape images + /// + protected List tapeImages { get; set; } + + /// + /// Disk images + /// + protected List diskImages { get; set; } + + /// + /// The index of the currently 'loaded' tape image + /// + 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(); + } + } + + /// + /// The index of the currently 'loaded' disk image + /// + 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(); + } + } + + /// + /// Called on first instantiation (and subsequent core reboots) + /// + /// + protected void InitializeMedia(List files) + { + mediaImages = files; + LoadAllMedia(); + Spectrum.OSD_TapeInit(); + } + + /// + /// Attempts to load all media into the relevant structures + /// + protected void LoadAllMedia() + { + tapeImages = new List(); + diskImages = new List(); + + 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(); + } + + /// + /// Attempts to load a tape into the tape device based on tapeMediaIndex + /// + protected void LoadTapeMedia() + { + TapeDevice.LoadTape(tapeImages[tapeMediaIndex]); + } + + /// + /// Attempts to load a disk into the disk device based on diskMediaIndex + /// + protected void LoadDiskMedia() + { + throw new NotImplementedException("+3 disk drive device not yet implemented"); + } + + /// + /// Identifies and sorts the various media types + /// + /// + 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs new file mode 100644 index 0000000000..436e23e558 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Memory * + /// + public abstract partial class SpectrumBase + { + #region Memory Fields & Properties + + /// + /// ROM Banks + /// + public byte[] ROM0 = new byte[0x4000]; + public byte[] ROM1 = new byte[0x4000]; + public byte[] ROM2 = new byte[0x4000]; + public byte[] ROM3 = new byte[0x4000]; + + /// + /// RAM Banks + /// + 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 + + /// + /// Signs that the shadow screen is now displaying + /// Note: normal screen memory in RAM5 is not altered, the ULA just outputs Screen1 instead (RAM7) + /// + protected bool SHADOWPaged; + + /// + /// Index of the current RAM page + /// /// 128k, +2/2a and +3 only + /// + public int RAMPaged; + + /// + /// Signs that all paging is disabled + /// If this is TRUE, then 128k and above machines need a hard reset before paging is allowed again + /// + protected bool PagingDisabled; + + /// + /// Index of the currently paged ROM + /// 128k, +2/2a and +3 only + /// + protected int ROMPaged; + public virtual int _ROMpaged + { + get { return ROMPaged; } + set { ROMPaged = value; } + } + + /* + * +3/+2A only + */ + + /// + /// High bit of the ROM selection (in normal paging mode) + /// + protected bool ROMhigh = false; + + /// + /// Low bit of the ROM selection (in normal paging mode) + /// + protected bool ROMlow = false; + + /// + /// Signs that the +2a/+3 special paging mode is activated + /// + protected bool SpecialPagingMode; + + /// + /// Index of the current special paging mode (0-3) + /// + protected int PagingConfiguration; + + #endregion + + + + #region Memory Related Methods + + /// + /// Simulates reading from the bus + /// Paging should be handled here + /// + /// + /// + public abstract byte ReadBus(ushort addr); + + /// + /// Pushes a value onto the data bus that should be valid as long as the interrupt is true + /// + /// + /// + public virtual byte PushBus() + { + return 0xFF; + } + + /// + /// Simulates writing to the bus + /// Paging should be handled here + /// + /// + /// + public virtual void WriteBus(ushort addr, byte value) + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public abstract byte ReadMemory(ushort addr); + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public abstract void WriteMemory(ushort addr, byte value); + + /// + /// Sets up the ROM + /// + /// + public abstract void InitROM(RomData romData); + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// + /// + /// + public virtual byte FetchScreenMemory(ushort addr) + { + var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000)); + return value; + } + + #endregion + + #region Helper Methods + + /// + /// Detects whether this is a 48k machine (or a 128k in 48k mode) + /// + /// + public virtual bool IsIn48kMode() + { + if (this.GetType() == typeof(ZX48) || + this.GetType() == typeof(ZX16) || + PagingDisabled) + { + return true; + } + else + return false; + } + + /// + /// Monitors ROM access + /// Used to auto start/stop the tape device when appropriate + /// + /// + 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 + // This is sometimes used when the tape is to be stopped + // + if (addr == 56) + { + TapeDevice.MaskableInterruptCount++; + + if (TapeDevice.MaskableInterruptCount > 20) + { + TapeDevice.MaskableInterruptCount = 0; + TapeDevice.AutoStopTape(); + } + + //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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs new file mode 100644 index 0000000000..3b53dd28e3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -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 +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Port Access * + /// + public abstract partial class SpectrumBase + { + /// + /// The last OUT data that was sent to the ULA + /// + protected byte LastULAOutByte; + public byte LASTULAOutByte + { + get { return LastULAOutByte; } + set { LastULAOutByte = value; } + } + + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public abstract byte ReadPort(ushort port); + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public abstract void WritePort(ushort port, byte value); + + /// + /// Increments the CPU totalCycles counter by the tStates value specified + /// + /// + public virtual void PortContention(int tStates) + { + CPU.TotalExecutedCycles += tStates; + } + + /// + /// 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 + /// + /// + 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; + } + } + } + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs new file mode 100644 index 0000000000..a1e44356d1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -0,0 +1,354 @@ +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 +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Main properties / fields / contruction* + /// + public abstract partial class SpectrumBase + { + #region Devices + + /// + /// The calling ZXSpectrum class (piped in via constructor) + /// + public ZXSpectrum Spectrum { get; set; } + + /// + /// Reference to the instantiated Z80 cpu (piped in via constructor) + /// + public Z80A CPU { get; set; } + + /// + /// ROM and extended info + /// + public RomData RomData { get; set; } + + /// + /// The emulated ULA device + /// + public ULABase ULADevice { get; set; } + + /// + /// The spectrum buzzer/beeper + /// + public IBeeperDevice BuzzerDevice { get; set; } + + /// + /// Device representing the AY-3-8912 chip found in the 128k and up spectrums + /// + public IPSG AYDevice { get; set; } + + /// + /// The spectrum keyboard + /// + public virtual IKeyboard KeyboardDevice { get; set; } + + /// + /// The spectrum datacorder device + /// + public virtual DatacorderDevice TapeDevice { get; set; } + + /// + /// Holds the currently selected joysticks + /// + public virtual IJoystick[] JoystickCollection { get; set; } + + /// + /// Signs whether the disk motor is on or off + /// + protected bool DiskMotorState; + + /// + /// +3/2a printer port strobe + /// + protected bool PrinterPortStrobe; + + #endregion + + #region Emulator State + + /// + /// Signs whether the frame has ended + /// + public bool FrameCompleted; + + /// + /// Overflow from the previous frame (in Z80 cycles) + /// + public int OverFlow; + + /// + /// The total number of frames rendered + /// + public int FrameCount; + + /// + /// The current cycle (T-State) that we are at in the frame + /// + public long _frameCycles; + + /// + /// Stores where we are in the frame after each CPU cycle + /// + public long LastFrameStartCPUTick; + + /// + /// Gets the current frame cycle according to the CPU tick count + /// + public virtual long CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick; + + /// + /// Non-Deterministic bools + /// + public bool _render; + public bool _renderSound; + + #endregion + + #region Constants + + /// + /// Mask constants & misc + /// + 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 + + /// + /// Executes a single frame + /// + 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(); + } + + // 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 + + /// + /// Hard reset of the emulated machine + /// + public virtual void HardReset() + { + ULADevice.ResetInterrupt(); + ROMPaged = 0; + SpecialPagingMode = false; + RAMPaged = 0; + CPU.RegPC = 0; + + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("IY", 0xFFFF); + Spectrum.SetCpuRegister("IX", 0xFFFF); + Spectrum.SetCpuRegister("AF", 0xFFFF); + Spectrum.SetCpuRegister("BC", 0xFFFF); + Spectrum.SetCpuRegister("DE", 0xFFFF); + Spectrum.SetCpuRegister("HL", 0xFFFF); + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); + Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); + Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); + Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); + + CPU.Regs[CPU.I] = 0; + CPU.Regs[CPU.R] = 0; + + TapeDevice.Reset(); + if (AYDevice != null) + AYDevice.Reset(); + + byte[][] rams = new byte[][] + { + RAM0, + RAM1, + RAM2, + RAM3, + RAM4, + RAM5, + RAM6, + RAM7 + }; + + foreach (var r in rams) + { + for (int i = 0; i < r.Length; i++) + { + r[i] = 0x00; + } + } + } + + /// + /// Soft reset of the emulated machine + /// + public virtual void SoftReset() + { + ULADevice.ResetInterrupt(); + ROMPaged = 0; + SpecialPagingMode = false; + RAMPaged = 0; + CPU.RegPC = 0; + + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("IY", 0xFFFF); + Spectrum.SetCpuRegister("IX", 0xFFFF); + Spectrum.SetCpuRegister("AF", 0xFFFF); + Spectrum.SetCpuRegister("BC", 0xFFFF); + Spectrum.SetCpuRegister("DE", 0xFFFF); + Spectrum.SetCpuRegister("HL", 0xFFFF); + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); + Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); + Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); + Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); + + CPU.Regs[CPU.I] = 0; + CPU.Regs[CPU.R] = 0; + + TapeDevice.Reset(); + if (AYDevice != null) + AYDevice.Reset(); + + byte[][] rams = new byte[][] + { + RAM0, + RAM1, + RAM2, + RAM3, + RAM4, + RAM5, + RAM6, + RAM7 + }; + + foreach (var r in rams) + { + for (int i = 0; i < r.Length; i++) + { + r[i] = 0x00; + } + } + } + + #endregion + + #region IStatable + + public void SyncState(Serializer ser) + { + ser.BeginSection("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); + ((AYChip)AYDevice as AYChip).PanningConfiguration = Spectrum.Settings.AYPanConfig; + } + + + ser.Sync("tapeMediaIndex", ref tapeMediaIndex); + TapeMediaIndex = tapeMediaIndex; + + ser.Sync("diskMediaIndex", ref diskMediaIndex); + DiskMediaIndex = diskMediaIndex; + + TapeDevice.SyncState(ser); + + ser.EndSection(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs new file mode 100644 index 0000000000..79479b4789 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -0,0 +1,760 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// ULA (Uncommitted Logic Array) implementation + /// + public abstract class ULABase : IVideoProvider + { + #region General + + /// + /// Length of the frame in T-States + /// + public int FrameLength; + + /// + /// Emulated clock speed + /// + public int ClockSpeed; + + /// + /// Whether machine is late or early timing model + /// + public bool LateTiming; //currently not implemented + + /// + /// The current cycle within the current frame + /// + public int CurrentTStateInFrame; + + + protected SpectrumBase _machine; + + #endregion + + #region Palettes + + /// + /// The standard ULA palette + /// + 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 + + /// + /// T-State at which to start applying contention + /// + public int contentionStartPeriod; + /// + /// T-State at which to end applying contention + /// + public int contentionEndPeriod; + /// + /// T-State memory contention delay mapping + /// + public byte[] contentionTable; + + #endregion + + #region Screen Rendering + + /// + /// Video output buffer + /// + public int[] ScreenBuffer; + /// + /// Display memory + /// + protected byte[] screen; + /// + /// Attribute memory lookup (mapped 1:1 to screen for convenience) + /// + protected short[] attr; + /// + /// T-State display mapping + /// + protected short[] tstateToDisp; + /// + /// Table that stores T-State to screen/attribute address values + /// + public short[] floatingBusTable; + /// + /// Cycle at which the last render update took place + /// + protected long lastTState; + /// + /// T-States elapsed since last render update + /// + protected long elapsedTStates; + /// + /// T-State of top left raster pixel + /// + protected int actualULAStart; + /// + /// Offset into display memory based on current T-State + /// + protected int screenByteCtr; + /// + /// Offset into current pixel of rasterizer + /// + protected int ULAByteCtr; + /// + /// The current border colour + /// + public int borderColour; + /// + /// Signs whether the colour flash is ON or OFF + /// + protected bool flashOn = false; + + + protected int flashCounter; + public int FlashCounter + { + get { return flashCounter; } + set + { + flashCounter = value; + } + } + + + /// + /// Internal frame counter used for flasher operations + /// + protected int frameCounter = 0; + + /// + /// Last 8-bit bitmap read from display memory + /// (Floating bus implementation) + /// + protected int lastPixelValue; + /// + /// Last 8-bit attr val read from attribute memory + /// (Floating bus implementation) + /// + protected int lastAttrValue; + /// + /// Last 8-bit bitmap read from display memory+1 + /// (Floating bus implementation) + /// + protected int lastPixelValuePlusOne; + /// + /// Last 8-bit attr val read from attribute memory+1 + /// (Floating bus implementation) + /// + protected int lastAttrValuePlusOne; + + /// + /// Used to create the non-border display area + /// + protected int TtateAtLeft; + protected int TstateWidth; + protected int TstateAtTop; + protected int TstateHeight; + protected int TstateAtRight; + protected int TstateAtBottom; + + /// + /// Total T-States in one scanline + /// + protected int TstatesPerScanline; + /// + /// Total pixels in one scanline + /// + protected int ScanLineWidth; + /// + /// Total chars in one PRINT row + /// + protected int CharRows; + /// + /// Total chars in one PRINT column + /// + protected int CharCols; + /// + /// Total pixels in one display row + /// + protected int ScreenWidth; + /// + /// Total pixels in one display column + /// + protected int ScreenHeight; + /// + /// Total pixels in top border + /// + protected int BorderTopHeight; + /// + /// Total pixels in bottom border + /// + protected int BorderBottomHeight; + /// + /// Total pixels in left border width + /// + protected int BorderLeftWidth; + /// + /// Total pixels in right border width + /// + protected int BorderRightWidth; + /// + /// Memory address of display start + /// + protected int DisplayStart; + /// + /// Total number of bytes of display memory + /// + protected int DisplayLength; + /// + /// Memory address of attribute start + /// + protected int AttributeStart; + /// + /// Total number of bytes of attribute memory + /// + protected int AttributeLength; + + /// + /// Raised when ULA has finished painting the entire screen + /// + public bool needsPaint = false; + + #endregion + + #region Interrupt + + /// + /// The number of T-States that the INT pin is simulated to be held low + /// + public int InterruptPeriod; + + /// + /// The longest instruction cycle count + /// + protected int LongestOperationCycles = 23; + + /// + /// Signs that an interrupt has been raised in this frame. + /// + protected bool InterruptRaised; + + /// + /// Signs that the interrupt signal has been revoked + /// + protected bool InterruptRevoked; + + /// + /// Resets the interrupt - this should happen every frame in order to raise + /// the VBLANK interrupt in the proceding frame + /// + public virtual void ResetInterrupt() + { + InterruptRaised = false; + InterruptRevoked = false; + } + + /// + /// Generates an interrupt in the current phase if needed + /// + /// + 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 + + /// + /// Resets the ULA chip + /// + public abstract void Reset(); + + /// + /// Builds the contention table for the emulated model + /// + public abstract void BuildContentionTable(); + + /// + /// Returns true if the given memory address should be contended + /// + /// + /// + public abstract bool IsContended(int addr); + + /// + /// Contends the machine for a given address + /// + /// + 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; + } + + /// + /// Resets render state once interrupt is generated + /// + public void ULAUpdateStart() + { + ULAByteCtr = 0; + screenByteCtr = DisplayStart; + lastTState = actualULAStart; + needsPaint = true; + } + + /// + /// Flash processing + /// + public void CalcFlashCounter() + { + flashCounter++; + + if (flashCounter > 15) + { + flashOn = !flashOn; + flashCounter = 0; + } + } + + /// + /// Builds the T-State to attribute map used with the floating bus + /// + 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); + } + } + + /// + /// Updates the screen buffer based on the number of T-States supplied + /// + /// + 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs new file mode 100644 index 0000000000..e769fc46ef --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -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 +--------+--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + 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; + } + + /// + /// Sets up the ROM + /// + /// + /// + 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]; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs new file mode 100644 index 0000000000..e01525017a --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -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 + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + 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; + } + */ + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs new file mode 100644 index 0000000000..59d9108290 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs new file mode 100644 index 0000000000..3ea955ddca --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -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 + + /// + /// Main constructor + /// + /// + /// + public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List 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 + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs new file mode 100644 index 0000000000..72c546da51 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs @@ -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 +{ + /// + /// The +2 is almost identical to the 128k from an emulation point of view + /// There are just a few small changes in the ROMs + /// + public partial class ZX128Plus2 : ZX128 + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) + : base(spectrum, cpu, borderType, files, joysticks) + { + + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs new file mode 100644 index 0000000000..265a413002 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs @@ -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. + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + 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; + } + + /// + /// Sets up the ROM + /// + /// + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs new file mode 100644 index 0000000000..24e7a13760 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -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 + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + 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; + } + + /// + /// +3 and 2a overidden method + /// + 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; } + } + + /// + /// Override port contention + /// +3/2a does not have the same ULA IO contention + /// + /// + public override void ContendPortAddress(ushort addr) + { + CPU.TotalExecutedCycles += 4; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs new file mode 100644 index 0000000000..c6547833ad --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs @@ -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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs new file mode 100644 index 0000000000..3cd1f695d5 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs @@ -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 + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus2a(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs new file mode 100644 index 0000000000..bead71f975 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -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. + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + 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; + } + + /// + /// Sets up the ROM + /// + /// + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs new file mode 100644 index 0000000000..e56c23440b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -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 + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + 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; + } + + /// + /// +3 and 2a overidden method + /// + 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; } + } + + /// + /// Override port contention + /// +3/2a does not have the same ULA IO contention + /// + /// + public override void ContendPortAddress(ushort addr) + { + CPU.TotalExecutedCycles += 4; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs new file mode 100644 index 0000000000..3b2291a082 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs @@ -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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs new file mode 100644 index 0000000000..916232bcb6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -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 + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs new file mode 100644 index 0000000000..3f6499e916 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -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 + + /// + /// Main constructor + /// + /// + /// + public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) + : base(spectrum, cpu, borderType, files, joysticks) + { + + } + + #endregion + + + #region Memory + + /* 48K Spectrum has NO memory paging + * + * + | Bank 0 | + | | + | | + | screen | + 0x4000 +--------+ + | ROM 0 | + | | + | | + | | + 0x0000 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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; + } + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs new file mode 100644 index 0000000000..5f7958f4d9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -0,0 +1,137 @@ +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 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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]; + case 2: return RAM1[index]; + case 3: return RAM2[index]; + default: return 0; + } + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + 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); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs new file mode 100644 index 0000000000..0cb5b9c7c1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -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 + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + 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); + + } + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs new file mode 100644 index 0000000000..3b4eca3dc3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs new file mode 100644 index 0000000000..9dacc597f3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -0,0 +1,56 @@ +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 + + /// + /// Main constructor + /// + /// + /// + public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List 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 + + #region Reset + + public override void HardReset() + { + base.HardReset(); + + Random rn = new Random(); + for (int d = 0; d < 6912; d++) + { + RAM0[d] = (byte)rn.Next(255); + } + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs new file mode 100644 index 0000000000..def321b245 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs @@ -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 +{ + /// + /// Represents the different types of media serializer avaiable + /// + public enum MediaSerializationType + { + NONE, + TZX, + TAP + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs new file mode 100644 index 0000000000..44c99c28ff --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs @@ -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 +{ + /// + /// Abtract class that represents all Media Serializers + /// + public abstract class MediaSerializer + { + /// + /// The type of serializer + /// + public abstract MediaSerializationType FormatType { get; } + + /// + /// Signs whether this class can be used to serialize + /// + public virtual bool IsSerializer + { + get + { + return false; + } + } + + /// + /// Signs whether this class can be used to de-serialize + /// + public virtual bool IsDeSerializer + { + get + { + return false; + } + } + + /// + /// Serialization method + /// + /// + public virtual void Serialize(byte[] data) + { + throw new NotImplementedException(this.GetType().ToString() + + "Serialize operation is not implemented for this serializer"); + } + + /// + /// DeSerialization method + /// + /// + public virtual void DeSerialize(byte[] data) + { + throw new NotImplementedException(this.GetType().ToString() + + "DeSerialize operation is not implemented for this serializer"); + } + + #region Static Tools + + /// + /// Converts an int32 value into a byte array + /// + /// + /// + 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; + } + + /// + /// Returns an int32 from a byte array based on offset + /// + /// + /// + /// + protected static int GetInt32(byte[] buf, int offsetIndex) + { + return buf[offsetIndex] | buf[offsetIndex + 1] << 8 | buf[offsetIndex + 2] << 16 | buf[offsetIndex + 3] << 24; + } + + /// + /// Returns an uint16 from a byte array based on offset + /// + /// + /// + /// + protected static ushort GetWordValue(byte[] buf, int offsetIndex) + { + return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8); + } + + /// + /// Updates a byte array with a uint16 value based on offset + /// + /// + /// + /// + protected static void SetWordValue(byte[] buf, int offsetIndex, ushort value) + { + buf[offsetIndex] = (byte)value; + buf[offsetIndex + 1] = (byte)(value >> 8); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs new file mode 100644 index 0000000000..d0db311d27 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs @@ -0,0 +1,306 @@ +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 +{ + /// + /// Reponsible for TAP format serializaton + /// + public class TapSerializer : MediaSerializer + { + /// + /// The type of serializer + /// + private MediaSerializationType _formatType = MediaSerializationType.TAP; + public override MediaSerializationType FormatType + { + get + { + return _formatType; + } + } + + /// + /// Signs whether this class can be used to serialize + /// + public override bool IsSerializer { get { return false; } } + + /// + /// Signs whether this class can be used to de-serialize + /// + public override bool IsDeSerializer { get { return true; } } + + #region Construction + + private DatacorderDevice _datacorder; + + public TapSerializer(DatacorderDevice _tapeDevice) + { + _datacorder = _tapeDevice; + } + + #endregion + + + #region TAP Format Constants + + /// + /// Pilot pulse length + /// + public const int PILOT_PL = 2168; + + /// + /// Pilot pulses in the ROM header block + /// + public const int HEADER_PILOT_COUNT = 8063; + + /// + /// Pilot pulses in the ROM data block + /// + public const int DATA_PILOT_COUNT = 3223; + + /// + /// Sync 1 pulse length + /// + public const int SYNC_1_PL = 667; + + /// + /// Sync 2 pulse lenth + /// + public const int SYNC_2_PL = 735; + + /// + /// Bit 0 pulse length + /// + public const int BIT_0_PL = 855; + + /// + /// Bit 1 pulse length + /// + public const int BIT_1_PL = 1710; + + /// + /// End sync pulse length + /// + public const int TERM_SYNC = 947; + + /// + /// 1 millisecond pause + /// + public const int PAUSE_MS = 3500; + + /// + /// Used bit count in last byte + /// + public const int BIT_COUNT_IN_LAST = 8; + + #endregion + + /// + /// DeSerialization method + /// + /// + 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 dataPeriods = new List(); + + // 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); + + // default pause for tap files + tdb.PauseInMS = 1000; + + // add to the tapedatablock object + tdb.DataPeriods = dataPeriods; + + // add the raw data + tdb.BlockData = blockdata; + + // add block to the tape + _datacorder.DataBlocks.Add(tdb); + + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs new file mode 100644 index 0000000000..14fc54a460 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs @@ -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 +{ + /// + /// Represents the possible commands that can be raised from each tape block + /// + public enum TapeCommand + { + NONE, + STOP_THE_TAPE, + STOP_THE_TAPE_48K, + BEGIN_GROUP, + END_GROUP, + SHOW_MESSAGE, + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs new file mode 100644 index 0000000000..ee1a6bc4d8 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs @@ -0,0 +1,275 @@ +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 +{ + /// + /// Represents a tape block + /// + public class TapeDataBlock + { + /// + /// Either the TZX block ID, or -1 in the case of non-tzx blocks + /// + private int _blockID = -1; + public int BlockID + { + get { return _blockID; } + set { + _blockID = value; + + if (MetaData == null) + MetaData = new Dictionary(); + + AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString()); + } + } + + /// + /// The block type + /// + private BlockType _blockType; + public BlockType BlockDescription + { + get { return _blockType; } + set { + _blockType = value; + if (MetaData == null) + MetaData = new Dictionary(); + } + } + + /// + /// Byte array containing the raw block data + /// + private byte[] _blockData; + public byte[] BlockData + { + get { return _blockData; } + set { _blockData = value; } + } + + /// + /// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization) + /// Its basically tape information + /// + private byte[][] _tapeDescriptionData; + + /// + /// Returns the Tape Description Data in a human readable format + /// + public List TapeDescriptionData + { + get + { + List data = new List(); + + foreach (byte[] b in _tapeDescriptionData) + { + data.Add(Encoding.ASCII.GetString(b)); + } + + return data; + } + } + + + #region Block Meta Data + + /// + /// Dictionary of block related data + /// + public Dictionary MetaData { get; set; } + + /// + /// Adds a single metadata item to the Dictionary + /// + /// + /// + 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 + + + + /// + /// List containing the pulse timing values + /// + public List DataPeriods = new List(); + + /// + /// Command that is raised by this data block + /// (that may or may not need to be acted on) + /// + private TapeCommand _command = TapeCommand.NONE; + public TapeCommand Command + { + get { return _command; } + set { _command = value; } + } + + /// + /// The defined post-block pause + /// + private int _pauseInMS; + public int PauseInMS + { + get { return _pauseInMS; } + set { _pauseInMS = value; } + } + + + /// + /// Returns the data periods as an array + /// (primarily to aid in bizhawk state serialization) + /// + /// + public int[] GetDataPeriodsArray() + { + return DataPeriods.ToArray(); + } + + /// + /// Accepts an array of data periods and updates the DataPeriods list accordingly + /// (primarily to aid in bizhawk state serialization) + /// + /// + public void SetDataPeriodsArray(int[] periodArray) + { + DataPeriods = new List(); + + if (periodArray == null) + return; + + DataPeriods = periodArray.ToList(); + } + + /// + /// Bizhawk state serialization + /// + /// + 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(); + } + } + + /// + /// The types of TZX blocks + /// + 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 + } + + /// + /// Different title possibilities + /// + 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs new file mode 100644 index 0000000000..8b3bc9e1ec --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs @@ -0,0 +1,1678 @@ +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 +{ + /// + /// Reponsible for TZX format serializaton + /// + public class TzxSerializer : MediaSerializer + { + /// + /// The type of serializer + /// + private MediaSerializationType _formatType = MediaSerializationType.TZX; + public override MediaSerializationType FormatType + { + get + { + return _formatType; + } + } + + /// + /// Signs whether this class can be used to serialize + /// + public override bool IsSerializer { get { return false; } } + + /// + /// Signs whether this class can be used to de-serialize + /// + public override bool IsDeSerializer { get { return true; } } + + /// + /// Working list of generated tape data blocks + /// + private List _blocks = new List(); + + /// + /// Position counter + /// + private int _position = 0; + + /// + /// Object to keep track of loops - this assumes there is only one loop at a time + /// + private List> _loopCounter = new List>(); + + #region Construction + + private DatacorderDevice _datacorder; + + public TzxSerializer(DatacorderDevice _tapeDevice) + { + _datacorder = _tapeDevice; + } + + #endregion + + /// + /// DeSerialization method + /// + /// + public override void DeSerialize(byte[] data) + { + // clear existing tape blocks + _datacorder.DataBlocks.Clear(); + +/* + // TZX Header + length: 10 bytes + Offset Value Type Description + 0x00 "ZXTape!" ASCII[7] TZX signature + 0x07 0x1A BYTE End of text file marker + 0x08 1 BYTE TZX major revision number + 0x09 20 BYTE TZX minor revision number +*/ + + // check whether this is a valid tzx format file by looking at the identifier in the header + // (first 7 bytes of the file) + string ident = Encoding.ASCII.GetString(data, 0, 7); + // and 'end of text' marker + byte eotm = data[7]; + + // version info + int majorVer = data[8]; + int minorVer = data[9]; + + if (ident != "ZXTape!" || eotm != 0x1A) + { + // this is not a valid TZX format file + throw new Exception(this.GetType().ToString() + + "This is not a valid TZX format file"); + } + + // iterate through each block + _position = 10; + while (_position < data.Length) + { + // block ID is the first byte in a new block + int ID = data[_position++]; + + // process the data + ProcessBlock(data, ID); + } + + } + + /// + /// Processes a TZX block + /// + /// + /// + private void ProcessBlock(byte[] data, int id) + { + // process based on detected block ID + switch (id) + { + // ID 10 - Standard Speed Data Block + case 0x10: + ProcessBlockID10(data); + break; + // ID 11 - Turbo Speed Data Block + case 0x11: + ProcessBlockID11(data); + break; + // ID 12 - Pure Tone + case 0x12: + ProcessBlockID12(data); + break; + // ID 13 - Pulse sequence + case 0x13: + ProcessBlockID13(data); + break; + // ID 14 - Pure Data Block + case 0x14: + ProcessBlockID14(data); + break; + // ID 15 - Direct Recording + case 0x15: + ProcessBlockID15(data); + break; + // ID 18 - CSW Recording + case 0x18: + ProcessBlockID18(data); + break; + // ID 19 - Generalized Data Block + case 0x19: + ProcessBlockID19(data); + break; + // ID 20 - Pause (silence) or 'Stop the Tape' command + case 0x20: + ProcessBlockID20(data); + break; + // ID 21 - Group start + case 0x21: + ProcessBlockID21(data); + break; + // ID 22 - Group end + case 0x22: + ProcessBlockID22(data); + break; + // ID 23 - Jump to block + case 0x23: + ProcessBlockID23(data); + break; + // ID 24 - Loop start + case 0x24: + ProcessBlockID24(data); + break; + // ID 25 - Loop end + case 0x25: + ProcessBlockID25(data); + break; + // ID 26 - Call sequence + case 0x26: + ProcessBlockID26(data); + break; + // ID 27 - Return from sequence + case 0x27: + ProcessBlockID27(data); + break; + // ID 28 - Select block + case 0x28: + ProcessBlockID28(data); + break; + // ID 2A - Stop the tape if in 48K mode + case 0x2A: + ProcessBlockID2A(data); + break; + // ID 2B - Set signal level + case 0x2B: + ProcessBlockID2B(data); + break; + // ID 30 - Text description + case 0x30: + ProcessBlockID30(data); + break; + // ID 31 - Message block + case 0x31: + ProcessBlockID31(data); + break; + // ID 32 - Archive info + case 0x32: + ProcessBlockID32(data); + break; + // ID 33 - Hardware type + case 0x33: + ProcessBlockID33(data); + break; + // ID 35 - Custom info block + case 0x35: + ProcessBlockID35(data); + break; + // ID 5A - "Glue" block + case 0x5A: + ProcessBlockID5A(data); + break; + + #region Depreciated Blocks + + // ID 16 - C64 ROM Type Data Block + case 0x16: + ProcessBlockID16(data); + break; + // ID 17 - C64 Turbo Tape Data Block + case 0x17: + ProcessBlockID17(data); + break; + // ID 34 - Emulation info + case 0x34: + ProcessBlockID34(data); + break; + // ID 40 - Snapshot block + case 0x40: + ProcessBlockID40(data); + break; + + #endregion + + default: + ProcessUnidentifiedBlock(data); + break; + } + } + + #region TZX Block Processors + + #region ID 10 - Standard Speed Data Block +/* length: [02,03]+04 + Offset Value Type Description + 0x00 - WORD Pause after this block (ms.) {1000} + 0x02 N WORD Length of data that follow + 0x04 - BYTE[N] Data as in .TAP files + + This block must be replayed with the standard Spectrum ROM timing values - see the values in + curly brackets in block ID 11. The pilot tone consists in 8063 pulses if the first data byte + (flag byte) is < 128, 3223 otherwise. This block can be used for the ROM loading routines AND + for custom loading routines that use the same timings as ROM ones do. */ + private void ProcessBlockID10(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x10; + t.BlockDescription = BlockType.Standard_Speed_Data_Block; + t.DataPeriods = new List(); + + int pauseLen = GetWordValue(data, _position); + if (pauseLen == 0) + pauseLen = 1000; + + t.PauseInMS = pauseLen; + + int blockLen = GetWordValue(data, _position + 2); + + _position += 4; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Standard, pauseLen); + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 11 - Turbo Speed Data Block +/* length: [0F,10,11]+12 + Offset Value Type Description + 0x00 - WORD Length of PILOT pulse {2168} + 0x02 - WORD Length of SYNC first pulse {667} + 0x04 - WORD Length of SYNC second pulse {735} + 0x06 - WORD Length of ZERO bit pulse {855} + 0x08 - WORD Length of ONE bit pulse {1710} + 0x0A - WORD Length of PILOT tone (number of pulses) {8063 header (flag<128), 3223 data (flag>=128)} + 0x0C - BYTE Used bits in the last byte (other bits should be 0) {8} + (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00, + where MSb is the leftmost bit, LSb is the rightmost bit) + 0x0D - WORD Pause after this block (ms.) {1000} + 0x0F N BYTE[3] Length of data that follow + 0x12 - BYTE[N] Data as in .TAP files + + This block is very similar to the normal TAP block but with some additional info on the timings and other important + differences. The same tape encoding is used as for the standard speed data block. If a block should use some non-standard + sync or pilot tones (i.e. all sorts of protection schemes) then use the next three blocks to describe it.*/ + private void ProcessBlockID11(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x11; + t.BlockDescription = BlockType.Turbo_Speed_Data_Block; + t.DataPeriods = new List(); + + int pilotPL = GetWordValue(data, _position); + int sync1P = GetWordValue(data, _position + 2); + int sync2P = GetWordValue(data, _position + 4); + int bit0P = GetWordValue(data, _position + 6); + int bit1P = GetWordValue(data, _position + 8); + int pilotTL = GetWordValue(data, _position + 10); + int bitinbyte = data[_position + 12]; + int pause = GetWordValue(data, _position + 13); + + int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x0F); + + _position += 0x12; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Turbo, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte); + + t.PauseInMS = pause; + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 12 - Pure Tone +/* length: 04 + Offset Value Type Description + 0x00 - WORD Length of one pulse in T-states + 0x02 - WORD Number of pulses + + This will produce a tone which is basically the same as the pilot tone in the ID 10, ID 11 blocks. You can define how + long the pulse is and how many pulses are in the tone. */ + private void ProcessBlockID12(byte[] data) + { + int blockLen = 4; + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x12; + t.BlockDescription = BlockType.Pure_Tone; + t.DataPeriods = new List(); + t.PauseInMS = 0; + + // get values + int pulseLength = GetWordValue(data, _position); + int pulseCount = GetWordValue(data, _position + 2); + + t.AddMetaData(BlockDescriptorTitle.Pulse_Length, pulseLength.ToString() + " T-States"); + t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString()); + + // build period information + for (int p = 0; p < pulseCount; p++) + { + t.DataPeriods.Add(pulseLength); + } + + // add the block + _datacorder.DataBlocks.Add(t); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 13 - Pulse sequence +/* length: [00]*02+01 + Offset Value Type Description + 0x00 N BYTE Number of pulses + 0x01 - WORD[N] Pulses' lengths + + This will produce N pulses, each having its own timing. Up to 255 pulses can be stored in this block; this is useful for non-standard + sync tones used by some protection schemes. */ + private void ProcessBlockID13(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x13; + t.BlockDescription = BlockType.Pulse_Sequence; + t.DataPeriods = new List(); + + t.PauseInMS = 0; + + // get pulse count + int pulseCount = data[_position]; + t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString()); + _position++; + + // build period information + for (int p = 0; p < pulseCount; p++, _position += 2) + { + // get pulse length + int pulseLength = GetWordValue(data, _position); + t.AddMetaData(BlockDescriptorTitle.Needs_Parsing, "Pulse " + p + " Length\t" + pulseLength.ToString() + " T-States"); + t.DataPeriods.Add(pulseLength); + } + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 14 - Pure Data Block +/* length: [07,08,09]+0A + Offset Value Type Description + 0x00 - WORD Length of ZERO bit pulse + 0x02 - WORD Length of ONE bit pulse + 0x04 - BYTE Used bits in last byte (other bits should be 0) + (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00, + where MSb is the leftmost bit, LSb is the rightmost bit) + 0x05 - WORD Pause after this block (ms.) + 0x07 N BYTE[3] Length of data that follow + 0x0A - BYTE[N] Data as in .TAP files + + This is the same as in the turbo loading data block, except that it has no pilot or sync pulses. */ + private void ProcessBlockID14(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x14; + t.BlockDescription = BlockType.Pure_Data_Block; + t.DataPeriods = new List(); + + int pilotPL = 0; + int sync1P = 0; + int sync2P = 0; + int bit0P = GetWordValue(data, _position + 0); + int bit1P = GetWordValue(data, _position + 2); + int pilotTL = 0; + int bitinbyte = data[_position + 4]; + int pause = GetWordValue(data, _position + 5); + + int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x07); + + _position += 0x0A; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Pure, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte); + + t.PauseInMS = pause; + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 15 - Direct Recording +/* length: [05,06,07]+08 + Offset Value Type Description + 0x00 - WORD Number of T-states per sample (bit of data) + 0x02 - WORD Pause after this block in milliseconds (ms.) + 0x04 - BYTE Used bits (samples) in last byte of data (1-8) + (e.g. if this is 2, only first two samples of the last byte will be played) + 0x05 N BYTE[3] Length of samples' data + 0x08 - BYTE[N] Samples data. Each bit represents a state on the EAR port (i.e. one sample). + MSb is played first. + + This block is used for tapes which have some parts in a format such that the turbo loader block cannot be used. + This is not like a VOC file, since the information is much more compact. Each sample value is represented by one bit only + (0 for low, 1 for high) which means that the block will be at most 1/8 the size of the equivalent VOC. + The preferred sampling frequencies are 22050 or 44100 Hz (158 or 79 T-states/sample). + Please, if you can, don't use other sampling frequencies. + Please use this block only if you cannot use any other block. */ + private void ProcessBlockID15(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x15; + t.BlockDescription = BlockType.Direct_Recording; + t.DataPeriods = new List(); + + // get values + int samLen = GetInt32(data, _position + 5); + int samSize = 0xFFFFFF & samLen; + + int tStatesPerSample = GetWordValue(data, _position); + int pauseAfterBlock = GetWordValue(data, _position + 2); + int usedBitsInLastByte = data[_position + 4]; + + // skip to samples data + _position += 8; + + int pulseLength = 0; + int pulseCount = 0; + + // ascertain the pulse count + for (int i = 0; i < samSize; i++) + { + for (int p = 0x80; p != 0; p >>= 1) + { + if (((data[_position + i] ^ pulseLength) & p) != 0) + { + pulseCount++; + pulseLength ^= -1; + } + } + } + + // get the pulses + t.DataPeriods = new List(pulseCount + 2); + int tStateCount = 0; + pulseLength = 0; + for (int i = 1; i < samSize; i++) + { + for (int p = 0x80; p != 0; p >>= 1) + { + tStateCount += tStatesPerSample; + if (((data[_position] ^ pulseLength) & p) != 0) + { + t.DataPeriods.Add(tStateCount); + pulseLength ^= -1; + tStateCount = 0; + } + } + + // incrememt position + _position++; + } + + // get the pulses in the last byte of data + for (int p = 0x80; p != (byte)(0x80 >> usedBitsInLastByte); p >>= 1) + { + tStateCount += tStatesPerSample; + if (((data[_position] ^ pulseLength) & p) != 0) + { + t.DataPeriods.Add(tStateCount); + pulseLength ^= -1; + tStateCount = 0; + } + } + + // add final pulse + t.DataPeriods.Add(tStateCount); + + // add end of block pause + if (pauseAfterBlock > 0) + { + t.DataPeriods.Add(3500 * pauseAfterBlock); + } + + t.PauseInMS = pauseAfterBlock; + + // increment position + _position++; + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 18 - CSW Recording +/* length: [00,01,02,03]+04 + Offset Value Type Description + 0x00 10+N DWORD Block length (without these four bytes) + 0x04 - WORD Pause after this block (in ms). + 0x06 - BYTE[3] Sampling rate + 0x09 - BYTE Compression type + 0x01: RLE + 0x02: Z-RLE + 0x0A - DWORD Number of stored pulses (after decompression, for validation purposes) + 0x0E - BYTE[N] CSW data, encoded according to the CSW file format specification. + + This block contains a sequence of raw pulses encoded in CSW format v2 (Compressed Square Wave). */ + private void ProcessBlockID18(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x18; + t.BlockDescription = BlockType.CSW_Recording; + t.DataPeriods = new List(); + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 19 - Generalized Data Block +/* length: [00,01,02,03]+04 + Offset Value Type Description + 0x00 - DWORD Block length (without these four bytes) + 0x04 - WORD Pause after this block (ms) + 0x06 TOTP DWORD Total number of symbols in pilot/sync block (can be 0) + 0x0A NPP BYTE Maximum number of pulses per pilot/sync symbol + 0x0B ASP BYTE Number of pilot/sync symbols in the alphabet table (0=256) + 0x0C TOTD DWORD Total number of symbols in data stream (can be 0) + 0x10 NPD BYTE Maximum number of pulses per data symbol + 0x11 ASD BYTE Number of data symbols in the alphabet table (0=256) + 0x12 - SYMDEF[ASP] Pilot and sync symbols definition table + This field is present only if TOTP>0 + 0x12+ + (2*NPP+1)*ASP - PRLE[TOTP] Pilot and sync data stream + This field is present only if TOTP>0 + 0x12+ + (TOTP>0)*((2*NPP+1)*ASP)+ + TOTP*3 - SYMDEF[ASD] Data symbols definition table + This field is present only if TOTD>0 + 0x12+ + (TOTP>0)*((2*NPP+1)*ASP)+ + TOTP*3+ + (2*NPD+1)*ASD - BYTE[DS] Data stream + This field is present only if TOTD>0 + + This block has been specifically developed to represent an extremely wide range of data encoding techniques. + The basic idea is that each loading component (pilot tone, sync pulses, data) is associated to a specific sequence + of pulses, where each sequence (wave) can contain a different number of pulses from the others. + In this way we can have a situation where bit 0 is represented with 4 pulses and bit 1 with 8 pulses. + + ---- + SYMDEF structure format + Offset Value Type Description + 0x00 - BYTE Symbol flags + b0-b1: starting symbol polarity + 00: opposite to the current level (make an edge, as usual) - default + 01: same as the current level (no edge - prolongs the previous pulse) + 10: force low level + 11: force high level + 0x01 - WORD[MAXP] Array of pulse lengths. + + The alphabet is stored using a table where each symbol is a row of pulses. The number of columns (i.e. pulses) of the table is the + length of the longest sequence amongst all (MAXP=NPP or NPD, for pilot/sync or data blocks respectively); shorter waves are terminated by a + zero-length pulse in the sequence. + Any number of data symbols is allowed, so we can have more than two distinct waves; for example, imagine a loader which writes two bits at a + time by encoding them with four distinct pulse lengths: this loader would have an alphabet of four symbols, each associated to a specific + sequence of pulses (wave). + ---- + ---- + PRLE structure format + Offset Value Type Description + 0x00 - BYTE Symbol to be represented + 0x01 - WORD Number of repetitions + + Most commonly, pilot and sync are repetitions of the same pulse, thus they are represented using a very simple RLE encoding structure which stores + the symbol and the number of times it must be repeated. + Each symbol in the data stream is represented by a string of NB bits of the block data, where NB = ceiling(Log2(ASD)). + Thus the length of the whole data stream in bits is NB*TOTD, or in bytes DS=ceil(NB*TOTD/8). + ---- */ + private void ProcessBlockID19(byte[] data) + { + string test = "dgfg"; + } + #endregion + + #region ID 20 - Pause (silence) or 'Stop the Tape' command +/* length: 02 + Offset Value Type Description + 0x00 - WORD Pause duration (ms.) + + This will make a silence (low amplitude level (0)) for a given time in milliseconds. If the value is 0 then the + emulator or utility should (in effect) STOP THE TAPE, i.e. should not continue loading until the user or emulator requests it. */ + private void ProcessBlockID20(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x20; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Pause_or_Stop_the_Tape; + + int pauseDuration = GetWordValue(data, _position); + if (pauseDuration != 0) + { + //t.BlockDescription = "Pause: " + pauseDuration + " ms"; + } + else + { + //t.BlockDescription = "[STOP THE TAPE]"; + } + + t.PauseInMS = pauseDuration; + + if (pauseDuration == 0) + { + // issue stop the tape command + t.Command = TapeCommand.STOP_THE_TAPE; + // add 1ms period + t.DataPeriods.Add(3500); + pauseDuration = -1; + } + else + { + // this is actually just a pause + pauseDuration = 3500 * pauseDuration; + } + + // add end of block pause + t.DataPeriods.Add(pauseDuration); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advanced position to next block + _position += 2; + } + #endregion + + #region ID 21 - Group start +/* length: [00]+01 + Offset Value Type Description + 0x00 L BYTE Length of the group name string + 0x01 - CHAR[L] Group name in ASCII format (please keep it under 30 characters long) + + This block marks the start of a group of blocks which are to be treated as one single (composite) block. + This is very handy for tapes that use lots of subblocks like Bleepload (which may well have over 160 custom loading blocks). + You can also give the group a name (example 'Bleepload Block 1'). + For each group start block, there must be a group end block. Nesting of groups is not allowed. */ + private void ProcessBlockID21(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x21; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Group_Start; + + int nameLength = data[_position]; + _position++; + + string name = Encoding.ASCII.GetString(data, _position, nameLength); + //t.BlockDescription = "[GROUP: " + name + "]"; + t.Command = TapeCommand.BEGIN_GROUP; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += nameLength; + + ; + } + #endregion + + #region ID 22 - Group end +/* length: 00 + + This indicates the end of a group. This block has no body. */ + private void ProcessBlockID22(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x22; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Group_End; + t.Command = TapeCommand.END_GROUP; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 23 - Jump to block +/* length: 02 + Offset Value Type Description + 0x00 - WORD Relative jump value + + This block will enable you to jump from one block to another within the file. The value is a signed short word + (usually 'signed short' in C); Some examples: + Jump 0 = 'Loop Forever' - this should never happen + Jump 1 = 'Go to the next block' - it is like NOP in assembler ;) + Jump 2 = 'Skip one block' + Jump -1 = 'Go to the previous block' + All blocks are included in the block count!. */ + private void ProcessBlockID23(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x23; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Jump_to_Block; + + int relativeJumpValue = GetWordValue(data, _position); + string result = string.Empty; + + switch(relativeJumpValue) + { + case 0: + result = "Loop Forever"; + break; + case 1: + result = "To Next Block"; + break; + case 2: + result = "Skip One Block"; + break; + case -1: + result = "Go to Previous Block"; + break; + } + + //t.BlockDescription = "[JUMP BLOCK - " + result +"]"; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 2; + } + #endregion + + #region ID 24 - Loop start +/* length: 02 + Offset Value Type Description + 0x00 - WORD Number of repetitions (greater than 1) + + If you have a sequence of identical blocks, or of identical groups of blocks, you can use this block to tell how many times they should + be repeated. This block is the same as the FOR statement in BASIC. + For simplicity reasons don't nest loop blocks! */ + private void ProcessBlockID24(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x24; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Loop_Start; + + // loop should start from the next block + int loopStart = _datacorder.DataBlocks.Count() + 1; + + int numberOfRepetitions = GetWordValue(data, _position); + + // update loop counter + _loopCounter.Add( + new KeyValuePair( + loopStart, + numberOfRepetitions)); + + // update description + //t.BlockDescription = "[LOOP START - " + numberOfRepetitions + " times]"; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 2; + } + #endregion + + #region ID 25 - Loop end +/* length: 00 + + This is the same as BASIC's NEXT statement. It means that the utility should jump back to the start of the loop if it hasn't + been run for the specified number of times. + This block has no body. */ + private void ProcessBlockID25(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x25; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Loop_End; + + // get the most recent loop info + var loop = _loopCounter.LastOrDefault(); + + int loopStart = loop.Key; + int numberOfRepetitions = loop.Value; + + if (numberOfRepetitions == 0) + { + return; + } + + // get the number of blocks to loop + int blockCnt = _datacorder.DataBlocks.Count() - loopStart; + + // loop through each group to repeat + for (int b = 0; b < numberOfRepetitions; b++) + { + TapeDataBlock repeater = new TapeDataBlock(); + //repeater.BlockDescription = "[LOOP REPEAT - " + (b + 1) + "]"; + repeater.DataPeriods = new List(); + + // add the repeat block + _datacorder.DataBlocks.Add(repeater); + + // now iterate through and add the blocks to be repeated + for (int i = 0; i < blockCnt; i++) + { + var block = _datacorder.DataBlocks[loopStart + i]; + _datacorder.DataBlocks.Add(block); + } + } + } + #endregion + + #region ID 26 - Call sequence +/* length: [00,01]*02+02 + Offset Value Type Description + 0x00 N WORD Number of calls to be made + 0x02 - WORD[N] Array of call block numbers (relative-signed offsets) + + This block is an analogue of the CALL Subroutine statement. It basically executes a sequence of blocks that are somewhere else and + then goes back to the next block. Because more than one call can be normally used you can include a list of sequences to be called. + The 'nesting' of call blocks is also not allowed for the simplicity reasons. You can, of course, use the CALL blocks in the LOOP + sequences and vice versa. The value is relative for the obvious reasons - so that you can add some blocks in the beginning of the + file without disturbing the call values. Please take a look at 'Jump To Block' for reference on the values. */ + private void ProcessBlockID26(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x26; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Call_Sequence; + + int blockSize = 2 + 2 * GetWordValue(data, _position); + t.PauseInMS = 0; + + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 27 - Return from sequence +/* length: 00 + + This block indicates the end of the Called Sequence. The next block played will be the block after the last CALL block (or the next Call, + if the Call block had multiple calls). + Again, this block has no body. */ + private void ProcessBlockID27(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x27; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Return_From_Sequence; + t.PauseInMS = 0; + + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 28 - Select block +/* length: [00,01]+02 + Offset Value Type Description + 0x00 - WORD Length of the whole block (without these two bytes) + 0x02 N BYTE Number of selections + 0x03 - SELECT[N] List of selections + + ---- + SELECT structure format + Offset Value Type Description + 0x00 - WORD Relative Offset + 0x02 L BYTE Length of description text + 0x03 - CHAR[L] Description text (please use single line and max. 30 chars) + ---- + + This block is useful when the tape consists of two or more separately-loadable parts. With this block, you are able to select + one of the parts and the utility/emulator will start loading from that block. For example you can use it when the game has a + separate Trainer or when it is a multiload. Of course, to make some use of it the emulator/utility has to show a menu with the + selections when it encounters such a block. All offsets are relative signed words. */ + private void ProcessBlockID28(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x28; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Select_Block; + + int blockSize = 2 + GetWordValue(data, _position); + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 2A - Stop the tape if in 48K mode +/* length: 04 + Offset Value Type Description + 0x00 0 DWORD Length of the block without these four bytes (0) + + When this block is encountered, the tape will stop ONLY if the machine is an 48K Spectrum. This block is to be used for + multiloading games that load one level at a time in 48K mode, but load the entire tape at once if in 128K mode. + This block has no body of its own, but follows the extension rule. */ + private void ProcessBlockID2A(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x2A; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Stop_the_Tape_48K; + t.Command = TapeCommand.STOP_THE_TAPE_48K; + + int blockSize = 4 + GetWordValue(data, _position); + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 2B - Set signal level +/* length: 05 + Offset Value Type Description + 0x00 1 DWORD Block length (without these four bytes) + 0x04 - BYTE Signal level (0=low, 1=high) + + This block sets the current signal level to the specified value (high or low). It should be used whenever it is necessary to avoid any + ambiguities, e.g. with custom loaders which are level-sensitive. */ + private void ProcessBlockID2B(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x2B; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Set_Signal_Level; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 5; + } + #endregion + + #region ID 30 - Text description +/* length: [00]+01 + Offset Value Type Description + 0x00 N BYTE Length of the text description + 0x01 - CHAR[N] Text description in ASCII format + + This is meant to identify parts of the tape, so you know where level 1 starts, where to rewind to when the game ends, etc. + This description is not guaranteed to be shown while the tape is playing, but can be read while browsing the tape or changing + the tape pointer. + The description can be up to 255 characters long but please keep it down to about 30 so the programs can show it in one line + (where this is appropriate). + Please use 'Archive Info' block for title, authors, publisher, etc. */ + private void ProcessBlockID30(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x30; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Text_Description; + + int textLen = data[_position]; + _position++; + + string desc = Encoding.ASCII.GetString(data, _position, textLen); + + t.PauseInMS = 0; + + //t.BlockDescription = "[" + desc + "]"; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += textLen; + } + #endregion + + #region ID 31 - Message block +/* length: [01]+02 + Offset Value Type Description + 0x00 - BYTE Time (in seconds) for which the message should be displayed + 0x01 N BYTE Length of the text message + 0x02 - CHAR[N] Message that should be displayed in ASCII format + + This will enable the emulators to display a message for a given time. This should not stop the tape and it should not make silence. + If the time is 0 then the emulator should wait for the user to press a key. + The text message should: + stick to a maximum of 30 chars per line; + use single 0x0D (13 decimal) to separate lines; + stick to a maximum of 8 lines. + If you do not obey these rules, emulators may display your message in any way they like. */ + private void ProcessBlockID31(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x31; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Message_Block; + + _position++; + + int msgLen = data[_position]; + _position++; + + string desc = Encoding.ASCII.GetString(data, _position, msgLen); + + t.Command = TapeCommand.SHOW_MESSAGE; + + //t.BlockDescription = "[MESSAGE: " + desc + "]"; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += msgLen; + } + #endregion + + #region ID 32 - Archive info +/* length: [00,01]+02 + Offset Value Type Description + 0x00 - WORD Length of the whole block (without these two bytes) + 0x02 N BYTE Number of text strings + 0x03 - TEXT[N] List of text strings + + ---- + TEXT structure format + Offset Value Type Description + 0x00 - BYTE Text identification byte: + 00 - Full title + 01 - Software house/publisher + 02 - Author(s) + 03 - Year of publication + 04 - Language + 05 - Game/utility type + 06 - Price + 07 - Protection scheme/loader + 08 - Origin + FF - Comment(s) + 0x01 L BYTE Length of text string + 0x02 - CHAR[L] Text string in ASCII format + ---- + + Use this block at the beginning of the tape to identify the title of the game, author, publisher, year of publication, price (including + the currency), type of software (arcade adventure, puzzle, word processor, ...), protection scheme it uses (Speedlock 1, Alkatraz, ...) + and its origin (Original, Budget re-release, ...), etc. This block is built in a way that allows easy future expansion. + The block consists of a series of text strings. Each text has its identification number (which tells us what the text means) and then + the ASCII text. To make it possible to skip this block, if needed, the length of the whole block is at the beginning of it. + If all texts on the tape are in English language then you don't have to supply the 'Language' field + The information about what hardware the tape uses is in the 'Hardware Type' block, so no need for it here. */ + private void ProcessBlockID32(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x32; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Archive_Info; + + int blockLen = GetWordValue(data, 0); + _position += 2; + int stringCount = data[_position++]; + + // iterate through each string + for (int s = 0; s < stringCount; s++) + { + // identify the type of text + int type = data[_position++]; + + // get text length + int strLen = data[_position++]; + + string title = "Info: "; + + switch (type) + { + case 0x00: + title = "Full Title: "; + break; + case 0x01: + title = "Software House/Publisher: "; + break; + case 0x02: + title = "Author(s): "; + break; + case 0x03: + title = "Year of Publication: "; + break; + case 0x04: + title = "Language: "; + break; + case 0x05: + title = "Game/Utility Type: "; + break; + case 0x06: + title = "Price: "; + break; + case 0x07: + title = "Protection Scheme/Loader: "; + break; + case 0x08: + title = "Origin: "; + break; + case 0xFF: + title = "Comment(s): "; + break; + default: + break; + } + + // add title to description + //t.BlockDescription += title; + + // get string data + string val = Encoding.ASCII.GetString(data, _position, strLen); + //t.BlockDescription += val + " \n"; + + t.PauseInMS = 0; + + // advance to next string block + _position += strLen; + } + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 33 - Hardware type +/* length: [00]*03+01 + Offset Value Type Description + 0x00 N BYTE Number of machines and hardware types for which info is supplied + 0x01 - HWINFO[N] List of machines and hardware + + ---- + HWINFO structure format + Offset Value Type Description + 0x00 - BYTE Hardware type + 0x01 - BYTE Hardware ID + 0x02 - BYTE Hardware information: + 00 - The tape RUNS on this machine or with this hardware, + but may or may not use the hardware or special features of the machine. + 01 - The tape USES the hardware or special features of the machine, + such as extra memory or a sound chip. + 02 - The tape RUNS but it DOESN'T use the hardware + or special features of the machine. + 03 - The tape DOESN'T RUN on this machine or with this hardware. + ---- + + This blocks contains information about the hardware that the programs on this tape use. Please include only machines and hardware for + which you are 100% sure that it either runs (or doesn't run) on or with, or you know it uses (or doesn't use) the hardware or special + features of that machine. + If the tape runs only on the ZX81 (and TS1000, etc.) then it clearly won't work on any Spectrum or Spectrum variant, so there's no + need to list this information. + If you are not sure or you haven't tested a tape on some particular machine/hardware combination then do not include it in the list. + The list of hardware types and IDs is somewhat large, and may be found at the end of the format description. */ + private void ProcessBlockID33(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x33; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Hardware_Type; + + t.PauseInMS = 0; + + _position += 2; + int blockLen = GetWordValue(data, 0); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #region ID 35 - Custom info block +/* length: [10,11,12,13]+14 + Offset Value Type Description + 0x00 - CHAR[10] Identification string (in ASCII) + 0x10 L DWORD Length of the custom info + 0x14 - BYTE[L] Custom info + + This block can be used to save any information you want. For example, it might contain some information written by a utility, + extra settings required by a particular emulator, or even poke data. */ + private void ProcessBlockID35(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x35; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Custom_Info_Block; + + t.PauseInMS = 0; + + string info = Encoding.ASCII.GetString(data, _position, 0x10); + //t.BlockDescription = "[CUSTOM INFO: " + info + "]"; + _position += 0x10; + + int blockLen = BitConverter.ToInt32(data, _position); + _position += 4; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #region ID 5A - "Glue" block +/* length: 09 + Offset Value Type Description + 0x00 - BYTE[9] Value: { "XTape!",0x1A,MajR,MinR } + Just skip these 9 bytes and you will end up on the next ID. + + This block is generated when you merge two ZX Tape files together. It is here so that you can easily copy the files together and use + them. Of course, this means that resulting file would be 10 bytes longer than if this block was not used. All you have to do + if you encounter this block ID is to skip next 9 bytes. + If you can avoid using this block for this purpose, then do so; it is preferable to use a utility to join the two files and + ensure that they are both of the higher version number. */ + private void ProcessBlockID5A(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x5A; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Glue_Block; + + t.PauseInMS = 0; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 9; + } + #endregion + + #region UnDetected Blocks + + private void ProcessUnidentifiedBlock(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = -2; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Unsupported; + //t.BlockDescription = "[UNSUPPORTED - 0x" + data[_position - 1] + "]"; + + _position += GetInt32(data, _position) & 0xFFFFFF; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 4; + } + + #endregion + + #region Depreciated Blocks + + // These mostly should be ignored by ZXHawk - here for completeness + + #region ID 16 - C64 ROM Type Data Block + private void ProcessBlockID16(byte[] data) + { + + } + #endregion + + #region ID 17 - C64 Turbo Tape Data Block + private void ProcessBlockID17(byte[] data) + { + + } + #endregion + + #region ID 34 - Emulation info + private void ProcessBlockID34(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x34; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Emulation_Info; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 8; + } + #endregion + + #region ID 40 - Snapshot block + /* length: [01,02,03]+04 + Offset Value Type Description + 0x00 - BYTE Snapshot type: + 00: .Z80 format + 01: .SNA format + 0x01 L BYTE[3] Snapshot length + 0x04 - BYTE[L] Snapshot itself + + This would enable one to snapshot the game at the start and still have all the tape blocks (level data, etc.) in the same file. + Only .Z80 and .SNA snapshots are supported for compatibility reasons! + The emulator should take care of that the snapshot is not taken while the actual Tape loading is taking place (which doesn't do much sense). + And when an emulator encounters the snapshot block it should load it and then continue with the next block. */ + private void ProcessBlockID40(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x40; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Snapshot_Block; + + _position++; + + int blockLen = data[_position] | + data[_position + 1] << 8 | + data[_position + 2] << 16; + _position += 3; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #endregion + + #endregion + + #region DataBlockDecoder + + /// + /// Used to process either a standard or turbo data block + /// + /// + /// + /// + private TapeDataBlock DecodeDataBlock + ( + TapeDataBlock block, + byte[] blockdata, + DataBlockType dataBlockType, + int pauseAfterBlock, + int pilotCount, + + int pilotToneLength = 2168, + int sync1PulseLength = 667, + int sync2PulseLength = 735, + int bit0PulseLength = 855, + int bit1PulseLength = 1710, + int bitsInLastByte = 8 + ) + { + // first get the block description + string description = string.Empty; + + // 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.) + */ + + int blockSize = blockdata.Length; + + // dont get description info for Pure Data Blocks + if (dataBlockType != DataBlockType.Pure) + { + if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3) + { + // This is the program header + string fileName = Encoding.ASCII.GetString(blockdata.Skip(2).Take(10).ToArray()).Trim(); + + string type = ""; + if (blockdata[0] == 0x00) + { + type = "Program"; + block.AddMetaData(BlockDescriptorTitle.Program, fileName); + } + else + { + type = "Bytes"; + block.AddMetaData(BlockDescriptorTitle.Bytes, fileName); + } + + // 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"; + block.AddMetaData(BlockDescriptorTitle.Data_Bytes, (blockSize - 2).ToString() + " 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")); + block.AddMetaData(BlockDescriptorTitle.Undefined, description); + } + } + + // update metadata + switch (dataBlockType) + { + case DataBlockType.Standard: + case DataBlockType.Turbo: + + if (dataBlockType == DataBlockType.Standard) + block.BlockDescription = BlockType.Standard_Speed_Data_Block; + if (dataBlockType == DataBlockType.Turbo) + block.BlockDescription = BlockType.Turbo_Speed_Data_Block; + + block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Length, pilotToneLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Count, pilotCount.ToString() + " Pulses"); + block.AddMetaData(BlockDescriptorTitle.First_Sync_Length, sync1PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Second_Sync_Length, sync2PulseLength.ToString() + " T-States"); + break; + + case DataBlockType.Pure: + block.BlockDescription = BlockType.Pure_Data_Block; + break; + } + + block.AddMetaData(BlockDescriptorTitle.Zero_Bit_Length, bit0PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.One_Bit_Length, bit1PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Data_Length, blockSize.ToString() + " Bytes"); + block.AddMetaData(BlockDescriptorTitle.Bits_In_Last_Byte, bitsInLastByte.ToString() + " Bits"); + block.AddMetaData(BlockDescriptorTitle.Pause_After_Data, pauseAfterBlock.ToString() + " ms"); + + // calculate period information + List dataPeriods = new List(); + + // generate pilot pulses + + if (pilotCount > 0) + { + for (int i = 0; i < pilotCount; i++) + { + dataPeriods.Add(pilotToneLength); + } + + // add syncro pulses + dataPeriods.Add(sync1PulseLength); + dataPeriods.Add(sync2PulseLength); + } + + 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(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + if ((blockdata[i] & b) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + } + } + + // add the last byte + for (byte c = 0x80; c != (byte)(0x80 >> bitsInLastByte); c >>= 1) + { + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + } + + // add block pause if pause is not 0 + if (pauseAfterBlock != 0) + { + int actualPause = pauseAfterBlock * 3500; + dataPeriods.Add(actualPause); + } + + // add to the tapedatablock object + block.DataPeriods = dataPeriods; + + // add the raw data + block.BlockData = blockdata; + + return block; + } + + /// + /// Used to process either a standard or turbo data block + /// + /// + /// + /// + private TapeDataBlock DecodeDataBlock + ( + TapeDataBlock block, + byte[] blockData, + DataBlockType dataBlockType, + int pauseAfterBlock, + + int pilotToneLength = 2168, + int sync1PulseLength = 667, + int sync2PulseLength = 735, + int bit0PulseLength = 855, + int bit1PulseLength = 1710, + int bitsInLastByte = 8 + ) + { + + // pilot count needs to be ascertained from flag byte + int pilotCount; + if (blockData[0] < 128) + pilotCount = 8063; + else + pilotCount = 3223; + + // now we can decode + var nBlock = DecodeDataBlock + ( + block, + blockData, + dataBlockType, + pauseAfterBlock, + pilotCount, + pilotToneLength, + sync1PulseLength, + sync2PulseLength, + bit0PulseLength, + bit1PulseLength, + bitsInLastByte + ); + + + return nBlock; + } + + #endregion + } + + public enum DataBlockType + { + Standard, + Turbo, + Pure + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs new file mode 100644 index 0000000000..303eda38b7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs @@ -0,0 +1,288 @@ +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// 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 + /// + 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 SoundProviders; + + public SoundProviderMixer(params ISoundProvider[] soundProviders) + { + SoundProviders = new List(); + + 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(); + + 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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs new file mode 100644 index 0000000000..38a281ecca --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -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 + { + /// + /// The one ZX Hawk ControllerDefinition + /// + public ControllerDefinition ZXSpectrumControllerDefinition + { + get + { + ControllerDefinition definition = new ControllerDefinition(); + definition.Name = "ZXSpectrum Controller"; + + // joysticks + List joys1 = new List + { + // 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 joys2 = new List + { + // 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 joys3 = new List + { + // 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 keys = new List + { + /// 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 power = new List + { + // Tape functions + "Soft Reset", "Hard Reset" + }; + + foreach (var s in power) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Power"; + } + + // Datacorder (tape device) + List tape = new List + { + // 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; + } + } + + + + /* + /// + /// 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 + /// + 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" + } + }; + + */ + } + + /// + /// The possible joystick types + /// + public enum JoystickType + { + NULL, + Kempston, + SinclairLEFT, + SinclairRIGHT, + Cursor + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs new file mode 100644 index 0000000000..e086ad09e3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs @@ -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 GetCpuFlagsAndRegisters() + { + return new Dictionary + { + ["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; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs new file mode 100644 index 0000000000..c87c9991d7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -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; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs new file mode 100644 index 0000000000..da34d34c66 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs @@ -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; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs new file mode 100644 index 0000000000..5245886f7c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -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 _byteArrayDomains = new Dictionary(); + private bool _memoryDomainsInit = false; + + private void SetupMemoryDomains() + { + var domains = new List + { + 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(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); + } + + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs new file mode 100644 index 0000000000..5f721f233d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -0,0 +1,359 @@ +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 + { + 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) + { + // restore user settings to devices + if (_machine != null && _machine.AYDevice != null) + { + ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig; + _machine.AYDevice.Volume = o.AYVolume; + } + if (_machine != null && _machine.BuzzerDevice != null) + { + ((Buzzer)_machine.BuzzerDevice as Buzzer).TapeVolume = o.TapeVolume; + ((Buzzer)_machine.BuzzerDevice as Buzzer).EarVolume = o.EarVolume; + } + + 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("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; } + + [DisplayName("Tape Loading Volume")] + [Description("The buzzer volume when the tape is playing")] + [DefaultValue(50)] + public int TapeVolume { get; set; } + + [DisplayName("Ear (buzzer output) Volume")] + [Description("The buzzer volume when sound is being generated by the spectrum")] + [DefaultValue(90)] + public int EarVolume { get; set; } + + [DisplayName("AY-3-8912 Volume")] + [Description("The AY chip volume")] + [DefaultValue(75)] + public int AYVolume { 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 + { + /// + /// Show all OSD messages + /// + Full, + /// + /// Only show machine/device generated messages + /// + Medium, + /// + /// No core-driven OSD messages + /// + None + } + + /// + /// The size of the Spectrum border + /// + public enum BorderType + { + /// + /// How it was originally back in the day + /// + Full, + + /// + /// All borders 24px + /// + Medium, + + /// + /// All borders 10px + /// + Small, + + /// + /// No border at all + /// + None, + + /// + /// Top and bottom border removed so that the result is *almost* 16:9 + /// + Widescreen, + } + + /// + /// The speed at which the tape is loaded + /// NOT IN USE YET + /// + public enum TapeLoadSpeed + { + Accurate, + //Fast, + //Fastest + } + } + + /// + /// Provides information on each emulated machine + /// + 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(); + + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs new file mode 100644 index 0000000000..01419fd093 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -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(); + } + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs new file mode 100644 index 0000000000..9ad4e3615a --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Handles all messaging (OSD) operations + /// + public partial class ZXSpectrum + { + /// + /// Writes a message to the OSD + /// + /// + /// + public void SendMessage(string message, MessageCategory category) + { + if (!CheckMessageSettings(category)) + return; + + StringBuilder sb = new StringBuilder(); + + switch (category) + { + case MessageCategory.Tape: + sb.Append("DATACORDER: "); + sb.Append(message); + break; + case MessageCategory.Input: + sb.Append("INPUT DETECTED: "); + sb.Append(message); + break; + case MessageCategory.Disk: + sb.Append("DISK DRIVE: "); + sb.Append(message); + break; + case MessageCategory.Emulator: + case MessageCategory.Misc: + sb.Append("ZXHAWK: "); + sb.Append(message); + break; + } + + CoreComm.Notify(sb.ToString()); + } + + #region Input Message Methods + + /// + /// Called when certain input presses are detected + /// + /// + public void OSD_FireInputMessage(string input) + { + StringBuilder sb = new StringBuilder(); + sb.Append(input); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Input); + } + + #endregion + + #region TapeDevice Message Methods + + /// + /// Tape message that is fired on core init + /// + public void OSD_TapeInit() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Tape Media Imported (count: " + _gameInfo.Count() + ")"); + sb.Append("\n"); + for (int i = 0; i < _gameInfo.Count(); i++) + sb.Append(i.ToString() + ": " + _gameInfo[i].Name + "\n"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator); + } + + /// + /// Tape message that is fired when tape is playing + /// + public void OSD_TapePlaying() + { + StringBuilder sb = new StringBuilder(); + sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when tape is stopped + /// + public void OSD_TapeStopped() + { + StringBuilder sb = new StringBuilder(); + sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when tape is rewound + /// + public void OSD_TapeRTZ() + { + StringBuilder sb = new StringBuilder(); + sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a new tape is inserted into the datacorder + /// + public void OSD_TapeInserted() + { + StringBuilder sb = new StringBuilder(); + sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + + /// + /// Tape message that is fired when a tape is stopped automatically + /// + public void OSD_TapeStoppedAuto() + { + StringBuilder sb = new StringBuilder(); + sb.Append("STOPPED (Auto Tape Trap Detected)"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape is started automatically + /// + public void OSD_TapePlayingAuto() + { + StringBuilder sb = new StringBuilder(); + sb.Append("PLAYING (Auto Tape Trap Detected)"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a new block starts playing + /// + public void OSD_TapePlayingBlockInfo(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Starting Block "+ blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape block is skipped (because it is empty) + /// + public void OSD_TapePlayingSkipBlockInfo(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Skipping Empty Block " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape is started automatically + /// + public void OSD_TapeEndDetected(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Skipping Empty Block " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when user has manually skipped to the next block + /// + public void OSD_TapeNextBlock(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("Manual Skip Next " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when user has manually skipped to the next block + /// + public void OSD_TapePrevBlock(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("Manual Skip Prev " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that prints the current status of the tape device + /// + public void OSD_ShowTapeStatus() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Status: "); + + if (_machine.TapeDevice.TapeIsPlaying) + sb.Append("PLAYING"); + else + sb.Append("STOPPED"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Block: "); + sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) + + " of " + _machine.TapeDevice.DataBlocks.Count() + ") " + + _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].BlockDescription); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Block Pos: "); + + int pos = _machine.TapeDevice.Position; + int end = _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].DataPeriods.Count; + double p = 0; + if (end != 0) + p = ((double)pos / (double)end) * (double)100; + + sb.Append(p.ToString("N0") + "%"); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + } + + #endregion + + /// + /// Checks whether message category is allowed to be sent + /// + /// + /// + public bool CheckMessageSettings(MessageCategory category) + { + switch (Settings.OSDMessageVerbosity) + { + case OSDVerbosity.Full: + return true; + case OSDVerbosity.None: + return false; + case OSDVerbosity.Medium: + switch (category) + { + case MessageCategory.Disk: + case MessageCategory.Emulator: + case MessageCategory.Tape: + case MessageCategory.Misc: + return true; + default: + return false; + } + default: + return true; + } + } + + /// + /// Defines the different message categories + /// + public enum MessageCategory + { + /// + /// No defined category as such + /// + Misc, + /// + /// User generated input messages (at the moment only tape/disk controls) + /// + Input, + /// + /// Tape device generated messages + /// + Tape, + /// + /// Disk device generated messages + /// + Disk, + /// + /// Emulator generated messages + /// + Emulator + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs new file mode 100644 index 0000000000..4cb9656858 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs @@ -0,0 +1,268 @@ +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 ZXSpectrum + { + /* + * CPU Helper Methods + */ + + public ushort RegPC + { + get { return (ushort)((_cpu.Regs[0] << 8 | _cpu.Regs[1])); } + set + { + _cpu.Regs[1] = (ushort)(value & 0xFF); + _cpu.Regs[0] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegIX + { + get { return (ushort)((_cpu.Regs[15] << 8 | _cpu.Regs[16] )); } + set + { + _cpu.Regs[16] = (ushort)(value & 0xFF); + _cpu.Regs[15] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegDE + { + get { return (ushort)((_cpu.Regs[8] << 8 | _cpu.Regs[9] )); } + set + { + _cpu.Regs[9] = (ushort)(value & 0xFF); + _cpu.Regs[8] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegAF + { + get { return (ushort)((_cpu.Regs[4] << 8 | _cpu.Regs[5])); } + set + { + _cpu.Regs[5] = (ushort)(value & 0xFF); + _cpu.Regs[4] = (ushort)((value >> 8) & 0xFF); + } + } + + + /// + /// Gets the IX word value + /// + /// + public ushort Get16BitIX() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.Ixh] | _cpu.Regs[_cpu.Ixl] << 8); + } + + /// + /// Set the IX word value + /// + /// + /// + public void Set16BitIX(ushort IX) + { + _cpu.Regs[_cpu.Ixh] = (ushort)(IX & 0xFF); + _cpu.Regs[_cpu.Ixl] = (ushort)((IX >> 8) & 0xff); + } + + /// + /// Gets the AF word value + /// + /// + public ushort Get16BitAF() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.A] | _cpu.Regs[_cpu.F] << 8); + } + + /// + /// Set the AF word value + /// + /// + /// + public void Set16BitAF(ushort AF) + { + _cpu.Regs[_cpu.A] = (ushort)(AF & 0xFF); + _cpu.Regs[_cpu.F] = (ushort)((AF >> 8) & 0xff); + } + + /// + /// Gets the AF shadow word value + /// + /// + public ushort Get16BitAF_() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.A_s] | _cpu.Regs[_cpu.F_s] << 8); + } + + /// + /// Set the AF shadow word value + /// + /// + /// + public void Set16BitAF_(ushort AF_) + { + _cpu.Regs[_cpu.A_s] = (ushort)(AF_ & 0xFF); + _cpu.Regs[_cpu.F_s] = (ushort)((AF_ >> 8) & 0xff); + } + + /// + /// Gets the DE word value + /// + /// + public ushort Get16BitDE() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.E] | _cpu.Regs[_cpu.D] << 8); + } + + /// + /// Set the DE word value + /// + /// + /// + public void Set16BitDE(ushort DE) + { + _cpu.Regs[_cpu.D] = (ushort)(DE & 0xFF); + _cpu.Regs[_cpu.E] = (ushort)((DE >> 8) & 0xff); + } + + + /// + /// Z80 Status Indicator Flag Reset masks + /// + /// + [Flags] + public enum FlagsResetMask : byte + { + /// Sign Flag + S = 0x7F, + + /// Zero Flag + Z = 0xBF, + + /// This flag is not used. + R5 = 0xDF, + + /// Half Carry Flag + H = 0xEF, + + /// This flag is not used. + R3 = 0xF7, + + /// Parity/Overflow Flag + PV = 0xFB, + + /// Add/Subtract Flag + N = 0xFD, + + /// Carry Flag + C = 0xFE, + } + + /// + /// Z80 Status Indicator Flag Set masks + /// + /// + [Flags] + public enum FlagsSetMask : byte + { + /// Sign Flag + /// + /// The Sign Flag (S) stores the state of the most-significant bit of + /// the Accumulator (bit 7). When the Z80 CPU performs arithmetic + /// operations on signed numbers, the binary twos complement notation + /// is used to represent and process numeric information. + /// + S = 0x80, + + /// + /// Zero Flag + /// + /// + /// The Zero Flag is set (1) or cleared (0) if the result generated by + /// the execution of certain instructions is 0. For 8-bit arithmetic and + /// logical operations, the Z flag is set to a 1 if the resulting byte in + /// the Accumulator is 0. If the byte is not 0, the Z flag is reset to 0. + /// + Z = 0x40, + + /// This flag is not used. + R5 = 0x20, + + /// Half Carry Flag + /// + /// The Half Carry Flag (H) is set (1) or cleared (0) depending on the + /// Carry and Borrow status between bits 3 and 4 of an 8-bit arithmetic + /// operation. This flag is used by the Decimal Adjust Accumulator (DAA) + /// instruction to correct the result of a packed BCD add or subtract operation. + /// + H = 0x10, + + /// This flag is not used. + R3 = 0x08, + + /// Parity/Overflow Flag + /// + /// The Parity/Overflow (P/V) Flag is set to a specific state depending on + /// the operation being performed. For arithmetic operations, this flag + /// indicates an overflow condition when the result in the Accumulator is + /// greater than the maximum possible number (+127) or is less than the + /// minimum possible number (–128). This overflow condition is determined by + /// examining the sign bits of the operands. + /// + PV = 0x04, + + /// Add/Subtract Flag + /// + /// The Add/Subtract Flag (N) is used by the Decimal Adjust Accumulator + /// instruction (DAA) to distinguish between the ADD and SUB instructions. + /// For ADD instructions, N is cleared to 0. For SUB instructions, N is set to 1. + /// + N = 0x02, + + /// Carry Flag + /// + /// The Carry Flag (C) is set or cleared depending on the operation being performed. + /// + C = 0x01, + + /// + /// Combination of S, Z, and PV + /// + SZPV = S | Z | PV, + + /// + /// Combination of N, and H + /// + NH = N | H, + + /// + /// Combination of R3, and R5 + /// + R3R5 = R3 | R5 + } + + /// + /// Helper method that returns a single INT32 from a BitArray + /// + /// + /// + public static int GetIntFromBitArray(BitArray bitArray) + { + if (bitArray.Length > 32) + throw new ArgumentException("Argument length shall be at most 32 bits."); + + int[] array = new int[1]; + bitArray.CopyTo(array, 0); + return array[0]; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs new file mode 100644 index 0000000000..fab7424edf --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -0,0 +1,281 @@ +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components; +using BizHawk.Emulation.Cores.Components.Z80A; +using BizHawk.Emulation.Cores.Properties; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + [Core( + "ZXHawk", + "Asnivor", + isPorted: false, + isReleased: false)] + public partial class ZXSpectrum : IRegionable, IDriveLight + { + public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) + { + var ser = new BasicServiceProvider(this); + ServiceProvider = ser; + InputCallbacks = new InputCallbackSystem(); + MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" }); + + CoreComm = comm; + + _gameInfo = game; + + _cpu = new Z80A(); + + _tracer = new TraceBuffer { Header = _cpu.TraceHeader }; + + //_file = file; + _files = files?.ToList() ?? new List(); + + if (settings == null) + settings = new ZXSpectrumSettings(); + if (syncSettings == null) + syncSettings = new ZXSpectrumSyncSettings(); + + PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings()); + PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings()); + + List joysticks = new List(); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType1); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3); + + deterministicEmulation = ((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).DeterministicEmulation; + + switch (SyncSettings.MachineType) + { + case MachineType.ZXSpectrum16: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + case MachineType.ZXSpectrum48: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + case MachineType.ZXSpectrum128: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + case MachineType.ZXSpectrum128Plus2: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + case MachineType.ZXSpectrum128Plus2a: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus2a, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + case MachineType.ZXSpectrum128Plus3: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; + default: + throw new InvalidOperationException("Machine not yet emulated"); + } + + + + _cpu.MemoryCallbacks = MemoryCallbacks; + + HardReset = _machine.HardReset; + SoftReset = _machine.SoftReset; + + _cpu.FetchMemory = _machine.ReadMemory; + _cpu.ReadMemory = _machine.ReadMemory; + _cpu.WriteMemory = _machine.WriteMemory; + _cpu.ReadHardware = _machine.ReadPort; + _cpu.WriteHardware = _machine.WritePort; + _cpu.FetchDB = _machine.PushBus; + + ser.Register(_tracer); + ser.Register(_cpu); + ser.Register(_machine.ULADevice); + + SoundMixer = new SoundProviderMixer((int)(32767 / 10), (ISoundProvider)_machine.BuzzerDevice); + if (_machine.AYDevice != null) + SoundMixer.AddSource(_machine.AYDevice); + //SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound; + + if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip)) + { + ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig; + _machine.AYDevice.Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYVolume; + } + + if (_machine.BuzzerDevice != null) + { + ((Buzzer)_machine.BuzzerDevice as Buzzer).TapeVolume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume; + ((Buzzer)_machine.BuzzerDevice as Buzzer).EarVolume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume; + } + + + ser.Register(SoundMixer); + + + HardReset(); + + SetupMemoryDomains(); + } + + public Action HardReset; + public Action SoftReset; + + private readonly Z80A _cpu; + private readonly TraceBuffer _tracer; + public IController _controller; + public SpectrumBase _machine; + + private List _gameInfo; + + private SoundProviderMixer SoundMixer; + + private readonly List _files; + + public bool DiagRom = false; + + private List diagRoms = new List + { + @"\DiagROM.v28", + @"\zx-diagnostics\testrom.bin" + }; + private int diagIndex = 1; + + private byte[] GetFirmware(int length, params string[] names) + { + if (DiagRom & File.Exists(Directory.GetCurrentDirectory() + diagRoms[diagIndex])) + { + var rom = File.ReadAllBytes(Directory.GetCurrentDirectory() + diagRoms[diagIndex]); + return rom; + } + + // Amstrad licensed ROMs are free to distribute and shipped with BizHawk + byte[] embeddedRom = new byte[length]; + bool embeddedFound = true; + switch (names.FirstOrDefault()) + { + case "48ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_48_ROM)); + break; + case "128ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_128_ROM)); + break; + case "PLUS2ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus2_rom)); + break; + case "PLUS2AROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus2a_rom)); + break; + case "PLUS3ROM": + byte[] r0 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM0_bin)); + byte[] r1 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM1_bin)); + byte[] r2 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM2_bin)); + byte[] r3 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM3_bin)); + embeddedRom = r0.Concat(r1).Concat(r2).Concat(r3).ToArray(); + break; + default: + embeddedFound = false; + break; + } + + if (embeddedFound) + return embeddedRom; + + // Embedded ROM not found, maybe this is a peripheral ROM? + var result = names.Select(n => CoreComm.CoreFileProvider.GetFirmware("ZXSpectrum", n, false)).FirstOrDefault(b => b != null && b.Length == length); + if (result == null) + { + throw new MissingFirmwareException($"At least one of these firmwares is required: {string.Join(", ", names)}"); + } + + return result; + } + + private MachineType _machineType; + + private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, List files, List joys) + { + _machineType = machineType; + + // setup the emulated model based on the MachineType + switch (machineType) + { + case MachineType.ZXSpectrum16: + _machine = new ZX16(this, _cpu, borderType, files, joys); + var _systemRom16 = GetFirmware(0x4000, "48ROM"); + var romData16 = RomData.InitROM(machineType, _systemRom16); + _machine.InitROM(romData16); + break; + case MachineType.ZXSpectrum48: + _machine = new ZX48(this, _cpu, borderType, files, joys); + var _systemRom = GetFirmware(0x4000, "48ROM"); + var romData = RomData.InitROM(machineType, _systemRom); + _machine.InitROM(romData); + break; + case MachineType.ZXSpectrum128: + _machine = new ZX128(this, _cpu, borderType, files, joys); + var _systemRom128 = GetFirmware(0x8000, "128ROM"); + var romData128 = RomData.InitROM(machineType, _systemRom128); + _machine.InitROM(romData128); + break; + case MachineType.ZXSpectrum128Plus2: + _machine = new ZX128Plus2(this, _cpu, borderType, files, joys); + var _systemRomP2 = GetFirmware(0x8000, "PLUS2ROM"); + var romDataP2 = RomData.InitROM(machineType, _systemRomP2); + _machine.InitROM(romDataP2); + break; + case MachineType.ZXSpectrum128Plus2a: + _machine = new ZX128Plus2a(this, _cpu, borderType, files, joys); + var _systemRomP4 = GetFirmware(0x10000, "PLUS2AROM"); + var romDataP4 = RomData.InitROM(machineType, _systemRomP4); + _machine.InitROM(romDataP4); + break; + case MachineType.ZXSpectrum128Plus3: + _machine = new ZX128Plus3(this, _cpu, borderType, files, joys); + var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); + var romDataP3 = RomData.InitROM(machineType, _systemRomP3); + _machine.InitROM(romDataP3); + //System.Windows.Forms.MessageBox.Show("+3 is not working at all yet :/"); + break; + } + } + + #region IRegionable + + public DisplayType Region => DisplayType.PAL; + + #endregion + + #region IDriveLight + + public bool DriveLightEnabled + { + get + { + return true; + } + } + + public bool DriveLightOn + { + get + { + if (_machine != null && + _machine.TapeDevice != null && + _machine.TapeDevice.TapeIsPlaying) + return true; + + return false; + } + } + + #endregion + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md new file mode 100644 index 0000000000..36eb6a6f07 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -0,0 +1,39 @@ +## ZXHawk + +At the moment this is experimental and is still being worked on. + +### Implemented and working (as far as I can tell) +* IEmulator +* ZX Spectrum 48k, 128k, +2 & +2A models +* ULA video output (implementing IVideoProvider) +* ULA Mode 1 VBLANK interrupt generation +* IM2 Interrupts and DataBus implementation (thanks Aloysha) +* Beeper/Buzzer output (implementing ISoundProvider) +* AY-3-8912 sound chip implementation (multiple stereo or mono panning options available as a setting) +* Keyboard input (implementing IInputPollable) +* Default keyboard keymappings +* Kempston, Cursor and Sinclair (Left & Right) joysticks emulated +* Tape device that will load spectrum games in realtime (*.tzx and *.tap) +* Most tape protection/loading schemes that I've tested are currently working +* IStatable +* ISettable core settings +* IDebuggable (for what it's worth) +* DeterministicEmulation as a SyncSetting, LagFrame detection and FrameAdvance render & renderSound bools respected (when DeterministicEmulation == false) +* Tape auto-loading routines (as a setting - default ON) +* Basic tape block navigation (NextBlock, PrevBlock) +* Tape-related OSD messages (verbosity level configured in settings) + +### Work in progress +* ZX Spectrum +3 emulation (partially working, see below) +* Exact emulator timings +* Floating memory bus emulation +* TASStudio (need to verify that this works as it should) + +### Not working +* +3 disk drive - no implementation yet +* Hard & Soft Reset menu options in the client (they are greyed out for some reason) + +### Help needed +* I'm not a TASer, i've never TASed before. It would be really useful if someone (anyone) can build this branch and test this core from a TAS-workflow / TAStudio perpective. There may still be some work to do an exact timings and memory contention, but otherwise this core is able to play the majority of speccy games out there. + +-Asnivor diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs index e6fa2f8307..6d5e8b0f27 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs @@ -356,7 +356,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 if (cyc_counter == 3) { _m6532.Timer.Tick(); - if (Tracer.Enabled) + if (Tracer.Enabled && Cpu.AtStart) { Tracer.Put(Cpu.TraceState()); } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600ControllerDeck.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600ControllerDeck.cs index 7f3813cc82..89604f6516 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600ControllerDeck.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600ControllerDeck.cs @@ -14,7 +14,8 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 { typeof(UnpluggedController), // Order must match Atari2600ControllerTypes enum values typeof(StandardController), - typeof(PaddleController) + typeof(PaddleController), + typeof(DrivingController) }; public Atari2600ControllerDeck(Atari2600ControllerTypes controller1, Atari2600ControllerTypes controller2) diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600Controllers.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600Controllers.cs index 9e5f41444e..90d9b1cd16 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600Controllers.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600Controllers.cs @@ -12,7 +12,8 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 { Unplugged, Joystick, - Paddle + Paddle, + Driving } /// @@ -160,4 +161,99 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 return x; } } + + public class DrivingController : IPort + { + public DrivingController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + BoolButtons = BaseDefinition + .Select(b => $"P{PortNum} " + b) + .ToList(), + FloatControls = { "P" + PortNum + " Wheel X 1", "P" + PortNum + " Wheel X 2" }, + FloatRanges = { new[] { -127.0f, 0, 127.0f }, new[] { -127.0f, 0, 127.0f } } + }; + } + + public int PortNum { get; } + + public void SyncState(Serializer ser) + { + // Nothing todo, I think + } + + public ControllerDefinition Definition { get; } + + private static readonly string[] BaseDefinition = + { + "Button" + }; + + public byte Read(IController c) + { + byte result = 0xFF; + + if (c.IsPressed($"P{PortNum} Button")) { result &= 0xF7; } + + float x = c.GetFloat(Definition.FloatControls[0]); + float y = c.GetFloat(Definition.FloatControls[1]); + + float angle = CalcDirection(x, y); + + byte temp2 = 0; + + int temp1 = (int)Math.Floor(angle / 45); + temp1 = temp1 % 4; + + if (temp1 == 0) + { + temp2 = 0xEF; + } + + if (temp1 == 1) + { + temp2 = 0xCF; + } + if (temp1 == 2) + { + temp2 = 0xDF; + } + + if (temp1 == 3) + { + temp2 = 0xFF; + } + + + result &= temp2; + + + return result; + } + + public int Read_Pot(IController c, int pot) + { + return -1; // indicates not applicable + } + + private static float CalcDirection(float x, float y) + { + y = -y; // vflip to match the arrangement of FloatControllerButtons + + // the wheel is arranged in a grey coded configuration of sensitivity ~2.5 degrees + // for each signal + // so overall the value returned changes every 1.25 degrees + + float angle = (float)(Math.Atan2(y, x) * 180.0 / Math.PI); + + if (angle < 0) + { + angle = 360 + angle; + } + + return angle; + } + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs index b3a08cde2b..6f8004fd45 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk { get { - return false; + return (_hsbios != null); } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs index 6f2ce9b238..32ac8c29f3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs @@ -168,6 +168,11 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk else { s_mapper = "1"; + } + + if (cart_2.Bit(2)) + { + cart_RAM = 8; } } else diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/AY_3_8910_SGM.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/AY_3_8910_SGM.cs new file mode 100644 index 0000000000..ae9ce5d00f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/AY_3_8910_SGM.cs @@ -0,0 +1,312 @@ +using System; + +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.ColecoVision +{ + public sealed class AY_3_8910_SGM + { + private short current_sample; + + public AY_3_8910_SGM() + { + Reset(); + } + + public byte[] Register = new byte[16]; + + public byte port_sel; + + public void Reset() + { + clock_A = clock_B = clock_C = 0x1000; + noise_clock = 0x20; + port_sel = 0; + + for (int i = 0; i < 16; i++) + { + Register[i] = 0x0000; + } + sync_psg_state(); + } + + public short Sample() + { + return current_sample; + } + + private static readonly int[] VolumeTable = + { + 0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA, + 0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA + }; + + private int psg_clock; + private int sq_per_A, sq_per_B, sq_per_C; + private int clock_A, clock_B, clock_C; + private int vol_A, vol_B, vol_C; + private bool A_on, B_on, C_on; + private bool A_up, B_up, C_up; + private bool A_noise, B_noise, C_noise; + + private int env_per; + private int env_clock; + private int env_shape; + private int env_E; + private int E_up_down; + private int env_vol_A, env_vol_B, env_vol_C; + + private int noise_clock; + private int noise_per; + private int noise = 0x1; + + public void SyncState(Serializer ser) + { + ser.BeginSection("PSG"); + + ser.Sync("Register", ref Register, false); + + ser.Sync("psg_clock", ref psg_clock); + ser.Sync("clock_A", ref clock_A); + ser.Sync("clock_B", ref clock_B); + ser.Sync("clock_C", ref clock_C); + ser.Sync("noise_clock", ref noise_clock); + ser.Sync("env_clock", ref env_clock); + ser.Sync("A_up", ref A_up); + ser.Sync("B_up", ref B_up); + ser.Sync("C_up", ref C_up); + ser.Sync("noise", ref noise); + ser.Sync("env_E", ref env_E); + ser.Sync("E_up_down", ref E_up_down); + ser.Sync("port_sel", ref port_sel); + + sync_psg_state(); + + ser.EndSection(); + } + + public byte ReadReg() + { + return Register[port_sel]; + } + + private void sync_psg_state() + { + sq_per_A = (Register[0] & 0xFF) | (((Register[1] & 0xF) << 8)); + if (sq_per_A == 0) + { + sq_per_A = 0x1000; + } + + sq_per_B = (Register[2] & 0xFF) | (((Register[3] & 0xF) << 8)); + if (sq_per_B == 0) + { + sq_per_B = 0x1000; + } + + sq_per_C = (Register[4] & 0xFF) | (((Register[5] & 0xF) << 8)); + if (sq_per_C == 0) + { + sq_per_C = 0x1000; + } + + env_per = (Register[11] & 0xFF) | (((Register[12] & 0xFF) << 8)); + if (env_per == 0) + { + env_per = 0x10000; + } + + env_per *= 2; + + A_on = Register[7].Bit(0); + B_on = Register[7].Bit(1); + C_on = Register[7].Bit(2); + A_noise = Register[7].Bit(3); + B_noise = Register[7].Bit(4); + C_noise = Register[7].Bit(5); + + noise_per = Register[6] & 0x1F; + if (noise_per == 0) + { + noise_per = 0x20; + } + + var shape_select = Register[13] & 0xF; + + if (shape_select < 4) + env_shape = 0; + else if (shape_select < 8) + env_shape = 1; + else + env_shape = 2 + (shape_select - 8); + + vol_A = Register[8] & 0xF; + env_vol_A = (Register[8] >> 4) & 0x1; + + vol_B = Register[9] & 0xF; + env_vol_B = (Register[9] >> 4) & 0x1; + + vol_C = Register[10] & 0xF; + env_vol_C = (Register[10] >> 4) & 0x1; + } + + public void WriteReg(byte value) + { + value &= 0xFF; + + Register[port_sel] = value; + + sync_psg_state(); + + if (port_sel == 13) + { + env_clock = env_per; + + if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5) + { + env_E = 15; + E_up_down = -1; + } + else + { + env_E = 0; + E_up_down = 1; + } + } + } + + public void generate_sound(int cycles_to_do) + { + // there are 8 cpu cycles for every psg cycle + bool sound_out_A; + bool sound_out_B; + bool sound_out_C; + + for (int i = 0; i < cycles_to_do; i++) + { + psg_clock++; + + if (psg_clock == 8) + { + psg_clock = 0; + + clock_A--; + clock_B--; + clock_C--; + + noise_clock--; + env_clock--; + + // clock noise + if (noise_clock == 0) + { + noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0); + noise_clock = noise_per; + } + + if (env_clock == 0) + { + env_clock = env_per; + + env_E += E_up_down; + + if (env_E == 16 || env_E == -1) + { + + // we just completed a period of the envelope, determine what to do now based on the envelope shape + if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9) + { + E_up_down = 0; + env_E = 0; + } + else if (env_shape == 5 || env_shape == 7) + { + E_up_down = 0; + env_E = 15; + } + else if (env_shape == 4 || env_shape == 8) + { + if (env_E == 16) + { + env_E = 15; + E_up_down = -1; + } + else + { + env_E = 0; + E_up_down = 1; + } + } + else if (env_shape == 2) + { + env_E = 15; + } + else + { + env_E = 0; + } + } + } + + if (clock_A == 0) + { + A_up = !A_up; + clock_A = sq_per_A; + } + + if (clock_B == 0) + { + B_up = !B_up; + clock_B = sq_per_B; + } + + if (clock_C == 0) + { + C_up = !C_up; + clock_C = sq_per_C; + } + + + sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up); + sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up); + sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up); + + // now calculate the volume of each channel and add them together + int v; + + if (env_vol_A == 0) + { + v = (short)(sound_out_A ? VolumeTable[vol_A] : 0); + } + else + { + v = (short)(sound_out_A ? VolumeTable[vol_A] : 0); + } + + if (env_vol_B == 0) + { + v += (short)(sound_out_B ? VolumeTable[vol_B] : 0); + + } + else + { + v += (short)(sound_out_B ? VolumeTable[env_E] : 0); + } + + if (env_vol_C == 0) + { + v += (short)(sound_out_C ? VolumeTable[vol_C] : 0); + } + else + { + v += (short)(sound_out_C ? VolumeTable[env_E] : 0); + } + + current_sample = (short)v; + } + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllerDeck.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllerDeck.cs index 97a45d4e9b..162fe4f4f7 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllerDeck.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllerDeck.cs @@ -44,27 +44,24 @@ namespace BizHawk.Emulation.Cores.ColecoVision Definition.FloatRanges.AddRange(Port2.Definition.FloatRanges); } - private int wheel1; - private int wheel2; + public float wheel1; + public float wheel2; + + public float temp_wheel1; + public float temp_wheel2; public byte ReadPort1(IController c, bool leftMode, bool updateWheel) { - if (updateWheel) - { - wheel1 = Port1.UpdateWheel(c, wheel1); - } + wheel1 = Port1.UpdateWheel(c); - return Port1.Read(c, leftMode, wheel1); + return Port1.Read(c, leftMode, updateWheel, temp_wheel1); } public byte ReadPort2(IController c, bool leftMode, bool updateWheel) { - if (updateWheel) - { - wheel2 = Port2.UpdateWheel(c, wheel2); - } + wheel2 = Port2.UpdateWheel(c); - return Port2.Read(c, leftMode, wheel2); + return Port2.Read(c, leftMode, updateWheel, temp_wheel2); } public ControllerDefinition Definition { get; } @@ -72,12 +69,12 @@ namespace BizHawk.Emulation.Cores.ColecoVision public void SyncState(Serializer ser) { ser.BeginSection("Port1"); - ser.Sync("Wheel 1", ref wheel1); Port1.SyncState(ser); + ser.Sync("temp_wheel1", ref temp_wheel1); ser.EndSection(); ser.BeginSection("Port2"); - ser.Sync("Wheel 2", ref wheel2); + ser.Sync("temp_wheel2", ref temp_wheel2); Port2.SyncState(ser); ser.EndSection(); } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllers.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllers.cs index bf018db6b5..3bd2179713 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllers.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoControllers.cs @@ -13,9 +13,9 @@ namespace BizHawk.Emulation.Cores.ColecoVision /// public interface IPort { - byte Read(IController c, bool leftMode, int wheel); + byte Read(IController c, bool leftMode, bool updateWheel, float wheelAngle); - int UpdateWheel(IController c, int wheel); + float UpdateWheel(IController c); ControllerDefinition Definition { get; } @@ -36,7 +36,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision }; } - public byte Read(IController c, bool left_mode, int wheel) + public byte Read(IController c, bool left_mode, bool updateWheel, float wheelAngle) { return 0x7F; // needs checking } @@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision public int PortNum { get; } - public int UpdateWheel(IController c, int wheel) + public float UpdateWheel(IController c) { return 0; } @@ -72,7 +72,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision public int PortNum { get; } - public byte Read(IController c, bool leftMode, int wheel) + public byte Read(IController c, bool leftMode, bool updateWheel, float wheelAngle) { if (leftMode) { @@ -124,7 +124,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision "Key 6", "Key 7", "Key 8", "Key 9", "Pound", "Star" }; - public int UpdateWheel(IController c, int wheel) + public float UpdateWheel(IController c) { return 0; } @@ -150,29 +150,62 @@ namespace BizHawk.Emulation.Cores.ColecoVision public ControllerDefinition Definition { get; } - public byte Read(IController c, bool leftMode, int wheel) + public byte Read(IController c, bool leftMode, bool updateWheel, float wheelAngle) { if (leftMode) { - byte retval = 0x4B; + + byte retval = 0x4F; if (c.IsPressed(Definition.BoolButtons[0])) retval &= 0x3F; + + float x = c.GetFloat(Definition.FloatControls[0]); + float y = c.GetFloat(Definition.FloatControls[1]); - int x = (int)c.GetFloat(Definition.FloatControls[0]); - int y = (int)c.GetFloat(Definition.FloatControls[1]); - retval |= CalcDirection(x, y); + float angle; + + if (updateWheel) + { + angle = wheelAngle; + } + else + { + angle = CalcDirection(x, y); + } + + byte temp2 = 0; + + int temp1 = (int)Math.Floor(angle / 1.25); + temp1 = temp1 % 4; + + if (temp1 == 0) + { + temp2 = 0x10; + } + + if (temp1 == 1) + { + temp2 = 0x30; + } + if (temp1 == 2) + { + temp2 = 0x20; + } + + if (temp1 == 3) + { + temp2 = 0x00; + } + + + retval |= temp2; return retval; } else { - byte retval = 0x4B; - if (c.IsPressed(Definition.BoolButtons[0])) retval &= 0x3F; + byte retval = 0x7F; - int x = (int)c.GetFloat(Definition.FloatControls[0]); - int y = (int)c.GetFloat(Definition.FloatControls[1]); - retval |= CalcDirection(x, y); - return retval; } } @@ -190,36 +223,29 @@ namespace BizHawk.Emulation.Cores.ColecoVision // x and y are both assumed to be in [-127, 127] // x increases from left to right // y increases from top to bottom - private static byte CalcDirection(int x, int y) + private static float CalcDirection(float x, float y) { y = -y; // vflip to match the arrangement of FloatControllerButtons - if (y >= 0 && x > 0) + // the wheel is arranged in a grey coded configuration of sensitivity ~2.5 degrees + // for each signal + // so overall the value returned changes every 1.25 degrees + + float angle = (float)(Math.Atan2(y, x) * 180.0/Math.PI); + + if (angle < 0) { - return 0x10; + angle = 360 + angle; } - if (y >= 0 && x <= 0) - { - return 0x30; - } - if (y < 0 && x <= 0) - { - return 0x20; - } - - if (y < 0 && x > 0) - { - return 0x00; - } - - Console.WriteLine("Error"); - return 0x1F; + return angle; } - public int UpdateWheel(IController c, int wheel) + public float UpdateWheel(IController c) { - return 0; + float x = c.GetFloat(Definition.FloatControls[0]); + float y = c.GetFloat(Definition.FloatControls[1]); + return CalcDirection(x, y); } } @@ -243,7 +269,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision public ControllerDefinition Definition { get; private set; } - public byte Read(IController c, bool left_mode, int wheel) + public byte Read(IController c, bool left_mode, bool updateWheel, float wheelAngle) { if (left_mode) { @@ -254,7 +280,45 @@ namespace BizHawk.Emulation.Cores.ColecoVision if (c.IsPressed(Definition.BoolButtons[3])) retval &= 0xF7; if (c.IsPressed(Definition.BoolButtons[4])) retval &= 0x3F; - retval |= CalcDirection(wheel); + float x = c.GetFloat(Definition.FloatControls[0]); + float y = c.GetFloat(Definition.FloatControls[1]); + + float angle; + + if (updateWheel) + { + angle = wheelAngle; + } + else + { + angle = CalcDirection(x, y); + } + + byte temp2 = 0; + + int temp1 = (int)Math.Floor(angle / 1.25); + temp1 = temp1 % 4; + + if (temp1 == 0) + { + temp2 = 0x10; + } + + if (temp1 == 1) + { + temp2 = 0x30; + } + if (temp1 == 2) + { + temp2 = 0x20; + } + + if (temp1 == 3) + { + temp2 = 0x00; + } + + retval |= temp2; return retval; } @@ -300,53 +364,32 @@ namespace BizHawk.Emulation.Cores.ColecoVision "Purple", "Blue" }; - // positive x represents spinning to the right, negative spinning to the left - private static byte CalcDirection(int wheel) + // x and y are both assumed to be in [-127, 127] + // x increases from left to right + // y increases from top to bottom + private static float CalcDirection(float x, float y) { - byte retval = 0; + y = -y; // vflip to match the arrangement of FloatControllerButtons - if (wheel >= 0 && wheel < 180) + // the wheel is arranged in a grey coded configuration of sensitivity ~2.5 degrees + // for each signal + // so overall the value returned changes every 1.25 degrees + + float angle = (float)(Math.Atan2(y, x) * 180.0 / Math.PI); + + if (angle < 0) { - retval = 0x00; + angle = 360 + angle; } - if (wheel >= 180 && wheel < 360) - { - retval = 0x10; - } - - if (wheel < 0 && wheel > -180) - { - retval = 0x20; - } - - if (wheel <= -180 && wheel > -360) - { - retval = 0x30; - } - - return retval; + return angle; } - public int UpdateWheel(IController c, int wheel) + public float UpdateWheel(IController c) { - int x = (int)c.GetFloat(Definition.FloatControls[0]); - - int diff = -x; - - wheel += diff; - - if (wheel >= 360) - { - wheel = wheel - 360; - } - - if (wheel <= -360) - { - wheel = wheel + 360; - } - - return wheel; + float x = c.GetFloat(Definition.FloatControls[0]); + float y = c.GetFloat(Definition.FloatControls[1]); + return CalcDirection(x, y); } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs index c48bbabf8f..8739d2d616 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs @@ -142,6 +142,6 @@ namespace BizHawk.Emulation.Cores.ColecoVision throw new NotImplementedException(); } - public int TotalExecutedCycles => _cpu.TotalExecutedCycles; + public int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IEmulator.cs index 22e67db446..40274119b2 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IEmulator.cs @@ -1,5 +1,6 @@ using BizHawk.Emulation.Common; using BizHawk.Common.NumberExtensions; +using System; namespace BizHawk.Emulation.Cores.ColecoVision { @@ -25,9 +26,8 @@ namespace BizHawk.Emulation.Cores.ColecoVision } _frame++; - _isLag = true; - PSG.BeginFrame(_cpu.TotalExecutedCycles); + _isLag = true; if (_tracer.Enabled) { _cpu.TraceCallback = s => _tracer.Put(s); @@ -36,14 +36,130 @@ namespace BizHawk.Emulation.Cores.ColecoVision { _cpu.TraceCallback = null; } - byte tempRet1 = ControllerDeck.ReadPort1(controller, true, true); - byte tempRet2 = ControllerDeck.ReadPort2(controller, true, true); + byte tempRet1 = ControllerDeck.ReadPort1(controller, true, false); + byte tempRet2 = ControllerDeck.ReadPort2(controller, true, false); - bool intPending = (!tempRet1.Bit(4)) | (!tempRet2.Bit(4)); + bool intPending = false; - _vdp.ExecuteFrame(intPending); + // the return values represent the controller's current state, but the sampling rate is not high enough + // to catch all changes in wheel orientation + // so we use the wheel variable and interpolate between frames - PSG.EndFrame(_cpu.TotalExecutedCycles); + // first determine how many degrees the wheels changed, and how many regions have been traversed + float change1 = (float)(((ControllerDeck.wheel1 - ControllerDeck.temp_wheel1) % 180) / 1.25); + float change2 = (float)(((ControllerDeck.wheel2 - ControllerDeck.temp_wheel2) % 180) / 1.25); + + // special cases + if ((ControllerDeck.temp_wheel1 > 270) && (ControllerDeck.wheel1 < 90)) + { + change1 = (float)((ControllerDeck.wheel1 + (360 - ControllerDeck.temp_wheel1)) / 1.25); + } + + if ((ControllerDeck.wheel1 > 270) && (ControllerDeck.temp_wheel1 < 90)) + { + change1 = -(float)((ControllerDeck.temp_wheel1 + (360 - ControllerDeck.wheel1)) / 1.25); + } + + if ((ControllerDeck.temp_wheel2 > 270) && (ControllerDeck.wheel2 < 90)) + { + change2 = (float)((ControllerDeck.wheel2 + (360 - ControllerDeck.temp_wheel2)) / 1.25); + } + + if ((ControllerDeck.wheel2 > 270) && (ControllerDeck.temp_wheel2 < 90)) + { + change2 = -(float)((ControllerDeck.temp_wheel2 + (360 - ControllerDeck.wheel2)) / 1.25); + } + + int changes_1 = change1 > 0 ? (int)Math.Floor(change1) : (int)Math.Ceiling(change1); + int changes_2 = change2 > 0 ? (int)Math.Floor(change2) : (int)Math.Ceiling(change2); + + + + for (int scanLine = 0; scanLine < 262; scanLine++) + { + _vdp.RenderScanline(scanLine); + + if (scanLine == 192) + { + _vdp.InterruptPending = true; + + if (_vdp.EnableInterrupts) + _cpu.NonMaskableInterrupt = true; + } + + for (int i = 0; i < 228; i++) + { + PSG.generate_sound(1); + if (use_SGM) { SGM_sound.generate_sound(1); } + _cpu.ExecuteOne(); + + // pick out sound samples from the sound devies twice per scanline + int v = PSG.Sample(); + + if (use_SGM) + { + v += SGM_sound.Sample(); + } + + if (v != _latchedSample) + { + _blip.AddDelta((uint)_sampleClock, v - _latchedSample); + _latchedSample = v; + } + + _sampleClock++; + } + + // starting from scanline 20, changes to the wheel are added once per scanline (up to 144) + if (scanLine > 20) + { + if (changes_1 != 0) + { + if (changes_1 > 0) + { + ControllerDeck.temp_wheel1 = (float)((ControllerDeck.temp_wheel1 + 1.25) % 360); + changes_1--; + } + else + { + ControllerDeck.temp_wheel1 = (float)((ControllerDeck.temp_wheel1 - 1.25) % 360); + changes_1++; + } + } + + if (changes_2 != 0) + { + if (changes_2 > 0) + { + ControllerDeck.temp_wheel2 = (float)((ControllerDeck.temp_wheel2 + 1.25) % 360); + changes_2--; + } + else + { + ControllerDeck.temp_wheel2 = (float)((ControllerDeck.temp_wheel2 - 1.25) % 360); + changes_2++; + } + } + } + + tempRet1 = ControllerDeck.ReadPort1(controller, true, true); + tempRet2 = ControllerDeck.ReadPort2(controller, true, true); + + intPending = (!tempRet1.Bit(4) && temp_1_prev) | (!tempRet2.Bit(4) && temp_2_prev); + + _cpu.FlagI = false; + if (intPending) + { + _cpu.FlagI = true; + intPending = false; + } + + temp_1_prev = tempRet1.Bit(4); + temp_2_prev = tempRet2.Bit(4); + } + + ControllerDeck.temp_wheel1 = ControllerDeck.wheel1; + ControllerDeck.temp_wheel2 = ControllerDeck.wheel2; if (_isLag) { @@ -51,6 +167,16 @@ namespace BizHawk.Emulation.Cores.ColecoVision } } + public bool use_SGM = false; + public bool is_MC = false; + public int MC_bank = 0; + public bool enable_SGM_high = false; + public bool enable_SGM_low = false; + public byte port_0x53, port_0x7F; + + public int _sampleClock = 0; + public int _latchedSample = 0; + public int Frame => _frame; public string SystemId => "Coleco"; diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISettable.cs index b043990793..5dfee99534 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISettable.cs @@ -46,6 +46,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision public class ColecoSyncSettings { public bool SkipBiosIntro { get; set; } + public bool UseSGM { get; set; } private string _port1 = ColecoVisionControllerDeck.DefaultControllerName; private string _port2 = ColecoVisionControllerDeck.DefaultControllerName; diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs index dc0d7b7a77..d08bba6748 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs @@ -1,10 +1,61 @@ -using BizHawk.Emulation.Cores.Components; +using System; + +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.ColecoVision { - public partial class ColecoVision + public partial class ColecoVision : ISoundProvider { - private SN76489 PSG; - private readonly FakeSyncSound _fakeSyncSound; + private SN76489col PSG; + private AY_3_8910_SGM SGM_sound; + + private readonly BlipBuffer _blip = new BlipBuffer(4096); + + public void DiscardSamples() + { + _blip.Clear(); + _sampleClock = 0; + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + public bool CanProvideAsync => false; + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only Sync mode is supported."); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + _blip.EndFrame((uint)_sampleClock); + _sampleClock = 0; + + nsamp = _blip.SamplesAvailable(); + samples = new short[nsamp * 2]; + + _blip.ReadSamples(samples, nsamp, true); + + for (int i = 0; i < nsamp * 2; i += 2) + { + samples[i + 1] = samples[i]; + } + } + + public void GetSamples(short[] samples) + { + throw new Exception(); + } + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IStatable.cs index d079fa50f4..de93ea177e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IStatable.cs @@ -54,11 +54,23 @@ namespace BizHawk.Emulation.Cores.ColecoVision ser.BeginSection("Coleco"); _vdp.SyncState(ser); + ControllerDeck.SyncState(ser); PSG.SyncState(ser); + SGM_sound.SyncState(ser); + ser.Sync("UseSGM", ref use_SGM); + ser.Sync("is_MC", ref is_MC); + ser.Sync("MC_bank", ref MC_bank); + ser.Sync("EnableSGMhigh", ref enable_SGM_high); + ser.Sync("EnableSGMlow", ref enable_SGM_low); + ser.Sync("Port_0x53", ref port_0x53); + ser.Sync("Port_0x7F", ref port_0x7F); ser.Sync("RAM", ref _ram, false); + ser.Sync("SGM_high_RAM", ref SGM_high_RAM, false); + ser.Sync("SGM_low_RAM", ref SGM_low_RAM, false); ser.Sync("Frame", ref _frame); ser.Sync("LagCount", ref _lagCount); ser.Sync("IsLag", ref _isLag); + ser.EndSection(); if (ser.IsReader) diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs index 070c8a2655..e9bcba4f8f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs @@ -1,6 +1,7 @@ using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components; using BizHawk.Emulation.Cores.Components.Z80A; +using System; namespace BizHawk.Emulation.Cores.ColecoVision { @@ -32,9 +33,9 @@ namespace BizHawk.Emulation.Cores.ColecoVision MemoryCallbacks = MemoryCallbacks }; - PSG = new SN76489(); - _fakeSyncSound = new FakeSyncSound(PSG, 735); - ser.Register(_fakeSyncSound); + PSG = new SN76489col(); + SGM_sound = new AY_3_8910_SGM(); + _blip.SetRates(3579545, 44100); ControllerDeck = new ColecoVisionControllerDeck(_syncSettings.Port1, _syncSettings.Port2); @@ -56,6 +57,9 @@ namespace BizHawk.Emulation.Cores.ColecoVision _tracer.Header = _cpu.TraceHeader; ser.Register(_cpu); ser.Register(_tracer); + + use_SGM = _syncSettings.UseSGM; + Console.WriteLine("Using the Super Game Module"); } private readonly Z80A _cpu; @@ -65,6 +69,11 @@ namespace BizHawk.Emulation.Cores.ColecoVision private byte[] _romData; private byte[] _ram = new byte[1024]; + public byte[] SGM_high_RAM = new byte[0x6000]; + public byte[] SGM_low_RAM = new byte[0x2000]; + + public bool temp_1_prev, temp_2_prev; + private int _frame; private IController _controller; @@ -79,10 +88,22 @@ namespace BizHawk.Emulation.Cores.ColecoVision private void LoadRom(byte[] rom, bool skipbios) { - _romData = new byte[0x8000]; - for (int i = 0; i < 0x8000; i++) + if (rom.Length <= 32768) { - _romData[i] = rom[i % rom.Length]; + _romData = new byte[0x8000]; + for (int i = 0; i < 0x8000; i++) + { + _romData[i] = rom[i % rom.Length]; + } + } + else + { + // all original ColecoVision games had 32k or less of ROM + // so, if we have more then that, we must be using a MegaCart mapper + is_MC = true; + + _romData = rom; + } // hack to skip colecovision title screen @@ -117,6 +138,29 @@ namespace BizHawk.Emulation.Cores.ColecoVision return ReadController2(); } + if (use_SGM) + { + if (port == 0x50) + { + return SGM_sound.port_sel; + } + + if (port == 0x52) + { + return SGM_sound.ReadReg(); + } + + if (port == 0x53) + { + return port_0x53; + } + + if (port == 0x7F) + { + return port_0x7F; + } + } + return 0xFF; } @@ -152,7 +196,49 @@ namespace BizHawk.Emulation.Cores.ColecoVision if (port >= 0xE0) { - PSG.WritePsgData(value, _cpu.TotalExecutedCycles); + PSG.WriteReg(value); + } + + if (use_SGM) + { + if (port == 0x50) + { + SGM_sound.port_sel = (byte)(value & 0xF); + } + + if (port == 0x51) + { + SGM_sound.WriteReg(value); + } + + if (port == 0x53) + { + if ((value & 1) > 0) + { + enable_SGM_high = true; + } + else + { + // NOTE: the documentation states that you shouldn't turn RAM back off once enabling it + // so we won't do anything here + } + + port_0x53 = value; + } + + if (port == 0x7F) + { + if (value == 0xF) + { + enable_SGM_low = false; + } + else if (value == 0xD) + { + enable_SGM_low = true; + } + + port_0x7F = value; + } } } @@ -162,13 +248,13 @@ namespace BizHawk.Emulation.Cores.ColecoVision byte retval; if (_inputPortSelection == InputPortMode.Left) { - retval = ControllerDeck.ReadPort1(_controller, true, false); + retval = ControllerDeck.ReadPort1(_controller, true, true); return retval; } if (_inputPortSelection == InputPortMode.Right) { - retval = ControllerDeck.ReadPort1(_controller, false, false); + retval = ControllerDeck.ReadPort1(_controller, false, true); return retval; } @@ -181,13 +267,13 @@ namespace BizHawk.Emulation.Cores.ColecoVision byte retval; if (_inputPortSelection == InputPortMode.Left) { - retval = ControllerDeck.ReadPort2(_controller, true, false); + retval = ControllerDeck.ReadPort2(_controller, true, true); return retval; } if (_inputPortSelection == InputPortMode.Right) { - retval = ControllerDeck.ReadPort2(_controller, false, false); + retval = ControllerDeck.ReadPort2(_controller, false, true); return retval; } @@ -198,17 +284,57 @@ namespace BizHawk.Emulation.Cores.ColecoVision { if (addr >= 0x8000) { - return _romData[addr & 0x7FFF]; + if (!is_MC) + { + return _romData[addr & 0x7FFF]; + } + else + { + // reading from 0xFFC0 to 0xFFFF triggers bank switching + // I don't know if it happens before or after the read though + + if (addr >= 0xFFC0) + { + MC_bank = (addr - 0xFFC0) & (_romData.Length / 0x4000 - 1); + } + + // the first 16K of the map is always the last 16k of the ROM + if (addr < 0xC000) + { + return _romData[_romData.Length - 0x4000 + (addr - 0x8000)]; + } + else + { + return _romData[MC_bank * 0x4000 + (addr - 0xC000)]; + } + } } - if (addr >= 0x6000) + if (!enable_SGM_high) { - return _ram[addr & 1023]; + if (addr >= 0x6000) + { + return _ram[addr & 1023]; + } + } + else + { + if (addr >= 0x2000) + { + return SGM_high_RAM[addr - 0x2000]; + } } if (addr < 0x2000) { - return _biosRom[addr]; + if (!enable_SGM_low) + { + return _biosRom[addr]; + } + else + { + return SGM_low_RAM[addr]; + } } ////Console.WriteLine("Unhandled read at {0:X4}", addr); @@ -217,11 +343,28 @@ namespace BizHawk.Emulation.Cores.ColecoVision private void WriteMemory(ushort addr, byte value) { - if (addr >= 0x6000 && addr < 0x8000) + if (!enable_SGM_high) { - _ram[addr & 1023] = value; + if (addr >= 0x6000 && addr < 0x8000) + { + _ram[addr & 1023] = value; + } + } + else + { + if (addr >= 0x2000 && addr < 0x8000) + { + SGM_high_RAM[addr - 0x2000] = value; + } } + if (addr < 0x2000) + { + if (enable_SGM_low) + { + SGM_low_RAM[addr] = value; + } + } ////Console.WriteLine("Unhandled write at {0:X4}:{1:X2}", addr, value); } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/SN76489col.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/SN76489col.cs new file mode 100644 index 0000000000..451ed8cc5b --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/SN76489col.cs @@ -0,0 +1,235 @@ +using System.Collections.Generic; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Common.NumberExtensions; +using System; + +namespace BizHawk.Emulation.Cores.ColecoVision +{ + public sealed class SN76489col + { + private short current_sample; + + public SN76489col() + { + Reset(); + } + + public byte[] Chan_vol = new byte[4]; + public ushort[] Chan_tone = new ushort[4]; + + public int chan_sel; + public bool vol_tone; + public bool noise_type; + public int noise_rate; + public bool noise_bit; + + private int psg_clock; + + private int clock_A, clock_B, clock_C; + + private bool A_up, B_up, C_up; + + private int noise_clock; + private int noise; + + private static readonly byte[] LogScale = { 255, 203, 161, 128, 102, 86, 64, 51, 40, 32, 26, 20, 16, 13, 10, 0 }; + + public void Reset() + { + clock_A = clock_B = clock_C = 0x1000; + noise_clock = 0x10; + chan_sel = 0; + + // reset the shift register + noise = 0x40000; + } + + public int Sample() + { + return current_sample; + } + + public void SyncState(Serializer ser) + { + ser.BeginSection("SN76489"); + + ser.Sync("Chan_vol", ref Chan_vol, false); + ser.Sync("Chan_tone", ref Chan_tone, false); + + ser.Sync("Chan_sel", ref chan_sel); + ser.Sync("vol_tone", ref vol_tone); + ser.Sync("noise_type", ref noise_type); + ser.Sync("noise_rate", ref noise_rate); + + ser.Sync("Clock_A", ref clock_A); + ser.Sync("Clock_B", ref clock_B); + ser.Sync("Clock_C", ref clock_C); + ser.Sync("noise_clock", ref noise_clock); + ser.Sync("noise_bit", ref noise_bit); + + ser.Sync("psg_clock", ref psg_clock); + + ser.Sync("A_up", ref A_up); + ser.Sync("B_up", ref B_up); + ser.Sync("C_up", ref C_up); + ser.Sync("noise", ref noise); + + ser.Sync("current_sample", ref current_sample); + + ser.EndSection(); + } + + public byte ReadReg() + { + // not used, reading not allowed, just return 0xFF + return 0xFF; + } + + public void WriteReg(byte value) + { + // if bit 7 is set, change the latch, otherwise modify the currently latched register + if (value.Bit(7)) + { + chan_sel = (value >> 5) & 3; + vol_tone = value.Bit(4); + + if (vol_tone) + { + Chan_vol[chan_sel] = (byte)(value & 0xF); + } + else + { + if (chan_sel < 3) + { + Chan_tone[chan_sel] &= 0x3F0; + Chan_tone[chan_sel] |= (ushort)(value & 0xF); + } + else + { + noise_type = value.Bit(2); + noise_rate = value & 3; + + // reset the shift register + noise = 0x40000; + } + } + } + else + { + if (vol_tone) + { + Chan_vol[chan_sel] = (byte)(value & 0xF); + } + else + { + if (chan_sel < 3) + { + Chan_tone[chan_sel] &= 0xF; + Chan_tone[chan_sel] |= (ushort)((value & 0x3F) << 4); + } + else + { + noise_type = value.Bit(2); + noise_rate = value & 3; + + // reset the shift register + noise = 0x40000; + } + } + } + } + + public void generate_sound(int cycles_to_do) + { + // there are 16 cpu cycles for every psg cycle + + for (int i = 0; i < cycles_to_do; i++) + { + psg_clock++; + + if (psg_clock == 16) + { + psg_clock = 0; + + clock_A--; + clock_B--; + clock_C--; + noise_clock--; + + // clock noise + if (noise_clock == 0) + { + noise_bit = noise.Bit(0); + if (noise_type) + { + int bit = (noise & 1) ^ ((noise >> 1) & 1); + noise = noise >> 1; + noise |= bit << 14; + + } + else + { + int bit = noise & 1; + noise = noise >> 1; + noise |= bit << 14; + } + + if (noise_rate == 0) + { + noise_clock = 0x10; + } + else if (noise_rate == 1) + { + noise_clock = 0x20; + } + else if (noise_rate == 2) + { + noise_clock = 0x40; + } + else + { + noise_clock = Chan_tone[2] + 1; + } + + noise_clock *= 2; + } + + + if (clock_A == 0) + { + A_up = !A_up; + clock_A = Chan_tone[0] + 1; + } + + if (clock_B == 0) + { + B_up = !B_up; + clock_B = Chan_tone[1] + 1; + } + + if (clock_C == 0) + { + C_up = !C_up; + clock_C = Chan_tone[2] + 1; + } + + // now calculate the volume of each channel and add them together + // the magic number 42 is to make the volume comparable to the MSG volume + int v; + + v = (short)(A_up ? LogScale[Chan_vol[0]] * 42 : 0); + + v += (short)(B_up ? LogScale[Chan_vol[1]] * 42 : 0); + + v += (short)(C_up ? LogScale[Chan_vol[2]] * 42 : 0); + + v += (short)(noise_bit ? LogScale[Chan_vol[3]] * 42 : 0); + + current_sample = (short)v; + } + } + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/TMS9918A.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/TMS9918A.cs index c86bd515c1..9133312472 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/TMS9918A.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/TMS9918A.cs @@ -23,11 +23,11 @@ namespace BizHawk.Emulation.Cores.ColecoVision private bool Mode3Bit => (Registers[1] & 8) > 0; private bool EnableDoubledSprites => (Registers[1] & 1) > 0; private bool EnableLargeSprites => (Registers[1] & 2) > 0; - private bool EnableInterrupts => (Registers[1] & 32) > 0; + public bool EnableInterrupts => (Registers[1] & 32) > 0; private bool DisplayOn => (Registers[1] & 64) > 0; private bool Mode16k => (Registers[1] & 128) > 0; - private bool InterruptPending + public bool InterruptPending { get { return (StatusByte & 0x80) != 0; } set { StatusByte = (byte)((StatusByte & ~0x02) | (value ? 0x80 : 0x00)); } @@ -39,38 +39,6 @@ namespace BizHawk.Emulation.Cores.ColecoVision private int TmsPatternNameTableBase; private int TmsSpriteAttributeBase; - public void ExecuteFrame(bool Int_pending) - { - for (int scanLine = 0; scanLine < 262; scanLine++) - { - RenderScanline(scanLine); - - if (scanLine == 192) - { - - InterruptPending = true; - - if (EnableInterrupts) - Cpu.NonMaskableInterrupt = true; - } - - for (int i = 0; i < 228; i++) - { - Cpu.ExecuteOne(); - } - - Cpu.FlagI = false; - if (Int_pending && scanLine==50) - { - if (EnableInterrupts) - { - Cpu.FlagI = true; - Int_pending = false; - } - } - } - } - public void WriteVdpControl(byte value) { if (VdpWaitingForLatchByte) @@ -170,12 +138,9 @@ namespace BizHawk.Emulation.Cores.ColecoVision else if (Mode2Bit) TmsMode = 2; else if (Mode3Bit) TmsMode = 3; else TmsMode = 0; - - if (TmsMode == 1) - throw new Exception("TMS video mode 1! please tell vecna which game uses this!"); } - private void RenderScanline(int scanLine) + public void RenderScanline(int scanLine) { if (scanLine >= 192) return; @@ -195,7 +160,11 @@ namespace BizHawk.Emulation.Cores.ColecoVision RenderBackgroundM3(scanLine); RenderTmsSprites(scanLine); } - // This may seem silly but if I ever implement mode 1, sprites are not rendered in that. + else if (TmsMode == 1) + { + RenderBackgroundM1(scanLine); + // no sprites (text mode) + } } private void RenderBackgroundM0(int scanLine) @@ -233,6 +202,39 @@ namespace BizHawk.Emulation.Cores.ColecoVision } } + private void RenderBackgroundM1(int scanLine) + { + if (DisplayOn == false) + { + Array.Clear(FrameBuffer, scanLine * 256, 256); + return; + } + + int yc = scanLine / 8; + int yofs = scanLine % 8; + int FrameBufferOffset = scanLine * 256; + int PatternNameOffset = TmsPatternNameTableBase + (yc * 40); + int ScreenBGColor = PaletteTMS9918[Registers[7] & 0x0F]; + + for (int xc = 0; xc < 40; xc++) + { + int pn = VRAM[PatternNameOffset++]; + int pv = VRAM[PatternGeneratorBase + (pn * 8) + yofs]; + int colorEntry = Registers[7]; + int fgIndex = (colorEntry >> 4) & 0x0F; + int bgIndex = colorEntry & 0x0F; + int fgColor = fgIndex == 0 ? ScreenBGColor : PaletteTMS9918[fgIndex]; + int bgColor = bgIndex == 0 ? ScreenBGColor : PaletteTMS9918[bgIndex]; + + FrameBuffer[FrameBufferOffset++] = ((pv & 0x80) > 0) ? fgColor : bgColor; + FrameBuffer[FrameBufferOffset++] = ((pv & 0x40) > 0) ? fgColor : bgColor; + FrameBuffer[FrameBufferOffset++] = ((pv & 0x20) > 0) ? fgColor : bgColor; + FrameBuffer[FrameBufferOffset++] = ((pv & 0x10) > 0) ? fgColor : bgColor; + FrameBuffer[FrameBufferOffset++] = ((pv & 0x08) > 0) ? fgColor : bgColor; + FrameBuffer[FrameBufferOffset++] = ((pv & 0x04) > 0) ? fgColor : bgColor; + } + } + void RenderBackgroundM2(int scanLine) { if (DisplayOn == false) diff --git a/BizHawk.Emulation.Cores/Consoles/Intellivision/STIC.cs b/BizHawk.Emulation.Cores/Consoles/Intellivision/STIC.cs index 97097e097e..856aea5f8e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Intellivision/STIC.cs +++ b/BizHawk.Emulation.Cores/Consoles/Intellivision/STIC.cs @@ -153,6 +153,9 @@ namespace BizHawk.Emulation.Cores.Intellivision else if (reg < 0x20) { value = (ushort)((value & 0x3FF) | 0x3C00); + + // self interactions can never be set, even by writing directly + value &= (ushort)(0xFFFF - (1 << (reg - 0x18))); } else if (reg < 0x28) { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs index 13467e19d2..e3d817d8c3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs @@ -58,6 +58,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA public static extern bool BizAdvance(IntPtr ctx, LibVBANext.Buttons keys, int[] vbuff, ref int nsamp, short[] sbuff, long time, short gyrox, short gyroy, short gyroz, byte luma); + [DllImport(dll, CallingConvention = cc)] + public static extern void BizSetPalette(IntPtr ctx, int[] palette); + [StructLayout(LayoutKind.Sequential)] public class MemoryAreas { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs index 88cf12cb40..7a655c3b02 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using BizHawk.Common; using BizHawk.Emulation.Common; +using System.ComponentModel.DataAnnotations; +using BizHawk.Emulation.Cores.Nintendo.Gameboy; namespace BizHawk.Emulation.Cores.Nintendo.GBA { @@ -32,6 +34,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA if (o.PlayChB) smask |= LibmGBA.Sounds.CHB; LibmGBA.BizSetSoundMask(_core, smask); + var palette = new int[65536]; + GBColors.ColorType c = GBColors.ColorType.vivid; + switch (o.ColorType) + { + case Settings.ColorTypes.Gambatte: c = GBColors.ColorType.gambatte; break; + case Settings.ColorTypes.Vivid: c = GBColors.ColorType.vivid; break; + case Settings.ColorTypes.VbaVivid: c = GBColors.ColorType.vbavivid; break; + case Settings.ColorTypes.VbaGbNew: c = GBColors.ColorType.vbagbnew; break; + case Settings.ColorTypes.VbaGbOld: c = GBColors.ColorType.vbabgbold; break; + case Settings.ColorTypes.BizhawkGba: c = GBColors.ColorType.gba; break; + } + GBColors.GetLut(c, palette); + for (var i = 32768; i < 65536; i++) + palette[i] = palette[i - 32768]; + LibmGBA.BizSetPalette(_core, palette); + _settings = o; return false; } @@ -84,6 +102,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA [DefaultValue(true)] public bool PlayChB { get; set; } + public enum ColorTypes + { + [Display(Name = "Gambatte CGB")] + Gambatte, + [Display(Name = "Vivid")] + Vivid, + [Display(Name = "VBA Vivid")] + VbaVivid, + [Display(Name = "VBA GB")] + VbaGbNew, + [Display(Name = "VBA GB (Old)")] + VbaGbOld, + [Display(Name = "Bizhawk GBA")] + BizhawkGba + } + + [DisplayName("Color Type")] + [DefaultValue(ColorTypes.Vivid)] + [TypeConverter(typeof(DescribableEnumConverter))] + public ColorTypes ColorType { get; set; } + public Settings Clone() { return (Settings)MemberwiseClone(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Audio.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Audio.cs index 28b9c2edf2..2574978258 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Audio.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Audio.cs @@ -188,11 +188,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk SQ1_len_cntr = SQ1_length; break; case 0xFF12: // NR12 (envelope) - Audio_Regs[NR12] = value; SQ1_st_vol = (byte)((value & 0xF0) >> 4); SQ1_env_add = (value & 8) > 0; SQ1_per = (byte)(value & 7); + + // several glitchy effects happen when writing to NRx2 during audio playing + if (((Audio_Regs[NR12] & 7) == 0) && !SQ1_vol_done) { SQ1_vol_state++; } + else if ((Audio_Regs[NR12] & 8) == 0) { SQ1_vol_state += 2; } + + if (((Audio_Regs[NR12] ^ value) & 8) > 0) { SQ1_vol_state = (byte)(0x10 - SQ1_vol_state); } + + SQ1_vol_state &= 0xF; + if ((value & 0xF8) == 0) { SQ1_enable = SQ1_swp_enable = false; SQ1_output = 0; } + Audio_Regs[NR12] = value; break; case 0xFF13: // NR13 (freq low) Audio_Regs[NR13] = value; @@ -269,11 +278,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk SQ2_len_cntr = SQ2_length; break; case 0xFF17: // NR22 (envelope) - Audio_Regs[NR22] = value; SQ2_st_vol = (byte)((value & 0xF0) >> 4); SQ2_env_add = (value & 8) > 0; SQ2_per = (byte)(value & 7); + + // several glitchy effects happen when writing to NRx2 during audio playing + if (((Audio_Regs[NR22] & 7) == 0) && !SQ2_vol_done) { SQ2_vol_state++; } + else if ((Audio_Regs[NR22] & 8) == 0) { SQ2_vol_state += 2; } + + if (((Audio_Regs[NR22] ^ value) & 8) > 0) { SQ2_vol_state = (byte)(0x10 - SQ2_vol_state); } + + SQ2_vol_state &= 0xF; if ((value & 0xF8) == 0) { SQ2_enable = false; SQ2_output = 0; } + Audio_Regs[NR22] = value; break; case 0xFF18: // NR23 (freq low) Audio_Regs[NR23] = value; @@ -388,11 +405,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk NOISE_len_cntr = NOISE_length; break; case 0xFF21: // NR42 (envelope) - Audio_Regs[NR42] = value; NOISE_st_vol = (byte)((value & 0xF0) >> 4); NOISE_env_add = (value & 8) > 0; NOISE_per = (byte)(value & 7); + + // several glitchy effects happen when writing to NRx2 during audio playing + if (((Audio_Regs[NR42] & 7) == 0) && !NOISE_vol_done) { NOISE_vol_state++; } + else if ((Audio_Regs[NR42] & 8) == 0) { NOISE_vol_state += 2; } + + if (((Audio_Regs[NR42] ^ value) & 8) > 0) { NOISE_vol_state = (byte)(0x10 - NOISE_vol_state); } + + NOISE_vol_state &= 0xF; if ((value & 0xF8) == 0) { NOISE_enable = false; NOISE_output = 0; } + Audio_Regs[NR42] = value; break; case 0xFF22: // NR43 (shift) Audio_Regs[NR43] = value; @@ -961,7 +986,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk ser.Sync("sequencer_tick", ref sequencer_tick); ser.Sync("master_audio_clock", ref master_audio_clock); - } + + ser.Sync("AUD_CTRL_vin_L_en", ref AUD_CTRL_vin_L_en); + ser.Sync("AUD_CTRL_vin_R_en", ref AUD_CTRL_vin_R_en); + ser.Sync("AUD_CTRL_sq1_L_en", ref AUD_CTRL_sq1_L_en); + ser.Sync("AUD_CTRL_sq2_L_en", ref AUD_CTRL_sq2_L_en); + ser.Sync("AUD_CTRL_wave_L_en", ref AUD_CTRL_wave_L_en); + ser.Sync("AUD_CTRL_noise_L_en", ref AUD_CTRL_noise_L_en); + ser.Sync("AUD_CTRL_sq1_R_en", ref AUD_CTRL_sq1_R_en); + ser.Sync("AUD_CTRL_sq2_R_en", ref AUD_CTRL_sq2_R_en); + ser.Sync("AUD_CTRL_wave_R_en", ref AUD_CTRL_wave_R_en); + ser.Sync("AUD_CTRL_noise_R_en", ref AUD_CTRL_noise_R_en); + ser.Sync("AUD_CTRL_power", ref AUD_CTRL_power); + ser.Sync("AUD_CTRL_vol_L", ref AUD_CTRL_vol_L); + ser.Sync("AUD_CTRL_vol_R", ref AUD_CTRL_vol_R); + } public byte Read_NR52() { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs index af6cc525d9..bc094d10b3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs @@ -37,7 +37,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk color_palette[3] = color_palette_Gr[3]; } - if (_tracer.Enabled) { cpu.TraceCallback = s => _tracer.Put(s); @@ -103,9 +102,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk input_register |= 0xF; } - // check for interrupts - - + // check for interrupts if (((contr_prev & 8) > 0) && ((input_register & 8) == 0) || ((contr_prev & 4) > 0) && ((input_register & 4) == 0) || ((contr_prev & 2) > 0) && ((input_register & 2) == 0) || @@ -115,8 +112,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk REG_FF0F |= 0x10; } - - while (!vblank_rise && (ticker < 100000)) + while (!vblank_rise) { audio.tick(); timer.tick_1(); @@ -133,7 +129,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { vblank_rise = true; } + ticker++; + if (ticker > 10000000) { throw new Exception("ERROR: Unable to Resolve Frame"); } + in_vblank_old = in_vblank; } @@ -174,7 +173,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk Marshal.FreeHGlobal(iptr3); } - #region Video provider public int _frameHz = 60; @@ -183,7 +181,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public int[] GetVideoBuffer() { - return _vidbuffer; + if (ppu.blank_frame) + { + for (int i = 0; i < _vidbuffer.Length; i++) + { + _vidbuffer[i] = (int)color_palette[0]; + } + ppu.blank_frame = false; + } + return _vidbuffer; } public int VirtualWidth => 160; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IMemoryDomains.cs index 375abbd592..b336e091fe 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IMemoryDomains.cs @@ -41,9 +41,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk MemoryDomain.Endian.Little, addr => _rom[addr], (addr, value) => ZP_RAM[addr] = value, - 1) + 1), }; + if (cart_RAM != null) + { + var CartRam = new MemoryDomainByteArray("Cart RAM", MemoryDomain.Endian.Little, cart_RAM, true, 1); + domains.Add(CartRam); + } + MemoryDomains = new MemoryDomainList(domains); (ServiceProvider as BasicServiceProvider).Register(MemoryDomains); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISaveRam.cs index d1fa270c30..8e9c0ddbe8 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISaveRam.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISaveRam.cs @@ -7,19 +7,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { public byte[] CloneSaveRam() { - return (byte[])_sram.Clone(); + return (byte[])cart_RAM.Clone(); } public void StoreSaveRam(byte[] data) { - Buffer.BlockCopy(data, 0, _sram, 0, data.Length); + Buffer.BlockCopy(data, 0, cart_RAM, 0, data.Length); + Console.WriteLine("loading SRAM here"); } public bool SaveRamModified { get { - return false; + return has_bat & _syncSettings.Use_SRAM; } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISettable.cs index 79217600e4..56cea73ae2 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.ISettable.cs @@ -44,8 +44,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk Gr } - [DisplayName("Console Mode")] - [Description("Pick which console to run, 'Auto' chooses from ROM header, 'GB' and 'GBC' chooses the respective system")] + [DisplayName("Color Mode")] + [Description("Pick Between Green scale and Grey scale colors")] [DefaultValue(PaletteType.BW)] public PaletteType Palette { get; set; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs index ec6cc6a5c8..1caa711336 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs @@ -44,11 +44,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public byte[] OAM = new byte[0xA0]; public readonly byte[] _rom; - public readonly byte[] _bios; - public readonly byte[] _sram = new byte[2048]; + public readonly byte[] _bios; public readonly byte[] header = new byte[0x50]; public byte[] cart_RAM; + public bool has_bat; private int _frame = 0; @@ -88,7 +88,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk _syncSettings = (GBSyncSettings)syncSettings ?? new GBSyncSettings(); _controllerDeck = new GBHawkControllerDeck(_syncSettings.Port1); - byte[] Bios = comm.CoreFileProvider.GetFirmware("GB", "World", false, "BIOS Not Found, Cannot Load"); + byte[] Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load"); if (Bios == null) { @@ -250,39 +250,39 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk switch (header[0x47]) { - case 0x0: mapper = new MapperDefault(); mppr = "NROM"; break; - case 0x1: mapper = new MapperMBC1(); mppr = "MBC1"; break; - case 0x2: mapper = new MapperMBC1(); mppr = "MBC1"; break; - case 0x3: mapper = new MapperMBC1(); mppr = "MBC1"; break; - case 0x5: mapper = new MapperMBC2(); mppr = "MBC2"; break; - case 0x6: mapper = new MapperMBC2(); mppr = "MBC2"; break; - case 0x8: mapper = new MapperDefault(); mppr = "NROM"; break; - case 0x9: mapper = new MapperDefault(); mppr = "NROM"; break; - case 0xB: mapper = new MapperMMM01(); mppr = "MMM01"; break; - case 0xC: mapper = new MapperMMM01(); mppr = "MMM01"; break; - case 0xD: mapper = new MapperMMM01(); mppr = "MMM01"; break; - case 0xF: mapper = new MapperMBC3(); mppr = "MBC3"; break; - case 0x10: mapper = new MapperMBC3(); mppr = "MBC3"; break; - case 0x11: mapper = new MapperMBC3(); mppr = "MBC3"; break; - case 0x12: mapper = new MapperMBC3(); mppr = "MBC3"; break; - case 0x13: mapper = new MapperMBC3(); mppr = "MBC3"; break; - case 0x19: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x1A: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x1B: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x1C: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x1D: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x1E: mapper = new MapperMBC5(); mppr = "MBC5"; break; - case 0x20: mapper = new MapperMBC6(); mppr = "MBC6"; break; - case 0x22: mapper = new MapperMBC7(); mppr = "MBC7"; break; - case 0xFC: mapper = new MapperCamera(); mppr = "CAM"; break; - case 0xFD: mapper = new MapperTAMA5(); mppr = "TAMA5"; break; - case 0xFE: mapper = new MapperHuC3(); mppr = "HuC3"; break; - case 0xFF: mapper = new MapperHuC1(); mppr = "HuC1"; break; + case 0x0: mapper = new MapperDefault(); mppr = "NROM"; break; + case 0x1: mapper = new MapperMBC1(); mppr = "MBC1"; break; + case 0x2: mapper = new MapperMBC1(); mppr = "MBC1"; break; + case 0x3: mapper = new MapperMBC1(); mppr = "MBC1"; has_bat = true; break; + case 0x5: mapper = new MapperMBC2(); mppr = "MBC2"; break; + case 0x6: mapper = new MapperMBC2(); mppr = "MBC2"; has_bat = true; break; + case 0x8: mapper = new MapperDefault(); mppr = "NROM"; break; + case 0x9: mapper = new MapperDefault(); mppr = "NROM"; has_bat = true; break; + case 0xB: mapper = new MapperMMM01(); mppr = "MMM01"; break; + case 0xC: mapper = new MapperMMM01(); mppr = "MMM01"; break; + case 0xD: mapper = new MapperMMM01(); mppr = "MMM01"; has_bat = true; break; + case 0xF: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break; + case 0x10: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break; + case 0x11: mapper = new MapperMBC3(); mppr = "MBC3"; break; + case 0x12: mapper = new MapperMBC3(); mppr = "MBC3"; break; + case 0x13: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break; + case 0x19: mapper = new MapperMBC5(); mppr = "MBC5"; break; + case 0x1A: mapper = new MapperMBC5(); mppr = "MBC5"; has_bat = true; break; + case 0x1B: mapper = new MapperMBC5(); mppr = "MBC5"; break; + case 0x1C: mapper = new MapperMBC5(); mppr = "MBC5"; break; + case 0x1D: mapper = new MapperMBC5(); mppr = "MBC5"; break; + case 0x1E: mapper = new MapperMBC5(); mppr = "MBC5"; has_bat = true; break; + case 0x20: mapper = new MapperMBC6(); mppr = "MBC6"; break; + case 0x22: mapper = new MapperMBC7(); mppr = "MBC7"; has_bat = true; break; + case 0xFC: mapper = new MapperCamera(); mppr = "CAM"; break; + case 0xFD: mapper = new MapperTAMA5(); mppr = "TAMA5"; break; + case 0xFE: mapper = new MapperHuC3(); mppr = "HuC3"; break; + case 0xFF: mapper = new MapperHuC1(); mppr = "HuC1"; break; // Bootleg mappers // NOTE: Sachen mapper selection does not account for scrambling, so if another bootleg mapper // identifies itself as 0x31, this will need to be modified - case 0x31: mapper = new MapperSachen2(); mppr = "Schn2"; break; + case 0x31: mapper = new MapperSachen2(); mppr = "Schn2"; break; case 0x4: case 0x7: @@ -299,8 +299,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk // mapper not implemented Console.WriteLine(header[0x47]); throw new Exception("Mapper not implemented"); - break; - } // special case for multi cart mappers @@ -357,16 +355,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk Console.Write("RAM: "); Console.WriteLine(cart_RAM.Length); - if (_syncSettings.Use_SRAM) + for (int i = 0; i < cart_RAM.Length; i++) { - // load cartRAM here - } - else - { - for (int i = 0; i < cart_RAM.Length; i++) - { - cart_RAM[i] = 0xFF; - } + cart_RAM[i] = 0xFF; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawkControllers.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawkControllers.cs index 1900a4879b..87006a5539 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawkControllers.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawkControllers.cs @@ -44,12 +44,38 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public byte Read(IController c) { byte result = 0xFF; - for (int i = 0; i < 8; i++) + + if (c.IsPressed(Definition.BoolButtons[0])) { - if (c.IsPressed(Definition.BoolButtons[i])) - { - result -= (byte)(1 << i); - } + result -= 4; + } + if (c.IsPressed(Definition.BoolButtons[1])) + { + result -= 8; + } + if (c.IsPressed(Definition.BoolButtons[2])) + { + result -= 2; + } + if (c.IsPressed(Definition.BoolButtons[3])) + { + result -= 1; + } + if (c.IsPressed(Definition.BoolButtons[4])) + { + result -= 128; + } + if (c.IsPressed(Definition.BoolButtons[5])) + { + result -= 64; + } + if (c.IsPressed(Definition.BoolButtons[6])) + { + result -= 32; + } + if (c.IsPressed(Definition.BoolButtons[7])) + { + result -= 16; } return result; @@ -57,7 +83,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk private static readonly string[] BaseDefinition = { - "Right", "Left", "Up", "Down", "A", "B", "Select", "Start", "Power" + "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" }; public void SyncState(Serializer ser) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC1.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC1.cs index c0c46c1890..d8763afa6a 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC1.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC1.cs @@ -83,7 +83,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { if (addr < 0x2000) { - RAM_enable = (value & 0xA) == 0xA; + RAM_enable = (value & 0xF) == 0xA; } else if (addr < 0x4000) { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs index aa2bc8ca7e..6dc0c08613 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs @@ -59,6 +59,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public int tile_byte; public int sprite_fetch_cycles; public bool fetch_sprite; + public bool fetch_sprite_01; + public bool fetch_sprite_4; + public bool going_to_fetch; + public int sprite_fetch_counter; + public byte[] sprite_attr_list = new byte[160]; + public byte[] sprite_pixel_list = new byte[160]; + public byte[] sprite_present_list = new byte[160]; public int temp_fetch; public int tile_inc; public bool pre_render; @@ -74,11 +81,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public byte[] sprite_sel = new byte[2]; public int sl_use_index; public bool no_sprites; - public int sprite_fetch_index; public int[] SL_sprites_ordered = new int[40]; // (x_end, data_low, data_high, attr) - public int index_used; + public int evaled_sprites; public int sprite_ordered_index; - public int bottom_index; + public bool blank_frame; // windowing state public int window_counter; @@ -88,6 +94,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk public int window_y_tile; public int window_x_tile; public int window_y_tile_inc; + public int window_x_latch; public byte ReadReg(int addr) { @@ -125,6 +132,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk OAM_access_write = true; } + if (!LCDC.Bit(7) && value.Bit(7)) + { + // don't draw for one frame after turning on + blank_frame = true; + } + LCDC = value; break; case 0xFF41: // STAT @@ -175,13 +188,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk case 0xFF4B: // WX window_x = value; break; - } + } } public void tick() { - // tick DMA - if (DMA_start) + // tick DMA, note that DMA is halted when the CPU is halted + if (DMA_start && !Core.cpu.halted) { if (DMA_clock >= 4) { @@ -195,14 +208,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } else if ((DMA_clock % 4) == 3) { - if ((DMA_inc % 4) == 3) - { - Core.OAM[DMA_inc] = DMA_byte; - } - else - { - Core.OAM[DMA_inc] = DMA_byte; - } + Core.OAM[DMA_inc] = DMA_byte; if (DMA_inc < (0xA0 - 1)) { DMA_inc++; } } @@ -213,6 +219,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk if (DMA_clock == 648) { DMA_start = false; + DMA_OAM_access = true; } } @@ -232,10 +239,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } } - cycle = 0; LY += LY_inc; - //Console.WriteLine(Core.cpu.TotalExecutedCycles); no_scan = false; // here is where LY = LYC gets cleared (but only if LY isnt 0 as that's a special case @@ -387,7 +392,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { if (cycle == 4) { - // apparently, writes can make it to OAm one cycle longer then reads + // apparently, writes can make it to OAM one cycle longer then reads OAM_access_write = false; // here mode 2 will be set to true and interrupts fired if enabled @@ -416,11 +421,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk // render the screen and handle hblank render(cycle - 80); } - } - + } } - if ((LY_inc == 0)) { if (cycle == 12) @@ -458,10 +461,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } else { - // screen disable sets STAT as though it were vblank, but there is no Stat IRQ asserted - //STAT &= 0xFC; - //STAT |= 0x01; - STAT &= 0xF8; VBL_INT = LYC_INT = HBL_INT = OAM_INT = false; @@ -489,7 +488,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk // process latch delays //latch_delay(); - } // might be needed, not sure yet @@ -527,14 +525,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { if (OAM_scan_index < 40) { + ushort temp = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4] : (ushort)0xFF; // (sprite Y - 16) equals LY, we have a sprite - if ((Core.OAM[OAM_scan_index * 4] - 16) <= LY && - ((Core.OAM[OAM_scan_index * 4] - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY) + if ((temp - 16) <= LY && + ((temp - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY) { // always pick the first 10 in range sprites if (SL_sprites_index < 10) { - SL_sprites[SL_sprites_index * 4] = Core.OAM[OAM_scan_index * 4]; + SL_sprites[SL_sprites_index * 4] = temp; write_sprite = 1; } @@ -552,7 +551,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } else { - SL_sprites[SL_sprites_index * 4 + write_sprite] = Core.OAM[OAM_scan_index * 4 + write_sprite]; + ushort temp2 = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4 + write_sprite] : (ushort)0xFF; + SL_sprites[SL_sprites_index * 4 + write_sprite] = temp2; write_sprite++; if (write_sprite == 4) @@ -576,18 +576,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk OAM_access_write = true; VRAM_access_read = false; + // window X is latched for the scanline, mid-line changes have no effect + window_x_latch = window_x; + OAM_scan_index = 0; read_case = 0; internal_cycle = 0; pre_render = true; tile_inc = 0; - pixel_counter = 0; + pixel_counter = -8; sl_use_index = 0; - index_used = 0; - bottom_index = 0; - sprite_ordered_index = 0; fetch_sprite = false; + fetch_sprite_01 = false; + fetch_sprite_4 = false; + going_to_fetch = false; no_sprites = false; + evaled_sprites = 0; window_pre_render = false; if (window_started && LCDC.Bit(5)) @@ -602,28 +606,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } window_started = false; - // calculate the row number of the tiles to be fetched - y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32; + if (SL_sprites_index == 0) { no_sprites = true; } + // it is much easier to process sprites if we order them according to the rules of sprite priority first + if (!no_sprites) { reorder_and_assemble_sprites(); } - if (SL_sprites_index == 0) - { - no_sprites = true; - } } - + // before anything else, we have to check if windowing is in effect - if (LCDC.Bit(5) && !window_started && (LY >= window_y) && (pixel_counter >= (window_x - 7))) + if (LCDC.Bit(5) && !window_started && (LY >= window_y) && (pixel_counter >= (window_x_latch - 7)) && (window_x_latch < 167)) { /* Console.Write(LY); Console.Write(" "); - Console.Write(window_y); + Console.Write(cycle); Console.Write(" "); Console.Write(window_y_tile_inc); Console.Write(" "); - Console.WriteLine(scroll_y); + Console.Write(window_x_latch); + Console.Write(" "); + Console.WriteLine(pixel_counter); */ - if (pixel_counter == 0 && window_x <= 7) + if (pixel_counter == 0 && window_x_latch <= 7) { // if the window starts at zero, we still do the first access to the BG // but then restart all over again at the window @@ -637,7 +640,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } window_counter = 0; - window_x_tile = (int)Math.Floor((float)(pixel_counter - (window_x - 7)) / 8); + window_x_tile = (int)Math.Floor((float)(pixel_counter - (window_x_latch - 7)) / 8); window_tile_inc = 0; window_started = true; @@ -645,132 +648,117 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk if (!pre_render && !fetch_sprite && !window_pre_render) { - // start by fetching all the sprites that need to be fetched - if (!no_sprites) + // start shifting data into the LCD + if (render_counter >= (render_offset + 8)) { - for (int i = 0; i < SL_sprites_index; i++) + pixel = tile_data_latch[0].Bit(7 - (render_counter % 8)) ? 1 : 0; + pixel |= tile_data_latch[1].Bit(7 - (render_counter % 8)) ? 2 : 0; + + int ref_pixel = pixel; + if (LCDC.Bit(0)) { - if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) && - (pixel_counter < SL_sprites[i * 4 + 1]) && - !index_used.Bit(i)) - { - fetch_sprite = true; - sprite_fetch_index = 0; - } + pixel = (BGP >> (pixel * 2)) & 3; } - } - - if (!fetch_sprite) - { - // start shifting data into the LCD - if (render_counter >= (render_offset + 8)) + else { - pixel = tile_data_latch[0].Bit(7 - (render_counter % 8)) ? 1 : 0; - pixel |= tile_data_latch[1].Bit(7 - (render_counter % 8)) ? 2 : 0; - - int ref_pixel = pixel; - if (LCDC.Bit(0)) - { - pixel = (BGP >> (pixel * 2)) & 3; - } - else - { - pixel = 0; - } + pixel = 0; + } + // now we have the BG pixel, we next need the sprite pixel + if (!no_sprites) + { + bool have_sprite = false; + int s_pixel = 0; + int sprite_attr = 0; - // now we have the BG pixel, we next need the sprite pixel - if (!no_sprites) + if (sprite_present_list[pixel_counter] == 1) { - bool have_sprite = false; - int i = bottom_index; - int s_pixel = 0; - int sprite_attr = 0; - - while (i < sprite_ordered_index) - { - if (SL_sprites_ordered[i * 4] == pixel_counter) - { - bottom_index++; - if (bottom_index == SL_sprites_index) { no_sprites = true; } - } - else if (!have_sprite) - { - // we can use the current sprite, so pick out a pixel for it - int t_index = pixel_counter - (SL_sprites_ordered[i * 4] - 8); - - t_index = 7 - t_index; - - sprite_data[0] = (byte)((SL_sprites_ordered[i * 4 + 1] >> t_index) & 1); - sprite_data[1] = (byte)(((SL_sprites_ordered[i * 4 + 2] >> t_index) & 1) << 1); - - s_pixel = sprite_data[0] + sprite_data[1]; - sprite_attr = SL_sprites_ordered[i * 4 + 3]; - - // pixel color of 0 is transparent, so if this is the case we dont have a pixel - if (s_pixel != 0) - { - have_sprite = true; - } - } - i++; - } - - if (have_sprite) - { - bool use_sprite = false; - if (LCDC.Bit(1)) - { - if (!sprite_attr.Bit(7)) - { - use_sprite = true; - } - else if (ref_pixel == 0) - { - use_sprite = true; - } - - if (!LCDC.Bit(0)) - { - use_sprite = true; - } - } - - if (use_sprite) - { - if (sprite_attr.Bit(4)) - { - pixel = (obj_pal_1 >> (s_pixel * 2)) & 3; - } - else - { - pixel = (obj_pal_0 >> (s_pixel * 2)) & 3; - } - } - } + have_sprite = true; + s_pixel = sprite_pixel_list[pixel_counter]; + sprite_attr = sprite_attr_list[pixel_counter]; } - // based on sprite priority and pixel values, pick a final pixel color - Core._vidbuffer[LY * 160 + pixel_counter] = (int)Core.color_palette[pixel]; - pixel_counter++; - - if (pixel_counter == 160) + if (have_sprite) { - read_case = 8; - hbl_countdown = 4; + bool use_sprite = false; + if (LCDC.Bit(1)) + { + if (!sprite_attr.Bit(7)) + { + use_sprite = true; + } + else if (ref_pixel == 0) + { + use_sprite = true; + } + + if (!LCDC.Bit(0)) + { + use_sprite = true; + } + } + + if (use_sprite) + { + if (sprite_attr.Bit(4)) + { + pixel = (obj_pal_1 >> (s_pixel * 2)) & 3; + } + else + { + pixel = (obj_pal_0 >> (s_pixel * 2)) & 3; + } + } } } - render_counter++; + + // based on sprite priority and pixel values, pick a final pixel color + Core._vidbuffer[LY * 160 + pixel_counter] = (int)Core.color_palette[pixel]; + pixel_counter++; + + if (pixel_counter == 160) + { + read_case = 8; + hbl_countdown = 7; + } } + else if ((render_counter >= render_offset) && (pixel_counter < 0)) + { + pixel_counter++; + } + render_counter++; } if (!fetch_sprite) { - if (latch_new_data) + if (!pre_render) { - latch_new_data = false; - tile_data_latch[0] = tile_data[0]; - tile_data_latch[1] = tile_data[1]; + // before we go on to read case 3, we need to know if we stall there or not + // Gekkio's tests show that if sprites are at position 0 or 1 (mod 8) + // then it takes an extra cycle (1 or 2 more t-states) to process them + + if (!no_sprites && (pixel_counter < 160)) + { + for (int i = 0; i < SL_sprites_index; i++) + { + if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) && + (pixel_counter < (SL_sprites[i * 4 + 1])) && + !evaled_sprites.Bit(i)) + { + going_to_fetch = true; + fetch_sprite = true; + + if ((SL_sprites[i * 4 + 1] % 8) < 2) + { + fetch_sprite_01 = true; + } + if ((SL_sprites[i * 4 + 1] % 8) > 3) + { + fetch_sprite_4 = true; + } + } + } + } } switch (read_case) @@ -778,10 +766,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk case 0: // read a background tile if ((internal_cycle % 2) == 0) { + // calculate the row number of the tiles to be fetched + y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32; temp_fetch = y_tile * 32 + (x_tile + tile_inc) % 32; tile_byte = LCDC.Bit(3) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch]; - } else { @@ -857,7 +846,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk // here we set up rendering pre_render = false; render_offset = scroll_x % 8; - render_counter = -1; + render_counter = 0; latch_counter = 0; read_case = 0; } @@ -865,7 +854,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk { read_case = 3; } - } break; @@ -960,13 +948,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk // here we set up rendering window_pre_render = false; render_offset = 0; - render_counter = -1; + render_counter = 0; latch_counter = 0; read_case = 4; } else { read_case = 7; + } } window_counter++; @@ -1010,54 +999,71 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk break; } internal_cycle++; + + if (latch_new_data) + { + latch_new_data = false; + tile_data_latch[0] = tile_data[0]; + tile_data_latch[1] = tile_data[1]; + } } + // every in range sprite takes 6 cycles to process + // sprites located at x=0 still take 6 cycles to process even though they don't appear on screen + // sprites above x=168 do not take any cycles to process however if (fetch_sprite) { - if (sprite_fetch_index < SL_sprites_index) + if (going_to_fetch) { - if (pixel_counter != 0) { - if ((pixel_counter == (SL_sprites[sprite_fetch_index * 4 + 1] - 8)) && - //(pixel_counter < SL_sprites[sprite_fetch_index * 4 + 1]) && - !index_used.Bit(sprite_fetch_index)) - { - sl_use_index = sprite_fetch_index; - process_sprite(); - SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[sprite_fetch_index * 4 + 1]; - SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0]; - SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1]; - SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[sprite_fetch_index * 4 + 3]; - sprite_ordered_index++; - index_used |= (1 << sl_use_index); - } - sprite_fetch_index++; - if (sprite_fetch_index == SL_sprites_index) { fetch_sprite = false; } - } - else + going_to_fetch = false; + sprite_fetch_counter = 0; + + if (fetch_sprite_01) { - // whan pixel counter is 0, we want to scan all the points before 0 as well - // certainly non-physical but good enough for now - for (int j = -7; j < 1; j++) + sprite_fetch_counter += 2; + fetch_sprite_01 = false; + } + + if (fetch_sprite_4) + { + sprite_fetch_counter -= 2; + fetch_sprite_4 = false; + } + + int last_eval = 0; + + // at this time it is unknown what each cycle does, but we only need to accurately keep track of cycles + for (int i = 0; i < SL_sprites_index; i++) + { + if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) && + (pixel_counter < (SL_sprites[i * 4 + 1])) && + !evaled_sprites.Bit(i)) { - for (int i = 0; i < SL_sprites_index; i++) - { - if ((j == (SL_sprites[i * 4 + 1] - 8)) && - !index_used.Bit(i)) - { - sl_use_index = i; - process_sprite(); - SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[i * 4 + 1]; - SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0]; - SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1]; - SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[i * 4 + 3]; - sprite_ordered_index++; - index_used |= (1 << sl_use_index); - } - } + sprite_fetch_counter += 6; + evaled_sprites |= (1 << i); + last_eval = SL_sprites[i * 4 + 1]; } - fetch_sprite = false; + } + + // if we didn't evaluate all the sprites immediately, 2 more cycles are added to restart it + if (evaled_sprites != (Math.Pow(2,SL_sprites_index) - 1)) + { + if ((last_eval % 8) == 0) { sprite_fetch_counter += 3; } + else if ((last_eval % 8) == 1) { sprite_fetch_counter += 2; } + else if ((last_eval % 8) == 2) { sprite_fetch_counter += 3; } + else if ((last_eval % 8) == 3) { sprite_fetch_counter += 2; } + else if ((last_eval % 8) == 4) { sprite_fetch_counter += 3; } + else { sprite_fetch_counter += 2; } } } + else + { + sprite_fetch_counter--; + if (sprite_fetch_counter == 0) + { + fetch_sprite = false; + } + } } } @@ -1075,12 +1081,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk obj_pal_1 = 0xFF; window_y = 0x0; window_x = 0x0; + window_x_latch = 0xFF; LY_inc = 1; no_scan = false; OAM_access_read = true; VRAM_access_read = true; OAM_access_write = true; VRAM_access_write = true; + DMA_OAM_access = true; cycle = 0; LYC_INT = false; @@ -1156,6 +1164,74 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk } } + // order sprites according to x coordinate + // note that for sprites of equal x coordinate, priority goes to first on the list + public void reorder_and_assemble_sprites() + { + sprite_ordered_index = 0; + + for (int i = 0; i < 256; i++) + { + for (int j = 0; j < SL_sprites_index; j++) + { + if (SL_sprites[j * 4 + 1] == i) + { + sl_use_index = j; + process_sprite(); + SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[j * 4 + 1]; + SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0]; + SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1]; + SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[j * 4 + 3]; + sprite_ordered_index++; + } + } + } + + bool have_pixel = false; + byte s_pixel = 0; + byte sprite_attr = 0; + + for (int i = 0; i < 160; i++) + { + have_pixel = false; + for (int j = 0; j < SL_sprites_index; j++) + { + if ((i >= (SL_sprites_ordered[j * 4] - 8)) && + (i < SL_sprites_ordered[j * 4]) && + !have_pixel) + { + // we can use the current sprite, so pick out a pixel for it + int t_index = i - (SL_sprites_ordered[j * 4] - 8); + + t_index = 7 - t_index; + + sprite_data[0] = (byte)((SL_sprites_ordered[j * 4 + 1] >> t_index) & 1); + sprite_data[1] = (byte)(((SL_sprites_ordered[j * 4 + 2] >> t_index) & 1) << 1); + + s_pixel = (byte)(sprite_data[0] + sprite_data[1]); + sprite_attr = (byte)SL_sprites_ordered[j * 4 + 3]; + + // pixel color of 0 is transparent, so if this is the case we dont have a pixel + if (s_pixel != 0) + { + have_pixel = true; + } + } + } + + if (have_pixel) + { + sprite_present_list[i] = 1; + sprite_pixel_list[i] = s_pixel; + sprite_attr_list[i] = sprite_attr; + } + else + { + sprite_present_list[i] = 0; + } + } + } + public void SyncState(Serializer ser) { ser.Sync("LCDC", ref LCDC); @@ -1206,6 +1282,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk ser.Sync("tile_byte", ref tile_byte); ser.Sync("sprite_fetch_cycles", ref sprite_fetch_cycles); ser.Sync("fetch_sprite", ref fetch_sprite); + ser.Sync("fetch_sprite_01", ref fetch_sprite_01); + ser.Sync("fetch_sprite_4", ref fetch_sprite_4); + ser.Sync("going_to_fetch", ref going_to_fetch); + ser.Sync("sprite_fetch_counter", ref sprite_fetch_counter); + ser.Sync("sprite_attr_list", ref sprite_attr_list, false); + ser.Sync("sprite_pixel_list", ref sprite_pixel_list, false); + ser.Sync("sprite_present_list", ref sprite_present_list, false); ser.Sync("temp_fetch", ref temp_fetch); ser.Sync("tile_inc", ref tile_inc); ser.Sync("pre_render", ref pre_render); @@ -1221,11 +1304,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk ser.Sync("sl_use_index", ref sl_use_index); ser.Sync("sprite_sel", ref sprite_sel, false); ser.Sync("no_sprites", ref no_sprites); - ser.Sync("sprite_fetch_index", ref sprite_fetch_index); + ser.Sync("evaled_sprites", ref evaled_sprites); ser.Sync("SL_sprites_ordered", ref SL_sprites_ordered, false); - ser.Sync("index_used", ref index_used); ser.Sync("sprite_ordered_index", ref sprite_ordered_index); - ser.Sync("bottom_index", ref bottom_index); + ser.Sync("blank_frame", ref blank_frame); ser.Sync("window_counter", ref window_counter); ser.Sync("window_pre_render", ref window_pre_render); @@ -1234,6 +1316,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk ser.Sync("window_y_tile", ref window_y_tile); ser.Sync("window_x_tile", ref window_x_tile); ser.Sync("window_y_tile_inc", ref window_y_tile_inc); + ser.Sync("window_x_latch", ref window_x_latch); } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs index 7e2ba05432..2fd947256c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs @@ -132,6 +132,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DeepEqualsIgnore] private bool _equalLengthFrames; + [DisplayName("Initial DIV offset")] + [Description("Internal. Probably doesn't work. Leave this set to 0. Accepts values from 0 to 65532 in steps of 4")] + [DefaultValue(0)] + public int InitialDiv { get; set; } + public GambatteSyncSettings() { SettingsUtil.SetDefaultValues(this); @@ -146,6 +151,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { return !DeepEquality.DeepEquals(x, y); } + + public uint GetInitialDivInternal() + { + return (uint)(InitialDiv & 0xfffc); + } } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs index 5818d6634b..55af4c3d1f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs @@ -38,6 +38,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy writer.Write(Frame); writer.Write(frameOverflow); writer.Write(_cycleCount); + writer.Write(IsCgb); } public void LoadStateBinary(BinaryReader reader) @@ -61,6 +62,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy Frame = reader.ReadInt32(); frameOverflow = reader.ReadUInt32(); _cycleCount = reader.ReadUInt64(); + IsCgb = reader.ReadBoolean(); } public byte[] SaveStateBinary() @@ -84,7 +86,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy private void NewSaveCoreSetBuff() { _savebuff = new byte[LibGambatte.gambatte_newstatelen(GambatteState)]; - _savebuff2 = new byte[_savebuff.Length + 4 + 21]; + _savebuff2 = new byte[_savebuff.Length + 4 + 21 + 1]; } private readonly JsonSerializer ser = new JsonSerializer { Formatting = Formatting.Indented }; @@ -97,6 +99,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public bool IsLagFrame; public ulong _cycleCount; public uint frameOverflow; + public bool IsCgb; } internal TextState SaveState() @@ -110,6 +113,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy s.ExtraData.Frame = Frame; s.ExtraData.frameOverflow = frameOverflow; s.ExtraData._cycleCount = _cycleCount; + s.ExtraData.IsCgb = IsCgb; return s; } @@ -123,6 +127,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy Frame = s.ExtraData.Frame; frameOverflow = s.ExtraData.frameOverflow; _cycleCount = s.ExtraData._cycleCount; + IsCgb = s.ExtraData.IsCgb; } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 74412c7ff4..4fa2a67ec4 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -63,6 +63,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy real_rtc_time = !DeterministicEmulation && _syncSettings.RealTimeRTC; + DivInternal = _syncSettings.GetInitialDivInternal(); + LibGambatte.LoadFlags flags = 0; switch (_syncSettings.ConsoleMode) @@ -88,11 +90,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy flags |= LibGambatte.LoadFlags.MULTICART_COMPAT; } - if (LibGambatte.gambatte_load(GambatteState, file, (uint)file.Length, GetCurrentTime(), flags) != 0) + if (LibGambatte.gambatte_load(GambatteState, file, (uint)file.Length, GetCurrentTime(), flags, DivInternal) != 0) { throw new InvalidOperationException("gambatte_load() returned non-zero (is this not a gb or gbc rom?)"); } + if ((flags & LibGambatte.LoadFlags.FORCE_DMG) == LibGambatte.LoadFlags.FORCE_DMG) + { + byte[] Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load"); + + IsCgb = false; + + if (LibGambatte.gambatte_loaddmgbios(GambatteState, Bios) != 0) + { + throw new InvalidOperationException("gambatte_loaddmgbios() returned non-zero (bios error)"); + } + } + else + { + byte[] Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load"); + + IsCgb = true; + + if (LibGambatte.gambatte_loadgbcbios(GambatteState, Bios) != 0) + { + throw new InvalidOperationException("gambatte_loadgbcbios() returned non-zero (bios error)"); + } + } + // set real default colors (before anyone mucks with them at all) PutSettings((GambatteSettings)settings ?? new GambatteSettings()); @@ -149,6 +174,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// private LibGambatte.Buttons CurrentButtons = 0; + private uint DivInternal = 0; + #region RTC /// @@ -209,6 +236,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public int LagCount { get; set; } public bool IsLagFrame { get; set; } + public bool IsCgb { get; set; } // all cycle counts are relative to a 2*1024*1024 mhz refclock @@ -251,7 +279,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// public bool IsCGBMode() { - return LibGambatte.gambatte_iscgb(GambatteState); + //return LibGambatte.gambatte_iscgb(GambatteState); + return IsCgb; } private InputCallbackSystem _inputCallbacks = new InputCallbackSystem(); @@ -297,7 +326,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (controller.IsPressed("Power")) { - LibGambatte.gambatte_reset(GambatteState, GetCurrentTime()); + LibGambatte.gambatte_reset(GambatteState, GetCurrentTime(), DivInternal); } if (Tracer.Enabled) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs index 3624bc9512..ecb9b432ca 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -57,7 +57,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// ORed combination of LoadFlags. /// 0 on success, negative value on failure. [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern int gambatte_load(IntPtr core, byte[] romdata, uint length, long now, LoadFlags flags); + public static extern int gambatte_load(IntPtr core, byte[] romdata, uint length, long now, LoadFlags flags, uint div); + + /// + /// Load GB BIOS image. + /// + /// opaque state pointer + /// the bios data, can be disposed of once this function returns + /// 0 on success, negative value on failure. + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_loaddmgbios(IntPtr core, byte[] biosdata); + + /// + /// Load GBC BIOS image. + /// + /// opaque state pointer + /// the bios data, can be disposed of once this function returns + /// 0 on success, negative value on failure. + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_loadgbcbios(IntPtr core, byte[] biosdata); /// /// Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer, @@ -106,7 +124,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// opaque state pointer /// RTC time when the reset occurs [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern void gambatte_reset(IntPtr core, long now); + public static extern void gambatte_reset(IntPtr core, long now, uint div); /// /// palette type for gambatte_setdmgpalettecolor diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusAudioApi.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs similarity index 100% rename from BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusAudioApi.cs rename to BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusCoreApi.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusCoreApi.cs similarity index 100% rename from BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusCoreApi.cs rename to BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusCoreApi.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusInputAPI.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusInputApi.cs similarity index 100% rename from BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusInputAPI.cs rename to BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusInputApi.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusVideoApi.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusVideoApi.cs similarity index 100% rename from BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeAPI/mupen64plusVideoApi.cs rename to BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusVideoApi.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs index 72cbcba367..303073dfbc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs @@ -539,7 +539,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES int linear_counter_reload, control_flag; // reg1 (n/a) // reg2/3 - int timer_cnt, halt_flag, len_cnt; + int timer_cnt, reload_flag, len_cnt; public bool halt_2; // misc.. int lenctr_en; @@ -556,7 +556,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("linear_counter_reload", ref linear_counter_reload); ser.Sync("control_flag", ref control_flag); ser.Sync("timer_cnt", ref timer_cnt); - ser.Sync("halt_flag", ref halt_flag); + ser.Sync("reload_flag", ref reload_flag); ser.Sync("len_cnt", ref len_cnt); ser.Sync("lenctr_en", ref lenctr_en); @@ -580,6 +580,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void WriteReg(int addr, byte val) { // Console.WriteLine("tri writes addr={0}, val={1:x2}", addr, val); + switch (addr) { case 0: @@ -605,7 +606,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; } - halt_flag = 1; + reload_flag = 1; // allow the lenctr_en to kill the len_cnt set_lenctr_en(lenctr_en); @@ -669,14 +670,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void clock_length_and_sweep() { // env_loopdoubles as "halt length counter" - if (len_cnt > 0 && halt_flag == 0) + if (len_cnt > 0 && control_flag == 0) len_cnt--; } public void clock_linear_counter() { - // Console.WriteLine("linear_counter: {0}", linear_counter); - if (halt_flag == 1) + //Console.WriteLine("linear_counter: {0}", linear_counter); + if (reload_flag == 1) { linear_counter = linear_counter_reload; } @@ -685,7 +686,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES linear_counter--; } - halt_flag = control_flag; + if (control_flag == 0) { reload_flag = 0; } } } // class TriangleUnit @@ -1328,6 +1329,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } SyncIRQ(); + nes._irq_apu = irq_pending; // since the units run concurrently, the APU frame sequencer is ran last because // it can change the ouput values of the pulse/triangle channels diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper043.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper043.cs new file mode 100644 index 0000000000..49e87f21da --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper043.cs @@ -0,0 +1,138 @@ +using System; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.NES +{ + public sealed class Mapper043 : NES.NESBoardBase + { + int prg = 0; + int irqcnt = 0; + bool irqenable = false; + bool swap; + + + private static int[] lut = { 4, 3, 5, 3, 6, 3, 7, 3 }; + + public override bool Configure(NES.EDetectionOrigin origin) + { + switch (Cart.board_type) + { + case "MAPPER043": + break; + default: + return false; + } + + Cart.wram_size = 0; + // not sure on initial mirroring + SetMirrorType(EMirrorType.Vertical); + return true; + } + + public override void WriteEXP(int addr, byte value) + { + addr += 0x4000; + + switch (addr & 0xF1FF) + { + case 0x4022: + prg = lut[value & 0x7]; + break; + + case 0x4120: + swap = (value & 1) == 1; + break; + + case 0x4122: + irqenable = (value & 1) == 1; + IRQSignal = false; + irqcnt = 0; + break; + } + } + + public override void WritePRG(int addr, byte value) + { + addr += 0x8000; + switch (addr & 0xF1FF) + { + case 0x8122: + irqenable = (value & 1) == 1; + IRQSignal = false; + irqcnt = 0; + break; + } + } + + public override byte ReadEXP(int addr) + { + if (addr > 0x1000) + { + return ROM[(addr - 0x1000) + 8 * 0x2000]; + } + else return base.ReadEXP(addr); + } + + public override byte ReadWRAM(int addr) + { + if (swap) + { + return ROM[addr]; + } + else + { + return ROM[addr + 0x4000]; + } + } + + public override byte ReadPRG(int addr) + { + if (addr < 0x2000) + { + return ROM[addr + 0x2000]; + } + else if (addr < 0x4000) + { + return ROM[addr - 0x2000]; + } + else if (addr < 0x6000) + { + return ROM[(addr - 0x4000) + prg * 0x2000]; + } + else + { + if (swap) + { + return ROM[(addr - 0x6000) + 8 * 0x2000]; + } + else + { + return ROM[(addr - 0x6000) + 9 * 0x2000]; + } + } + } + + public override void ClockCPU() + { + if (irqenable) + { + irqcnt++; + + if (irqcnt >= 4096) + { + irqenable = false; + IRQSignal = true; + } + } + } + + public override void SyncState(Serializer ser) + { + base.SyncState(ser); + ser.Sync("prg", ref prg); + ser.Sync("irqenable", ref irqenable); + ser.Sync("irqcnt", ref irqcnt); + ser.Sync("swap", ref swap); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper063.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper063.cs new file mode 100644 index 0000000000..c808732674 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper063.cs @@ -0,0 +1,85 @@ +using System; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.NES +{ + public sealed class Mapper063 : NES.NESBoardBase + { + int prg0, prg1, prg2, prg3; + bool open_bus; + + public override bool Configure(NES.EDetectionOrigin origin) + { + switch (Cart.board_type) + { + case "MAPPER063": + break; + default: + return false; + } + + Cart.wram_size = 0; + // not sure on initial mirroring + SetMirrorType(EMirrorType.Vertical); + + WritePRG(0, 0); + return true; + } + + public override void WritePRG(int addr, byte value) + { + open_bus = ((addr & 0x0300) == 0x0300); + + prg0 = (addr >> 1 & 0x1FC) | ((addr & 0x2) > 0 ? 0x0 : (addr >> 1 & 0x2) | 0x0); + prg1 = (addr >> 1 & 0x1FC) | ((addr & 0x2) > 0 ? 0x1 : (addr >> 1 & 0x2) | 0x1); + prg2 = (addr >> 1 & 0x1FC) | ((addr & 0x2) > 0 ? 0x2 : (addr >> 1 & 0x2) | 0x0); + prg3 = (addr & 0x800) > 0 ? ((addr & 0x07C) | ((addr & 0x06) > 0 ? 0x03 : 0x01)) : ((addr >> 1 & 0x01FC) | ((addr & 0x02) > 0 ? 0x03 : ((addr >> 1 & 0x02) | 0x01))); + + SetMirrorType((addr & 0x01) > 0 ? EMirrorType.Horizontal : EMirrorType.Vertical); + } + + public override byte ReadPRG(int addr) + { + if (addr < 0x2000) + { + if (open_bus) + { + return this.NES.DB; + } + else + { + return ROM[addr + prg0 * 0x2000]; + } + } + else if (addr < 0x4000) + { + if (open_bus) + { + return this.NES.DB; + } + else + { + return ROM[(addr - 0x2000) + prg1 * 0x2000]; + } + } + else if (addr < 0x6000) + { + return ROM[(addr - 0x4000) + prg2 * 0x2000]; + } + else + { + return ROM[(addr - 0x6000) + prg3 * 0x2000]; + } + } + + public override void SyncState(Serializer ser) + { + base.SyncState(ser); + ser.Sync("prg0", ref prg0); + ser.Sync("prg1", ref prg1); + ser.Sync("prg2", ref prg2); + ser.Sync("prg3", ref prg3); + ser.Sync("open_bus", ref open_bus); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL-KOF97.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL-KOF97.cs index 1c90947375..2e7f029f53 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL-KOF97.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL-KOF97.cs @@ -33,36 +33,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public override void WritePRG(int addr, byte value) { - if (addr < 0x1000) // 0x8000 - 0x8FFF - { - base.WritePRG(addr & 1, Unscramble(value)); - } + value = Unscramble(value); - else if (addr == 0x1000) // 9000 = 8001 + if (addr == 0x1000) // 9000 = 8001 { - base.WritePRG(1, Unscramble(value)); + base.WritePRG(1, value); } - - else if (addr == 0x2000) // A000 = 8000) - { - base.WritePRG(0, Unscramble(value)); - } - else if (addr == 0x5000) // D000 = C001 { - base.WritePRG(0x4001, Unscramble(value)); + base.WritePRG(0x4001, value); } - - else if (addr >= 0x6000 && addr < 0x7000) // 0xE0000 - 0xEFFF + else if (addr == 0x7000) // F000 = E001 { - base.WritePRG(addr & 1, Unscramble(value)); + base.WritePRG(0x6001, value); } - - else if (addr == 0x7000) // F000 - { - base.WritePRG(0x6001, Unscramble(value)); - } - else { base.WritePRG(addr, value); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_SMB2J.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_SMB2J.cs new file mode 100644 index 0000000000..41123272c6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_SMB2J.cs @@ -0,0 +1,92 @@ +using System; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.NES +{ + public sealed class UNIF_UNL_SMB2J : NES.NESBoardBase + { + int prg = 0; + int prg_count; + int irqcnt = 0; + bool irqenable = false; + bool swap; + + public override bool Configure(NES.EDetectionOrigin origin) + { + switch (Cart.board_type) + { + case "UNIF_UNL-SMB2J": + break; + default: + return false; + } + + prg_count = Cart.prg_size/4; + + Cart.wram_size = 0; + // not sure on initial mirroring + SetMirrorType(EMirrorType.Vertical); + return true; + } + + public override void WriteEXP(int addr, byte value) + { + addr += 0x4000; + + switch (addr) + { + case 0x4022: + if (ROM.Length > 0x10000) { prg = (value & 0x01) << 2; } + break; + + case 0x4122: + irqenable = (value & 3) > 0; + IRQSignal = false; + irqcnt = 0; + break; + } + } + + public override byte ReadEXP(int addr) + { + if (addr > 0x1000) + { + return ROM[(addr - 0x1000) + (prg_count - 3) * 0x1000]; + } + else return base.ReadEXP(addr); + } + + public override byte ReadWRAM(int addr) + { + return ROM[addr + (prg_count - 2) * 0x1000]; + } + + public override byte ReadPRG(int addr) + { + return ROM[(addr + prg * 01000)]; + } + + public override void ClockCPU() + { + if (irqenable) + { + irqcnt++; + + if (irqcnt >= 4096) + { + irqenable = false; + IRQSignal = true; + } + } + } + + public override void SyncState(Serializer ser) + { + base.SyncState(ser); + ser.Sync("prg", ref prg); + ser.Sync("irqenable", ref irqenable); + ser.Sync("irqcnt", ref irqcnt); + ser.Sync("swap", ref swap); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/FDS.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/FDS.cs index e649c730e5..c32715179c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/FDS.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/FDS.cs @@ -69,6 +69,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES for (int i = 0; i < NumSides; i++) ser.Sync("diskdiffs" + i, ref diskdiffs[i], true); ser.Sync("_timerirq", ref _timerirq); + ser.Sync("timer_irq_active", ref timer_irq_active); + ser.Sync("timerirq_cd", ref timerirq_cd); ser.Sync("_diskirq", ref _diskirq); ser.Sync("diskenable", ref diskenable); ser.Sync("soundenable", ref soundenable); @@ -237,7 +239,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } bool diskirq { get { return _diskirq; } set { _diskirq = value; SetIRQ(); } } bool timerirq { get { return _timerirq; } set { _timerirq = value; SetIRQ(); } } - + int timerirq_cd; + bool timer_irq_active; public override void WriteEXP(int addr, byte value) { @@ -270,14 +273,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } else { - _timerirq = false; + timerirq = false; + timer_irq_active = false; } } break; case 0x0023: diskenable = (value & 1) != 0; - if (!diskenable) { _timerirq = false; } + if (!diskenable) + { + timerirq = false; + timer_irq_active = false; + } soundenable = (value & 2) != 0; break; case 0x0024: @@ -315,6 +323,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ret |= 1; ret |= (byte)tmp; timerirq = false; + timer_irq_active = false; } break; case 0x0031: @@ -360,16 +369,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { timervalue--; } - if (timervalue == 0) + else { timervalue = timerlatch; - timerirq = true; + //timerirq = true; if ((timerreg & 1) == 0) { timerreg -= 2; } + + if (!timer_irq_active) + { + timer_irq_active = true; + timerirq_cd = 3; + } + } } + + if (timerirq_cd > 0) + { + timerirq_cd--; + } + + if ((timerirq_cd == 0) && (timer_irq_active)) + { + timerirq = true; + } + audio.Clock(); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/RamAdapter.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/RamAdapter.cs index 9c86f953e7..540e471764 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/RamAdapter.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/FDS/RamAdapter.cs @@ -214,7 +214,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES disk = null; state = RamAdapterState.IDLE; SetCycles(); - Console.WriteLine("FDS: Disk ejected"); + //Console.WriteLine("FDS: Disk ejected"); DriveLightCallback?.Invoke(false); } @@ -234,7 +234,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES this.writeprotect = writeprotect; state = RamAdapterState.INSERTING; SetCycles(); - Console.WriteLine("FDS: Disk Inserted"); + //Console.WriteLine("FDS: Disk Inserted"); originaldisk = (byte[])disk.Clone(); DriveLightCallback?.Invoke(false); } @@ -388,7 +388,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } } if ((value & 2) != 0) + { transferreset = true; + } + else + { + transferreset = false; + } + if ((cached4025 & 0x40) == 0 && (value & 0x40) != 0) { lookingforendofgap = true; @@ -469,6 +476,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (disk != null && state != RamAdapterState.INSERTING && !writeprotect) ret &= unchecked((byte)~0x04); + //Console.Write(state); + return ret; } @@ -491,9 +500,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES Write(); if (diskpos >= disksize) { - // Console.WriteLine("FDS: End of Disk"); + //Console.WriteLine("FDS: End of Disk"); state = RamAdapterState.RESET; - transferreset = false; + //transferreset = false; //numcrc = 0; DriveLightCallback?.Invoke(false); } @@ -505,9 +514,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES state = RamAdapterState.IDLE; diskpos = 0; SetCycles(); - transferreset = false; + //transferreset = false; //numcrc = 0; - // Console.WriteLine("FDS: Return or Insert Complete"); + //Console.WriteLine("FDS: Return or Insert Complete"); DriveLightCallback?.Invoke(false); break; case RamAdapterState.SPINUP: diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index 667f92a230..7828f82e1d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES EDetectionOrigin origin = EDetectionOrigin.None; int sprdma_countdown; - bool _irq_apu; //various irq signals that get merged to the cpu irq pin + public bool _irq_apu; //various irq signals that get merged to the cpu irq pin /// clock speed of the main cpu in hz public int cpuclockrate { get; private set; } @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // cheat addr index tracker // disables all cheats each frame - public int[] cheat_indexes = new int[500]; + public int[] cheat_indexes = new int[0x10000]; public int num_cheats; // new input system @@ -288,6 +288,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ram[0xEC] = 0; ram[0xED] = 0; } + + if (cart.DB_GameInfo.Hash == "00C50062A2DECE99580063777590F26A253AAB6B") // Silva Saga + { + for (int i = 0; i < Board.WRAM.Length; i++) + { + Board.WRAM[i] = 0xFF; + } + + } + + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 768c95f13b..63e65192ff 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -745,7 +745,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // now that we have a sprite, we can fill in the next scnaline's sprite pixels with it // this saves quite a bit of processing compared to checking each pixel - if (s < soam_index_prev) + if (s < soam_index_prev && (ppur.status.sl != 0) && (ppur.status.sl != 240)) { int temp_x = t_oam[s].oam_x; for (int i = 0; (temp_x + i) < 256 && i < 8; i++) @@ -845,18 +845,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } int temp_x = t_oam[s].oam_x; - for (int i = 0; (temp_x + i) < 256 && i < 8; i++) + if ((ppur.status.sl != 0) && (ppur.status.sl != 240)) { - if (sl_sprites[256 + temp_x + i] == 0) + for (int i = 0; (temp_x + i) < 256 && i < 8; i++) { - if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i)) + if (sl_sprites[256 + temp_x + i] == 0) { - int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0; - spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0); + if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i)) + { + int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0; + spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0); - sl_sprites[temp_x + i] = (byte)s; - sl_sprites[256 + temp_x + i] = (byte)spixel; - sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr; + sl_sprites[temp_x + i] = (byte)s; + sl_sprites[256 + temp_x + i] = (byte)spixel; + sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr; + } } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index c0fb578f40..899ae0254f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -23,6 +23,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { Resolver = new DynamicLibraryImportResolver(LibQuickNES.dllname); QN = BizInvoker.GetInvoker(Resolver, CallingConventionAdapters.Native); + QN.qn_setup_mappers(); } [CoreConstructor("NES")] diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.ISettable.cs index 00f27089c4..122935eb76 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.ISettable.cs @@ -31,6 +31,9 @@ namespace BizHawk.Emulation.Cores.PCEngine } Settings = o; + + CoreComm.ScreenLogicalOffsetY = Settings.Top_Line; + return ret; } @@ -41,7 +44,7 @@ namespace BizHawk.Emulation.Cores.PCEngine return ret; } - internal PCESettings Settings; + public PCESettings Settings; private PCESyncSettings _syncSettings; public class PCESettings @@ -51,6 +54,10 @@ namespace BizHawk.Emulation.Cores.PCEngine public bool ShowBG2 = true; public bool ShowOBJ2 = true; + // cropping settings + public int Top_Line = 18; + public int Bottom_Line = 252; + // these three require core reboot to use public bool SpriteLimit = false; public bool EqualizeVolume = false; diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/ScsiCDBus.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/ScsiCDBus.cs index afa8a8a790..1a8472594f 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/ScsiCDBus.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/ScsiCDBus.cs @@ -409,7 +409,12 @@ namespace BizHawk.Emulation.Cores.PCEngine if (CommandBuffer[4] == 0) SectorsLeftToRead = 256; - DataReadWaitTimer = pce.Cpu.TotalExecutedCycles + 5000; // figure out proper read delay later + // figure out proper read delay later + // 10000 fixes Mugen Senshi Valis, which runs code in a timed loop, expecting a certain number of VBlanks + // to happen before reading is complete + // 175000 fixes 4 in 1 CD, loading Gate of Thunder + // which expects a certain number of timer interrupts to happen before loading is complete + DataReadWaitTimer = pce.Cpu.TotalExecutedCycles + 175000; pce.CDAudio.Stop(); } diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.Render.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.Render.cs index 43164ab43c..4122f23bc8 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.Render.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.Render.cs @@ -22,6 +22,8 @@ namespace BizHawk.Emulation.Cores.PCEngine public int BackgroundY; public int RCRCounter; public int ActiveLine; + public bool latch_bgy; + public int ActiveDisplayStartLine; public int HBlankCycles = 79; public bool PerformSpriteLimit; @@ -31,12 +33,31 @@ namespace BizHawk.Emulation.Cores.PCEngine public void ExecFrame(bool render) { - if (MultiResHack > 0 && render) - Array.Clear(FrameBuffer, 0, FrameBuffer.Length); + Array.Clear(FrameBuffer, 0, FrameBuffer.Length); + + ActiveDisplayStartLine = DisplayStartLine; + + /* + Console.Write("VDS: "); + Console.Write((Registers[VPR] >> 8)); + Console.Write(" VSW: "); + Console.Write((Registers[VPR] & 0xFF)); + Console.Write(" VDR: "); + Console.Write((Registers[VDW] & 0xFF)); + Console.Write(" VCR: "); + Console.Write((Registers[VCR] & 0xFF)); + Console.Write(" HDS: "); + Console.Write((Registers[HSR] >> 8)); + Console.Write(" HSW: "); + Console.Write((Registers[HSR] & 0xFF)); + Console.Write(" HDE: "); + Console.Write((Registers[HDR] >> 8)); + Console.Write(" HDW: "); + Console.WriteLine((Registers[HDR] & 0xFF)); + */ while (true) { - int ActiveDisplayStartLine = DisplayStartLine; int VBlankLine = ActiveDisplayStartLine + Registers[VDW] + 1; if (VBlankLine > 261) VBlankLine = 261; @@ -58,7 +79,7 @@ namespace BizHawk.Emulation.Cores.PCEngine } } - cpu.Execute(HBlankCycles); + cpu.Execute(24); if (InActiveDisplay) { @@ -66,10 +87,21 @@ namespace BizHawk.Emulation.Cores.PCEngine BackgroundY = Registers[BYR]; else { + if (latch_bgy) + { + BackgroundY = Registers[BYR]; + latch_bgy = false; + } BackgroundY++; BackgroundY &= 0x01FF; + } + } + cpu.Execute(HBlankCycles - 24); + + if (InActiveDisplay) + { if (render) RenderScanLine(); } @@ -106,7 +138,8 @@ namespace BizHawk.Emulation.Cores.PCEngine public void RenderScanLine() { - if (ActiveLine >= FrameHeight) + if ((ScanLine >= pce.Settings.Bottom_Line) || + (ScanLine < pce.Settings.Top_Line)) return; RenderBackgroundScanline(pce.Settings.ShowBG1); @@ -124,7 +157,7 @@ namespace BizHawk.Emulation.Cores.PCEngine int p = vce.Palette[256]; fixed (int* FBptr = FrameBuffer) { - int* dst = FBptr + ActiveLine * FramePitch; + int* dst = FBptr + (ScanLine - pce.Settings.Top_Line) * FramePitch; for (int i = 0; i < FrameWidth; i++) *dst++ = p; } @@ -148,7 +181,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { // pointer to the BAT and the framebuffer for this line ushort* BatRow = VRAMptr + yTile * BatWidth; - int* dst = FBptr + ActiveLine * FramePitch; + int* dst = FBptr + (ScanLine - pce.Settings.Top_Line) * FramePitch; // parameters that change per tile ushort BatEnt; @@ -202,7 +235,7 @@ namespace BizHawk.Emulation.Cores.PCEngine if (BackgroundEnabled == false) { for (int i = 0; i < FrameWidth; i++) - FrameBuffer[(ActiveLine * FramePitch) + i] = vce.Palette[256]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + i] = vce.Palette[256]; return; } @@ -226,10 +259,10 @@ namespace BizHawk.Emulation.Cores.PCEngine byte c = PatternBuffer[(tileNo * 64) + (yOfs * 8) + xOfs]; if (c == 0) - FrameBuffer[(ActiveLine * FramePitch) + x] = vce.Palette[0]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + x] = vce.Palette[0]; else { - FrameBuffer[(ActiveLine * FramePitch) + x] = show ? vce.Palette[paletteBase + c] : vce.Palette[0]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + x] = show ? vce.Palette[paletteBase + c] : vce.Palette[0]; PriorityBuffer[x] = 1; } } @@ -361,7 +394,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { InterSpritePriorityBuffer[xs] = 1; if ((priority || PriorityBuffer[xs] == 0) && show) - FrameBuffer[(ActiveLine * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; } } } @@ -378,7 +411,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { InterSpritePriorityBuffer[xs] = 1; if ((priority || PriorityBuffer[xs] == 0) && show) - FrameBuffer[(ActiveLine * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; } } @@ -399,7 +432,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { InterSpritePriorityBuffer[xs] = 1; if ((priority || PriorityBuffer[xs] == 0) && show) - FrameBuffer[(ActiveLine * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; } } if (width == 32) @@ -415,7 +448,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { InterSpritePriorityBuffer[xs] = 1; if ((priority || PriorityBuffer[xs] == 0) && show) - FrameBuffer[(ActiveLine * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; + FrameBuffer[((ScanLine - pce.Settings.Top_Line) * FramePitch) + xs] = vce.Palette[paletteBase + pixel]; } } } @@ -424,10 +457,10 @@ namespace BizHawk.Emulation.Cores.PCEngine } } - private int FramePitch = 256; - private int FrameWidth = 256; - private int FrameHeight = 240; - private int[] FrameBuffer = new int[256 * 240]; + private int FramePitch = 320; + private int FrameWidth = 320; + private int FrameHeight = 262; + private int[] FrameBuffer = new int[320 * 262]; // IVideoProvider implementation public int[] GetVideoBuffer() @@ -436,9 +469,9 @@ namespace BizHawk.Emulation.Cores.PCEngine } public int VirtualWidth => FramePitch; - public int VirtualHeight => FrameHeight; + public int VirtualHeight => BufferHeight; public int BufferWidth => FramePitch; - public int BufferHeight => FrameHeight; + public int BufferHeight => (pce.Settings.Bottom_Line - pce.Settings.Top_Line); public int BackgroundColor => vce.Palette[256]; public int VsyncNumerator diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.cs index 7217541377..e8fda4374b 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/VDC.cs @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.PCEngine public int RequestedFrameWidth => ((Registers[HDR] & 0x3F) + 1) * 8; public int RequestedFrameHeight => (Registers[VDW] & 0x1FF) + 1; - public int DisplayStartLine => (Registers[VPR] >> 8) + (Registers[VPR] & 0x1F); + public int DisplayStartLine => (Registers[VPR] >> 8) + 3 + (Registers[VPR] & 0x1F); private const int MAWR = 0; // Memory Address Write Register private const int MARR = 1; // Memory Address Read Register @@ -117,8 +117,8 @@ namespace BizHawk.Emulation.Cores.PCEngine Registers[RegisterLatch] &= 0xFF00; Registers[RegisterLatch] |= value; - if (RegisterLatch == BYR) - BackgroundY = Registers[BYR] & 0x1FF; + if (RegisterLatch == BYR) { latch_bgy = true; } + //BackgroundY = Registers[BYR] & 0x1FF; RegisterCommit(RegisterLatch, msbComplete: false); } @@ -153,23 +153,26 @@ namespace BizHawk.Emulation.Cores.PCEngine break; case BYR: Registers[BYR] &= 0x1FF; - BackgroundY = Registers[BYR]; + latch_bgy = true; + //BackgroundY = Registers[BYR]; break; case HDR: // Horizontal Display Register - update framebuffer size FrameWidth = RequestedFrameWidth; FramePitch = MultiResHack == 0 ? FrameWidth : MultiResHack; - if (FrameBuffer.Length != FramePitch * FrameHeight) - FrameBuffer = new int[FramePitch * FrameHeight]; + //if (FrameBuffer.Length != FramePitch * FrameHeight) + //FrameBuffer = new int[FramePitch * FrameHeight]; + FrameBuffer = new int[320 * 262]; break; case VDW: // Vertical Display Word? - update framebuffer size - FrameHeight = RequestedFrameHeight; + //FrameHeight = RequestedFrameHeight; FrameWidth = RequestedFrameWidth; - if (FrameHeight > 242) - FrameHeight = 242; + //if (FrameHeight > 242) + //FrameHeight = 242; if (MultiResHack != 0) FramePitch = MultiResHack; - if (FrameBuffer.Length != FramePitch * FrameHeight) - FrameBuffer = new int[FramePitch * FrameHeight]; + //if (FrameBuffer.Length != FramePitch * FrameHeight) + //FrameBuffer = new int[FramePitch * FrameHeight]; + FrameBuffer = new int[320 * 262]; break; case LENR: // Initiate DMA transfer if (!msbComplete) break; @@ -320,6 +323,7 @@ namespace BizHawk.Emulation.Cores.PCEngine ser.Sync("ScanLine", ref ScanLine); ser.Sync("BackgroundY", ref BackgroundY); + ser.Sync("latch_bgy", ref latch_bgy); ser.Sync("RCRCounter", ref RCRCounter); ser.Sync("ActiveLine", ref ActiveLine); ser.EndSection(); diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/VPC.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/VPC.cs index a24910a44e..8c68463029 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/VPC.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/VPC.cs @@ -101,6 +101,7 @@ namespace BizHawk.Emulation.Cores.PCEngine const int DCR = 15; int EffectivePriorityMode = 0; + int ScanLine; int FrameHeight; int FrameWidth; @@ -122,18 +123,21 @@ namespace BizHawk.Emulation.Cores.PCEngine EffectivePriorityMode = 0; } + Array.Clear(FrameBuffer, 0, FrameBuffer.Length); + // Latch frame dimensions and framebuffer, for purely dumb reasons FrameWidth = VDC1.BufferWidth; FrameHeight = VDC1.BufferHeight; FrameBuffer = VDC1.GetVideoBuffer(); - int ScanLine = 0; + ScanLine = 0; + int ActiveDisplayStartLine = VDC1.DisplayStartLine; + while (true) { VDC1.ScanLine = ScanLine; VDC2.ScanLine = ScanLine; - - int ActiveDisplayStartLine = VDC1.DisplayStartLine; + int VBlankLine = ActiveDisplayStartLine + VDC1.Registers[VDW] + 1; if (VBlankLine > 261) VBlankLine = 261; @@ -171,7 +175,7 @@ namespace BizHawk.Emulation.Cores.PCEngine } } - CPU.Execute(VDC1.HBlankCycles); + CPU.Execute(24); if (InActiveDisplay) { @@ -182,15 +186,33 @@ namespace BizHawk.Emulation.Cores.PCEngine } else { + if (VDC1.latch_bgy) + { + VDC1.BackgroundY = VDC2.Registers[BYR]; + VDC1.latch_bgy = false; + } + + if (VDC2.latch_bgy) + { + VDC2.BackgroundY = VDC2.Registers[BYR]; + VDC2.latch_bgy = false; + } + VDC1.BackgroundY++; VDC1.BackgroundY &= 0x01FF; VDC2.BackgroundY++; VDC2.BackgroundY &= 0x01FF; } + } + CPU.Execute(VDC1.HBlankCycles - 24); + + if (InActiveDisplay) + { if (render) RenderScanLine(); } + if (ScanLine == VBlankLine && VDC1.VBlankInterruptEnabled) VDC1.StatusByte |= VDC.StatusVerticalBlanking; @@ -238,10 +260,12 @@ namespace BizHawk.Emulation.Cores.PCEngine private void RenderScanLine() { - if (VDC1.ActiveLine >= FrameHeight) + if ((ScanLine >= PCE.Settings.Bottom_Line) || + (ScanLine < PCE.Settings.Top_Line)) + { return; - - InitializeScanLine(VDC1.ActiveLine); + } + InitializeScanLine(ScanLine); switch (EffectivePriorityMode) { @@ -264,10 +288,9 @@ namespace BizHawk.Emulation.Cores.PCEngine { // Clear priority buffer Array.Clear(PriorityBuffer, 0, FrameWidth); - // Initialize scanline to background color for (int i = 0; i < FrameWidth; i++) - FrameBuffer[(scanline * FrameWidth) + i] = VCE.Palette[256]; + FrameBuffer[((scanline) * FrameWidth) + i] = VCE.Palette[256]; } private unsafe void RenderBackgroundScanline(VDC vdc, byte priority, bool show) @@ -291,7 +314,7 @@ namespace BizHawk.Emulation.Cores.PCEngine { // pointer to the BAT and the framebuffer for this line ushort* BatRow = VRAMptr + yTile * vdc.BatWidth; - int* dst = FBptr + vdc.ActiveLine * FrameWidth; + int* dst = FBptr + (ScanLine - PCE.Settings.Top_Line) * FrameWidth; // parameters that change per tile ushort BatEnt; @@ -447,7 +470,7 @@ namespace BizHawk.Emulation.Cores.PCEngine byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { - if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; + if (show) FrameBuffer[((ScanLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } @@ -466,7 +489,7 @@ namespace BizHawk.Emulation.Cores.PCEngine byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { - if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; + if (show) FrameBuffer[((ScanLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } @@ -488,7 +511,7 @@ namespace BizHawk.Emulation.Cores.PCEngine byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { - if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; + if (show) FrameBuffer[((ScanLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } @@ -506,7 +529,7 @@ namespace BizHawk.Emulation.Cores.PCEngine byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { - if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; + if (show) FrameBuffer[((ScanLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Korea.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Korea.cs index d737844976..5a914b3d95 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Korea.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Korea.cs @@ -1,4 +1,6 @@ -namespace BizHawk.Emulation.Cores.Sega.MasterSystem +using System; + +namespace BizHawk.Emulation.Cores.Sega.MasterSystem { public partial class SMS { @@ -8,9 +10,32 @@ byte ReadMemoryKR(ushort address) { - if (address < 0x8000) return RomData[address & 0x7FFF]; - if (address < 0xC000) return RomData[(RomBank2 * BankSize) + (address & BankSizeMask)]; - return SystemRam[address & RamSizeMask]; + if (address < 0xC000) + { + if ((Port3E & 0x48) == 0x48) // cart and bios disabled, return empty bus + return 0xFF; + if (BiosMapped && BiosRom != null) + { + // korean BIOS (and a couple of rarer BIOses) use memory slot 2 mechanics as needed + if (address < 0x8000) + { + return BiosRom[address]; + } + else + { + return BiosRom[(Bios_bank * BankSize) + (address & BankSizeMask)]; + } + } + else + { + if (address < 0x8000) return RomData[address & 0x7FFF]; + else return RomData[(RomBank2 * BankSize) + (address & BankSizeMask)]; + } + } + else + { + return SystemRam[address & RamSizeMask]; + } } CDLog_MapResults MapMemoryKR(ushort address, bool write) @@ -26,6 +51,11 @@ SystemRam[address & RamSizeMask] = value; else if (address == 0xA000) RomBank2 = (byte)(value % RomBanks); + + if ((address == 0xFFFF) && BiosMapped) + { + Bios_bank = value; + } } void InitKoreaMapper() @@ -36,6 +66,7 @@ RomBank0 = 0; RomBank1 = 1; RomBank2 = 2; + Bios_bank = 2; } // ====================================================================== diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Sega.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Sega.cs index 6e13147651..e6762567b9 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Sega.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/MemoryMap.Sega.cs @@ -30,7 +30,32 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem if ((Port3E & 0x48) == 0x48) // cart and bios disabled, return empty bus ret = 0xFF; else if (BiosMapped && BiosRom != null) - ret = BiosRom[address & 0x1FFF]; + { + if (BiosRom.Length == 0x2000) + { + ret = BiosRom[address & 0x1FFF]; + } + else + { + // korean BIOS (and a couple of rarer BIOses) use memory slot 2 mechanics as needed + if (address < 0x8000) + { + return BiosRom[address]; + } + else + { + switch (SaveRamBank) + { + case 0: ret = BiosRom[(Bios_bank * BankSize) + (address & BankSizeMask)]; break; + case 1: if (SaveRAM != null) ret = SaveRAM[(address & BankSizeMask) % SaveRAM.Length]; break; + case 2: if (SaveRAM != null) ret = SaveRAM[(BankSize + (address & BankSizeMask)) % SaveRAM.Length]; break; + default: + ret = SystemRam[address & RamSizeMask]; + break; + } + } + } + } else if (address < 1024) ret = RomData[address]; else if (address < 0x4000) @@ -125,7 +150,17 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem } else if (address == 0xFFFD) RomBank0 = (byte)(value % RomBanks); else if (address == 0xFFFE) RomBank1 = (byte)(value % RomBanks); - else if (address == 0xFFFF) RomBank2 = (byte)(value % RomBanks); + else if (address == 0xFFFF) + { + if (BiosMapped) + { + Bios_bank = (byte)value; + } + else + { + RomBank2 = (byte)(value % RomBanks); + } + } return; } } @@ -139,6 +174,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem WriteMemorySega(0xFFFD, 0); WriteMemorySega(0xFFFE, 1); WriteMemorySega(0xFFFF, 2); + Bios_bank = 2; } // Mapper when loading a BIOS as a ROM (simulating no cart loaded) diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs index d8b0d15402..3a9bf83be6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs @@ -144,7 +144,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public int TotalExecutedCycles { - get { return Cpu.TotalExecutedCycles; } + get { return (int)Cpu.TotalExecutedCycles; } } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISettable.cs index d2112e2dab..4a45ac0e7a 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISettable.cs @@ -63,8 +63,8 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public bool EnableFM = true; public bool AllowOverlock = false; public bool UseBIOS = true; - public string ConsoleRegion = "Export"; - public string DisplayType = "NTSC"; + public string ConsoleRegion = "Auto"; + public string DisplayType = "Auto"; public string ControllerType = "Standard"; public SMSSyncSettings Clone() diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IStatable.cs index 87564dfe64..c01b5f50a3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IStatable.cs @@ -61,6 +61,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem ser.Sync("RomBank1", ref RomBank1); ser.Sync("RomBank2", ref RomBank2); ser.Sync("RomBank3", ref RomBank3); + ser.Sync("Bios_bank", ref Bios_bank); ser.Sync("Port01", ref Port01); ser.Sync("Port02", ref Port02); ser.Sync("Port3E", ref Port3E); @@ -72,9 +73,10 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem if (SaveRAM != null) { ser.Sync("SaveRAM", ref SaveRAM, false); - ser.Sync("SaveRamBank", ref SaveRamBank); } + ser.Sync("SaveRamBank", ref SaveRamBank); + if (ExtRam != null) { ser.Sync("ExtRAM", ref ExtRam, true); diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs index 41cf46626f..0f393dff0c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs @@ -69,6 +69,12 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem CoreComm.Notify("Region was forced to Japan for game compatibility."); } + if (game["Korea"] && RegionStr != "Korea") + { + RegionStr = "Korea"; + CoreComm.Notify("Region was forced to Korea for game compatibility."); + } + if ((game.NotInDatabase || game["FM"]) && SyncSettings.EnableFM && !IsGameGear) { HasYM2413 = true; @@ -195,6 +201,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem // ROM private byte[] RomData; private byte RomBank0, RomBank1, RomBank2, RomBank3; + private byte Bios_bank; private byte RomBanks; private byte[] BiosRom; @@ -241,6 +248,8 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem return "Export"; if (gameRegion.IndexOf("Australia") >= 0) return "Export"; + if (gameRegion.IndexOf("Korea") >= 0) + return "Korea"; return "Japan"; } @@ -382,6 +391,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem } } - private readonly string[] validRegions = { "Export", "Japan", "Auto" }; + private readonly string[] validRegions = { "Export", "Japan", "Korea" , "Auto" }; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs index b619d2c85c..ba82d0ff1a 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs @@ -52,7 +52,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx get { throw new NotImplementedException(); } } - private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem(new[] { "M68K BUS" }); + private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem(new[] { "M68K BUS", "" }); private LibGPGX.mem_cb ExecCallback; private LibGPGX.mem_cb ReadCallback; diff --git a/BizHawk.Emulation.Cores/FileID.cs b/BizHawk.Emulation.Cores/FileID.cs index edc5252484..1314be8b6f 100644 --- a/BizHawk.Emulation.Cores/FileID.cs +++ b/BizHawk.Emulation.Cores/FileID.cs @@ -42,6 +42,7 @@ namespace BizHawk.Emulation.Cores WS, WSC, NGC, C64, + ZXSpectrum, INT, A26, A52, A78, LNX, diff --git a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs index 5a3bbcce9a..73259a9985 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs +++ b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs @@ -89,5 +89,85 @@ namespace BizHawk.Emulation.Cores.Properties { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM0_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM0_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM1_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM1_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM2_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM2_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM3_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM3_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_128_ROM { + get { + object obj = ResourceManager.GetObject("ZX_128_ROM", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_48_ROM { + get { + object obj = ResourceManager.GetObject("ZX_48_ROM", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_plus2_rom { + get { + object obj = ResourceManager.GetObject("ZX_plus2_rom", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_plus2a_rom { + get { + object obj = ResourceManager.GetObject("ZX_plus2a_rom", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/BizHawk.Emulation.Cores/Properties/Resources.resx b/BizHawk.Emulation.Cores/Properties/Resources.resx index bdb07b9a9f..b5e12deeae 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.resx +++ b/BizHawk.Emulation.Cores/Properties/Resources.resx @@ -127,4 +127,28 @@ ..\Resources\sgb-cart-present.spc.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\resources\spectrum3_v4-0_rom0.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom1.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom2.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom3.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\128.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\48.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\plus2a.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\plus2.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Resources/128.ROM.gz b/BizHawk.Emulation.Cores/Resources/128.ROM.gz new file mode 100644 index 0000000000..697dc6e872 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/128.ROM.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/48.ROM.gz b/BizHawk.Emulation.Cores/Resources/48.ROM.gz new file mode 100644 index 0000000000..76b8c9d536 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/48.ROM.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz new file mode 100644 index 0000000000..d70d805d62 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM1.bin.gz b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM1.bin.gz new file mode 100644 index 0000000000..f813586462 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM1.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM2.bin.gz b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM2.bin.gz new file mode 100644 index 0000000000..df586562a5 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM2.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM3.bin.gz b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM3.bin.gz new file mode 100644 index 0000000000..3062bc7fb1 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM3.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/plus2.rom.gz b/BizHawk.Emulation.Cores/Resources/plus2.rom.gz new file mode 100644 index 0000000000..274507f352 Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/plus2.rom.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/plus2a.rom.gz b/BizHawk.Emulation.Cores/Resources/plus2a.rom.gz new file mode 100644 index 0000000000..418ea1f1cf Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/plus2a.rom.gz differ diff --git a/BizHawk.Emulation.Cores/Sound/SN76489.cs b/BizHawk.Emulation.Cores/Sound/SN76489.cs index 1944edf267..c5e7e113c5 100644 --- a/BizHawk.Emulation.Cores/Sound/SN76489.cs +++ b/BizHawk.Emulation.Cores/Sound/SN76489.cs @@ -21,15 +21,15 @@ namespace BizHawk.Emulation.Cores.Components const int SampleRate = 44100; private static readonly byte[] LogScale = { 0, 10, 13, 16, 20, 26, 32, 40, 51, 64, 81, 102, 128, 161, 203, 255 }; - public void Mix(short[] samples, int start, int len, int maxVolume) + public void Mix(short[] samples, long start, long len, int maxVolume) { if (Volume == 0) return; float adjustedWaveLengthInSamples = SampleRate / (Noise ? (Frequency / (float)Wave.Length) : Frequency); float moveThroughWaveRate = Wave.Length / adjustedWaveLengthInSamples; - int end = start + len; - for (int i = start; i < end; ) + long end = start + len; + for (long i = start; i < end; ) { short value = Wave[(int)WaveOffset]; @@ -46,7 +46,7 @@ namespace BizHawk.Emulation.Cores.Components public byte PsgLatch; private readonly Queue commands = new Queue(256); - int frameStartTime, frameStopTime; + long frameStartTime, frameStopTime; const int PsgBase = 111861; @@ -84,7 +84,7 @@ namespace BizHawk.Emulation.Cores.Components } } - public void BeginFrame(int cycles) + public void BeginFrame(long cycles) { while (commands.Count > 0) { @@ -94,12 +94,12 @@ namespace BizHawk.Emulation.Cores.Components frameStartTime = cycles; } - public void EndFrame(int cycles) + public void EndFrame(long cycles) { frameStopTime = cycles; } - public void WritePsgData(byte value, int cycles) + public void WritePsgData(byte value, long cycles) { commands.Enqueue(new QueuedCommand { Value = value, Time = cycles - frameStartTime }); } @@ -227,15 +227,15 @@ namespace BizHawk.Emulation.Cores.Components public void DiscardSamples() { commands.Clear(); } public void GetSamples(short[] samples) { - int elapsedCycles = frameStopTime - frameStartTime; + long elapsedCycles = frameStopTime - frameStartTime; if (elapsedCycles == 0) elapsedCycles = 1; // hey it's better than diving by zero - int start = 0; + long start = 0; while (commands.Count > 0) { var cmd = commands.Dequeue(); - int pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1; + long pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1; GetSamplesImmediate(samples, start, pos - start); start = pos; WritePsgDataImmediate(cmd.Value); @@ -243,16 +243,16 @@ namespace BizHawk.Emulation.Cores.Components GetSamplesImmediate(samples, start, samples.Length - start); } - public void GetSamplesImmediate(short[] samples, int start, int len) + public void GetSamplesImmediate(short[] samples, long start, long len) { - for (int i = 0; i < 4; i++) + for (long i = 0; i < 4; i++) Channels[i].Mix(samples, start, len, MaxVolume); } class QueuedCommand { public byte Value; - public int Time; + public long Time; } } } \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj b/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj index 9ad6e73985..deb41e4f0d 100644 --- a/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj +++ b/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj @@ -8,7 +8,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -20,7 +21,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -132,4 +134,4 @@ --> - \ No newline at end of file + diff --git a/BizHawk.sln.DotSettings b/BizHawk.sln.DotSettings index e447905302..c6e4f57315 100644 --- a/BizHawk.sln.DotSettings +++ b/BizHawk.sln.DotSettings @@ -2,6 +2,7 @@ False True ExplicitlyExcluded + DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -96,5 +97,6 @@ UI VBA ROM - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> \ No newline at end of file diff --git a/Bizware/BizHawk.Bizware.BizwareGL.GdiPlus/BizHawk.Bizware.BizwareGL.GdiPlus.csproj b/Bizware/BizHawk.Bizware.BizwareGL.GdiPlus/BizHawk.Bizware.BizwareGL.GdiPlus.csproj index 262f6002ae..d672923fe1 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL.GdiPlus/BizHawk.Bizware.BizwareGL.GdiPlus.csproj +++ b/Bizware/BizHawk.Bizware.BizwareGL.GdiPlus/BizHawk.Bizware.BizwareGL.GdiPlus.csproj @@ -22,7 +22,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\..\output\dll\ @@ -32,7 +33,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset @@ -74,4 +76,4 @@ --> - \ No newline at end of file + diff --git a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/BizHawk.Bizware.BizwareGL.OpenTK.csproj b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/BizHawk.Bizware.BizwareGL.OpenTK.csproj index bf02058e67..2f55beb615 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/BizHawk.Bizware.BizwareGL.OpenTK.csproj +++ b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/BizHawk.Bizware.BizwareGL.OpenTK.csproj @@ -22,7 +22,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -34,7 +35,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -81,4 +83,4 @@ --> - \ No newline at end of file + diff --git a/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/BizHawk.Bizware.BizwareGL.SlimDX.csproj b/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/BizHawk.Bizware.BizwareGL.SlimDX.csproj index 372464897a..cf7d477acb 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/BizHawk.Bizware.BizwareGL.SlimDX.csproj +++ b/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/BizHawk.Bizware.BizwareGL.SlimDX.csproj @@ -22,7 +22,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset ..\..\output\dll\ @@ -32,7 +33,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset @@ -77,4 +79,4 @@ --> - \ No newline at end of file + diff --git a/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj b/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj index 77024c16b4..6e52535337 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj +++ b/Bizware/BizHawk.Bizware.BizwareGL/BizHawk.Bizware.BizwareGL.csproj @@ -22,7 +22,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -34,7 +35,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -107,4 +109,4 @@ --> - \ No newline at end of file + diff --git a/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs b/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs index 94a879483d..11e4d360b2 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs +++ b/Bizware/BizHawk.Bizware.BizwareGL/StringRenderer.cs @@ -32,34 +32,66 @@ namespace BizHawk.Bizware.BizwareGL public sd.SizeF Measure(string str) { float x = 0; + float y = FontInfo.LineHeight; + float ox = x; int len = str.Length; + for (int i = 0; i < len; i++) { - Cyotek.Drawing.BitmapFont.Character c; - if (!FontInfo.Characters.TryGetValue(str[i], out c)) - c = FontInfo.Characters[unchecked((char)-1)]; + int c = str[i]; - x += c.XAdvance; + if (c == '\r') + { + if (i != len - 1 && str[i + 1] == '\n') + i++; + } + + if (c == '\r') + { + c = '\n'; + } + + if (c == '\n') + { + if (x > ox) + ox = x; + x = 0; + y += FontInfo.LineHeight; + continue; + } + + Cyotek.Drawing.BitmapFont.Character bfc; + if (!FontInfo.Characters.TryGetValue((char)c, out bfc)) + bfc = FontInfo.Characters[unchecked((char)-1)]; + + x += bfc.XAdvance; } - return new sd.SizeF(x, FontInfo.LineHeight); + return new sd.SizeF(Math.Max(x, ox), y); } public void RenderString(IGuiRenderer renderer, float x, float y, string str) { float ox = x; int len = str.Length; + for (int i = 0; i < len; i++) { int c = str[i]; + if (c == '\r') { if (i != len - 1 && str[i + 1] == '\n') i++; } - if (c == '\r') c = '\n'; - if(c == '\n') { + if (c == '\r') + { + c = '\n'; + } + + if(c == '\n') + { x = ox; y += FontInfo.LineHeight; continue; diff --git a/Build/BizHawk.Build.Tool/Tool.cs b/Build/BizHawk.Build.Tool/Tool.cs index ad1016f38f..514991000b 100644 --- a/Build/BizHawk.Build.Tool/Tool.cs +++ b/Build/BizHawk.Build.Tool/Tool.cs @@ -18,6 +18,7 @@ namespace BizHawk.Build.Tool case "GIT_REV": SVN_REV(false,cmdArgs); break; case "NXCOMPAT": NXCOMPAT(cmdArgs); break; case "LARGEADDRESS": LARGEADDRESS(cmdArgs); break; + case "TIMESTAMP": TIMESTAMP(cmdArgs); break; } } @@ -113,6 +114,15 @@ namespace BizHawk.Build.Tool } } + //clears the timestamp in PE header (for deterministic builds) + static void TIMESTAMP(string[] args) + { + using (var fs = new FileStream(args[0], FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) + { + fs.Position = 0x88; + fs.WriteByte(0); fs.WriteByte(0); fs.WriteByte(0); fs.WriteByte(0); + } + } //sets NXCOMPAT bit in PE header static void NXCOMPAT(string[] args) diff --git a/LuaInterface/Libs/luaperks.7z b/LuaInterface/Libs/luaperks.7z index 270c8cc6df..99067f7006 100644 Binary files a/LuaInterface/Libs/luaperks.7z and b/LuaInterface/Libs/luaperks.7z differ diff --git a/LuaInterface/LuaInterface/LuaInterface.csproj b/LuaInterface/LuaInterface/LuaInterface.csproj index 3885a26f3f..42d4aa1b1c 100644 --- a/LuaInterface/LuaInterface/LuaInterface.csproj +++ b/LuaInterface/LuaInterface/LuaInterface.csproj @@ -162,7 +162,7 @@ false - ..\..\references\x64\ + ..\..\output\dll\ TRACE true pdbonly diff --git a/References/x64/SlimDX.dll b/References/x64/SlimDX.dll index d86ff79dd7..d2818f8ca4 100644 Binary files a/References/x64/SlimDX.dll and b/References/x64/SlimDX.dll differ diff --git a/Version/version.csproj b/Version/Version.csproj similarity index 83% rename from Version/version.csproj rename to Version/Version.csproj index be9d2d7cf2..85cf67cfdb 100644 --- a/Version/version.csproj +++ b/Version/Version.csproj @@ -23,7 +23,8 @@ full AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false @@ -33,7 +34,8 @@ pdbonly AnyCPU prompt - MinimumRecommendedRules.ruleset + + MinimumRecommendedRules.ruleset false false @@ -51,4 +53,4 @@ --> - \ No newline at end of file + diff --git a/libgambatte/include/gambatte.h b/libgambatte/include/gambatte.h index 5f04191fe6..2b953984a9 100644 --- a/libgambatte/include/gambatte.h +++ b/libgambatte/include/gambatte.h @@ -51,7 +51,7 @@ public: enum LoadFlag { FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */ GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */ - MULTICART_COMPAT = 4 /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */ + MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */ }; /** Load ROM image. @@ -60,8 +60,11 @@ public: * @param flags ORed combination of LoadFlags. * @return 0 on success, negative value on failure. */ - int load(const char *romfiledata, unsigned romfilelength, std::uint32_t now, unsigned flags = 0); + int load(const char *romfiledata, unsigned romfilelength, std::uint32_t now, unsigned flags, unsigned div); + int loadGBCBios(const char* biosfiledata); + int loadDMGBios(const char* biosfiledata); + /** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer, * or until a video frame has been drawn. * @@ -90,7 +93,7 @@ public: /** Reset to initial state. * Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again. */ - void reset(std::uint32_t now); + void reset(std::uint32_t now, unsigned div); /** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and SP2_PALETTE. * @param colorNum 0 <= colorNum < 4 @@ -109,7 +112,7 @@ public: void setTraceCallback(void (*callback)(void *)); void setScanlineCallback(void (*callback)(), int sl); void setRTCCallback(std::uint32_t (*callback)()); - void setLinkCallback(void (*callback)()); + void setLinkCallback(void(*callback)()); /** Returns true if the currently loaded ROM image is treated as having CGB support. */ bool isCgb() const; @@ -135,6 +138,9 @@ public: void GetRegs(int *dest); + void SetInterruptAddresses(int *addrs, int numAddrs); + int GetHitInterruptAddress(); + templatevoid SyncState(NewState *ns); private: diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index 4d34da2828..0af47e31bb 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -1,231 +1,222 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {5D630682-7BDA-474D-B387-0EB420DDC199} - Win32Proj - libgambatte - - - - DynamicLibrary - true - Unicode - v140 - - - DynamicLibrary - true - Unicode - v140 - - - DynamicLibrary - false - true - Unicode - v140 - - - DynamicLibrary - false - true - Unicode - v140 - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - true - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - true - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {5D630682-7BDA-474D-B387-0EB420DDC199} + Win32Proj + libgambatte + 8.1 + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libgambatte/src/cinterface.cpp b/libgambatte/src/cinterface.cpp index abb750b12b..fc4d793a75 100644 --- a/libgambatte/src/cinterface.cpp +++ b/libgambatte/src/cinterface.cpp @@ -1,203 +1,225 @@ -#include "cinterface.h" -#include "gambatte.h" -#include -#include -#include "newstate.h" - -using namespace gambatte; - -// new is actually called in a few different places, so replace all of them for determinism guarantees -void *operator new(std::size_t n) -{ - void *p = std::malloc(n); - std::memset(p, 0, n); - return p; -} - -void operator delete(void *p) -{ - std::free(p); -} - -GBEXPORT GB *gambatte_create() -{ - return new GB(); -} - -GBEXPORT void gambatte_destroy(GB *g) -{ - delete g; -} - -GBEXPORT int gambatte_load(GB *g, const char *romfiledata, unsigned romfilelength, long long now, unsigned flags) -{ - int ret = g->load(romfiledata, romfilelength, now, flags); - return ret; -} - -GBEXPORT int gambatte_runfor(GB *g, short *soundbuf, unsigned *samples) -{ - unsigned sampv = *samples; - int ret = g->runFor((unsigned int *) soundbuf, sampv); - *samples = sampv; - return ret; -} - -GBEXPORT void gambatte_blitto(GB *g, unsigned int *videobuf, int pitch) -{ - g->blitTo((unsigned int *)videobuf, pitch); -} - -GBEXPORT void gambatte_setlayers(GB *g, unsigned mask) -{ - g->setLayers(mask); -} - -GBEXPORT void gambatte_reset(GB *g, long long now) -{ - g->reset(now); -} - -GBEXPORT void gambatte_setdmgpalettecolor(GB *g, unsigned palnum, unsigned colornum, unsigned rgb32) -{ - g->setDmgPaletteColor(palnum, colornum, rgb32); -} - -GBEXPORT void gambatte_setcgbpalette(GB *g, unsigned *lut) -{ - g->setCgbPalette(lut); -} - -GBEXPORT void gambatte_setinputgetter(GB *g, unsigned (*getinput)(void)) -{ - g->setInputGetter(getinput); -} - -GBEXPORT void gambatte_setreadcallback(GB *g, void (*callback)(unsigned)) -{ - g->setReadCallback(callback); -} - -GBEXPORT void gambatte_setwritecallback(GB *g, void (*callback)(unsigned)) -{ - g->setWriteCallback(callback); -} - -GBEXPORT void gambatte_setexeccallback(GB *g, void (*callback)(unsigned)) -{ - g->setExecCallback(callback); -} - -GBEXPORT void gambatte_setcdcallback(GB *g, CDCallback cdc) -{ - g->setCDCallback(cdc); -} - - -GBEXPORT void gambatte_settracecallback(GB *g, void (*callback)(void *)) -{ - g->setTraceCallback(callback); -} - -GBEXPORT void gambatte_setscanlinecallback(GB *g, void (*callback)(), int sl) -{ - g->setScanlineCallback(callback, sl); -} - -GBEXPORT void gambatte_setrtccallback(GB *g, unsigned int (*callback)()) -{ - g->setRTCCallback(callback); -} - -GBEXPORT void gambatte_setlinkcallback(GB *g, void (*callback)()) -{ - g->setLinkCallback(callback); -} - -GBEXPORT int gambatte_iscgb(GB *g) -{ - return g->isCgb(); -} - -GBEXPORT int gambatte_isloaded(GB *g) -{ - return g->isLoaded(); -} - -GBEXPORT void gambatte_savesavedata(GB *g, char *dest) -{ - g->saveSavedata(dest); -} - -GBEXPORT void gambatte_loadsavedata(GB *g, const char *data) -{ - g->loadSavedata(data); -} - -GBEXPORT int gambatte_savesavedatalength(GB *g) -{ - return g->saveSavedataLength(); -} - -GBEXPORT int gambatte_newstatelen(GB *g) -{ - NewStateDummy dummy; - g->SyncState(&dummy); - return dummy.GetLength(); -} - -GBEXPORT int gambatte_newstatesave(GB *g, char *data, int len) -{ - NewStateExternalBuffer saver(data, len); - g->SyncState(&saver); - return !saver.Overflow() && saver.GetLength() == len; -} - -GBEXPORT int gambatte_newstateload(GB *g, const char *data, int len) -{ - NewStateExternalBuffer loader((char *)data, len); - g->SyncState(&loader); - return !loader.Overflow() && loader.GetLength() == len; -} - -GBEXPORT void gambatte_newstatesave_ex(GB *g, FPtrs *ff) -{ - NewStateExternalFunctions saver(ff); - g->SyncState(&saver); -} - -GBEXPORT void gambatte_newstateload_ex(GB *g, FPtrs *ff) -{ - NewStateExternalFunctions loader(ff); - g->SyncState(&loader); -} - -GBEXPORT void gambatte_romtitle(GB *g, char *dest) -{ - std::strcpy(dest, g->romTitle().c_str()); -} - -GBEXPORT int gambatte_getmemoryarea(GB *g, int which, unsigned char **data, int *length) -{ - return g->getMemoryArea(which, data, length); -} - -GBEXPORT unsigned char gambatte_cpuread(GB *g, unsigned short addr) -{ - return g->ExternalRead(addr); -} - -GBEXPORT void gambatte_cpuwrite(GB *g, unsigned short addr, unsigned char val) -{ - g->ExternalWrite(addr, val); -} - -GBEXPORT int gambatte_linkstatus(GB *g, int which) -{ - return g->LinkStatus(which); -} - -GBEXPORT void gambatte_getregs(GB *g, int *dest) -{ - g->GetRegs(dest); -} +#include "cinterface.h" +#include "gambatte.h" +#include +#include +#include "newstate.h" + +using namespace gambatte; + +// new is actually called in a few different places, so replace all of them for determinism guarantees +void *operator new(std::size_t n) +{ + void *p = std::malloc(n); + std::memset(p, 0, n); + return p; +} + +void operator delete(void *p) +{ + std::free(p); +} + +GBEXPORT GB *gambatte_create() +{ + return new GB(); +} + +GBEXPORT void gambatte_destroy(GB *g) +{ + delete g; +} + +GBEXPORT int gambatte_load(GB *g, const char *romfiledata, unsigned romfilelength, long long now, unsigned flags, unsigned div) +{ + int ret = g->load(romfiledata, romfilelength, now, flags, div); + return ret; +} + +GBEXPORT int gambatte_loadgbcbios(GB* g, const char* biosfiledata) +{ + int ret = g->loadGBCBios(biosfiledata); + return ret; +} + +GBEXPORT int gambatte_loaddmgbios(GB* g, const char* biosfiledata) +{ + int ret = g->loadDMGBios(biosfiledata); + return ret; +} + +GBEXPORT int gambatte_runfor(GB *g, short *soundbuf, unsigned *samples) +{ + unsigned sampv = *samples; + int ret = g->runFor((unsigned int *) soundbuf, sampv); + *samples = sampv; + return ret; +} + +GBEXPORT void gambatte_blitto(GB *g, unsigned int *videobuf, int pitch) +{ + g->blitTo((unsigned int *)videobuf, pitch); +} + +GBEXPORT void gambatte_setlayers(GB *g, unsigned mask) +{ + g->setLayers(mask); +} + +GBEXPORT void gambatte_reset(GB *g, long long now, unsigned div) +{ + g->reset(now, div); +} + +GBEXPORT void gambatte_setdmgpalettecolor(GB *g, unsigned palnum, unsigned colornum, unsigned rgb32) +{ + g->setDmgPaletteColor(palnum, colornum, rgb32); +} + +GBEXPORT void gambatte_setcgbpalette(GB *g, unsigned *lut) +{ + g->setCgbPalette(lut); +} + +GBEXPORT void gambatte_setinputgetter(GB *g, unsigned (*getinput)(void)) +{ + g->setInputGetter(getinput); +} + +GBEXPORT void gambatte_setreadcallback(GB *g, void (*callback)(unsigned)) +{ + g->setReadCallback(callback); +} + +GBEXPORT void gambatte_setwritecallback(GB *g, void (*callback)(unsigned)) +{ + g->setWriteCallback(callback); +} + +GBEXPORT void gambatte_setexeccallback(GB *g, void (*callback)(unsigned)) +{ + g->setExecCallback(callback); +} + +GBEXPORT void gambatte_setcdcallback(GB *g, CDCallback cdc) +{ + g->setCDCallback(cdc); +} + + +GBEXPORT void gambatte_settracecallback(GB *g, void (*callback)(void *)) +{ + g->setTraceCallback(callback); +} + +GBEXPORT void gambatte_setscanlinecallback(GB *g, void (*callback)(), int sl) +{ + g->setScanlineCallback(callback, sl); +} + +GBEXPORT void gambatte_setrtccallback(GB *g, unsigned int (*callback)()) +{ + g->setRTCCallback(callback); +} + +GBEXPORT void gambatte_setlinkcallback(GB *g, void(*callback)()) +{ + g->setLinkCallback(callback); +} + +GBEXPORT int gambatte_iscgb(GB *g) +{ + return g->isCgb(); +} + +GBEXPORT int gambatte_isloaded(GB *g) +{ + return g->isLoaded(); +} + +GBEXPORT void gambatte_savesavedata(GB *g, char *dest) +{ + g->saveSavedata(dest); +} + +GBEXPORT void gambatte_loadsavedata(GB *g, const char *data) +{ + g->loadSavedata(data); +} + +GBEXPORT int gambatte_savesavedatalength(GB *g) +{ + return g->saveSavedataLength(); +} + +GBEXPORT int gambatte_newstatelen(GB *g) +{ + NewStateDummy dummy; + g->SyncState(&dummy); + return dummy.GetLength(); +} + +GBEXPORT int gambatte_newstatesave(GB *g, char *data, int len) +{ + NewStateExternalBuffer saver(data, len); + g->SyncState(&saver); + return !saver.Overflow() && saver.GetLength() == len; +} + +GBEXPORT int gambatte_newstateload(GB *g, const char *data, int len) +{ + NewStateExternalBuffer loader((char *)data, len); + g->SyncState(&loader); + return !loader.Overflow() && loader.GetLength() == len; +} + +GBEXPORT void gambatte_newstatesave_ex(GB *g, FPtrs *ff) +{ + NewStateExternalFunctions saver(ff); + g->SyncState(&saver); +} + +GBEXPORT void gambatte_newstateload_ex(GB *g, FPtrs *ff) +{ + NewStateExternalFunctions loader(ff); + g->SyncState(&loader); +} + +GBEXPORT void gambatte_romtitle(GB *g, char *dest) +{ + std::strcpy(dest, g->romTitle().c_str()); +} + +GBEXPORT int gambatte_getmemoryarea(GB *g, int which, unsigned char **data, int *length) +{ + return g->getMemoryArea(which, data, length); +} + +GBEXPORT unsigned char gambatte_cpuread(GB *g, unsigned short addr) +{ + return g->ExternalRead(addr); +} + +GBEXPORT void gambatte_cpuwrite(GB *g, unsigned short addr, unsigned char val) +{ + g->ExternalWrite(addr, val); +} + +GBEXPORT int gambatte_linkstatus(GB *g, int which) +{ + return g->LinkStatus(which); +} + +GBEXPORT void gambatte_getregs(GB *g, int *dest) +{ + g->GetRegs(dest); +} + +GBEXPORT void gambatte_setinterruptaddresses(GB *g, int *addrs, int numAddrs) +{ + g->SetInterruptAddresses(addrs, numAddrs); +} + +GBEXPORT int gambatte_gethitinterruptaddress(GB *g) +{ + return g->GetHitInterruptAddress(); +} diff --git a/libgambatte/src/cinterface.h b/libgambatte/src/cinterface.h index 353991ab12..5e8a0087ec 100644 --- a/libgambatte/src/cinterface.h +++ b/libgambatte/src/cinterface.h @@ -1,8 +1,8 @@ -#ifndef CINTERFACE_H -#define CINTERFACE_H - -// these are all documented on the C# side - -#define GBEXPORT extern "C" __declspec(dllexport) - -#endif +#ifndef CINTERFACE_H +#define CINTERFACE_H + +// these are all documented on the C# side + +#define GBEXPORT extern "C" __declspec(dllexport) + +#endif diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index 88665f1989..0fcebe0ca6 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -1,2893 +1,2918 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "cpu.h" -#include "memory.h" -#include "savestate.h" - -namespace gambatte { - -CPU::CPU() -: memory(Interrupter(SP, PC)), - cycleCounter_(0), - PC(0x100), - SP(0xFFFE), - HF1(0xF), - HF2(0xF), - ZF(0), - CF(0x100), - A(0x01), - B(0x00), - C(0x13), - D(0x00), - E(0xD8), - H(0x01), - L(0x4D), - skip(false), - tracecallback(0) -{ -} - -long CPU::runFor(const unsigned long cycles) { - process(cycles/* << memory.isDoubleSpeed()*/); - - const long csb = memory.cyclesSinceBlit(cycleCounter_); - - if (cycleCounter_ & 0x80000000) - cycleCounter_ = memory.resetCounters(cycleCounter_); - - return csb; -} - -// (HF2 & 0x200) == true means HF is set. -// (HF2 & 0x400) marks the subtract flag. -// (HF2 & 0x800) is set for inc/dec. -// (HF2 & 0x100) is set if there's a carry to add. -static void calcHF(const unsigned HF1, unsigned& HF2) { - unsigned arg1 = HF1 & 0xF; - unsigned arg2 = (HF2 & 0xF) + (HF2 >> 8 & 1); - - if (HF2 & 0x800) { - arg1 = arg2; - arg2 = 1; - } - - if (HF2 & 0x400) - arg1 -= arg2; - else - arg1 = (arg1 + arg2) << 5; - - HF2 |= arg1 & 0x200; -} - -#define F() (((HF2 & 0x600) | (CF & 0x100)) >> 4 | ((ZF & 0xFF) ? 0 : 0x80)) - -#define FROM_F(f_in) do { \ - unsigned from_f_var = f_in; \ -\ - ZF = ~from_f_var & 0x80; \ - HF2 = from_f_var << 4 & 0x600; \ - CF = from_f_var << 4 & 0x100; \ -} while (0) - -void CPU::setStatePtrs(SaveState &state) { - memory.setStatePtrs(state); -} - -void CPU::loadState(const SaveState &state) { - memory.loadState(state/*, cycleCounter_*/); - - cycleCounter_ = state.cpu.cycleCounter; - PC = state.cpu.PC & 0xFFFF; - SP = state.cpu.SP & 0xFFFF; - A = state.cpu.A & 0xFF; - B = state.cpu.B & 0xFF; - C = state.cpu.C & 0xFF; - D = state.cpu.D & 0xFF; - E = state.cpu.E & 0xFF; - FROM_F(state.cpu.F); - H = state.cpu.H & 0xFF; - L = state.cpu.L & 0xFF; - skip = state.cpu.skip; -} - -#define BC() ( B << 8 | C ) -#define DE() ( D << 8 | E ) -#define HL() ( H << 8 | L ) - -#define READ(dest, addr) do { (dest) = memory.read(addr, cycleCounter); cycleCounter += 4; } while (0) -// #define PC_READ(dest, addr) do { (dest) = memory.pc_read(addr, cycleCounter); cycleCounter += 4; } while (0) -#define PC_READ(dest) do { (dest) = memory.read_excb(PC, cycleCounter, false); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) -#define PC_READ_FIRST(dest) do { (dest) = memory.read_excb(PC, cycleCounter, true); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) -#define FF_READ(dest, addr) do { (dest) = memory.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) - -#define WRITE(addr, data) do { memory.write(addr, data, cycleCounter); cycleCounter += 4; } while (0) -#define FF_WRITE(addr, data) do { memory.ff_write(addr, data, cycleCounter); cycleCounter += 4; } while (0) - -#define PC_MOD(data) do { PC = data; cycleCounter += 4; } while (0) - -#define PUSH(r1, r2) do { \ - SP = (SP - 1) & 0xFFFF; \ - WRITE(SP, (r1)); \ - SP = (SP - 1) & 0xFFFF; \ - WRITE(SP, (r2)); \ -} while (0) - -//CB OPCODES (Shifts, rotates and bits): -//swap r (8 cycles): -//Swap upper and lower nibbles of 8-bit register, reset flags, check zero flag: -#define swap_r(r) do { \ - CF = HF2 = 0; \ - ZF = (r); \ - (r) = (ZF << 4 | ZF >> 4) & 0xFF; \ -} while (0) - -//rlc r (8 cycles): -//Rotate 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: -#define rlc_r(r) do { \ - CF = (r) << 1; \ - ZF = CF | CF >> 8; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rl r (8 cycles): -//Rotate 8-bit register left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF and HCF, Check ZF: -#define rl_r(r) do { \ - const unsigned rl_r_var_oldcf = CF >> 8 & 1; \ - CF = (r) << 1; \ - ZF = CF | rl_r_var_oldcf; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rrc r (8 cycles): -//Rotate 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: -#define rrc_r(r) do { \ - ZF = (r); \ - CF = ZF << 8; \ - (r) = (ZF | CF) >> 1 & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rr r (8 cycles): -//Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: -#define rr_r(r) do { \ - const unsigned rr_r_var_oldcf = CF & 0x100; \ - CF = (r) << 8; \ - (r) = ZF = ((r) | rr_r_var_oldcf) >> 1; \ - HF2 = 0; \ -} while (0) - -//sla r (8 cycles): -//Shift 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: -#define sla_r(r) do { \ - ZF = CF = (r) << 1; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//sra r (8 cycles): -//Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: -#define sra_r(r) do { \ - CF = (r) << 8; \ - ZF = (r) >> 1; \ - (r) = ZF | ((r) & 0x80); \ - HF2 = 0; \ -} while (0) - -//srl r (8 cycles): -//Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: -#define srl_r(r) do { \ - ZF = (r); \ - CF = (r) << 8; \ - ZF >>= 1; \ - (r) = ZF; \ - HF2 = 0; \ -} while (0) - -//bit n,r (8 cycles): -//bit n,(hl) (12 cycles): -//Test bitn in 8-bit value, check ZF, unset SF, set HCF: -#define bitn_u8(bitmask, u8) do { \ - ZF = (u8) & (bitmask); \ - HF2 = 0x200; \ -} while (0) - -#define bit0_u8(u8) bitn_u8(1, (u8)) -#define bit1_u8(u8) bitn_u8(2, (u8)) -#define bit2_u8(u8) bitn_u8(4, (u8)) -#define bit3_u8(u8) bitn_u8(8, (u8)) -#define bit4_u8(u8) bitn_u8(0x10, (u8)) -#define bit5_u8(u8) bitn_u8(0x20, (u8)) -#define bit6_u8(u8) bitn_u8(0x40, (u8)) -#define bit7_u8(u8) bitn_u8(0x80, (u8)) - -//set n,r (8 cycles): -//Set bitn of 8-bit register: -#define set0_r(r) ( (r) |= 0x1 ) -#define set1_r(r) ( (r) |= 0x2 ) -#define set2_r(r) ( (r) |= 0x4 ) -#define set3_r(r) ( (r) |= 0x8 ) -#define set4_r(r) ( (r) |= 0x10 ) -#define set5_r(r) ( (r) |= 0x20 ) -#define set6_r(r) ( (r) |= 0x40 ) -#define set7_r(r) ( (r) |= 0x80 ) - -//set n,(hl) (16 cycles): -//Set bitn of value at address stored in HL: -#define setn_mem_hl(n) do { \ - const unsigned setn_mem_hl_var_addr = HL(); \ - unsigned setn_mem_hl_var_tmp; \ -\ - READ(setn_mem_hl_var_tmp, setn_mem_hl_var_addr); \ - setn_mem_hl_var_tmp |= 1 << (n); \ -\ - WRITE(setn_mem_hl_var_addr, setn_mem_hl_var_tmp); \ -} while (0) - -//res n,r (8 cycles): -//Unset bitn of 8-bit register: -#define res0_r(r) ( (r) &= 0xFE ) -#define res1_r(r) ( (r) &= 0xFD ) -#define res2_r(r) ( (r) &= 0xFB ) -#define res3_r(r) ( (r) &= 0xF7 ) -#define res4_r(r) ( (r) &= 0xEF ) -#define res5_r(r) ( (r) &= 0xDF ) -#define res6_r(r) ( (r) &= 0xBF ) -#define res7_r(r) ( (r) &= 0x7F ) - -//res n,(hl) (16 cycles): -//Unset bitn of value at address stored in HL: -#define resn_mem_hl(n) do { \ - const unsigned resn_mem_hl_var_addr = HL(); \ - unsigned resn_mem_hl_var_tmp; \ -\ - READ(resn_mem_hl_var_tmp, resn_mem_hl_var_addr); \ - resn_mem_hl_var_tmp &= ~(1 << (n)); \ -\ - WRITE(resn_mem_hl_var_addr, resn_mem_hl_var_tmp); \ -} while (0) - - -//16-BIT LOADS: -//ld rr,nn (12 cycles) -//set rr to 16-bit value of next 2 bytes in memory -#define ld_rr_nn(r1, r2) do { \ - PC_READ(r2); \ - PC_READ(r1); \ -} while (0) - -//push rr (16 cycles): -//Push value of register pair onto stack: -#define push_rr(r1, r2) do { \ - PUSH(r1, r2); \ - cycleCounter += 4; \ -} while (0) - -//pop rr (12 cycles): -//Pop two bytes off stack into register pair: -#define pop_rr(r1, r2) do { \ - READ(r2, SP); \ - SP = (SP + 1) & 0xFFFF; \ - READ(r1, SP); \ - SP = (SP + 1) & 0xFFFF; \ -} while (0) - -//8-BIT ALU: -//add a,r (4 cycles): -//add a,(addr) (8 cycles): -//Add 8-bit value to A, check flags: -#define add_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A + HF2; \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//adc a,r (4 cycles): -//adc a,(addr) (8 cycles): -//Add 8-bit value+CF to A, check flags: -#define adc_a_u8(u8) do { \ - HF1 = A; \ - HF2 = (CF & 0x100) | (u8); \ - ZF = CF = (CF >> 8 & 1) + (u8) + A; \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//sub a,r (4 cycles): -//sub a,(addr) (8 cycles): -//Subtract 8-bit value from A, check flags: -#define sub_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A - HF2; \ - A = ZF & 0xFF; \ - HF2 |= 0x400; \ - calcHF(HF1, HF2); \ -} while (0) - -//sbc a,r (4 cycles): -//sbc a,(addr) (8 cycles): -//Subtract CF and 8-bit value from A, check flags: -#define sbc_a_u8(u8) do { \ - HF1 = A; \ - HF2 = 0x400 | (CF & 0x100) | (u8); \ - ZF = CF = A - ((CF >> 8) & 1) - (u8); \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//and a,r (4 cycles): -//and a,(addr) (8 cycles): -//bitwise and 8-bit value into A, check flags: -#define and_a_u8(u8) do { \ - HF2 = 0x200; \ - CF = 0; \ - A &= (u8); \ - ZF = A; \ -} while (0) - -//or a,r (4 cycles): -//or a,(hl) (8 cycles): -//bitwise or 8-bit value into A, check flags: -#define or_a_u8(u8) do { \ - CF = HF2 = 0; \ - A |= (u8); \ - ZF = A; \ -} while (0) - -//xor a,r (4 cycles): -//xor a,(hl) (8 cycles): -//bitwise xor 8-bit value into A, check flags: -#define xor_a_u8(u8) do { \ - CF = HF2 = 0; \ - A ^= (u8); \ - ZF = A; \ -} while (0) - -//cp a,r (4 cycles): -//cp a,(addr) (8 cycles): -//Compare (subtract without storing result) 8-bit value to A, check flags: -#define cp_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A - HF2; \ - HF2 |= 0x400; \ - calcHF(HF1, HF2); \ -} while (0) - -//inc r (4 cycles): -//Increment value of 8-bit register, check flags except CF: -#define inc_r(r) do { \ - HF2 = (r) | 0x800; \ - ZF = (r) + 1; \ - (r) = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//dec r (4 cycles): -//Decrement value of 8-bit register, check flags except CF: -#define dec_r(r) do { \ - HF2 = (r) | 0xC00; \ - ZF = (r) - 1; \ - (r) = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//16-BIT ARITHMETIC -//add hl,rr (8 cycles): -//add 16-bit register to HL, check flags except ZF: -/*#define add_hl_rr(rh, rl) do { \ - L = HF1 = L + (rl); \ - HF1 >>= 8; \ - HF1 += H; \ - HF2 = (rh); \ - H = CF = HF1 + (rh); \ - cycleCounter += 4; \ -} while (0)*/ - -#define add_hl_rr(rh, rl) do { \ - CF = L + (rl); \ - L = CF & 0xFF; \ - HF1 = H; \ - HF2 = (CF & 0x100) | (rh); \ - CF = H + (CF >> 8) + (rh); \ - H = CF & 0xFF; \ - cycleCounter += 4; \ - calcHF(HF1, HF2); \ -} while (0) - -//inc rr (8 cycles): -//Increment 16-bit register: -#define inc_rr(rh, rl) do { \ - const unsigned inc_rr_var_tmp = (rl) + 1; \ - (rl) = inc_rr_var_tmp & 0xFF; \ - (rh) = ((rh) + (inc_rr_var_tmp >> 8)) & 0xFF; \ - cycleCounter += 4; \ -} while (0) - -//dec rr (8 cycles): -//Decrement 16-bit register: -#define dec_rr(rh, rl) do { \ - const unsigned dec_rr_var_tmp = (rl) - 1; \ - (rl) = dec_rr_var_tmp & 0xFF; \ - (rh) = ((rh) - (dec_rr_var_tmp >> 8 & 1)) & 0xFF; \ - cycleCounter += 4; \ -} while (0) - -#define sp_plus_n(sumout) do { \ - unsigned sp_plus_n_var_n; \ - PC_READ(sp_plus_n_var_n); \ - sp_plus_n_var_n = (sp_plus_n_var_n ^ 0x80) - 0x80; \ - \ - const unsigned sp_plus_n_var_sum = SP + sp_plus_n_var_n; \ - CF = SP ^ sp_plus_n_var_n ^ sp_plus_n_var_sum; \ - HF2 = CF << 5 & 0x200; \ - ZF = 1; \ - cycleCounter += 4; \ - (sumout) = sp_plus_n_var_sum & 0xFFFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//JUMPS: -//jp nn (16 cycles): -//Jump to address stored in the next two bytes in memory: -#define jp_nn() do { \ - unsigned jp_nn_var_l, jp_nn_var_h; \ -\ - PC_READ(jp_nn_var_l); \ - PC_READ(jp_nn_var_h); \ -\ - PC_MOD(jp_nn_var_h << 8 | jp_nn_var_l); \ -} while (0) - -//jr disp (12 cycles): -//Jump to value of next (signed) byte in memory+current address: -#define jr_disp() do { \ - unsigned jr_disp_var_tmp; \ -\ - PC_READ(jr_disp_var_tmp); \ - jr_disp_var_tmp = (jr_disp_var_tmp ^ 0x80) - 0x80; \ -\ - PC_MOD((PC + jr_disp_var_tmp) & 0xFFFF); \ -} while (0) - -// CALLS, RESTARTS AND RETURNS: -// call nn (24 cycles): -// Jump to 16-bit immediate operand and push return address onto stack: -#define call_nn() do { \ - unsigned const npc = (PC + 2) & 0xFFFF; \ - jp_nn(); \ - PUSH(npc >> 8, npc & 0xFF); \ -} while (0) - -//rst n (16 Cycles): -//Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): -#define rst_n(n) do { \ - PUSH(PC >> 8, PC & 0xFF); \ - PC_MOD(n); \ -} while (0) - -//ret (16 cycles): -//Pop two bytes from the stack and jump to that address: -#define ret() do { \ - unsigned ret_var_l, ret_var_h; \ -\ - pop_rr(ret_var_h, ret_var_l); \ -\ - PC_MOD(ret_var_h << 8 | ret_var_l); \ -} while (0) - -void CPU::process(const unsigned long cycles) { - memory.setEndtime(cycleCounter_, cycles); - memory.updateInput(); - - //unsigned char A = A_; - unsigned long cycleCounter = cycleCounter_; - - while (memory.isActive()) { - //unsigned short PC = PC_; - - if (memory.halted()) { - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - } else while (cycleCounter < memory.nextEventTime()) { - unsigned char opcode; - - if (tracecallback) { - int result[14]; - result[0] = cycleCounter; - result[1] = PC; - result[2] = SP; - result[3] = A; - result[4] = B; - result[5] = C; - result[6] = D; - result[7] = E; - result[8] = F(); - result[9] = H; - result[10] = L; - result[11] = skip; - PC_READ_FIRST(opcode); - result[12] = opcode; - result[13] = memory.debugGetLY(); - tracecallback((void *)result); - } - else { - PC_READ_FIRST(opcode); - } - - if (skip) { - PC = (PC - 1) & 0xFFFF; - skip = false; - } - - switch (opcode) { - //nop (4 cycles): - //Do nothing for 4 cycles: - case 0x00: - break; - case 0x01: - ld_rr_nn(B, C); - break; - case 0x02: - WRITE(BC(), A); - break; - case 0x03: - inc_rr(B, C); - break; - case 0x04: - inc_r(B); - break; - case 0x05: - dec_r(B); - break; - case 0x06: - PC_READ(B); - break; - - //rlca (4 cycles): - //Rotate 8-bit register A left, store old bit7 in CF. Reset SF, HCF, ZF: - case 0x07: - CF = A << 1; - A = (CF | CF >> 8) & 0xFF; - HF2 = 0; - ZF = 1; - break; - - //ld (nn),SP (20 cycles): - //Put value of SP into address given by next 2 bytes in memory: - case 0x08: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - const unsigned addr = h << 8 | l; - - WRITE(addr, SP & 0xFF); - WRITE((addr + 1) & 0xFFFF, SP >> 8); - } - break; - - case 0x09: - add_hl_rr(B, C); - break; - case 0x0A: - READ(A, BC()); - break; - case 0x0B: - dec_rr(B, C); - break; - case 0x0C: - inc_r(C); - break; - case 0x0D: - dec_r(C); - break; - case 0x0E: - PC_READ(C); - break; - - //rrca (4 cycles): - //Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: - case 0x0F: - CF = A << 8 | A; - A = CF >> 1 & 0xFF; - HF2 = 0; - ZF = 1; - break; - - //stop (4 cycles): - //Halt CPU and LCD display until button pressed: - case 0x10: - PC = (PC + 1) & 0xFFFF; - - cycleCounter = memory.stop(cycleCounter); - - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - - break; - case 0x11: - ld_rr_nn(D, E); - break; - case 0x12: - WRITE(DE(), A); - break; - case 0x13: - inc_rr(D, E); - break; - case 0x14: - inc_r(D); - break; - case 0x15: - dec_r(D); - break; - case 0x16: - PC_READ(D); - break; - - //rla (4 cycles): - //Rotate 8-bit register A left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF, HCF, ZF: - case 0x17: - { - const unsigned oldcf = CF >> 8 & 1; - CF = A << 1; - A = (CF | oldcf) & 0xFF; - } - - HF2 = 0; - ZF = 1; - break; - - case 0x18: - jr_disp(); - break; - case 0x19: - add_hl_rr(D, E); - break; - case 0x1A: - READ(A, DE()); - break; - case 0x1B: - dec_rr(D, E); - break; - case 0x1C: - inc_r(E); - break; - case 0x1D: - dec_r(E); - break; - case 0x1E: - PC_READ(E); - break; - - //rra (4 cycles): - //Rotate 8-bit register A right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF, HCF, ZF: - case 0x1F: - { - const unsigned oldcf = CF & 0x100; - CF = A << 8; - A = (A | oldcf) >> 1; - } - - HF2 = 0; - ZF = 1; - break; - - //jr nz,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if ZF is unset: - case 0x20: - if (ZF & 0xFF) { - jr_disp(); - } else { - PC_MOD((PC + 1) & 0xFFFF); - } - break; - - case 0x21: - ld_rr_nn(H, L); - break; - - //ldi (hl),a (8 cycles): - //Put A into memory address in hl. Increment HL: - case 0x22: - { - unsigned addr = HL(); - - WRITE(addr, A); - - addr = (addr + 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x23: - inc_rr(H, L); - break; - case 0x24: - inc_r(H); - break; - case 0x25: - dec_r(H); - break; - case 0x26: - PC_READ(H); - break; - - - //daa (4 cycles): - //Adjust register A to correctly represent a BCD. Check ZF, HF and CF: - case 0x27: - /*{ - unsigned correction = ((A > 0x99) || (CF & 0x100)) ? 0x60 : 0x00; - - calcHF(HF1, HF2); - - if ((A & 0x0F) > 0x09 || (HF2 & 0x200)) - correction |= 0x06; - - HF1 = A; - HF2 = (HF2 & 0x400) | correction; - CF = (correction & 0x40) << 2; - A = (HF2 & 0x400) ? A - correction : (A + correction); - ZF = A; - }*/ - - calcHF(HF1, HF2); - - { - unsigned correction = (CF & 0x100) ? 0x60 : 0x00; - - if (HF2 & 0x200) - correction |= 0x06; - - if (!(HF2 &= 0x400)) { - if ((A & 0x0F) > 0x09) - correction |= 0x06; - - if (A > 0x99) - correction |= 0x60; - - A += correction; - } else - A -= correction; - - CF = correction << 2 & 0x100; - ZF = A; - A &= 0xFF; - } - break; - - //jr z,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if ZF is set: - case 0x28: - if (ZF & 0xFF) { - PC_MOD((PC + 1) & 0xFFFF); - } else { - jr_disp(); - } - break; - - //add hl,hl (8 cycles): - //add 16-bit register HL to HL, check flags except ZF: - case 0x29: - add_hl_rr(H, L); - break; - - //ldi a,(hl) (8 cycles): - //Put value at address in hl into A. Increment HL: - case 0x2A: - { - unsigned addr = HL(); - - READ(A, addr); - - addr = (addr + 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x2B: - dec_rr(H, L); - break; - case 0x2C: - inc_r(L); - break; - case 0x2D: - dec_r(L); - break; - case 0x2E: - PC_READ(L); - break; - - //cpl (4 cycles): - //Complement register A. (Flip all bits), set SF and HCF: - case 0x2F: /*setSubtractFlag(); setHalfCarryFlag();*/ - HF2 = 0x600; - A ^= 0xFF; - break; - - //jr nc,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if CF is unset: - case 0x30: - if (CF & 0x100) { - PC_MOD((PC + 1) & 0xFFFF); - } else { - jr_disp(); - } - break; - - //ld sp,nn (12 cycles) - //set sp to 16-bit value of next 2 bytes in memory - case 0x31: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - SP = h << 8 | l; - } - break; - - //ldd (hl),a (8 cycles): - //Put A into memory address in hl. Decrement HL: - case 0x32: - { - unsigned addr = HL(); - - WRITE(addr, A); - - addr = (addr - 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x33: - SP = (SP + 1) & 0xFFFF; - cycleCounter += 4; - break; - - //inc (hl) (12 cycles): - //Increment value at address in hl, check flags except CF: - case 0x34: - { - const unsigned addr = HL(); - - READ(HF2, addr); - ZF = HF2 + 1; - WRITE(addr, ZF & 0xFF); - HF2 |= 0x800; - calcHF(HF1, HF2); - } - break; - - //dec (hl) (12 cycles): - //Decrement value at address in hl, check flags except CF: - case 0x35: - { - const unsigned addr = HL(); - - READ(HF2, addr); - ZF = HF2 - 1; - WRITE(addr, ZF & 0xFF); - HF2 |= 0xC00; - calcHF(HF1, HF2); - } - break; - - //ld (hl),n (12 cycles): - //set memory at address in hl to value of next byte in memory: - case 0x36: - { - unsigned tmp; - - PC_READ(tmp); - WRITE(HL(), tmp); - } - break; - - //scf (4 cycles): - //Set CF. Unset SF and HCF: - case 0x37: /*setCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ - CF = 0x100; - HF2 = 0; - break; - - //jr c,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if CF is set: - case 0x38: //PC+=(((int8_t)memory.read(PC++))*CarryFlag()); Cycles(8); break; - if (CF & 0x100) { - jr_disp(); - } else { - PC_MOD((PC + 1) & 0xFFFF); - } - break; - - //add hl,sp (8 cycles): - //add SP to HL, check flags except ZF: - case 0x39: /*add_hl_rr(SP>>8, SP); break;*/ - CF = L + SP; - L = CF & 0xFF; - HF1 = H; - HF2 = ((CF ^ SP) & 0x100) | SP >> 8; - CF >>= 8; - CF += H; - H = CF & 0xFF; - cycleCounter += 4; - calcHF(HF1, HF2); - break; - - //ldd a,(hl) (8 cycles): - //Put value at address in hl into A. Decrement HL: - case 0x3A: - { - unsigned addr = HL(); - - A = memory.read(addr, cycleCounter); - cycleCounter += 4; - - addr = (addr - 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x3B: - SP = (SP - 1) & 0xFFFF; - cycleCounter += 4; - break; - case 0x3C: - inc_r(A); - break; - case 0x3D: - dec_r(A); - break; - case 0x3E: - PC_READ(A); - break; - - //ccf (4 cycles): - //Complement CF (unset if set vv.) Unset SF and HCF. - case 0x3F: /*complementCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ - CF ^= 0x100; - HF2 = 0; - break; - - //ld r,r (4 cycles):next_irqEventTime - //ld r,(r) (8 cycles): - case 0x40: - B = B; - break; - case 0x41: - B = C; - break; - case 0x42: - B = D; - break; - case 0x43: - B = E; - break; - case 0x44: - B = H; - break; - case 0x45: - B = L; - break; - case 0x46: - READ(B, HL()); - break; - case 0x47: - B = A; - break; - case 0x48: - C = B; - break; - case 0x49: - C = C; - break; - case 0x4A: - C = D; - break; - case 0x4B: - C = E; - break; - case 0x4C: - C = H; - break; - case 0x4D: - C = L; - break; - case 0x4E: - READ(C, HL()); - break; - case 0x4F: - C = A; - break; - case 0x50: - D = B; - break; - case 0x51: - D = C; - break; - case 0x52: - D = D; - break; - case 0x53: - D = E; - break; - case 0x54: - D = H; - break; - case 0x55: - D = L; - break; - case 0x56: - READ(D, HL()); - break; - case 0x57: - D = A; - break; - case 0x58: - E = B; - break; - case 0x59: - E = C; - break; - case 0x5A: - E = D; - break; - case 0x5B: - E = E; - break; - case 0x5C: - E = H; - break; - case 0x5D: - E = L; - break; - case 0x5E: - READ(E, HL()); - break; - case 0x5F: - E = A; - break; - case 0x60: - H = B; - break; - case 0x61: - H = C; - break; - case 0x62: - H = D; - break; - case 0x63: - H = E; - break; - case 0x64: - H = H; - break; - case 0x65: - H = L; - break; - case 0x66: - READ(H, HL()); - break; - case 0x67: - H = A; - break; - case 0x68: - L = B; - break; - case 0x69: - L = C; - break; - case 0x6A: - L = D; - break; - case 0x6B: - L = E; - break; - case 0x6C: - L = H; - break; - case 0x6D: - L = L; - break; - case 0x6E: - READ(L, HL()); - break; - case 0x6F: - L = A; - break; - case 0x70: - WRITE(HL(), B); - break; - case 0x71: - WRITE(HL(), C); - break; - case 0x72: - WRITE(HL(), D); - break; - case 0x73: - WRITE(HL(), E); - break; - case 0x74: - WRITE(HL(), H); - break; - case 0x75: - WRITE(HL(), L); - break; - - //halt (4 cycles): - case 0x76: - if (!memory.ime() && (memory.ff_read(0xFF0F, cycleCounter) & memory.ff_read(0xFFFF, cycleCounter) & 0x1F)) { - if (memory.isCgb()) - cycleCounter += 4; - else - skip = true; - } else { - memory.halt(); - - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - } - - break; - case 0x77: - WRITE(HL(), A); - break; - case 0x78: - A = B; - break; - case 0x79: - A = C; - break; - case 0x7A: - A = D; - break; - case 0x7B: - A = E; - break; - case 0x7C: - A = H; - break; - case 0x7D: - A = L; - break; - case 0x7E: - READ(A, HL()); - break; - case 0x7F: - // A = A; - break; - case 0x80: - add_a_u8(B); - break; - case 0x81: - add_a_u8(C); - break; - case 0x82: - add_a_u8(D); - break; - case 0x83: - add_a_u8(E); - break; - case 0x84: - add_a_u8(H); - break; - case 0x85: - add_a_u8(L); - break; - case 0x86: - { - unsigned data; - - READ(data, HL()); - - add_a_u8(data); - } - break; - case 0x87: - add_a_u8(A); - break; - case 0x88: - adc_a_u8(B); - break; - case 0x89: - adc_a_u8(C); - break; - case 0x8A: - adc_a_u8(D); - break; - case 0x8B: - adc_a_u8(E); - break; - case 0x8C: - adc_a_u8(H); - break; - case 0x8D: - adc_a_u8(L); - break; - case 0x8E: - { - unsigned data; - - READ(data, HL()); - - adc_a_u8(data); - } - break; - case 0x8F: - adc_a_u8(A); - break; - case 0x90: - sub_a_u8(B); - break; - case 0x91: - sub_a_u8(C); - break; - case 0x92: - sub_a_u8(D); - break; - case 0x93: - sub_a_u8(E); - break; - case 0x94: - sub_a_u8(H); - break; - case 0x95: - sub_a_u8(L); - break; - case 0x96: - { - unsigned data; - - READ(data, HL()); - - sub_a_u8(data); - } - break; - //A-A is always 0: - case 0x97: - HF2 = 0x400; - CF = ZF = A = 0; - break; - case 0x98: - sbc_a_u8(B); - break; - case 0x99: - sbc_a_u8(C); - break; - case 0x9A: - sbc_a_u8(D); - break; - case 0x9B: - sbc_a_u8(E); - break; - case 0x9C: - sbc_a_u8(H); - break; - case 0x9D: - sbc_a_u8(L); - break; - case 0x9E: - { - unsigned data; - - READ(data, HL()); - - sbc_a_u8(data); - } - break; - case 0x9F: - sbc_a_u8(A); - break; - case 0xA0: - and_a_u8(B); - break; - case 0xA1: - and_a_u8(C); - break; - case 0xA2: - and_a_u8(D); - break; - case 0xA3: - and_a_u8(E); - break; - case 0xA4: - and_a_u8(H); - break; - case 0xA5: - and_a_u8(L); - break; - case 0xA6: - { - unsigned data; - - READ(data, HL()); - - and_a_u8(data); - } - break; - //A&A will always be A: - case 0xA7: - ZF = A; - CF = 0; - HF2 = 0x200; - break; - case 0xA8: - xor_a_u8(B); - break; - case 0xA9: - xor_a_u8(C); - break; - case 0xAA: - xor_a_u8(D); - break; - case 0xAB: - xor_a_u8(E); - break; - case 0xAC: - xor_a_u8(H); - break; - case 0xAD: - xor_a_u8(L); - break; - case 0xAE: - { - unsigned data; - - READ(data, HL()); - - xor_a_u8(data); - } - break; - //A^A will always be 0: - case 0xAF: - CF = HF2 = ZF = A = 0; - break; - case 0xB0: - or_a_u8(B); - break; - case 0xB1: - or_a_u8(C); - break; - case 0xB2: - or_a_u8(D); - break; - case 0xB3: - or_a_u8(E); - break; - case 0xB4: - or_a_u8(H); - break; - case 0xB5: - or_a_u8(L); - break; - case 0xB6: - { - unsigned data; - - READ(data, HL()); - - or_a_u8(data); - } - break; - //A|A will always be A: - case 0xB7: - ZF = A; - HF2 = CF = 0; - break; - case 0xB8: - cp_a_u8(B); - break; - case 0xB9: - cp_a_u8(C); - break; - case 0xBA: - cp_a_u8(D); - break; - case 0xBB: - cp_a_u8(E); - break; - case 0xBC: - cp_a_u8(H); - break; - case 0xBD: - cp_a_u8(L); - break; - case 0xBE: - { - unsigned data; - - READ(data, HL()); - - cp_a_u8(data); - } - break; - //A always equals A: - case 0xBF: - CF = ZF = 0; - HF2 = 0x400; - calcHF(HF1, HF2); \ - break; - - //ret nz (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if ZF is unset: - case 0xC0: - cycleCounter += 4; - - if (ZF & 0xFF) { - ret(); - } - break; - - case 0xC1: - pop_rr(B, C); - break; - - //jp nz,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if ZF is unset: - case 0xC2: - if (ZF & 0xFF) { - jp_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xC3: - jp_nn(); - break; - - //call nz,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is unset: - case 0xC4: - if (ZF & 0xFF) { - call_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xC5: - push_rr(B, C); - break; - case 0xC6: - { - unsigned data; - - PC_READ(data); - - add_a_u8(data); - } - break; - case 0xC7: - rst_n(0x00); - break; - - //ret z (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if ZF is set: - case 0xC8: - cycleCounter += 4; - - if (!(ZF & 0xFF)) { - ret(); - } - - break; - - //ret (16 cycles): - //Pop two bytes from the stack and jump to that address: - case 0xC9: - ret(); - break; - - //jp z,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if ZF is set: - case 0xCA: - if (ZF & 0xFF) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - jp_nn(); - } - break; - - - //CB OPCODES (Shifts, rotates and bits): - case 0xCB: - PC_READ(opcode); - - switch (opcode) { - case 0x00: - rlc_r(B); - break; - case 0x01: - rlc_r(C); - break; - case 0x02: - rlc_r(D); - break; - case 0x03: - rlc_r(E); - break; - case 0x04: - rlc_r(H); - break; - case 0x05: - rlc_r(L); - break; - //rlc (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: - case 0x06: - { - const unsigned addr = HL(); - - READ(CF, addr); - CF <<= 1; - - ZF = CF | (CF >> 8); - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x07: - rlc_r(A); - break; - case 0x08: - rrc_r(B); - break; - case 0x09: - rrc_r(C); - break; - case 0x0A: - rrc_r(D); - break; - case 0x0B: - rrc_r(E); - break; - case 0x0C: - rrc_r(H); - break; - case 0x0D: - rrc_r(L); - break; - //rrc (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: - case 0x0E: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - CF = ZF << 8; - - WRITE(addr, (ZF | CF) >> 1 & 0xFF); - - HF2 = 0; - } - break; - case 0x0F: - rrc_r(A); - break; - case 0x10: - rl_r(B); - break; - case 0x11: - rl_r(C); - break; - case 0x12: - rl_r(D); - break; - case 0x13: - rl_r(E); - break; - case 0x14: - rl_r(H); - break; - case 0x15: - rl_r(L); - break; - //rl (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL left thorugh CF, store old bit7 in CF, old CF value becoms bit0. Reset SF and HCF. Check ZF: - case 0x16: - { - const unsigned addr = HL(); - const unsigned oldcf = CF >> 8 & 1; - - READ(CF, addr); - CF <<= 1; - - ZF = CF | oldcf; - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x17: - rl_r(A); - break; - case 0x18: - rr_r(B); - break; - case 0x19: - rr_r(C); - break; - case 0x1A: - rr_r(D); - break; - case 0x1B: - rr_r(E); - break; - case 0x1C: - rr_r(H); - break; - case 0x1D: - rr_r(L); - break; - //rr (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL right thorugh CF, store old bit0 in CF, old CF value becoms bit7. Reset SF and HCF. Check ZF: - case 0x1E: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - const unsigned oldcf = CF & 0x100; - CF = ZF << 8; - ZF = (ZF | oldcf) >> 1; - - WRITE(addr, ZF); - - HF2 = 0; - } - break; - case 0x1F: - rr_r(A); - break; - case 0x20: - sla_r(B); - break; - case 0x21: - sla_r(C); - break; - case 0x22: - sla_r(D); - break; - case 0x23: - sla_r(E); - break; - case 0x24: - sla_r(H); - break; - case 0x25: - sla_r(L); - break; - //sla (hl) (16 cycles): - //Shift 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: - case 0x26: - { - const unsigned addr = HL(); - - READ(CF, addr); - CF <<= 1; - - ZF = CF; - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x27: - sla_r(A); - break; - case 0x28: - sra_r(B); - break; - case 0x29: - sra_r(C); - break; - case 0x2A: - sra_r(D); - break; - case 0x2B: - sra_r(E); - break; - case 0x2C: - sra_r(H); - break; - case 0x2D: - sra_r(L); - break; - //sra (hl) (16 cycles): - //Shift 8-bit value stored at address in HL right, store old bit0 in CF, bit7=old bit7. Reset SF and HCF. Check ZF: - case 0x2E: - { - const unsigned addr = HL(); - - READ(CF, addr); - - ZF = CF >> 1; - - WRITE(addr, ZF | (CF & 0x80)); - - CF <<= 8; - HF2 = 0; - } - break; - case 0x2F: - sra_r(A); - break; - case 0x30: - swap_r(B); - break; - case 0x31: - swap_r(C); - break; - case 0x32: - swap_r(D); - break; - case 0x33: - swap_r(E); - break; - case 0x34: - swap_r(H); - break; - case 0x35: - swap_r(L); - break; - //swap (hl) (16 cycles): - //Swap upper and lower nibbles of 8-bit value stored at address in HL, reset flags, check zero flag: - case 0x36: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - WRITE(addr, (ZF << 4 | ZF >> 4) & 0xFF); - - CF = HF2 = 0; - } - break; - case 0x37: - swap_r(A); - break; - case 0x38: - srl_r(B); - break; - case 0x39: - srl_r(C); - break; - case 0x3A: - srl_r(D); - break; - case 0x3B: - srl_r(E); - break; - case 0x3C: - srl_r(H); - break; - case 0x3D: - srl_r(L); - break; - //srl (hl) (16 cycles): - //Shift 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: - case 0x3E: - { - const unsigned addr = HL(); - - READ(CF, addr); - - ZF = CF >> 1; - - WRITE(addr, ZF); - - CF <<= 8; - HF2 = 0; - } - break; - case 0x3F: - srl_r(A); - break; - case 0x40: - bit0_u8(B); - break; - case 0x41: - bit0_u8(C); - break; - case 0x42: - bit0_u8(D); - break; - case 0x43: - bit0_u8(E); - break; - case 0x44: - bit0_u8(H); - break; - case 0x45: - bit0_u8(L); - break; - case 0x46: - { - unsigned data; - - READ(data, HL()); - - bit0_u8(data); - } - break; - case 0x47: - bit0_u8(A); - break; - case 0x48: - bit1_u8(B); - break; - case 0x49: - bit1_u8(C); - break; - case 0x4A: - bit1_u8(D); - break; - case 0x4B: - bit1_u8(E); - break; - case 0x4C: - bit1_u8(H); - break; - case 0x4D: - bit1_u8(L); - break; - case 0x4E: - { - unsigned data; - - READ(data, HL()); - - bit1_u8(data); - } - break; - case 0x4F: - bit1_u8(A); - break; - case 0x50: - bit2_u8(B); - break; - case 0x51: - bit2_u8(C); - break; - case 0x52: - bit2_u8(D); - break; - case 0x53: - bit2_u8(E); - break; - case 0x54: - bit2_u8(H); - break; - case 0x55: - bit2_u8(L); - break; - case 0x56: - { - unsigned data; - - READ(data, HL()); - - bit2_u8(data); - } - break; - case 0x57: - bit2_u8(A); - break; - case 0x58: - bit3_u8(B); - break; - case 0x59: - bit3_u8(C); - break; - case 0x5A: - bit3_u8(D); - break; - case 0x5B: - bit3_u8(E); - break; - case 0x5C: - bit3_u8(H); - break; - case 0x5D: - bit3_u8(L); - break; - case 0x5E: - { - unsigned data; - - READ(data, HL()); - - bit3_u8(data); - } - break; - case 0x5F: - bit3_u8(A); - break; - case 0x60: - bit4_u8(B); - break; - case 0x61: - bit4_u8(C); - break; - case 0x62: - bit4_u8(D); - break; - case 0x63: - bit4_u8(E); - break; - case 0x64: - bit4_u8(H); - break; - case 0x65: - bit4_u8(L); - break; - case 0x66: - { - unsigned data; - - READ(data, HL()); - - bit4_u8(data); - } - break; - case 0x67: - bit4_u8(A); - break; - case 0x68: - bit5_u8(B); - break; - case 0x69: - bit5_u8(C); - break; - case 0x6A: - bit5_u8(D); - break; - case 0x6B: - bit5_u8(E); - break; - case 0x6C: - bit5_u8(H); - break; - case 0x6D: - bit5_u8(L); - break; - case 0x6E: - { - unsigned data; - - READ(data, HL()); - - bit5_u8(data); - } - break; - case 0x6F: - bit5_u8(A); - break; - case 0x70: - bit6_u8(B); - break; - case 0x71: - bit6_u8(C); - break; - case 0x72: - bit6_u8(D); - break; - case 0x73: - bit6_u8(E); - break; - case 0x74: - bit6_u8(H); - break; - case 0x75: - bit6_u8(L); - break; - case 0x76: - { - unsigned data; - - READ(data, HL()); - - bit6_u8(data); - } - break; - case 0x77: - bit6_u8(A); - break; - case 0x78: - bit7_u8(B); - break; - case 0x79: - bit7_u8(C); - break; - case 0x7A: - bit7_u8(D); - break; - case 0x7B: - bit7_u8(E); - break; - case 0x7C: - bit7_u8(H); - break; - case 0x7D: - bit7_u8(L); - break; - case 0x7E: - { - unsigned data; - - READ(data, HL()); - - bit7_u8(data); - } - break; - case 0x7F: - bit7_u8(A); - break; - case 0x80: - res0_r(B); - break; - case 0x81: - res0_r(C); - break; - case 0x82: - res0_r(D); - break; - case 0x83: - res0_r(E); - break; - case 0x84: - res0_r(H); - break; - case 0x85: - res0_r(L); - break; - case 0x86: - resn_mem_hl(0); - break; - case 0x87: - res0_r(A); - break; - case 0x88: - res1_r(B); - break; - case 0x89: - res1_r(C); - break; - case 0x8A: - res1_r(D); - break; - case 0x8B: - res1_r(E); - break; - case 0x8C: - res1_r(H); - break; - case 0x8D: - res1_r(L); - break; - case 0x8E: - resn_mem_hl(1); - break; - case 0x8F: - res1_r(A); - break; - case 0x90: - res2_r(B); - break; - case 0x91: - res2_r(C); - break; - case 0x92: - res2_r(D); - break; - case 0x93: - res2_r(E); - break; - case 0x94: - res2_r(H); - break; - case 0x95: - res2_r(L); - break; - case 0x96: - resn_mem_hl(2); - break; - case 0x97: - res2_r(A); - break; - case 0x98: - res3_r(B); - break; - case 0x99: - res3_r(C); - break; - case 0x9A: - res3_r(D); - break; - case 0x9B: - res3_r(E); - break; - case 0x9C: - res3_r(H); - break; - case 0x9D: - res3_r(L); - break; - case 0x9E: - resn_mem_hl(3); - break; - case 0x9F: - res3_r(A); - break; - case 0xA0: - res4_r(B); - break; - case 0xA1: - res4_r(C); - break; - case 0xA2: - res4_r(D); - break; - case 0xA3: - res4_r(E); - break; - case 0xA4: - res4_r(H); - break; - case 0xA5: - res4_r(L); - break; - case 0xA6: - resn_mem_hl(4); - break; - case 0xA7: - res4_r(A); - break; - case 0xA8: - res5_r(B); - break; - case 0xA9: - res5_r(C); - break; - case 0xAA: - res5_r(D); - break; - case 0xAB: - res5_r(E); - break; - case 0xAC: - res5_r(H); - break; - case 0xAD: - res5_r(L); - break; - case 0xAE: - resn_mem_hl(5); - break; - case 0xAF: - res5_r(A); - break; - case 0xB0: - res6_r(B); - break; - case 0xB1: - res6_r(C); - break; - case 0xB2: - res6_r(D); - break; - case 0xB3: - res6_r(E); - break; - case 0xB4: - res6_r(H); - break; - case 0xB5: - res6_r(L); - break; - case 0xB6: - resn_mem_hl(6); - break; - case 0xB7: - res6_r(A); - break; - case 0xB8: - res7_r(B); - break; - case 0xB9: - res7_r(C); - break; - case 0xBA: - res7_r(D); - break; - case 0xBB: - res7_r(E); - break; - case 0xBC: - res7_r(H); - break; - case 0xBD: - res7_r(L); - break; - case 0xBE: - resn_mem_hl(7); - break; - case 0xBF: - res7_r(A); - break; - case 0xC0: - set0_r(B); - break; - case 0xC1: - set0_r(C); - break; - case 0xC2: - set0_r(D); - break; - case 0xC3: - set0_r(E); - break; - case 0xC4: - set0_r(H); - break; - case 0xC5: - set0_r(L); - break; - case 0xC6: - setn_mem_hl(0); - break; - case 0xC7: - set0_r(A); - break; - case 0xC8: - set1_r(B); - break; - case 0xC9: - set1_r(C); - break; - case 0xCA: - set1_r(D); - break; - case 0xCB: - set1_r(E); - break; - case 0xCC: - set1_r(H); - break; - case 0xCD: - set1_r(L); - break; - case 0xCE: - setn_mem_hl(1); - break; - case 0xCF: - set1_r(A); - break; - case 0xD0: - set2_r(B); - break; - case 0xD1: - set2_r(C); - break; - case 0xD2: - set2_r(D); - break; - case 0xD3: - set2_r(E); - break; - case 0xD4: - set2_r(H); - break; - case 0xD5: - set2_r(L); - break; - case 0xD6: - setn_mem_hl(2); - break; - case 0xD7: - set2_r(A); - break; - case 0xD8: - set3_r(B); - break; - case 0xD9: - set3_r(C); - break; - case 0xDA: - set3_r(D); - break; - case 0xDB: - set3_r(E); - break; - case 0xDC: - set3_r(H); - break; - case 0xDD: - set3_r(L); - break; - case 0xDE: - setn_mem_hl(3); - break; - case 0xDF: - set3_r(A); - break; - case 0xE0: - set4_r(B); - break; - case 0xE1: - set4_r(C); - break; - case 0xE2: - set4_r(D); - break; - case 0xE3: - set4_r(E); - break; - case 0xE4: - set4_r(H); - break; - case 0xE5: - set4_r(L); - break; - case 0xE6: - setn_mem_hl(4); - break; - case 0xE7: - set4_r(A); - break; - case 0xE8: - set5_r(B); - break; - case 0xE9: - set5_r(C); - break; - case 0xEA: - set5_r(D); - break; - case 0xEB: - set5_r(E); - break; - case 0xEC: - set5_r(H); - break; - case 0xED: - set5_r(L); - break; - case 0xEE: - setn_mem_hl(5); - break; - case 0xEF: - set5_r(A); - break; - case 0xF0: - set6_r(B); - break; - case 0xF1: - set6_r(C); - break; - case 0xF2: - set6_r(D); - break; - case 0xF3: - set6_r(E); - break; - case 0xF4: - set6_r(H); - break; - case 0xF5: - set6_r(L); - break; - case 0xF6: - setn_mem_hl(6); - break; - case 0xF7: - set6_r(A); - break; - case 0xF8: - set7_r(B); - break; - case 0xF9: - set7_r(C); - break; - case 0xFA: - set7_r(D); - break; - case 0xFB: - set7_r(E); - break; - case 0xFC: - set7_r(H); - break; - case 0xFD: - set7_r(L); - break; - case 0xFE: - setn_mem_hl(7); - break; - case 0xFF: - set7_r(A); - break; -// default: break; - } - break; - - - //call z,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is set: - case 0xCC: - if (ZF & 0xFF) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - call_nn(); - } - break; - - case 0xCD: - call_nn(); - break; - case 0xCE: - { - unsigned data; - - PC_READ(data); - - adc_a_u8(data); - } - break; - case 0xCF: - rst_n(0x08); - break; - - //ret nc (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if CF is unset: - case 0xD0: - cycleCounter += 4; - - if (!(CF & 0x100)) { - ret(); - } - - break; - - case 0xD1: - pop_rr(D, E); - break; - - //jp nc,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if CF is unset: - case 0xD2: - if (CF & 0x100) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - jp_nn(); - } - break; - - case 0xD3: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - //call nc,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is unset: - case 0xD4: - if (CF & 0x100) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - call_nn(); - } - break; - - case 0xD5: - push_rr(D, E); - break; - case 0xD6: - { - unsigned data; - - PC_READ(data); - - sub_a_u8(data); - } - break; - case 0xD7: - rst_n(0x10); - break; - - //ret c (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if CF is set: - case 0xD8: - cycleCounter += 4; - - if (CF & 0x100) { - ret(); - } - - break; - - //reti (16 cycles): - //Pop two bytes from the stack and jump to that address, then enable interrupts: - case 0xD9: - { - unsigned l, h; - - pop_rr(h, l); - - memory.ei(cycleCounter); - - PC_MOD(h << 8 | l); - } - break; - - //jp c,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if CF is set: - case 0xDA: //PC=( ((PC+2)*(1-CarryFlag())) + (((memory.read(PC+1)<<8)+memory.read(PC))*CarryFlag()) ); Cycles(12); break; - if (CF & 0x100) { - jp_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xDB: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - //call z,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is set: - case 0xDC: - if (CF & 0x100) { - call_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xDD: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - case 0xDE: - { - unsigned data; - - PC_READ(data); - - sbc_a_u8(data); - } - break; - case 0xDF: - rst_n(0x18); - break; - - //ld ($FF00+n),a (12 cycles): - //Put value in A into address (0xFF00 + next byte in memory): - case 0xE0: - { - unsigned tmp; - - PC_READ(tmp); - - FF_WRITE(0xFF00 | tmp, A); - } - break; - - case 0xE1: - pop_rr(H, L); - break; - - //ld ($FF00+C),a (8 ycles): - //Put A into address (0xFF00 + register C): - case 0xE2: - FF_WRITE(0xFF00 | C, A); - break; - case 0xE3: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xE4: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xE5: - push_rr(H, L); - break; - case 0xE6: - { - unsigned data; - - PC_READ(data); - - and_a_u8(data); - } - break; - case 0xE7: - rst_n(0x20); - break; - - //add sp,n (16 cycles): - //Add next (signed) byte in memory to SP, reset ZF and SF, check HCF and CF: - case 0xE8: - /*{ - int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); - HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; - CF = SP + tmp; - SP = CF; - CF >>= 8; - ZF = 1; - cycleCounter += 12; - }*/ - sp_plus_n(SP); - cycleCounter += 4; - break; - - //jp hl (4 cycles): - //Jump to address in hl: - case 0xE9: - PC = HL(); - break; - - //ld (nn),a (16 cycles): - //set memory at address given by the next 2 bytes to value in A: - //Incrementing PC before call, because of possible interrupt. - case 0xEA: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - WRITE(h << 8 | l, A); - } - break; - - case 0xEB: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xEC: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xED: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xEE: - { - unsigned data; - - PC_READ(data); - - xor_a_u8(data); - } - break; - case 0xEF: - rst_n(0x28); - break; - - //ld a,($FF00+n) (12 cycles): - //Put value at address (0xFF00 + next byte in memory) into A: - case 0xF0: - { - unsigned tmp; - - PC_READ(tmp); - - FF_READ(A, 0xFF00 | tmp); - } - break; - - case 0xF1: /*pop_rr(A, F); Cycles(12); break;*/ - { - unsigned F; - - pop_rr(A, F); - - FROM_F(F); - } - break; - - //ld a,($FF00+C) (8 cycles): - //Put value at address (0xFF00 + register C) into A: - case 0xF2: - FF_READ(A, 0xFF00 | C); - break; - - //di (4 cycles): - case 0xF3: - memory.di(); - break; - - case 0xF4: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xF5: /*push_rr(A, F); Cycles(16); break;*/ - calcHF(HF1, HF2); - - { - unsigned F = F(); - - push_rr(A, F); - } - break; - - case 0xF6: - { - unsigned data; - - PC_READ(data); - - or_a_u8(data); - } - break; - case 0xF7: - rst_n(0x30); - break; - - //ldhl sp,n (12 cycles): - //Put (sp+next (signed) byte in memory) into hl (unsets ZF and SF, may enable HF and CF): - case 0xF8: - /*{ - int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); - HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; - CF = SP + tmp; - L = CF; - CF >>= 8; - H = CF; - ZF = 1; - cycleCounter += 8; - }*/ - { - unsigned sum; - sp_plus_n(sum); - L = sum & 0xFF; - H = sum >> 8; - } - break; - - //ld sp,hl (8 cycles): - //Put value in HL into SP - case 0xF9: - SP = HL(); - cycleCounter += 4; - break; - - //ld a,(nn) (16 cycles): - //set A to value in memory at address given by the 2 next bytes. - case 0xFA: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - READ(A, h << 8 | l); - } - break; - - //ei (4 cycles): - //Enable Interrupts after next instruction: - case 0xFB: - memory.ei(cycleCounter); - break; - - case 0xFC: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xFD: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xFE: - { - unsigned data; - - PC_READ(data); - - cp_a_u8(data); - } - break; - case 0xFF: - rst_n(0x38); - break; -// default: break; - } - } - - //PC_ = PC; - cycleCounter = memory.event(cycleCounter); - } - - //A_ = A; - cycleCounter_ = cycleCounter; -} - -void CPU::GetRegs(int *dest) -{ - dest[0] = PC; - dest[1] = SP; - dest[2] = A; - dest[3] = B; - dest[4] = C; - dest[5] = D; - dest[6] = E; - dest[7] = F(); - dest[8] = H; - dest[9] = L; -} - -SYNCFUNC(CPU) -{ - SSS(memory); - NSS(cycleCounter_); - NSS(PC); - NSS(SP); - NSS(HF1); - NSS(HF2); - NSS(ZF); - NSS(CF); - NSS(A); - NSS(B); - NSS(C); - NSS(D); - NSS(E); - NSS(H); - NSS(L); - NSS(skip); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "cpu.h" +#include "memory.h" +#include "savestate.h" + +namespace gambatte { + +CPU::CPU() +: memory(Interrupter(SP, PC), SP, PC), + cycleCounter_(0), + PC(0x100), + SP(0xFFFE), + HF1(0xF), + HF2(0xF), + ZF(0), + CF(0x100), + A(0x01), + B(0x00), + C(0x13), + D(0x00), + E(0xD8), + H(0x01), + L(0x4D), + skip(false), + numInterruptAddresses(), + tracecallback(0) +{ +} + +long CPU::runFor(const unsigned long cycles) { + process(cycles/* << memory.isDoubleSpeed()*/); + + const long csb = memory.cyclesSinceBlit(cycleCounter_); + + if (cycleCounter_ & 0x80000000) + cycleCounter_ = memory.resetCounters(cycleCounter_); + + return csb; +} + +// (HF2 & 0x200) == true means HF is set. +// (HF2 & 0x400) marks the subtract flag. +// (HF2 & 0x800) is set for inc/dec. +// (HF2 & 0x100) is set if there's a carry to add. +static void calcHF(const unsigned HF1, unsigned& HF2) { + unsigned arg1 = HF1 & 0xF; + unsigned arg2 = (HF2 & 0xF) + (HF2 >> 8 & 1); + + if (HF2 & 0x800) { + arg1 = arg2; + arg2 = 1; + } + + if (HF2 & 0x400) + arg1 -= arg2; + else + arg1 = (arg1 + arg2) << 5; + + HF2 |= arg1 & 0x200; +} + +#define F() (((HF2 & 0x600) | (CF & 0x100)) >> 4 | ((ZF & 0xFF) ? 0 : 0x80)) + +#define FROM_F(f_in) do { \ + unsigned from_f_var = f_in; \ +\ + ZF = ~from_f_var & 0x80; \ + HF2 = from_f_var << 4 & 0x600; \ + CF = from_f_var << 4 & 0x100; \ +} while (0) + +void CPU::setStatePtrs(SaveState &state) { + memory.setStatePtrs(state); +} + +void CPU::loadState(const SaveState &state) { + memory.loadState(state/*, cycleCounter_*/); + + cycleCounter_ = state.cpu.cycleCounter; + PC = state.cpu.PC & 0xFFFF; + SP = state.cpu.SP & 0xFFFF; + A = state.cpu.A & 0xFF; + B = state.cpu.B & 0xFF; + C = state.cpu.C & 0xFF; + D = state.cpu.D & 0xFF; + E = state.cpu.E & 0xFF; + FROM_F(state.cpu.F); + H = state.cpu.H & 0xFF; + L = state.cpu.L & 0xFF; + skip = state.cpu.skip; +} + +#define BC() ( B << 8 | C ) +#define DE() ( D << 8 | E ) +#define HL() ( H << 8 | L ) + +#define READ(dest, addr) do { (dest) = memory.read(addr, cycleCounter); cycleCounter += 4; } while (0) +#define PEEK(dest, addr) do { (dest) = memory.read(addr, cycleCounter); } while(0) +// #define PC_READ(dest, addr) do { (dest) = memory.pc_read(addr, cycleCounter); cycleCounter += 4; } while (0) +#define PC_READ(dest) do { (dest) = memory.read_excb(PC, cycleCounter, false); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) +#define PC_READ_FIRST(dest) do { (dest) = memory.read_excb(PC, cycleCounter, true); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) +#define FF_READ(dest, addr) do { (dest) = memory.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) + +#define WRITE(addr, data) do { memory.write(addr, data, cycleCounter); cycleCounter += 4; } while (0) +#define FF_WRITE(addr, data) do { memory.ff_write(addr, data, cycleCounter); cycleCounter += 4; } while (0) + +#define PC_MOD(data) do { PC = data; cycleCounter += 4; } while (0) + +#define PUSH(r1, r2) do { \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r1)); \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r2)); \ +} while (0) + +//CB OPCODES (Shifts, rotates and bits): +//swap r (8 cycles): +//Swap upper and lower nibbles of 8-bit register, reset flags, check zero flag: +#define swap_r(r) do { \ + CF = HF2 = 0; \ + ZF = (r); \ + (r) = (ZF << 4 | ZF >> 4) & 0xFF; \ +} while (0) + +//rlc r (8 cycles): +//Rotate 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define rlc_r(r) do { \ + CF = (r) << 1; \ + ZF = CF | CF >> 8; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rl r (8 cycles): +//Rotate 8-bit register left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF and HCF, Check ZF: +#define rl_r(r) do { \ + const unsigned rl_r_var_oldcf = CF >> 8 & 1; \ + CF = (r) << 1; \ + ZF = CF | rl_r_var_oldcf; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rrc r (8 cycles): +//Rotate 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define rrc_r(r) do { \ + ZF = (r); \ + CF = ZF << 8; \ + (r) = (ZF | CF) >> 1 & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rr r (8 cycles): +//Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: +#define rr_r(r) do { \ + const unsigned rr_r_var_oldcf = CF & 0x100; \ + CF = (r) << 8; \ + (r) = ZF = ((r) | rr_r_var_oldcf) >> 1; \ + HF2 = 0; \ +} while (0) + +//sla r (8 cycles): +//Shift 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define sla_r(r) do { \ + ZF = CF = (r) << 1; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//sra r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: +#define sra_r(r) do { \ + CF = (r) << 8; \ + ZF = (r) >> 1; \ + (r) = ZF | ((r) & 0x80); \ + HF2 = 0; \ +} while (0) + +//srl r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define srl_r(r) do { \ + ZF = (r); \ + CF = (r) << 8; \ + ZF >>= 1; \ + (r) = ZF; \ + HF2 = 0; \ +} while (0) + +//bit n,r (8 cycles): +//bit n,(hl) (12 cycles): +//Test bitn in 8-bit value, check ZF, unset SF, set HCF: +#define bitn_u8(bitmask, u8) do { \ + ZF = (u8) & (bitmask); \ + HF2 = 0x200; \ +} while (0) + +#define bit0_u8(u8) bitn_u8(1, (u8)) +#define bit1_u8(u8) bitn_u8(2, (u8)) +#define bit2_u8(u8) bitn_u8(4, (u8)) +#define bit3_u8(u8) bitn_u8(8, (u8)) +#define bit4_u8(u8) bitn_u8(0x10, (u8)) +#define bit5_u8(u8) bitn_u8(0x20, (u8)) +#define bit6_u8(u8) bitn_u8(0x40, (u8)) +#define bit7_u8(u8) bitn_u8(0x80, (u8)) + +//set n,r (8 cycles): +//Set bitn of 8-bit register: +#define set0_r(r) ( (r) |= 0x1 ) +#define set1_r(r) ( (r) |= 0x2 ) +#define set2_r(r) ( (r) |= 0x4 ) +#define set3_r(r) ( (r) |= 0x8 ) +#define set4_r(r) ( (r) |= 0x10 ) +#define set5_r(r) ( (r) |= 0x20 ) +#define set6_r(r) ( (r) |= 0x40 ) +#define set7_r(r) ( (r) |= 0x80 ) + +//set n,(hl) (16 cycles): +//Set bitn of value at address stored in HL: +#define setn_mem_hl(n) do { \ + const unsigned setn_mem_hl_var_addr = HL(); \ + unsigned setn_mem_hl_var_tmp; \ +\ + READ(setn_mem_hl_var_tmp, setn_mem_hl_var_addr); \ + setn_mem_hl_var_tmp |= 1 << (n); \ +\ + WRITE(setn_mem_hl_var_addr, setn_mem_hl_var_tmp); \ +} while (0) + +//res n,r (8 cycles): +//Unset bitn of 8-bit register: +#define res0_r(r) ( (r) &= 0xFE ) +#define res1_r(r) ( (r) &= 0xFD ) +#define res2_r(r) ( (r) &= 0xFB ) +#define res3_r(r) ( (r) &= 0xF7 ) +#define res4_r(r) ( (r) &= 0xEF ) +#define res5_r(r) ( (r) &= 0xDF ) +#define res6_r(r) ( (r) &= 0xBF ) +#define res7_r(r) ( (r) &= 0x7F ) + +//res n,(hl) (16 cycles): +//Unset bitn of value at address stored in HL: +#define resn_mem_hl(n) do { \ + const unsigned resn_mem_hl_var_addr = HL(); \ + unsigned resn_mem_hl_var_tmp; \ +\ + READ(resn_mem_hl_var_tmp, resn_mem_hl_var_addr); \ + resn_mem_hl_var_tmp &= ~(1 << (n)); \ +\ + WRITE(resn_mem_hl_var_addr, resn_mem_hl_var_tmp); \ +} while (0) + + +//16-BIT LOADS: +//ld rr,nn (12 cycles) +//set rr to 16-bit value of next 2 bytes in memory +#define ld_rr_nn(r1, r2) do { \ + PC_READ(r2); \ + PC_READ(r1); \ +} while (0) + +//push rr (16 cycles): +//Push value of register pair onto stack: +#define push_rr(r1, r2) do { \ + PUSH(r1, r2); \ + cycleCounter += 4; \ +} while (0) + +//pop rr (12 cycles): +//Pop two bytes off stack into register pair: +#define pop_rr(r1, r2) do { \ + READ(r2, SP); \ + SP = (SP + 1) & 0xFFFF; \ + READ(r1, SP); \ + SP = (SP + 1) & 0xFFFF; \ +} while (0) + +//8-BIT ALU: +//add a,r (4 cycles): +//add a,(addr) (8 cycles): +//Add 8-bit value to A, check flags: +#define add_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A + HF2; \ + A = ZF & 0xFF; \ +} while (0) + +//adc a,r (4 cycles): +//adc a,(addr) (8 cycles): +//Add 8-bit value+CF to A, check flags: +#define adc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = (CF & 0x100) | (u8); \ + ZF = CF = (CF >> 8 & 1) + (u8) + A; \ + A = ZF & 0xFF; \ +} while (0) + +//sub a,r (4 cycles): +//sub a,(addr) (8 cycles): +//Subtract 8-bit value from A, check flags: +#define sub_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + A = ZF & 0xFF; \ + HF2 |= 0x400; \ +} while (0) + +//sbc a,r (4 cycles): +//sbc a,(addr) (8 cycles): +//Subtract CF and 8-bit value from A, check flags: +#define sbc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = 0x400 | (CF & 0x100) | (u8); \ + ZF = CF = A - ((CF >> 8) & 1) - (u8); \ + A = ZF & 0xFF; \ +} while (0) + +//and a,r (4 cycles): +//and a,(addr) (8 cycles): +//bitwise and 8-bit value into A, check flags: +#define and_a_u8(u8) do { \ + HF2 = 0x200; \ + CF = 0; \ + A &= (u8); \ + ZF = A; \ +} while (0) + +//or a,r (4 cycles): +//or a,(hl) (8 cycles): +//bitwise or 8-bit value into A, check flags: +#define or_a_u8(u8) do { \ + CF = HF2 = 0; \ + A |= (u8); \ + ZF = A; \ +} while (0) + +//xor a,r (4 cycles): +//xor a,(hl) (8 cycles): +//bitwise xor 8-bit value into A, check flags: +#define xor_a_u8(u8) do { \ + CF = HF2 = 0; \ + A ^= (u8); \ + ZF = A; \ +} while (0) + +//cp a,r (4 cycles): +//cp a,(addr) (8 cycles): +//Compare (subtract without storing result) 8-bit value to A, check flags: +#define cp_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + HF2 |= 0x400; \ +} while (0) + +//inc r (4 cycles): +//Increment value of 8-bit register, check flags except CF: +#define inc_r(r) do { \ + HF2 = (r) | 0x800; \ + ZF = (r) + 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//dec r (4 cycles): +//Decrement value of 8-bit register, check flags except CF: +#define dec_r(r) do { \ + HF2 = (r) | 0xC00; \ + ZF = (r) - 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//16-BIT ARITHMETIC +//add hl,rr (8 cycles): +//add 16-bit register to HL, check flags except ZF: +/*#define add_hl_rr(rh, rl) do { \ + L = HF1 = L + (rl); \ + HF1 >>= 8; \ + HF1 += H; \ + HF2 = (rh); \ + H = CF = HF1 + (rh); \ + cycleCounter += 4; \ +} while (0)*/ + +#define add_hl_rr(rh, rl) do { \ + CF = L + (rl); \ + L = CF & 0xFF; \ + HF1 = H; \ + HF2 = (CF & 0x100) | (rh); \ + CF = H + (CF >> 8) + (rh); \ + H = CF & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//inc rr (8 cycles): +//Increment 16-bit register: +#define inc_rr(rh, rl) do { \ + const unsigned inc_rr_var_tmp = (rl) + 1; \ + (rl) = inc_rr_var_tmp & 0xFF; \ + (rh) = ((rh) + (inc_rr_var_tmp >> 8)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//dec rr (8 cycles): +//Decrement 16-bit register: +#define dec_rr(rh, rl) do { \ + const unsigned dec_rr_var_tmp = (rl) - 1; \ + (rl) = dec_rr_var_tmp & 0xFF; \ + (rh) = ((rh) - (dec_rr_var_tmp >> 8 & 1)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +#define sp_plus_n(sumout) do { \ + unsigned sp_plus_n_var_n; \ + PC_READ(sp_plus_n_var_n); \ + sp_plus_n_var_n = (sp_plus_n_var_n ^ 0x80) - 0x80; \ + \ + const unsigned sp_plus_n_var_sum = SP + sp_plus_n_var_n; \ + CF = SP ^ sp_plus_n_var_n ^ sp_plus_n_var_sum; \ + HF2 = CF << 5 & 0x200; \ + ZF = 1; \ + cycleCounter += 4; \ + (sumout) = sp_plus_n_var_sum & 0xFFFF; \ +} while (0) + +//JUMPS: +//jp nn (16 cycles): +//Jump to address stored in the next two bytes in memory: +#define jp_nn() do { \ + unsigned jp_nn_var_l, jp_nn_var_h; \ +\ + PC_READ(jp_nn_var_l); \ + PC_READ(jp_nn_var_h); \ +\ + PC_MOD(jp_nn_var_h << 8 | jp_nn_var_l); \ +} while (0) + +//jr disp (12 cycles): +//Jump to value of next (signed) byte in memory+current address: +#define jr_disp() do { \ + unsigned jr_disp_var_tmp; \ +\ + PC_READ(jr_disp_var_tmp); \ + jr_disp_var_tmp = (jr_disp_var_tmp ^ 0x80) - 0x80; \ +\ + PC_MOD((PC + jr_disp_var_tmp) & 0xFFFF); \ +} while (0) + +// CALLS, RESTARTS AND RETURNS: +// call nn (24 cycles): +// Jump to 16-bit immediate operand and push return address onto stack: +#define call_nn() do { \ + unsigned const npc = (PC + 2) & 0xFFFF; \ + jp_nn(); \ + PUSH(npc >> 8, npc & 0xFF); \ +} while (0) + +//rst n (16 Cycles): +//Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): +#define rst_n(n) do { \ + PUSH(PC >> 8, PC & 0xFF); \ + PC_MOD(n); \ +} while (0) + +//ret (16 cycles): +//Pop two bytes from the stack and jump to that address: +#define ret() do { \ + unsigned ret_var_l, ret_var_h; \ +\ + pop_rr(ret_var_h, ret_var_l); \ +\ + PC_MOD(ret_var_h << 8 | ret_var_l); \ +} while (0) + +void CPU::process(const unsigned long cycles) { + memory.setEndtime(cycleCounter_, cycles); + hitInterruptAddress = 0; + memory.updateInput(); + + //unsigned char A = A_; + unsigned long cycleCounter = cycleCounter_; + + while (memory.isActive()) { + //unsigned short PC = PC_; + + if (memory.halted()) { + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } else while (cycleCounter < memory.nextEventTime()) { + unsigned char opcode = 0x00; + + int FullPC = PC; + + if (PC >= 0x4000 && PC <= 0x7FFF) + FullPC |= memory.curRomBank() << 16; + + for (int i = 0; i < numInterruptAddresses; i++) { + if (FullPC == interruptAddresses[i]) { + hitInterruptAddress = interruptAddresses[i]; + memory.setEndtime(cycleCounter, 0); + break; + } + } + + if (!hitInterruptAddress) + { + if (tracecallback) { + int result[14]; + result[0] = cycleCounter; + result[1] = PC; + result[2] = SP; + result[3] = A; + result[4] = B; + result[5] = C; + result[6] = D; + result[7] = E; + result[8] = F(); + result[9] = H; + result[10] = L; + result[11] = skip; + PC_READ_FIRST(opcode); + result[12] = opcode; + result[13] = memory.debugGetLY(); + tracecallback((void *)result); + } + else { + PC_READ_FIRST(opcode); + } + + if (skip) { + PC = (PC - 1) & 0xFFFF; + skip = false; + } + } + + switch (opcode) { + //nop (4 cycles): + //Do nothing for 4 cycles: + case 0x00: + break; + case 0x01: + ld_rr_nn(B, C); + break; + case 0x02: + WRITE(BC(), A); + break; + case 0x03: + inc_rr(B, C); + break; + case 0x04: + inc_r(B); + break; + case 0x05: + dec_r(B); + break; + case 0x06: + PC_READ(B); + break; + + //rlca (4 cycles): + //Rotate 8-bit register A left, store old bit7 in CF. Reset SF, HCF, ZF: + case 0x07: + CF = A << 1; + A = (CF | CF >> 8) & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //ld (nn),SP (20 cycles): + //Put value of SP into address given by next 2 bytes in memory: + case 0x08: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + const unsigned addr = h << 8 | l; + + WRITE(addr, SP & 0xFF); + WRITE((addr + 1) & 0xFFFF, SP >> 8); + } + break; + + case 0x09: + add_hl_rr(B, C); + break; + case 0x0A: + READ(A, BC()); + break; + case 0x0B: + dec_rr(B, C); + break; + case 0x0C: + inc_r(C); + break; + case 0x0D: + dec_r(C); + break; + case 0x0E: + PC_READ(C); + break; + + //rrca (4 cycles): + //Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: + case 0x0F: + CF = A << 8 | A; + A = CF >> 1 & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //stop (4 cycles): + //Halt CPU and LCD display until button pressed: + case 0x10: + { + unsigned char followingByte; + PEEK(followingByte, PC); + PC = (PC + 1) & 0xFFFF; + + //if (followingByte != 0x00) { + //memory.di(); + //memory.blackScreen(); + //} + + cycleCounter = memory.stop(cycleCounter); + + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } + break; + case 0x11: + ld_rr_nn(D, E); + break; + case 0x12: + WRITE(DE(), A); + break; + case 0x13: + inc_rr(D, E); + break; + case 0x14: + inc_r(D); + break; + case 0x15: + dec_r(D); + break; + case 0x16: + PC_READ(D); + break; + + //rla (4 cycles): + //Rotate 8-bit register A left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF, HCF, ZF: + case 0x17: + { + const unsigned oldcf = CF >> 8 & 1; + CF = A << 1; + A = (CF | oldcf) & 0xFF; + } + + HF2 = 0; + ZF = 1; + break; + + case 0x18: + jr_disp(); + break; + case 0x19: + add_hl_rr(D, E); + break; + case 0x1A: + READ(A, DE()); + break; + case 0x1B: + dec_rr(D, E); + break; + case 0x1C: + inc_r(E); + break; + case 0x1D: + dec_r(E); + break; + case 0x1E: + PC_READ(E); + break; + + //rra (4 cycles): + //Rotate 8-bit register A right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF, HCF, ZF: + case 0x1F: + { + const unsigned oldcf = CF & 0x100; + CF = A << 8; + A = (A | oldcf) >> 1; + } + + HF2 = 0; + ZF = 1; + break; + + //jr nz,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is unset: + case 0x20: + if (ZF & 0xFF) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + case 0x21: + ld_rr_nn(H, L); + break; + + //ldi (hl),a (8 cycles): + //Put A into memory address in hl. Increment HL: + case 0x22: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x23: + inc_rr(H, L); + break; + case 0x24: + inc_r(H); + break; + case 0x25: + dec_r(H); + break; + case 0x26: + PC_READ(H); + break; + + + //daa (4 cycles): + //Adjust register A to correctly represent a BCD. Check ZF, HF and CF: + case 0x27: + /*{ + unsigned correction = ((A > 0x99) || (CF & 0x100)) ? 0x60 : 0x00; + + calcHF(HF1, HF2); + + if ((A & 0x0F) > 0x09 || (HF2 & 0x200)) + correction |= 0x06; + + HF1 = A; + HF2 = (HF2 & 0x400) | correction; + CF = (correction & 0x40) << 2; + A = (HF2 & 0x400) ? A - correction : (A + correction); + ZF = A; + }*/ + + calcHF(HF1, HF2); + + { + unsigned correction = (CF & 0x100) ? 0x60 : 0x00; + + if (HF2 & 0x200) + correction |= 0x06; + + if (!(HF2 &= 0x400)) { + if ((A & 0x0F) > 0x09) + correction |= 0x06; + + if (A > 0x99) + correction |= 0x60; + + A += correction; + } else + A -= correction; + + CF = correction << 2 & 0x100; + ZF = A; + A &= 0xFF; + } + break; + + //jr z,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is set: + case 0x28: + if (ZF & 0xFF) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //add hl,hl (8 cycles): + //add 16-bit register HL to HL, check flags except ZF: + case 0x29: + add_hl_rr(H, L); + break; + + //ldi a,(hl) (8 cycles): + //Put value at address in hl into A. Increment HL: + case 0x2A: + { + unsigned addr = HL(); + + READ(A, addr); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x2B: + dec_rr(H, L); + break; + case 0x2C: + inc_r(L); + break; + case 0x2D: + dec_r(L); + break; + case 0x2E: + PC_READ(L); + break; + + //cpl (4 cycles): + //Complement register A. (Flip all bits), set SF and HCF: + case 0x2F: /*setSubtractFlag(); setHalfCarryFlag();*/ + HF2 = 0x600; + A ^= 0xFF; + break; + + //jr nc,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is unset: + case 0x30: + if (CF & 0x100) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //ld sp,nn (12 cycles) + //set sp to 16-bit value of next 2 bytes in memory + case 0x31: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + SP = h << 8 | l; + } + break; + + //ldd (hl),a (8 cycles): + //Put A into memory address in hl. Decrement HL: + case 0x32: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x33: + SP = (SP + 1) & 0xFFFF; + cycleCounter += 4; + break; + + //inc (hl) (12 cycles): + //Increment value at address in hl, check flags except CF: + case 0x34: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 + 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0x800; + } + break; + + //dec (hl) (12 cycles): + //Decrement value at address in hl, check flags except CF: + case 0x35: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 - 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0xC00; + } + break; + + //ld (hl),n (12 cycles): + //set memory at address in hl to value of next byte in memory: + case 0x36: + { + unsigned tmp; + + PC_READ(tmp); + WRITE(HL(), tmp); + } + break; + + //scf (4 cycles): + //Set CF. Unset SF and HCF: + case 0x37: /*setCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF = 0x100; + HF2 = 0; + break; + + //jr c,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is set: + case 0x38: //PC+=(((int8_t)memory.read(PC++))*CarryFlag()); Cycles(8); break; + if (CF & 0x100) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + //add hl,sp (8 cycles): + //add SP to HL, check flags except ZF: + case 0x39: /*add_hl_rr(SP>>8, SP); break;*/ + CF = L + SP; + L = CF & 0xFF; + HF1 = H; + HF2 = ((CF ^ SP) & 0x100) | SP >> 8; + CF >>= 8; + CF += H; + H = CF & 0xFF; + cycleCounter += 4; + break; + + //ldd a,(hl) (8 cycles): + //Put value at address in hl into A. Decrement HL: + case 0x3A: + { + unsigned addr = HL(); + + A = memory.read(addr, cycleCounter); + cycleCounter += 4; + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x3B: + SP = (SP - 1) & 0xFFFF; + cycleCounter += 4; + break; + case 0x3C: + inc_r(A); + break; + case 0x3D: + dec_r(A); + break; + case 0x3E: + PC_READ(A); + break; + + //ccf (4 cycles): + //Complement CF (unset if set vv.) Unset SF and HCF. + case 0x3F: /*complementCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF ^= 0x100; + HF2 = 0; + break; + + //ld r,r (4 cycles):next_irqEventTime + //ld r,(r) (8 cycles): + case 0x40: + B = B; + break; + case 0x41: + B = C; + break; + case 0x42: + B = D; + break; + case 0x43: + B = E; + break; + case 0x44: + B = H; + break; + case 0x45: + B = L; + break; + case 0x46: + READ(B, HL()); + break; + case 0x47: + B = A; + break; + case 0x48: + C = B; + break; + case 0x49: + C = C; + break; + case 0x4A: + C = D; + break; + case 0x4B: + C = E; + break; + case 0x4C: + C = H; + break; + case 0x4D: + C = L; + break; + case 0x4E: + READ(C, HL()); + break; + case 0x4F: + C = A; + break; + case 0x50: + D = B; + break; + case 0x51: + D = C; + break; + case 0x52: + D = D; + break; + case 0x53: + D = E; + break; + case 0x54: + D = H; + break; + case 0x55: + D = L; + break; + case 0x56: + READ(D, HL()); + break; + case 0x57: + D = A; + break; + case 0x58: + E = B; + break; + case 0x59: + E = C; + break; + case 0x5A: + E = D; + break; + case 0x5B: + E = E; + break; + case 0x5C: + E = H; + break; + case 0x5D: + E = L; + break; + case 0x5E: + READ(E, HL()); + break; + case 0x5F: + E = A; + break; + case 0x60: + H = B; + break; + case 0x61: + H = C; + break; + case 0x62: + H = D; + break; + case 0x63: + H = E; + break; + case 0x64: + H = H; + break; + case 0x65: + H = L; + break; + case 0x66: + READ(H, HL()); + break; + case 0x67: + H = A; + break; + case 0x68: + L = B; + break; + case 0x69: + L = C; + break; + case 0x6A: + L = D; + break; + case 0x6B: + L = E; + break; + case 0x6C: + L = H; + break; + case 0x6D: + L = L; + break; + case 0x6E: + READ(L, HL()); + break; + case 0x6F: + L = A; + break; + case 0x70: + WRITE(HL(), B); + break; + case 0x71: + WRITE(HL(), C); + break; + case 0x72: + WRITE(HL(), D); + break; + case 0x73: + WRITE(HL(), E); + break; + case 0x74: + WRITE(HL(), H); + break; + case 0x75: + WRITE(HL(), L); + break; + + //halt (4 cycles): + case 0x76: + if (!memory.ime() && (memory.ff_read(0xFF0F, cycleCounter) & memory.ff_read(0xFFFF, cycleCounter) & 0x1F)) { + if (memory.isCgb()) + cycleCounter += 4; + else + skip = true; + } else { + memory.halt(); + + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } + + break; + case 0x77: + WRITE(HL(), A); + break; + case 0x78: + A = B; + break; + case 0x79: + A = C; + break; + case 0x7A: + A = D; + break; + case 0x7B: + A = E; + break; + case 0x7C: + A = H; + break; + case 0x7D: + A = L; + break; + case 0x7E: + READ(A, HL()); + break; + case 0x7F: + // A = A; + break; + case 0x80: + add_a_u8(B); + break; + case 0x81: + add_a_u8(C); + break; + case 0x82: + add_a_u8(D); + break; + case 0x83: + add_a_u8(E); + break; + case 0x84: + add_a_u8(H); + break; + case 0x85: + add_a_u8(L); + break; + case 0x86: + { + unsigned data; + + READ(data, HL()); + + add_a_u8(data); + } + break; + case 0x87: + add_a_u8(A); + break; + case 0x88: + adc_a_u8(B); + break; + case 0x89: + adc_a_u8(C); + break; + case 0x8A: + adc_a_u8(D); + break; + case 0x8B: + adc_a_u8(E); + break; + case 0x8C: + adc_a_u8(H); + break; + case 0x8D: + adc_a_u8(L); + break; + case 0x8E: + { + unsigned data; + + READ(data, HL()); + + adc_a_u8(data); + } + break; + case 0x8F: + adc_a_u8(A); + break; + case 0x90: + sub_a_u8(B); + break; + case 0x91: + sub_a_u8(C); + break; + case 0x92: + sub_a_u8(D); + break; + case 0x93: + sub_a_u8(E); + break; + case 0x94: + sub_a_u8(H); + break; + case 0x95: + sub_a_u8(L); + break; + case 0x96: + { + unsigned data; + + READ(data, HL()); + + sub_a_u8(data); + } + break; + //A-A is always 0: + case 0x97: + HF2 = 0x400; + CF = ZF = A = 0; + break; + case 0x98: + sbc_a_u8(B); + break; + case 0x99: + sbc_a_u8(C); + break; + case 0x9A: + sbc_a_u8(D); + break; + case 0x9B: + sbc_a_u8(E); + break; + case 0x9C: + sbc_a_u8(H); + break; + case 0x9D: + sbc_a_u8(L); + break; + case 0x9E: + { + unsigned data; + + READ(data, HL()); + + sbc_a_u8(data); + } + break; + case 0x9F: + sbc_a_u8(A); + break; + case 0xA0: + and_a_u8(B); + break; + case 0xA1: + and_a_u8(C); + break; + case 0xA2: + and_a_u8(D); + break; + case 0xA3: + and_a_u8(E); + break; + case 0xA4: + and_a_u8(H); + break; + case 0xA5: + and_a_u8(L); + break; + case 0xA6: + { + unsigned data; + + READ(data, HL()); + + and_a_u8(data); + } + break; + //A&A will always be A: + case 0xA7: + ZF = A; + CF = 0; + HF2 = 0x200; + break; + case 0xA8: + xor_a_u8(B); + break; + case 0xA9: + xor_a_u8(C); + break; + case 0xAA: + xor_a_u8(D); + break; + case 0xAB: + xor_a_u8(E); + break; + case 0xAC: + xor_a_u8(H); + break; + case 0xAD: + xor_a_u8(L); + break; + case 0xAE: + { + unsigned data; + + READ(data, HL()); + + xor_a_u8(data); + } + break; + //A^A will always be 0: + case 0xAF: + CF = HF2 = ZF = A = 0; + break; + case 0xB0: + or_a_u8(B); + break; + case 0xB1: + or_a_u8(C); + break; + case 0xB2: + or_a_u8(D); + break; + case 0xB3: + or_a_u8(E); + break; + case 0xB4: + or_a_u8(H); + break; + case 0xB5: + or_a_u8(L); + break; + case 0xB6: + { + unsigned data; + + READ(data, HL()); + + or_a_u8(data); + } + break; + //A|A will always be A: + case 0xB7: + ZF = A; + HF2 = CF = 0; + break; + case 0xB8: + cp_a_u8(B); + break; + case 0xB9: + cp_a_u8(C); + break; + case 0xBA: + cp_a_u8(D); + break; + case 0xBB: + cp_a_u8(E); + break; + case 0xBC: + cp_a_u8(H); + break; + case 0xBD: + cp_a_u8(L); + break; + case 0xBE: + { + unsigned data; + + READ(data, HL()); + + cp_a_u8(data); + } + break; + //A always equals A: + case 0xBF: + CF = ZF = 0; + HF2 = 0x400; + break; + + //ret nz (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is unset: + case 0xC0: + cycleCounter += 4; + + if (ZF & 0xFF) { + ret(); + } + break; + + case 0xC1: + pop_rr(B, C); + break; + + //jp nz,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is unset: + case 0xC2: + if (ZF & 0xFF) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC3: + jp_nn(); + break; + + //call nz,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is unset: + case 0xC4: + if (ZF & 0xFF) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC5: + push_rr(B, C); + break; + case 0xC6: + { + unsigned data; + + PC_READ(data); + + add_a_u8(data); + } + break; + case 0xC7: + rst_n(0x00); + break; + + //ret z (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is set: + case 0xC8: + cycleCounter += 4; + + if (!(ZF & 0xFF)) { + ret(); + } + + break; + + //ret (16 cycles): + //Pop two bytes from the stack and jump to that address: + case 0xC9: + ret(); + break; + + //jp z,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is set: + case 0xCA: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + + //CB OPCODES (Shifts, rotates and bits): + case 0xCB: + PC_READ(opcode); + + switch (opcode) { + case 0x00: + rlc_r(B); + break; + case 0x01: + rlc_r(C); + break; + case 0x02: + rlc_r(D); + break; + case 0x03: + rlc_r(E); + break; + case 0x04: + rlc_r(H); + break; + case 0x05: + rlc_r(L); + break; + //rlc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x06: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF | (CF >> 8); + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x07: + rlc_r(A); + break; + case 0x08: + rrc_r(B); + break; + case 0x09: + rrc_r(C); + break; + case 0x0A: + rrc_r(D); + break; + case 0x0B: + rrc_r(E); + break; + case 0x0C: + rrc_r(H); + break; + case 0x0D: + rrc_r(L); + break; + //rrc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x0E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + CF = ZF << 8; + + WRITE(addr, (ZF | CF) >> 1 & 0xFF); + + HF2 = 0; + } + break; + case 0x0F: + rrc_r(A); + break; + case 0x10: + rl_r(B); + break; + case 0x11: + rl_r(C); + break; + case 0x12: + rl_r(D); + break; + case 0x13: + rl_r(E); + break; + case 0x14: + rl_r(H); + break; + case 0x15: + rl_r(L); + break; + //rl (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left thorugh CF, store old bit7 in CF, old CF value becoms bit0. Reset SF and HCF. Check ZF: + case 0x16: + { + const unsigned addr = HL(); + const unsigned oldcf = CF >> 8 & 1; + + READ(CF, addr); + CF <<= 1; + + ZF = CF | oldcf; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x17: + rl_r(A); + break; + case 0x18: + rr_r(B); + break; + case 0x19: + rr_r(C); + break; + case 0x1A: + rr_r(D); + break; + case 0x1B: + rr_r(E); + break; + case 0x1C: + rr_r(H); + break; + case 0x1D: + rr_r(L); + break; + //rr (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right thorugh CF, store old bit0 in CF, old CF value becoms bit7. Reset SF and HCF. Check ZF: + case 0x1E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + const unsigned oldcf = CF & 0x100; + CF = ZF << 8; + ZF = (ZF | oldcf) >> 1; + + WRITE(addr, ZF); + + HF2 = 0; + } + break; + case 0x1F: + rr_r(A); + break; + case 0x20: + sla_r(B); + break; + case 0x21: + sla_r(C); + break; + case 0x22: + sla_r(D); + break; + case 0x23: + sla_r(E); + break; + case 0x24: + sla_r(H); + break; + case 0x25: + sla_r(L); + break; + //sla (hl) (16 cycles): + //Shift 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x26: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x27: + sla_r(A); + break; + case 0x28: + sra_r(B); + break; + case 0x29: + sra_r(C); + break; + case 0x2A: + sra_r(D); + break; + case 0x2B: + sra_r(E); + break; + case 0x2C: + sra_r(H); + break; + case 0x2D: + sra_r(L); + break; + //sra (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF, bit7=old bit7. Reset SF and HCF. Check ZF: + case 0x2E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF | (CF & 0x80)); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x2F: + sra_r(A); + break; + case 0x30: + swap_r(B); + break; + case 0x31: + swap_r(C); + break; + case 0x32: + swap_r(D); + break; + case 0x33: + swap_r(E); + break; + case 0x34: + swap_r(H); + break; + case 0x35: + swap_r(L); + break; + //swap (hl) (16 cycles): + //Swap upper and lower nibbles of 8-bit value stored at address in HL, reset flags, check zero flag: + case 0x36: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + WRITE(addr, (ZF << 4 | ZF >> 4) & 0xFF); + + CF = HF2 = 0; + } + break; + case 0x37: + swap_r(A); + break; + case 0x38: + srl_r(B); + break; + case 0x39: + srl_r(C); + break; + case 0x3A: + srl_r(D); + break; + case 0x3B: + srl_r(E); + break; + case 0x3C: + srl_r(H); + break; + case 0x3D: + srl_r(L); + break; + //srl (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x3E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x3F: + srl_r(A); + break; + case 0x40: + bit0_u8(B); + break; + case 0x41: + bit0_u8(C); + break; + case 0x42: + bit0_u8(D); + break; + case 0x43: + bit0_u8(E); + break; + case 0x44: + bit0_u8(H); + break; + case 0x45: + bit0_u8(L); + break; + case 0x46: + { + unsigned data; + + READ(data, HL()); + + bit0_u8(data); + } + break; + case 0x47: + bit0_u8(A); + break; + case 0x48: + bit1_u8(B); + break; + case 0x49: + bit1_u8(C); + break; + case 0x4A: + bit1_u8(D); + break; + case 0x4B: + bit1_u8(E); + break; + case 0x4C: + bit1_u8(H); + break; + case 0x4D: + bit1_u8(L); + break; + case 0x4E: + { + unsigned data; + + READ(data, HL()); + + bit1_u8(data); + } + break; + case 0x4F: + bit1_u8(A); + break; + case 0x50: + bit2_u8(B); + break; + case 0x51: + bit2_u8(C); + break; + case 0x52: + bit2_u8(D); + break; + case 0x53: + bit2_u8(E); + break; + case 0x54: + bit2_u8(H); + break; + case 0x55: + bit2_u8(L); + break; + case 0x56: + { + unsigned data; + + READ(data, HL()); + + bit2_u8(data); + } + break; + case 0x57: + bit2_u8(A); + break; + case 0x58: + bit3_u8(B); + break; + case 0x59: + bit3_u8(C); + break; + case 0x5A: + bit3_u8(D); + break; + case 0x5B: + bit3_u8(E); + break; + case 0x5C: + bit3_u8(H); + break; + case 0x5D: + bit3_u8(L); + break; + case 0x5E: + { + unsigned data; + + READ(data, HL()); + + bit3_u8(data); + } + break; + case 0x5F: + bit3_u8(A); + break; + case 0x60: + bit4_u8(B); + break; + case 0x61: + bit4_u8(C); + break; + case 0x62: + bit4_u8(D); + break; + case 0x63: + bit4_u8(E); + break; + case 0x64: + bit4_u8(H); + break; + case 0x65: + bit4_u8(L); + break; + case 0x66: + { + unsigned data; + + READ(data, HL()); + + bit4_u8(data); + } + break; + case 0x67: + bit4_u8(A); + break; + case 0x68: + bit5_u8(B); + break; + case 0x69: + bit5_u8(C); + break; + case 0x6A: + bit5_u8(D); + break; + case 0x6B: + bit5_u8(E); + break; + case 0x6C: + bit5_u8(H); + break; + case 0x6D: + bit5_u8(L); + break; + case 0x6E: + { + unsigned data; + + READ(data, HL()); + + bit5_u8(data); + } + break; + case 0x6F: + bit5_u8(A); + break; + case 0x70: + bit6_u8(B); + break; + case 0x71: + bit6_u8(C); + break; + case 0x72: + bit6_u8(D); + break; + case 0x73: + bit6_u8(E); + break; + case 0x74: + bit6_u8(H); + break; + case 0x75: + bit6_u8(L); + break; + case 0x76: + { + unsigned data; + + READ(data, HL()); + + bit6_u8(data); + } + break; + case 0x77: + bit6_u8(A); + break; + case 0x78: + bit7_u8(B); + break; + case 0x79: + bit7_u8(C); + break; + case 0x7A: + bit7_u8(D); + break; + case 0x7B: + bit7_u8(E); + break; + case 0x7C: + bit7_u8(H); + break; + case 0x7D: + bit7_u8(L); + break; + case 0x7E: + { + unsigned data; + + READ(data, HL()); + + bit7_u8(data); + } + break; + case 0x7F: + bit7_u8(A); + break; + case 0x80: + res0_r(B); + break; + case 0x81: + res0_r(C); + break; + case 0x82: + res0_r(D); + break; + case 0x83: + res0_r(E); + break; + case 0x84: + res0_r(H); + break; + case 0x85: + res0_r(L); + break; + case 0x86: + resn_mem_hl(0); + break; + case 0x87: + res0_r(A); + break; + case 0x88: + res1_r(B); + break; + case 0x89: + res1_r(C); + break; + case 0x8A: + res1_r(D); + break; + case 0x8B: + res1_r(E); + break; + case 0x8C: + res1_r(H); + break; + case 0x8D: + res1_r(L); + break; + case 0x8E: + resn_mem_hl(1); + break; + case 0x8F: + res1_r(A); + break; + case 0x90: + res2_r(B); + break; + case 0x91: + res2_r(C); + break; + case 0x92: + res2_r(D); + break; + case 0x93: + res2_r(E); + break; + case 0x94: + res2_r(H); + break; + case 0x95: + res2_r(L); + break; + case 0x96: + resn_mem_hl(2); + break; + case 0x97: + res2_r(A); + break; + case 0x98: + res3_r(B); + break; + case 0x99: + res3_r(C); + break; + case 0x9A: + res3_r(D); + break; + case 0x9B: + res3_r(E); + break; + case 0x9C: + res3_r(H); + break; + case 0x9D: + res3_r(L); + break; + case 0x9E: + resn_mem_hl(3); + break; + case 0x9F: + res3_r(A); + break; + case 0xA0: + res4_r(B); + break; + case 0xA1: + res4_r(C); + break; + case 0xA2: + res4_r(D); + break; + case 0xA3: + res4_r(E); + break; + case 0xA4: + res4_r(H); + break; + case 0xA5: + res4_r(L); + break; + case 0xA6: + resn_mem_hl(4); + break; + case 0xA7: + res4_r(A); + break; + case 0xA8: + res5_r(B); + break; + case 0xA9: + res5_r(C); + break; + case 0xAA: + res5_r(D); + break; + case 0xAB: + res5_r(E); + break; + case 0xAC: + res5_r(H); + break; + case 0xAD: + res5_r(L); + break; + case 0xAE: + resn_mem_hl(5); + break; + case 0xAF: + res5_r(A); + break; + case 0xB0: + res6_r(B); + break; + case 0xB1: + res6_r(C); + break; + case 0xB2: + res6_r(D); + break; + case 0xB3: + res6_r(E); + break; + case 0xB4: + res6_r(H); + break; + case 0xB5: + res6_r(L); + break; + case 0xB6: + resn_mem_hl(6); + break; + case 0xB7: + res6_r(A); + break; + case 0xB8: + res7_r(B); + break; + case 0xB9: + res7_r(C); + break; + case 0xBA: + res7_r(D); + break; + case 0xBB: + res7_r(E); + break; + case 0xBC: + res7_r(H); + break; + case 0xBD: + res7_r(L); + break; + case 0xBE: + resn_mem_hl(7); + break; + case 0xBF: + res7_r(A); + break; + case 0xC0: + set0_r(B); + break; + case 0xC1: + set0_r(C); + break; + case 0xC2: + set0_r(D); + break; + case 0xC3: + set0_r(E); + break; + case 0xC4: + set0_r(H); + break; + case 0xC5: + set0_r(L); + break; + case 0xC6: + setn_mem_hl(0); + break; + case 0xC7: + set0_r(A); + break; + case 0xC8: + set1_r(B); + break; + case 0xC9: + set1_r(C); + break; + case 0xCA: + set1_r(D); + break; + case 0xCB: + set1_r(E); + break; + case 0xCC: + set1_r(H); + break; + case 0xCD: + set1_r(L); + break; + case 0xCE: + setn_mem_hl(1); + break; + case 0xCF: + set1_r(A); + break; + case 0xD0: + set2_r(B); + break; + case 0xD1: + set2_r(C); + break; + case 0xD2: + set2_r(D); + break; + case 0xD3: + set2_r(E); + break; + case 0xD4: + set2_r(H); + break; + case 0xD5: + set2_r(L); + break; + case 0xD6: + setn_mem_hl(2); + break; + case 0xD7: + set2_r(A); + break; + case 0xD8: + set3_r(B); + break; + case 0xD9: + set3_r(C); + break; + case 0xDA: + set3_r(D); + break; + case 0xDB: + set3_r(E); + break; + case 0xDC: + set3_r(H); + break; + case 0xDD: + set3_r(L); + break; + case 0xDE: + setn_mem_hl(3); + break; + case 0xDF: + set3_r(A); + break; + case 0xE0: + set4_r(B); + break; + case 0xE1: + set4_r(C); + break; + case 0xE2: + set4_r(D); + break; + case 0xE3: + set4_r(E); + break; + case 0xE4: + set4_r(H); + break; + case 0xE5: + set4_r(L); + break; + case 0xE6: + setn_mem_hl(4); + break; + case 0xE7: + set4_r(A); + break; + case 0xE8: + set5_r(B); + break; + case 0xE9: + set5_r(C); + break; + case 0xEA: + set5_r(D); + break; + case 0xEB: + set5_r(E); + break; + case 0xEC: + set5_r(H); + break; + case 0xED: + set5_r(L); + break; + case 0xEE: + setn_mem_hl(5); + break; + case 0xEF: + set5_r(A); + break; + case 0xF0: + set6_r(B); + break; + case 0xF1: + set6_r(C); + break; + case 0xF2: + set6_r(D); + break; + case 0xF3: + set6_r(E); + break; + case 0xF4: + set6_r(H); + break; + case 0xF5: + set6_r(L); + break; + case 0xF6: + setn_mem_hl(6); + break; + case 0xF7: + set6_r(A); + break; + case 0xF8: + set7_r(B); + break; + case 0xF9: + set7_r(C); + break; + case 0xFA: + set7_r(D); + break; + case 0xFB: + set7_r(E); + break; + case 0xFC: + set7_r(H); + break; + case 0xFD: + set7_r(L); + break; + case 0xFE: + setn_mem_hl(7); + break; + case 0xFF: + set7_r(A); + break; +// default: break; + } + break; + + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is set: + case 0xCC: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xCD: + call_nn(); + break; + case 0xCE: + { + unsigned data; + + PC_READ(data); + + adc_a_u8(data); + } + break; + case 0xCF: + rst_n(0x08); + break; + + //ret nc (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is unset: + case 0xD0: + cycleCounter += 4; + + if (!(CF & 0x100)) { + ret(); + } + + break; + + case 0xD1: + pop_rr(D, E); + break; + + //jp nc,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is unset: + case 0xD2: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + case 0xD3: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + //call nc,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is unset: + case 0xD4: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xD5: + push_rr(D, E); + break; + case 0xD6: + { + unsigned data; + + PC_READ(data); + + sub_a_u8(data); + } + break; + case 0xD7: + rst_n(0x10); + break; + + //ret c (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is set: + case 0xD8: + cycleCounter += 4; + + if (CF & 0x100) { + ret(); + } + + break; + + //reti (16 cycles): + //Pop two bytes from the stack and jump to that address, then enable interrupts: + case 0xD9: + { + unsigned l, h; + + pop_rr(h, l); + + memory.ei(cycleCounter); + + PC_MOD(h << 8 | l); + } + break; + + //jp c,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is set: + case 0xDA: //PC=( ((PC+2)*(1-CarryFlag())) + (((memory.read(PC+1)<<8)+memory.read(PC))*CarryFlag()) ); Cycles(12); break; + if (CF & 0x100) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDB: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is set: + case 0xDC: + if (CF & 0x100) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDD: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + case 0xDE: + { + unsigned data; + + PC_READ(data); + + sbc_a_u8(data); + } + break; + case 0xDF: + rst_n(0x18); + break; + + //ld ($FF00+n),a (12 cycles): + //Put value in A into address (0xFF00 + next byte in memory): + case 0xE0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_WRITE(0xFF00 | tmp, A); + } + break; + + case 0xE1: + pop_rr(H, L); + break; + + //ld ($FF00+C),a (8 ycles): + //Put A into address (0xFF00 + register C): + case 0xE2: + FF_WRITE(0xFF00 | C, A); + break; + case 0xE3: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xE4: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xE5: + push_rr(H, L); + break; + case 0xE6: + { + unsigned data; + + PC_READ(data); + + and_a_u8(data); + } + break; + case 0xE7: + rst_n(0x20); + break; + + //add sp,n (16 cycles): + //Add next (signed) byte in memory to SP, reset ZF and SF, check HCF and CF: + case 0xE8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + SP = CF; + CF >>= 8; + ZF = 1; + cycleCounter += 12; + }*/ + sp_plus_n(SP); + cycleCounter += 4; + break; + + //jp hl (4 cycles): + //Jump to address in hl: + case 0xE9: + PC = HL(); + break; + + //ld (nn),a (16 cycles): + //set memory at address given by the next 2 bytes to value in A: + //Incrementing PC before call, because of possible interrupt. + case 0xEA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + WRITE(h << 8 | l, A); + } + break; + + case 0xEB: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xEC: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xED: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xEE: + { + unsigned data; + + PC_READ(data); + + xor_a_u8(data); + } + break; + case 0xEF: + rst_n(0x28); + break; + + //ld a,($FF00+n) (12 cycles): + //Put value at address (0xFF00 + next byte in memory) into A: + case 0xF0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_READ(A, 0xFF00 | tmp); + } + break; + + case 0xF1: /*pop_rr(A, F); Cycles(12); break;*/ + { + unsigned F; + + pop_rr(A, F); + + FROM_F(F); + } + break; + + //ld a,($FF00+C) (8 cycles): + //Put value at address (0xFF00 + register C) into A: + case 0xF2: + FF_READ(A, 0xFF00 | C); + break; + + //di (4 cycles): + case 0xF3: + memory.di(); + break; + + case 0xF4: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xF5: /*push_rr(A, F); Cycles(16); break;*/ + calcHF(HF1, HF2); + + { + unsigned F = F(); + + push_rr(A, F); + } + break; + + case 0xF6: + { + unsigned data; + + PC_READ(data); + + or_a_u8(data); + } + break; + case 0xF7: + rst_n(0x30); + break; + + //ldhl sp,n (12 cycles): + //Put (sp+next (signed) byte in memory) into hl (unsets ZF and SF, may enable HF and CF): + case 0xF8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + L = CF; + CF >>= 8; + H = CF; + ZF = 1; + cycleCounter += 8; + }*/ + { + unsigned sum; + sp_plus_n(sum); + L = sum & 0xFF; + H = sum >> 8; + } + break; + + //ld sp,hl (8 cycles): + //Put value in HL into SP + case 0xF9: + SP = HL(); + cycleCounter += 4; + break; + + //ld a,(nn) (16 cycles): + //set A to value in memory at address given by the 2 next bytes. + case 0xFA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + READ(A, h << 8 | l); + } + break; + + //ei (4 cycles): + //Enable Interrupts after next instruction: + case 0xFB: + memory.ei(cycleCounter); + break; + + case 0xFC: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xFD: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xFE: + { + unsigned data; + + PC_READ(data); + + cp_a_u8(data); + } + break; + case 0xFF: + rst_n(0x38); + break; +// default: break; + } + } + + //PC_ = PC; + cycleCounter = memory.event(cycleCounter); + } + + //A_ = A; + cycleCounter_ = cycleCounter; +} + +void CPU::GetRegs(int *dest) +{ + dest[0] = PC; + dest[1] = SP; + dest[2] = A; + dest[3] = B; + dest[4] = C; + dest[5] = D; + dest[6] = E; + dest[7] = F(); + dest[8] = H; + dest[9] = L; +} + +void CPU::SetInterruptAddresses(int *addrs, int numAddrs) +{ + interruptAddresses = addrs; + numInterruptAddresses = numAddrs; +} + +int CPU::GetHitInterruptAddress() +{ + return hitInterruptAddress; +} + +SYNCFUNC(CPU) +{ + SSS(memory); + NSS(cycleCounter_); + NSS(PC); + NSS(SP); + NSS(HF1); + NSS(HF2); + NSS(ZF); + NSS(CF); + NSS(A); + NSS(B); + NSS(C); + NSS(D); + NSS(E); + NSS(H); + NSS(L); + NSS(skip); +} + +} diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index b36112f7ea..46dc592015 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -1,136 +1,147 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef CPU_H -#define CPU_H - -#include "memory.h" -#include "newstate.h" - -namespace gambatte { - -class CPU { - Memory memory; - - unsigned long cycleCounter_; - - unsigned short PC; - unsigned short SP; - - unsigned HF1, HF2, ZF, CF; - - unsigned char A, B, C, D, E, /*F,*/ H, L; - - bool skip; - - void process(unsigned long cycles); - - void (*tracecallback)(void *); - -public: - - CPU(); -// void halt(); - -// unsigned interrupt(unsigned address, unsigned cycleCounter); - - long runFor(unsigned long cycles); - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state); - void setLayers(unsigned mask) { memory.setLayers(mask); } - - void loadSavedata(const char *data) { memory.loadSavedata(data); } - int saveSavedataLength() {return memory.saveSavedataLength(); } - void saveSavedata(char *dest) { memory.saveSavedata(dest); } - - bool getMemoryArea(int which, unsigned char **data, int *length) { return memory.getMemoryArea(which, data, length); } - - void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { - memory.setVideoBuffer(videoBuf, pitch); - } - - void setInputGetter(unsigned (*getInput)()) { - memory.setInputGetter(getInput); - } - - void setReadCallback(void (*callback)(unsigned)) { - memory.setReadCallback(callback); - } - - void setWriteCallback(void (*callback)(unsigned)) { - memory.setWriteCallback(callback); - } - - void setExecCallback(void (*callback)(unsigned)) { - memory.setExecCallback(callback); - } - - void setCDCallback(CDCallback cdc) { - memory.setCDCallback(cdc); - } - - void setTraceCallback(void (*callback)(void *)) { - tracecallback = callback; - } - - void setScanlineCallback(void (*callback)(), int sl) { - memory.setScanlineCallback(callback, sl); - } - - void setRTCCallback(std::uint32_t (*callback)()) { - memory.setRTCCallback(callback); - } - - void setLinkCallback(void (*callback)()) { - memory.setLinkCallback(callback); - } - - int load(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat) { - return memory.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat); - } - - bool loaded() const { return memory.loaded(); } - const char * romTitle() const { return memory.romTitle(); } - - void setSoundBuffer(uint_least32_t *const buf) { memory.setSoundBuffer(buf); } - unsigned fillSoundBuffer() { return memory.fillSoundBuffer(cycleCounter_); } - - bool isCgb() const { return memory.isCgb(); } - - void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { - memory.setDmgPaletteColor(palNum, colorNum, rgb32); - } - - void setCgbPalette(unsigned *lut) { - memory.setCgbPalette(lut); - } - - //unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } - unsigned char ExternalRead(unsigned short addr) { return memory.peek(addr); } - void ExternalWrite(unsigned short addr, unsigned char val) { memory.write_nocb(addr, val, cycleCounter_); } - - int LinkStatus(int which) { return memory.LinkStatus(which); } - - void GetRegs(int *dest); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CPU_H +#define CPU_H + +#include "memory.h" +#include "newstate.h" + +namespace gambatte { + +class CPU { + Memory memory; + + unsigned long cycleCounter_; + + unsigned short PC; + unsigned short SP; + + unsigned HF1, HF2, ZF, CF; + + unsigned char A, B, C, D, E, /*F,*/ H, L; + + bool skip; + + int *interruptAddresses; + int numInterruptAddresses; + int hitInterruptAddress; + + void process(unsigned long cycles); + + void (*tracecallback)(void *); + +public: + + CPU(); +// void halt(); + +// unsigned interrupt(unsigned address, unsigned cycleCounter); + + long runFor(unsigned long cycles); + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state); + void setLayers(unsigned mask) { memory.setLayers(mask); } + + void loadSavedata(const char *data) { memory.loadSavedata(data); } + int saveSavedataLength() {return memory.saveSavedataLength(); } + void saveSavedata(char *dest) { memory.saveSavedata(dest); } + + bool getMemoryArea(int which, unsigned char **data, int *length) { return memory.getMemoryArea(which, data, length); } + + void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { + memory.setVideoBuffer(videoBuf, pitch); + } + + void setInputGetter(unsigned (*getInput)()) { + memory.setInputGetter(getInput); + } + + void setReadCallback(void (*callback)(unsigned)) { + memory.setReadCallback(callback); + } + + void setWriteCallback(void (*callback)(unsigned)) { + memory.setWriteCallback(callback); + } + + void setExecCallback(void (*callback)(unsigned)) { + memory.setExecCallback(callback); + } + + void setCDCallback(CDCallback cdc) { + memory.setCDCallback(cdc); + } + + void setTraceCallback(void (*callback)(void *)) { + tracecallback = callback; + } + + void setScanlineCallback(void (*callback)(), int sl) { + memory.setScanlineCallback(callback, sl); + } + + void setRTCCallback(std::uint32_t (*callback)()) { + memory.setRTCCallback(callback); + } + + void setLinkCallback(void(*callback)()) { + memory.setLinkCallback(callback); + } + + int load(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat) { + return memory.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat); + } + + bool loaded() const { return memory.loaded(); } + const char * romTitle() const { return memory.romTitle(); } + + void setSoundBuffer(uint_least32_t *const buf) { memory.setSoundBuffer(buf); } + unsigned fillSoundBuffer() { return memory.fillSoundBuffer(cycleCounter_); } + + bool isCgb() const { return memory.isCgb(); } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { + memory.setDmgPaletteColor(palNum, colorNum, rgb32); + } + + void setCgbPalette(unsigned *lut) { + memory.setCgbPalette(lut); + } + + unsigned char* cgbBiosBuffer() { return memory.cgbBiosBuffer(); } + unsigned char* dmgBiosBuffer() { return memory.dmgBiosBuffer(); } + bool gbIsCgb() { return memory.gbIsCgb(); } + + //unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } + unsigned char ExternalRead(unsigned short addr) { return memory.peek(addr); } + void ExternalWrite(unsigned short addr, unsigned char val) { memory.write_nocb(addr, val, cycleCounter_); } + + int LinkStatus(int which) { return memory.LinkStatus(which); } + + void GetRegs(int *dest); + + void SetInterruptAddresses(int *addrs, int numAddrs); + int GetHitInterruptAddress(); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index 5e9c59e63f..167e1093e1 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -26,12 +26,12 @@ namespace gambatte { struct GB::Priv { CPU cpu; - bool gbaCgbMode; + unsigned loadflags; unsigned layersMask; uint_least32_t vbuff[160*144]; - Priv() : gbaCgbMode(false), layersMask(LAYER_MASK_BG | LAYER_MASK_OBJ) + Priv() : loadflags(0), layersMask(LAYER_MASK_BG | LAYER_MASK_OBJ) { } @@ -81,7 +81,7 @@ void GB::blitTo(gambatte::uint_least32_t *videoBuf, int pitch) } } -void GB::reset(const std::uint32_t now) { +void GB::reset(const std::uint32_t now, const unsigned div) { if (p_->cpu.loaded()) { int length = p_->cpu.saveSavedataLength(); @@ -94,8 +94,7 @@ void GB::reset(const std::uint32_t now) { SaveState state; p_->cpu.setStatePtrs(state); - - setInitState(state, p_->cpu.isCgb(), p_->gbaCgbMode, now); + setInitState(state, !(p_->loadflags & FORCE_DMG), p_->loadflags & GBA_CGB, now, div); p_->cpu.loadState(state); if (length > 0) { @@ -141,16 +140,17 @@ void GB::setLinkCallback(void(*callback)()) { p_->cpu.setLinkCallback(callback); } -int GB::load(const char *romfiledata, unsigned romfilelength, const std::uint32_t now, const unsigned flags) { +int GB::load(const char *romfiledata, unsigned romfilelength, const std::uint32_t now, const unsigned flags, const unsigned div) { //if (p_->cpu.loaded()) // p_->cpu.saveSavedata(); const int failed = p_->cpu.load(romfiledata, romfilelength, flags & FORCE_DMG, flags & MULTICART_COMPAT); - + if (!failed) { SaveState state; p_->cpu.setStatePtrs(state); - setInitState(state, p_->cpu.isCgb(), p_->gbaCgbMode = flags & GBA_CGB, now); + p_->loadflags = flags; + setInitState(state, !(flags & FORCE_DMG), flags & GBA_CGB, now, div); p_->cpu.loadState(state); //p_->cpu.loadSavedata(); } @@ -158,6 +158,16 @@ int GB::load(const char *romfiledata, unsigned romfilelength, const std::uint32_ return failed; } +int GB::loadGBCBios(const char* biosfiledata) { + memcpy(p_->cpu.cgbBiosBuffer(), biosfiledata, 0x900); + return 0; +} + +int GB::loadDMGBios(const char* biosfiledata) { + memcpy(p_->cpu.dmgBiosBuffer(), biosfiledata, 0x100); + return 0; +} + bool GB::isCgb() const { return p_->cpu.isCgb(); } @@ -228,10 +238,20 @@ void GB::GetRegs(int *dest) { p_->cpu.GetRegs(dest); } +void GB::SetInterruptAddresses(int *addrs, int numAddrs) +{ + p_->cpu.SetInterruptAddresses(addrs, numAddrs); +} + +int GB::GetHitInterruptAddress() +{ + return p_->cpu.GetHitInterruptAddress(); +} + SYNCFUNC(GB) { SSS(p_->cpu); - NSS(p_->gbaCgbMode); + NSS(p_->loadflags); NSS(p_->vbuff); } diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index 5f11104d08..f8c488efcc 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1041,11 +1041,11 @@ static void setInitialCgbIoamhram(unsigned char *const ioamhram) { }; static const unsigned char ffxxDump[0x100] = { - 0xCF, 0x00, 0x7C, 0xFF, 0x44, 0x00, 0x00, 0xF8, + 0xCF, 0x00, 0x7C, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, - 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, + 0x80, 0x3F, 0x00, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, - 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, + 0xFF, 0x00, 0x00, 0xBF, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, @@ -1146,62 +1146,59 @@ static void setInitialDmgIoamhram(unsigned char *const ioamhram) { } // anon namespace -void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbMode, const std::uint32_t now) { +void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbMode, const std::uint32_t now, const unsigned div) { static const unsigned char cgbObjpDump[0x40] = { - 0x00, 0x00, 0xF2, 0xAB, - 0x61, 0xC2, 0xD9, 0xBA, - 0x88, 0x6E, 0xDD, 0x63, - 0x28, 0x27, 0xFB, 0x9F, - 0x35, 0x42, 0xD6, 0xD4, - 0x50, 0x48, 0x57, 0x5E, - 0x23, 0x3E, 0x3D, 0xCA, - 0x71, 0x21, 0x37, 0xC0, - 0xC6, 0xB3, 0xFB, 0xF9, - 0x08, 0x00, 0x8D, 0x29, - 0xA3, 0x20, 0xDB, 0x87, - 0x62, 0x05, 0x5D, 0xD4, - 0x0E, 0x08, 0xFE, 0xAF, - 0x20, 0x02, 0xD7, 0xFF, - 0x07, 0x6A, 0x55, 0xEC, + 0x00, 0x00, 0xF2, 0xAB, + 0x61, 0xC2, 0xD9, 0xBA, + 0x88, 0x6E, 0xDD, 0x63, + 0x28, 0x27, 0xFB, 0x9F, + 0x35, 0x42, 0xD6, 0xD4, + 0x50, 0x48, 0x57, 0x5E, + 0x23, 0x3E, 0x3D, 0xCA, + 0x71, 0x21, 0x37, 0xC0, + 0xC6, 0xB3, 0xFB, 0xF9, + 0x08, 0x00, 0x8D, 0x29, + 0xA3, 0x20, 0xDB, 0x87, + 0x62, 0x05, 0x5D, 0xD4, + 0x0E, 0x08, 0xFE, 0xAF, + 0x20, 0x02, 0xD7, 0xFF, + 0x07, 0x6A, 0x55, 0xEC, 0x83, 0x40, 0x0B, 0x77 }; - - state.cpu.PC = 0x100; - state.cpu.SP = 0xFFFE; - state.cpu.A = cgb * 0x10 | 0x01; - state.cpu.B = cgb & gbaCgbMode; - state.cpu.C = 0x13; - state.cpu.D = 0x00; - state.cpu.E = 0xD8; - state.cpu.F = 0xB0; - state.cpu.H = 0x01; - state.cpu.L = 0x4D; - state.cpu.skip = false; - setInitialVram(state.mem.vram.ptr, cgb); - state.cpu.cycleCounter = cgb ? 0x102A0 : 0x102A0 + 0x8D2C; + state.cpu.cycleCounter = 8; + state.cpu.PC = 0; + state.cpu.SP = 0; + state.cpu.A = 0; + state.cpu.B = 0; + state.cpu.C = 0; + state.cpu.D = 0; + state.cpu.E = 0; + state.cpu.F = 0; + state.cpu.H = 0; + state.cpu.L = 0; + state.cpu.skip = false; + state.mem.biosMode = true; + state.mem.cgbSwitching = false; + state.mem.agbMode = gbaCgbMode; std::memset(state.mem.sram.ptr, 0xFF, state.mem.sram.getSz()); + + setInitialVram(state.mem.vram.ptr, cgb); if (cgb) { setInitialCgbWram(state.mem.wram.ptr); - } - else { - setInitialDmgWram(state.mem.wram.ptr); - } - - if (cgb) { setInitialCgbIoamhram(state.mem.ioamhram.ptr); - } - else { + } else { + setInitialDmgWram(state.mem.wram.ptr); setInitialDmgIoamhram(state.mem.ioamhram.ptr); } - - state.mem.ioamhram.ptr[0x140] = 0x91; - state.mem.ioamhram.ptr[0x104] = 0x1C; + + state.mem.ioamhram.ptr[0x104] = 0; + state.mem.ioamhram.ptr[0x140] = 0; state.mem.ioamhram.ptr[0x144] = 0x00; - - state.mem.divLastUpdate = 0; + + state.mem.divLastUpdate = 0 - div; state.mem.timaLastUpdate = 0; state.mem.tmatime = DISABLED_TIME; state.mem.nextSerialtime = DISABLED_TIME; @@ -1218,29 +1215,30 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.mem.enableRam = false; state.mem.rambankMode = false; state.mem.hdmaTransfer = false; + state.mem.gbIsCgb = cgb; - + for (unsigned i = 0x00; i < 0x40; i += 0x02) { - state.ppu.bgpData.ptr[i] = 0xFF; + state.ppu.bgpData.ptr[i ] = 0xFF; state.ppu.bgpData.ptr[i + 1] = 0x7F; } - + std::memcpy(state.ppu.objpData.ptr, cgbObjpDump, sizeof(cgbObjpDump)); - + if (!cgb) { state.ppu.bgpData.ptr[0] = state.mem.ioamhram.get()[0x147]; state.ppu.objpData.ptr[0] = state.mem.ioamhram.get()[0x148]; state.ppu.objpData.ptr[1] = state.mem.ioamhram.get()[0x149]; } - + for (unsigned pos = 0; pos < 80; ++pos) state.ppu.oamReaderBuf.ptr[pos] = state.mem.ioamhram.ptr[(pos * 2 & ~3) | (pos & 1)]; - + std::fill_n(state.ppu.oamReaderSzbuf.ptr, 40, false); std::memset(state.ppu.spAttribList, 0, sizeof(state.ppu.spAttribList)); std::memset(state.ppu.spByte0List, 0, sizeof(state.ppu.spByte0List)); std::memset(state.ppu.spByte1List, 0, sizeof(state.ppu.spByte1List)); - state.ppu.videoCycles = cgb ? 144 * 456ul + 164 : 153 * 456ul + 396; + state.ppu.videoCycles = 0; state.ppu.enableDisplayM0Time = state.cpu.cycleCounter; state.ppu.winYPos = 0xFF; state.ppu.xpos = 0; @@ -1254,7 +1252,7 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.ppu.state = 0; state.ppu.nextSprite = 0; state.ppu.currentSprite = 0; - state.ppu.lyc = state.mem.ioamhram.get()[0x145]; + state.ppu.lyc = state.mem.ioamhram.get()[0x145]; state.ppu.m0lyc = state.mem.ioamhram.get()[0x145]; state.ppu.weMaster = false; state.ppu.winDrawState = 0; @@ -1263,36 +1261,38 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.ppu.nextM0Irq = 0; state.ppu.oldWy = state.mem.ioamhram.get()[0x14A]; state.ppu.pendingLcdstatIrq = false; + state.ppu.isCgb = cgb; - state.spu.cycleCounter = 0x1000 | (state.cpu.cycleCounter >> 1 & 0xFFF); // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. - + + state.spu.cycleCounter = 0; // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. + state.spu.ch1.sweep.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch1.sweep.shadow = 0; state.spu.ch1.sweep.nr0 = 0; state.spu.ch1.sweep.negging = false; - state.spu.ch1.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + state.spu.ch1.duty.nextPosUpdate = (state.spu.cycleCounter & ~1ul) + 37 * 2; state.spu.ch1.duty.nr3 = 0; state.spu.ch1.duty.pos = 0; state.spu.ch1.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch1.env.volume = 0; state.spu.ch1.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch1.lcounter.lengthCounter = 0x40; + state.spu.ch1.lcounter.lengthCounter = 0; state.spu.ch1.nr4 = 0; state.spu.ch1.master = true; - - state.spu.ch2.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + + state.spu.ch2.duty.nextPosUpdate = SoundUnit::COUNTER_DISABLED; state.spu.ch2.duty.nr3 = 0; state.spu.ch2.duty.pos = 0; - state.spu.ch2.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch2.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch2.env.volume = 0; state.spu.ch2.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch2.lcounter.lengthCounter = 0x40; + state.spu.ch2.lcounter.lengthCounter = 0; state.spu.ch2.nr4 = 0; state.spu.ch2.master = false; - + for (unsigned i = 0; i < 0x10; ++i) state.spu.ch3.waveRam.ptr[i] = state.mem.ioamhram.get()[0x130 + i]; - + state.spu.ch3.lcounter.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch3.lcounter.lengthCounter = 0x100; state.spu.ch3.waveCounter = SoundUnit::COUNTER_DISABLED; @@ -1302,16 +1302,16 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.spu.ch3.wavePos = 0; state.spu.ch3.sampleBuf = 0; state.spu.ch3.master = false; - + state.spu.ch4.lfsr.counter = state.spu.cycleCounter + 4; state.spu.ch4.lfsr.reg = 0xFF; - state.spu.ch4.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch4.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch4.env.volume = 0; state.spu.ch4.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch4.lcounter.lengthCounter = 0x40; + state.spu.ch4.lcounter.lengthCounter = 0; state.spu.ch4.nr4 = 0; state.spu.ch4.master = false; - + state.rtc.baseTime = now; state.rtc.haltTime = state.rtc.baseTime; state.rtc.dataDh = 0; diff --git a/libgambatte/src/initstate.h b/libgambatte/src/initstate.h index 8d8ed5aaf6..dd20a3d07a 100644 --- a/libgambatte/src/initstate.h +++ b/libgambatte/src/initstate.h @@ -22,7 +22,7 @@ #include namespace gambatte { -void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, std::uint32_t now); +void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, std::uint32_t now, unsigned div); } #endif diff --git a/libgambatte/src/insertion_sort.h b/libgambatte/src/insertion_sort.h index 939ba07442..f6a3c991a4 100644 --- a/libgambatte/src/insertion_sort.h +++ b/libgambatte/src/insertion_sort.h @@ -1,51 +1,51 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aams * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#ifndef INSERTION_SORT_H -#define INSERTION_SORT_H - -#include - -template -void insertionSort(T *const start, T *const end, Less less) { - if (start >= end) - return; - - T *a = start; - - while (++a < end) { - const T e = *a; - - T *b = a; - - while (b != start && less(e, *(b - 1))) { - *b = *(b - 1); - b = b - 1; - } - - *b = e; - } -} - -template -inline void insertionSort(T *const start, T *const end) { - insertionSort(start, end, std::less()); -} - -#endif /*INSERTION_SORT_H*/ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aams * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef INSERTION_SORT_H +#define INSERTION_SORT_H + +#include + +template +void insertionSort(T *const start, T *const end, Less less) { + if (start >= end) + return; + + T *a = start; + + while (++a < end) { + const T e = *a; + + T *b = a; + + while (b != start && less(e, *(b - 1))) { + *b = *(b - 1); + b = b - 1; + } + + *b = e; + } +} + +template +inline void insertionSort(T *const start, T *const end) { + insertionSort(start, end, std::less()); +} + +#endif /*INSERTION_SORT_H*/ diff --git a/libgambatte/src/interruptrequester.cpp b/libgambatte/src/interruptrequester.cpp index 5ed7869c39..c656ad2368 100644 --- a/libgambatte/src/interruptrequester.cpp +++ b/libgambatte/src/interruptrequester.cpp @@ -94,13 +94,13 @@ void InterruptRequester::setIfreg(const unsigned ifreg) { eventTimes.setValue(pendingIrqs() ? minIntTime : static_cast(DISABLED_TIME)); } -SYNCFUNC(InterruptRequester) -{ - SSS(eventTimes); - NSS(minIntTime); - NSS(ifreg_); - NSS(iereg_); - NSS(intFlags.flags_); -} +SYNCFUNC(InterruptRequester) +{ + SSS(eventTimes); + NSS(minIntTime); + NSS(ifreg_); + NSS(iereg_); + NSS(intFlags.flags_); +} } diff --git a/libgambatte/src/interruptrequester.h b/libgambatte/src/interruptrequester.h index c99efc73f6..8d22e35afb 100644 --- a/libgambatte/src/interruptrequester.h +++ b/libgambatte/src/interruptrequester.h @@ -21,7 +21,7 @@ #include "counterdef.h" #include "minkeeper.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { struct SaveState; @@ -62,6 +62,7 @@ public: void resetCc(unsigned long oldCc, unsigned long newCc); unsigned ifreg() const { return ifreg_; } + unsigned iereg() const { return iereg_; } unsigned pendingIrqs() const { return ifreg_ & iereg_; } bool ime() const { return intFlags.ime(); } bool halted() const { return intFlags.halted(); } @@ -81,7 +82,7 @@ public: void setEventTime(const MemEventId id, unsigned long value) { eventTimes.setValue(id, value); } unsigned long eventTime(MemEventId id) const { return eventTimes.value(id); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; inline void flagHdmaReq(InterruptRequester *const intreq) { intreq->setEventTime(0); } diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index abee7c2042..e062fb0c96 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -1,753 +1,750 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "cartridge.h" -#include "../savestate.h" -#include -#include -#include -#include - -namespace gambatte { - -namespace { - -static unsigned toMulti64Rombank(const unsigned rombank) { - return (rombank >> 1 & 0x30) | (rombank & 0xF); -} - -class DefaultMbc : public Mbc { -public: - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { - return (addr< 0x4000) == (bank == 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - } -}; - -class Mbc0 : public DefaultMbc { - MemPtrs &memptrs; - bool enableRam; - -public: - explicit Mbc0(MemPtrs &memptrs) - : memptrs(memptrs), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - if (P < 0x2000) { - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - enableRam = ss.enableRam; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(enableRam); - } -}; - -static inline unsigned rambanks(const MemPtrs &memptrs) { - return static_cast(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000; -} - -static inline unsigned rombanks(const MemPtrs &memptrs) { - return static_cast(memptrs.romdataend() - memptrs.romdata() ) / 0x4000; -} - -class Mbc1 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - bool rambankMode; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } - void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } - -public: - explicit Mbc1(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false), - rambankMode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = rambankMode ? data & 0x1F : (rombank & 0x60) | (data & 0x1F); - setRombank(); - break; - case 2: - if (rambankMode) { - rambank = data & 3; - setRambank(); - } else { - rombank = (data << 5 & 0x60) | (rombank & 0x1F); - setRombank(); - } - - break; - case 3: - // Pretty sure this should take effect immediately, but I have a policy not to change old behavior - // unless I have something (eg. a verified test or a game) that justifies it. - rambankMode = data & 1; - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - ss.rambankMode = rambankMode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - rambankMode = ss.rambankMode; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - NSS(rambankMode); - } -}; - -class Mbc1Multi64 : public Mbc { - MemPtrs &memptrs; - unsigned char rombank; - bool enableRam; - bool rombank0Mode; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } - - void setRombank() const { - if (rombank0Mode) { - const unsigned rb = toMulti64Rombank(rombank); - memptrs.setRombank0(rb & 0x30); - memptrs.setRombank(adjustedRombank(rb)); - } else { - memptrs.setRombank0(0); - memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); - } - } - -public: - explicit Mbc1Multi64(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - enableRam(false), - rombank0Mode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - break; - case 1: - rombank = (rombank & 0x60) | (data & 0x1F); - memptrs.setRombank(adjustedRombank(rombank0Mode ? toMulti64Rombank(rombank) : rombank & (rombanks(memptrs) - 1))); - break; - case 2: - rombank = (data << 5 & 0x60) | (rombank & 0x1F); - setRombank(); - break; - case 3: - rombank0Mode = data & 1; - setRombank(); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.enableRam = enableRam; - ss.rambankMode = rombank0Mode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - enableRam = ss.enableRam; - rombank0Mode = ss.rambankMode; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - setRombank(); - } - - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { - return (addr < 0x4000) == ((bank & 0xF) == 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(enableRam); - NSS(rombank0Mode); - } -}; - -class Mbc2 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - bool enableRam; - -public: - explicit Mbc2(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P & 0x6100) { - case 0x0000: - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - break; - case 0x2100: - rombank = data & 0xF; - memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - enableRam = ss.enableRam; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(enableRam); - } -}; - -class Mbc3 : public DefaultMbc { - MemPtrs &memptrs; - Rtc *const rtc; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x7F ? bank : bank | 1; } - void setRambank() const { - unsigned flags = enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0; - - if (rtc) { - rtc->set(enableRam, rambank); - - if (rtc->getActive()) - flags |= MemPtrs::RTC_EN; - } - - memptrs.setRambank(flags, rambank & (rambanks(memptrs) - 1)); - } - // we adjust the rombank before masking with size? this seems correct, as how would the mbc - // know that high rom address outputs were not connected - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank) & (rombanks(memptrs) - 1)); } - -public: - Mbc3(MemPtrs &memptrs, Rtc *const rtc) - : memptrs(memptrs), - rtc(rtc), - rombank(1), - rambank(0), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = data & 0x7F; - setRombank(); - break; - case 2: - rambank = data; - setRambank(); - break; - case 3: - if (rtc) - rtc->latch(data); - - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - } -}; - -class HuC1 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - bool rambankMode; - - void setRambank() const { - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : MemPtrs::READ_EN, - rambankMode ? rambank & (rambanks(memptrs) - 1) : 0); - } - - void setRombank() const { memptrs.setRombank((rambankMode ? rombank : rambank << 6 | rombank) & (rombanks(memptrs) - 1)); } - -public: - explicit HuC1(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false), - rambankMode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = data & 0x3F; - setRombank(); - break; - case 2: - rambank = data & 3; - rambankMode ? setRambank() : setRombank(); - break; - case 3: - rambankMode = data & 1; - setRambank(); - setRombank(); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - ss.rambankMode = rambankMode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - rambankMode = ss.rambankMode; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - NSS(rambankMode); - } -}; - -class Mbc5 : public DefaultMbc { - MemPtrs &memptrs; - unsigned short rombank; - unsigned char rambank; - bool enableRam; - - static unsigned adjustedRombank(const unsigned bank) { return bank; } - void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } - -public: - explicit Mbc5(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = P < 0x3000 ? (rombank & 0x100) | data - : (data << 8 & 0x100) | (rombank & 0xFF); - setRombank(); - break; - case 2: - rambank = data & 0xF; - setRambank(); - break; - case 3: - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - } -}; - -static bool hasRtc(const unsigned headerByte0x147) { - switch (headerByte0x147) { - case 0x0F: - case 0x10: return true; - default: return false; - } -} - -} - -void Cartridge::setStatePtrs(SaveState &state) { - state.mem.vram.set(memptrs.vramdata(), memptrs.vramdataend() - memptrs.vramdata()); - state.mem.sram.set(memptrs.rambankdata(), memptrs.rambankdataend() - memptrs.rambankdata()); - state.mem.wram.set(memptrs.wramdata(0), memptrs.wramdataend() - memptrs.wramdata(0)); -} - -void Cartridge::loadState(const SaveState &state) { - rtc.loadState(state); - mbc->loadState(state.mem); -} - -static void enforce8bit(unsigned char *data, unsigned long sz) { - if (static_cast(0x100)) - while (sz--) - *data++ &= 0xFF; -} - -static unsigned pow2ceil(unsigned n) { - --n; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - ++n; - - return n; -} - -int Cartridge::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { - //const std::auto_ptr rom(newFileInstance(romfile)); - - //if (rom->fail()) - // return -1; - - unsigned rambanks = 1; - unsigned rombanks = 2; - bool cgb = false; - enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1 } type = PLAIN; - - { - unsigned char header[0x150]; - //rom->read(reinterpret_cast(header), sizeof header); - if (romfilelength >= sizeof header) - std::memcpy(header, romfiledata, sizeof header); - else - return -1; - - switch (header[0x0147]) { - case 0x00: std::puts("Plain ROM loaded."); type = PLAIN; break; - case 0x01: std::puts("MBC1 ROM loaded."); type = MBC1; break; - case 0x02: std::puts("MBC1 ROM+RAM loaded."); type = MBC1; break; - case 0x03: std::puts("MBC1 ROM+RAM+BATTERY loaded."); type = MBC1; break; - case 0x05: std::puts("MBC2 ROM loaded."); type = MBC2; break; - case 0x06: std::puts("MBC2 ROM+BATTERY loaded."); type = MBC2; break; - case 0x08: std::puts("Plain ROM with additional RAM loaded."); type = PLAIN; break; - case 0x09: std::puts("Plain ROM with additional RAM and Battery loaded."); type = PLAIN; break; - case 0x0B: std::puts("MM01 ROM not supported."); return -1; - case 0x0C: std::puts("MM01 ROM not supported."); return -1; - case 0x0D: std::puts("MM01 ROM not supported."); return -1; - case 0x0F: std::puts("MBC3 ROM+TIMER+BATTERY loaded."); type = MBC3; break; - case 0x10: std::puts("MBC3 ROM+TIMER+RAM+BATTERY loaded."); type = MBC3; break; - case 0x11: std::puts("MBC3 ROM loaded."); type = MBC3; break; - case 0x12: std::puts("MBC3 ROM+RAM loaded."); type = MBC3; break; - case 0x13: std::puts("MBC3 ROM+RAM+BATTERY loaded."); type = MBC3; break; - case 0x15: std::puts("MBC4 ROM not supported."); return -1; - case 0x16: std::puts("MBC4 ROM not supported."); return -1; - case 0x17: std::puts("MBC4 ROM not supported."); return -1; - case 0x19: std::puts("MBC5 ROM loaded."); type = MBC5; break; - case 0x1A: std::puts("MBC5 ROM+RAM loaded."); type = MBC5; break; - case 0x1B: std::puts("MBC5 ROM+RAM+BATTERY loaded."); type = MBC5; break; - case 0x1C: std::puts("MBC5+RUMBLE ROM not supported."); type = MBC5; break; - case 0x1D: std::puts("MBC5+RUMBLE+RAM ROM not suported."); type = MBC5; break; - case 0x1E: std::puts("MBC5+RUMBLE+RAM+BATTERY ROM not supported."); type = MBC5; break; - case 0xFC: std::puts("Pocket Camera ROM not supported."); return -1; - case 0xFD: std::puts("Bandai TAMA5 ROM not supported."); return -1; - case 0xFE: std::puts("HuC3 ROM not supported."); return -1; - case 0xFF: std::puts("HuC1 ROM+RAM+BATTERY loaded."); type = HUC1; break; - default: std::puts("Wrong data-format, corrupt or unsupported ROM."); return -1; - } - - /*switch (header[0x0148]) { - case 0x00: rombanks = 2; break; - case 0x01: rombanks = 4; break; - case 0x02: rombanks = 8; break; - case 0x03: rombanks = 16; break; - case 0x04: rombanks = 32; break; - case 0x05: rombanks = 64; break; - case 0x06: rombanks = 128; break; - case 0x07: rombanks = 256; break; - case 0x08: rombanks = 512; break; - case 0x52: rombanks = 72; break; - case 0x53: rombanks = 80; break; - case 0x54: rombanks = 96; break; - default: return -1; - } - - std::printf("rombanks: %u\n", rombanks);*/ - - switch (header[0x0149]) { - case 0x00: /*std::puts("No RAM");*/ rambanks = type == MBC2; break; - case 0x01: /*std::puts("2kB RAM");*/ /*rambankrom=1; break;*/ - case 0x02: /*std::puts("8kB RAM");*/ - rambanks = 1; - break; - case 0x03: /*std::puts("32kB RAM");*/ - rambanks = 4; - break; - case 0x04: /*std::puts("128kB RAM");*/ - rambanks = 16; - break; - case 0x05: /*std::puts("undocumented kB RAM");*/ - rambanks = 16; - break; - default: /*std::puts("Wrong data-format, corrupt or unsupported ROM loaded.");*/ - rambanks = 16; - break; - } - - //cgb = header[0x0143] >> 7 & (1 ^ forceDmg); - cgb = forceDmg ? false : true; - std::printf("cgb: %d\n", cgb); - } - - std::printf("rambanks: %u\n", rambanks); - - const std::size_t filesize = romfilelength; //rom->size(); - rombanks = std::max(pow2ceil(filesize / 0x4000), 2u); - std::printf("rombanks: %u\n", static_cast(filesize / 0x4000)); - - mbc.reset(); - - memptrs.reset(rombanks, rambanks, cgb ? 8 : 2); - rtc.set(false, 0); - - //rom->rewind(); - //rom->read(reinterpret_cast(memptrs.romdata()), (filesize / 0x4000) * 0x4000ul); - - std::memcpy(memptrs.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul); - std::memset(memptrs.romdata() + (filesize / 0x4000) * 0x4000ul, 0xFF, (rombanks - filesize / 0x4000) * 0x4000ul); - enforce8bit(memptrs.romdata(), rombanks * 0x4000ul); - - //if (rom->fail()) - // return -1; - - switch (type) { - case PLAIN: mbc.reset(new Mbc0(memptrs)); break; - case MBC1: - if (!rambanks && rombanks == 64 && multicartCompat) { - std::puts("Multi-ROM \"MBC1\" presumed"); - mbc.reset(new Mbc1Multi64(memptrs)); - } else - mbc.reset(new Mbc1(memptrs)); - - break; - case MBC2: mbc.reset(new Mbc2(memptrs)); break; - case MBC3: mbc.reset(new Mbc3(memptrs, hasRtc(memptrs.romdata()[0x147]) ? &rtc : 0)); break; - case MBC5: mbc.reset(new Mbc5(memptrs)); break; - case HUC1: mbc.reset(new HuC1(memptrs)); break; - } - - return 0; -} - -static bool hasBattery(const unsigned char headerByte0x147) { - switch (headerByte0x147) { - case 0x03: - case 0x06: - case 0x09: - case 0x0F: - case 0x10: - case 0x13: - case 0x1B: - case 0x1E: - case 0xFF: return true; - default: return false; - } -} - -void Cartridge::loadSavedata(const char *data) { - if (hasBattery(memptrs.romdata()[0x147])) { - int length = memptrs.rambankdataend() - memptrs.rambankdata(); - std::memcpy(memptrs.rambankdata(), data, length); - data += length; - enforce8bit(memptrs.rambankdata(), length); - } - - if (hasRtc(memptrs.romdata()[0x147])) { - unsigned long basetime; - std::memcpy(&basetime, data, 4); - rtc.setBaseTime(basetime); - } -} - -int Cartridge::saveSavedataLength() { - int ret = 0; - if (hasBattery(memptrs.romdata()[0x147])) { - ret = memptrs.rambankdataend() - memptrs.rambankdata(); - } - if (hasRtc(memptrs.romdata()[0x147])) { - ret += 4; - } - return ret; -} - -void Cartridge::saveSavedata(char *dest) { - if (hasBattery(memptrs.romdata()[0x147])) { - int length = memptrs.rambankdataend() - memptrs.rambankdata(); - std::memcpy(dest, memptrs.rambankdata(), length); - dest += length; - } - - if (hasRtc(memptrs.romdata()[0x147])) { - const unsigned long basetime = rtc.getBaseTime(); - std::memcpy(dest, &basetime, 4); - } -} - -bool Cartridge::getMemoryArea(int which, unsigned char **data, int *length) const { - if (!data || !length) - return false; - - switch (which) - { - case 0: - *data = memptrs.vramdata(); - *length = memptrs.vramdataend() - memptrs.vramdata(); - return true; - case 1: - *data = memptrs.romdata(); - *length = memptrs.romdataend() - memptrs.romdata(); - return true; - case 2: - *data = memptrs.wramdata(0); - *length = memptrs.wramdataend() - memptrs.wramdata(0); - return true; - case 3: - *data = memptrs.rambankdata(); - *length = memptrs.rambankdataend() - memptrs.rambankdata(); - return true; - - default: - return false; - } - return false; -} - -SYNCFUNC(Cartridge) -{ - SSS(memptrs); - SSS(rtc); - TSS(mbc); -} - -} +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "cartridge.h" +#include "../savestate.h" +#include +#include +#include +#include + +namespace gambatte { + +namespace { + +static unsigned toMulti64Rombank(const unsigned rombank) { + return (rombank >> 1 & 0x30) | (rombank & 0xF); +} + +class DefaultMbc : public Mbc { +public: + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { + return (addr< 0x4000) == (bank == 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + } +}; + +class Mbc0 : public DefaultMbc { + MemPtrs &memptrs; + bool enableRam; + +public: + explicit Mbc0(MemPtrs &memptrs) + : memptrs(memptrs), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + if (P < 0x2000) { + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + enableRam = ss.enableRam; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(enableRam); + } +}; + +static inline unsigned rambanks(const MemPtrs &memptrs) { + return static_cast(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000; +} + +static inline unsigned rombanks(const MemPtrs &memptrs) { + return static_cast(memptrs.romdataend() - memptrs.romdata() ) / 0x4000; +} + +class Mbc1 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + bool rambankMode; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } + void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } + +public: + explicit Mbc1(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false), + rambankMode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = rambankMode ? data & 0x1F : (rombank & 0x60) | (data & 0x1F); + setRombank(); + break; + case 2: + if (rambankMode) { + rambank = data & 3; + setRambank(); + } else { + rombank = (data << 5 & 0x60) | (rombank & 0x1F); + setRombank(); + } + + break; + case 3: + // Pretty sure this should take effect immediately, but I have a policy not to change old behavior + // unless I have something (eg. a verified test or a game) that justifies it. + rambankMode = data & 1; + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + ss.rambankMode = rambankMode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + rambankMode = ss.rambankMode; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + NSS(rambankMode); + } +}; + +class Mbc1Multi64 : public Mbc { + MemPtrs &memptrs; + unsigned char rombank; + bool enableRam; + bool rombank0Mode; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } + + void setRombank() const { + if (rombank0Mode) { + const unsigned rb = toMulti64Rombank(rombank); + memptrs.setRombank0(rb & 0x30); + memptrs.setRombank(adjustedRombank(rb)); + } else { + memptrs.setRombank0(0); + memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); + } + } + +public: + explicit Mbc1Multi64(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + enableRam(false), + rombank0Mode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + break; + case 1: + rombank = (rombank & 0x60) | (data & 0x1F); + memptrs.setRombank(adjustedRombank(rombank0Mode ? toMulti64Rombank(rombank) : rombank & (rombanks(memptrs) - 1))); + break; + case 2: + rombank = (data << 5 & 0x60) | (rombank & 0x1F); + setRombank(); + break; + case 3: + rombank0Mode = data & 1; + setRombank(); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.enableRam = enableRam; + ss.rambankMode = rombank0Mode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + enableRam = ss.enableRam; + rombank0Mode = ss.rambankMode; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + setRombank(); + } + + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { + return (addr < 0x4000) == ((bank & 0xF) == 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(enableRam); + NSS(rombank0Mode); + } +}; + +class Mbc2 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + bool enableRam; + +public: + explicit Mbc2(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P & 0x6100) { + case 0x0000: + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + break; + case 0x2100: + rombank = data & 0xF; + memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + enableRam = ss.enableRam; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(enableRam); + } +}; + +class Mbc3 : public DefaultMbc { + MemPtrs &memptrs; + Rtc *const rtc; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x7F ? bank : bank | 1; } + void setRambank() const { + unsigned flags = enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0; + + if (rtc) { + rtc->set(enableRam, rambank); + + if (rtc->getActive()) + flags |= MemPtrs::RTC_EN; + } + + memptrs.setRambank(flags, rambank & (rambanks(memptrs) - 1)); + } + // we adjust the rombank before masking with size? this seems correct, as how would the mbc + // know that high rom address outputs were not connected + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank) & (rombanks(memptrs) - 1)); } + +public: + Mbc3(MemPtrs &memptrs, Rtc *const rtc) + : memptrs(memptrs), + rtc(rtc), + rombank(1), + rambank(0), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = data & 0x7F; + setRombank(); + break; + case 2: + rambank = data; + setRambank(); + break; + case 3: + if (rtc) + rtc->latch(data); + + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + } +}; + +class HuC1 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + bool rambankMode; + + void setRambank() const { + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : MemPtrs::READ_EN, + rambankMode ? rambank & (rambanks(memptrs) - 1) : 0); + } + + void setRombank() const { memptrs.setRombank((rambankMode ? rombank : rambank << 6 | rombank) & (rombanks(memptrs) - 1)); } + +public: + explicit HuC1(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false), + rambankMode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = data & 0x3F; + setRombank(); + break; + case 2: + rambank = data & 3; + rambankMode ? setRambank() : setRombank(); + break; + case 3: + rambankMode = data & 1; + setRambank(); + setRombank(); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + ss.rambankMode = rambankMode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + rambankMode = ss.rambankMode; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + NSS(rambankMode); + } +}; + +class Mbc5 : public DefaultMbc { + MemPtrs &memptrs; + unsigned short rombank; + unsigned char rambank; + bool enableRam; + + static unsigned adjustedRombank(const unsigned bank) { return bank; } + void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } + +public: + explicit Mbc5(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = P < 0x3000 ? (rombank & 0x100) | data + : (data << 8 & 0x100) | (rombank & 0xFF); + setRombank(); + break; + case 2: + rambank = data & 0xF; + setRambank(); + break; + case 3: + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + } +}; + +static bool hasRtc(const unsigned headerByte0x147) { + switch (headerByte0x147) { + case 0x0F: + case 0x10: return true; + default: return false; + } +} + +} + +void Cartridge::setStatePtrs(SaveState &state) { + state.mem.vram.set(memptrs.vramdata(), memptrs.vramdataend() - memptrs.vramdata()); + state.mem.sram.set(memptrs.rambankdata(), memptrs.rambankdataend() - memptrs.rambankdata()); + state.mem.wram.set(memptrs.wramdata(0), memptrs.wramdataend() - memptrs.wramdata(0)); +} + +void Cartridge::loadState(const SaveState &state) { + rtc.loadState(state); + mbc->loadState(state.mem); +} + +static void enforce8bit(unsigned char *data, unsigned long sz) { + if (static_cast(0x100)) + while (sz--) + *data++ &= 0xFF; +} + +static unsigned pow2ceil(unsigned n) { + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + + return n; +} + +int Cartridge::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { + //const std::auto_ptr rom(newFileInstance(romfile)); + + //if (rom->fail()) + // return -1; + + unsigned rambanks = 1; + unsigned rombanks = 2; + bool cgb = false; + enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1 } type = PLAIN; + + { + unsigned char header[0x150]; + //rom->read(reinterpret_cast(header), sizeof header); + if (romfilelength >= sizeof header) + std::memcpy(header, romfiledata, sizeof header); + else + return -1; + + switch (header[0x0147]) { + case 0x00: std::puts("Plain ROM loaded."); type = PLAIN; break; + case 0x01: std::puts("MBC1 ROM loaded."); type = MBC1; break; + case 0x02: std::puts("MBC1 ROM+RAM loaded."); type = MBC1; break; + case 0x03: std::puts("MBC1 ROM+RAM+BATTERY loaded."); type = MBC1; break; + case 0x05: std::puts("MBC2 ROM loaded."); type = MBC2; break; + case 0x06: std::puts("MBC2 ROM+BATTERY loaded."); type = MBC2; break; + case 0x08: std::puts("Plain ROM with additional RAM loaded."); type = PLAIN; break; + case 0x09: std::puts("Plain ROM with additional RAM and Battery loaded."); type = PLAIN; break; + case 0x0B: std::puts("MM01 ROM not supported."); return -1; + case 0x0C: std::puts("MM01 ROM not supported."); return -1; + case 0x0D: std::puts("MM01 ROM not supported."); return -1; + case 0x0F: std::puts("MBC3 ROM+TIMER+BATTERY loaded."); type = MBC3; break; + case 0x10: std::puts("MBC3 ROM+TIMER+RAM+BATTERY loaded."); type = MBC3; break; + case 0x11: std::puts("MBC3 ROM loaded."); type = MBC3; break; + case 0x12: std::puts("MBC3 ROM+RAM loaded."); type = MBC3; break; + case 0x13: std::puts("MBC3 ROM+RAM+BATTERY loaded."); type = MBC3; break; + case 0x15: std::puts("MBC4 ROM not supported."); return -1; + case 0x16: std::puts("MBC4 ROM not supported."); return -1; + case 0x17: std::puts("MBC4 ROM not supported."); return -1; + case 0x19: std::puts("MBC5 ROM loaded."); type = MBC5; break; + case 0x1A: std::puts("MBC5 ROM+RAM loaded."); type = MBC5; break; + case 0x1B: std::puts("MBC5 ROM+RAM+BATTERY loaded."); type = MBC5; break; + case 0x1C: std::puts("MBC5+RUMBLE ROM not supported."); type = MBC5; break; + case 0x1D: std::puts("MBC5+RUMBLE+RAM ROM not suported."); type = MBC5; break; + case 0x1E: std::puts("MBC5+RUMBLE+RAM+BATTERY ROM not supported."); type = MBC5; break; + case 0xFC: std::puts("Pocket Camera ROM not supported."); return -1; + case 0xFD: std::puts("Bandai TAMA5 ROM not supported."); return -1; + case 0xFE: std::puts("HuC3 ROM not supported."); return -1; + case 0xFF: std::puts("HuC1 ROM+RAM+BATTERY loaded."); type = HUC1; break; + default: std::puts("Wrong data-format, corrupt or unsupported ROM."); return -1; + } + + /*switch (header[0x0148]) { + case 0x00: rombanks = 2; break; + case 0x01: rombanks = 4; break; + case 0x02: rombanks = 8; break; + case 0x03: rombanks = 16; break; + case 0x04: rombanks = 32; break; + case 0x05: rombanks = 64; break; + case 0x06: rombanks = 128; break; + case 0x07: rombanks = 256; break; + case 0x08: rombanks = 512; break; + case 0x52: rombanks = 72; break; + case 0x53: rombanks = 80; break; + case 0x54: rombanks = 96; break; + default: return -1; + } + + std::printf("rombanks: %u\n", rombanks);*/ + + switch (header[0x0149]) { + case 0x00: /*std::puts("No RAM");*/ rambanks = type == MBC2; break; + case 0x01: /*std::puts("2kB RAM");*/ /*rambankrom=1; break;*/ + case 0x02: /*std::puts("8kB RAM");*/ + rambanks = 1; + break; + case 0x03: /*std::puts("32kB RAM");*/ + rambanks = 4; + break; + case 0x04: /*std::puts("128kB RAM");*/ + rambanks = 16; + break; + case 0x05: /*std::puts("undocumented kB RAM");*/ + rambanks = 16; + break; + default: /*std::puts("Wrong data-format, corrupt or unsupported ROM loaded.");*/ + rambanks = 16; + break; + } + + cgb = !forceDmg; + std::printf("cgb: %d\n", cgb); + } + + std::printf("rambanks: %u\n", rambanks); + + const std::size_t filesize = romfilelength; //rom->size(); + rombanks = std::max(pow2ceil(filesize / 0x4000), 2u); + std::printf("rombanks: %u\n", static_cast(filesize / 0x4000)); + + mbc.reset(); + memptrs.reset(rombanks, rambanks, cgb ? 8 : 2); + rtc.set(false, 0); + + //rom->rewind(); + //rom->read(reinterpret_cast(memptrs.romdata()), (filesize / 0x4000) * 0x4000ul); + std::memcpy(memptrs.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul); + std::memset(memptrs.romdata() + (filesize / 0x4000) * 0x4000ul, 0xFF, (rombanks - filesize / 0x4000) * 0x4000ul); + enforce8bit(memptrs.romdata(), rombanks * 0x4000ul); + + //if (rom->fail()) + // return -1; + + switch (type) { + case PLAIN: mbc.reset(new Mbc0(memptrs)); break; + case MBC1: + if (!rambanks && rombanks == 64 && multicartCompat) { + std::puts("Multi-ROM \"MBC1\" presumed"); + mbc.reset(new Mbc1Multi64(memptrs)); + } else + mbc.reset(new Mbc1(memptrs)); + + break; + case MBC2: mbc.reset(new Mbc2(memptrs)); break; + case MBC3: mbc.reset(new Mbc3(memptrs, hasRtc(memptrs.romdata()[0x147]) ? &rtc : 0)); break; + case MBC5: mbc.reset(new Mbc5(memptrs)); break; + case HUC1: mbc.reset(new HuC1(memptrs)); break; + } + + return 0; +} + +static bool hasBattery(const unsigned char headerByte0x147) { + switch (headerByte0x147) { + case 0x03: + case 0x06: + case 0x09: + case 0x0F: + case 0x10: + case 0x13: + case 0x1B: + case 0x1E: + case 0xFF: return true; + default: return false; + } +} + +void Cartridge::loadSavedata(const char *data) { + if (hasBattery(memptrs.romdata()[0x147])) { + int length = memptrs.rambankdataend() - memptrs.rambankdata(); + std::memcpy(memptrs.rambankdata(), data, length); + data += length; + enforce8bit(memptrs.rambankdata(), length); + } + + if (hasRtc(memptrs.romdata()[0x147])) { + unsigned long basetime; + std::memcpy(&basetime, data, 4); + rtc.setBaseTime(basetime); + } +} + +int Cartridge::saveSavedataLength() { + int ret = 0; + if (hasBattery(memptrs.romdata()[0x147])) { + ret = memptrs.rambankdataend() - memptrs.rambankdata(); + } + if (hasRtc(memptrs.romdata()[0x147])) { + ret += 4; + } + return ret; +} + +void Cartridge::saveSavedata(char *dest) { + if (hasBattery(memptrs.romdata()[0x147])) { + int length = memptrs.rambankdataend() - memptrs.rambankdata(); + std::memcpy(dest, memptrs.rambankdata(), length); + dest += length; + } + + if (hasRtc(memptrs.romdata()[0x147])) { + const unsigned long basetime = rtc.getBaseTime(); + std::memcpy(dest, &basetime, 4); + } +} + +bool Cartridge::getMemoryArea(int which, unsigned char **data, int *length) const { + if (!data || !length) + return false; + + switch (which) + { + case 0: + *data = memptrs.vramdata(); + *length = memptrs.vramdataend() - memptrs.vramdata(); + return true; + case 1: + *data = memptrs.romdata(); + *length = memptrs.romdataend() - memptrs.romdata(); + return true; + case 2: + *data = memptrs.wramdata(0); + *length = memptrs.wramdataend() - memptrs.wramdata(0); + return true; + case 3: + *data = memptrs.rambankdata(); + *length = memptrs.rambankdataend() - memptrs.rambankdata(); + return true; + + default: + return false; + } + return false; +} + +SYNCFUNC(Cartridge) +{ + SSS(memptrs); + SSS(rtc); + TSS(mbc); +} + +} diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index 0015924966..511c4438ae 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -1,112 +1,113 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef CARTRIDGE_H -#define CARTRIDGE_H - -#include "memptrs.h" -#include "rtc.h" -#include "savestate.h" -#include -#include -#include -#include "newstate.h" - -namespace gambatte { - - //DOOM -//enum eAddressMappingType -//{ -// eAddressMappingType_ROM, -// eAddressMappingType_RAM -//}; -// -//struct AddressMapping -//{ -// int32_t address; -// eAddressMappingType type; -//}; - -class Mbc { -public: - virtual ~Mbc() {} - virtual void romWrite(unsigned P, unsigned data) = 0; - virtual void loadState(const SaveState::Mem &ss) = 0; - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0; - //virtual void mapAddress(AddressMapping* mapping, unsigned address) const = 0; //DOOM - - templatevoid SyncState(NewState *ns) - { - // can't have virtual templates, so.. - SyncState(ns, isReader); - } - virtual void SyncState(NewState *ns, bool isReader) = 0; -}; - -class Cartridge { - MemPtrs memptrs; - Rtc rtc; - std::auto_ptr mbc; - -public: - void setStatePtrs(SaveState &); - void loadState(const SaveState &); - - bool loaded() const { return mbc.get(); } - - const unsigned char * rmem(unsigned area) const { return memptrs.rmem(area); } - unsigned char * wmem(unsigned area) const { return memptrs.wmem(area); } - unsigned char * vramdata() const { return memptrs.vramdata(); } - unsigned char * romdata(unsigned area) const { return memptrs.romdata(area); } - unsigned char * wramdata(unsigned area) const { return memptrs.wramdata(area); } - const unsigned char * rdisabledRam() const { return memptrs.rdisabledRam(); } - const unsigned char * rsrambankptr() const { return memptrs.rsrambankptr(); } - unsigned char * wsrambankptr() const { return memptrs.wsrambankptr(); } - unsigned char * vrambankptr() const { return memptrs.vrambankptr(); } - OamDmaSrc oamDmaSrc() const { return memptrs.oamDmaSrc(); } - - void setVrambank(unsigned bank) { memptrs.setVrambank(bank); } - void setWrambank(unsigned bank) { memptrs.setWrambank(bank); } - void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs.setOamDmaSrc(oamDmaSrc); } - - void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); } - - bool isCgb() const { return gambatte::isCgb(memptrs); } - - void rtcWrite(unsigned data) { rtc.write(data); } - unsigned char rtcRead() const { return *rtc.getActive(); } - - void loadSavedata(const char *data); - int saveSavedataLength(); - void saveSavedata(char *dest); - - bool getMemoryArea(int which, unsigned char **data, int *length) const; - - int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); - const char * romTitle() const { return reinterpret_cast(memptrs.romdata() + 0x134); } - - void setRTCCallback(std::uint32_t (*callback)()) { - rtc.setRTCCallback(callback); - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CARTRIDGE_H +#define CARTRIDGE_H + +#include "memptrs.h" +#include "rtc.h" +#include "savestate.h" +#include +#include +#include +#include "newstate.h" + +namespace gambatte { + + //DOOM +//enum eAddressMappingType +//{ +// eAddressMappingType_ROM, +// eAddressMappingType_RAM +//}; +// +//struct AddressMapping +//{ +// int32_t address; +// eAddressMappingType type; +//}; + +class Mbc { +public: + virtual ~Mbc() {} + virtual void romWrite(unsigned P, unsigned data) = 0; + virtual void loadState(const SaveState::Mem &ss) = 0; + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0; + //virtual void mapAddress(AddressMapping* mapping, unsigned address) const = 0; //DOOM + + templatevoid SyncState(NewState *ns) + { + // can't have virtual templates, so.. + SyncState(ns, isReader); + } + virtual void SyncState(NewState *ns, bool isReader) = 0; +}; + +class Cartridge { + MemPtrs memptrs; + Rtc rtc; + std::auto_ptr mbc; + +public: + void setStatePtrs(SaveState &); + void loadState(const SaveState &); + + bool loaded() const { return mbc.get(); } + + const unsigned char * rmem(unsigned area) const { return memptrs.rmem(area); } + unsigned char * wmem(unsigned area) const { return memptrs.wmem(area); } + unsigned char * vramdata() const { return memptrs.vramdata(); } + unsigned char * romdata(unsigned area) const { return memptrs.romdata(area); } + unsigned char * wramdata(unsigned area) const { return memptrs.wramdata(area); } + const unsigned char * rdisabledRam() const { return memptrs.rdisabledRam(); } + const unsigned char * rsrambankptr() const { return memptrs.rsrambankptr(); } + unsigned char * wsrambankptr() const { return memptrs.wsrambankptr(); } + unsigned char * vrambankptr() const { return memptrs.vrambankptr(); } + OamDmaSrc oamDmaSrc() const { return memptrs.oamDmaSrc(); } + unsigned curRomBank() const { return memptrs.curRomBank(); } + + void setVrambank(unsigned bank) { memptrs.setVrambank(bank); } + void setWrambank(unsigned bank) { memptrs.setWrambank(bank); } + void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs.setOamDmaSrc(oamDmaSrc); } + + void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); } + + bool isCgb() const { return gambatte::isCgb(memptrs); } + + void rtcWrite(unsigned data) { rtc.write(data); } + unsigned char rtcRead() const { return *rtc.getActive(); } + + void loadSavedata(const char *data); + int saveSavedataLength(); + void saveSavedata(char *dest); + + bool getMemoryArea(int which, unsigned char **data, int *length) const; + + int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); + const char * romTitle() const { return reinterpret_cast(memptrs.romdata() + 0x134); } + + void setRTCCallback(std::uint32_t (*callback)()) { + rtc.setRTCCallback(callback); + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/mem/memptrs.cpp b/libgambatte/src/mem/memptrs.cpp index 1b0399770a..bd166e86cf 100644 --- a/libgambatte/src/mem/memptrs.cpp +++ b/libgambatte/src/mem/memptrs.cpp @@ -1,226 +1,223 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "memptrs.h" -#include -#include - -namespace gambatte { - -MemPtrs::MemPtrs() -: rmem_(), wmem_(), romdata_(), wramdata_(), vrambankptr_(0), rsrambankptr_(0), - wsrambankptr_(0), memchunk_(0), rambankdata_(0), wramdataend_(0), oamDmaSrc_(OAM_DMA_SRC_OFF), - memchunk_len(0) -{ -} - -MemPtrs::~MemPtrs() { - delete []memchunk_; -} - -void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks) { - delete []memchunk_; - - memchunk_len = 0x4000 + rombanks * 0x4000ul + 0x4000 + rambanks * 0x2000ul + wrambanks * 0x1000ul + 0x4000; - memchunk_ = new unsigned char[memchunk_len]; - - romdata_[0] = romdata(); - - rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000; - wramdata_[0] = rambankdata_ + rambanks * 0x2000ul; - wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul; - - std::memset(rdisabledRamw(), 0xFF, 0x2000); - - oamDmaSrc_ = OAM_DMA_SRC_OFF; - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; - setRombank(1); - setRambank(0, 0); - setVrambank(0); - setWrambank(1); - - // we save only the ram areas - memchunk_saveoffs = vramdata() - memchunk_; - memchunk_savelen = wramdataend() - memchunk_ - memchunk_saveoffs; -} - -void MemPtrs::setRombank0(const unsigned bank) { - - romdata_[0] = romdata() + bank * 0x4000ul; - - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - disconnectOamDmaAreas(); -} - -void MemPtrs::setRombank(const unsigned bank) { - - romdata_[1] = romdata() + bank * 0x4000ul - 0x4000; - - rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; - disconnectOamDmaAreas(); -} - -void MemPtrs::setRambank(const unsigned flags, const unsigned rambank) { - unsigned char *const srambankptr = flags & RTC_EN - ? 0 - : (rambankdata() != rambankdataend() - ? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000); - - rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000; - wsrambankptr_ = flags & WRITE_EN ? srambankptr : wdisabledRam() - 0xA000; - rmem_[0xB] = rmem_[0xA] = rsrambankptr_; - wmem_[0xB] = wmem_[0xA] = wsrambankptr_; - disconnectOamDmaAreas(); -} - -void MemPtrs::setWrambank(const unsigned bank) { - wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; - disconnectOamDmaAreas(); -} - -void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc) { - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; - rmem_[0xB] = rmem_[0xA] = rsrambankptr_; - wmem_[0xB] = wmem_[0xA] = wsrambankptr_; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; - - oamDmaSrc_ = oamDmaSrc; - disconnectOamDmaAreas(); -} - -void MemPtrs::disconnectOamDmaAreas() { - if (isCgb(*this)) { - switch (oamDmaSrc_) { - case OAM_DMA_SRC_ROM: // fall through - case OAM_DMA_SRC_SRAM: - case OAM_DMA_SRC_INVALID: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - break; - case OAM_DMA_SRC_VRAM: - break; - case OAM_DMA_SRC_WRAM: - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case OAM_DMA_SRC_OFF: - break; - } - } else { - switch (oamDmaSrc_) { - case OAM_DMA_SRC_ROM: // fall through - case OAM_DMA_SRC_SRAM: - case OAM_DMA_SRC_WRAM: - case OAM_DMA_SRC_INVALID: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case OAM_DMA_SRC_VRAM: - break; - case OAM_DMA_SRC_OFF: - break; - } - } -} - -// all pointers here are relative to memchunk_ -#define MSS(a) RSS(a,memchunk_) -#define MSL(a) RSL(a,memchunk_) - -SYNCFUNC(MemPtrs) -{ - /* - int memchunk_len_old = memchunk_len; - int memchunk_saveoffs_old = memchunk_saveoffs; - int memchunk_savelen_old = memchunk_savelen; - */ - - NSS(memchunk_len); - NSS(memchunk_saveoffs); - NSS(memchunk_savelen); - - /* - if (isReader) - { - if (memchunk_len != memchunk_len_old || memchunk_saveoffs != memchunk_saveoffs_old || memchunk_savelen != memchunk_savelen_old) - __debugbreak(); - } - */ - - PSS(memchunk_ + memchunk_saveoffs, memchunk_savelen); - - MSS(rmem_[0x0]); - MSS(wmem_[0x0]); - MSS(rmem_[0x1]); - MSS(wmem_[0x1]); - MSS(rmem_[0x2]); - MSS(wmem_[0x2]); - MSS(rmem_[0x3]); - MSS(wmem_[0x3]); - MSS(rmem_[0x4]); - MSS(wmem_[0x4]); - MSS(rmem_[0x5]); - MSS(wmem_[0x5]); - MSS(rmem_[0x6]); - MSS(wmem_[0x6]); - MSS(rmem_[0x7]); - MSS(wmem_[0x7]); - MSS(rmem_[0x8]); - MSS(wmem_[0x8]); - MSS(rmem_[0x9]); - MSS(wmem_[0x9]); - MSS(rmem_[0xa]); - MSS(wmem_[0xa]); - MSS(rmem_[0xb]); - MSS(wmem_[0xb]); - MSS(rmem_[0xc]); - MSS(wmem_[0xc]); - MSS(rmem_[0xd]); - MSS(wmem_[0xd]); - MSS(rmem_[0xe]); - MSS(wmem_[0xe]); - MSS(rmem_[0xf]); - MSS(wmem_[0xf]); - //for (int i = 0; i < 0x10; i++) - //{ - // MSS(rmem_[i]); - // MSS(wmem_[i]); - //} - MSS(romdata_[0]); - MSS(romdata_[1]); - MSS(wramdata_[0]); - MSS(wramdata_[1]); - MSS(vrambankptr_); - MSS(rsrambankptr_); - MSS(wsrambankptr_); - MSS(rambankdata_); - MSS(wramdataend_); - NSS(oamDmaSrc_); -} - -} +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "memptrs.h" +#include +#include + +namespace gambatte { + +MemPtrs::MemPtrs() +: rmem_(), wmem_(), romdata_(), wramdata_(), vrambankptr_(0), rsrambankptr_(0), + wsrambankptr_(0), memchunk_(0), rambankdata_(0), wramdataend_(0), oamDmaSrc_(OAM_DMA_SRC_OFF), + curRomBank_(1), + memchunk_len(0) +{ +} + +MemPtrs::~MemPtrs() { + delete []memchunk_; +} + +void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks) { + delete []memchunk_; + memchunk_len = 0x4000 + rombanks * 0x4000ul + 0x4000 + rambanks * 0x2000ul + wrambanks * 0x1000ul + 0x4000; + memchunk_ = new unsigned char[memchunk_len]; + + romdata_[0] = romdata(); + rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000; + wramdata_[0] = rambankdata_ + rambanks * 0x2000ul; + wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul; + + std::memset(rdisabledRamw(), 0xFF, 0x2000); + + oamDmaSrc_ = OAM_DMA_SRC_OFF; + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + setRombank(1); + setRambank(0, 0); + setVrambank(0); + setWrambank(1); + + // we save only the ram areas + memchunk_saveoffs = vramdata() - memchunk_; + memchunk_savelen = wramdataend() - memchunk_ - memchunk_saveoffs; +} + +void MemPtrs::setRombank0(const unsigned bank) { + romdata_[0] = romdata() + bank * 0x4000ul; + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + disconnectOamDmaAreas(); +} + +void MemPtrs::setRombank(const unsigned bank) { + curRomBank_ = bank; + romdata_[1] = romdata() + bank * 0x4000ul - 0x4000; + rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; + disconnectOamDmaAreas(); +} + +void MemPtrs::setRambank(const unsigned flags, const unsigned rambank) { + unsigned char *const srambankptr = flags & RTC_EN + ? 0 + : (rambankdata() != rambankdataend() + ? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000); + + rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000; + wsrambankptr_ = flags & WRITE_EN ? srambankptr : wdisabledRam() - 0xA000; + rmem_[0xB] = rmem_[0xA] = rsrambankptr_; + wmem_[0xB] = wmem_[0xA] = wsrambankptr_; + disconnectOamDmaAreas(); +} + +void MemPtrs::setWrambank(const unsigned bank) { + wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000; + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; + disconnectOamDmaAreas(); +} + +void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc) { + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; + rmem_[0xB] = rmem_[0xA] = rsrambankptr_; + wmem_[0xB] = wmem_[0xA] = wsrambankptr_; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + + oamDmaSrc_ = oamDmaSrc; + disconnectOamDmaAreas(); +} + +void MemPtrs::disconnectOamDmaAreas() { + if (isCgb(*this)) { + switch (oamDmaSrc_) { + case OAM_DMA_SRC_ROM: // fall through + case OAM_DMA_SRC_SRAM: + case OAM_DMA_SRC_INVALID: + std::fill(rmem_, rmem_ + 8, static_cast(0)); + rmem_[0xB] = rmem_[0xA] = 0; + wmem_[0xB] = wmem_[0xA] = 0; + break; + case OAM_DMA_SRC_VRAM: + break; + case OAM_DMA_SRC_WRAM: + rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; + wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; + break; + case OAM_DMA_SRC_OFF: + break; + } + } else { + switch (oamDmaSrc_) { + case OAM_DMA_SRC_ROM: // fall through + case OAM_DMA_SRC_SRAM: + case OAM_DMA_SRC_WRAM: + case OAM_DMA_SRC_INVALID: + std::fill(rmem_, rmem_ + 8, static_cast(0)); + rmem_[0xB] = rmem_[0xA] = 0; + wmem_[0xB] = wmem_[0xA] = 0; + rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; + wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; + break; + case OAM_DMA_SRC_VRAM: + break; + case OAM_DMA_SRC_OFF: + break; + } + } +} + +// all pointers here are relative to memchunk_ +#define MSS(a) RSS(a,memchunk_) +#define MSL(a) RSL(a,memchunk_) + +SYNCFUNC(MemPtrs) +{ + /* + int memchunk_len_old = memchunk_len; + int memchunk_saveoffs_old = memchunk_saveoffs; + int memchunk_savelen_old = memchunk_savelen; + */ + + NSS(memchunk_len); + NSS(memchunk_saveoffs); + NSS(memchunk_savelen); + + /* + if (isReader) + { + if (memchunk_len != memchunk_len_old || memchunk_saveoffs != memchunk_saveoffs_old || memchunk_savelen != memchunk_savelen_old) + __debugbreak(); + } + */ + + PSS(memchunk_ + memchunk_saveoffs, memchunk_savelen); + + MSS(rmem_[0x0]); + MSS(wmem_[0x0]); + MSS(rmem_[0x1]); + MSS(wmem_[0x1]); + MSS(rmem_[0x2]); + MSS(wmem_[0x2]); + MSS(rmem_[0x3]); + MSS(wmem_[0x3]); + MSS(rmem_[0x4]); + MSS(wmem_[0x4]); + MSS(rmem_[0x5]); + MSS(wmem_[0x5]); + MSS(rmem_[0x6]); + MSS(wmem_[0x6]); + MSS(rmem_[0x7]); + MSS(wmem_[0x7]); + MSS(rmem_[0x8]); + MSS(wmem_[0x8]); + MSS(rmem_[0x9]); + MSS(wmem_[0x9]); + MSS(rmem_[0xa]); + MSS(wmem_[0xa]); + MSS(rmem_[0xb]); + MSS(wmem_[0xb]); + MSS(rmem_[0xc]); + MSS(wmem_[0xc]); + MSS(rmem_[0xd]); + MSS(wmem_[0xd]); + MSS(rmem_[0xe]); + MSS(wmem_[0xe]); + MSS(rmem_[0xf]); + MSS(wmem_[0xf]); + //for (int i = 0; i < 0x10; i++) + //{ + // MSS(rmem_[i]); + // MSS(wmem_[i]); + //} + MSS(romdata_[0]); + MSS(romdata_[1]); + MSS(wramdata_[0]); + MSS(wramdata_[1]); + MSS(vrambankptr_); + MSS(rsrambankptr_); + MSS(wsrambankptr_); + MSS(rambankdata_); + MSS(wramdataend_); + NSS(oamDmaSrc_); + NSS(curRomBank_); +} + +} diff --git a/libgambatte/src/mem/memptrs.h b/libgambatte/src/mem/memptrs.h index 5e4f772a26..68fd2b0006 100644 --- a/libgambatte/src/mem/memptrs.h +++ b/libgambatte/src/mem/memptrs.h @@ -1,93 +1,96 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef MEMPTRS_H -#define MEMPTRS_H - -#include "newstate.h" - -namespace gambatte { - -enum OamDmaSrc { OAM_DMA_SRC_ROM, OAM_DMA_SRC_SRAM, OAM_DMA_SRC_VRAM, - OAM_DMA_SRC_WRAM, OAM_DMA_SRC_INVALID, OAM_DMA_SRC_OFF }; - -class MemPtrs { - const unsigned char *rmem_[0x10]; - unsigned char *wmem_[0x10]; - - unsigned char *romdata_[2]; - unsigned char *wramdata_[2]; - unsigned char *vrambankptr_; - unsigned char *rsrambankptr_; - unsigned char *wsrambankptr_; - unsigned char *memchunk_; - unsigned char *rambankdata_; - unsigned char *wramdataend_; - - OamDmaSrc oamDmaSrc_; - - int memchunk_len; - int memchunk_saveoffs; - int memchunk_savelen; - - MemPtrs(const MemPtrs &); - MemPtrs & operator=(const MemPtrs &); - void disconnectOamDmaAreas(); - unsigned char * rdisabledRamw() const { return wramdataend_ ; } - unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; } -public: - enum RamFlag { READ_EN = 1, WRITE_EN = 2, RTC_EN = 4 }; - - MemPtrs(); - ~MemPtrs(); - void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks); - - const unsigned char * rmem(unsigned area) const { return rmem_[area]; } - unsigned char * wmem(unsigned area) const { return wmem_[area]; } - unsigned char * vramdata() const { return rambankdata_ - 0x4000; } - unsigned char * vramdataend() const { return rambankdata_; } - unsigned char * romdata() const { return memchunk_ + 0x4000;} - unsigned char * romdata(unsigned area) const { return romdata_[area]; } - unsigned char * romdataend() const { return rambankdata_ - 0x4000; } - unsigned char * wramdata(unsigned area) const { return wramdata_[area]; } - unsigned char * wramdataend() const { return wramdataend_; } - unsigned char * rambankdata() const { return rambankdata_; } - unsigned char * rambankdataend() const { return wramdata_[0]; } - const unsigned char * rdisabledRam() const { return rdisabledRamw(); } - const unsigned char * rsrambankptr() const { return rsrambankptr_; } - unsigned char * wsrambankptr() const { return wsrambankptr_; } - unsigned char * vrambankptr() const { return vrambankptr_; } - OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; } - - void setRombank0(unsigned bank); - void setRombank(unsigned bank); - void setRambank(unsigned ramFlags, unsigned rambank); - void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; } - void setWrambank(unsigned bank); - void setOamDmaSrc(OamDmaSrc oamDmaSrc); - - templatevoid SyncState(NewState *ns); -}; - -inline bool isCgb(const MemPtrs &memptrs) { - return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000; -} - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MEMPTRS_H +#define MEMPTRS_H + +#include "newstate.h" + +namespace gambatte { + +enum OamDmaSrc { OAM_DMA_SRC_ROM, OAM_DMA_SRC_SRAM, OAM_DMA_SRC_VRAM, + OAM_DMA_SRC_WRAM, OAM_DMA_SRC_INVALID, OAM_DMA_SRC_OFF }; + +class MemPtrs { + const unsigned char *rmem_[0x10]; + unsigned char *wmem_[0x10]; + + unsigned char *romdata_[2]; + unsigned char *wramdata_[2]; + unsigned char *vrambankptr_; + unsigned char *rsrambankptr_; + unsigned char *wsrambankptr_; + unsigned char *memchunk_; + unsigned char *rambankdata_; + unsigned char *wramdataend_; + + OamDmaSrc oamDmaSrc_; + + unsigned curRomBank_; + + int memchunk_len; + int memchunk_saveoffs; + int memchunk_savelen; + + MemPtrs(const MemPtrs &); + MemPtrs & operator=(const MemPtrs &); + void disconnectOamDmaAreas(); + unsigned char * rdisabledRamw() const { return wramdataend_ ; } + unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; } +public: + enum RamFlag { READ_EN = 1, WRITE_EN = 2, RTC_EN = 4 }; + + MemPtrs(); + ~MemPtrs(); + void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks); + + const unsigned char * rmem(unsigned area) const { return rmem_[area]; } + unsigned char * wmem(unsigned area) const { return wmem_[area]; } + unsigned char * vramdata() const { return rambankdata_ - 0x4000; } + unsigned char * vramdataend() const { return rambankdata_; } + unsigned char * romdata() const { return memchunk_ + 0x4000; } + unsigned char * romdata(unsigned area) const { return romdata_[area]; } + unsigned char * romdataend() const { return rambankdata_ - 0x4000; } + unsigned char * wramdata(unsigned area) const { return wramdata_[area]; } + unsigned char * wramdataend() const { return wramdataend_; } + unsigned char * rambankdata() const { return rambankdata_; } + unsigned char * rambankdataend() const { return wramdata_[0]; } + const unsigned char * rdisabledRam() const { return rdisabledRamw(); } + const unsigned char * rsrambankptr() const { return rsrambankptr_; } + unsigned char * wsrambankptr() const { return wsrambankptr_; } + unsigned char * vrambankptr() const { return vrambankptr_; } + OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; } + unsigned curRomBank() const { return curRomBank_; } + + void setRombank0(unsigned bank); + void setRombank(unsigned bank); + void setRambank(unsigned ramFlags, unsigned rambank); + void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; } + void setWrambank(unsigned bank); + void setOamDmaSrc(OamDmaSrc oamDmaSrc); + + templatevoid SyncState(NewState *ns); +}; + +inline bool isCgb(const MemPtrs &memptrs) { + return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000; +} + +} + +#endif diff --git a/libgambatte/src/mem/rtc.cpp b/libgambatte/src/mem/rtc.cpp index 3fc1ed1a2e..ae97d0876c 100644 --- a/libgambatte/src/mem/rtc.cpp +++ b/libgambatte/src/mem/rtc.cpp @@ -1,177 +1,177 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "rtc.h" -#include "../savestate.h" -#include - -namespace gambatte { - -Rtc::Rtc() -: activeData(NULL), - activeSet(NULL), - baseTime(0), - haltTime(0), - index(5), - dataDh(0), - dataDl(0), - dataH(0), - dataM(0), - dataS(0), - enabled(false), - lastLatchData(false), - timeCB(0) -{ -} - -void Rtc::doLatch() { - std::uint32_t tmp = ((dataDh & 0x40) ? haltTime : timeCB()) - baseTime; - - while (tmp > 0x1FF * 86400) { - baseTime += 0x1FF * 86400; - tmp -= 0x1FF * 86400; - dataDh |= 0x80; - } - - dataDl = (tmp / 86400) & 0xFF; - dataDh &= 0xFE; - dataDh |= ((tmp / 86400) & 0x100) >> 8; - tmp %= 86400; - - dataH = tmp / 3600; - tmp %= 3600; - - dataM = tmp / 60; - tmp %= 60; - - dataS = tmp; -} - -void Rtc::doSwapActive() { - if (!enabled || index > 4) { - activeData = NULL; - activeSet = NULL; - } else switch (index) { - case 0x00: - activeData = &dataS; - activeSet = &Rtc::setS; - break; - case 0x01: - activeData = &dataM; - activeSet = &Rtc::setM; - break; - case 0x02: - activeData = &dataH; - activeSet = &Rtc::setH; - break; - case 0x03: - activeData = &dataDl; - activeSet = &Rtc::setDl; - break; - case 0x04: - activeData = &dataDh; - activeSet = &Rtc::setDh; - break; - } -} - -void Rtc::loadState(const SaveState &state) { - baseTime = state.rtc.baseTime; - haltTime = state.rtc.haltTime; - dataDh = state.rtc.dataDh; - dataDl = state.rtc.dataDl; - dataH = state.rtc.dataH; - dataM = state.rtc.dataM; - dataS = state.rtc.dataS; - lastLatchData = state.rtc.lastLatchData; - - doSwapActive(); -} - -void Rtc::setDh(const unsigned new_dh) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_highdays = ((unixtime - baseTime) / 86400) & 0x100; - baseTime += old_highdays * 86400; - baseTime -= ((new_dh & 0x1) << 8) * 86400; - - if ((dataDh ^ new_dh) & 0x40) { - if (new_dh & 0x40) - haltTime = timeCB(); - else - baseTime += timeCB() - haltTime; - } -} - -void Rtc::setDl(const unsigned new_lowdays) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_lowdays = ((unixtime - baseTime) / 86400) & 0xFF; - baseTime += old_lowdays * 86400; - baseTime -= new_lowdays * 86400; -} - -void Rtc::setH(const unsigned new_hours) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_hours = ((unixtime - baseTime) / 3600) % 24; - baseTime += old_hours * 3600; - baseTime -= new_hours * 3600; -} - -void Rtc::setM(const unsigned new_minutes) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_minutes = ((unixtime - baseTime) / 60) % 60; - baseTime += old_minutes * 60; - baseTime -= new_minutes * 60; -} - -void Rtc::setS(const unsigned new_seconds) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - baseTime += (unixtime - baseTime) % 60; - baseTime -= new_seconds; -} - -SYNCFUNC(Rtc) -{ - EBS(activeData, 0); - EVS(activeData, &dataS, 1); - EVS(activeData, &dataM, 2); - EVS(activeData, &dataH, 3); - EVS(activeData, &dataDl, 4); - EVS(activeData, &dataDh, 5); - EES(activeData, NULL); - - EBS(activeSet, 0); - EVS(activeSet, &Rtc::setS, 1); - EVS(activeSet, &Rtc::setM, 2); - EVS(activeSet, &Rtc::setH, 3); - EVS(activeSet, &Rtc::setDl, 4); - EVS(activeSet, &Rtc::setDh, 5); - EES(activeSet, NULL); - - NSS(baseTime); - NSS(haltTime); - NSS(index); - NSS(dataDh); - NSS(dataDl); - NSS(dataH); - NSS(dataM); - NSS(dataS); - NSS(enabled); - NSS(lastLatchData); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "rtc.h" +#include "../savestate.h" +#include + +namespace gambatte { + +Rtc::Rtc() +: activeData(NULL), + activeSet(NULL), + baseTime(0), + haltTime(0), + index(5), + dataDh(0), + dataDl(0), + dataH(0), + dataM(0), + dataS(0), + enabled(false), + lastLatchData(false), + timeCB(0) +{ +} + +void Rtc::doLatch() { + std::uint32_t tmp = ((dataDh & 0x40) ? haltTime : timeCB()) - baseTime; + + while (tmp > 0x1FF * 86400) { + baseTime += 0x1FF * 86400; + tmp -= 0x1FF * 86400; + dataDh |= 0x80; + } + + dataDl = (tmp / 86400) & 0xFF; + dataDh &= 0xFE; + dataDh |= ((tmp / 86400) & 0x100) >> 8; + tmp %= 86400; + + dataH = tmp / 3600; + tmp %= 3600; + + dataM = tmp / 60; + tmp %= 60; + + dataS = tmp; +} + +void Rtc::doSwapActive() { + if (!enabled || index > 4) { + activeData = NULL; + activeSet = NULL; + } else switch (index) { + case 0x00: + activeData = &dataS; + activeSet = &Rtc::setS; + break; + case 0x01: + activeData = &dataM; + activeSet = &Rtc::setM; + break; + case 0x02: + activeData = &dataH; + activeSet = &Rtc::setH; + break; + case 0x03: + activeData = &dataDl; + activeSet = &Rtc::setDl; + break; + case 0x04: + activeData = &dataDh; + activeSet = &Rtc::setDh; + break; + } +} + +void Rtc::loadState(const SaveState &state) { + baseTime = state.rtc.baseTime; + haltTime = state.rtc.haltTime; + dataDh = state.rtc.dataDh; + dataDl = state.rtc.dataDl; + dataH = state.rtc.dataH; + dataM = state.rtc.dataM; + dataS = state.rtc.dataS; + lastLatchData = state.rtc.lastLatchData; + + doSwapActive(); +} + +void Rtc::setDh(const unsigned new_dh) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_highdays = ((unixtime - baseTime) / 86400) & 0x100; + baseTime += old_highdays * 86400; + baseTime -= ((new_dh & 0x1) << 8) * 86400; + + if ((dataDh ^ new_dh) & 0x40) { + if (new_dh & 0x40) + haltTime = timeCB(); + else + baseTime += timeCB() - haltTime; + } +} + +void Rtc::setDl(const unsigned new_lowdays) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_lowdays = ((unixtime - baseTime) / 86400) & 0xFF; + baseTime += old_lowdays * 86400; + baseTime -= new_lowdays * 86400; +} + +void Rtc::setH(const unsigned new_hours) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_hours = ((unixtime - baseTime) / 3600) % 24; + baseTime += old_hours * 3600; + baseTime -= new_hours * 3600; +} + +void Rtc::setM(const unsigned new_minutes) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_minutes = ((unixtime - baseTime) / 60) % 60; + baseTime += old_minutes * 60; + baseTime -= new_minutes * 60; +} + +void Rtc::setS(const unsigned new_seconds) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + baseTime += (unixtime - baseTime) % 60; + baseTime -= new_seconds; +} + +SYNCFUNC(Rtc) +{ + EBS(activeData, 0); + EVS(activeData, &dataS, 1); + EVS(activeData, &dataM, 2); + EVS(activeData, &dataH, 3); + EVS(activeData, &dataDl, 4); + EVS(activeData, &dataDh, 5); + EES(activeData, NULL); + + EBS(activeSet, 0); + EVS(activeSet, &Rtc::setS, 1); + EVS(activeSet, &Rtc::setM, 2); + EVS(activeSet, &Rtc::setH, 3); + EVS(activeSet, &Rtc::setDl, 4); + EVS(activeSet, &Rtc::setDh, 5); + EES(activeSet, NULL); + + NSS(baseTime); + NSS(haltTime); + NSS(index); + NSS(dataDh); + NSS(dataDl); + NSS(dataH); + NSS(dataM); + NSS(dataS); + NSS(enabled); + NSS(lastLatchData); +} + +} diff --git a/libgambatte/src/mem/rtc.h b/libgambatte/src/mem/rtc.h index 00652a2181..cdbd7e66d0 100644 --- a/libgambatte/src/mem/rtc.h +++ b/libgambatte/src/mem/rtc.h @@ -1,98 +1,98 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef RTC_H -#define RTC_H - -#include -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Rtc { -private: - unsigned char *activeData; - void (Rtc::*activeSet)(unsigned); - std::uint32_t baseTime; - std::uint32_t haltTime; - unsigned char index; - unsigned char dataDh; - unsigned char dataDl; - unsigned char dataH; - unsigned char dataM; - unsigned char dataS; - bool enabled; - bool lastLatchData; - std::uint32_t (*timeCB)(); - - void doLatch(); - void doSwapActive(); - void setDh(unsigned new_dh); - void setDl(unsigned new_lowdays); - void setH(unsigned new_hours); - void setM(unsigned new_minutes); - void setS(unsigned new_seconds); - -public: - Rtc(); - - const unsigned char* getActive() const { return activeData; } - std::uint32_t getBaseTime() const { return baseTime; } - - void setBaseTime(const std::uint32_t baseTime) { - this->baseTime = baseTime; -// doLatch(); - } - - void latch(const unsigned data) { - if (!lastLatchData && data == 1) - doLatch(); - - lastLatchData = data; - } - - void loadState(const SaveState &state); - - void set(const bool enabled, unsigned bank) { - bank &= 0xF; - bank -= 8; - - this->enabled = enabled; - this->index = bank; - - doSwapActive(); - } - - void write(const unsigned data) { -// if (activeSet) - (this->*activeSet)(data); - *activeData = data; - } - - void setRTCCallback(std::uint32_t (*callback)()) { - timeCB = callback; - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RTC_H +#define RTC_H + +#include +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Rtc { +private: + unsigned char *activeData; + void (Rtc::*activeSet)(unsigned); + std::uint32_t baseTime; + std::uint32_t haltTime; + unsigned char index; + unsigned char dataDh; + unsigned char dataDl; + unsigned char dataH; + unsigned char dataM; + unsigned char dataS; + bool enabled; + bool lastLatchData; + std::uint32_t (*timeCB)(); + + void doLatch(); + void doSwapActive(); + void setDh(unsigned new_dh); + void setDl(unsigned new_lowdays); + void setH(unsigned new_hours); + void setM(unsigned new_minutes); + void setS(unsigned new_seconds); + +public: + Rtc(); + + const unsigned char* getActive() const { return activeData; } + std::uint32_t getBaseTime() const { return baseTime; } + + void setBaseTime(const std::uint32_t baseTime) { + this->baseTime = baseTime; +// doLatch(); + } + + void latch(const unsigned data) { + if (!lastLatchData && data == 1) + doLatch(); + + lastLatchData = data; + } + + void loadState(const SaveState &state); + + void set(const bool enabled, unsigned bank) { + bank &= 0xF; + bank -= 8; + + this->enabled = enabled; + this->index = bank; + + doSwapActive(); + } + + void write(const unsigned data) { +// if (activeSet) + (this->*activeSet)(data); + *activeData = data; + } + + void setRTCCallback(std::uint32_t (*callback)()) { + timeCB = callback; + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 0a52eb6686..a5fcc148ef 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -1,1118 +1,1161 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "memory.h" -#include "video.h" -#include "sound.h" -#include "savestate.h" -#include - -namespace gambatte { - -Memory::Memory(const Interrupter &interrupter_in) -: readCallback(0), - writeCallback(0), - execCallback(0), - cdCallback(0), - linkCallback(0), - getInput(0), - divLastUpdate(0), - lastOamDmaUpdate(DISABLED_TIME), - display(ioamhram, 0, VideoInterruptRequester(&intreq)), - interrupter(interrupter_in), - dmaSource(0), - dmaDestination(0), - oamDmaPos(0xFE), - serialCnt(0), - blanklcd(false), - LINKCABLE(false), - linkClockTrigger(false) -{ - intreq.setEventTime(144*456ul); - intreq.setEventTime(0); -} - -void Memory::setStatePtrs(SaveState &state) { - state.mem.ioamhram.set(ioamhram, sizeof ioamhram); - - cart.setStatePtrs(state); - display.setStatePtrs(state); - sound.setStatePtrs(state); -} - - -static inline int serialCntFrom(const unsigned long cyclesUntilDone, const bool cgbFast) { - return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; -} - -void Memory::loadState(const SaveState &state) { - sound.loadState(state); - display.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart.rdisabledRam() : ioamhram); - tima.loadState(state, TimaInterruptRequester(intreq)); - cart.loadState(state); - intreq.loadState(state); - - divLastUpdate = state.mem.divLastUpdate; - intreq.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); - intreq.setEventTime(state.mem.unhaltTime); - lastOamDmaUpdate = state.mem.lastOamDmaUpdate; - dmaSource = state.mem.dmaSource; - dmaDestination = state.mem.dmaDestination; - oamDmaPos = state.mem.oamDmaPos; - serialCnt = intreq.eventTime(SERIAL) != DISABLED_TIME - ? serialCntFrom(intreq.eventTime(SERIAL) - state.cpu.cycleCounter, ioamhram[0x102] & isCgb() * 2) - : 8; - - cart.setVrambank(ioamhram[0x14F] & isCgb()); - cart.setOamDmaSrc(OAM_DMA_SRC_OFF); - cart.setWrambank(isCgb() && (ioamhram[0x170] & 0x07) ? ioamhram[0x170] & 0x07 : 1); - - if (lastOamDmaUpdate != DISABLED_TIME) { - oamDmaInitSetup(); - - const unsigned oamEventPos = oamDmaPos < 0xA0 ? 0xA0 : 0x100; - - intreq.setEventTime(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4); - } - - intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : state.cpu.cycleCounter); - blanklcd = false; - - if (!isCgb()) - std::memset(cart.vramdata() + 0x2000, 0, 0x2000); -} - -void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { - if (intreq.eventTime(BLIT) <= cycleCounter) - intreq.setEventTime(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed())); - - intreq.setEventTime(cycleCounter + (inc << isDoubleSpeed())); -} - -void Memory::updateSerial(const unsigned long cc) { - if (!LINKCABLE) { - if (intreq.eventTime(SERIAL) != DISABLED_TIME) { - if (intreq.eventTime(SERIAL) <= cc) { - ioamhram[0x101] = (((ioamhram[0x101] + 1) << serialCnt) - 1) & 0xFF; - ioamhram[0x102] &= 0x7F; - intreq.setEventTime(DISABLED_TIME); - intreq.flagIrq(8); - } else { - const int targetCnt = serialCntFrom(intreq.eventTime(SERIAL) - cc, ioamhram[0x102] & isCgb() * 2); - ioamhram[0x101] = (((ioamhram[0x101] + 1) << (serialCnt - targetCnt)) - 1) & 0xFF; - serialCnt = targetCnt; - } - } - } - else { - if (intreq.eventTime(SERIAL) != DISABLED_TIME) { - if (intreq.eventTime(SERIAL) <= cc) { - linkClockTrigger = true; - intreq.setEventTime(DISABLED_TIME); - if (linkCallback) - linkCallback(); - } - } - } -} - -void Memory::updateTimaIrq(const unsigned long cc) { - while (intreq.eventTime(TIMA) <= cc) - tima.doIrqEvent(TimaInterruptRequester(intreq)); -} - -void Memory::updateIrqs(const unsigned long cc) { - updateSerial(cc); - updateTimaIrq(cc); - display.update(cc); -} - -unsigned long Memory::event(unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (intreq.minEventId()) { - case UNHALT: - intreq.unhalt(); - intreq.setEventTime(DISABLED_TIME); - break; - case END: - intreq.setEventTime(DISABLED_TIME - 1); - - while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME) - cycleCounter = event(cycleCounter); - - intreq.setEventTime(DISABLED_TIME); - - break; - case BLIT: - { - const bool lcden = ioamhram[0x140] >> 7 & 1; - unsigned long blitTime = intreq.eventTime(BLIT); - - if (lcden | blanklcd) { - display.updateScreen(blanklcd, cycleCounter); - intreq.setEventTime(DISABLED_TIME); - intreq.setEventTime(DISABLED_TIME); - - while (cycleCounter >= intreq.minEventTime()) - cycleCounter = event(cycleCounter); - } else - blitTime += 70224 << isDoubleSpeed(); - - blanklcd = lcden ^ 1; - intreq.setEventTime(blitTime); - } - break; - case SERIAL: - updateSerial(cycleCounter); - break; - case OAM: - intreq.setEventTime(lastOamDmaUpdate == DISABLED_TIME ? - static_cast(DISABLED_TIME) : intreq.eventTime(OAM) + 0xA0 * 4); - break; - case DMA: - { - const bool doubleSpeed = isDoubleSpeed(); - unsigned dmaSrc = dmaSource; - unsigned dmaDest = dmaDestination; - unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; - unsigned length = hdmaReqFlagged(intreq) ? 0x10 : dmaLength; - - ackDmaReq(&intreq); - - if ((static_cast(dmaDest) + length) & 0x10000) { - length = 0x10000 - dmaDest; - ioamhram[0x155] |= 0x80; - } - - dmaLength -= length; - - if (!(ioamhram[0x140] & 0x80)) - dmaLength = 0; - - { - unsigned long lOamDmaUpdate = lastOamDmaUpdate; - lastOamDmaUpdate = DISABLED_TIME; - - while (length--) { - const unsigned src = dmaSrc++ & 0xFFFF; - const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); - - cycleCounter += 2 << doubleSpeed; - - if (cycleCounter - 3 > lOamDmaUpdate) { - oamDmaPos = (oamDmaPos + 1) & 0xFF; - lOamDmaUpdate += 4; - - if (oamDmaPos < 0xA0) { - if (oamDmaPos == 0) - startOamDma(lOamDmaUpdate - 1); - - ioamhram[src & 0xFF] = data; - } else if (oamDmaPos == 0xA0) { - endOamDma(lOamDmaUpdate - 1); - lOamDmaUpdate = DISABLED_TIME; - } - } - - nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); - } - - lastOamDmaUpdate = lOamDmaUpdate; - } - - cycleCounter += 4; - - dmaSource = dmaSrc; - dmaDestination = dmaDest; - ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); - - if ((ioamhram[0x155] & 0x80) && display.hdmaIsEnabled()) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - display.disableHdma(cycleCounter); - } - } - - break; - case TIMA: - tima.doIrqEvent(TimaInterruptRequester(intreq)); - break; - case VIDEO: - display.update(cycleCounter); - break; - case INTERRUPTS: - if (halted()) { - if (isCgb()) - cycleCounter += 4; - - intreq.unhalt(); - intreq.setEventTime(DISABLED_TIME); - } - - if (ime()) { - unsigned address; - const unsigned pendingIrqs = intreq.pendingIrqs(); - const unsigned n = pendingIrqs & -pendingIrqs; - - if (n < 8) { - static const unsigned char lut[] = { 0x40, 0x48, 0x48, 0x50 }; - address = lut[n-1]; - } else - address = 0x50 + n; - - intreq.ackIrq(n); - cycleCounter = interrupter.interrupt(address, cycleCounter, *this); - } - - break; - } - - return cycleCounter; -} - -unsigned long Memory::stop(unsigned long cycleCounter) { - cycleCounter += 4 << isDoubleSpeed(); - - if (ioamhram[0x14D] & isCgb()) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - - display.speedChange(cycleCounter); - ioamhram[0x14D] ^= 0x81; - - intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed())); - - if (intreq.eventTime(END) > cycleCounter) { - intreq.setEventTime(cycleCounter + (isDoubleSpeed() ? - (intreq.eventTime(END) - cycleCounter) << 1 : (intreq.eventTime(END) - cycleCounter) >> 1)); - } - // when switching speed, it seems that the CPU spontaneously restarts soon? - // otherwise, the cpu should be allowed to stay halted as long as needed - // so only execute this line when switching speed - intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); - } - - intreq.halt(); - - return cycleCounter; -} - -static void decCycles(unsigned long &counter, const unsigned long dec) { - if (counter != DISABLED_TIME) - counter -= dec; -} - -void Memory::decEventCycles(const MemEventId eventId, const unsigned long dec) { - if (intreq.eventTime(eventId) != DISABLED_TIME) - intreq.setEventTime(eventId, intreq.eventTime(eventId) - dec); -} - -unsigned long Memory::resetCounters(unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - updateIrqs(cycleCounter); - - const unsigned long oldCC = cycleCounter; - - { - const unsigned long divinc = (cycleCounter - divLastUpdate) >> 8; - ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; - divLastUpdate += divinc << 8; - } - - const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; - - decCycles(divLastUpdate, dec); - decCycles(lastOamDmaUpdate, dec); - decEventCycles(SERIAL, dec); - decEventCycles(OAM, dec); - decEventCycles(BLIT, dec); - decEventCycles(END, dec); - decEventCycles(UNHALT, dec); - - cycleCounter -= dec; - - intreq.resetCc(oldCC, cycleCounter); - tima.resetCc(oldCC, cycleCounter, TimaInterruptRequester(intreq)); - display.resetCc(oldCC, cycleCounter); - sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); - - return cycleCounter; -} - -void Memory::updateInput() { - unsigned state = 0xF; - - if ((ioamhram[0x100] & 0x30) != 0x30 && getInput) { - unsigned input = (*getInput)(); - unsigned dpad_state = ~input >> 4; - unsigned button_state = ~input; - if (!(ioamhram[0x100] & 0x10)) - state &= dpad_state; - if (!(ioamhram[0x100] & 0x20)) - state &= button_state; - } - - if (state != 0xF && (ioamhram[0x100] & 0xF) == 0xF) - intreq.flagIrq(0x10); - - ioamhram[0x100] = (ioamhram[0x100] & -0x10u) | state; -} - -void Memory::updateOamDma(const unsigned long cycleCounter) { - const unsigned char *const oamDmaSrc = oamDmaSrcPtr(); - unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; - - while (cycles--) { - oamDmaPos = (oamDmaPos + 1) & 0xFF; - lastOamDmaUpdate += 4; - - if (oamDmaPos < 0xA0) { - if (oamDmaPos == 0) - startOamDma(lastOamDmaUpdate - 1); - - ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : cart.rtcRead(); - } else if (oamDmaPos == 0xA0) { - endOamDma(lastOamDmaUpdate - 1); - lastOamDmaUpdate = DISABLED_TIME; - break; - } - } -} - -void Memory::oamDmaInitSetup() { - if (ioamhram[0x146] < 0xA0) { - cart.setOamDmaSrc(ioamhram[0x146] < 0x80 ? OAM_DMA_SRC_ROM : OAM_DMA_SRC_VRAM); - } else if (ioamhram[0x146] < 0xFE - isCgb() * 0x1E) { - cart.setOamDmaSrc(ioamhram[0x146] < 0xC0 ? OAM_DMA_SRC_SRAM : OAM_DMA_SRC_WRAM); - } else - cart.setOamDmaSrc(OAM_DMA_SRC_INVALID); -} - -static const unsigned char * oamDmaSrcZero() { - static unsigned char zeroMem[0xA0]; - return zeroMem; -} - -const unsigned char * Memory::oamDmaSrcPtr() const { - switch (cart.oamDmaSrc()) { - case OAM_DMA_SRC_ROM: return cart.romdata(ioamhram[0x146] >> 6) + (ioamhram[0x146] << 8); - case OAM_DMA_SRC_SRAM: return cart.rsrambankptr() ? cart.rsrambankptr() + (ioamhram[0x146] << 8) : 0; - case OAM_DMA_SRC_VRAM: return cart.vrambankptr() + (ioamhram[0x146] << 8); - case OAM_DMA_SRC_WRAM: return cart.wramdata(ioamhram[0x146] >> 4 & 1) + (ioamhram[0x146] << 8 & 0xFFF); - case OAM_DMA_SRC_INVALID: - case OAM_DMA_SRC_OFF: break; - } - - return ioamhram[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart.rdisabledRam(); -} - -void Memory::startOamDma(const unsigned long cycleCounter) { - display.oamChange(cart.rdisabledRam(), cycleCounter); -} - -void Memory::endOamDma(const unsigned long cycleCounter) { - oamDmaPos = 0xFE; - cart.setOamDmaSrc(OAM_DMA_SRC_OFF); - display.oamChange(ioamhram, cycleCounter); -} - -unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (P & 0x7F) { - case 0x00: - updateInput(); - break; - case 0x01: - case 0x02: - updateSerial(cycleCounter); - break; - case 0x04: - { - const unsigned long divcycles = (cycleCounter - divLastUpdate) >> 8; - ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; - divLastUpdate += divcycles << 8; - } - - break; - case 0x05: - ioamhram[0x105] = tima.tima(cycleCounter); - break; - case 0x0F: - updateIrqs(cycleCounter); - ioamhram[0x10F] = intreq.ifreg(); - break; - case 0x26: - if (ioamhram[0x126] & 0x80) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - ioamhram[0x126] = 0xF0 | sound.getStatus(); - } else - ioamhram[0x126] = 0x70; - - break; - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - sound.generate_samples(cycleCounter, isDoubleSpeed()); - return sound.waveRamRead(P & 0xF); - case 0x41: - return ioamhram[0x141] | display.getStat(ioamhram[0x145], cycleCounter); - case 0x44: - return display.getLyReg(cycleCounter/*+4*/); - case 0x69: - return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); - case 0x6B: - return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); - default: break; - } - - return ioamhram[P - 0xFE00]; -} - -static bool isInOamDmaConflictArea(const OamDmaSrc oamDmaSrc, const unsigned addr, const bool cgb) { - struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; - - static const Area cgbAreas[] = { - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x0000, 0xC000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - static const Area dmgAreas[] = { - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - const Area *const a = cgb ? cgbAreas : dmgAreas; - - return addr < a[oamDmaSrc].areaUpper && addr - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; -} - -unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { - if (P < 0xFF80) { - if (lastOamDmaUpdate != DISABLED_TIME) { - updateOamDma(cycleCounter); - - if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) - return ioamhram[oamDmaPos]; - } - - if (P < 0xC000) { - if (P < 0x8000) - return cart.romdata(P >> 14)[P]; - - if (P < 0xA000) { - if (!display.vramAccessible(cycleCounter)) - return 0xFF; - - return cart.vrambankptr()[P]; - } - - if (cart.rsrambankptr()) - return cart.rsrambankptr()[P]; - - return cart.rtcRead(); - } - - if (P < 0xFE00) - return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; - - if (P >= 0xFF00) - return nontrivial_ff_read(P, cycleCounter); - - if (!display.oamReadable(cycleCounter) || oamDmaPos < 0xA0) - return 0xFF; - } - - return ioamhram[P - 0xFE00]; -} - -unsigned Memory::nontrivial_peek(const unsigned P) { - if (P < 0xC000) { - if (P < 0x8000) - return cart.romdata(P >> 14)[P]; - - if (P < 0xA000) { - return cart.vrambankptr()[P]; - } - - if (cart.rsrambankptr()) - return cart.rsrambankptr()[P]; - - return cart.rtcRead(); // verified side-effect free - } - if (P < 0xFE00) - return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; - if (P >= 0xFF00 && P < 0xFF80) - return nontrivial_ff_peek(P); - return ioamhram[P - 0xFE00]; -} - -unsigned Memory::nontrivial_ff_peek(const unsigned P) { - // some regs may be somewhat wrong with this - return ioamhram[P - 0xFE00]; -} - -void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (P & 0xFF) { - case 0x00: - if ((data ^ ioamhram[0x100]) & 0x30) { - ioamhram[0x100] = (ioamhram[0x100] & ~0x30u) | (data & 0x30); - updateInput(); - } - return; - case 0x01: - updateSerial(cycleCounter); - break; - case 0x02: - updateSerial(cycleCounter); - - serialCnt = 8; - intreq.setEventTime((data & 0x81) == 0x81 - ? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8) - : static_cast(DISABLED_TIME)); - - data |= 0x7E - isCgb() * 2; - break; - case 0x04: - ioamhram[0x104] = 0; - divLastUpdate = cycleCounter; - return; - case 0x05: - tima.setTima(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x06: - tima.setTma(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x07: - data |= 0xF8; - tima.setTac(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x0F: - updateIrqs(cycleCounter); - intreq.setIfreg(0xE0 | data); - return; - case 0x10: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr10(data); - data |= 0x80; - break; - case 0x11: - if (!sound.isEnabled()) { - if (isCgb()) - return; - - data &= 0x3F; - } - - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr11(data); - data |= 0x3F; - break; - case 0x12: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr12(data); - break; - case 0x13: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr13(data); - return; - case 0x14: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr14(data); - data |= 0xBF; - break; - case 0x16: - if (!sound.isEnabled()) { - if (isCgb()) - return; - - data &= 0x3F; - } - - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr21(data); - data |= 0x3F; - break; - case 0x17: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr22(data); - break; - case 0x18: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr23(data); - return; - case 0x19: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr24(data); - data |= 0xBF; - break; - case 0x1A: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr30(data); - data |= 0x7F; - break; - case 0x1B: - if (!sound.isEnabled() && isCgb()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr31(data); - return; - case 0x1C: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr32(data); - data |= 0x9F; - break; - case 0x1D: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr33(data); - return; - case 0x1E: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr34(data); - data |= 0xBF; - break; - case 0x20: - if (!sound.isEnabled() && isCgb()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr41(data); - return; - case 0x21: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr42(data); - break; - case 0x22: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr43(data); - break; - case 0x23: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr44(data); - data |= 0xBF; - break; - case 0x24: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_so_volume(data); - break; - case 0x25: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.map_so(data); - break; - case 0x26: - if ((ioamhram[0x126] ^ data) & 0x80) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - - if (!(data & 0x80)) { - for (unsigned i = 0xFF10; i < 0xFF26; ++i) - ff_write(i, 0, cycleCounter); - - sound.setEnabled(false); - } else { - sound.reset(); - sound.setEnabled(true); - } - } - - data = (data & 0x80) | (ioamhram[0x126] & 0x7F); - break; - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.waveRamWrite(P & 0xF, data); - break; - case 0x40: - if (ioamhram[0x140] != data) { - if ((ioamhram[0x140] ^ data) & 0x80) { - const unsigned lyc = display.getStat(ioamhram[0x145], cycleCounter) & 4; - const bool hdmaEnabled = display.hdmaIsEnabled(); - - display.lcdcChange(data, cycleCounter); - ioamhram[0x144] = 0; - ioamhram[0x141] &= 0xF8; - - if (data & 0x80) { - intreq.setEventTime(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed())); - } else { - ioamhram[0x141] |= lyc; - intreq.setEventTime(cycleCounter + (456 * 4 << isDoubleSpeed())); - - if (hdmaEnabled) - flagHdmaReq(&intreq); - } - } else - display.lcdcChange(data, cycleCounter); - - ioamhram[0x140] = data; - } - - return; - case 0x41: - display.lcdstatChange(data, cycleCounter); - data = (ioamhram[0x141] & 0x87) | (data & 0x78); - break; - case 0x42: - display.scyChange(data, cycleCounter); - break; - case 0x43: - display.scxChange(data, cycleCounter); - break; - case 0x45: - display.lycRegChange(data, cycleCounter); - break; - case 0x46: - if (lastOamDmaUpdate != DISABLED_TIME) - endOamDma(cycleCounter); - - lastOamDmaUpdate = cycleCounter; - intreq.setEventTime(cycleCounter + 8); - ioamhram[0x146] = data; - oamDmaInitSetup(); - return; - case 0x47: - if (!isCgb()) - display.dmgBgPaletteChange(data, cycleCounter); - - break; - case 0x48: - if (!isCgb()) - display.dmgSpPalette1Change(data, cycleCounter); - - break; - case 0x49: - if (!isCgb()) - display.dmgSpPalette2Change(data, cycleCounter); - - break; - case 0x4A: - display.wyChange(data, cycleCounter); - break; - case 0x4B: - display.wxChange(data, cycleCounter); - break; - - case 0x4D: - if (isCgb()) - ioamhram[0x14D] = (ioamhram[0x14D] & ~1u) | (data & 1); return; - case 0x4F: - if (isCgb()) { - cart.setVrambank(data & 1); - ioamhram[0x14F] = 0xFE | data; - } - - return; - case 0x50: - // this is the register that turns off the bootrom - // it can only ever be written to once (with 1) once boot rom finishes - return; - case 0x51: - dmaSource = data << 8 | (dmaSource & 0xFF); - return; - case 0x52: - dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); - return; - case 0x53: - dmaDestination = data << 8 | (dmaDestination & 0xFF); - return; - case 0x54: - dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); - return; - case 0x55: - if (isCgb()) { - ioamhram[0x155] = data & 0x7F; - - if (display.hdmaIsEnabled()) { - if (!(data & 0x80)) { - ioamhram[0x155] |= 0x80; - display.disableHdma(cycleCounter); - } - } else { - if (data & 0x80) { - if (ioamhram[0x140] & 0x80) { - display.enableHdma(cycleCounter); - } else - flagHdmaReq(&intreq); - } else - flagGdmaReq(&intreq); - } - } - - return; - case 0x56: - if (isCgb()) - ioamhram[0x156] = data | 0x3E; - - return; - case 0x68: - if (isCgb()) - ioamhram[0x168] = data | 0x40; - - return; - case 0x69: - if (isCgb()) { - const unsigned index = ioamhram[0x168] & 0x3F; - - display.cgbBgColorChange(index, data, cycleCounter); - - ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); - } - - return; - case 0x6A: - if (isCgb()) - ioamhram[0x16A] = data | 0x40; - - return; - case 0x6B: - if (isCgb()) { - const unsigned index = ioamhram[0x16A] & 0x3F; - - display.cgbSpColorChange(index, data, cycleCounter); - - ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); - } - - return; - case 0x6C: - if (isCgb()) - ioamhram[0x16C] = data | 0xFE; - - return; - case 0x70: - if (isCgb()) { - cart.setWrambank((data & 0x07) ? (data & 0x07) : 1); - ioamhram[0x170] = data | 0xF8; - } - - return; - case 0x72: - case 0x73: - case 0x74: - if (isCgb()) - break; - - return; - case 0x75: - if (isCgb()) - ioamhram[0x175] = data | 0x8F; - - return; - case 0xFF: - intreq.setIereg(data); - break; - default: - return; - } - - ioamhram[P - 0xFE00] = data; -} - -void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) { - updateOamDma(cycleCounter); - - if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) { - ioamhram[oamDmaPos] = data; - return; - } - } - - if (P < 0xFE00) { - if (P < 0xA000) { - if (P < 0x8000) { - cart.mbcWrite(P, data); - } else if (display.vramAccessible(cycleCounter)) { - display.vramChange(cycleCounter); - cart.vrambankptr()[P] = data; - } - } else if (P < 0xC000) { - if (cart.wsrambankptr()) - cart.wsrambankptr()[P] = data; - else - cart.rtcWrite(data); - } else - cart.wramdata(P >> 12 & 1)[P & 0xFFF] = data; - } else if (P - 0xFF80u >= 0x7Fu) { - if (P < 0xFF00) { - if (display.oamWritable(cycleCounter) && oamDmaPos >= 0xA0 && (P < 0xFEA0 || isCgb())) { - display.oamChange(cycleCounter); - ioamhram[P - 0xFE00] = data; - } - } else - nontrivial_ff_write(P, data, cycleCounter); - } else - ioamhram[P - 0xFE00] = data; -} - -int Memory::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { - if (const int fail = cart.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) - return fail; - - sound.init(cart.isCgb()); - display.reset(ioamhram, cart.vramdata(), cart.isCgb()); - - return 0; -} - -unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - return sound.fillBuffer(); -} - -void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { - display.setDmgPaletteColor(palNum, colorNum, rgb32); -} - -void Memory::setCgbPalette(unsigned *lut) { - display.setCgbPalette(lut); -} - -bool Memory::getMemoryArea(int which, unsigned char **data, int *length) { - if (!data || !length) - return false; - - switch (which) - { - case 4: // oam - *data = &ioamhram[0]; - *length = 160; - return true; - case 5: // hram - *data = &ioamhram[384]; - *length = 128; - return true; - case 6: // bgpal - *data = (unsigned char *)display.bgPalette(); - *length = 32; - return true; - case 7: // sppal - *data = (unsigned char *)display.spPalette(); - *length = 32; - return true; - default: // pass to cartridge - return cart.getMemoryArea(which, data, length); - } -} - -int Memory::LinkStatus(int which) -{ - switch (which) - { - case 256: // ClockSignaled - return linkClockTrigger; - case 257: // AckClockSignal - linkClockTrigger = false; - return 0; - case 258: // GetOut - return ioamhram[0x101] & 0xff; - case 259: // connect link cable - LINKCABLE = true; - return 0; - default: // ShiftIn - if (ioamhram[0x102] & 0x80) // was enabled - { - ioamhram[0x101] = which; - ioamhram[0x102] &= 0x7F; - intreq.flagIrq(8); - } - return 0; - } - - return -1; -} - -SYNCFUNC(Memory) -{ - SSS(cart); - NSS(ioamhram); - NSS(divLastUpdate); - NSS(lastOamDmaUpdate); - - SSS(intreq); - SSS(tima); - SSS(display); - SSS(sound); - //SSS(interrupter); // no state - - NSS(dmaSource); - NSS(dmaDestination); - NSS(oamDmaPos); - NSS(serialCnt); - NSS(blanklcd); - - NSS(LINKCABLE); - NSS(linkClockTrigger); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "memory.h" +#include "video.h" +#include "sound.h" +#include "savestate.h" +#include + +namespace gambatte { + +Memory::Memory(const Interrupter &interrupter_in, unsigned short &sp, unsigned short &pc) +: readCallback(0), + writeCallback(0), + execCallback(0), + cdCallback(0), + linkCallback(0), + getInput(0), + divLastUpdate(0), + lastOamDmaUpdate(DISABLED_TIME), + display(ioamhram, 0, VideoInterruptRequester(&intreq)), + interrupter(interrupter_in), + dmaSource(0), + dmaDestination(0), + oamDmaPos(0xFE), + serialCnt(0), + blanklcd(false), + LINKCABLE(false), + linkClockTrigger(false), + SP(sp), + PC(pc) +{ + intreq.setEventTime(144*456ul); + intreq.setEventTime(0); +} + +void Memory::setStatePtrs(SaveState &state) { + state.mem.ioamhram.set(ioamhram, sizeof ioamhram); + + cart.setStatePtrs(state); + display.setStatePtrs(state); + sound.setStatePtrs(state); +} + + +static inline int serialCntFrom(const unsigned long cyclesUntilDone, const bool cgbFast) { + return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; +} + +void Memory::loadState(const SaveState &state) { + biosMode = state.mem.biosMode; + cgbSwitching = state.mem.cgbSwitching; + agbMode = state.mem.agbMode; + gbIsCgb_ = state.mem.gbIsCgb; + sound.loadState(state); + display.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart.rdisabledRam() : ioamhram); + tima.loadState(state, TimaInterruptRequester(intreq)); + cart.loadState(state); + intreq.loadState(state); + + divLastUpdate = state.mem.divLastUpdate; + intreq.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); + intreq.setEventTime(state.mem.unhaltTime); + lastOamDmaUpdate = state.mem.lastOamDmaUpdate; + dmaSource = state.mem.dmaSource; + dmaDestination = state.mem.dmaDestination; + oamDmaPos = state.mem.oamDmaPos; + serialCnt = intreq.eventTime(SERIAL) != DISABLED_TIME + ? serialCntFrom(intreq.eventTime(SERIAL) - state.cpu.cycleCounter, ioamhram[0x102] & isCgb() * 2) + : 8; + + cart.setVrambank(ioamhram[0x14F] & isCgb()); + cart.setOamDmaSrc(OAM_DMA_SRC_OFF); + cart.setWrambank(isCgb() && (ioamhram[0x170] & 0x07) ? ioamhram[0x170] & 0x07 : 1); + + if (lastOamDmaUpdate != DISABLED_TIME) { + oamDmaInitSetup(); + + const unsigned oamEventPos = oamDmaPos < 0xA0 ? 0xA0 : 0x100; + + intreq.setEventTime(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4); + } + + intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : state.cpu.cycleCounter); + blanklcd = false; + + if (!isCgb()) + std::memset(cart.vramdata() + 0x2000, 0, 0x2000); +} + +void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { + if (intreq.eventTime(BLIT) <= cycleCounter) + intreq.setEventTime(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed())); + + intreq.setEventTime(cycleCounter + (inc << isDoubleSpeed())); +} + +void Memory::updateSerial(const unsigned long cc) { + if (!LINKCABLE) { + if (intreq.eventTime(SERIAL) != DISABLED_TIME) { + if (intreq.eventTime(SERIAL) <= cc) { + ioamhram[0x101] = (((ioamhram[0x101] + 1) << serialCnt) - 1) & 0xFF; + ioamhram[0x102] &= 0x7F; + intreq.setEventTime(DISABLED_TIME); + intreq.flagIrq(8); + } else { + const int targetCnt = serialCntFrom(intreq.eventTime(SERIAL) - cc, ioamhram[0x102] & isCgb() * 2); + ioamhram[0x101] = (((ioamhram[0x101] + 1) << (serialCnt - targetCnt)) - 1) & 0xFF; + serialCnt = targetCnt; + } + } + } + else { + if (intreq.eventTime(SERIAL) != DISABLED_TIME) { + if (intreq.eventTime(SERIAL) <= cc) { + linkClockTrigger = true; + intreq.setEventTime(DISABLED_TIME); + if (linkCallback) + linkCallback(); + } + } + } +} + +void Memory::updateTimaIrq(const unsigned long cc) { + while (intreq.eventTime(TIMA) <= cc) + tima.doIrqEvent(TimaInterruptRequester(intreq)); +} + +void Memory::updateIrqs(const unsigned long cc) { + updateSerial(cc); + updateTimaIrq(cc); + display.update(cc); +} + +unsigned long Memory::event(unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (intreq.minEventId()) { + case UNHALT: + intreq.unhalt(); + intreq.setEventTime(DISABLED_TIME); + break; + case END: + intreq.setEventTime(DISABLED_TIME - 1); + + while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME) + cycleCounter = event(cycleCounter); + + intreq.setEventTime(DISABLED_TIME); + + break; + case BLIT: + { + const bool lcden = ioamhram[0x140] >> 7 & 1; + unsigned long blitTime = intreq.eventTime(BLIT); + + if (lcden | blanklcd) { + display.updateScreen(blanklcd, cycleCounter); + intreq.setEventTime(DISABLED_TIME); + intreq.setEventTime(DISABLED_TIME); + + while (cycleCounter >= intreq.minEventTime()) + cycleCounter = event(cycleCounter); + } else + blitTime += 70224 << isDoubleSpeed(); + + blanklcd = lcden ^ 1; + intreq.setEventTime(blitTime); + } + break; + case SERIAL: + updateSerial(cycleCounter); + break; + case OAM: + intreq.setEventTime(lastOamDmaUpdate == DISABLED_TIME ? + static_cast(DISABLED_TIME) : intreq.eventTime(OAM) + 0xA0 * 4); + break; + case DMA: + { + const bool doubleSpeed = isDoubleSpeed(); + unsigned dmaSrc = dmaSource; + unsigned dmaDest = dmaDestination; + unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; + unsigned length = hdmaReqFlagged(intreq) ? 0x10 : dmaLength; + + ackDmaReq(&intreq); + + if ((static_cast(dmaDest) + length) & 0x10000) { + length = 0x10000 - dmaDest; + ioamhram[0x155] |= 0x80; + } + + dmaLength -= length; + + if (!(ioamhram[0x140] & 0x80)) + dmaLength = 0; + + { + unsigned long lOamDmaUpdate = lastOamDmaUpdate; + lastOamDmaUpdate = DISABLED_TIME; + + while (length--) { + const unsigned src = dmaSrc++ & 0xFFFF; + const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); + + cycleCounter += 2 << doubleSpeed; + + if (cycleCounter - 3 > lOamDmaUpdate) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lOamDmaUpdate += 4; + + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lOamDmaUpdate - 1); + + ioamhram[src & 0xFF] = data; + } else if (oamDmaPos == 0xA0) { + endOamDma(lOamDmaUpdate - 1); + lOamDmaUpdate = DISABLED_TIME; + } + } + + nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); + } + + lastOamDmaUpdate = lOamDmaUpdate; + } + + cycleCounter += 4; + + dmaSource = dmaSrc; + dmaDestination = dmaDest; + ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); + + if ((ioamhram[0x155] & 0x80) && display.hdmaIsEnabled()) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + display.disableHdma(cycleCounter); + } + } + + break; + case TIMA: + tima.doIrqEvent(TimaInterruptRequester(intreq)); + break; + case VIDEO: + display.update(cycleCounter); + break; + case INTERRUPTS: + if (halted()) { + if (gbIsCgb_) + cycleCounter += 4; + + intreq.unhalt(); + intreq.setEventTime(DISABLED_TIME); + } + + if (ime()) { + unsigned address; + + cycleCounter += 12; + display.update(cycleCounter); + SP = (SP - 2) & 0xFFFF; + write(SP + 1, PC >> 8, cycleCounter); + unsigned ie = intreq.iereg(); + + cycleCounter += 4; + display.update(cycleCounter); + write(SP, PC & 0xFF, cycleCounter); + const unsigned pendingIrqs = ie & intreq.ifreg(); + + cycleCounter += 4; + display.update(cycleCounter); + const unsigned n = pendingIrqs & -pendingIrqs; + + if (n == 0) { + address = 0; + } + else if (n < 8) { + static const unsigned char lut[] = { 0x40, 0x48, 0x48, 0x50 }; + address = lut[n-1]; + } else + address = 0x50 + n; + + intreq.ackIrq(n); + PC = address; + } + + break; + } + + return cycleCounter; +} + +unsigned long Memory::stop(unsigned long cycleCounter) { + cycleCounter += 4 << isDoubleSpeed(); + + if (ioamhram[0x14D] & isCgb()) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + + display.speedChange(cycleCounter); + ioamhram[0x14D] ^= 0x81; + + intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed())); + + if (intreq.eventTime(END) > cycleCounter) { + intreq.setEventTime(cycleCounter + (isDoubleSpeed() ? + (intreq.eventTime(END) - cycleCounter) << 1 : (intreq.eventTime(END) - cycleCounter) >> 1)); + } + // when switching speed, it seems that the CPU spontaneously restarts soon? + // otherwise, the cpu should be allowed to stay halted as long as needed + // so only execute this line when switching speed + intreq.halt(); + intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); + } + else { + if ((ioamhram[0x100] & 0x30) == 0x30) { + di(); + intreq.halt(); + } + else { + intreq.halt(); + intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); + } + } + + return cycleCounter; +} + +static void decCycles(unsigned long &counter, const unsigned long dec) { + if (counter != DISABLED_TIME) + counter -= dec; +} + +void Memory::decEventCycles(const MemEventId eventId, const unsigned long dec) { + if (intreq.eventTime(eventId) != DISABLED_TIME) + intreq.setEventTime(eventId, intreq.eventTime(eventId) - dec); +} + +unsigned long Memory::resetCounters(unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + updateIrqs(cycleCounter); + + const unsigned long oldCC = cycleCounter; + + { + const unsigned long divinc = (cycleCounter - divLastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; + divLastUpdate += divinc << 8; + } + + const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; + + decCycles(divLastUpdate, dec); + decCycles(lastOamDmaUpdate, dec); + decEventCycles(SERIAL, dec); + decEventCycles(OAM, dec); + decEventCycles(BLIT, dec); + decEventCycles(END, dec); + decEventCycles(UNHALT, dec); + + cycleCounter -= dec; + + intreq.resetCc(oldCC, cycleCounter); + tima.resetCc(oldCC, cycleCounter, TimaInterruptRequester(intreq)); + display.resetCc(oldCC, cycleCounter); + sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); + + return cycleCounter; +} + +void Memory::updateInput() { + unsigned state = 0xF; + + if ((ioamhram[0x100] & 0x30) != 0x30 && getInput) { + unsigned input = (*getInput)(); + unsigned dpad_state = ~input >> 4; + unsigned button_state = ~input; + if (!(ioamhram[0x100] & 0x10)) + state &= dpad_state; + if (!(ioamhram[0x100] & 0x20)) + state &= button_state; + } + + if (state != 0xF && (ioamhram[0x100] & 0xF) == 0xF) + intreq.flagIrq(0x10); + + ioamhram[0x100] = (ioamhram[0x100] & -0x10u) | state; +} + +void Memory::updateOamDma(const unsigned long cycleCounter) { + const unsigned char *const oamDmaSrc = oamDmaSrcPtr(); + unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; + + while (cycles--) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lastOamDmaUpdate += 4; + + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lastOamDmaUpdate - 1); + + ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : cart.rtcRead(); + } else if (oamDmaPos == 0xA0) { + endOamDma(lastOamDmaUpdate - 1); + lastOamDmaUpdate = DISABLED_TIME; + break; + } + } +} + +void Memory::oamDmaInitSetup() { + if (ioamhram[0x146] < 0xA0) { + cart.setOamDmaSrc(ioamhram[0x146] < 0x80 ? OAM_DMA_SRC_ROM : OAM_DMA_SRC_VRAM); + } else if (ioamhram[0x146] < 0xFE - isCgb() * 0x1E) { + cart.setOamDmaSrc(ioamhram[0x146] < 0xC0 ? OAM_DMA_SRC_SRAM : OAM_DMA_SRC_WRAM); + } else + cart.setOamDmaSrc(OAM_DMA_SRC_INVALID); +} + +static const unsigned char * oamDmaSrcZero() { + static unsigned char zeroMem[0xA0]; + return zeroMem; +} + +const unsigned char * Memory::oamDmaSrcPtr() const { + switch (cart.oamDmaSrc()) { + case OAM_DMA_SRC_ROM: return cart.romdata(ioamhram[0x146] >> 6) + (ioamhram[0x146] << 8); + case OAM_DMA_SRC_SRAM: return cart.rsrambankptr() ? cart.rsrambankptr() + (ioamhram[0x146] << 8) : 0; + case OAM_DMA_SRC_VRAM: return cart.vrambankptr() + (ioamhram[0x146] << 8); + case OAM_DMA_SRC_WRAM: return cart.wramdata(ioamhram[0x146] >> 4 & 1) + (ioamhram[0x146] << 8 & 0xFFF); + case OAM_DMA_SRC_INVALID: + case OAM_DMA_SRC_OFF: break; + } + + return ioamhram[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart.rdisabledRam(); +} + +void Memory::startOamDma(const unsigned long cycleCounter) { + display.oamChange(cart.rdisabledRam(), cycleCounter); +} + +void Memory::endOamDma(const unsigned long cycleCounter) { + oamDmaPos = 0xFE; + cart.setOamDmaSrc(OAM_DMA_SRC_OFF); + display.oamChange(ioamhram, cycleCounter); +} + +unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (P & 0x7F) { + case 0x00: + updateInput(); + break; + case 0x01: + case 0x02: + updateSerial(cycleCounter); + break; + case 0x04: + { + const unsigned long divcycles = (cycleCounter - divLastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; + divLastUpdate += divcycles << 8; + } + + break; + case 0x05: + ioamhram[0x105] = tima.tima(cycleCounter); + break; + case 0x0F: + updateIrqs(cycleCounter); + ioamhram[0x10F] = intreq.ifreg(); + break; + case 0x26: + if (ioamhram[0x126] & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + ioamhram[0x126] = 0xF0 | sound.getStatus(); + } else + ioamhram[0x126] = 0x70; + + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.waveRamRead(P & 0xF); + case 0x41: + return ioamhram[0x141] | display.getStat(ioamhram[0x145], cycleCounter); + case 0x44: + return display.getLyReg(cycleCounter/*+4*/); + case 0x69: + return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); + case 0x6B: + return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); + default: break; + } + + return ioamhram[P - 0xFE00]; +} + +static bool isInOamDmaConflictArea(const OamDmaSrc oamDmaSrc, const unsigned addr, const bool cgb) { + struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; + + static const Area cgbAreas[] = { + { 0xC000, 0x8000, 0x2000, 0 }, + { 0xC000, 0x8000, 0x2000, 0 }, + { 0xA000, 0x0000, 0x8000, 0 }, + { 0xFE00, 0x0000, 0xC000, 0 }, + { 0xC000, 0x8000, 0x2000, 0 }, + { 0x0000, 0x0000, 0x0000, 0 } + }; + + static const Area dmgAreas[] = { + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xA000, 0x0000, 0x8000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0x0000, 0x0000, 0x0000, 0 } + }; + + const Area *const a = cgb ? cgbAreas : dmgAreas; + + return addr < a[oamDmaSrc].areaUpper && addr - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; +} + +unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { + if (P < 0xFF80) { + if (lastOamDmaUpdate != DISABLED_TIME) { + updateOamDma(cycleCounter); + + if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) + return ioamhram[oamDmaPos]; + } + + if (P < 0xC000) { + if (P < 0x8000) + return cart.romdata(P >> 14)[P]; + + if (P < 0xA000) { + if (!display.vramAccessible(cycleCounter)) + return 0xFF; + + return cart.vrambankptr()[P]; + } + + if (cart.rsrambankptr()) + return cart.rsrambankptr()[P]; + + return cart.rtcRead(); + } + + if (P < 0xFE00) + return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; + + if (P >= 0xFF00) + return nontrivial_ff_read(P, cycleCounter); + + if (!display.oamReadable(cycleCounter) || oamDmaPos < 0xA0) + return 0xFF; + } + + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_peek(const unsigned P) { + if (P < 0xC000) { + if (P < 0x8000) + return cart.romdata(P >> 14)[P]; + + if (P < 0xA000) { + return cart.vrambankptr()[P]; + } + + if (cart.rsrambankptr()) + return cart.rsrambankptr()[P]; + + return cart.rtcRead(); // verified side-effect free + } + if (P < 0xFE00) + return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; + if (P >= 0xFF00 && P < 0xFF80) + return nontrivial_ff_peek(P); + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_ff_peek(const unsigned P) { + // some regs may be somewhat wrong with this + return ioamhram[P - 0xFE00]; +} + +void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (P & 0xFF) { + case 0x00: + if ((data ^ ioamhram[0x100]) & 0x30) { + ioamhram[0x100] = (ioamhram[0x100] & ~0x30u) | (data & 0x30); + updateInput(); + } + return; + case 0x01: + updateSerial(cycleCounter); + break; + case 0x02: + updateSerial(cycleCounter); + + serialCnt = 8; + intreq.setEventTime((data & 0x81) == 0x81 + ? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8) + : static_cast(DISABLED_TIME)); + + data |= 0x7E - isCgb() * 2; + break; + case 0x04: + ioamhram[0x104] = 0; + divLastUpdate = cycleCounter; + return; + case 0x05: + tima.setTima(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x06: + tima.setTma(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x07: + data |= 0xF8; + tima.setTac(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x0F: + updateIrqs(cycleCounter); + intreq.setIfreg(0xE0 | data); + return; + case 0x10: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr10(data); + data |= 0x80; + break; + case 0x11: + if (!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr11(data); + data |= 0x3F; + break; + case 0x12: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr12(data); + break; + case 0x13: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr13(data); + return; + case 0x14: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr14(data); + data |= 0xBF; + break; + case 0x16: + if (!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr21(data); + data |= 0x3F; + break; + case 0x17: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr22(data); + break; + case 0x18: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr23(data); + return; + case 0x19: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr24(data); + data |= 0xBF; + break; + case 0x1A: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr30(data); + data |= 0x7F; + break; + case 0x1B: + if (!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr31(data); + return; + case 0x1C: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr32(data); + data |= 0x9F; + break; + case 0x1D: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr33(data); + return; + case 0x1E: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr34(data); + data |= 0xBF; + break; + case 0x20: + if (!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr41(data); + return; + case 0x21: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr42(data); + break; + case 0x22: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr43(data); + break; + case 0x23: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr44(data); + data |= 0xBF; + break; + case 0x24: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_so_volume(data); + break; + case 0x25: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.map_so(data); + break; + case 0x26: + if ((ioamhram[0x126] ^ data) & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + + if (!(data & 0x80)) { + for (unsigned i = 0xFF10; i < 0xFF26; ++i) + ff_write(i, 0, cycleCounter); + + sound.setEnabled(false); + } else { + sound.reset(); + sound.setEnabled(true); + } + } + + data = (data & 0x80) | (ioamhram[0x126] & 0x7F); + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.waveRamWrite(P & 0xF, data); + break; + case 0x40: + if (ioamhram[0x140] != data) { + if ((ioamhram[0x140] ^ data) & 0x80) { + const unsigned lyc = display.getStat(ioamhram[0x145], cycleCounter) & 4; + const bool hdmaEnabled = display.hdmaIsEnabled(); + + display.lcdcChange(data, cycleCounter); + ioamhram[0x144] = 0; + ioamhram[0x141] &= 0xF8; + + if (data & 0x80) { + intreq.setEventTime(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed())); + } else { + ioamhram[0x141] |= lyc; + intreq.setEventTime(cycleCounter + (456 * 4 << isDoubleSpeed())); + + if (hdmaEnabled) + flagHdmaReq(&intreq); + } + } else + display.lcdcChange(data, cycleCounter); + + ioamhram[0x140] = data; + } + + return; + case 0x41: + display.lcdstatChange(data, cycleCounter); + data = (ioamhram[0x141] & 0x87) | (data & 0x78); + break; + case 0x42: + display.scyChange(data, cycleCounter); + break; + case 0x43: + display.scxChange(data, cycleCounter); + break; + case 0x45: + display.lycRegChange(data, cycleCounter); + break; + case 0x46: + if (lastOamDmaUpdate != DISABLED_TIME) + endOamDma(cycleCounter); + + lastOamDmaUpdate = cycleCounter; + intreq.setEventTime(cycleCounter + 8); + ioamhram[0x146] = data; + oamDmaInitSetup(); + return; + case 0x47: + if (!isCgb()) + display.dmgBgPaletteChange(data, cycleCounter); + + break; + case 0x48: + if (!isCgb()) + display.dmgSpPalette1Change(data, cycleCounter); + + break; + case 0x49: + if (!isCgb()) + display.dmgSpPalette2Change(data, cycleCounter); + + break; + case 0x4A: + display.wyChange(data, cycleCounter); + break; + case 0x4B: + display.wxChange(data, cycleCounter); + break; + case 0x4C: + if (biosMode) { + //flagClockReq(&intreq); + } + break; + case 0x4D: + if (isCgb()) + ioamhram[0x14D] = (ioamhram[0x14D] & ~1u) | (data & 1); return; + case 0x4F: + if (isCgb()) { + cart.setVrambank(data & 1); + ioamhram[0x14F] = 0xFE | data; + } + + return; + case 0x50: + biosMode = false; + if (cgbSwitching) { + display.copyCgbPalettesToDmg(); + display.setCgb(false); + cgbSwitching = false; + } + return; + case 0x51: + dmaSource = data << 8 | (dmaSource & 0xFF); + return; + case 0x52: + dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); + return; + case 0x53: + dmaDestination = data << 8 | (dmaDestination & 0xFF); + return; + case 0x54: + dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); + return; + case 0x55: + if (isCgb()) { + ioamhram[0x155] = data & 0x7F; + + if (display.hdmaIsEnabled()) { + if (!(data & 0x80)) { + ioamhram[0x155] |= 0x80; + display.disableHdma(cycleCounter); + } + } else { + if (data & 0x80) { + if (ioamhram[0x140] & 0x80) { + display.enableHdma(cycleCounter); + } else + flagHdmaReq(&intreq); + } else + flagGdmaReq(&intreq); + } + } + + return; + case 0x56: + if (isCgb()) + ioamhram[0x156] = data | 0x3E; + + return; + case 0x68: + if (isCgb()) + ioamhram[0x168] = data | 0x40; + + return; + case 0x69: + if (isCgb()) { + const unsigned index = ioamhram[0x168] & 0x3F; + + display.cgbBgColorChange(index, data, cycleCounter); + + ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); + } + + return; + case 0x6A: + if (isCgb()) + ioamhram[0x16A] = data | 0x40; + + return; + case 0x6B: + if (isCgb()) { + const unsigned index = ioamhram[0x16A] & 0x3F; + + display.cgbSpColorChange(index, data, cycleCounter); + + ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); + } + + return; + case 0x6C: + ioamhram[0x16C] = data | 0xFE; + cgbSwitching = true; + + return; + case 0x70: + if (isCgb()) { + cart.setWrambank((data & 0x07) ? (data & 0x07) : 1); + ioamhram[0x170] = data | 0xF8; + } + + return; + case 0x72: + case 0x73: + case 0x74: + if (isCgb()) + break; + + return; + case 0x75: + if (isCgb()) + ioamhram[0x175] = data | 0x8F; + + return; + case 0xFF: + intreq.setIereg(data); + break; + default: + return; + } + + ioamhram[P - 0xFE00] = data; +} + +void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) { + updateOamDma(cycleCounter); + + if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) { + ioamhram[oamDmaPos] = data; + return; + } + } + + if (P < 0xFE00) { + if (P < 0xA000) { + if (P < 0x8000) { + cart.mbcWrite(P, data); + } else if (display.vramAccessible(cycleCounter)) { + display.vramChange(cycleCounter); + cart.vrambankptr()[P] = data; + } + } else if (P < 0xC000) { + if (cart.wsrambankptr()) + cart.wsrambankptr()[P] = data; + else + cart.rtcWrite(data); + } else + cart.wramdata(P >> 12 & 1)[P & 0xFFF] = data; + } else if (P - 0xFF80u >= 0x7Fu) { + if (P < 0xFF00) { + if (display.oamWritable(cycleCounter) && oamDmaPos >= 0xA0 && (P < 0xFEA0 || isCgb())) { + display.oamChange(cycleCounter); + ioamhram[P - 0xFE00] = data; + } + } else + nontrivial_ff_write(P, data, cycleCounter); + } else + ioamhram[P - 0xFE00] = data; +} + +int Memory::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { + if (const int fail = cart.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) + return fail; + + sound.init(cart.isCgb()); + display.reset(ioamhram, cart.vramdata(), cart.isCgb()); + + return 0; +} + +unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.fillBuffer(); +} + +void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { + display.setDmgPaletteColor(palNum, colorNum, rgb32); +} + +void Memory::setCgbPalette(unsigned *lut) { + display.setCgbPalette(lut); +} + +bool Memory::getMemoryArea(int which, unsigned char **data, int *length) { + if (!data || !length) + return false; + + switch (which) + { + case 4: // oam + *data = &ioamhram[0]; + *length = 160; + return true; + case 5: // hram + *data = &ioamhram[384]; + *length = 128; + return true; + case 6: // bgpal + *data = (unsigned char *)display.bgPalette(); + *length = 32; + return true; + case 7: // sppal + *data = (unsigned char *)display.spPalette(); + *length = 32; + return true; + default: // pass to cartridge + return cart.getMemoryArea(which, data, length); + } +} + +int Memory::LinkStatus(int which) +{ + switch (which) + { + case 256: // ClockSignaled + return linkClockTrigger; + case 257: // AckClockSignal + linkClockTrigger = false; + return 0; + case 258: // GetOut + return ioamhram[0x101] & 0xff; + case 259: // connect link cable + LINKCABLE = true; + return 0; + default: // ShiftIn + if (ioamhram[0x102] & 0x80) // was enabled + { + ioamhram[0x101] = which; + ioamhram[0x102] &= 0x7F; + intreq.flagIrq(8); + } + return 0; + } + + return -1; +} + +SYNCFUNC(Memory) +{ + SSS(cart); + NSS(ioamhram); + NSS(divLastUpdate); + NSS(lastOamDmaUpdate); + NSS(biosMode); + NSS(cgbSwitching); + NSS(agbMode); + NSS(gbIsCgb_); + + SSS(intreq); + SSS(tima); + SSS(display); + SSS(sound); + //SSS(interrupter); // no state + + NSS(dmaSource); + NSS(dmaDestination); + NSS(oamDmaPos); + NSS(serialCnt); + NSS(blanklcd); + + NSS(LINKCABLE); + NSS(linkClockTrigger); +} + +} diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index 9ea65bf4a1..95a2e18f16 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -1,298 +1,336 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef MEMORY_H -#define MEMORY_H - -#include "mem/cartridge.h" -#include "video.h" -#include "sound.h" -#include "interrupter.h" -#include "tima.h" -#include "newstate.h" -#include "gambatte.h" - -namespace gambatte { -class InputGetter; -class FilterInfo; - -class Memory { - Cartridge cart; - unsigned char ioamhram[0x200]; - - void (*readCallback)(unsigned); - void (*writeCallback)(unsigned); - void (*execCallback)(unsigned); - CDCallback cdCallback; - void (*linkCallback)(); - - unsigned (*getInput)(); - unsigned long divLastUpdate; - unsigned long lastOamDmaUpdate; - - InterruptRequester intreq; - Tima tima; - LCD display; - PSG sound; - Interrupter interrupter; - - unsigned short dmaSource; - unsigned short dmaDestination; - unsigned char oamDmaPos; - unsigned char serialCnt; - bool blanklcd; - - bool LINKCABLE; - bool linkClockTrigger; - - void decEventCycles(MemEventId eventId, unsigned long dec); - - void oamDmaInitSetup(); - void updateOamDma(unsigned long cycleCounter); - void startOamDma(unsigned long cycleCounter); - void endOamDma(unsigned long cycleCounter); - const unsigned char * oamDmaSrcPtr() const; - - unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter); - unsigned nontrivial_read(unsigned P, unsigned long cycleCounter); - void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); - void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); - - unsigned nontrivial_peek(unsigned P); - unsigned nontrivial_ff_peek(unsigned P); - - void updateSerial(unsigned long cc); - void updateTimaIrq(unsigned long cc); - void updateIrqs(unsigned long cc); - - bool isDoubleSpeed() const { return display.isDoubleSpeed(); } - -public: - explicit Memory(const Interrupter &interrupter); - - bool loaded() const { return cart.loaded(); } - const char * romTitle() const { return cart.romTitle(); } - - int debugGetLY() const { return display.debugGetLY(); } - - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state/*, unsigned long oldCc*/); - void loadSavedata(const char *data) { cart.loadSavedata(data); } - int saveSavedataLength() {return cart.saveSavedataLength(); } - void saveSavedata(char *dest) { cart.saveSavedata(dest); } - void updateInput(); - - bool getMemoryArea(int which, unsigned char **data, int *length); // { return cart.getMemoryArea(which, data, length); } - - unsigned long stop(unsigned long cycleCounter); - bool isCgb() const { return display.isCgb(); } - bool ime() const { return intreq.ime(); } - bool halted() const { return intreq.halted(); } - unsigned long nextEventTime() const { return intreq.minEventTime(); } - - void setLayers(unsigned mask) { display.setLayers(mask); } - - bool isActive() const { return intreq.eventTime(END) != DISABLED_TIME; } - - long cyclesSinceBlit(const unsigned long cc) const { - return cc < intreq.eventTime(BLIT) ? -1 : static_cast((cc - intreq.eventTime(BLIT)) >> isDoubleSpeed()); - } - - void halt() { intreq.halt(); } - void ei(unsigned long cycleCounter) { if (!ime()) { intreq.ei(cycleCounter); } } - - void di() { intreq.di(); } - - unsigned ff_read(const unsigned P, const unsigned long cycleCounter) { - return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00]; - } - - struct CDMapResult - { - eCDLog_AddrType type; - unsigned addr; - }; - - CDMapResult CDMap(const unsigned P) const - { - if(P<0x4000) - { - CDMapResult ret = { eCDLog_AddrType_ROM, P }; - return ret; - } - else if(P<0x8000) - { - unsigned bank = cart.rmem(P>>12) - cart.rmem(0); - unsigned addr = P+bank; - CDMapResult ret = { eCDLog_AddrType_ROM, addr }; - return ret; - } - else if(P<0xA000) {} - else if(P<0xC000) - { - if(cart.wsrambankptr()) - { - //not bankable. but. we're not sure how much might be here - unsigned char *data; - int length; - bool has = cart.getMemoryArea(3,&data,&length); - unsigned addr = P&(length-1); - if(has && length!=0) - { - CDMapResult ret = { eCDLog_AddrType_CartRAM, addr }; - return ret; - } - } - } - else if(P<0xE000) - { - unsigned bank = cart.wramdata(P >> 12 & 1) - cart.wramdata(0); - unsigned addr = (P&0xFFF)+bank; - CDMapResult ret = { eCDLog_AddrType_WRAM, addr }; - return ret; - } - else if(P<0xFF80) {} - else - { - ////this is just for debugging, really, it's pretty useless - //CDMapResult ret = { eCDLog_AddrType_HRAM, (P-0xFF80) }; - //return ret; - } - - CDMapResult ret = { eCDLog_AddrType_None }; - return ret; - } - - - unsigned read(const unsigned P, const unsigned long cycleCounter) { - if (readCallback) - readCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); - } - - unsigned read_excb(const unsigned P, const unsigned long cycleCounter, bool first) { - if (execCallback) - execCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,first?eCDLog_Flags_ExecFirst : eCDLog_Flags_ExecOperand); - } - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); - } - - unsigned peek(const unsigned P) { - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_peek(P); - } - - void write_nocb(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (cart.wmem(P >> 12)) { - cart.wmem(P >> 12)[P] = data; - } else - nontrivial_write(P, data, cycleCounter); - } - - void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (cart.wmem(P >> 12)) { - cart.wmem(P >> 12)[P] = data; - } else - nontrivial_write(P, data, cycleCounter); - if (writeCallback) - writeCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - } - - void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (P - 0xFF80u < 0x7Fu) { - ioamhram[P - 0xFE00] = data; - } else - nontrivial_ff_write(P, data, cycleCounter); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - } - - unsigned long event(unsigned long cycleCounter); - unsigned long resetCounters(unsigned long cycleCounter); - - int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); - - void setInputGetter(unsigned (*getInput)()) { - this->getInput = getInput; - } - - void setReadCallback(void (*callback)(unsigned)) { - this->readCallback = callback; - } - void setWriteCallback(void (*callback)(unsigned)) { - this->writeCallback = callback; - } - void setExecCallback(void (*callback)(unsigned)) { - this->execCallback = callback; - } - void setCDCallback(CDCallback cdc) { - this->cdCallback = cdc; - } - - void setScanlineCallback(void (*callback)(), int sl) { - display.setScanlineCallback(callback, sl); - } - - void setRTCCallback(std::uint32_t (*callback)()) { - cart.setRTCCallback(callback); - } - - void setLinkCallback(void (*callback)()) { - this->linkCallback = callback; - } - - void setEndtime(unsigned long cc, unsigned long inc); - - void setSoundBuffer(uint_least32_t *const buf) { sound.setBuffer(buf); } - unsigned fillSoundBuffer(unsigned long cc); - - void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { - display.setVideoBuffer(videoBuf, pitch); - } - - void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); - void setCgbPalette(unsigned *lut); - - int LinkStatus(int which); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MEMORY_H +#define MEMORY_H + +static unsigned char const agbOverride[0xD] = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 }; + +#include "mem/cartridge.h" +#include "video.h" +#include "sound.h" +#include "interrupter.h" +#include "tima.h" +#include "newstate.h" +#include "gambatte.h" + +namespace gambatte { +class InputGetter; +class FilterInfo; + +class Memory { + Cartridge cart; + unsigned char ioamhram[0x200]; + unsigned char cgbBios[0x900]; + unsigned char dmgBios[0x100]; + bool biosMode; + bool cgbSwitching; + bool agbMode; + bool gbIsCgb_; + unsigned short &SP; + unsigned short &PC; + + void (*readCallback)(unsigned); + void (*writeCallback)(unsigned); + void (*execCallback)(unsigned); + CDCallback cdCallback; + void(*linkCallback)(); + + unsigned (*getInput)(); + unsigned long divLastUpdate; + unsigned long lastOamDmaUpdate; + + InterruptRequester intreq; + Tima tima; + LCD display; + PSG sound; + Interrupter interrupter; + + unsigned short dmaSource; + unsigned short dmaDestination; + unsigned char oamDmaPos; + unsigned char serialCnt; + bool blanklcd; + + bool LINKCABLE; + bool linkClockTrigger; + + void decEventCycles(MemEventId eventId, unsigned long dec); + + void oamDmaInitSetup(); + void updateOamDma(unsigned long cycleCounter); + void startOamDma(unsigned long cycleCounter); + void endOamDma(unsigned long cycleCounter); + const unsigned char * oamDmaSrcPtr() const; + + unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter); + unsigned nontrivial_read(unsigned P, unsigned long cycleCounter); + void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); + void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); + + unsigned nontrivial_peek(unsigned P); + unsigned nontrivial_ff_peek(unsigned P); + + void updateSerial(unsigned long cc); + void updateTimaIrq(unsigned long cc); + void updateIrqs(unsigned long cc); + + bool isDoubleSpeed() const { return display.isDoubleSpeed(); } + +public: + explicit Memory(const Interrupter &interrupter, unsigned short &sp, unsigned short &pc); + + bool loaded() const { return cart.loaded(); } + unsigned curRomBank() const { return cart.curRomBank(); } + const char * romTitle() const { return cart.romTitle(); } + + int debugGetLY() const { return display.debugGetLY(); } + + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state/*, unsigned long oldCc*/); + void loadSavedata(const char *data) { cart.loadSavedata(data); } + int saveSavedataLength() {return cart.saveSavedataLength(); } + void saveSavedata(char *dest) { cart.saveSavedata(dest); } + void updateInput(); + + unsigned char* cgbBiosBuffer() { return (unsigned char*)cgbBios; } + unsigned char* dmgBiosBuffer() { return (unsigned char*)dmgBios; } + bool gbIsCgb() { return gbIsCgb_; } + + bool getMemoryArea(int which, unsigned char **data, int *length); // { return cart.getMemoryArea(which, data, length); } + + unsigned long stop(unsigned long cycleCounter); + bool isCgb() const { return display.isCgb(); } + bool ime() const { return intreq.ime(); } + bool halted() const { return intreq.halted(); } + unsigned long nextEventTime() const { return intreq.minEventTime(); } + + void setLayers(unsigned mask) { display.setLayers(mask); } + + bool isActive() const { return intreq.eventTime(END) != DISABLED_TIME; } + + long cyclesSinceBlit(const unsigned long cc) const { + return cc < intreq.eventTime(BLIT) ? -1 : static_cast((cc - intreq.eventTime(BLIT)) >> isDoubleSpeed()); + } + + void halt() { intreq.halt(); } + void ei(unsigned long cycleCounter) { if (!ime()) { intreq.ei(cycleCounter); } } + + void di() { intreq.di(); } + + unsigned ff_read(const unsigned P, const unsigned long cycleCounter) { + return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00]; + } + + struct CDMapResult + { + eCDLog_AddrType type; + unsigned addr; + }; + + CDMapResult CDMap(const unsigned P) const + { + if(P<0x4000) + { + CDMapResult ret = { eCDLog_AddrType_ROM, P }; + return ret; + } + else if(P<0x8000) + { + unsigned bank = cart.rmem(P>>12) - cart.rmem(0); + unsigned addr = P+bank; + CDMapResult ret = { eCDLog_AddrType_ROM, addr }; + return ret; + } + else if(P<0xA000) {} + else if(P<0xC000) + { + if(cart.wsrambankptr()) + { + //not bankable. but. we're not sure how much might be here + unsigned char *data; + int length; + bool has = cart.getMemoryArea(3,&data,&length); + unsigned addr = P&(length-1); + if(has && length!=0) + { + CDMapResult ret = { eCDLog_AddrType_CartRAM, addr }; + return ret; + } + } + } + else if(P<0xE000) + { + unsigned bank = cart.wramdata(P >> 12 & 1) - cart.wramdata(0); + unsigned addr = (P&0xFFF)+bank; + CDMapResult ret = { eCDLog_AddrType_WRAM, addr }; + return ret; + } + else if(P<0xFF80) {} + else + { + ////this is just for debugging, really, it's pretty useless + //CDMapResult ret = { eCDLog_AddrType_HRAM, (P-0xFF80) }; + //return ret; + } + + CDMapResult ret = { eCDLog_AddrType_None }; + return ret; + } + + + unsigned readBios(const unsigned P) { + if (gbIsCgb_) { + if (agbMode && P >= 0xF3 && P < 0x100) { + return (agbOverride[P - 0xF3] + cgbBios[P]) & 0xFF; + } + return cgbBios[P]; + } + return dmgBios[P]; + } + + unsigned read(const unsigned P, const unsigned long cycleCounter) { + if (readCallback) + readCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); + } + + unsigned read_excb(const unsigned P, const unsigned long cycleCounter, bool first) { + if (execCallback) + execCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,first?eCDLog_Flags_ExecFirst : eCDLog_Flags_ExecOperand); + } + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); + } + + unsigned peek(const unsigned P) { + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_peek(P); + } + + void write_nocb(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (cart.wmem(P >> 12)) { + cart.wmem(P >> 12)[P] = data; + } else + nontrivial_write(P, data, cycleCounter); + } + + void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (cart.wmem(P >> 12)) { + cart.wmem(P >> 12)[P] = data; + } else + nontrivial_write(P, data, cycleCounter); + if (writeCallback) + writeCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + } + + void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (P - 0xFF80u < 0x7Fu) { + ioamhram[P - 0xFE00] = data; + } else + nontrivial_ff_write(P, data, cycleCounter); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + } + + unsigned long event(unsigned long cycleCounter); + unsigned long resetCounters(unsigned long cycleCounter); + + int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); + + void setInputGetter(unsigned (*getInput)()) { + this->getInput = getInput; + } + + void setReadCallback(void (*callback)(unsigned)) { + this->readCallback = callback; + } + void setWriteCallback(void (*callback)(unsigned)) { + this->writeCallback = callback; + } + void setExecCallback(void (*callback)(unsigned)) { + this->execCallback = callback; + } + void setCDCallback(CDCallback cdc) { + this->cdCallback = cdc; + } + + void setScanlineCallback(void (*callback)(), int sl) { + display.setScanlineCallback(callback, sl); + } + + void setRTCCallback(std::uint32_t (*callback)()) { + cart.setRTCCallback(callback); + } + + void setLinkCallback(void(*callback)()) { + this->linkCallback = callback; + } + + void setEndtime(unsigned long cc, unsigned long inc); + + void setSoundBuffer(uint_least32_t *const buf) { sound.setBuffer(buf); } + unsigned fillSoundBuffer(unsigned long cc); + + void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { + display.setVideoBuffer(videoBuf, pitch); + } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); + void setCgbPalette(unsigned *lut); + + void blackScreen() { + display.blackScreen(); + } + + int LinkStatus(int which); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/minkeeper.h b/libgambatte/src/minkeeper.h index 0031adf098..ad2d6f79d2 100644 --- a/libgambatte/src/minkeeper.h +++ b/libgambatte/src/minkeeper.h @@ -20,7 +20,7 @@ #define MINKEEPER_H #include -#include "newstate.h" +#include "newstate.h" namespace MinKeeperUtil { template struct CeiledLog2 { enum { R = 1 + CeiledLog2<(n + 1) / 2>::R }; }; @@ -108,12 +108,12 @@ public: unsigned long value(const int id) const { return values[id]; } // not sure if i understood everything in minkeeper correctly, so something might be missing here? - template - void SyncState(gambatte::NewState *ns) - { - NSS(values); - NSS(minValue_); - NSS(a); + template + void SyncState(gambatte::NewState *ns) + { + NSS(values); + NSS(minValue_); + NSS(a); } }; diff --git a/libgambatte/src/newstate.cpp b/libgambatte/src/newstate.cpp index fa01dbf6ef..9d4214674a 100644 --- a/libgambatte/src/newstate.cpp +++ b/libgambatte/src/newstate.cpp @@ -1,69 +1,69 @@ -#include "newstate.h" -#include -#include - -namespace gambatte { - -NewStateDummy::NewStateDummy() - :length(0) -{ -} -void NewStateDummy::Save(const void *ptr, size_t size, const char *name) -{ - length += size; -} -void NewStateDummy::Load(void *ptr, size_t size, const char *name) -{ -} - -NewStateExternalBuffer::NewStateExternalBuffer(char *buffer, long maxlength) - :buffer(buffer), length(0), maxlength(maxlength) -{ -} - -void NewStateExternalBuffer::Save(const void *ptr, size_t size, const char *name) -{ - if (maxlength - length >= (long)size) - { - std::memcpy(buffer + length, ptr, size); - } - length += size; -} - -void NewStateExternalBuffer::Load(void *ptr, size_t size, const char *name) -{ - char *dst = static_cast(ptr); - if (maxlength - length >= (long)size) - { - std::memcpy(dst, buffer + length, size); - } - length += size; -} - -NewStateExternalFunctions::NewStateExternalFunctions(const FPtrs *ff) - :Save_(ff->Save_), - Load_(ff->Load_), - EnterSection_(ff->EnterSection_), - ExitSection_(ff->ExitSection_) -{ -} - -void NewStateExternalFunctions::Save(const void *ptr, size_t size, const char *name) -{ - Save_(ptr, size, name); -} -void NewStateExternalFunctions::Load(void *ptr, size_t size, const char *name) -{ - Load_(ptr, size, name); -} -void NewStateExternalFunctions::EnterSection(const char *name) -{ - EnterSection_(name); -} -void NewStateExternalFunctions::ExitSection(const char *name) -{ - ExitSection_(name); -} - - -} +#include "newstate.h" +#include +#include + +namespace gambatte { + +NewStateDummy::NewStateDummy() + :length(0) +{ +} +void NewStateDummy::Save(const void *ptr, size_t size, const char *name) +{ + length += size; +} +void NewStateDummy::Load(void *ptr, size_t size, const char *name) +{ +} + +NewStateExternalBuffer::NewStateExternalBuffer(char *buffer, long maxlength) + :buffer(buffer), length(0), maxlength(maxlength) +{ +} + +void NewStateExternalBuffer::Save(const void *ptr, size_t size, const char *name) +{ + if (maxlength - length >= (long)size) + { + std::memcpy(buffer + length, ptr, size); + } + length += size; +} + +void NewStateExternalBuffer::Load(void *ptr, size_t size, const char *name) +{ + char *dst = static_cast(ptr); + if (maxlength - length >= (long)size) + { + std::memcpy(dst, buffer + length, size); + } + length += size; +} + +NewStateExternalFunctions::NewStateExternalFunctions(const FPtrs *ff) + :Save_(ff->Save_), + Load_(ff->Load_), + EnterSection_(ff->EnterSection_), + ExitSection_(ff->ExitSection_) +{ +} + +void NewStateExternalFunctions::Save(const void *ptr, size_t size, const char *name) +{ + Save_(ptr, size, name); +} +void NewStateExternalFunctions::Load(void *ptr, size_t size, const char *name) +{ + Load_(ptr, size, name); +} +void NewStateExternalFunctions::EnterSection(const char *name) +{ + EnterSection_(name); +} +void NewStateExternalFunctions::ExitSection(const char *name) +{ + ExitSection_(name); +} + + +} diff --git a/libgambatte/src/newstate.h b/libgambatte/src/newstate.h index 37e2a7038f..2457a83669 100644 --- a/libgambatte/src/newstate.h +++ b/libgambatte/src/newstate.h @@ -1,102 +1,102 @@ -#ifndef NEWSTATE_H -#define NEWSTATE_H - -#include -#include - -namespace gambatte { - -class NewState -{ -public: - virtual void Save(const void *ptr, size_t size, const char *name) = 0; - virtual void Load(void *ptr, size_t size, const char *name) = 0; - virtual void EnterSection(const char *name) { } - virtual void ExitSection(const char *name) { } -}; - -class NewStateDummy : public NewState -{ -private: - long length; -public: - NewStateDummy(); - long GetLength() { return length; } - void Rewind() { length = 0; } - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); -}; - -class NewStateExternalBuffer : public NewState -{ -private: - char *const buffer; - long length; - const long maxlength; -public: - NewStateExternalBuffer(char *buffer, long maxlength); - long GetLength() { return length; } - void Rewind() { length = 0; } - bool Overflow() { return length > maxlength; } - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); -}; - -struct FPtrs -{ - void (*Save_)(const void *ptr, size_t size, const char *name); - void (*Load_)(void *ptr, size_t size, const char *name); - void (*EnterSection_)(const char *name); - void (*ExitSection_)(const char *name); -}; - -class NewStateExternalFunctions : public NewState -{ -private: - void (*Save_)(const void *ptr, size_t size, const char *name); - void (*Load_)(void *ptr, size_t size, const char *name); - void (*EnterSection_)(const char *name); - void (*ExitSection_)(const char *name); -public: - NewStateExternalFunctions(const FPtrs *ff); - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); - virtual void EnterSection(const char *name); - virtual void ExitSection(const char *name); -}; - -// defines and explicitly instantiates -#define SYNCFUNC(x)\ - template void x::SyncState(NewState *ns);\ - template void x::SyncState(NewState *ns);\ - templatevoid x::SyncState(NewState *ns) - -// N = normal variable -// P = pointer to fixed size data -// S = "sub object" -// T = "ptr to sub object" -// R = pointer, store its offset from some other pointer -// E = general purpose cased value "enum" - - -// first line is default value in converted enum; last line is default value in argument x -#define EBS(x,d) do { int _ttmp = (d); if (isReader) ns->Load(&_ttmp, sizeof(_ttmp), #x); if (0) -#define EVS(x,v,n) else if (!isReader && (x) == (v)) _ttmp = (n); else if (isReader && _ttmp == (n)) (x) = (v) -#define EES(x,d) else if (isReader) (x) = (d); if (!isReader) ns->Save(&_ttmp, sizeof(_ttmp), #x); } while (0) - -#define RSS(x,b) do { if (isReader)\ -{ ptrdiff_t _ttmp; ns->Load(&_ttmp, sizeof(_ttmp), #x); (x) = (_ttmp == (ptrdiff_t)0xdeadbeef ? 0 : (b) + _ttmp); }\ - else\ -{ ptrdiff_t _ttmp = (x) == 0 ? 0xdeadbeef : (x) - (b); ns->Save(&_ttmp, sizeof(_ttmp), #x); } } while (0) - -#define PSS(x,s) do { if (isReader) ns->Load((x), (s), #x); else ns->Save((x), (s), #x); } while (0) - -#define NSS(x) do { if (isReader) ns->Load(&(x), sizeof(x), #x); else ns->Save(&(x), sizeof(x), #x); } while (0) - -#define SSS(x) do { ns->EnterSection(#x); (x).SyncState(ns); ns->ExitSection(#x); } while (0) - -#define TSS(x) do { ns->EnterSection(#x); (x)->SyncState(ns); ns->ExitSection(#x); } while (0) - -} - -#endif +#ifndef NEWSTATE_H +#define NEWSTATE_H + +#include +#include + +namespace gambatte { + +class NewState +{ +public: + virtual void Save(const void *ptr, size_t size, const char *name) = 0; + virtual void Load(void *ptr, size_t size, const char *name) = 0; + virtual void EnterSection(const char *name) { } + virtual void ExitSection(const char *name) { } +}; + +class NewStateDummy : public NewState +{ +private: + long length; +public: + NewStateDummy(); + long GetLength() { return length; } + void Rewind() { length = 0; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); +}; + +class NewStateExternalBuffer : public NewState +{ +private: + char *const buffer; + long length; + const long maxlength; +public: + NewStateExternalBuffer(char *buffer, long maxlength); + long GetLength() { return length; } + void Rewind() { length = 0; } + bool Overflow() { return length > maxlength; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); +}; + +struct FPtrs +{ + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); +}; + +class NewStateExternalFunctions : public NewState +{ +private: + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); +public: + NewStateExternalFunctions(const FPtrs *ff); + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); + virtual void EnterSection(const char *name); + virtual void ExitSection(const char *name); +}; + +// defines and explicitly instantiates +#define SYNCFUNC(x)\ + template void x::SyncState(NewState *ns);\ + template void x::SyncState(NewState *ns);\ + templatevoid x::SyncState(NewState *ns) + +// N = normal variable +// P = pointer to fixed size data +// S = "sub object" +// T = "ptr to sub object" +// R = pointer, store its offset from some other pointer +// E = general purpose cased value "enum" + + +// first line is default value in converted enum; last line is default value in argument x +#define EBS(x,d) do { int _ttmp = (d); if (isReader) ns->Load(&_ttmp, sizeof(_ttmp), #x); if (0) +#define EVS(x,v,n) else if (!isReader && (x) == (v)) _ttmp = (n); else if (isReader && _ttmp == (n)) (x) = (v) +#define EES(x,d) else if (isReader) (x) = (d); if (!isReader) ns->Save(&_ttmp, sizeof(_ttmp), #x); } while (0) + +#define RSS(x,b) do { if (isReader)\ +{ ptrdiff_t _ttmp; ns->Load(&_ttmp, sizeof(_ttmp), #x); (x) = (_ttmp == (ptrdiff_t)0xdeadbeef ? 0 : (b) + _ttmp); }\ + else\ +{ ptrdiff_t _ttmp = (x) == 0 ? 0xdeadbeef : (x) - (b); ns->Save(&_ttmp, sizeof(_ttmp), #x); } } while (0) + +#define PSS(x,s) do { if (isReader) ns->Load((x), (s), #x); else ns->Save((x), (s), #x); } while (0) + +#define NSS(x) do { if (isReader) ns->Load(&(x), sizeof(x), #x); else ns->Save(&(x), sizeof(x), #x); } while (0) + +#define SSS(x) do { ns->EnterSection(#x); (x).SyncState(ns); ns->ExitSection(#x); } while (0) + +#define TSS(x) do { ns->EnterSection(#x); (x)->SyncState(ns); ns->ExitSection(#x); } while (0) + +} + +#endif diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index 4110161ca0..014efbb4c8 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -38,7 +38,7 @@ struct SaveState { void set(T *ptr, const unsigned long sz) { this->ptr = ptr; this->sz = sz; } friend class SaverList; - friend void setInitState(SaveState &, bool, bool, std::uint32_t); + friend void setInitState(SaveState &, bool, bool, std::uint32_t, unsigned); }; struct CPU { @@ -78,6 +78,10 @@ struct SaveState { bool enableRam; bool rambankMode; bool hdmaTransfer; + bool biosMode; + bool cgbSwitching; + bool agbMode; + bool gbIsCgb; } mem; struct PPU { @@ -113,6 +117,7 @@ struct SaveState { unsigned char wscx; bool weMaster; bool pendingLcdstatIrq; + bool isCgb; } ppu; struct SPU { diff --git a/libgambatte/src/sound.cpp b/libgambatte/src/sound.cpp index be28da8e1d..01685afc74 100644 --- a/libgambatte/src/sound.cpp +++ b/libgambatte/src/sound.cpp @@ -173,16 +173,16 @@ unsigned PSG::getStatus() const { } // the buffer and position are not saved, as they're set and flushed on each runfor() call -SYNCFUNC(PSG) -{ - SSS(ch1); - SSS(ch2); - SSS(ch3); - SSS(ch4); - NSS(lastUpdate); - NSS(soVol); - NSS(rsum); - NSS(enabled); -} +SYNCFUNC(PSG) +{ + SSS(ch1); + SSS(ch2); + SSS(ch3); + SSS(ch4); + NSS(lastUpdate); + NSS(soVol); + NSS(rsum); + NSS(enabled); +} } diff --git a/libgambatte/src/sound.h b/libgambatte/src/sound.h index 0d39db788e..7d430d613d 100644 --- a/libgambatte/src/sound.h +++ b/libgambatte/src/sound.h @@ -23,7 +23,7 @@ #include "sound/channel2.h" #include "sound/channel3.h" #include "sound/channel4.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -89,7 +89,7 @@ public: void map_so(unsigned nr51); unsigned getStatus() const; - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/channel1.cpp b/libgambatte/src/sound/channel1.cpp index b99902f28e..eae98c966c 100644 --- a/libgambatte/src/sound/channel1.cpp +++ b/libgambatte/src/sound/channel1.cpp @@ -97,14 +97,14 @@ void Channel1::SweepUnit::loadState(const SaveState &state) { negging = state.spu.ch1.sweep.negging; } -template -void Channel1::SweepUnit::SyncState(NewState *ns) -{ - NSS(counter); - NSS(shadow); - NSS(nr0); - NSS(negging); -} +template +void Channel1::SweepUnit::SyncState(NewState *ns) +{ + NSS(counter); + NSS(shadow); + NSS(nr0); + NSS(negging); +} Channel1::Channel1() : staticOutputTest(*this, dutyUnit), @@ -250,26 +250,26 @@ void Channel1::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel1) -{ - SSS(lengthCounter); - SSS(dutyUnit); - SSS(envelopeUnit); - SSS(sweepUnit); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &dutyUnit, 1); - EVS(nextEventUnit, &sweepUnit, 2); - EVS(nextEventUnit, &envelopeUnit, 3); - EVS(nextEventUnit, &lengthCounter, 4); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} +SYNCFUNC(Channel1) +{ + SSS(lengthCounter); + SSS(dutyUnit); + SSS(envelopeUnit); + SSS(sweepUnit); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &dutyUnit, 1); + EVS(nextEventUnit, &sweepUnit, 2); + EVS(nextEventUnit, &envelopeUnit, 3); + EVS(nextEventUnit, &lengthCounter, 4); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel1.h b/libgambatte/src/sound/channel1.h index 3467547a58..5cd7c23ed2 100644 --- a/libgambatte/src/sound/channel1.h +++ b/libgambatte/src/sound/channel1.h @@ -1,97 +1,97 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL1_H -#define SOUND_CHANNEL1_H - -#include "gbint.h" -#include "master_disabler.h" -#include "length_counter.h" -#include "duty_unit.h" -#include "envelope_unit.h" -#include "static_output_tester.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel1 { - class SweepUnit : public SoundUnit { - MasterDisabler &disableMaster; - DutyUnit &dutyUnit; - unsigned short shadow; - unsigned char nr0; - bool negging; - - unsigned calcFreq(); - - public: - SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit); - void event(); - void nr0Change(unsigned newNr0); - void nr4Init(unsigned long cycleCounter); - void reset(); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); - }; - - friend class StaticOutputTester; - - StaticOutputTester staticOutputTest; - DutyMasterDisabler disableMaster; - LengthCounter lengthCounter; - DutyUnit dutyUnit; - EnvelopeUnit envelopeUnit; - SweepUnit sweepUnit; - - SoundUnit *nextEventUnit; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - - unsigned char nr4; - bool master; - - void setEvent(); - -public: - Channel1(); - void setNr0(unsigned data); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - - void setSo(unsigned long soMask); - bool isActive() const { return master; } - - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - void reset(); - void init(bool cgb); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL1_H +#define SOUND_CHANNEL1_H + +#include "gbint.h" +#include "master_disabler.h" +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel1 { + class SweepUnit : public SoundUnit { + MasterDisabler &disableMaster; + DutyUnit &dutyUnit; + unsigned short shadow; + unsigned char nr0; + bool negging; + + unsigned calcFreq(); + + public: + SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit); + void event(); + void nr0Change(unsigned newNr0); + void nr4Init(unsigned long cycleCounter); + void reset(); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); + }; + + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + SweepUnit sweepUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel1(); + void setNr0(unsigned data); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + bool isActive() const { return master; } + + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel2.cpp b/libgambatte/src/sound/channel2.cpp index 78cb44fb07..b3135e2299 100644 --- a/libgambatte/src/sound/channel2.cpp +++ b/libgambatte/src/sound/channel2.cpp @@ -153,24 +153,24 @@ void Channel2::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel2) -{ - SSS(lengthCounter); - SSS(dutyUnit); - SSS(envelopeUnit); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &dutyUnit, 1); - EVS(nextEventUnit, &envelopeUnit, 2); - EVS(nextEventUnit, &lengthCounter, 3); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} +SYNCFUNC(Channel2) +{ + SSS(lengthCounter); + SSS(dutyUnit); + SSS(envelopeUnit); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &dutyUnit, 1); + EVS(nextEventUnit, &envelopeUnit, 2); + EVS(nextEventUnit, &lengthCounter, 3); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel2.h b/libgambatte/src/sound/channel2.h index 2f98309e6a..9cc2ef28b6 100644 --- a/libgambatte/src/sound/channel2.h +++ b/libgambatte/src/sound/channel2.h @@ -1,75 +1,75 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL2_H -#define SOUND_CHANNEL2_H - -#include "gbint.h" -#include "length_counter.h" -#include "duty_unit.h" -#include "envelope_unit.h" -#include "static_output_tester.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel2 { - friend class StaticOutputTester; - - StaticOutputTester staticOutputTest; - DutyMasterDisabler disableMaster; - LengthCounter lengthCounter; - DutyUnit dutyUnit; - EnvelopeUnit envelopeUnit; - - SoundUnit *nextEventUnit; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - - unsigned char nr4; - bool master; - - void setEvent(); - -public: - Channel2(); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - - void setSo(unsigned long soMask); - // void deactivate() { disableMaster(); setEvent(); } - bool isActive() const { return master; } - - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - void reset(); - void init(bool cgb); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL2_H +#define SOUND_CHANNEL2_H + +#include "gbint.h" +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel2 { + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel2(); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + // void deactivate() { disableMaster(); setEvent(); } + bool isActive() const { return master; } + + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel3.cpp b/libgambatte/src/sound/channel3.cpp index d4cf78414c..0d0d069f9f 100644 --- a/libgambatte/src/sound/channel3.cpp +++ b/libgambatte/src/sound/channel3.cpp @@ -196,27 +196,27 @@ void Channel3::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel3) -{ - NSS(waveRam); - - SSS(lengthCounter); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - NSS(waveCounter); - NSS(lastReadTime); - - NSS(nr0); - NSS(nr3); - NSS(nr4); - NSS(wavePos); - NSS(rShift); - NSS(sampleBuf); - - NSS(master); - NSS(cgb); -} +SYNCFUNC(Channel3) +{ + NSS(waveRam); + + SSS(lengthCounter); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + NSS(waveCounter); + NSS(lastReadTime); + + NSS(nr0); + NSS(nr3); + NSS(nr4); + NSS(wavePos); + NSS(rShift); + NSS(sampleBuf); + + NSS(master); + NSS(cgb); +} } diff --git a/libgambatte/src/sound/channel3.h b/libgambatte/src/sound/channel3.h index ae0ba2f64a..fe81789899 100644 --- a/libgambatte/src/sound/channel3.h +++ b/libgambatte/src/sound/channel3.h @@ -1,105 +1,105 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL3_H -#define SOUND_CHANNEL3_H - -#include "gbint.h" -#include "master_disabler.h" -#include "length_counter.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel3 { - class Ch3MasterDisabler : public MasterDisabler { - unsigned long &waveCounter; - - public: - Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter(wC) {} - void operator()() { MasterDisabler::operator()(); waveCounter = SoundUnit::COUNTER_DISABLED; } - }; - - unsigned char waveRam[0x10]; - - Ch3MasterDisabler disableMaster; - LengthCounter lengthCounter; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - unsigned long waveCounter; - unsigned long lastReadTime; - - unsigned char nr0; - unsigned char nr3; - unsigned char nr4; - unsigned char wavePos; - unsigned char rShift; - unsigned char sampleBuf; - - bool master; - bool cgb; - - void updateWaveCounter(unsigned long cc); - -public: - Channel3(); - bool isActive() const { return master; } - void reset(); - void init(bool cgb); - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state); - void setNr0(unsigned data); - void setNr1(unsigned data) { lengthCounter.nr1Change(data, nr4, cycleCounter); } - void setNr2(unsigned data); - void setNr3(unsigned data) { nr3 = data; } - void setNr4(unsigned data); - void setSo(unsigned long soMask); - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - unsigned waveRamRead(unsigned index) const { - if (master) { - if (!cgb && cycleCounter != lastReadTime) - return 0xFF; - - index = wavePos >> 1; - } - - return waveRam[index]; - } - - void waveRamWrite(unsigned index, unsigned data) { - if (master) { - if (!cgb && cycleCounter != lastReadTime) - return; - - index = wavePos >> 1; - } - - waveRam[index] = data; - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL3_H +#define SOUND_CHANNEL3_H + +#include "gbint.h" +#include "master_disabler.h" +#include "length_counter.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel3 { + class Ch3MasterDisabler : public MasterDisabler { + unsigned long &waveCounter; + + public: + Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter(wC) {} + void operator()() { MasterDisabler::operator()(); waveCounter = SoundUnit::COUNTER_DISABLED; } + }; + + unsigned char waveRam[0x10]; + + Ch3MasterDisabler disableMaster; + LengthCounter lengthCounter; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + unsigned long waveCounter; + unsigned long lastReadTime; + + unsigned char nr0; + unsigned char nr3; + unsigned char nr4; + unsigned char wavePos; + unsigned char rShift; + unsigned char sampleBuf; + + bool master; + bool cgb; + + void updateWaveCounter(unsigned long cc); + +public: + Channel3(); + bool isActive() const { return master; } + void reset(); + void init(bool cgb); + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state); + void setNr0(unsigned data); + void setNr1(unsigned data) { lengthCounter.nr1Change(data, nr4, cycleCounter); } + void setNr2(unsigned data); + void setNr3(unsigned data) { nr3 = data; } + void setNr4(unsigned data); + void setSo(unsigned long soMask); + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + unsigned waveRamRead(unsigned index) const { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return 0xFF; + + index = wavePos >> 1; + } + + return waveRam[index]; + } + + void waveRamWrite(unsigned index, unsigned data) { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return; + + index = wavePos >> 1; + } + + waveRam[index] = data; + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel4.cpp b/libgambatte/src/sound/channel4.cpp index f1b355adc7..31c0590646 100644 --- a/libgambatte/src/sound/channel4.cpp +++ b/libgambatte/src/sound/channel4.cpp @@ -157,15 +157,15 @@ void Channel4::Lfsr::loadState(const SaveState &state) { nr3 = state.mem.ioamhram.get()[0x122]; } -template -void Channel4::Lfsr::SyncState(NewState *ns) -{ - NSS(counter); - NSS(backupCounter); - NSS(reg); - NSS(nr3); - NSS(master); -} +template +void Channel4::Lfsr::SyncState(NewState *ns) +{ + NSS(counter); + NSS(backupCounter); + NSS(reg); + NSS(nr3); + NSS(master); +} Channel4::Channel4() : staticOutputTest(*this, lfsr), @@ -295,25 +295,25 @@ void Channel4::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign cycleCounter -= SoundUnit::COUNTER_MAX; } } - -SYNCFUNC(Channel4) -{ - SSS(lengthCounter); - SSS(envelopeUnit); - SSS(lfsr); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &lfsr, 1); - EVS(nextEventUnit, &envelopeUnit, 2); - EVS(nextEventUnit, &lengthCounter, 3); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} + +SYNCFUNC(Channel4) +{ + SSS(lengthCounter); + SSS(envelopeUnit); + SSS(lfsr); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &lfsr, 1); + EVS(nextEventUnit, &envelopeUnit, 2); + EVS(nextEventUnit, &lengthCounter, 3); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel4.h b/libgambatte/src/sound/channel4.h index 6365ad682f..785c98f2f6 100644 --- a/libgambatte/src/sound/channel4.h +++ b/libgambatte/src/sound/channel4.h @@ -24,7 +24,7 @@ #include "length_counter.h" #include "envelope_unit.h" #include "static_output_tester.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -52,7 +52,7 @@ class Channel4 { void killCounter() { counter = COUNTER_DISABLED; } void reviveCounter(unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; class Ch4MasterDisabler : public MasterDisabler { @@ -97,7 +97,7 @@ public: void init(bool cgb); void loadState(const SaveState &state); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/duty_unit.cpp b/libgambatte/src/sound/duty_unit.cpp index 2059239a75..02ba2a8a04 100644 --- a/libgambatte/src/sound/duty_unit.cpp +++ b/libgambatte/src/sound/duty_unit.cpp @@ -142,15 +142,15 @@ void DutyUnit::reviveCounter(const unsigned long cc) { setCounter(); } -SYNCFUNC(DutyUnit) -{ - NSS(counter); - NSS(nextPosUpdate); - NSS(period); - NSS(pos); - NSS(duty); - NSS(high); - NSS(enableEvents); -} +SYNCFUNC(DutyUnit) +{ + NSS(counter); + NSS(nextPosUpdate); + NSS(period); + NSS(pos); + NSS(duty); + NSS(high); + NSS(enableEvents); +} } diff --git a/libgambatte/src/sound/duty_unit.h b/libgambatte/src/sound/duty_unit.h index cdf4a64f32..0a5f3e9412 100644 --- a/libgambatte/src/sound/duty_unit.h +++ b/libgambatte/src/sound/duty_unit.h @@ -22,7 +22,7 @@ #include "sound_unit.h" #include "master_disabler.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -55,7 +55,7 @@ public: unsigned getFreq() const { return 2048 - (period >> 1); } void setFreq(unsigned newFreq, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; class DutyMasterDisabler : public MasterDisabler { diff --git a/libgambatte/src/sound/envelope_unit.cpp b/libgambatte/src/sound/envelope_unit.cpp index f482bbf7a3..7c81607fa0 100644 --- a/libgambatte/src/sound/envelope_unit.cpp +++ b/libgambatte/src/sound/envelope_unit.cpp @@ -98,11 +98,11 @@ void EnvelopeUnit::loadState(const SaveState::SPU::Env &estate, const unsigned n this->nr2 = nr2; } -SYNCFUNC(EnvelopeUnit) -{ - NSS(counter); - NSS(nr2); - NSS(volume); -} +SYNCFUNC(EnvelopeUnit) +{ + NSS(counter); + NSS(nr2); + NSS(volume); +} } diff --git a/libgambatte/src/sound/envelope_unit.h b/libgambatte/src/sound/envelope_unit.h index 68d6bdc54c..4a0587d141 100644 --- a/libgambatte/src/sound/envelope_unit.h +++ b/libgambatte/src/sound/envelope_unit.h @@ -21,7 +21,7 @@ #include "sound_unit.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -48,7 +48,7 @@ public: void reset(); void loadState(const SaveState::SPU::Env &estate, unsigned nr2, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/length_counter.cpp b/libgambatte/src/sound/length_counter.cpp index cb3616a4ff..e1e9f00862 100644 --- a/libgambatte/src/sound/length_counter.cpp +++ b/libgambatte/src/sound/length_counter.cpp @@ -83,11 +83,11 @@ void LengthCounter::loadState(const SaveState::SPU::LCounter &lstate, const unsi lengthCounter = lstate.lengthCounter; } -SYNCFUNC(LengthCounter) -{ - NSS(counter); - NSS(lengthCounter); - NSS(cgb); -} +SYNCFUNC(LengthCounter) +{ + NSS(counter); + NSS(lengthCounter); + NSS(cgb); +} } diff --git a/libgambatte/src/sound/length_counter.h b/libgambatte/src/sound/length_counter.h index 533606171a..7250ac0a10 100644 --- a/libgambatte/src/sound/length_counter.h +++ b/libgambatte/src/sound/length_counter.h @@ -21,7 +21,7 @@ #include "sound_unit.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -42,7 +42,7 @@ public: void init(bool cgb); void loadState(const SaveState::SPU::LCounter &lstate, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/tima.cpp b/libgambatte/src/tima.cpp index c125bd57fb..5272443ba2 100644 --- a/libgambatte/src/tima.cpp +++ b/libgambatte/src/tima.cpp @@ -163,13 +163,13 @@ void Tima::doIrqEvent(const TimaInterruptRequester timaIrq) { timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() + ((256u - tma_) << timaClock[tac_ & 3])); } -SYNCFUNC(Tima) -{ - NSS(lastUpdate_); - NSS(tmatime_); - NSS(tima_); - NSS(tma_); - NSS(tac_); -} +SYNCFUNC(Tima) +{ + NSS(lastUpdate_); + NSS(tmatime_); + NSS(tima_); + NSS(tma_); + NSS(tac_); +} } diff --git a/libgambatte/src/tima.h b/libgambatte/src/tima.h index 4bb6c31d31..a3c0c995bb 100644 --- a/libgambatte/src/tima.h +++ b/libgambatte/src/tima.h @@ -60,7 +60,7 @@ public: void doIrqEvent(TimaInterruptRequester timaIrq); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index fe17b72310..5b9fbc86c0 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -37,6 +37,10 @@ void LCD::setCgbPalette(unsigned *lut) { } unsigned long LCD::gbcToRgb32(const unsigned bgr15) { + unsigned long const r = bgr15 & 0x1F; + unsigned long const g = bgr15 >> 5 & 0x1F; + unsigned long const b = bgr15 >> 10 & 0x1F; + return cgbColorsRgb32[bgr15 & 0x7FFF]; } @@ -66,6 +70,10 @@ void LCD::reset(const unsigned char *const oamram, const unsigned char *vram, co refreshPalettes(); } +void LCD::setCgb(bool cgb) { + ppu.setCgb(cgb); +} + static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter) { if (!(statReg & 0x20)) return DISABLED_TIME; @@ -150,6 +158,32 @@ void LCD::refreshPalettes() { } } +void LCD::copyCgbPalettesToDmg() { + for (unsigned i = 0; i < 4; i++) { + dmgColorsRgb32[i] = gbcToRgb32(bgpData[i * 2] | bgpData[i * 2 + 1] << 8); + } + for (unsigned i = 0; i < 8; i++) { + dmgColorsRgb32[i + 4] = gbcToRgb32(objpData[i * 2] | objpData[i * 2 + 1] << 8); + } +} + +void LCD::blackScreen() { + if (ppu.cgb()) { + for (unsigned i = 0; i < 8 * 8; i += 2) { + ppu.bgPalette()[i >> 1] = 0; + ppu.spPalette()[i >> 1] = 0; + } + } + else { + for (unsigned i = 0; i < 4; i++) { + dmgColorsRgb32[i] = 0; + } + for (unsigned i = 0; i < 8; i++) { + dmgColorsRgb32[i + 4] = 0; + } + } +} + namespace { template @@ -734,19 +768,19 @@ void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, con // don't need to save or load rgb32 color data -SYNCFUNC(LCD) -{ - SSS(ppu); - NSS(bgpData); - NSS(objpData); - SSS(eventTimes_); - SSS(m0Irq_); - SSS(lycIrq); - SSS(nextM0Time_); - - NSS(statReg); - NSS(m2IrqStatReg_); - NSS(m1IrqStatReg_); -} +SYNCFUNC(LCD) +{ + SSS(ppu); + NSS(bgpData); + NSS(objpData); + SSS(eventTimes_); + SSS(m0Irq_); + SSS(lycIrq); + SSS(nextM0Time_); + + NSS(statReg); + NSS(m2IrqStatReg_); + NSS(m1IrqStatReg_); +} } diff --git a/libgambatte/src/video.h b/libgambatte/src/video.h index cf4cd6e30a..a42bd5608f 100644 --- a/libgambatte/src/video.h +++ b/libgambatte/src/video.h @@ -25,7 +25,7 @@ #include "interruptrequester.h" #include "minkeeper.h" #include -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -79,10 +79,10 @@ public: unsigned statReg() const { return statReg_; } template - void SyncState(NewState *ns) - { - NSS(statReg_); - NSS(lycReg_); + void SyncState(NewState *ns) + { + NSS(statReg_); + NSS(lycReg_); } }; @@ -120,12 +120,12 @@ class LCD { void flagIrq(const unsigned bit) { memEventRequester_.flagIrq(bit); } void flagHdmaReq() { memEventRequester_.flagHdmaReq(); } - template - void SyncState(NewState *ns) - { - SSS(eventMin_); - SSS(memEventMin_); - //SSS(memEventRequester_); // not needed + template + void SyncState(NewState *ns) + { + SSS(eventMin_); + SSS(memEventMin_); + //SSS(memEventRequester_); // not needed } }; @@ -175,6 +175,9 @@ public: void setCgbPalette(unsigned *lut); void setVideoBuffer(uint_least32_t *videoBuf, int pitch); void setLayers(unsigned mask) { ppu.setLayers(mask); } + void setCgb(bool cgb); + void copyCgbPalettesToDmg(); + void blackScreen(); int debugGetLY() const { return ppu.lyCounter().ly(); } @@ -273,7 +276,7 @@ public: void setScanlineCallback(void (*callback)(), int sl) { scanlinecallback = callback; scanlinecallbacksl = sl; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/ly_counter.cpp b/libgambatte/src/video/ly_counter.cpp index 01ae017132..4b978dfeee 100644 --- a/libgambatte/src/video/ly_counter.cpp +++ b/libgambatte/src/video/ly_counter.cpp @@ -64,13 +64,13 @@ void LyCounter::setDoubleSpeed(const bool ds_in) { ds = ds_in; lineTime_ = 456U << ds_in; } - -SYNCFUNC(LyCounter) -{ - NSS(time_); - NSS(lineTime_); - NSS(ly_); - NSS(ds); -} + +SYNCFUNC(LyCounter) +{ + NSS(time_); + NSS(lineTime_); + NSS(ly_); + NSS(ds); +} } diff --git a/libgambatte/src/video/ly_counter.h b/libgambatte/src/video/ly_counter.h index f65d9ef7dc..9aca9877b1 100644 --- a/libgambatte/src/video/ly_counter.h +++ b/libgambatte/src/video/ly_counter.h @@ -19,7 +19,7 @@ #ifndef LY_COUNTER_H #define LY_COUNTER_H -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -52,7 +52,7 @@ public: void setDoubleSpeed(bool ds_in); unsigned long time() const { return time_; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/lyc_irq.cpp b/libgambatte/src/video/lyc_irq.cpp index d9a138a3fd..024ef35233 100644 --- a/libgambatte/src/video/lyc_irq.cpp +++ b/libgambatte/src/video/lyc_irq.cpp @@ -95,14 +95,14 @@ void LycIrq::lcdReset() { lycReg_ = lycRegSrc_; } -SYNCFUNC(LycIrq) -{ - NSS(time_); - NSS(lycRegSrc_); - NSS(statRegSrc_); - NSS(lycReg_); - NSS(statReg_); - NSS(cgb_); -} +SYNCFUNC(LycIrq) +{ + NSS(time_); + NSS(lycRegSrc_); + NSS(statRegSrc_); + NSS(lycReg_); + NSS(statReg_); + NSS(cgb_); +} } diff --git a/libgambatte/src/video/lyc_irq.h b/libgambatte/src/video/lyc_irq.h index 19577a802a..510039d996 100644 --- a/libgambatte/src/video/lyc_irq.h +++ b/libgambatte/src/video/lyc_irq.h @@ -19,7 +19,7 @@ #ifndef VIDEO_LYC_IRQ_H #define VIDEO_LYC_IRQ_H -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -54,7 +54,7 @@ public: regChange(statRegSrc_, lycReg, lyCounter, cc); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/next_m0_time.cpp b/libgambatte/src/video/next_m0_time.cpp index 15bf6441b6..79cc59d06c 100644 --- a/libgambatte/src/video/next_m0_time.cpp +++ b/libgambatte/src/video/next_m0_time.cpp @@ -7,9 +7,9 @@ void NextM0Time::predictNextM0Time(const PPU &ppu) { predictedNextM0Time_ = ppu.predictedNextXposTime(167); } -SYNCFUNC(NextM0Time) -{ - NSS(predictedNextM0Time_); -} - -} +SYNCFUNC(NextM0Time) +{ + NSS(predictedNextM0Time_); +} + +} diff --git a/libgambatte/src/video/next_m0_time.h b/libgambatte/src/video/next_m0_time.h index a47f337118..0a91c4d330 100644 --- a/libgambatte/src/video/next_m0_time.h +++ b/libgambatte/src/video/next_m0_time.h @@ -1,7 +1,7 @@ #ifndef NEXT_M0_TIME_H_ #define NEXT_M0_TIME_H_ -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -14,7 +14,7 @@ public: void invalidatePredictedNextM0Time() { predictedNextM0Time_ = 0; } unsigned predictedNextM0Time() const { return predictedNextM0Time_; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index ed884f7b97..c1f10175f5 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -1630,6 +1630,7 @@ void PPU::loadState(const SaveState &ss, const unsigned char *const oamram) { p_.weMaster = ss.ppu.weMaster; p_.winDrawState = ss.ppu.winDrawState & (WIN_DRAW_START | WIN_DRAW_STARTED); p_.lastM0Time = p_.now - ss.ppu.lastM0Time; + p_.cgb = ss.ppu.isCgb; loadSpriteList(p_, ss); if (m3loopState && videoCycles < 144 * 456L && p_.xpos < 168 @@ -1739,70 +1740,70 @@ void PPU::update(const unsigned long cc) { } } -SYNCFUNC(PPU) -{ - NSS(p_.bgPalette); - NSS(p_.spPalette); - NSS(p_.spriteList); - NSS(p_.spwordList); - NSS(p_.nextSprite); - NSS(p_.currentSprite); - - EBS(p_.nextCallPtr, 0); - EVS(p_.nextCallPtr, &M2::Ly0::f0_, 1); - EVS(p_.nextCallPtr, &M2::LyNon0::f0_, 2); - EVS(p_.nextCallPtr, &M2::LyNon0::f1_, 3); - EVS(p_.nextCallPtr, &M3Start::f0_, 4); - EVS(p_.nextCallPtr, &M3Start::f1_, 5); - EVS(p_.nextCallPtr, &M3Loop::Tile::f0_, 6); - EVS(p_.nextCallPtr, &M3Loop::Tile::f1_, 7); - EVS(p_.nextCallPtr, &M3Loop::Tile::f2_, 8); - EVS(p_.nextCallPtr, &M3Loop::Tile::f3_, 9); - EVS(p_.nextCallPtr, &M3Loop::Tile::f4_, 10); - EVS(p_.nextCallPtr, &M3Loop::Tile::f5_, 11); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f0_, 12); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f1_, 13); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f2_, 14); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f3_, 15); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f4_, 16); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f5_, 17); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f0_, 18); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f1_, 19); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f2_, 20); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f3_, 21); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f4_, 22); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f5_, 23); - EES(p_.nextCallPtr, NULL); - - NSS(p_.now); - NSS(p_.lastM0Time); - NSS(p_.cycles); - - NSS(p_.tileword); - NSS(p_.ntileword); - - SSS(p_.spriteMapper); - SSS(p_.lyCounter); - //SSS(p_.framebuf); // no state - - NSS(p_.lcdc); - NSS(p_.scy); - NSS(p_.scx); - NSS(p_.wy); - NSS(p_.wy2); - NSS(p_.wx); - NSS(p_.winDrawState); - NSS(p_.wscx); - NSS(p_.winYPos); - NSS(p_.reg0); - NSS(p_.reg1); - NSS(p_.attrib); - NSS(p_.nattrib); - NSS(p_.xpos); - NSS(p_.endx); - - NSS(p_.cgb); - NSS(p_.weMaster); -} +SYNCFUNC(PPU) +{ + NSS(p_.bgPalette); + NSS(p_.spPalette); + NSS(p_.spriteList); + NSS(p_.spwordList); + NSS(p_.nextSprite); + NSS(p_.currentSprite); + + EBS(p_.nextCallPtr, 0); + EVS(p_.nextCallPtr, &M2::Ly0::f0_, 1); + EVS(p_.nextCallPtr, &M2::LyNon0::f0_, 2); + EVS(p_.nextCallPtr, &M2::LyNon0::f1_, 3); + EVS(p_.nextCallPtr, &M3Start::f0_, 4); + EVS(p_.nextCallPtr, &M3Start::f1_, 5); + EVS(p_.nextCallPtr, &M3Loop::Tile::f0_, 6); + EVS(p_.nextCallPtr, &M3Loop::Tile::f1_, 7); + EVS(p_.nextCallPtr, &M3Loop::Tile::f2_, 8); + EVS(p_.nextCallPtr, &M3Loop::Tile::f3_, 9); + EVS(p_.nextCallPtr, &M3Loop::Tile::f4_, 10); + EVS(p_.nextCallPtr, &M3Loop::Tile::f5_, 11); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f0_, 12); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f1_, 13); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f2_, 14); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f3_, 15); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f4_, 16); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f5_, 17); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f0_, 18); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f1_, 19); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f2_, 20); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f3_, 21); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f4_, 22); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f5_, 23); + EES(p_.nextCallPtr, NULL); + + NSS(p_.now); + NSS(p_.lastM0Time); + NSS(p_.cycles); + + NSS(p_.tileword); + NSS(p_.ntileword); + + SSS(p_.spriteMapper); + SSS(p_.lyCounter); + //SSS(p_.framebuf); // no state + + NSS(p_.lcdc); + NSS(p_.scy); + NSS(p_.scx); + NSS(p_.wy); + NSS(p_.wy2); + NSS(p_.wx); + NSS(p_.winDrawState); + NSS(p_.wscx); + NSS(p_.winYPos); + NSS(p_.reg0); + NSS(p_.reg1); + NSS(p_.attrib); + NSS(p_.nattrib); + NSS(p_.xpos); + NSS(p_.endx); + + NSS(p_.cgb); + NSS(p_.weMaster); +} } diff --git a/libgambatte/src/video/ppu.h b/libgambatte/src/video/ppu.h index 6ca279af9e..a32176d589 100644 --- a/libgambatte/src/video/ppu.h +++ b/libgambatte/src/video/ppu.h @@ -23,7 +23,7 @@ #include "video/sprite_mapper.h" #include "gbint.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -133,8 +133,9 @@ public: unsigned long * spPalette() { return p_.spPalette; } void update(unsigned long cc); void setLayers(unsigned mask) { p_.layersMask = mask; } + void setCgb(bool cgb) { p_.cgb = cgb; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/sprite_mapper.cpp b/libgambatte/src/video/sprite_mapper.cpp index 80b588c088..cd2e28b1b2 100644 --- a/libgambatte/src/video/sprite_mapper.cpp +++ b/libgambatte/src/video/sprite_mapper.cpp @@ -117,16 +117,16 @@ void SpriteMapper::OamReader::loadState(const SaveState &ss, const unsigned char change(lu); } -SYNCFUNC(SpriteMapper::OamReader) -{ - NSS(buf); - NSS(szbuf); - - NSS(lu); - NSS(lastChange); - NSS(largeSpritesSrc); - NSS(cgb_); -} +SYNCFUNC(SpriteMapper::OamReader) +{ + NSS(buf); + NSS(szbuf); + + NSS(lu); + NSS(lastChange); + NSS(largeSpritesSrc); + NSS(cgb_); +} void SpriteMapper::OamReader::enableDisplay(const unsigned long cc) { std::memset(buf, 0x00, sizeof(buf)); @@ -190,13 +190,13 @@ unsigned long SpriteMapper::doEvent(const unsigned long time) { return oamReader.changed() ? time + oamReader.lyCounter.lineTime() : static_cast(DISABLED_TIME); } -SYNCFUNC(SpriteMapper) -{ - NSS(spritemap); - NSS(num); - - SSS(nextM0Time_); - SSS(oamReader); -} +SYNCFUNC(SpriteMapper) +{ + NSS(spritemap); + NSS(num); + + SSS(nextM0Time_); + SSS(oamReader); +} } diff --git a/libgambatte/src/video/sprite_mapper.h b/libgambatte/src/video/sprite_mapper.h index 09e269d769..b6171f842d 100644 --- a/libgambatte/src/video/sprite_mapper.h +++ b/libgambatte/src/video/sprite_mapper.h @@ -21,7 +21,7 @@ #include "ly_counter.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { class NextM0Time; @@ -58,7 +58,7 @@ class SpriteMapper { void loadState(const SaveState &ss, const unsigned char *oamram); bool inactivePeriodAfterDisplayEnable(const unsigned long cc) const { return cc < lu; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; enum { NEED_SORTING_MASK = 0x80 }; @@ -124,7 +124,7 @@ public: void loadState(const SaveState &state, const unsigned char *const oamram) { oamReader.loadState(state, oamram); mapSprites(); } bool inactivePeriodAfterDisplayEnable(unsigned long cc) const { return oamReader.inactivePeriodAfterDisplayEnable(cc); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libmupen64plus/GLideN64 b/libmupen64plus/GLideN64 index 7ea2de6dda..3d09b7719e 160000 --- a/libmupen64plus/GLideN64 +++ b/libmupen64plus/GLideN64 @@ -1 +1 @@ -Subproject commit 7ea2de6ddac62351de472e69a84fd90b59ec95ad +Subproject commit 3d09b7719e0181ace91c7f2c7c966cfae410561c diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index 17abbf85e0..20e9164416 100644 Binary files a/output/dll/libgambatte.dll and b/output/dll/libgambatte.dll differ diff --git a/output/dll/lua51.dll b/output/dll/lua51.dll index 09644398cf..bff6562fd5 100644 Binary files a/output/dll/lua51.dll and b/output/dll/lua51.dll differ diff --git a/output/dll/mgba.dll b/output/dll/mgba.dll index e902245ec3..ab39aaeb38 100644 Binary files a/output/dll/mgba.dll and b/output/dll/mgba.dll differ diff --git a/output/dll/mupen64plus-video-GLideN64.dll b/output/dll/mupen64plus-video-GLideN64.dll index 1b51968f13..dc59326a68 100644 Binary files a/output/dll/mupen64plus-video-GLideN64.dll and b/output/dll/mupen64plus-video-GLideN64.dll differ