diff --git a/.gitmodules b/.gitmodules index 3f826bf4e6..6fa9c4bc00 100644 --- a/.gitmodules +++ b/.gitmodules @@ -88,3 +88,7 @@ [submodule "waterbox/dosbox/dosbox-x"] path = waterbox/dosbox/dosbox-x url = https://github.com/TASEmulators/dosbox-x.git +[submodule "waterbox/dsda/core"] + path = waterbox/dsda/core + url = https://github.com/TASEmulators/dsda-doom.git + branch = wbx diff --git a/Assets/defctrl.json b/Assets/defctrl.json index 92467a990d..4588f7d419 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -1117,6 +1117,32 @@ "GamepadPrefix": "X1 ", "Prescale": 1.0 } + }, + "Gameboy Controller": { + "Rumble": { + "Channels": "Left+Right", + "GamepadPrefix": "X1 ", + "Prescale": 1.0 + } + }, + "GBA Controller": { + "Rumble": { + "Channels": "Left+Right", + "GamepadPrefix": "X1 ", + "Prescale": 1.0 + } + }, + "PSX Front Panel": { + "P1 Rumble Left (strong)": { + "Channels": "Left", + "GamepadPrefix": "X1 ", + "Prescale": 1.0 + }, + "P1 Rumble Right (weak)": { + "Channels": "Right", + "GamepadPrefix": "X1 ", + "Prescale": 1.0 + } } } } diff --git a/Assets/dll/dsda.wbx.zst b/Assets/dll/dsda.wbx.zst new file mode 100644 index 0000000000..86e25ebf0e Binary files /dev/null and b/Assets/dll/dsda.wbx.zst differ diff --git a/Assets/dll/gpgx.wbx.zst b/Assets/dll/gpgx.wbx.zst index 517ad74495..81bff2bcf7 100644 Binary files a/Assets/dll/gpgx.wbx.zst and b/Assets/dll/gpgx.wbx.zst differ diff --git a/src/BizHawk.Client.Common/Api/Classes/UserDataApi.cs b/src/BizHawk.Client.Common/Api/Classes/UserDataApi.cs index a2bf2a5863..03f9e2ba71 100644 --- a/src/BizHawk.Client.Common/Api/Classes/UserDataApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/UserDataApi.cs @@ -18,7 +18,11 @@ namespace BizHawk.Client.Common get { ICollection keys = _movieSession.UserBag.Keys; +#if NET5_0_OR_GREATER + return (keys as KeyCollectionType) ?? keys.ToHashSet(); +#else return (keys as KeyCollectionType) ?? keys.ToList(); +#endif } } diff --git a/src/BizHawk.Client.Common/Controller.cs b/src/BizHawk.Client.Common/Controller.cs index e159e8fd0f..eba8729950 100644 --- a/src/BizHawk.Client.Common/Controller.cs +++ b/src/BizHawk.Client.Common/Controller.cs @@ -48,6 +48,11 @@ namespace BizHawk.Client.Common private readonly Dictionary _feedbackBindings = new Dictionary(); +#if BIZHAWKBUILD_SUPERHAWK + public bool AnyInputHeld + => _buttons.ContainsValue(true) || _axes.Any(kvp => kvp.Value != _axisRanges[kvp.Key].Neutral); +#endif + public bool this[string button] => IsPressed(button); // Looks for bindings which are activated by the supplied physical button. diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index d4622a25ab..763478430a 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -990,6 +990,8 @@ namespace BizHawk.Client.Common public static readonly IReadOnlyCollection DOS = new[] { "ima", "img" }; + public static readonly IReadOnlyCollection Doom = new[] { "wad" }; + public static readonly IReadOnlyCollection GB = new[] { "gb", "gbc", "sgb" }; public static readonly IReadOnlyCollection GBA = new[] { "gba" }; @@ -1048,6 +1050,7 @@ namespace BizHawk.Client.Common .Concat(C64) .Concat(Coleco) .Concat(DOS) + .Concat(Doom) .Concat(GB) .Concat(GBA) .Concat(GEN) @@ -1091,6 +1094,7 @@ namespace BizHawk.Client.Common new FilesystemFilter(/*VSystemID.Raw.C64*/"SID Commodore 64 Music File", Array.Empty(), devBuildExtraExts: new[] { "sid" }, devBuildAddArchiveExts: true), new FilesystemFilter(/*VSystemID.Raw.DOS*/"DOS", RomFileExtensions.DOS), new FilesystemFilter(/*VSystemID.Raw.Coleco*/"ColecoVision", RomFileExtensions.Coleco, addArchiveExts: true), + new FilesystemFilter(/*VSystemID.Raw.Doom*/"Doom / Hexen / Heretic WAD File", RomFileExtensions.Doom), new FilesystemFilter(/*VSystemID.Raw.GB*/"Gameboy", RomFileExtensions.GB.Concat(new[] { "gbs" }).ToList(), addArchiveExts: true), new FilesystemFilter(/*VSystemID.Raw.GBA*/"Gameboy Advance", RomFileExtensions.GBA, addArchiveExts: true), new FilesystemFilter(/*VSystemID.Raw.GEN*/"Genesis", RomFileExtensions.GEN.Concat(FilesystemFilter.DiscExtensions).ToList(), addArchiveExts: true), diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs index e41402915f..9e4aa62ce9 100644 --- a/src/BizHawk.Client.Common/config/Config.cs +++ b/src/BizHawk.Client.Common/config/Config.cs @@ -115,6 +115,7 @@ namespace BizHawk.Client.Common private Dictionary TargetZoomFactors { get; set; } = new() { + [VSystemID.Raw.Doom] = 1, [VSystemID.Raw.GB] = 3, [VSystemID.Raw.GBA] = 3, [VSystemID.Raw.GBC] = 3, @@ -203,6 +204,9 @@ namespace BizHawk.Client.Common public bool Unthrottled { get; set; } = false; public bool AutoMinimizeSkipping { get; set; } = true; public bool VSyncThrottle { get; set; } = false; +#if BIZHAWKBUILD_SUPERHAWK + public bool SuperHawkThrottle { get; set; } = false; +#endif public RewindConfig Rewind { get; set; } = new RewindConfig(); diff --git a/src/BizHawk.Client.Common/config/RewindConfig.cs b/src/BizHawk.Client.Common/config/RewindConfig.cs index 992253cb40..b85ac563b0 100644 --- a/src/BizHawk.Client.Common/config/RewindConfig.cs +++ b/src/BizHawk.Client.Common/config/RewindConfig.cs @@ -25,7 +25,7 @@ bool UseFixedRewindInterval { get; } /// - /// Desired frame length (number of emulated frames you can go back before running out of buffer) + /// Desired minimum rewind range (number of emulated frames you can go back before running out of buffer) /// int TargetFrameLength { get; } diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs index 5d30328994..81b34a9017 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs @@ -164,7 +164,7 @@ namespace BizHawk.Client.Common long addr, int length, string domain) - => APIs.Memory.WriteByteRange(addr, new List(APIs.Comm.MMF.ReadBytesFromFile(mmf_filename, length)), domain); + => APIs.Memory.WriteByteRange(addr, APIs.Comm.MMF.ReadBytesFromFile(mmf_filename, length), domain); [LuaMethod("mmfRead", "Reads a string from a memory mapped file")] public string MmfRead(string mmf_filename, int expectedSize) diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index 2cacfa3815..79680e62a2 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -82,12 +82,6 @@ namespace BizHawk.Client.Common else if (Movie.IsPlaying()) { LatchInputToLog(); - // if we're at the movie's end and the MovieEndAction is record, just continue recording in play mode - // TODO change to TAStudio check - if (Movie is ITasMovie && Movie.Emulator.Frame == Movie.FrameCount && Settings.MovieEndAction == MovieEndAction.Record) - { - Movie.RecordFrame(Movie.Emulator.Frame, MovieOut.Source); - } } else if (Movie.IsRecording()) { @@ -96,16 +90,14 @@ namespace BizHawk.Client.Common } } - public void HandleFrameAfter() + public void HandleFrameAfter(bool ignoreMovieEndAction) { if (Movie is ITasMovie tasMovie) { tasMovie.GreenzoneCurrentFrame(); - // TODO change to TAStudio check - if (Settings.MovieEndAction == MovieEndAction.Record) return; } - if (Movie.IsPlaying() && Movie.Emulator.Frame >= Movie.FrameCount) + if (!ignoreMovieEndAction && Movie.IsPlaying() && Movie.Emulator.Frame == Movie.FrameCount) { HandlePlaybackEnd(); } diff --git a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs index 792314f540..6aaa127a56 100644 --- a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs +++ b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs @@ -34,6 +34,7 @@ namespace BizHawk.Client.Common // note: ChannelF II PAL timings might be slightly different... ["ChannelF_PAL"] = 15625.0 / 312.0, // 4000000 / (256 * 312) ["Coleco"] = 59.9227510135505, + ["Doom"] = 35.0, ["FDS"] = 60.098813897440515532, ["FDS_PAL"] = 50.006977968268290849, ["GEN"] = 53693175 / (3420.0 * 262), diff --git a/src/BizHawk.Client.Common/movie/import/BkmImport.cs b/src/BizHawk.Client.Common/movie/import/BkmImport.cs index 8d2cf60546..d45dc2fc63 100644 --- a/src/BizHawk.Client.Common/movie/import/BkmImport.cs +++ b/src/BizHawk.Client.Common/movie/import/BkmImport.cs @@ -1,8 +1,9 @@ using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores; namespace BizHawk.Client.Common.movie.import { - // ReSharper disable once UnusedMember.Global [ImporterFor("BizHawk", ".bkm")] internal class BkmImport : MovieImporter { @@ -13,15 +14,20 @@ namespace BizHawk.Client.Common.movie.import for (var i = 0; i < bkm.InputLogLength; i++) { - // TODO: this is currently broken because Result.Movie.Emulator is no longer getting set, - // however using that was sketchy anyway because it relied on the currently loaded core for import - var input = bkm.GetInputState(i, Result.Movie.Emulator.ControllerDefinition, bkm.Header[HeaderKeys.Platform]); + var input = bkm.GetInputState(i, bkm.Header[HeaderKeys.Platform]); Result.Movie.AppendFrame(input); } + Result.Movie.LogKey = bkm.GenerateLogKey; + Result.Movie.HeaderEntries.Clear(); foreach (var (k, v) in bkm.Header) Result.Movie.HeaderEntries[k] = v; + // migrate some stuff, probably incomplete + if (Result.Movie.HeaderEntries[HeaderKeys.Core] is "QuickNes") Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.QuickNes; + if (Result.Movie.HeaderEntries[HeaderKeys.Core] is "EMU7800") Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.A7800Hawk; + if (Result.Movie.HeaderEntries[HeaderKeys.Platform] is "DGB") Result.Movie.HeaderEntries[HeaderKeys.Platform] = VSystemID.Raw.GBL; + Result.Movie.SyncSettingsJson = bkm.SyncSettingsJson; Result.Movie.Comments.Clear(); diff --git a/src/BizHawk.Client.Common/movie/import/DoomLmpImport.cs b/src/BizHawk.Client.Common/movie/import/DoomLmpImport.cs new file mode 100644 index 0000000000..24b31a6087 --- /dev/null +++ b/src/BizHawk.Client.Common/movie/import/DoomLmpImport.cs @@ -0,0 +1,86 @@ +using BizHawk.Common.IOExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores; +using BizHawk.Emulation.Cores.Computers.Doom; + +namespace BizHawk.Client.Common +{ + // LMP file format: https://doomwiki.org/wiki/Demo#Technical_information + // In better detail, from archive.org: http://web.archive.org/web/20070630072856/http://demospecs.planetquake.gamespy.com/lmp/lmp.html + [ImporterFor("Doom", ".lmp")] + internal class DoomLmpImport : MovieImporter + { + protected override void RunImport() + { + var input = SourceFile.OpenRead().ReadAllBytes(); + var i = 0; + Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.DSDA; + Result.Movie.SystemID = VSystemID.Raw.Doom; + + // Try to decide game version based on signature + var signature = input[i]; + DSDA.CompatibilityLevel presumedCompatibilityLevel; + if (signature <= 102) + { + // there is no signature, the first byte is the skill level, so don't advance + Console.WriteLine("Reading DOOM LMP demo version: <=1.12"); + presumedCompatibilityLevel = DSDA.CompatibilityLevel.C0; + } + else + { + i++; + Console.WriteLine("Reading DOOM LMP demo version: {0}", signature); + presumedCompatibilityLevel = signature < 109 + ? DSDA.CompatibilityLevel.C1 // 1.666 + : DSDA.CompatibilityLevel.C2; // 1.9 + } + + DSDA.DoomSyncSettings syncSettings = new() + { + InputFormat = DoomControllerTypes.Doom, + CompatibilityMode = presumedCompatibilityLevel, + SkillLevel = (DSDA.SkillLevel) (1 + input[i++]), + InitialEpisode = input[i++], + InitialMap = input[i++], + MultiplayerMode = (DSDA.MultiplayerMode) input[i++], + MonstersRespawn = input[i++] is not 0, + FastMonsters = input[i++] is not 0, + NoMonsters = input[i++] is not 0, + }; + _ = input[i++]; // DisplayPlayer is a non-sync setting so importers can't* set it + syncSettings.Player1Present = input[i++] is not 0; + syncSettings.Player2Present = input[i++] is not 0; + syncSettings.Player3Present = input[i++] is not 0; + syncSettings.Player4Present = input[i++] is not 0; + Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings); + + var doomController1 = new DoomController(1); + var controller = new SimpleController(doomController1.Definition); + controller.Definition.BuildMnemonicsCache(Result.Movie.SystemID); + void ParsePlayer(string playerPfx) + { + controller.AcceptNewAxis(playerPfx + "Run Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Strafing Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Turning Speed", unchecked((sbyte) input[i++])); + + var specialValue = input[i++]; + controller[playerPfx + "Fire"] = (specialValue & 0b00000001) is not 0; + controller[playerPfx + "Action"] = (specialValue & 0b00000010) is not 0; + controller.AcceptNewAxis(playerPfx + "Weapon Select", (specialValue & 0b00011100) >> 2); + controller[playerPfx + "Alt Weapon"] = (specialValue & 0b00100000) is not 0; + } + do + { + if (syncSettings.Player1Present) ParsePlayer("P1 "); + if (syncSettings.Player2Present) ParsePlayer("P2 "); + if (syncSettings.Player3Present) ParsePlayer("P3 "); + if (syncSettings.Player4Present) ParsePlayer("P4 "); + Result.Movie.AppendFrame(controller); + if (i == input.Length) throw new Exception("Reached end of input movie stream without finalization byte"); + } + while (input[i] is not 0x80); + } + } +} diff --git a/src/BizHawk.Client.Common/movie/import/DsmImport.cs b/src/BizHawk.Client.Common/movie/import/DsmImport.cs index 021de6ec30..7ee4fa6ad2 100644 --- a/src/BizHawk.Client.Common/movie/import/DsmImport.cs +++ b/src/BizHawk.Client.Common/movie/import/DsmImport.cs @@ -11,6 +11,17 @@ namespace BizHawk.Client.Common [ImporterFor("DeSmuME", ".dsm")] internal class DsmImport : MovieImporter { + [Flags] + private enum MovieCommand + { + MIC = 1, + RESET = 2, + LID = 4 + } + + private bool _lidOpen = true; + private int _countLid; + private static readonly ControllerDefinition DeSmuMEControllerDef = new ControllerDefinition("NDS Controller") { BoolButtons = @@ -143,7 +154,30 @@ namespace BizHawk.Client.Common private void ProcessCmd(string cmd, SimpleController controller) { - // TODO + MovieCommand command = (MovieCommand) int.Parse(cmd); + + controller["Microphone"] = command.HasFlag(MovieCommand.MIC); + + bool hasPowerCommand = command.HasFlag(MovieCommand.RESET); + controller["Power"] = hasPowerCommand; + if (hasPowerCommand) + { + _lidOpen = true; + _countLid = 0; + } + + bool hasLidCommand = command.HasFlag(MovieCommand.LID); + controller["LidClose"] = hasLidCommand && _lidOpen && _countLid == 0; + controller["LidOpen"] = hasLidCommand && !_lidOpen && _countLid == 0; + if (hasLidCommand && _countLid == 0) + { + _countLid = 30; + _lidOpen = !_lidOpen; + } + else if (_countLid > 0) + { + _countLid--; + } } } } diff --git a/src/BizHawk.Client.Common/movie/import/HereticLmpImport.cs b/src/BizHawk.Client.Common/movie/import/HereticLmpImport.cs new file mode 100644 index 0000000000..483b2783f9 --- /dev/null +++ b/src/BizHawk.Client.Common/movie/import/HereticLmpImport.cs @@ -0,0 +1,70 @@ +using BizHawk.Common.IOExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores; +using BizHawk.Emulation.Cores.Computers.Doom; + +namespace BizHawk.Client.Common +{ + // LMP file format: https://doomwiki.org/wiki/Demo#Technical_information + // In better detail, from archive.org: http://web.archive.org/web/20070630072856/http://demospecs.planetquake.gamespy.com/lmp/lmp.html + [ImporterFor("Heretic", ".hereticlmp")] + internal class HereticLmpImport : MovieImporter + { + protected override void RunImport() + { + var input = SourceFile.OpenRead().ReadAllBytes(); + var i = 0; + Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.DSDA; + Result.Movie.SystemID = VSystemID.Raw.Doom; + DSDA.DoomSyncSettings syncSettings = new() + { + InputFormat = DoomControllerTypes.Heretic, + MultiplayerMode = DSDA.MultiplayerMode.M0, + MonstersRespawn = false, + FastMonsters = false, + NoMonsters = false, + CompatibilityMode = DSDA.CompatibilityLevel.C0, + SkillLevel = (DSDA.SkillLevel) (1 + input[i++]), + InitialEpisode = input[i++], + InitialMap = input[i++], + Player1Present = input[i++] is not 0, + Player2Present = input[i++] is not 0, + Player3Present = input[i++] is not 0, + Player4Present = input[i++] is not 0, + }; + Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings); + + var hereticController = new HereticController(1); + var controller = new SimpleController(hereticController.Definition); + controller.Definition.BuildMnemonicsCache(Result.Movie.SystemID); + void ParsePlayer(string playerPfx) + { + controller.AcceptNewAxis(playerPfx + "Run Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Strafing Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Turning Speed", unchecked((sbyte) input[i++])); + + var specialValue = input[i++]; + controller[playerPfx + "Fire"] = (specialValue & 0b00000001) is not 0; + controller[playerPfx + "Action"] = (specialValue & 0b00000010) is not 0; + controller.AcceptNewAxis(playerPfx + "Weapon Select", (specialValue & 0b00011100) >> 2); + controller[playerPfx + "Alt Weapon"] = (specialValue & 0b00100000) is not 0; + + controller.AcceptNewAxis(playerPfx + "Fly / Look", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Use Artifact", unchecked((sbyte) input[i++])); + } + do + { + if (syncSettings.Player1Present) ParsePlayer("P1 "); + if (syncSettings.Player2Present) ParsePlayer("P2 "); + if (syncSettings.Player3Present) ParsePlayer("P3 "); + if (syncSettings.Player4Present) ParsePlayer("P4 "); + Result.Movie.AppendFrame(controller); + if (i == input.Length) throw new Exception("Reached end of input movie stream without finalization byte"); + } + while (input[i] is not 0x80); + } + } +} diff --git a/src/BizHawk.Client.Common/movie/import/HexenLmpImport.cs b/src/BizHawk.Client.Common/movie/import/HexenLmpImport.cs new file mode 100644 index 0000000000..105adcf78c --- /dev/null +++ b/src/BizHawk.Client.Common/movie/import/HexenLmpImport.cs @@ -0,0 +1,85 @@ +using BizHawk.Common.IOExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores; +using BizHawk.Emulation.Cores.Computers.Doom; + +namespace BizHawk.Client.Common +{ + // LMP file format: https://doomwiki.org/wiki/Demo#Technical_information + // In better detail, from archive.org: http://web.archive.org/web/20070630072856/http://demospecs.planetquake.gamespy.com/lmp/lmp.html + [ImporterFor("Hexen", ".hexenlmp")] + internal class HexenLmpImport : MovieImporter + { + protected override void RunImport() + { + var input = SourceFile.OpenRead().ReadAllBytes(); + var i = 0; + Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.DSDA; + Result.Movie.SystemID = VSystemID.Raw.Doom; + DSDA.DoomSyncSettings syncSettings = new() + { + InputFormat = DoomControllerTypes.Hexen, + MultiplayerMode = DSDA.MultiplayerMode.M0, + MonstersRespawn = false, + FastMonsters = false, + NoMonsters = false, + CompatibilityMode = DSDA.CompatibilityLevel.C0, + SkillLevel = (DSDA.SkillLevel) (1 + input[i++]), + InitialEpisode = input[i++], + InitialMap = input[i++], + Player1Present = input[i++] is not 0, + Player1Class = (DSDA.HexenClass) input[i++], + Player2Present = input[i++] is not 0, + Player2Class = (DSDA.HexenClass) input[i++], + Player3Present = input[i++] is not 0, + Player3Class = (DSDA.HexenClass) input[i++], + Player4Present = input[i++] is not 0, + Player4Class = (DSDA.HexenClass) input[i++], + }; + _ = input[i++]; // player 5 isPresent + _ = input[i++]; // player 5 class + _ = input[i++]; // player 6 isPresent + _ = input[i++]; // player 6 class + _ = input[i++]; // player 7 isPresent + _ = input[i++]; // player 7 class + _ = input[i++]; // player 8 isPresent + _ = input[i++]; // player 8 class + Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings); + + var hexenController = new HexenController(1); + var controller = new SimpleController(hexenController.Definition); + controller.Definition.BuildMnemonicsCache(Result.Movie.SystemID); + void ParsePlayer(string playerPfx) + { + controller.AcceptNewAxis(playerPfx + "Run Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Strafing Speed", unchecked((sbyte) input[i++])); + + controller.AcceptNewAxis(playerPfx + "Turning Speed", unchecked((sbyte) input[i++])); + + var specialValue = input[i++]; + controller[playerPfx + "Fire"] = (specialValue & 0b00000001) is not 0; + controller[playerPfx + "Action"] = (specialValue & 0b00000010) is not 0; + controller.AcceptNewAxis(playerPfx + "Weapon Select", (specialValue & 0b00011100) >> 2); + controller[playerPfx + "Alt Weapon"] = (specialValue & 0b00100000) is not 0; + + controller.AcceptNewAxis(playerPfx + "Fly / Look", unchecked((sbyte) input[i++])); + + var useArtifact = input[i++]; + controller.AcceptNewAxis(playerPfx + "Use Artifact", useArtifact & 0b00111111); + controller[playerPfx + "End Player"] = (useArtifact & 0b01000000) is not 0; + controller[playerPfx + "Jump"] = (useArtifact & 0b10000000) is not 0; + } + do + { + if (syncSettings.Player1Present) ParsePlayer("P1 "); + if (syncSettings.Player2Present) ParsePlayer("P2 "); + if (syncSettings.Player3Present) ParsePlayer("P3 "); + if (syncSettings.Player4Present) ParsePlayer("P4 "); + Result.Movie.AppendFrame(controller); + if (i == input.Length) throw new Exception("Reached end of input movie stream without finalization byte"); + } + while (input[i] is not 0x80); + } + } +} diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs index 797795f505..9e164cafa5 100644 --- a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs +++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using BizHawk.Common.CollectionExtensions; using BizHawk.Emulation.Common; @@ -7,32 +8,10 @@ namespace BizHawk.Client.Common { internal class BkmControllerAdapter : IController { - public BkmControllerAdapter(ControllerDefinition definition, string systemId) + public BkmControllerAdapter(string systemId) { - // We do need to map the definition name to the legacy - // controller names that were used back in the bkm days - var name = systemId switch - { - "Lynx" => "Lynx Controller", - "SNES" => "SNES Controller", - "C64" => "Commodore 64 Controller", - "GBA" => "GBA Controller", - "A78" => "Atari 7800 ProLine Joystick Controller", - "DGB" => "Dual Gameboy Controller", - "WSWAN" => "WonderSwan Controller", - "N64" => "Nintendo 64 Controller", - "SAT" => "Saturn Controller", - "GEN" => "GPGX Genesis Controller", - "NES" => "NES Controller", - "GB" => "Gameboy Controller", - "A26" => "Atari 2600 Basic Controller", - "TI83" => "TI83 Controller", - "Coleco" => "ColecoVision Basic Controller", - "SMS Controller" => "SMS", - _ => "Null Controller", - }; - Definition = new(copyFrom: definition, withName: name); - Definition.BuildMnemonicsCache(systemId); //TODO these aren't the same... + Definition = BkmMnemonicConstants.ControllerDefinitions[systemId]; + Definition.BuildMnemonicsCache(systemId); } public ControllerDefinition Definition { get; set; } @@ -43,167 +22,126 @@ namespace BizHawk.Client.Common public int AxisValue(string name) => _myAxisControls.GetValueOrDefault(name); - public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotSupportedException(); - public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi + public void SetHapticChannelStrength(string name, int strength) => throw new NotSupportedException(); + + private void SetFromMnemonic(ReadOnlySpan mnemonic) + { + if (mnemonic.IsEmpty) return; + var iterator = 0; + + foreach ((string buttonName, AxisSpec? axisSpec) in Definition.ControlsOrdered.Skip(1).SelectMany(static x => x)) + { + while (mnemonic[iterator] == '|') iterator++; + + if (axisSpec.HasValue) + { + var separatorIndex = iterator + mnemonic[iterator..].IndexOfAny(',', '|'); +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER + var val = int.Parse(mnemonic[iterator..separatorIndex]); +#else + var axisValueString = mnemonic[iterator..separatorIndex].ToString(); + var val = int.Parse(axisValueString); +#endif + _myAxisControls[buttonName] = val; + + iterator = separatorIndex + 1; + } + else + { + _myBoolButtons[buttonName] = mnemonic[iterator] != '.'; + iterator++; + } + } + } /// /// latches all buttons from the supplied mnemonic string /// public void SetControllersAsMnemonic(string mnemonic) { - switch (ControlType) + // _myBoolButtons.Clear(); + + switch (Definition.Name) { - case "Null Controller": - return; - case "Lynx Controller": - SetLynxControllersAsMnemonic(mnemonic); - return; - case "SNES Controller": - SetSNESControllersAsMnemonic(mnemonic); - return; - case "Commodore 64 Controller": - SetC64ControllersAsMnemonic(mnemonic); - return; + case "Gameboy Controller": + Force("Power", mnemonic[1] == 'P'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; case "GBA Controller": - SetGBAControllersAsMnemonic(mnemonic); - return; - case "Atari 7800 ProLine Joystick Controller": - SetAtari7800AsMnemonic(mnemonic); - return; - case "Dual Gameboy Controller": - SetDualGameBoyControllerAsMnemonic(mnemonic); - return; - case "WonderSwan Controller": - SetWonderSwanControllerAsMnemonic(mnemonic); - return; - case "Nintendo 64 Controller": - SetN64ControllersAsMnemonic(mnemonic); - return; - case "Saturn Controller": - SetSaturnControllersAsMnemonic(mnemonic); - return; + Force("Power", mnemonic[1] == 'P'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; case "GPGX Genesis Controller": - { - if (IsGenesis6Button()) - { - SetGenesis6ControllersAsMnemonic(mnemonic); - } - else - { - SetGenesis3ControllersAsMnemonic(mnemonic); - } - - return; - } - } - - var c = new MnemonicChecker(mnemonic); - - _myBoolButtons.Clear(); - - int start = 3; - if (ControlType == "NES Controller") - { - if (mnemonic.Length < 2) - { - return; - } - - switch (mnemonic[1]) - { - case 'P': - Force("Power", true); - break; - case 'E': - Force("FDS Eject", true); - break; - case '0': - Force("FDS Insert 0", true); - break; - case '1': - Force("FDS Insert 1", true); - break; - case '2': - Force("FDS Insert 2", true); - break; - case '3': - Force("FDS Insert 3", true); - break; - case 'c': - Force("VS Coin 1", true); - break; - case 'C': - Force("VS Coin 2", true); - break; - default: - { - if (mnemonic[1] != '.') - { - Force("Reset", true); - } - - break; - } - } - } - - if (ControlType == "Gameboy Controller") - { - if (mnemonic.Length < 2) - { - return; - } - - Force("Power", mnemonic[1] != '.'); - } - - if (ControlType == "SMS Controller" || ControlType == "TI83 Controller" || ControlType == "ColecoVision Basic Controller") - { - start = 1; - } - - if (ControlType == "Atari 2600 Basic Controller") - { - if (mnemonic.Length < 2) - { - return; - } - - Force("Reset", mnemonic[1] != '.' && mnemonic[1] != '0'); - Force("Select", mnemonic[2] != '.' && mnemonic[2] != '0'); - start = 4; - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - int ctr = start; - if (mnemonic.Length < srcIndex + ctr + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - string prefix = ""; - if (ControlType != "Gameboy Controller" && ControlType != "TI83 Controller") - { - prefix = $"P{player} "; - } - - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force(prefix + button, c[srcIndex + ctr++]); - } - } - - if (ControlType == "SMS Controller") - { - int srcIndex = BkmMnemonicConstants.Players[ControlType] * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - int ctr = start; - foreach (var command in BkmMnemonicConstants.Commands[ControlType].Keys) - { - Force(command, c[srcIndex + ctr++]); - } + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[1] == 'r'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "NES Controller": + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[1] == 'r'); + Force("FDS Eject", mnemonic[1] == 'E'); + Force("FDS Insert 0", mnemonic[1] == '0'); + Force("FDS Insert 1", mnemonic[1] == '1'); + Force("FDS Insert 2", mnemonic[1] == '2'); + Force("FDS Insert 3", mnemonic[1] == '3'); + Force("VS Coin 1", mnemonic[1] == 'c'); + Force("VS Coin 2", mnemonic[1] == 'C'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "SNES Controller": + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[1] == 'r'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "PC Engine Controller": + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "SMS Controller": + SetFromMnemonic(mnemonic.AsSpan(1)); + Force("Pause", mnemonic[^3] == 'p'); + Force("Reset", mnemonic[^2] == 'r'); + break; + case "TI83 Controller": + SetFromMnemonic(mnemonic.AsSpan(1)); + break; + case "Atari 2600 Basic Controller": + Force("Reset", mnemonic[1] == 'r'); + Force("Select", mnemonic[2] == 's'); + SetFromMnemonic(mnemonic.AsSpan(4)); + break; + case "Atari 7800 ProLine Joystick Controller": + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[2] == 'r'); + Force("Select", mnemonic[3] == 's'); + Force("Pause", mnemonic[4] == 'p'); + SetFromMnemonic(mnemonic.AsSpan(6)); + break; + case "Commodore 64 Controller": + SetFromMnemonic(mnemonic.AsSpan(1)); + break; + case "ColecoVision Basic Controller": + SetFromMnemonic(mnemonic.AsSpan(1)); + break; + case "Nintento 64 Controller": + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[1] == 'r'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "Saturn Controller": + Force("Power", mnemonic[1] == 'P'); + Force("Reset", mnemonic[1] == 'r'); + SetFromMnemonic(mnemonic.AsSpan(3)); + break; + case "Dual Gameboy Controller": + SetFromMnemonic(mnemonic.AsSpan()); + break; + case "WonderSwan Controller": + SetFromMnemonic(mnemonic.AsSpan(1)); + Force("Power", mnemonic[^3] == 'P'); + Force("Rotate", mnemonic[^2] == 'R'); + break; } } @@ -211,381 +149,9 @@ namespace BizHawk.Client.Common private readonly Dictionary _myBoolButtons = new(); - private bool IsGenesis6Button() => Definition.BoolButtons.Contains("P1 X"); - private void Force(string button, bool state) { _myBoolButtons[button] = state; } - - private void Force(string name, int state) - { - _myAxisControls[name] = state; - } - - private string ControlType => Definition.Name; - - private void SetGBAControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - - int start = 3; - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force(button, c[start++]); - } - } - - private void SetGenesis6ControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - else if (mnemonic[1] != '.' && mnemonic[1] != '0') - { - Force("Reset", true); - } - - if (mnemonic.Length < 9) - { - return; - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 3; - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - } - - private void SetGenesis3ControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - else if (mnemonic[1] != '.' && mnemonic[1] != '0') - { - Force("Reset", true); - } - - if (mnemonic.Length < 9) - { - return; - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons["GPGX 3-Button Controller"].Count + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons["GPGX 3-Button Controller"].Count - 1) - { - return; - } - - int start = 3; - foreach (string button in BkmMnemonicConstants.Buttons["GPGX 3-Button Controller"].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - } - - private void SetSNESControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - else if (mnemonic[1] != '.' && mnemonic[1] != '0') - { - Force("Reset", true); - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 3; - foreach (var button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - } - - private void SetLynxControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 3; - foreach (var button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force(button, c[srcIndex + start++]); - } - } - } - - private void SetN64ControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - else if (mnemonic[1] != '.' && mnemonic[1] != '0') - { - Force("Reset", true); - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + (BkmMnemonicConstants.Analogs[ControlType].Count * 4) + 1 + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 3; - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - - foreach (string name in BkmMnemonicConstants.Analogs[ControlType].Keys) - { - Force($"P{player} {name}", int.Parse(mnemonic.Substring(srcIndex + start, 4))); - start += 5; - } - } - } - - private void SetSaturnControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 2) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - else if (mnemonic[1] != '.' && mnemonic[1] != '0') - { - Force("Reset", true); - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - - if (mnemonic.Length < srcIndex + 3 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 3; - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - } - - private void SetAtari7800AsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - if (mnemonic.Length < 5) - { - return; - } - - if (mnemonic[1] == 'P') - { - Force("Power", true); - } - - if (mnemonic[2] == 'r') - { - Force("Reset", true); - } - - if (mnemonic[3] == 's') - { - Force("Select", true); - } - - if (mnemonic[4] == 'p') - { - Force("Pause", true); - } - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - int start = 6; - if (mnemonic.Length < srcIndex + start + BkmMnemonicConstants.Buttons[ControlType].Count) - { - return; - } - - foreach (string button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - } - - private void SetDualGameBoyControllerAsMnemonic(string mnemonic) - { - var checker = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - for (int i = 0; i < BkmMnemonicConstants.DgbMnemonic.Length; i++) - { - var t = BkmMnemonicConstants.DgbMnemonic[i]; - if (t.Item1 != null) - { - Force(t.Item1, checker[i]); - } - } - } - - private void SetWonderSwanControllerAsMnemonic(string mnemonic) - { - var checker = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - for (int i = 0; i < BkmMnemonicConstants.WsMnemonic.Length; i++) - { - var t = BkmMnemonicConstants.WsMnemonic[i]; - if (t.Item1 != null) - { - Force(t.Item1, checker[i]); - } - } - } - - private void SetC64ControllersAsMnemonic(string mnemonic) - { - var c = new MnemonicChecker(mnemonic); - _myBoolButtons.Clear(); - - for (int player = 1; player <= BkmMnemonicConstants.Players[ControlType]; player++) - { - int srcIndex = (player - 1) * (BkmMnemonicConstants.Buttons[ControlType].Count + 1); - - if (mnemonic.Length < srcIndex + 1 + BkmMnemonicConstants.Buttons[ControlType].Count - 1) - { - return; - } - - int start = 1; - foreach (var button in BkmMnemonicConstants.Buttons[ControlType].Keys) - { - Force($"P{player} {button}", c[srcIndex + start++]); - } - } - - int startKey = 13; - foreach (string button in BkmMnemonicConstants.Buttons["Commodore 64 Keyboard"].Keys) - { - Force(button, c[startKey++]); - } - } - - private sealed class MnemonicChecker - { - private readonly string _mnemonic; - - public MnemonicChecker(string mnemonic) - { - _mnemonic = mnemonic; - } - - public bool this[int c] => !string.IsNullOrEmpty(_mnemonic) && _mnemonic[c] != '.'; - } } } diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerDefinition.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerDefinition.cs new file mode 100644 index 0000000000..97e181015b --- /dev/null +++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerDefinition.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + internal class BkmControllerDefinition(string name) : ControllerDefinition(name) + { + // same as ControllerDefinition.GenOrderedControls, just with Axes after BoolButtons + protected override IReadOnlyList> GenOrderedControls() + { + var ret = new List<(string, AxisSpec?)>[PlayerCount + 1]; + for (var i = 0; i < ret.Length; i++) ret[i] = new(); + foreach (var btn in BoolButtons) ret[PlayerNumber(btn)].Add((btn, null)); + foreach ((string buttonName, var axisSpec) in Axes) ret[PlayerNumber(buttonName)].Add((buttonName, axisSpec)); + return ret; + } + } +} diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmMnemonicConstants.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmMnemonicConstants.cs index 4541d4c085..ca90c3b547 100644 --- a/src/BizHawk.Client.Common/movie/import/bkm/BkmMnemonicConstants.cs +++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmMnemonicConstants.cs @@ -1,203 +1,156 @@ using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { internal static class BkmMnemonicConstants { - public static readonly IReadOnlyDictionary> Buttons = new Dictionary> + public static readonly IReadOnlyDictionary ControllerDefinitions = new Dictionary { - ["Gameboy Controller"] = new Dictionary + [VSystemID.Raw.GB] = new BkmControllerDefinition("Gameboy Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Start"] = "S", ["B"] = "B", ["A"] = "A" - }, - ["Lynx Controller"] = new Dictionary + BoolButtons = new[] { "Up", "Down", "Left", "Right", "Select", "Start", "B", "A" }.Select(b => $"P1 {b}") + .Append("Power") + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.GBA] = new BkmControllerDefinition("GBA Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Start"] = "S", ["B"] = "B", ["A"] = "A" - }, - ["GBA Controller"] = new Dictionary + BoolButtons = new[] { "Up", "Down", "Left", "Right", "Select", "Start", "B", "A", "L", "R" }.Select(b => $"P1 {b}") + .Append("Power") + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.GEN] = new BkmControllerDefinition("GPGX Genesis Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Start"] = "S", ["B"] = "B", ["A"] = "A", ["L"] = "L", ["R"] = "R" - }, - ["Genesis 3-Button Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "A", "B", "C", "Start", "X", "Y", "Z", "Mode" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.NES] = new BkmControllerDefinition("NES Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Start"] = "S", ["A"] = "A", ["B"] = "B", ["C"] = "C" - }, - ["GPGX Genesis Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 4) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Select", "Start", "B", "A" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power", "FDS Eject", "FDS Insert 0", "FDS Insert 1", "FDS Insert 2", "FDS Insert 3", "VS Coin 1", "VS Coin 2" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.SNES] = new BkmControllerDefinition("SNES Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["A"] = "A", ["B"] = "B", ["C"] = "C", ["Start"] = "S", ["X"] = "X", ["Y"] = "Y", ["Z"] = "Z", ["Mode"] = "M" - }, - ["GPGX 3-Button Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 4) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Select", "Start", "B", "A", "X", "Y", "L", "R" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.PCE] = new BkmControllerDefinition("PC Engine Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["A"] = "A", ["B"] = "B", ["C"] = "C", ["Start"] = "S" - }, - ["NES Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 5) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Select", "Run", "B2", "B1" } + .Select(b => $"P{i} {b}")) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.SMS] = new BkmControllerDefinition("SMS Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Start"] = "S", ["B"] = "B", ["A"] = "A" - }, - ["SNES Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "B1", "B2" } + .Select(b => $"P{i} {b}")) + .Concat([ "Pause", "Reset" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.TI83] = new BkmControllerDefinition("TI83 Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Start"] = "S", ["B"] = "B", ["A"] = "A", ["X"] = "X", ["Y"] = "Y", ["L"] = "L", ["R"] = "R" - }, - ["PC Engine Controller"] = new Dictionary + BoolButtons = new[] { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "DOT", "ON", "ENTER", + "UP", "DOWN", "LEFT", "RIGHT", "PLUS", "MINUS", "MULTIPLY", "DIVIDE", + "CLEAR", "EXP", "DASH", "PARAOPEN", "PARACLOSE", "TAN", "VARS", "COS", + "PRGM", "STAT", "MATRIX", "X", "STO", "LN", "LOG", "SQUARED", "NEG1", + "MATH", "ALPHA", "GRAPH", "TRACE", "ZOOM", "WINDOW", "Y", "2ND", "MODE" + }.Select(b => $"P1 {b}") + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.A26] = new BkmControllerDefinition("Atari 2600 Basic Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Select"] = "s", ["Run"] = "r", ["B2"] = "2", ["B1"] = "1" - }, - ["SMS Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Button" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Select" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.A78] = new BkmControllerDefinition("Atari 7800 ProLine Joystick Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["B1"] = "1", ["B2"] = "2" - }, - ["TI83 Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Trigger", "Trigger 2" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power", "Select", "Pause" ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.C64] = new BkmControllerDefinition("Commodore 64 Controller") { - ["0"] = "0", ["1"] = "1", ["2"] = "2", ["3"] = "3", ["4"] = "4", ["5"] = "5", ["6"] = "6", ["7"] = "7", ["8"] = "8", ["9"] = "9", - ["DOT"] = "`", ["ON"] = "O", ["ENTER"] = "=", ["UP"] = "U", ["DOWN"] = "D", ["LEFT"] = "L", ["RIGHT"] = "R", ["PLUS"] = "+", ["MINUS"] = "_", ["MULTIPLY"] = "*", - ["DIVIDE"] = "/", ["CLEAR"] = "c", ["EXP"] = "^", ["DASH"] = "-", ["PARAOPEN"] = "(", ["PARACLOSE"] = ")", ["TAN"] = "T", ["VARS"] = "V", ["COS"] = "C", ["PRGM"] = "P", - ["STAT"] = "s", ["MATRIX"] = "m", ["X"] = "X", ["STO"] = ">", ["LN"] = "n", ["LOG"] = "L", ["SQUARED"] = "2", ["NEG1"] = "1", ["MATH"] = "H", ["ALPHA"] = "A", - ["GRAPH"] = "G", ["TRACE"] = "t", ["ZOOM"] = "Z", ["WINDOW"] = "W", ["Y"] = "Y", ["2ND"] = "&", ["MODE"] = "O" - }, - ["Atari 2600 Basic Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Button" } + .Select(b => $"P{i} {b}")) + .Concat([ "Key F1", "Key F3", "Key F5", "Key F7", "Key Left Arrow", "Key 1", + "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Plus", + "Key Minus", "Key Pound", "Key Clear/Home", "Key Insert/Delete", "Key Control", "Key Q", "Key W", "Key E", + "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P", "Key At", "Key Asterisk", "Key Up Arrow", + "Key Restore", "Key Run/Stop", "Key Lck", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", + "Key K", "Key L", "Key Colon", "Key Semicolon", "Key Equal", "Key Return", "Key Commodore", "Key Left Shift", + "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Comma", "Key Period", + "Key Slash", "Key Right Shift", "Key Cursor Up/Down", "Key Cursor Left/Right", "Key Space" + ]) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.Coleco] = new BkmControllerDefinition("ColecoVision Basic Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Button"] = "B" - }, - ["Atari 7800 ProLine Joystick Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "L", "R", + "Key1", "Key2", "Key3", "Key4", "Key5", "Key6", + "Key7", "Key8", "Key9", "Star", "Key0", "Pound" + }.Select(b => $"P{i} {b}")) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.N64] = new BkmControllerDefinition("Nintento 64 Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Trigger"] = "1", ["Trigger 2"] = "2" - }, - ["Commodore 64 Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 4) + .SelectMany(i => new[] { + "DPad U", "DPad D", "DPad L", "DPad R", + "B", "A", "Z", "Start", "L", "R", + "C Up", "C Down", "C Left", "C Right" + }.Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power" ]) + .ToArray() + }.AddXYPair("P1 {0} Axis", AxisPairOrientation.RightAndUp, (-128).RangeTo(127), 0) + .AddXYPair("P2 {0} Axis", AxisPairOrientation.RightAndUp, (-128).RangeTo(127), 0) + .AddXYPair("P3 {0} Axis", AxisPairOrientation.RightAndUp, (-128).RangeTo(127), 0) + .AddXYPair("P4 {0} Axis", AxisPairOrientation.RightAndUp, (-128).RangeTo(127), 0) + .MakeImmutable(), + [VSystemID.Raw.SAT] = new BkmControllerDefinition("Saturn Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Button"] = "B" - }, - ["Commodore 64 Keyboard"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Up", "Down", "Left", "Right", "Start", "X", "Y", "Z", "A", "B", "C", "L", "R" } + .Select(b => $"P{i} {b}")) + .Concat([ "Reset", "Power" ]) + .ToArray() + }.MakeImmutable(), + ["DGB"] = new BkmControllerDefinition("Dual Gameboy Controller") { - ["Key F1"] = "1", ["Key F3"] = "3", ["Key F5"] = "5", ["Key F7"] = "7", ["Key Left Arrow"] = "l", ["Key 1"] = "1", - ["Key 2"] = "2", ["Key 3"] = "3", ["Key 4"] = "4", ["Key 5"] = "5", ["Key 6"] = "6", ["Key 7"] = "7", ["Key 8"] = "8", ["Key 9"] = "9", ["Key 0"] = "0", ["Key Plus"] = "+", - ["Key Minus"] = "-", ["Key Pound"] = "l", ["Key Clear/Home"] = "c", ["Key Insert/Delete"] = "i", ["Key Control"] = "c", ["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 At"] = "@", ["Key Asterisk"] = "*", ["Key Up Arrow"] = "u", - ["Key Restore"] = "r", ["Key Run/Stop"] = "s", ["Key Lck"] = "k", ["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 Colon"] = ":", ["Key Semicolon"] = ";", ["Key Equal"] = "=", ["Key Return"] = "e", ["Key Commodore"] = "o", ["Key Left Shift"] = "s", - ["Key Z"] = "Z", ["Key X"] = "X", ["Key C"] = "C", ["Key V"] = "V", ["Key B"] = "B", ["Key N"] = "N", ["Key M"] = "M", ["Key Comma"] = ",", - ["Key Period"] = ">", ["Key Slash"] = "/", ["Key Right Shift"] = "s", ["Key Cursor Up/Down"] = "u", ["Key Cursor Left/Right"] = "l", ["Key Space"] = "_" - }, - ["ColecoVision Basic Controller"] = new Dictionary + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "Power", "Up", "Down", "Left", "Right", "Select", "Start", "B", "A" } + .Select(b => $"P{i} {b}")) + .ToArray() + }.MakeImmutable(), + [VSystemID.Raw.WSWAN] = new BkmControllerDefinition("WonderSwan Controller") { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["L"] = "l", ["R"] = "r", - ["Key1"] = "1", ["Key2"] = "2", ["Key3"] = "3", ["Key4"] = "4", ["Key5"] = "5", ["Key6"] = "6", - ["Key7"] = "7", ["Key8"] = "8", ["Key9"] = "9", ["Star"] = "*", ["Key0"] = "0", ["Pound"] = "#" - }, - ["Nintendo 64 Controller"] = new Dictionary - { - ["DPad U"] = "U", ["DPad D"] = "D", ["DPad L"] = "L", ["DPad R"] = "R", - ["B"] = "B", ["A"] = "A", ["Z"] = "Z", ["Start"] = "S", ["L"] = "L", ["R"] = "R", - ["C Up"] = "u", ["C Down"] = "d", ["C Left"] = "l", ["C Right"] = "r" - }, - ["Saturn Controller"] = new Dictionary - { - ["Up"] = "U", ["Down"] = "D", ["Left"] = "L", ["Right"] = "R", ["Start"] = "S", ["X"] = "X", ["Y"] = "Y", ["Z"] = "Z", ["A"] = "A", ["B"] = "B", ["C"] = "C", ["L"] = "l", ["R"] = "r" - } - }; - - public static readonly IReadOnlyDictionary> Analogs = new Dictionary> - { - ["Nintendo 64 Controller"] = new Dictionary { ["X Axis"] = "X" , ["Y Axis"] = "Y" } - }; - - public static readonly IReadOnlyDictionary> Commands = new Dictionary> - { - ["Atari 2600 Basic Controller"] = new Dictionary { ["Reset"] = "r", ["Select"] = "s" }, - ["Atari 7800 ProLine Joystick Controller"] = new Dictionary { ["Reset"] = "r", ["Select"] = "s" }, - ["Gameboy Controller"] = new Dictionary { ["Power"] = "P" }, - ["GBA Controller"] = new Dictionary { ["Power"] = "P" }, - ["Genesis 3-Button Controller"] = new Dictionary { ["Reset"] = "r" }, - ["GPGX Genesis Controller"] = new Dictionary { ["Power"] = "P", ["Reset"] = "r" }, - ["NES Controller"] = new Dictionary { ["Reset"] = "r", ["Power"] = "P", ["FDS Eject"] = "E", ["FDS Insert 0"] = "0", ["FDS Insert 1"] = "1", ["VS Coin 1"] = "c", ["VS Coin 2"] = "C" }, - ["SNES Controller"] = new Dictionary { ["Power"] = "P", ["Reset"] = "r" }, - ["PC Engine Controller"] = new Dictionary(), - ["SMS Controller"] = new Dictionary { ["Pause"] = "p", ["Reset"] = "r" }, - ["TI83 Controller"] = new Dictionary(), - ["Nintendo 64 Controller"] = new Dictionary { ["Power"] = "P", ["Reset"] = "r" }, - ["Saturn Controller"] = new Dictionary { ["Power"] = "P", ["Reset"] = "r" }, - ["GPGX 3-Button Controller"] = new Dictionary { ["Power"] = "P", ["Reset"] = "r" } - }; - - public static readonly IReadOnlyDictionary Players = new Dictionary - { - ["Gameboy Controller"] = 1, - ["GBA Controller"] = 1, - ["Genesis 3-Button Controller"] = 2, - ["GPGX Genesis Controller"] = 2, - ["NES Controller"] = 4, - ["SNES Controller"] = 4, - ["PC Engine Controller"] = 5, - ["SMS Controller"] = 2, - ["TI83 Controller"] = 1, - ["Atari 2600 Basic Controller"] = 2, - ["Atari 7800 ProLine Joystick Controller"] = 2, - ["ColecoVision Basic Controller"] = 2, - ["Commodore 64 Controller"] = 2, - ["Nintendo 64 Controller"] = 4, - ["Saturn Controller"] = 2, - ["GPGX 3-Button Controller"] = 2, - ["Lynx Controller"] = 1 - }; - - // just experimenting with different possibly more painful ways to handle mnemonics - // |P|UDLRsSBA| - public static readonly (string, char)[] DgbMnemonic = - { - (null, '|'), - ("P1 Power", 'P'), - (null, '|'), - ("P1 Up", 'U'), - ("P1 Down", 'D'), - ("P1 Left", 'L'), - ("P1 Right", 'R'), - ("P1 Select", 's'), - ("P1 Start", 'S'), - ("P1 B", 'B'), - ("P1 A", 'A'), - (null, '|'), - ("P2 Power", 'P'), - (null, '|'), - ("P2 Up", 'U'), - ("P2 Down", 'D'), - ("P2 Left", 'L'), - ("P2 Right", 'R'), - ("P2 Select", 's'), - ("P2 Start", 'S'), - ("P2 B", 'B'), - ("P2 A", 'A'), - (null, '|') - }; - - public static readonly (string, char)[] WsMnemonic = - { - (null, '|'), - ("P1 X1", '1'), - ("P1 X3", '3'), - ("P1 X4", '4'), - ("P1 X2", '2'), - ("P1 Y1", '1'), - ("P1 Y3", '3'), - ("P1 Y4", '4'), - ("P1 Y2", '2'), - ("P1 Start", 'S'), - ("P1 B", 'B'), - ("P1 A", 'A'), - (null, '|'), - ("P2 X1", '1'), - ("P2 X3", '3'), - ("P2 X4", '4'), - ("P2 X2", '2'), - ("P2 Y1", '1'), - ("P2 Y3", '3'), - ("P2 Y4", '4'), - ("P2 Y2", '2'), - ("P2 Start", 'S'), - ("P2 B", 'B'), - ("P2 A", 'A'), - (null, '|'), - ("Power", 'P'), - ("Rotate", 'R'), - (null, '|') + BoolButtons = Enumerable.Range(1, 2) + .SelectMany(i => new[] { "X1", "X3", "X4", "X2", "Y1", "Y3", "Y4", "Y2", "Start", "B", "A" } + .Select(b => $"P{i} {b}")) + .Concat([ "Power", "Rotate" ]) + .ToArray() + }.MakeImmutable() }; } } diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmMovie.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmMovie.cs index 569282b13e..b7b5952283 100644 --- a/src/BizHawk.Client.Common/movie/import/bkm/BkmMovie.cs +++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmMovie.cs @@ -2,25 +2,26 @@ using System.Collections.Generic; using System.IO; using BizHawk.Common.StringExtensions; -using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { internal class BkmMovie { + private BkmControllerAdapter _adapter; + private readonly List _log = new List(); public BkmHeader Header { get; } = new BkmHeader(); public string Filename { get; set; } = ""; public bool Loaded { get; private set; } public int InputLogLength => Loaded ? _log.Count : 0; - public BkmControllerAdapter GetInputState(int frame, ControllerDefinition definition, string sytemId) + public BkmControllerAdapter GetInputState(int frame, string sytemId) { if (frame < InputLogLength && frame >= 0) { - var adapter = new BkmControllerAdapter(definition, sytemId); - adapter.SetControllersAsMnemonic(_log[frame]); - return adapter; + _adapter ??= new BkmControllerAdapter(sytemId); + _adapter.SetControllersAsMnemonic(_log[frame]); + return _adapter; } return null; @@ -30,6 +31,8 @@ namespace BizHawk.Client.Common public IList Comments => Header.Comments; + public string GenerateLogKey => Bk2LogEntryGenerator.GenerateLogKey(_adapter.Definition); + public string SyncSettingsJson { get => Header[HeaderKeys.SyncSettings]; diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs index 57aaa3ebc2..88f268b2d2 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs @@ -54,7 +54,7 @@ namespace BizHawk.Client.Common IMovieController GenerateMovieController(ControllerDefinition definition = null, string logKey = null); void HandleFrameBefore(); - void HandleFrameAfter(); + void HandleFrameAfter(bool ignoreMovieEndAction); void HandleSaveState(TextWriter writer); bool CheckSavestateTimeline(TextReader reader); diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasLagLog.cs b/src/BizHawk.Client.Common/movie/tasproj/TasLagLog.cs index ef88015c5f..e54713c7c1 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasLagLog.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasLagLog.cs @@ -1,5 +1,8 @@ using System.Collections.Generic; using System.IO; +#if NET8_0_OR_GREATER +using System.Linq; +#endif using BizHawk.Common.CollectionExtensions; diff --git a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs index e936c67343..945f6b5039 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/ZwinderStateManagerSettings.cs @@ -44,7 +44,7 @@ namespace BizHawk.Client.Common public int CurrentBufferSize { get; set; } = 256; [DisplayName("Current - Target Frame Length")] - [Description("Desired frame length (number of emulated frames you can go back before running out of buffer)\n\nThe Current buffer is the primary buffer used near the last edited frame. This should be the largest buffer to ensure minimal gaps during editing.")] + [Description("Desired minimum rewind range (number of emulated frames you can go back before running out of buffer)\n\nThe Current buffer is the primary buffer used near the last edited frame. This should be the largest buffer to ensure minimal gaps during editing.")] [Range(1, int.MaxValue)] [TypeConverter(typeof(ConstrainedIntConverter))] public int CurrentTargetFrameLength { get; set; } = 500; @@ -67,7 +67,7 @@ namespace BizHawk.Client.Common public int RecentBufferSize { get; set; } = 128; [DisplayName("Recent - Target Frame Length")] - [Description("Desired frame length (number of emulated frames you can go back before running out of buffer).\n\nThe Recent buffer is where the current frames decay as the buffer fills up. The goal of this buffer is to maximize the amount of movie that can be fairly quickly navigated to. Therefore, a high target frame length is ideal here.")] + [Description("Desired minimum rewind range (number of emulated frames you can go back before running out of buffer).\n\nThe Recent buffer is where the current frames decay as the buffer fills up. The goal of this buffer is to maximize the amount of movie that can be fairly quickly navigated to. Therefore, a high target frame length is ideal here.")] [Range(1, int.MaxValue)] [TypeConverter(typeof(ConstrainedIntConverter))] public int RecentTargetFrameLength { get; set; } = 2000; @@ -90,7 +90,7 @@ namespace BizHawk.Client.Common public int GapsBufferSize { get; set; } = 64; [DisplayName("Gaps - Target Frame Length")] - [Description("Desired frame length (number of emulated frames you can go back before running out of buffer)\n\nThe Gap buffer is used for temporary storage when replaying older segment of the run without editing. It is used to 're-greenzone' large gaps while navigating around in an older area of the movie. This buffer can be small, and a similar size to target frame length ratio as current is ideal.")] + [Description("Desired minimum rewind range (number of emulated frames you can go back before running out of buffer)\n\nThe Gap buffer is used for temporary storage when replaying older segment of the run without editing. It is used to 're-greenzone' large gaps while navigating around in an older area of the movie. This buffer can be small, and a similar size to target frame length ratio as current is ideal.")] [Range(1, int.MaxValue)] [TypeConverter(typeof(ConstrainedIntConverter))] public int GapsTargetFrameLength { get; set; } = 125; diff --git a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs index a64141f567..4365dfa2a5 100644 --- a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs +++ b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs @@ -145,7 +145,7 @@ namespace BizHawk.Client.Common var sizeRatio = Size / (float)_states[HeadStateIndex].Size; var frameRatio = _targetFrameLength / sizeRatio; - var idealInterval = (int)Math.Round(frameRatio); + var idealInterval = (int)Math.Ceiling(frameRatio); return Math.Max(idealInterval, 1); } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index d6f37fcfee..ae8678a4bc 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -267,6 +267,7 @@ namespace BizHawk.Client.Common.RamSearchEngine public void AddRange(IEnumerable addresses, bool append) { + using var @lock = Domain.EnterExit(); var list = _settings.Size switch { WatchSize.Byte => addresses.ToBytes(_settings), @@ -278,6 +279,54 @@ namespace BizHawk.Client.Common.RamSearchEngine _watchList = (append ? _watchList.Concat(list) : list).ToArray(); } + public void ConvertTo(WatchSize size) + { + using var @lock = Domain.EnterExit(); + var maxAddress = Domain.Size - (int)size; + var addresses = AllAddresses().Where(address => address <= maxAddress); + _watchList = size switch + { + WatchSize.Byte => addresses.ToBytes(_settings).ToArray(), + WatchSize.Word when _settings.CheckMisAligned => addresses.ToWords(_settings).ToArray(), + WatchSize.Word => addresses.Where(static address => address % 2 == 0).ToWords(_settings).ToArray(), + WatchSize.DWord when _settings.CheckMisAligned => addresses.ToDWords(_settings).ToArray(), + WatchSize.DWord => addresses.Where(static address => address % 4 == 0).ToDWords(_settings).ToArray(), + _ => _watchList + }; + + _settings.Size = size; + } + + private IEnumerable AllAddresses() + { + foreach (var watch in _watchList) + { + if (_settings.CheckMisAligned) + { + yield return watch.Address; + } + else + { + switch (_settings.Size) + { + case WatchSize.Word: + yield return watch.Address; + yield return watch.Address + 1; + break; + case WatchSize.DWord: + yield return watch.Address; + yield return watch.Address + 1; + yield return watch.Address + 2; + yield return watch.Address + 3; + break; + default: + yield return watch.Address; + break; + } + } + } + } + public void Sort(string column, bool reverse) { switch (column) diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/Cell.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/Cell.cs index d401076bea..4ea717f8d0 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/Cell.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/Cell.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Collections.Generic; using System.Diagnostics; using BizHawk.Common; @@ -91,6 +92,11 @@ namespace BizHawk.Client.EmuHawk public sealed class CellList : SortedList { + public CellList() {} + + public CellList(IEnumerable collection) + : base(collection) {} + /// restore the distinctness invariant from ; though I don't think we actually rely on it anywhere --yoshi public override void Add(Cell item) { @@ -117,6 +123,9 @@ namespace BizHawk.Client.EmuHawk return i >= 0 && _list[i].RowIndex == rowIndex; } #endif + + public new CellList Slice(int start, int length) + => new(SliceImpl(start: start, length: length)); } public static class CellExtensions diff --git a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs index f7a044686a..dcc2607ca2 100644 --- a/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs +++ b/src/BizHawk.Client.EmuHawk/CustomControls/InputRoll/InputRoll.cs @@ -22,7 +22,7 @@ namespace BizHawk.Client.EmuHawk { private readonly IControlRenderer _renderer; - private readonly CellList _selectedItems = new(); + private CellList _selectedItems = new(); // scrollbar location(s) are calculated later (e.g. on resize) private readonly VScrollBar _vBar = new VScrollBar { Visible = false }; @@ -269,10 +269,10 @@ namespace BizHawk.Client.EmuHawk _rowCount = value; - //TODO replace this with a binary search + truncate if (_selectedItems.LastOrDefault()?.RowIndex >= _rowCount) { - _selectedItems.RemoveAll(i => i.RowIndex >= _rowCount); + var iLastToKeep = _selectedItems.LowerBoundBinarySearch(static c => c.RowIndex ?? -1, _rowCount); + _selectedItems = _selectedItems.Slice(start: 0, length: iLastToKeep + 1); } RecalculateScrollBars(); diff --git a/src/BizHawk.Client.EmuHawk/IControlMainform.cs b/src/BizHawk.Client.EmuHawk/IControlMainform.cs index 74309bd701..2cfa6ececa 100644 --- a/src/BizHawk.Client.EmuHawk/IControlMainform.cs +++ b/src/BizHawk.Client.EmuHawk/IControlMainform.cs @@ -61,5 +61,7 @@ bool WantsToControlRestartMovie { get; } bool RestartMovie(); + + bool WantsToBypassMovieEndAction { get; } } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs index b1dc69b7e5..4f367783e9 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -183,13 +183,6 @@ namespace BizHawk.Client.EmuHawk MovieEndRecordMenuItem.Checked = Config.Movies.MovieEndAction == MovieEndAction.Record; MovieEndStopMenuItem.Checked = Config.Movies.MovieEndAction == MovieEndAction.Stop; MovieEndPauseMenuItem.Checked = Config.Movies.MovieEndAction == MovieEndAction.Pause; - - // Arguably an IControlMainForm property should be set here, but in reality only Tastudio is ever going to interfere with this logic - MovieEndFinishMenuItem.Enabled = - MovieEndRecordMenuItem.Enabled = - MovieEndStopMenuItem.Enabled = - MovieEndPauseMenuItem.Enabled = - !Tools.Has(); } private void AVSubMenu_DropDownOpened(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs b/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs index 9899c44a73..3fc685676e 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs @@ -21,6 +21,7 @@ using BizHawk.Emulation.Cores.Computers.DOS; using BizHawk.Emulation.Cores.Computers.AmstradCPC; using BizHawk.Emulation.Cores.Computers.AppleII; using BizHawk.Emulation.Cores.Computers.Commodore64; +using BizHawk.Emulation.Cores.Computers.Doom; using BizHawk.Emulation.Cores.Computers.MSX; using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; using BizHawk.Emulation.Cores.Computers.TIC80; @@ -1181,7 +1182,10 @@ namespace BizHawk.Client.EmuHawk // DOSBox items.Add(CreateCoreSubmenu(VSystemCategory.PCs, CoreNames.DOSBox, CreateGenericCoreConfigItem(CoreNames.DOSBox))); - // Emu83 + // DSDA-Doom + items.Add(CreateCoreSubmenu(VSystemCategory.Other, CoreNames.DSDA, CreateGenericCoreConfigItem(CoreNames.DSDA))); + + // Emu83 items.Add(CreateCoreSubmenu(VSystemCategory.Other, CoreNames.Emu83, CreateSettingsItem("Palette...", (_, _) => OpenTI83PaletteSettingsDialog(GetSettingsAdapterFor())))); // Encore @@ -1523,6 +1527,24 @@ namespace BizHawk.Client.EmuHawk DisplayDefaultCoreMenu(); break; } - } + } + + private void InitializeComponent() + { + this.SuspendLayout(); + // + // MainForm + // + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "MainForm"; + this.Load += new System.EventHandler(this.MainForm_Load); + this.ResumeLayout(false); + + } + + private void MainForm_Load(object sender, EventArgs e) + { + + } } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 8d33094981..c8ea9768bf 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -75,6 +75,15 @@ namespace BizHawk.Client.EmuHawk } } +#if BIZHAWKBUILD_SUPERHAWK + ToolStripMenuItemEx superHawkThrottleMenuItem = new() { Text = "SUPER·HAWK" }; + superHawkThrottleMenuItem.Click += (_, _) => Config.SuperHawkThrottle = !Config.SuperHawkThrottle; + SpeedSkipSubMenu.DropDownItems.Insert( + SpeedSkipSubMenu.DropDownItems.IndexOf(MinimizeSkippingMenuItem), + superHawkThrottleMenuItem); + ConfigSubMenu.DropDownOpened += (_, _) => superHawkThrottleMenuItem.Checked = Config.SuperHawkThrottle; +#endif + foreach (var (appliesTo, coreNames) in Config.CorePickerUIData) { var submenu = new ToolStripMenuItem { Text = string.Join(" | ", appliesTo) }; @@ -1109,6 +1118,7 @@ namespace BizHawk.Client.EmuHawk private IControlMainform ToolControllingStopMovie => Tools.FirstOrNull(tool => tool.WantsToControlStopMovie); private IControlMainform ToolControllingRestartMovie => Tools.FirstOrNull(tool => tool.WantsToControlRestartMovie); private IControlMainform ToolControllingReadOnly => Tools.FirstOrNull(tool => tool.WantsToControlReadOnly); + private IControlMainform ToolBypassingMovieEndAction => Tools.FirstOrNull(tool => tool.WantsToBypassMovieEndAction); private DisplayManager DisplayManager; @@ -2913,7 +2923,6 @@ namespace BizHawk.Client.EmuHawk private void StepRunLoop_Core(bool force = false) { var runFrame = false; - _runloopFrameAdvance = false; var currentTimestamp = Stopwatch.GetTimestamp(); double frameAdvanceTimestampDeltaMs = (double)(currentTimestamp - _frameAdvanceTimestamp) / Stopwatch.Frequency * 1000.0; @@ -2937,14 +2946,21 @@ namespace BizHawk.Client.EmuHawk else { PauseEmulator(); - oldFrameAdvanceCondition = false; } } - if (oldFrameAdvanceCondition || FrameInch) + bool frameAdvance = oldFrameAdvanceCondition || FrameInch; + if (!frameAdvance && _runloopFrameAdvance && _runloopFrameProgress) + { + // handle release of frame advance + _runloopFrameProgress = false; + PauseEmulator(); + } + + _runloopFrameAdvance = frameAdvance; + if (frameAdvance) { FrameInch = false; - _runloopFrameAdvance = true; // handle the initial trigger of a frame advance if (_frameAdvanceTimestamp == 0) @@ -2966,22 +2982,20 @@ namespace BizHawk.Client.EmuHawk } else { - // handle release of frame advance: do we need to deactivate FrameProgress? - if (_runloopFrameProgress) - { - _runloopFrameProgress = false; - PauseEmulator(); - } - _frameAdvanceTimestamp = 0; } +#if BIZHAWKBUILD_SUPERHAWK + if (!EmulatorPaused && (!Config.SuperHawkThrottle || InputManager.ClientControls.AnyInputHeld)) +#else if (!EmulatorPaused) +#endif { runFrame = true; } bool isRewinding = Rewind(ref runFrame, currentTimestamp, out var returnToRecording); + _runloopFrameProgress |= isRewinding; float atten = 0; @@ -3068,7 +3082,7 @@ namespace BizHawk.Client.EmuHawk bool render = !InvisibleEmulation && (!_throttle.skipNextFrame || _currAviWriter?.UsesVideo is true || atTurboSeekEnd); bool newFrame = Emulator.FrameAdvance(InputManager.ControllerOutput, render, renderSound); - MovieSession.HandleFrameAfter(); + MovieSession.HandleFrameAfter(ToolBypassingMovieEndAction is not null); if (returnToRecording) { diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs index f60fee9f7b..035bbf01fd 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs @@ -14,6 +14,8 @@ using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.DiscSystem; +using CE = BizHawk.Common.CollectionExtensions.CollectionExtensions; + #pragma warning disable BHI1007 // target-typed Exception TODO don't namespace BizHawk.Client.EmuHawk @@ -38,7 +40,7 @@ namespace BizHawk.Client.EmuHawk }; var buf2048 = new byte[2048]; - var buffer = new List(); + List> bitsToHash = new(); int FirstDataTrackLBA() { @@ -58,13 +60,13 @@ namespace BizHawk.Client.EmuHawk { var slba = FirstDataTrackLBA(); dsr.ReadLBA_2048(slba + 1, buf2048, 0); - buffer.AddRange(new ArraySegment(buf2048, 128 - 22, 22)); + bitsToHash.Add(new(buf2048, offset: 128 - 22, count: 22)); var bootSector = (buf2048[0] << 16) | (buf2048[1] << 8) | buf2048[2]; var numSectors = buf2048[3]; for (var i = 0; i < numSectors; i++) { dsr.ReadLBA_2048(slba + bootSector + i, buf2048, 0); - buffer.AddRange(buf2048); + bitsToHash.Add(new(buf2048)); } break; } @@ -72,13 +74,13 @@ namespace BizHawk.Client.EmuHawk { var slba = FirstDataTrackLBA(); dsr.ReadLBA_2048(slba + 1, buf2048, 0); - buffer.AddRange(new ArraySegment(buf2048, 0, 128)); + bitsToHash.Add(new(buf2048, offset: 0, count: 128)); var bootSector = (buf2048[35] << 24) | (buf2048[34] << 16) | (buf2048[33] << 8) | buf2048[32]; var numSectors = (buf2048[39] << 24) | (buf2048[38] << 16) | (buf2048[37] << 8) | buf2048[36]; for (var i = 0; i < numSectors; i++) { dsr.ReadLBA_2048(slba + bootSector + i, buf2048, 0); - buffer.AddRange(buf2048); + bitsToHash.Add(new(buf2048)); } break; } @@ -191,7 +193,7 @@ namespace BizHawk.Client.EmuHawk exePath = exePath[index..endIndex]; } - buffer.AddRange(Encoding.ASCII.GetBytes(exePath)); + bitsToHash.Add(new(Encoding.ASCII.GetBytes(exePath))); // get sector for exe sector = GetFileSector(exePath, out var exeSize); @@ -204,13 +206,13 @@ namespace BizHawk.Client.EmuHawk exeSize = ((buf2048[31] << 24) | (buf2048[30] << 16) | (buf2048[29] << 8) | buf2048[28]) + 2048; } - buffer.AddRange(new ArraySegment(buf2048, 0, Math.Min(2048, exeSize))); + bitsToHash.Add(new(buf2048, offset: 0, count: Math.Min(2048, exeSize))); exeSize -= 2048; while (exeSize > 0) { dsr.ReadLBA_2048(sector++, buf2048, 0); - buffer.AddRange(new ArraySegment(buf2048, 0, Math.Min(2048, exeSize))); + bitsToHash.Add(new(buf2048, offset: 0, count: Math.Min(2048, exeSize))); exeSize -= 2048; } @@ -219,7 +221,7 @@ namespace BizHawk.Client.EmuHawk case ConsoleID.SegaCD: case ConsoleID.Saturn: dsr.ReadLBA_2048(0, buf2048, 0); - buffer.AddRange(new ArraySegment(buf2048, 0, 512)); + bitsToHash.Add(new(buf2048, offset: 0, count: 512)); break; case ConsoleID.JaguarCD: var discHasher = new DiscHasher(disc); @@ -230,7 +232,7 @@ namespace BizHawk.Client.EmuHawk }; } - var hash = MD5Checksum.ComputeDigestHex(buffer.ToArray()); + var hash = MD5Checksum.ComputeDigestHex(CE.ConcatArrays(bitsToHash)); return IdentifyHash(hash); } diff --git a/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs index e354bd0283..56e15d6cba 100755 --- a/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs @@ -258,7 +258,7 @@ // // TargetFrameLengthNumeric // - this.TargetFrameLengthNumeric.Location = new System.Drawing.Point(146, 138); + this.TargetFrameLengthNumeric.Location = new System.Drawing.Point(236, 138); this.TargetFrameLengthNumeric.Maximum = new decimal(new int[] { 500000, 0, @@ -277,6 +277,7 @@ 0, 0, 0}); + this.TargetFrameLengthNumeric.ValueChanged += new System.EventHandler(this.FrameLength_ValueChanged); // // TargetRewindIntervalNumeric // @@ -299,6 +300,7 @@ 0, 0, 0}); + this.TargetRewindIntervalNumeric.ValueChanged += new System.EventHandler(this.RewindInterval_ValueChanged); // // EstTimeLabel // @@ -537,7 +539,7 @@ this.TargetFrameLengthRadioButton.Size = new System.Drawing.Size(125, 17); this.TargetFrameLengthRadioButton.TabIndex = 48; this.TargetFrameLengthRadioButton.TabStop = true; - this.TargetFrameLengthRadioButton.Text = "Desired frame length:"; + this.TargetFrameLengthRadioButton.Text = "Desired minimum rewind range in frames:"; this.TargetFrameLengthRadioButton.UseVisualStyleBackColor = true; // // TargetRewindIntervalRadioButton @@ -550,6 +552,7 @@ this.TargetRewindIntervalRadioButton.TabStop = true; this.TargetRewindIntervalRadioButton.Text = "Rewinds every fixed number of frames: "; this.TargetRewindIntervalRadioButton.UseVisualStyleBackColor = true; + this.TargetRewindIntervalRadioButton.CheckedChanged += new System.EventHandler(this.RewindInterval_CheckedChanged); // // RewindConfig // @@ -636,4 +639,4 @@ private System.Windows.Forms.RadioButton TargetFrameLengthRadioButton; private System.Windows.Forms.RadioButton TargetRewindIntervalRadioButton; } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs b/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs index 2667da4b69..6076cac6af 100755 --- a/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs @@ -46,7 +46,7 @@ namespace BizHawk.Client.EmuHawk FullnessLabel.Text = $"{fullnessRatio:P2}"; var stateCount = rewinder.Count; RewindFramesUsedLabel.Text = stateCount.ToString(); - _avgStateSize = stateCount is 0 ? 0UL : (ulong) Math.Round(rewinder.Size * fullnessRatio / stateCount); + _avgStateSize = stateCount is 0 ? (ulong) _statableCore.CloneSavestate().Length : (ulong) Math.Round(rewinder.Size * fullnessRatio / stateCount); } else { @@ -155,17 +155,26 @@ namespace BizHawk.Client.EmuHawk private void CalculateEstimates() { double estFrames = 0.0; + var bufferSize = 1L << (int) BufferSizeUpDown.Value; + labelEx1.Text = bufferSize.ToString(); + int calculatedRewindInterval = TargetRewindIntervalRadioButton.Checked ? (int) TargetRewindIntervalNumeric.Value : 1; if (_avgStateSize is not 0UL) { - var bufferSize = 1L << (int) BufferSizeUpDown.Value; - labelEx1.Text = bufferSize.ToString(); bufferSize *= 1024 * 1024; estFrames = bufferSize / (double) _avgStateSize; + if (TargetFrameLengthRadioButton.Checked) + calculatedRewindInterval = (int) Math.Ceiling((int) TargetFrameLengthNumeric.Value / estFrames); } ApproxFramesLabel.Text = $"{estFrames:n0} frames"; - EstTimeLabel.Text = $"{estFrames / _framerate / 60.0:n} minutes"; + EstTimeLabel.Text = $"{estFrames / _framerate / 60.0 * calculatedRewindInterval:n} minutes"; } + private void FrameLength_ValueChanged(object sender, EventArgs e) => CalculateEstimates(); + + private void RewindInterval_ValueChanged(object sender, EventArgs e) => CalculateEstimates(); + + private void RewindInterval_CheckedChanged(object sender, EventArgs e) => CalculateEstimates(); + private void BufferSizeUpDown_ValueChanged(object sender, EventArgs e) { CalculateEstimates(); diff --git a/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.IControlMainform.cs b/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.IControlMainform.cs index 5c3360d602..a7e990dcc8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.IControlMainform.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.IControlMainform.cs @@ -45,6 +45,8 @@ public bool RestartMovie() => false; + public bool WantsToBypassMovieEndAction => false; + // TODO: We want to prevent movies and probably other things } } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index f2bb6d35f2..44c258e4c8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -500,7 +500,7 @@ namespace BizHawk.Client.EmuHawk { if (Engaged()) { - Tastudio.QueryItemBgColorCallback = (index, name) => _th.SafeParseColor(luaf.Call(index, name)?[0]); + Tastudio.QueryItemBgColorCallback = (index, name) => _th.SafeParseColor(luaf.Call(index, name)?.FirstOrDefault()); } } @@ -523,7 +523,7 @@ namespace BizHawk.Client.EmuHawk Tastudio.QueryItemIconCallback = (index, name) => { var result = luaf.Call(index, name); - if (result?[0] != null) + if (result?.FirstOrDefault() is not null) { return _iconCache.GetValueOrPutNew1(result[0].ToString()).ToBitmap(); } diff --git a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs index d24dc60d2b..2d0196cd8b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs @@ -48,7 +48,8 @@ this.MultiDiskMenuStrip.ImageScalingSize = new System.Drawing.Size(20, 20); this.MultiDiskMenuStrip.Location = new System.Drawing.Point(0, 0); this.MultiDiskMenuStrip.Name = "MultiDiskMenuStrip"; - this.MultiDiskMenuStrip.Size = new System.Drawing.Size(675, 24); + this.MultiDiskMenuStrip.Padding = new System.Windows.Forms.Padding(4, 2, 0, 2); + this.MultiDiskMenuStrip.Size = new System.Drawing.Size(506, 24); this.MultiDiskMenuStrip.TabIndex = 0; this.MultiDiskMenuStrip.Text = "menuStrip1"; // @@ -56,10 +57,9 @@ // this.SaveRunButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.SaveRunButton.Enabled = false; - this.SaveRunButton.Location = new System.Drawing.Point(424, 405); - this.SaveRunButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.SaveRunButton.Location = new System.Drawing.Point(318, 329); this.SaveRunButton.Name = "SaveRunButton"; - this.SaveRunButton.Size = new System.Drawing.Size(113, 28); + this.SaveRunButton.Size = new System.Drawing.Size(85, 23); this.SaveRunButton.TabIndex = 9; this.SaveRunButton.Text = "Save and &Run"; this.SaveRunButton.UseVisualStyleBackColor = true; @@ -69,10 +69,9 @@ // 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(545, 405); - this.CancelBtn.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.CancelBtn.Location = new System.Drawing.Point(409, 329); this.CancelBtn.Name = "CancelBtn"; - this.CancelBtn.Size = new System.Drawing.Size(113, 28); + this.CancelBtn.Size = new System.Drawing.Size(85, 23); this.CancelBtn.TabIndex = 10; this.CancelBtn.Text = "&Close"; this.CancelBtn.UseVisualStyleBackColor = true; @@ -84,11 +83,9 @@ | System.Windows.Forms.AnchorStyles.Right))); this.grpName.Controls.Add(this.BrowseBtn); this.grpName.Controls.Add(this.NameBox); - this.grpName.Location = new System.Drawing.Point(11, 34); - this.grpName.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.grpName.Location = new System.Drawing.Point(8, 28); this.grpName.Name = "grpName"; - this.grpName.Padding = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.grpName.Size = new System.Drawing.Size(648, 55); + this.grpName.Size = new System.Drawing.Size(486, 45); this.grpName.TabIndex = 11; this.grpName.TabStop = false; this.grpName.Text = "Output Bundle Path"; @@ -96,10 +93,9 @@ // BrowseBtn // this.BrowseBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.BrowseBtn.Location = new System.Drawing.Point(556, 22); - this.BrowseBtn.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.BrowseBtn.Location = new System.Drawing.Point(417, 18); this.BrowseBtn.Name = "BrowseBtn"; - this.BrowseBtn.Size = new System.Drawing.Size(84, 28); + this.BrowseBtn.Size = new System.Drawing.Size(63, 23); this.BrowseBtn.TabIndex = 14; this.BrowseBtn.Text = "Browse..."; this.BrowseBtn.UseVisualStyleBackColor = true; @@ -109,10 +105,9 @@ // this.NameBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.NameBox.Location = new System.Drawing.Point(8, 23); - this.NameBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.NameBox.Location = new System.Drawing.Point(6, 19); this.NameBox.Name = "NameBox"; - this.NameBox.Size = new System.Drawing.Size(539, 22); + this.NameBox.Size = new System.Drawing.Size(405, 20); this.NameBox.TabIndex = 0; this.NameBox.TextChanged += new System.EventHandler(this.NameBox_TextChanged); // @@ -124,21 +119,20 @@ | System.Windows.Forms.AnchorStyles.Right))); this.FileSelectorPanel.AutoScroll = true; this.FileSelectorPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.FileSelectorPanel.Location = new System.Drawing.Point(11, 124); - this.FileSelectorPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.FileSelectorPanel.Location = new System.Drawing.Point(8, 101); this.FileSelectorPanel.Name = "FileSelectorPanel"; - this.FileSelectorPanel.Size = new System.Drawing.Size(647, 263); + this.FileSelectorPanel.Size = new System.Drawing.Size(486, 214); this.FileSelectorPanel.TabIndex = 12; this.FileSelectorPanel.DragDrop += new System.Windows.Forms.DragEventHandler(this.OnDragDrop); this.FileSelectorPanel.DragEnter += new System.Windows.Forms.DragEventHandler(this.OnDragEnter); + this.FileSelectorPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.FileSelectorPanel_Paint); // // AddButton // this.AddButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.AddButton.Location = new System.Drawing.Point(11, 405); - this.AddButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.AddButton.Location = new System.Drawing.Point(8, 329); this.AddButton.Name = "AddButton"; - this.AddButton.Size = new System.Drawing.Size(80, 28); + this.AddButton.Size = new System.Drawing.Size(60, 23); this.AddButton.TabIndex = 13; this.AddButton.Text = "Add"; this.AddButton.UseVisualStyleBackColor = true; @@ -149,28 +143,25 @@ this.SystemDropDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.SystemDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.SystemDropDown.FormattingEnabled = true; - this.SystemDropDown.Location = new System.Drawing.Point(540, 92); - this.SystemDropDown.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.SystemDropDown.Location = new System.Drawing.Point(405, 75); this.SystemDropDown.Name = "SystemDropDown"; - this.SystemDropDown.Size = new System.Drawing.Size(117, 24); + this.SystemDropDown.Size = new System.Drawing.Size(89, 21); this.SystemDropDown.TabIndex = 14; this.SystemDropDown.SelectedIndexChanged += new System.EventHandler(this.SystemDropDown_SelectedIndexChanged); // // SystemLabel // this.SystemLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.SystemLabel.Location = new System.Drawing.Point(473, 96); - this.SystemLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.SystemLabel.Location = new System.Drawing.Point(355, 78); this.SystemLabel.Name = "SystemLabel"; this.SystemLabel.Text = "System:"; // // btnRemove // this.btnRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.btnRemove.Location = new System.Drawing.Point(99, 405); - this.btnRemove.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btnRemove.Location = new System.Drawing.Point(74, 329); this.btnRemove.Name = "btnRemove"; - this.btnRemove.Size = new System.Drawing.Size(80, 28); + this.btnRemove.Size = new System.Drawing.Size(60, 23); this.btnRemove.TabIndex = 16; this.btnRemove.Text = "Remove"; this.btnRemove.UseVisualStyleBackColor = true; @@ -180,10 +171,9 @@ // this.SaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.SaveButton.Enabled = false; - this.SaveButton.Location = new System.Drawing.Point(303, 405); - this.SaveButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.SaveButton.Location = new System.Drawing.Point(227, 329); this.SaveButton.Name = "SaveButton"; - this.SaveButton.Size = new System.Drawing.Size(113, 28); + this.SaveButton.Size = new System.Drawing.Size(85, 23); this.SaveButton.TabIndex = 18; this.SaveButton.Text = "&Save"; this.SaveButton.UseVisualStyleBackColor = true; @@ -191,9 +181,9 @@ // // MultiDiskBundler // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(675, 448); + this.ClientSize = new System.Drawing.Size(506, 364); this.Controls.Add(this.SaveButton); this.Controls.Add(this.btnRemove); this.Controls.Add(this.SystemLabel); @@ -205,7 +195,6 @@ this.Controls.Add(this.SaveRunButton); this.Controls.Add(this.MultiDiskMenuStrip); this.MainMenuStrip = this.MultiDiskMenuStrip; - this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.Name = "MultiDiskBundler"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Load += new System.EventHandler(this.MultiDiskBundler_Load); diff --git a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs index 2cb9874544..539b70dd6b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs +++ b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs @@ -39,6 +39,7 @@ namespace BizHawk.Client.EmuHawk VSystemID.Raw.Arcade, VSystemID.Raw.C64, VSystemID.Raw.DOS, + VSystemID.Raw.Doom, VSystemID.Raw.GBL, VSystemID.Raw.GEN, VSystemID.Raw.GGL, @@ -344,5 +345,10 @@ namespace BizHawk.Client.EmuHawk { } + + private void FileSelectorPanel_Paint(object sender, PaintEventArgs e) + { + + } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs index c3fabeb55f..b242aad94d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs @@ -113,5 +113,7 @@ public bool WantsToControlReboot => false; public void RebootCore() => throw new NotSupportedException("This should never be called"); + + public bool WantsToBypassMovieEndAction => true; } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 9083965268..5b5564bdc8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -52,6 +52,16 @@ namespace BizHawk.Client.EmuHawk } } + protected override void UpdateBefore() + { + if (CurrentTasMovie.IsAtEnd()) + { + CurrentTasMovie.RecordFrame(CurrentTasMovie.Emulator.Frame, MovieSession.StickySource); + } + } + + protected override void FastUpdateBefore() => UpdateBefore(); + protected override void GeneralUpdate() { RefreshDialog(); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index d5f74ea092..0b49666a2b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -357,7 +357,11 @@ namespace BizHawk.Client.EmuHawk catch (Exception ex) { text = ""; - DialogController.ShowMessageBox($"oops\n{ex}"); + DialogController.ShowMessageBox("Encountered unrecoverable error while drawing the input roll.\n" + + "The current movie will be closed without saving.\n" + + $"The exception was:\n\n{ex}", caption: "Failed to draw input roll"); + TastudioStopMovie(); + StartNewTasMovie(); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 9372d7165b..1e87c41022 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -34,7 +34,6 @@ namespace BizHawk.Client.EmuHawk private readonly List _tasClipboard = new List(); private const string CursorColumnName = "CursorColumn"; private const string FrameColumnName = "FrameColumn"; - private MovieEndAction _originalEndAction; // The movie end behavior selected by the user (that is overridden by TAStudio) private UndoHistoryForm _undoForm; private Timer _autosaveTimer; @@ -288,14 +287,11 @@ namespace BizHawk.Client.EmuHawk // Attempts to load failed, abort if (!success) { - Disengage(); return false; } MainForm.AddOnScreenMessage("TAStudio engaged"); - _originalEndAction = Config.Movies.MovieEndAction; MainForm.DisableRewind(); - Config.Movies.MovieEndAction = MovieEndAction.Record; MainForm.SetMainformMovieInfo(); MovieSession.ReadOnly = true; SetSplicer(); @@ -674,7 +670,6 @@ namespace BizHawk.Client.EmuHawk _engaged = false; MainForm.PauseOnFrame = null; MainForm.AddOnScreenMessage("TAStudio disengaged"); - Config.Movies.MovieEndAction = _originalEndAction; WantsToControlRewind = false; MainForm.EnableRewind(true); MainForm.SetMainformMovieInfo(); diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs index 32a0fece67..05bd664e37 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs @@ -315,7 +315,8 @@ namespace BizHawk.Client.EmuHawk MessageLabel.Text = "Search restarted"; DoDomainSizeCheck(); _dropdownDontfire = true; - SetSize(_settings.Size); // Calls NewSearch() automatically + SetSize(_settings.Size); + NewSearch(); _dropdownDontfire = false; HardSetDisplayTypeDropDown(_settings.Type); } @@ -691,7 +692,12 @@ namespace BizHawk.Client.EmuHawk DifferenceBox.ByteSize = size; DifferentByBox.ByteSize = size; - NewSearch(); + _searches.ConvertTo(_settings.Size); + _searches.SetType(_settings.Type); + UpdateList(); + _searches.ClearHistory(); + + ToggleSearchDependentToolBarItems(); } private void PopulateTypeDropDown() diff --git a/src/BizHawk.Common/CustomCollections.cs b/src/BizHawk.Common/CustomCollections.cs index eb2738c1a0..8f0eec5c63 100644 --- a/src/BizHawk.Common/CustomCollections.cs +++ b/src/BizHawk.Common/CustomCollections.cs @@ -1,26 +1,50 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; namespace BizHawk.Common { - public class SortedList : ICollection + public class SortedList : IList where T : IComparable { + private const string ERR_MSG_OUT_OF_ORDER = "setting/inserting elements must preserve ordering"; + protected readonly List _list; public virtual int Count => _list.Count; public virtual bool IsReadOnly { get; } = false; - public SortedList() => _list = new List(); + protected SortedList(List wrapped) + => _list = wrapped; + + public SortedList() + : this(new()) {} public SortedList(IEnumerable collection) + : this(new(collection)) { - _list = new List(collection); _list.Sort(); } - public virtual T this[int index] => _list[index]; + public virtual T this[int index] + { + get => _list[index]; + set + { + // NOT allowing appends, to match BCL `List` + if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(paramName: nameof(index), index, message: $"index must be in 0..<{Count}"); + if (Count is 0) + { + _list.Add(value); + return; + } + var willBeGeqPrevious = index is 0 || value.CompareTo(_list[index - 1]) >= 0; + var willBeLeqFollowing = index == Count - 1 || _list[index + 1].CompareTo(value) >= 0; + if (willBeGeqPrevious && willBeLeqFollowing) _list[index] = value; + else throw new NotSupportedException(ERR_MSG_OUT_OF_ORDER); + } + } public virtual void Add(T item) { @@ -55,6 +79,21 @@ namespace BizHawk.Common return i < 0 ? -1 : i; } + public virtual void Insert(int index, T item) + { + // allowing appends per `IList` docs + if (index < 0 || Count < index) throw new ArgumentOutOfRangeException(paramName: nameof(index), index, message: $"index must be in 0..{Count}"); + if (Count is 0) + { + _list.Add(item); + return; + } + var willBeGeqPrevious = index is 0 || item.CompareTo(_list[index - 1]) >= 0; + var willBeLeqFollowing = index >= Count - 1 || _list[index].CompareTo(item) >= 0; + if (willBeGeqPrevious && willBeLeqFollowing) _list.Insert(index, item); + else throw new NotSupportedException(ERR_MSG_OUT_OF_ORDER); + } + public T LastOrDefault() => _list.Count is 0 ? default! : _list[_list.Count - 1]; @@ -97,6 +136,12 @@ namespace BizHawk.Common } } + public SortedList Slice(int start, int length) + => new(SliceImpl(start: start, length: length)); + + protected List SliceImpl(int start, int length) + => _list.Skip(start).Take(length).ToList(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/src/BizHawk.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs index 7f49167b62..e59ab64be4 100644 --- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs +++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs @@ -149,6 +149,36 @@ namespace BizHawk.Common.CollectionExtensions return combined; } + /// freshly-allocated array + public static T[] ConcatArrays(/*params*/ IReadOnlyCollection arrays) + { + var combinedLength = arrays.Sum(static a => a.Length); //TODO detect overflow + if (combinedLength is 0) return Array.Empty(); + var combined = new T[combinedLength]; + var i = 0; + foreach (var arr in arrays) + { + arr.AsSpan().CopyTo(combined.AsSpan(start: i)); + i += arr.Length; + } + return combined; + } + + /// freshly-allocated array + public static T[] ConcatArrays(/*params*/ IReadOnlyCollection> arrays) + { + var combinedLength = arrays.Sum(static a => a.Count); //TODO detect overflow + if (combinedLength is 0) return Array.Empty(); + var combined = new T[combinedLength]; + var i = 0; + foreach (var arr in arrays) + { + arr.AsSpan().CopyTo(combined.AsSpan(start: i)); + i += arr.Count; + } + return combined; + } + public static bool CountIsAtLeast(this IEnumerable collection, int n) => collection is ICollection countable ? countable.Count >= n @@ -165,10 +195,6 @@ namespace BizHawk.Common.CollectionExtensions /// If the key is not present, returns default(TValue). /// backported from .NET Core 2.0 /// - public static TValue? GetValueOrDefault(IDictionary dictionary, TKey key) - => dictionary.TryGetValue(key, out var found) ? found : default; - - /// public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key) => dictionary.TryGetValue(key, out var found) ? found : default; @@ -177,10 +203,6 @@ namespace BizHawk.Common.CollectionExtensions /// If the key is not present, returns . /// backported from .NET Core 2.0 /// - public static TValue? GetValueOrDefault(IDictionary dictionary, TKey key, TValue defaultValue) - => dictionary.TryGetValue(key, out var found) ? found : defaultValue; - - /// public static TValue? GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) => dictionary.TryGetValue(key, out var found) ? found : defaultValue; #endif @@ -310,9 +332,11 @@ namespace BizHawk.Common.CollectionExtensions return str.Substring(startIndex: offset, length: length); } +#if !NET8_0_OR_GREATER /// shallow clone - public static Dictionary ToDictionary(this IEnumerable> list) + public static Dictionary ToDictionary(this IEnumerable> list) where TKey : notnull => list.ToDictionary(static kvp => kvp.Key, static kvp => kvp.Value); +#endif public static bool IsSortedAsc(this IReadOnlyList list) where T : IComparable diff --git a/src/BizHawk.Common/Extensions/SpanSplit.cs b/src/BizHawk.Common/Extensions/SpanSplit.cs index 9fc43a4a76..167e01abde 100644 --- a/src/BizHawk.Common/Extensions/SpanSplit.cs +++ b/src/BizHawk.Common/Extensions/SpanSplit.cs @@ -5,16 +5,13 @@ * and https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs */ -#if !NET8_0_OR_GREATER +#if !NET9_0_OR_GREATER #pragma warning disable RS0030 // `Debug.Assert` w/o message, breaks BizHawk convention #pragma warning disable SA1514 // "Element documentation header should be preceded by blank line" using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -#if NETCOREAPP3_0_OR_GREATER -using System.Runtime.Intrinsics; -#endif namespace BizHawk.Common { @@ -534,13 +531,6 @@ namespace BizHawk.Common sep0 = separators[0]; sep1 = separators.Length > 1 ? separators[1] : sep0; sep2 = separators.Length > 2 ? separators[2] : sep1; -#if NETCOREAPP3_0_OR_GREATER - if (Vector128.IsHardwareAccelerated && source.Length >= Vector128.Count * 2) - { - MakeSeparatorListVectorized(source, ref sepListBuilder, sep0, sep1, sep2); - return; - } -#endif for (int i = 0; i < source.Length; i++) { @@ -583,120 +573,6 @@ namespace BizHawk.Common } } -#if NETCOREAPP3_0_OR_GREATER - private static void MakeSeparatorListVectorized(ReadOnlySpan sourceSpan, ref ValueListBuilder sepListBuilder, char c, char c2, char c3) - { - // Redundant test so we won't prejit remainder of this method - // on platforms where it is not supported - if (!Vector128.IsHardwareAccelerated) - { - throw new PlatformNotSupportedException(); - } - Debug.Assert(sourceSpan.Length >= Vector128.Count); - nuint lengthToExamine = (uint)sourceSpan.Length; - nuint offset = 0; - ref char source = ref MemoryMarshal.GetReference(sourceSpan); - - if (Vector512.IsHardwareAccelerated && lengthToExamine >= (uint)Vector512.Count*2) - { - Vector512 v1 = Vector512.Create((ushort)c); - Vector512 v2 = Vector512.Create((ushort)c2); - Vector512 v3 = Vector512.Create((ushort)c3); - - do - { - Vector512 vector = Vector512.LoadUnsafe(ref source, offset); - Vector512 v1Eq = Vector512.Equals(vector, v1); - Vector512 v2Eq = Vector512.Equals(vector, v2); - Vector512 v3Eq = Vector512.Equals(vector, v3); - Vector512 cmp = (v1Eq | v2Eq | v3Eq).AsByte(); - - if (cmp != Vector512.Zero) - { - // Skip every other bit - ulong mask = cmp.ExtractMostSignificantBits() & 0x5555555555555555; - do - { - uint bitPos = (uint)BitOperations.TrailingZeroCount(mask) / sizeof(char); - sepListBuilder.Append((int)(offset + bitPos)); - mask = BitOperations.ResetLowestSetBit(mask); - } while (mask != 0); - } - - offset += (nuint)Vector512.Count; - } while (offset <= lengthToExamine - (nuint)Vector512.Count); - } - else if (Vector256.IsHardwareAccelerated && lengthToExamine >= (uint)Vector256.Count*2) - { - Vector256 v1 = Vector256.Create((ushort)c); - Vector256 v2 = Vector256.Create((ushort)c2); - Vector256 v3 = Vector256.Create((ushort)c3); - - do - { - Vector256 vector = Vector256.LoadUnsafe(ref source, offset); - Vector256 v1Eq = Vector256.Equals(vector, v1); - Vector256 v2Eq = Vector256.Equals(vector, v2); - Vector256 v3Eq = Vector256.Equals(vector, v3); - Vector256 cmp = (v1Eq | v2Eq | v3Eq).AsByte(); - - if (cmp != Vector256.Zero) - { - // Skip every other bit - uint mask = cmp.ExtractMostSignificantBits() & 0x55555555; - do - { - uint bitPos = (uint)BitOperations.TrailingZeroCount(mask) / sizeof(char); - sepListBuilder.Append((int)(offset + bitPos)); - mask = BitOperations.ResetLowestSetBit(mask); - } while (mask != 0); - } - - offset += (nuint)Vector256.Count; - } while (offset <= lengthToExamine - (nuint)Vector256.Count); - } - else if (Vector128.IsHardwareAccelerated) - { - Vector128 v1 = Vector128.Create((ushort)c); - Vector128 v2 = Vector128.Create((ushort)c2); - Vector128 v3 = Vector128.Create((ushort)c3); - - do - { - Vector128 vector = Vector128.LoadUnsafe(ref source, offset); - Vector128 v1Eq = Vector128.Equals(vector, v1); - Vector128 v2Eq = Vector128.Equals(vector, v2); - Vector128 v3Eq = Vector128.Equals(vector, v3); - Vector128 cmp = (v1Eq | v2Eq | v3Eq).AsByte(); - - if (cmp != Vector128.Zero) - { - // Skip every other bit - uint mask = cmp.ExtractMostSignificantBits() & 0x5555; - do - { - uint bitPos = (uint)BitOperations.TrailingZeroCount(mask) / sizeof(char); - sepListBuilder.Append((int)(offset + bitPos)); - mask = BitOperations.ResetLowestSetBit(mask); - } while (mask != 0); - } - - offset += (nuint)Vector128.Count; - } while (offset <= lengthToExamine - (nuint)Vector128.Count); - } - - while (offset < lengthToExamine) - { - char curr = Unsafe.Add(ref source, offset); - if (curr == c || curr == c2 || curr == c3) - { - sepListBuilder.Append((int)offset); - } - offset++; - } - } -#endif - /// /// Uses ValueListBuilder to create list that holds indexes of separators in string. /// diff --git a/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs b/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs index 9fde902942..1f48d5a4d9 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs @@ -923,6 +923,28 @@ namespace BizHawk.Emulation.Common ["HOLD"] = 'H', ["START"] = 'S', ["RESET"] = 'r', + }, + [VSystemID.Raw.Doom] = new() + { + ["Action"] = 'A', + ["Fire"] = 'F', + ["Alt Weapon"] = 'X', + ["Jump"] = 'J', + ["End Player"] = 'E', + ["Forward"] = 'f', + ["Backward"] = 'b', + ["Turn Left"] = 'l', + ["Turn Right"] = 'r', + ["Strafe Left"] = 'L', + ["Strafe Right"] = 'R', + ["Shift Run"] = 's', + ["Weapon Select 1"] = '1', + ["Weapon Select 2"] = '2', + ["Weapon Select 3"] = '3', + ["Weapon Select 4"] = '4', + ["Weapon Select 5"] = '5', + ["Weapon Select 6"] = '6', + ["Weapon Select 7"] = '7', }, }; @@ -973,6 +995,17 @@ namespace BizHawk.Emulation.Common ["Mouse Position Y"] = "mpY", ["Mouse Scroll X"] = "msX", ["Mouse Scroll Y"] = "msY", + }, + [VSystemID.Raw.Doom] = new() + { + ["Run Speed"] = "R", + ["Strafing Speed"] = "S", + ["Turning Speed"] = "T", + ["Weapon Select"] = "W", + ["Fly / Look"] = "L", + ["Use Artifact"] = "U", + ["Mouse Running"] = "mR", + ["Mouse Turning"] = "mT" }, }; } diff --git a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs index 6a8ef2282a..bb2a6608a9 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs @@ -88,7 +88,7 @@ namespace BizHawk.Emulation.Common /// public IDictionary CategoryLabels { get; private set; } = new Dictionary(); - public void ApplyAxisConstraints(string constraintClass, IDictionary axes) + public void ApplyAxisConstraints(string constraintClass, Dictionary axes) { if (!Axes.HasContraints) return; foreach (var (k, v) in Axes) @@ -101,8 +101,8 @@ namespace BizHawk.Emulation.Common var xAxis = k; var yAxis = circular.PairedAxis; (axes[xAxis], axes[yAxis]) = circular.ApplyTo( - CollectionExtensions.GetValueOrDefault(axes, xAxis), - CollectionExtensions.GetValueOrDefault(axes, yAxis)); + axes.GetValueOrDefault(xAxis), + axes.GetValueOrDefault(yAxis)); break; } } diff --git a/src/BizHawk.Emulation.Common/Database/Database.cs b/src/BizHawk.Emulation.Common/Database/Database.cs index 3a8470c267..08aaa01af1 100644 --- a/src/BizHawk.Emulation.Common/Database/Database.cs +++ b/src/BizHawk.Emulation.Common/Database/Database.cs @@ -484,6 +484,10 @@ namespace BizHawk.Emulation.Common game.AddOption("VEC", "true"); break; + case ".WAD": + game.System = VSystemID.Raw.Doom; + break; + case ".ZIP": case ".7Z": game.System = VSystemID.Raw.Arcade; diff --git a/src/BizHawk.Emulation.Common/Extensions.cs b/src/BizHawk.Emulation.Common/Extensions.cs index 1d99142292..4e1caf0a89 100644 --- a/src/BizHawk.Emulation.Common/Extensions.cs +++ b/src/BizHawk.Emulation.Common/Extensions.cs @@ -27,6 +27,7 @@ namespace BizHawk.Emulation.Common [VSystemID.Raw.Coleco] = "ColecoVision", [VSystemID.Raw.DOS] = "DOS", // DEBUG + [VSystemID.Raw.Doom] = "Doom", [VSystemID.Raw.GBL] = "Game Boy Link", [VSystemID.Raw.GB] = "GB", [VSystemID.Raw.SGB] = "SGB", diff --git a/src/BizHawk.Emulation.Common/VSystemID.cs b/src/BizHawk.Emulation.Common/VSystemID.cs index c6f4fd5b66..a6ef35d69d 100644 --- a/src/BizHawk.Emulation.Common/VSystemID.cs +++ b/src/BizHawk.Emulation.Common/VSystemID.cs @@ -24,6 +24,7 @@ namespace BizHawk.Emulation.Common public const string ChannelF = "ChannelF"; public const string Coleco = "Coleco"; public const string DEBUG = "DEBUG"; + public const string Doom = "Doom"; public const string Dreamcast = "Dreamcast"; public const string DOS = "DOS"; public const string GameCube = "GameCube"; diff --git a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index be5ebc43be..2c05443ccc 100644 --- a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -28,6 +28,7 @@ + diff --git a/src/BizHawk.Emulation.Cores/CPUs/Intel8048/Disassembler.cs b/src/BizHawk.Emulation.Cores/CPUs/Intel8048/Disassembler.cs index c67c3f2283..d0a6ae32c4 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/Intel8048/Disassembler.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/Intel8048/Disassembler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text; namespace BizHawk.Emulation.Cores.Components.I8048 @@ -267,23 +266,18 @@ namespace BizHawk.Emulation.Cores.Components.I8048 public static string Disassemble(ushort addr, Func reader, out ushort size) { + StringBuilder ret = new(); ushort origaddr = addr; - List bytes = new List(); - bytes.Add(reader(addr++)); - - string result = table[bytes[0]]; - + ret.AppendFormat("{0:X4}: ", origaddr); + var opcode = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode); + var result = table[opcode]; if (result.Contains("i8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); result = result.Replace("i8", string.Format("#{0:X2}h", d)); } - - StringBuilder ret = new StringBuilder(); - ret.Append(string.Format("{0:X4}: ", origaddr)); - foreach (var b in bytes) - ret.Append(string.Format("{0:X2} ", b)); while (ret.Length < 22) ret.Append(' '); ret.Append(result); diff --git a/src/BizHawk.Emulation.Cores/CPUs/LR35902/NewDisassembler.cs b/src/BizHawk.Emulation.Cores/CPUs/LR35902/NewDisassembler.cs index b6cd9a4eaa..4b9c34a05f 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/LR35902/NewDisassembler.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/LR35902/NewDisassembler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text; namespace BizHawk.Emulation.Cores.Components.LR35902 @@ -1041,51 +1040,52 @@ namespace BizHawk.Emulation.Cores.Components.LR35902 public static string Disassemble(ushort addr, Func reader, bool rgbds, out ushort size) { + StringBuilder ret = new(); ushort origaddr = addr; - var bytes = new List + ret.AppendFormat("{0:X4}: ", origaddr); + var opcode = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode); + string result; + if (opcode is 0xCB) { - reader(addr++) - }; - - string result = (rgbds ? rgbds_table : table)[bytes[0]]; - if (bytes[0] == 0xcb) - { - bytes.Add(reader(addr++)); - result = (rgbds ? rgbds_table : table)[bytes[1] + 256]; + var opcode1 = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode1); + result = (rgbds ? rgbds_table : table)[opcode1 + 256]; + } + else + { + result = (rgbds ? rgbds_table : table)[opcode]; } - if (result.Contains("d8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); result = result.Replace("d8", rgbds ? $"${d:X2}" : $"#{d:X2}h"); } else if (result.Contains("d16")) { byte dlo = reader(addr++); byte dhi = reader(addr++); - bytes.Add(dlo); - bytes.Add(dhi); + ret.AppendFormat("{0:X2} {1:X2} ", dlo, dhi); result = result.Replace("d16", rgbds ? $"${dhi:X2}{dlo:X2}" : $"#{dhi:X2}{dlo:X2}h"); } else if (result.Contains("a16")) { byte dlo = reader(addr++); byte dhi = reader(addr++); - bytes.Add(dlo); - bytes.Add(dhi); + ret.AppendFormat("{0:X2} {1:X2} ", dlo, dhi); result = result.Replace("a16", rgbds ? $"${dhi:X2}{dlo:X2}" : $"#{dhi:X2}{dlo:X2}h"); } else if (result.Contains("a8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); result = result.Replace("a8", rgbds ? $"$FF{d:X2}" : $"#FF{d:X2}h"); } else if (result.Contains("r8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); int offs = d; if (offs >= 128) offs -= 256; @@ -1095,15 +1095,12 @@ namespace BizHawk.Emulation.Cores.Components.LR35902 else if (result.Contains("e8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); int offs = (d >= 128) ? (256 - d) : d; string sign = (d >= 128) ? "-" : ""; result = result.Replace("e8", rgbds ? sign + $"${offs:X2}" : sign + $"{offs:X2}h"); } - var ret = new StringBuilder(); - ret.Append($"{origaddr:X4}: "); - foreach (var b in bytes) - ret.Append($"{b:X2} "); + // else noop while (ret.Length < 17) ret.Append(' '); ret.Append(result); diff --git a/src/BizHawk.Emulation.Cores/CPUs/MC6800/Disassembler.cs b/src/BizHawk.Emulation.Cores/CPUs/MC6800/Disassembler.cs index b1d8c9cd1a..841cbf8f9e 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/MC6800/Disassembler.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/MC6800/Disassembler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text; namespace BizHawk.Emulation.Cores.Components.MC6800 @@ -267,47 +266,40 @@ namespace BizHawk.Emulation.Cores.Components.MC6800 public static string Disassemble(ushort addr, Func reader, out ushort size) { + StringBuilder ret = new(); ushort origaddr = addr; - List bytes = new List(); - bytes.Add(reader(addr++)); - - string result = table[bytes[0]]; - + ret.AppendFormat("{0:X4}: ", origaddr); + var opcode = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode); + var result = table[opcode]; if (result.Contains("i8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); result = result.Replace("i8", string.Format("#{0:X2}h", d)); } else if (result.Contains("i16")) { byte dhi = reader(addr++); byte dlo = reader(addr++); - bytes.Add(dhi); - bytes.Add(dlo); + ret.AppendFormat("{0:X2} {1:X2} ", dhi, dlo); result = result.Replace("i16", string.Format("#{0:X2}{1:X2}h", dhi, dlo)); } else if (result.Contains("ex16")) { byte dhi = reader(addr++); byte dlo = reader(addr++); - bytes.Add(dhi); - bytes.Add(dlo); + ret.AppendFormat("{0:X2} {1:X2} ", dhi, dlo); result = result.Replace("ex16", "(" + string.Format("#{0:X2}{1:X2}h", dhi, dlo) + ")"); } else if (result.Contains("ix16")) { byte d = reader(addr++); - bytes.Add(d); - + ret.AppendFormat("{0:X2} ", d); result = result.Replace("ix16", "X + " + "ea"); result = result.Replace("ea", string.Format("{0:N}h", d)); } - - StringBuilder ret = new StringBuilder(); - ret.Append(string.Format("{0:X4}: ", origaddr)); - foreach (var b in bytes) - ret.Append(string.Format("{0:X2} ", b)); + // else noop while (ret.Length < 22) ret.Append(' '); ret.Append(result); diff --git a/src/BizHawk.Emulation.Cores/CPUs/MC6809/Disassembler.cs b/src/BizHawk.Emulation.Cores/CPUs/MC6809/Disassembler.cs index ffb9527e73..218e94d901 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/MC6809/Disassembler.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/MC6809/Disassembler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text; namespace BizHawk.Emulation.Cores.Components.MC6809 @@ -787,49 +786,50 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 public static string Disassemble(ushort addr, Func reader, out ushort size) { + StringBuilder ret = new(); ushort origaddr = addr; - List bytes = new List(); - bytes.Add(reader(addr++)); + ret.AppendFormat("{0:X4}: ", origaddr); + var opcode = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode); - string result = table[bytes[0]]; - if (bytes[0] == 0x10) + string result = table[opcode]; + if (opcode is 0x10) { - bytes.Add(reader(addr++)); - result = table2[bytes[1]]; + var opcode1 = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode1); + result = table2[opcode1]; } - - if (bytes[0] == 0x11) + else if (opcode is 0x11) { - bytes.Add(reader(addr++)); - result = table3[bytes[1]]; + var opcode1 = reader(addr++); + ret.AppendFormat("{0:X2} ", opcode1); + result = table3[opcode1]; } if (result.Contains("i8")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); result = result.Replace("i8", string.Format("#{0:X2}h", d)); } else if (result.Contains("i16")) { byte dhi = reader(addr++); byte dlo = reader(addr++); - bytes.Add(dhi); - bytes.Add(dlo); + ret.AppendFormat("{0:X2} {1:X2} ", dhi, dlo); result = result.Replace("i16", string.Format("#{0:X2}{1:X2}h", dhi, dlo)); } else if (result.Contains("ex16")) { byte dhi = reader(addr++); byte dlo = reader(addr++); - bytes.Add(dhi); - bytes.Add(dlo); + ret.AppendFormat("{0:X2} {1:X2} ", dhi, dlo); result = result.Replace("ex16", "(" + string.Format("#{0:X2}{1:X2}h", dhi, dlo) + ")"); } else if (result.Contains("ix16")) { byte d = reader(addr++); - bytes.Add(d); + ret.AppendFormat("{0:X2} ", d); string temp_reg = ""; @@ -882,15 +882,14 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 break; case 0x8: byte e = reader(addr++); - bytes.Add(e); + ret.AppendFormat("{0:X2} ", e); result = result.Replace("ix16", "(" + temp_reg + " + ea)"); result = result.Replace("ea", string.Format("{0:X2}h", e)); break; case 0x9: byte f = reader(addr++); - bytes.Add(f); byte g = reader(addr++); - bytes.Add(g); + ret.AppendFormat("{0:X2} {1:X2} ", f, g); result = result.Replace("ix16", "(" + temp_reg + " + ea)"); result = result.Replace("ea", string.Format("{0:X2}{1:X2}h", f, g)); break; @@ -903,16 +902,15 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 case 0xC: temp_reg = "PC"; byte h = reader(addr++); - bytes.Add(h); + ret.AppendFormat("{0:X2} ", h); result = result.Replace("ix16", "(" + temp_reg + " + ea)"); result = result.Replace("ea", string.Format("{0:X2}h", h)); break; case 0xD: temp_reg = "PC"; byte i = reader(addr++); - bytes.Add(i); byte j = reader(addr++); - bytes.Add(j); + ret.AppendFormat("{0:X2} {1:X2} ", i, j); result = result.Replace("ix16", "(" + temp_reg + " + ea)"); result = result.Replace("ea", string.Format("{0:X2}{1:X2}h", i, j)); break; @@ -923,9 +921,8 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 if (((d >> 5) & 3) == 0) { byte k = reader(addr++); - bytes.Add(k); byte l = reader(addr++); - bytes.Add(l); + ret.AppendFormat("{0:X2} {1:X2} ", k, l); result = result.Replace("ix16", "(" + string.Format("{0:X2}{1:X2}h", k, l) + ")"); } else @@ -965,15 +962,14 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 break; case 0x8: byte e = reader(addr++); - bytes.Add(e); + ret.AppendFormat("{0:X2} ", e); result = result.Replace("ix16", temp_reg + " + ea"); result = result.Replace("ea", string.Format("{0:X2}h", e)); break; case 0x9: byte f = reader(addr++); - bytes.Add(f); byte g = reader(addr++); - bytes.Add(g); + ret.AppendFormat("{0:X2} {1:X2} ", f, g); result = result.Replace("ix16", temp_reg + " + ea"); result = result.Replace("ea", string.Format("{0:X2}{1:X2}h", f, g)); break; @@ -986,16 +982,15 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 case 0xC: temp_reg = "PC"; byte h = reader(addr++); - bytes.Add(h); + ret.AppendFormat("{0:X2} ", h); result = result.Replace("ix16", temp_reg + " + ea"); result = result.Replace("ea", string.Format("{0:X2}h", h)); break; case 0xD: temp_reg = "PC"; byte i = reader(addr++); - bytes.Add(i); byte j = reader(addr++); - bytes.Add(j); + ret.AppendFormat("{0:X2} {1:X2} ", i, j); result = result.Replace("ix16", temp_reg + " + ea"); result = result.Replace("ea", string.Format("{0:X2}{1:X2}h", i, j)); break; @@ -1009,11 +1004,8 @@ namespace BizHawk.Emulation.Cores.Components.MC6809 } } } + // else noop - StringBuilder ret = new StringBuilder(); - ret.Append(string.Format("{0:X4}: ", origaddr)); - foreach (var b in bytes) - ret.Append(string.Format("{0:X2} ", b)); while (ret.Length < 22) ret.Append(' '); ret.Append(result); diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/FloppyDisk.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/FloppyDisk.cs index 632098877a..bdfb1dc4d3 100644 --- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/FloppyDisk.cs +++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Media/Disk/FloppyDisk.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; + +using BizHawk.Common.CollectionExtensions; using BizHawk.Common.StringExtensions; namespace BizHawk.Emulation.Cores.Computers.AmstradCPC @@ -118,7 +120,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC if (!sec.ContainsMultipleWeakSectors) { byte[] origData = sec.SectorData.ToArray(); - List data = new List(); + List data = new(); //TODO pretty sure the length and indices here are known in advance and this can just be an array --yoshi for (int m = 0; m < 3; m++) { for (int i = 0; i < 512; i++) @@ -442,7 +444,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC // we are going to create a total of 5 weak sector copies // keeping the original copy byte[] origData = sec.SectorData.ToArray(); - List data = new List(); + List data = new(); //TODO pretty sure the length and indices here are known in advance and this can just be an array --yoshi //Random rnd = new Random(); for (int i = 0; i < 6; i++) @@ -589,19 +591,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC /// (including any multiple weak/random data) /// public byte[] TrackSectorData - { - get - { - List list = new List(); - - foreach (var sec in Sectors) - { - list.AddRange(sec.ActualData); - } - - return list.ToArray(); - } - } + => CollectionExtensions.ConcatArrays(Sectors.Select(static sec => sec.ActualData).ToArray()); } public class Sector @@ -652,15 +642,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC int size = 0x80 << SectorSize; if (size > ActualDataByteLength) { - List l = new List(); - l.AddRange(SectorData); - for (int i = 0; i < size - ActualDataByteLength; i++) - { - //l.Add(SectorData[i]); - l.Add(SectorData[SectorData.Length - 1]); - } - - return l.ToArray(); + var buf = new byte[SectorData.Length + size - ActualDataByteLength]; + SectorData.AsSpan().CopyTo(buf); +// SectorData.AsSpan(start: 0, length: buf.Length - SectorData.Length) +// .CopyTo(buf.AsSpan(start: SectorData.Length)); + buf.AsSpan(start: SectorData.Length).Fill(SectorData[SectorData.Length - 1]); + return buf; } else { diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ControllerDeck.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ControllerDeck.cs new file mode 100644 index 0000000000..acc7c87bae --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ControllerDeck.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using BizHawk.Common.CollectionExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public class DoomControllerDeck + { + public DoomControllerDeck(DoomControllerTypes controllerType, bool player1Present, bool player2Present, bool player3Present, bool player4Present) + { + Definition = new("Doom Demo LMP 1.9 Input Format") { }; + + if (player1Present) Port1 = ControllerCtors[controllerType](1); + if (player2Present) Port2 = ControllerCtors[controllerType](2); + if (player3Present) Port3 = ControllerCtors[controllerType](3); + if (player4Present) Port4 = ControllerCtors[controllerType](4); + + if (player1Present) Definition.BoolButtons.AddRange(Port1.Definition.BoolButtons.ToList()); + if (player2Present) Definition.BoolButtons.AddRange(Port2.Definition.BoolButtons.ToList()); + if (player3Present) Definition.BoolButtons.AddRange(Port3.Definition.BoolButtons.ToList()); + if (player4Present) Definition.BoolButtons.AddRange(Port4.Definition.BoolButtons.ToList()); + + if (player1Present) foreach (var kvp in Port1.Definition.Axes) Definition.Axes.Add(kvp); + if (player2Present) foreach (var kvp in Port2.Definition.Axes) Definition.Axes.Add(kvp); + if (player3Present) foreach (var kvp in Port3.Definition.Axes) Definition.Axes.Add(kvp); + if (player4Present) foreach (var kvp in Port4.Definition.Axes) Definition.Axes.Add(kvp); + + Definition.MakeImmutable(); + } + + public byte ReadPort1(IController c) + => Port1.Read(c); + + public byte ReadPort2(IController c) + => Port2.Read(c); + + public byte ReadPort3(IController c) + => Port3.Read(c); + + public byte ReadPort4(IController c) + => Port4.Read(c); + + public int ReadPot1(IController c, int pot) + => Port1.Read_Pot(c, pot); + + public int ReadPot2(IController c, int pot) + => Port2.Read_Pot(c, pot); + + public int ReadPot3(IController c, int pot) + => Port3.Read_Pot(c, pot); + + public int ReadPot4(IController c, int pot) + => Port4.Read_Pot(c, pot); + + public ControllerDefinition Definition { get; } + + private readonly IPort Port1; + private readonly IPort Port2; + private readonly IPort Port3; + private readonly IPort Port4; + + private static IReadOnlyDictionary> _controllerCtors; + + public static IReadOnlyDictionary> ControllerCtors => _controllerCtors + ??= new Dictionary> + { + [DoomControllerTypes.Doom] = portNum => new DoomController(portNum), + [DoomControllerTypes.Heretic] = portNum => new HereticController(portNum), + [DoomControllerTypes.Hexen] = portNum => new HexenController(portNum), + }; + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IEmulator.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IEmulator.cs new file mode 100644 index 0000000000..83710d3c62 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IEmulator.cs @@ -0,0 +1,235 @@ +using BizHawk.Emulation.Common; +using static BizHawk.Emulation.Cores.Computers.Doom.CInterface; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : IEmulator + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition => _controllerDeck.Definition; + + public bool FrameAdvance(IController controller, bool renderVideo, bool renderAudio) + { + // Declaring inputs + PackedPlayerInput player1Inputs = new PackedPlayerInput(); + PackedPlayerInput player2Inputs = new PackedPlayerInput(); + PackedPlayerInput player3Inputs = new PackedPlayerInput(); + PackedPlayerInput player4Inputs = new PackedPlayerInput(); + + if (_syncSettings.Player1Present) + { + player1Inputs._RunSpeed = _controllerDeck.ReadPot1(controller, 0); + player1Inputs._StrafingSpeed = _controllerDeck.ReadPot1(controller, 1); + player1Inputs._TurningSpeed = _controllerDeck.ReadPot1(controller, 2); + player1Inputs._WeaponSelect = _controllerDeck.ReadPot1(controller, 3); + var actionsBitfield = _controllerDeck.ReadPort1(controller); + player1Inputs._Fire = actionsBitfield & 0b00001; + player1Inputs._Action = (actionsBitfield & 0b00010) >> 1; + player1Inputs._AltWeapon = (actionsBitfield & 0b00100) >> 2; + + // Handling mouse-driven running + int mouseRunningSpeed = _controllerDeck.ReadPot1(controller, 4); + if (_player1LastMouseRunningValue > MOUSE_NO_INPUT) + { + int mouseRunningDelta = _player1LastMouseRunningValue - mouseRunningSpeed; + player1Inputs._RunSpeed += mouseRunningDelta * _syncSettings.MouseRunSensitivity; + if (player1Inputs._RunSpeed > 50) player1Inputs._RunSpeed = 50; + if (player1Inputs._RunSpeed < -50) player1Inputs._RunSpeed = -50; + } + _player1LastMouseRunningValue = mouseRunningSpeed; + + // Handling mouse-driven turning + int mouseTurningSpeed = _controllerDeck.ReadPot1(controller, 5); + if (_player1LastMouseTurningValue > MOUSE_NO_INPUT) + { + int mouseTurningDelta = _player1LastMouseTurningValue - mouseTurningSpeed; + player1Inputs._TurningSpeed += mouseTurningDelta * _syncSettings.MouseTurnSensitivity; + } + _player1LastMouseTurningValue = mouseTurningSpeed; + + // Raven Games + if (_syncSettings.InputFormat is DoomControllerTypes.Heretic or DoomControllerTypes.Hexen) + { + player1Inputs._FlyLook = _controllerDeck.ReadPot1(controller, 6); + player1Inputs._ArtifactUse = _controllerDeck.ReadPot1(controller, 7); + if (_syncSettings.InputFormat is DoomControllerTypes.Hexen) + { + player1Inputs._Jump = (actionsBitfield & 0b01000) >> 3; + player1Inputs._EndPlayer = (actionsBitfield & 0b10000) >> 4; + } + } + } + + if (_syncSettings.Player2Present) + { + player2Inputs._RunSpeed = _controllerDeck.ReadPot2(controller, 0); + player2Inputs._StrafingSpeed = _controllerDeck.ReadPot2(controller, 1); + player2Inputs._TurningSpeed = _controllerDeck.ReadPot2(controller, 2); + player2Inputs._WeaponSelect = _controllerDeck.ReadPot2(controller, 3); + var actionsBitfield = _controllerDeck.ReadPort2(controller); + player2Inputs._Fire = actionsBitfield & 0b00001; + player2Inputs._Action = (actionsBitfield & 0b00010) >> 1; + player2Inputs._AltWeapon = (actionsBitfield & 0b00100) >> 2; + + // Handling mouse-driven running + int mouseRunningSpeed = _controllerDeck.ReadPot2(controller, 4); + if (_player2LastMouseRunningValue > MOUSE_NO_INPUT) + { + int mouseRunningDelta = _player2LastMouseRunningValue - mouseRunningSpeed; + player2Inputs._RunSpeed += mouseRunningDelta * _syncSettings.MouseRunSensitivity; + if (player2Inputs._RunSpeed > 50) player2Inputs._RunSpeed = 50; + if (player2Inputs._RunSpeed < -50) player2Inputs._RunSpeed = -50; + } + _player2LastMouseRunningValue = mouseRunningSpeed; + + // Handling mouse-driven turning + int mouseTurningSpeed = _controllerDeck.ReadPot2(controller, 5); + if (_player2LastMouseTurningValue > MOUSE_NO_INPUT) + { + int mouseTurningDelta = _player2LastMouseTurningValue - mouseTurningSpeed; + player2Inputs._TurningSpeed += mouseTurningDelta * _syncSettings.MouseTurnSensitivity; + } + _player2LastMouseTurningValue = mouseTurningSpeed; + + // Raven Games + if (_syncSettings.InputFormat is DoomControllerTypes.Heretic or DoomControllerTypes.Hexen) + { + player2Inputs._FlyLook = _controllerDeck.ReadPot2(controller, 4); + player2Inputs._ArtifactUse = _controllerDeck.ReadPot2(controller, 5); + if (_syncSettings.InputFormat is DoomControllerTypes.Hexen) + { + player2Inputs._Jump = (actionsBitfield & 0b01000) >> 3; + player2Inputs._EndPlayer = (actionsBitfield & 0b10000) >> 4; + } + } + } + + if (_syncSettings.Player3Present) + { + player3Inputs._RunSpeed = _controllerDeck.ReadPot3(controller, 0); + player3Inputs._StrafingSpeed = _controllerDeck.ReadPot3(controller, 1); + player3Inputs._TurningSpeed = _controllerDeck.ReadPot3(controller, 2); + player3Inputs._WeaponSelect = _controllerDeck.ReadPot3(controller, 3); + var actionsBitfield = _controllerDeck.ReadPort3(controller); + player3Inputs._Fire = actionsBitfield & 0b00001; + player3Inputs._Action = (actionsBitfield & 0b00010) >> 1; + player3Inputs._AltWeapon = (actionsBitfield & 0b00100) >> 2; + + // Handling mouse-driven running + int mouseRunningSpeed = _controllerDeck.ReadPot3(controller, 4); + if (_player3LastMouseRunningValue > MOUSE_NO_INPUT) + { + int mouseRunningDelta = _player3LastMouseRunningValue - mouseRunningSpeed; + player3Inputs._RunSpeed += mouseRunningDelta * _syncSettings.MouseRunSensitivity; + if (player3Inputs._RunSpeed > 50) player3Inputs._RunSpeed = 50; + if (player3Inputs._RunSpeed < -50) player3Inputs._RunSpeed = -50; + } + _player3LastMouseRunningValue = mouseRunningSpeed; + + // Handling mouse-driven turning + int mouseTurningSpeed = _controllerDeck.ReadPot3(controller, 5); + if (_player3LastMouseTurningValue > MOUSE_NO_INPUT) + { + int mouseTurningDelta = _player3LastMouseTurningValue - mouseTurningSpeed; + player3Inputs._TurningSpeed += mouseTurningDelta * _syncSettings.MouseTurnSensitivity; + } + _player3LastMouseTurningValue = mouseTurningSpeed; + + // Raven Games + if (_syncSettings.InputFormat is DoomControllerTypes.Heretic or DoomControllerTypes.Hexen) + { + player3Inputs._FlyLook = _controllerDeck.ReadPot3(controller, 6); + player3Inputs._ArtifactUse = _controllerDeck.ReadPot3(controller, 7); + if (_syncSettings.InputFormat is DoomControllerTypes.Hexen) + { + player3Inputs._Jump = (actionsBitfield & 0b01000) >> 3; + player3Inputs._EndPlayer = (actionsBitfield & 0b10000) >> 4; + } + } + } + + if (_syncSettings.Player4Present) + { + player4Inputs._RunSpeed = _controllerDeck.ReadPot4(controller, 0); + player4Inputs._StrafingSpeed = _controllerDeck.ReadPot4(controller, 1); + player4Inputs._TurningSpeed = _controllerDeck.ReadPot4(controller, 2); + player4Inputs._WeaponSelect = _controllerDeck.ReadPot4(controller, 3); + var actionsBitfield = _controllerDeck.ReadPort4(controller); + player4Inputs._Fire = actionsBitfield & 0b00001; + player4Inputs._Action = (actionsBitfield & 0b00010) >> 1; + player4Inputs._AltWeapon = (actionsBitfield & 0b00100) >> 2; + + // Handling mouse-driven running + int mouseRunningSpeed = _controllerDeck.ReadPot4(controller, 4); + if (_player4LastMouseRunningValue > MOUSE_NO_INPUT) + { + int mouseRunningDelta = _player4LastMouseRunningValue - mouseRunningSpeed; + player4Inputs._RunSpeed += mouseRunningDelta * _syncSettings.MouseRunSensitivity; + if (player4Inputs._RunSpeed > 50) player4Inputs._RunSpeed = 50; + if (player4Inputs._RunSpeed < -50) player4Inputs._RunSpeed = -50; + } + _player4LastMouseRunningValue = mouseRunningSpeed; + + // Handling mouse-driven turning + int mouseTurningSpeed = _controllerDeck.ReadPot4(controller, 5); + if (_player4LastMouseTurningValue > MOUSE_NO_INPUT) + { + int mouseTurningDelta = _player4LastMouseTurningValue - mouseTurningSpeed; + player4Inputs._TurningSpeed += mouseTurningDelta * _syncSettings.MouseTurnSensitivity; + } + _player4LastMouseTurningValue = mouseTurningSpeed; + + // Raven Games + if (_syncSettings.InputFormat is DoomControllerTypes.Heretic or DoomControllerTypes.Hexen) + { + player4Inputs._FlyLook = _controllerDeck.ReadPot4(controller, 4); + player4Inputs._ArtifactUse = _controllerDeck.ReadPot4(controller, 5); + if (_syncSettings.InputFormat is DoomControllerTypes.Hexen) + { + player4Inputs._Jump = (actionsBitfield & 0b01000) >> 3; + player4Inputs._EndPlayer = (actionsBitfield & 0b10000) >> 4; + } + } + } + + PackedRenderInfo renderInfo = new PackedRenderInfo(); + renderInfo._RenderVideo = renderVideo ? 1 : 0; + renderInfo._RenderAudio = renderAudio ? 1 : 0; + renderInfo._PlayerPointOfView = _settings.DisplayPlayer - 1; + + Core.dsda_frame_advance( + ref player1Inputs, + ref player2Inputs, + ref player3Inputs, + ref player4Inputs, + ref renderInfo); + + if (renderVideo) + UpdateVideo(); + + if (renderAudio) + UpdateAudio(); + + Frame++; + + return true; + } + + public int Frame { get; private set; } + + public string SystemId => VSystemID.Raw.Doom; + + public bool DeterministicEmulation => true; + + public void ResetCounters() + { + Frame = 0; + } + + public void Dispose() + { + _elf.Dispose(); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IInputPollable.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IInputPollable.cs new file mode 100644 index 0000000000..1f9795e1ab --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IInputPollable.cs @@ -0,0 +1,15 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : IInputPollable + { + public int LagCount { get; set; } + + public bool IsLagFrame { get; set; } + + public IInputCallbackSystem InputCallbacks => _inputCallbacks; + + private readonly InputCallbackSystem _inputCallbacks = [ ]; + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IMemoryDomains.cs new file mode 100644 index 0000000000..c6fb19ec87 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IMemoryDomains.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA + { + private IMemoryDomains MemoryDomains; + + private void SetupMemoryDomains() + { + var domains = new List + { + new MemoryDomainDelegate( + "Things", + 0x1000000, + MemoryDomain.Endian.Little, + addr => + { + if (addr > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + return Core.dsda_read_memory_array(CInterface.MemoryArrayType.Things, (uint)addr); + }, + null, + 1), + }; + + MemoryDomains = new MemoryDomainList(domains) { }; + ((BasicServiceProvider)ServiceProvider).Register(MemoryDomains); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISettable.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISettable.cs new file mode 100644 index 0000000000..7a36ec0b7b --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISettable.cs @@ -0,0 +1,316 @@ +using System.ComponentModel; + +using BizHawk.Emulation.Common; +using BizHawk.Common; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : ISettable + { + public enum CompatibilityLevel : int + { + [Display(Name = "0 - Doom v1.2")] + C0 = 0, + [Display(Name = "1 - Doom v1.666")] + C1 = 1, + [Display(Name = "2 - Doom v1.9")] + C2 = 2, + [Display(Name = "3 - Ultimate Doom & Doom95")] + C3 = 3, + [Display(Name = "4 - Final Doom")] + C4 = 4, + [Display(Name = "5 - DOSDoom")] + C5 = 5, + [Display(Name = "6 - TASDoom")] + C6 = 6, + [Display(Name = "7 - Boom's Inaccurate Vanilla Compatibility Mode")] + C7 = 7, + [Display(Name = "8 - Boom v2.01")] + C8 = 8, + [Display(Name = "9 - Boom v2.02")] + C9 = 9, + [Display(Name = "10 - LxDoom")] + C10 = 10, + [Display(Name = "11 - MBF")] + C11 = 11, + [Display(Name = "12 - PrBoom v2.03beta")] + C12 = 12, + [Display(Name = "13 - PrBoom v2.1.0")] + C13 = 13, + [Display(Name = "14 - PrBoom v2.1.1 - 2.2.6")] + C14 = 14, + [Display(Name = "15 - PrBoom v2.3.x")] + C15 = 15, + [Display(Name = "16 - PrBoom v2.4.0")] + C16 = 16, + [Display(Name = "17 - PrBoom Latest Default")] + C17 = 17, + [Display(Name = "21 - MBF21")] + C21 = 21 + } + + public enum SkillLevel : int + { + [Display(Name = "1 - I'm too young to die")] + S1 = 1, + [Display(Name = "2 - Hey, not too rough")] + S2 = 2, + [Display(Name = "3 - Hurt me plenty")] + S3 = 3, + [Display(Name = "4 - Ultra-Violence")] + S4 = 4, + [Display(Name = "5 - Nightmare!")] + S5 = 5 + + } + + public enum MultiplayerMode : int + { + [Display(Name = "0 - Single Player / Coop")] + M0 = 0, + [Display(Name = "1 - Deathmatch")] + M1 = 1, + [Display(Name = "2 - Altdeath")] + M2 = 2 + } + + public enum HexenClass : int + { + [Display(Name = "Fighter")] + C1 = 1, + [Display(Name = "Cleric")] + C2 = 2, + [Display(Name = "Mage")] + C3 = 3 + } + + public const int TURBO_AUTO = -1; + + private DoomSettings _settings; + private DoomSyncSettings _syncSettings; + + public DoomSettings GetSettings() + => _settings.Clone(); + + public DoomSyncSettings GetSyncSettings() + => _syncSettings.Clone(); + + public PutSettingsDirtyBits PutSettings(object o) + => PutSettingsDirtyBits.None; + + public PutSettingsDirtyBits PutSyncSettings(DoomSyncSettings o) + { + var ret = DoomSyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; + } + + [CoreSettings] + public class DoomSettings + { + [JsonIgnore] + [DisplayName("Player Point of View")] + [Description("Which of the players' point of view to use during rendering")] + [Range(1, 4)] + [DefaultValue(1)] + [TypeConverter(typeof(ConstrainedIntConverter))] + public int DisplayPlayer { get; set; } + + public DoomSettings() + => SettingsUtil.SetDefaultValues(this); + + public DoomSettings Clone() + => (DoomSettings) MemberwiseClone(); + + public static bool NeedsReboot(DoomSettings x, DoomSettings y) + => false; + } + public PutSettingsDirtyBits PutSettings(DoomSettings o) + { + var ret = DoomSettings.NeedsReboot(_settings, o); + _settings = o; + if (_settings.DisplayPlayer == 1 && !_syncSettings.Player1Present) throw new Exception($"Trying to set display player '{_settings.DisplayPlayer}' but it is not active in this movie."); + if (_settings.DisplayPlayer == 2 && !_syncSettings.Player2Present) throw new Exception($"Trying to set display player '{_settings.DisplayPlayer}' but it is not active in this movie."); + if (_settings.DisplayPlayer == 3 && !_syncSettings.Player3Present) throw new Exception($"Trying to set display player '{_settings.DisplayPlayer}' but it is not active in this movie."); + if (_settings.DisplayPlayer == 4 && !_syncSettings.Player4Present) throw new Exception($"Trying to set display player '{_settings.DisplayPlayer}' but it is not active in this movie."); + return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; + } + + [CoreSettings] + public class DoomSyncSettings + { + [DefaultValue(DoomControllerTypes.Doom)] + [DisplayName("Input Format")] + [Description("The format provided for the players' input.")] + [TypeConverter(typeof(DescribableEnumConverter))] + public DoomControllerTypes InputFormat { get; set; } + + [DisplayName("Player 1 Present")] + [Description("Specifies if player 1 is present")] + [DefaultValue(true)] + public bool Player1Present { get; set; } + + [DisplayName("Player 2 Present")] + [Description("Specifies if player 2 is present")] + [DefaultValue(false)] + public bool Player2Present { get; set; } + + [DisplayName("Player 3 Present")] + [Description("Specifies if player 3 is present")] + [DefaultValue(false)] + public bool Player3Present { get; set; } + + [DisplayName("Player 4 Present")] + [Description("Specifies if player 4 is present")] + [DefaultValue(false)] + public bool Player4Present { get; set; } + + [DisplayName("Compatibility Mode")] + [Description("The version of Doom or its ports that this movie is meant to emulate.")] + [DefaultValue(CompatibilityLevel.C2)] + [TypeConverter(typeof(DescribableEnumConverter))] + public CompatibilityLevel CompatibilityMode { get; set; } + + [DisplayName("Skill Level")] + [Description("Establishes the general difficulty settings.")] + [DefaultValue(SkillLevel.S4)] + [TypeConverter(typeof(DescribableEnumConverter))] + public SkillLevel SkillLevel { get; set; } + + [DisplayName("Multiplayer Mode")] + [Description("Indicates the multiplayer mode")] + [DefaultValue(MultiplayerMode.M0)] + [TypeConverter(typeof(DescribableEnumConverter))] + public MultiplayerMode MultiplayerMode { get; set; } + + [DisplayName("Initial Episode")] + [Description("Selects the initial episode. Use '0' for non-episodic IWads (e.g., DOOM2)")] + [DefaultValue(0)] + public int InitialEpisode { get; set; } + + [DisplayName("Initial Map")] + [Description("Selects the initial map.")] + [DefaultValue(1)] + public int InitialMap { get; set; } + + [DisplayName("Turbo")] + [Description("Modifies the player running / strafing speed [0-255]. -1 means Disabled.")] + [Range(TURBO_AUTO, 255)] + [DefaultValue(TURBO_AUTO)] + [TypeConverter(typeof(ConstrainedIntConverter))] + public int Turbo { get; set; } + + [DisplayName("Fast Monsters")] + [Description("Makes monsters move and attack much faster (overriden to true when playing Nightmare! difficulty)")] + [DefaultValue(false)] + public bool FastMonsters { get; set; } + + [DisplayName("Monsters Respawn")] + [Description("Makes monsters respawn shortly after dying (overriden to true when playing Nightmare! difficulty)")] + [DefaultValue(false)] + public bool MonstersRespawn { get; set; } + + [DisplayName("No Monsters")] + [Description("Removes all monsters from the level.")] + [DefaultValue(false)] + public bool NoMonsters { get; set; } + + [DisplayName("Player 1 Hexen Class")] + [Description("The Hexen class to use for player 1. Has no effect for Doom / Heretic")] + [DefaultValue(HexenClass.C1)] + [TypeConverter(typeof(DescribableEnumConverter))] + public HexenClass Player1Class { get; set; } + + [DisplayName("Player 2 Hexen Class")] + [Description("The Hexen class to use for player 2. Has no effect for Doom / Heretic")] + [DefaultValue(HexenClass.C1)] + [TypeConverter(typeof(DescribableEnumConverter))] + public HexenClass Player2Class { get; set; } + + [DisplayName("Player 3 Hexen Class")] + [Description("The Hexen class to use for player 3. Has no effect for Doom / Heretic")] + [DefaultValue(HexenClass.C1)] + [TypeConverter(typeof(DescribableEnumConverter))] + public HexenClass Player3Class { get; set; } + + [DisplayName("Player 4 Hexen Class")] + [Description("The Hexen class to use for player 4. Has no effect for Doom / Heretic")] + [DefaultValue(HexenClass.C1)] + [TypeConverter(typeof(DescribableEnumConverter))] + public HexenClass Player4Class { get; set; } + + [DisplayName("Chain Episodes")] + [Description("Completing one episode leads to the next without interruption.")] + [DefaultValue(false)] + public bool ChainEpisodes { get; set; } + + [DisplayName("Strict Mode")] + [Description("Sets strict mode restrictions, preventing TAS-only inputs.")] + [DefaultValue(true)] + public bool StrictMode { get; set; } + + [DisplayName("Prevent Level Exit")] + [Description("Level exit triggers won't have an effect. This is useful for debugging / optimizing / botting purposes.")] + [DefaultValue(false)] + public bool PreventLevelExit { get; set; } + + [DisplayName("Prevent Game End")] + [Description("Game end triggers won't have an effect. This is useful for debugging / optimizing / botting purposes.")] + [DefaultValue(false)] + public bool PreventGameEnd { get; set; } + + [DisplayName("Mouse Running Sensitivity")] + [Description("How fast the Doom player will run when using the mouse")] + [DefaultValue(10)] + public int MouseRunSensitivity { get; set; } + + [DisplayName("Mouse Turning Sensitivity")] + [Description("How fast the Doom player will turn when using the mouse")] + [DefaultValue(1)] + public int MouseTurnSensitivity { get; set; } + + public CInterface.InitSettings GetNativeSettings(GameInfo game) + { + return new CInterface.InitSettings + { + _Player1Present = Player1Present ? 1 : 0, + _Player2Present = Player2Present ? 1 : 0, + _Player3Present = Player3Present ? 1 : 0, + _Player4Present = Player4Present ? 1 : 0, + _CompatibilityMode = (int)CompatibilityMode, + _SkillLevel = (int) SkillLevel, + _MultiplayerMode = (int) MultiplayerMode, + _InitialEpisode = InitialEpisode, + _InitialMap = InitialMap, + _Turbo = Turbo, + _FastMonsters = FastMonsters ? 1 : 0, + _MonstersRespawn = MonstersRespawn ? 1 : 0, + _NoMonsters = NoMonsters ? 1 : 0, + _Player1Class = (int) Player1Class, + _Player2Class = (int) Player2Class, + _Player3Class = (int) Player3Class, + _Player4Class = (int) Player4Class, + _ChainEpisodes = ChainEpisodes ? 1 : 0, + _StrictMode = StrictMode ? 1 : 0, + _PreventLevelExit = PreventLevelExit ? 1 : 0, + _PreventGameEnd = PreventGameEnd ? 1 : 0 + // MouseRunSensitivity is handled at Bizhawk level + // MouseTurnSensitivity is handled at Bizhawk level + }; + } + + public DoomSyncSettings Clone() + => (DoomSyncSettings)MemberwiseClone(); + + public DoomSyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + + public static bool NeedsReboot(DoomSyncSettings x, DoomSyncSettings y) + => !DeepEquality.DeepEquals(x, y); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISoundProvider.cs new file mode 100644 index 0000000000..f72f21e9c7 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.ISoundProvider.cs @@ -0,0 +1,56 @@ +using System.Runtime.InteropServices; + +using BizHawk.Emulation.Common; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : ISoundProvider + { + private readonly short[] _samples = new short[6280]; + private int _nsamp; + + public bool CanProvideAsync => false; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + nsamp = _nsamp; + samples = _samples; + _nsamp = 0; + } + + public void DiscardSamples() + => _nsamp = 0; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesAsync(short[] samples) + => throw new InvalidOperationException("Async mode is not supported."); + + private unsafe void UpdateAudio() + { + var src = IntPtr.Zero; + Core.dsda_get_audio(ref _nsamp, ref src); + + if (src != IntPtr.Zero) + { + using (_elf.EnterExit()) + { + Marshal.Copy(src, _samples, 0, _nsamp * 2); + } + } + else + { + _nsamp = 0; + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IStatable.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IStatable.cs new file mode 100644 index 0000000000..e969fcc0f6 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IStatable.cs @@ -0,0 +1,48 @@ +using System.IO; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : IStatable + { + public bool AvoidRewind => false; + + public void LoadStateBinary(BinaryReader reader) + { + _elf.LoadStateBinary(reader); + + // Getting last mouse positions + _player1LastMouseRunningValue = reader.ReadInt32(); + _player1LastMouseTurningValue = reader.ReadInt32(); + _player2LastMouseRunningValue = reader.ReadInt32(); + _player2LastMouseTurningValue = reader.ReadInt32(); + _player3LastMouseRunningValue = reader.ReadInt32(); + _player3LastMouseTurningValue = reader.ReadInt32(); + _player4LastMouseRunningValue = reader.ReadInt32(); + _player4LastMouseTurningValue = reader.ReadInt32(); + + Frame = reader.ReadInt32(); + // any managed pointers that we sent to the core need to be resent now! + //Core.stella_set_input_callback(_inputCallback); + UpdateVideo(); + } + + public void SaveStateBinary(BinaryWriter writer) + { + _elf.SaveStateBinary(writer); + + // Writing last mouse positions + writer.Write(_player1LastMouseRunningValue); + writer.Write(_player1LastMouseTurningValue); + writer.Write(_player2LastMouseRunningValue); + writer.Write(_player2LastMouseTurningValue); + writer.Write(_player3LastMouseRunningValue); + writer.Write(_player3LastMouseTurningValue); + writer.Write(_player4LastMouseRunningValue); + writer.Write(_player4LastMouseTurningValue); + + writer.Write(Frame); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IVideoProvider.cs new file mode 100644 index 0000000000..46fdc8f4a1 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IVideoProvider.cs @@ -0,0 +1,52 @@ +using BizHawk.Emulation.Common; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public partial class DSDA : IVideoProvider + { + public int[] GetVideoBuffer() => _vidBuff; + + public int VirtualWidth => BufferWidth; + + public int VirtualHeight => BufferHeight; + + public int PaletteSize { get; private set; } + + public int BufferWidth { get; private set; } + + public int BufferHeight { get; private set; } + + public int BackgroundColor => unchecked((int)0xff000000); + + public int VsyncNumerator { get; } + + public int VsyncDenominator { get; } + + private int[] _palBuffer = [ ]; + private int[] _vidBuff = [ ]; + + private unsafe void UpdateVideo() + { + using (_elf.EnterExit()) + { + var videoBufferSrc = IntPtr.Zero; + var palletteBufferSrc = IntPtr.Zero; + Core.dsda_get_video(out var width, out var height, out var pitch, ref videoBufferSrc, out var paletteSize, ref palletteBufferSrc); + + // Handling pallette buffer + PaletteSize = paletteSize; + if (_palBuffer.Length < PaletteSize) _palBuffer = new int[PaletteSize]; + var paletteBuffer = (int*) palletteBufferSrc.ToPointer(); + for (var i = 0; i < _palBuffer.Length; i++) _palBuffer[i] = paletteBuffer[i]; + + // Handling video buffer + BufferWidth = width; + BufferHeight = height; + if (_vidBuff.Length < BufferWidth * BufferHeight) _vidBuff = new int[BufferWidth * BufferHeight]; + var videoBuffer = (byte*) videoBufferSrc.ToPointer(); + for (var i = 0; i < _vidBuff.Length; i++) _vidBuff[i] = _palBuffer[videoBuffer[i]]; + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.cs new file mode 100644 index 0000000000..332b6562a3 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.cs @@ -0,0 +1,218 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +using BizHawk.BizInvoke; +using BizHawk.Common; +using BizHawk.Common.PathExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Properties; +using BizHawk.Emulation.Cores.Waterbox; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + [PortedCore( + name: CoreNames.DSDA, + author: "The DSDA Team", + portedVersion: "0.28.2 (fe0dfa0)", + portedUrl: "https://github.com/kraflab/dsda-doom")] + [ServiceNotApplicable(typeof(ISaveRam))] + public partial class DSDA : IRomInfo + { + [CoreConstructor(VSystemID.Raw.Doom)] + public DSDA(CoreLoadParameters lp) + { + var ser = new BasicServiceProvider(this); + ServiceProvider = ser; + _syncSettings = lp.SyncSettings ?? new DoomSyncSettings(); + _settings = lp.Settings ?? new DoomSettings(); + _controllerDeck = new DoomControllerDeck(_syncSettings.InputFormat, _syncSettings.Player1Present, _syncSettings.Player2Present, _syncSettings.Player3Present, _syncSettings.Player4Present); + _loadCallback = LoadCallback; + + // Gathering information for the rest of the wads + _wadFiles = lp.Roms; + + // Checking for correct IWAD configuration + bool foundIWAD = false; + string IWADName = ""; + foreach (var wadFile in _wadFiles) + { + bool recognized = false; + if (wadFile.RomData is [ (byte) 'I', (byte) 'W', (byte) 'A', (byte) 'D', .. ]) + { + // Check not more than one IWAD is provided + if (foundIWAD) throw new Exception($"More than one IWAD provided. Trying to load '{wadFile.RomPath}', but IWAD '{IWADName}' was already provided"); + IWADName = wadFile.RomPath; + foundIWAD = true; + recognized = true; + } + else if (wadFile.RomData is [ (byte) 'P', (byte) 'W', (byte) 'A', (byte) 'D', .. ]) + { + recognized = true; + } + if (!recognized) throw new Exception($"Unrecognized WAD provided: '{wadFile.RomPath}' has non-standard header."); + } + + // Check at least one IWAD was provided + if (!foundIWAD) throw new Exception($"No IWAD was provided"); + + // Getting dsda-doom.wad -- required by DSDA + _dsdaWadFileData = Zstd.DecompressZstdStream(new MemoryStream(Resources.DSDA_DOOM_WAD.Value)).ToArray(); + + // Getting sum of wad sizes for the accurate calculation of the invisible heap + uint totalWadSize = (uint)_dsdaWadFileData.Length; + foreach (var wadFile in _wadFiles) totalWadSize += (uint) wadFile.FileData.Length; + uint totalWadSizeKb = (totalWadSize / 1024) + 1; + Console.WriteLine("Reserving {0}kb for WAD file memory", totalWadSizeKb); + + _elf = new WaterboxHost(new WaterboxOptions + { + Path = PathUtils.DllDirectoryPath, + Filename = "dsda.wbx", + SbrkHeapSizeKB = 64 * 1024, // This core loads quite a bunch of things on global mem -- reserve enough memory + SealedHeapSizeKB = 4 * 1024, + InvisibleHeapSizeKB = totalWadSizeKb + 4 * 1024, // Make sure there's enough space for the wads + PlainHeapSizeKB = 4 * 1024, + MmapHeapSizeKB = 1024 * 1024 * 2, // Allow the game to malloc quite a lot of objects to support those big wads + SkipCoreConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck), + SkipMemoryConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck), + }); + + try + { + var callingConventionAdapter = CallingConventionAdapters.MakeWaterbox( + [ + _loadCallback + ], _elf); + + using (_elf.EnterExit()) + { + Core = BizInvoker.GetInvoker(_elf, _elf, callingConventionAdapter); + + // Adding dsda-doom wad file + Core.dsda_add_wad_file(_dsdaWadFileName, _dsdaWadFileData.Length, _loadCallback); + + // Adding rom files + foreach (var wadFile in _wadFiles) + { + var loadWadResult = Core.dsda_add_wad_file(wadFile.RomPath, wadFile.RomData.Length, _loadCallback); + if (!loadWadResult) throw new Exception($"Could not load WAD file: '{wadFile.RomPath}'"); + } + + var initSettings = _syncSettings.GetNativeSettings(lp.Game); + var initResult = Core.dsda_init(ref initSettings); + if (!initResult) throw new Exception($"{nameof(Core.dsda_init)}() failed"); + + int fps = 35; + VsyncNumerator = fps; + VsyncDenominator = 1; + + RomDetails = $"{lp.Game.Name}\r\n{SHA1Checksum.ComputePrefixedHex(_wadFiles[0].RomData)}\r\n{MD5Checksum.ComputePrefixedHex(_wadFiles[0].RomData)}"; + + _elf.Seal(); + } + + // pull the default video size from the core + UpdateVideo(); + + // Registering memory domains + SetupMemoryDomains(); + } + catch + { + Dispose(); + throw; + } + } + + // Remembering mouse position + private const int MOUSE_NO_INPUT = -65535; + private int _player1LastMouseRunningValue = MOUSE_NO_INPUT; + private int _player1LastMouseTurningValue = MOUSE_NO_INPUT; + private int _player2LastMouseRunningValue = MOUSE_NO_INPUT; + private int _player2LastMouseTurningValue = MOUSE_NO_INPUT; + private int _player3LastMouseRunningValue = MOUSE_NO_INPUT; + private int _player3LastMouseTurningValue = MOUSE_NO_INPUT; + private int _player4LastMouseRunningValue = MOUSE_NO_INPUT; + private int _player4LastMouseTurningValue = MOUSE_NO_INPUT; + + // IRegionable + public DisplayType Region { get; } + + // IRomInfo + public string RomDetails { get; } + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly CInterface.load_archive_cb _loadCallback; + + private readonly string _dsdaWadFileName = "dsda-doom.wad"; + private readonly byte[] _dsdaWadFileData; + private List _wadFiles; + + private readonly CInterface Core; + private readonly WaterboxHost _elf; + + private readonly DoomControllerDeck _controllerDeck; + + /// + /// core callback for file loading + /// + /// string identifying file to be loaded + /// buffer to load file to + /// maximum length buffer can hold + /// actual size loaded, or 0 on failure + private int LoadCallback(string filename, IntPtr buffer, int maxsize) + { + byte[] srcdata = null; + + if (buffer == IntPtr.Zero) + { + Console.WriteLine("Couldn't satisfy firmware request {0} because buffer == NULL", filename); + return 0; + } + + if (filename == _dsdaWadFileName) + { + if (_dsdaWadFileData == null) + { + Console.WriteLine("Could not read from 'dsda-doom.wad'. File must be missing from the Resources folder."); + return 0; + } + srcdata = _dsdaWadFileData; + } + + foreach (var wadFile in _wadFiles) + { + if (filename == wadFile.RomPath) + { + if (wadFile.FileData == null) + { + Console.WriteLine("Could not read from WAD file '{0}'", filename); + return 0; + } + srcdata = wadFile.FileData; + } + } + + if (srcdata != null) + { + if (srcdata.Length > maxsize) + { + Console.WriteLine("Couldn't satisfy firmware request {0} because {1} > {2}", filename, srcdata.Length, maxsize); + return 0; + } + else + { + Console.WriteLine("Copying Data from " + srcdata + " to " + buffer + " Size: " + srcdata.Length); + Marshal.Copy(srcdata, 0, buffer, srcdata.Length); + Console.WriteLine("Firmware request {0} satisfied at size {1}", filename, srcdata.Length); + return srcdata.Length; + } + } + else + { + throw new InvalidOperationException($"Unknown error processing file '{filename}'"); + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/DSDAControllers.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDAControllers.cs new file mode 100644 index 0000000000..b6e99baff2 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/DSDAControllers.cs @@ -0,0 +1,378 @@ +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public enum DoomControllerTypes + { + Doom, + Heretic, + Hexen + } + + public interface IPort + { + byte Read(IController c); + + int Read_Pot(IController c, int pot); + + ControllerDefinition Definition { get; } + + int PortNum { get; } + } + + public class DoomController : IPort + { + public DoomController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition("Doom Input Format") + { + BoolButtons = BaseDefinition + .Select(b => $"P{PortNum} " + b) + .ToList() + }.AddAxis($"P{PortNum} Run Speed", (-50).RangeTo(50), 0) + .AddAxis($"P{PortNum} Strafing Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Turning Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Weapon Select", (0).RangeTo(7), 0) + .AddAxis($"P{PortNum} Mouse Running", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Mouse Turning", (-128).RangeTo(127), 0) + .MakeImmutable(); + } + + public int PortNum { get; } + + public ControllerDefinition Definition { get; } + + private static readonly string[] BaseDefinition = + [ + "Fire", + "Action", + "Alt Weapon", + "Key Forward", + "Key Backward", + "Key Turn Left", + "Key Turn Right", + "Key Strafe Left", + "Key Strafe Right", + "Key Shift Run", + "Key Weapon Select 1", + "Key Weapon Select 2", + "Key Weapon Select 3", + "Key Weapon Select 4", + "Key Weapon Select 5", + "Key Weapon Select 6", + "Key Weapon Select 7", + ]; + + public byte Read(IController c) + { + byte result = 0; + + if (c.IsPressed($"P{PortNum} Fire")) { result |= 0b0001; } + if (c.IsPressed($"P{PortNum} Action")) { result |= 0b0010; } + if (c.IsPressed($"P{PortNum} Alt Weapon")) { result |= 0b0100; } + + return result; + } + + public int Read_Pot(IController c, int pot) + { + int x = c.AxisValue(Definition.Axes[pot]); + + // Handling running keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Run Speed") + { + if (c.IsPressed($"P{PortNum} Key Forward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 50 : 25; + } + + if (c.IsPressed($"P{PortNum} Key Backward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -50 : -25; + } + } + + // Handling strafing keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Strafing Speed") + { + if (c.IsPressed($"P{PortNum} Key Strafe Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 40 : 24; + } + + if (c.IsPressed($"P{PortNum} Key Strafe Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -40 : -24; + } + } + + // Handling turning keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Turning Speed") + { + if (c.IsPressed($"P{PortNum} Key Turn Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 5 : 2; + } + + if (c.IsPressed($"P{PortNum} Key Turn Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -5 : -2; + } + } + + // Handling weapon select keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Weapon Select") + { + if (c.IsPressed($"P{PortNum} Key Weapon Select 1")) x = 1; + if (c.IsPressed($"P{PortNum} Key Weapon Select 2")) x = 2; + if (c.IsPressed($"P{PortNum} Key Weapon Select 3")) x = 3; + if (c.IsPressed($"P{PortNum} Key Weapon Select 4")) x = 4; + if (c.IsPressed($"P{PortNum} Key Weapon Select 5")) x = 5; + if (c.IsPressed($"P{PortNum} Key Weapon Select 6")) x = 6; + if (c.IsPressed($"P{PortNum} Key Weapon Select 7")) x = 7; + } + + return x; + } + } + + public class HereticController : IPort + { + public HereticController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition("Heretic Input Format") + { + BoolButtons = BaseDefinition + .Select(b => $"P{PortNum} " + b) + .ToList() + }.AddAxis($"P{PortNum} Run Speed", (-50).RangeTo(50), 0) + .AddAxis($"P{PortNum} Strafing Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Turning Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Weapon Select", (0).RangeTo(7), 0) + .AddAxis($"P{PortNum} Mouse Running", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Mouse Turning", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Fly / Look", (-7).RangeTo(7), 0) + .AddAxis($"P{PortNum} Use Artifact", (0).RangeTo(10), 0) + .MakeImmutable(); + } + + public int PortNum { get; } + + public ControllerDefinition Definition { get; } + + private static readonly string[] BaseDefinition = + [ + "Fire", + "Action", + "Alt Weapon", + "Key Forward", + "Key Backward", + "Key Turn Left", + "Key Turn Right", + "Key Strafe Left", + "Key Strafe Right", + "Key Shift Run", + "Key Weapon Select 1", + "Key Weapon Select 2", + "Key Weapon Select 3", + "Key Weapon Select 4", + "Key Weapon Select 5", + "Key Weapon Select 6", + "Key Weapon Select 7", + ]; + + public byte Read(IController c) + { + byte result = 0; + + if (c.IsPressed($"P{PortNum} Fire")) { result |= 0b0001; } + if (c.IsPressed($"P{PortNum} Action")) { result |= 0b0010; } + if (c.IsPressed($"P{PortNum} Alt Weapon")) { result |= 0b0100; } + + return result; + } + + public int Read_Pot(IController c, int pot) + { + int x = c.AxisValue(Definition.Axes[pot]); + + // Handling running keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Run Speed") + { + if (c.IsPressed($"P{PortNum} Key Forward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 50 : 25; + } + + if (c.IsPressed($"P{PortNum} Key Backward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -50 : -25; + } + } + + // Handling strafing keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Strafing Speed") + { + if (c.IsPressed($"P{PortNum} Key Strafe Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 40 : 24; + } + + if (c.IsPressed($"P{PortNum} Key Strafe Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -40 : -24; + } + } + + // Handling turning keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Turning Speed") + { + if (c.IsPressed($"P{PortNum} Key Turn Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 5 : 2; + } + + if (c.IsPressed($"P{PortNum} Key Turn Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -5 : -2; + } + } + + // Handling weapon select keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Weapon Select") + { + if (c.IsPressed($"P{PortNum} Key Weapon Select 1")) x = 1; + if (c.IsPressed($"P{PortNum} Key Weapon Select 2")) x = 2; + if (c.IsPressed($"P{PortNum} Key Weapon Select 3")) x = 3; + if (c.IsPressed($"P{PortNum} Key Weapon Select 4")) x = 4; + if (c.IsPressed($"P{PortNum} Key Weapon Select 5")) x = 5; + if (c.IsPressed($"P{PortNum} Key Weapon Select 6")) x = 6; + if (c.IsPressed($"P{PortNum} Key Weapon Select 7")) x = 7; + } + + return x; + } + } + + public class HexenController : IPort + { + public HexenController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition("Hexen Input Format") + { + BoolButtons = BaseDefinition + .Select(b => $"P{PortNum} " + b) + .ToList() + }.AddAxis($"P{PortNum} Run Speed", (-50).RangeTo(50), 0) + .AddAxis($"P{PortNum} Strafing Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Turning Speed", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Weapon Select", (1).RangeTo(4), 0) + .AddAxis($"P{PortNum} Mouse Running", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Mouse Turning", (-128).RangeTo(127), 0) + .AddAxis($"P{PortNum} Fly / Look", (-7).RangeTo(7), 0) + .AddAxis($"P{PortNum} Use Artifact", (0).RangeTo(33), 0) + .MakeImmutable(); + } + + public int PortNum { get; } + + public ControllerDefinition Definition { get; } + + private static readonly string[] BaseDefinition = + [ + "Fire", + "Action", + "Alt Weapon", + "Jump", + "End Player", + "Key Forward", + "Key Backward", + "Key Turn Left", + "Key Turn Right", + "Key Strafe Left", + "Key Strafe Right", + "Key Shift Run", + "Key Weapon Select 1", + "Key Weapon Select 2", + "Key Weapon Select 3", + "Key Weapon Select 4" + ]; + + public byte Read(IController c) + { + byte result = 0; + + if (c.IsPressed($"P{PortNum} Fire")) { result |= 0b00001; } + if (c.IsPressed($"P{PortNum} Action")) { result |= 0b00010; } + if (c.IsPressed($"P{PortNum} Alt Weapon")) { result |= 0b00100; } + if (c.IsPressed($"P{PortNum} Jump")) { result |= 0b01000; } + if (c.IsPressed($"P{PortNum} End Player")) { result |= 0b10000; } + + return result; + } + + public int Read_Pot(IController c, int pot) + { + int x = c.AxisValue(Definition.Axes[pot]); + + // Handling running keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Run Speed") + { + if (c.IsPressed($"P{PortNum} Key Forward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 50 : 25; + } + + if (c.IsPressed($"P{PortNum} Key Backward")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -50 : -25; + } + } + + // Handling strafing keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Strafing Speed") + { + if (c.IsPressed($"P{PortNum} Key Strafe Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 40 : 24; + } + + if (c.IsPressed($"P{PortNum} Key Strafe Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -40 : -24; + } + } + + // Handling turning keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Turning Speed") + { + if (c.IsPressed($"P{PortNum} Key Turn Left")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? 5 : 2; + } + + if (c.IsPressed($"P{PortNum} Key Turn Right")) + { + x = c.IsPressed($"P{PortNum} Key Shift Run") ? -5 : -2; + } + } + + // Handling weapon select keys overriding axes values + if (Definition.Axes[pot] == $"P{PortNum} Weapon Select") + { + if (c.IsPressed($"P{PortNum} Key Weapon Select 1")) x = 1; + if (c.IsPressed($"P{PortNum} Key Weapon Select 2")) x = 2; + if (c.IsPressed($"P{PortNum} Key Weapon Select 3")) x = 3; + if (c.IsPressed($"P{PortNum} Key Weapon Select 4")) x = 4; + } + + return x; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/Doom/LibDSDA.cs b/src/BizHawk.Emulation.Cores/Computers/Doom/LibDSDA.cs new file mode 100644 index 0000000000..8e38d08ec4 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Doom/LibDSDA.cs @@ -0,0 +1,101 @@ +using System.Runtime.InteropServices; + +using BizHawk.BizInvoke; + +namespace BizHawk.Emulation.Cores.Computers.Doom +{ + public abstract class CInterface + { + public enum MemoryArrayType : int + { + Things = 0, + Lines = 1, + Sectors = 2 + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int load_archive_cb(string filename, IntPtr buffer, int maxsize); + + [StructLayout(LayoutKind.Sequential)] + public struct InitSettings + { + public int _Player1Present; + public int _Player2Present; + public int _Player3Present; + public int _Player4Present; + public int _CompatibilityMode; + public int _SkillLevel; + public int _MultiplayerMode; + public int _InitialEpisode; + public int _InitialMap; + public int _Turbo; + public int _FastMonsters; + public int _MonstersRespawn; + public int _NoMonsters; + public int _Player1Class; + public int _Player2Class; + public int _Player3Class; + public int _Player4Class; + public int _ChainEpisodes; + public int _StrictMode; + public int _PreventLevelExit; + public int _PreventGameEnd; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PackedPlayerInput + { + public int _RunSpeed; + public int _StrafingSpeed; + public int _TurningSpeed; + public int _WeaponSelect; + public int _Fire; + public int _Action; + public int _AltWeapon; + + // Hexen + Heretic (Raven Games) + public int _FlyLook; + public int _ArtifactUse; + + // Hexen only + public int _Jump; + public int _EndPlayer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PackedRenderInfo + { + public int _RenderVideo; + public int _RenderAudio; + public int _PlayerPointOfView; + } + + [BizImport(CallingConvention.Cdecl)] + public abstract void dsda_get_audio(ref int n, ref IntPtr buffer); + + [BizImport(CallingConvention.Cdecl)] + public abstract bool dsda_init(ref InitSettings settings); + + [BizImport(CallingConvention.Cdecl)] + public abstract void dsda_frame_advance( + ref PackedPlayerInput player1Inputs, + ref PackedPlayerInput player2Inputs, + ref PackedPlayerInput player3Inputs, + ref PackedPlayerInput player4Inputs, + ref PackedRenderInfo renderInfo); + + [BizImport(CallingConvention.Cdecl)] + public abstract void dsda_get_video(out int w, out int h, out int pitch, ref IntPtr buffer, out int palSize, ref IntPtr palBuffer); + + [BizImport(CallingConvention.Cdecl)] + public abstract bool dsda_add_wad_file( + string fileName, + int fileSize, + load_archive_cb feload_archive_cb); + + [BizImport(CallingConvention.Cdecl)] + public abstract byte dsda_read_memory_array( + MemoryArrayType type, + uint addr); + } +} diff --git a/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs index 70a7ee5782..d65c672063 100644 --- a/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs +++ b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; + +using BizHawk.Common.CollectionExtensions; using BizHawk.Common.StringExtensions; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum @@ -118,7 +120,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (!sec.ContainsMultipleWeakSectors) { byte[] origData = sec.SectorData.ToArray(); - List data = new List(); + List data = new(); //TODO pretty sure the length and indices here are known in advance and this can just be an array --yoshi for (int m = 0; m < 3; m++) { for (int i = 0; i < 512; i++) @@ -442,7 +444,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // we are going to create a total of 5 weak sector copies // keeping the original copy byte[] origData = sec.SectorData.ToArray(); - List data = new List(); + List data = new(); //TODO pretty sure the length and indices here are known in advance and this can just be an array --yoshi //Random rnd = new Random(); for (int i = 0; i < 6; i++) @@ -594,19 +596,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// (including any multiple weak/random data) /// public virtual byte[] TrackSectorData - { - get - { - List list = new List(); - - foreach (var sec in Sectors) - { - list.AddRange(sec.ActualData); - } - - return list.ToArray(); - } - } + => CollectionExtensions.ConcatArrays(Sectors.Select(static sec => sec.ActualData).ToArray()); } public class Sector @@ -655,15 +645,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum int size = 0x80 << SectorSize; if (size > ActualDataByteLength) { - List l = new List(); - l.AddRange(SectorData); - for (int i = 0; i < size - ActualDataByteLength; i++) - { - //l.Add(SectorData[i]); - l.Add(SectorData[SectorData.Length - 1]); - } - - return l.ToArray(); + var buf = new byte[SectorData.Length + size - ActualDataByteLength]; + SectorData.AsSpan().CopyTo(buf); +// SectorData.AsSpan(start: 0, length: buf.Length - SectorData.Length) +// .CopyTo(buf.AsSpan(start: SectorData.Length)); + buf.AsSpan(start: SectorData.Length).Fill(SectorData[SectorData.Length - 1]); + return buf; } return SectorData; diff --git a/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs index 771cf24683..27c3671124 100644 --- a/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs +++ b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/PZX/PzxConverter.cs @@ -232,7 +232,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum List s0 = new List(); List s1 = new List(); - List dData = new List(); uint initPulseLevel = 1; int dCount = 1; @@ -269,12 +268,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum s1.Add(s); } - for (int i = 0; i < Math.Ceiling((decimal)dCount / 8); i++) - { - var buff = b[pos++]; - dData.Add(buff); - } - + var dData = b.AsSpan(start: pos, length: (dCount + 7) >> 3/* == `ceil(dCount/8)` */); + pos += dData.Length; foreach (var by in dData) { for (int i = 7; i >= 0; i--) @@ -307,8 +302,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bLevel = !bLevel; t.DataLevels.Add(bLevel); } - - dData.Clear(); } // convert to tape block diff --git a/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.RomHeuristics.cs b/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.RomHeuristics.cs index 72ef083e68..495e543a46 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.RomHeuristics.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.RomHeuristics.cs @@ -232,12 +232,12 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 return "UNKNOWN"; } - private static bool IsProbablySC(IList rom) + private static bool IsProbablySC(ReadOnlySpan rom) { // We assume a Superchip cart contains the same bytes for its entire // RAM area; obviously this test will fail if it doesn't // The RAM area will be the first 256 bytes of each 4K bank - var numBanks = rom.Count / 4096; + var numBanks = rom.Length / 4096; for (var i = 0; i < numBanks; i++) { var first = rom[i * 4096]; @@ -273,15 +273,12 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 }); } - private static bool IsProbably4A50(IList rom) + private static bool IsProbably4A50(ReadOnlySpan rom) { // 4A50 carts store address $4A50 at the NMI vector, which // in this scheme is always in the last page of ROM at // $1FFA - $1FFB (at least this is true in rev 1 of the format) - if (rom[rom.Count - 6] == 0x50 && rom[rom.Count - 5] == 0x4A) - { - return true; - } + if (rom is [ .., 0x50, 0x4A, _, _, _, _ ]) return true; // Program starts at $1Fxx with NOP $6Exx or NOP $6Fxx? if ((rom[0xFFFD] & 0x1F) is 0x1F diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NDS/MelonDS.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NDS/MelonDS.cs index a7e3f83f6a..03634ca536 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NDS/MelonDS.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NDS/MelonDS.cs @@ -367,7 +367,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS } } - private static (ulong Full, uint Upper, uint Lower) GetDSiTitleId(IReadOnlyList file) + private static (ulong Full, uint Upper, uint Lower) GetDSiTitleId(ReadOnlySpan file) { ulong titleId = 0; for (var i = 0; i < 8; i++) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper244.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper244.cs index 146f87e441..e43c9a3b9a 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper244.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper244.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using BizHawk.Common; +using BizHawk.Common; namespace BizHawk.Emulation.Cores.Nintendo.NES { @@ -18,24 +17,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES return true; } - private readonly List> prg_perm = new List> - { - new List { 0, 1, 2, 3, }, - new List { 3, 2, 1, 0, }, - new List { 0, 2, 1, 3, }, - new List { 3, 1, 2, 0, }, + private readonly byte[,] prg_perm = { + { 0, 1, 2, 3 }, + { 3, 2, 1, 0 }, + { 0, 2, 1, 3 }, + { 3, 1, 2, 0 }, }; - private readonly List> chr_perm = new List> - { - new List { 0, 1, 2, 3, 4, 5, 6, 7, }, - new List { 0, 2, 1, 3, 4, 6, 5, 7, }, - new List { 0, 1, 4, 5, 2, 3, 6, 7, }, - new List { 0, 4, 1, 5, 2, 6, 3, 7, }, - new List { 0, 4, 2, 6, 1, 5, 3, 7, }, - new List { 0, 2, 4, 6, 1, 3, 5, 7, }, - new List { 7, 6, 5, 4, 3, 2, 1, 0, }, - new List { 7, 6, 5, 4, 3, 2, 1, 0, } + private readonly byte[,] chr_perm = { + { 0, 1, 2, 3, 4, 5, 6, 7 }, + { 0, 2, 1, 3, 4, 6, 5, 7 }, + { 0, 1, 4, 5, 2, 3, 6, 7 }, + { 0, 4, 1, 5, 2, 6, 3, 7 }, + { 0, 4, 2, 6, 1, 5, 3, 7 }, + { 0, 2, 4, 6, 1, 3, 5, 7 }, + { 7, 6, 5, 4, 3, 2, 1, 0 }, + { 7, 6, 5, 4, 3, 2, 1, 0 }, }; private int _chrRegister = 0; @@ -67,11 +64,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { if ((value & 0x08) > 0) { - _chrRegister = chr_perm[(value >> 4) & 7][value & 7]; + _chrRegister = chr_perm[(value >> 4) & 7, value & 7]; } else { - _prgRegister = prg_perm[(value >> 4) & 3][value & 3]; + _prgRegister = prg_perm[(value >> 4) & 3, value & 3]; } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDisassembler.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDisassembler.cs index cdd4aa5b56..acf8505e70 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDisassembler.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDisassembler.cs @@ -15,7 +15,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx } } - public string PCRegisterName => "M68K PC"; + public string PCRegisterName { get; } public IEnumerable AvailableCpus { get; } = [ "M68000" ]; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs index 72eef4580b..030a4a03a4 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs @@ -48,6 +48,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx _ => throw new InvalidOperationException("Invalid system id") }; } + PCRegisterName = SystemId is VSystemID.Raw.GEN ? "M68K PC" : "Z80 pc"; + if (SystemId is not VSystemID.Raw.GEN) ((BasicServiceProvider) ServiceProvider).Unregister(); // three or six button? // http://www.sega-16.com/forum/showthread.php?4398-Forgotten-Worlds-giving-you-GAME-OVER-immediately-Fix-inside&highlight=forgotten%20worlds diff --git a/src/BizHawk.Emulation.Cores/CoreNames.cs b/src/BizHawk.Emulation.Cores/CoreNames.cs index 560241f7c4..6252f77420 100644 --- a/src/BizHawk.Emulation.Cores/CoreNames.cs +++ b/src/BizHawk.Emulation.Cores/CoreNames.cs @@ -21,6 +21,7 @@ namespace BizHawk.Emulation.Cores public const string CPCHawk = "CPCHawk"; public const string Cygne = "Cygne/Mednafen"; public const string DOSBox = "DOSBox"; + public const string DSDA = "DSDA-Doom"; public const string Emu83 = "Emu83"; public const string Encore = "Encore"; public const string Faust = "Faust"; diff --git a/src/BizHawk.Emulation.Cores/FileID.cs b/src/BizHawk.Emulation.Cores/FileID.cs index f4aef542fb..c2e5339373 100644 --- a/src/BizHawk.Emulation.Cores/FileID.cs +++ b/src/BizHawk.Emulation.Cores/FileID.cs @@ -47,6 +47,9 @@ namespace BizHawk.Emulation.Cores JAD, SBI, M3U, + // Doom IWad/PWad File Types + WAD, + //audio codec formats WAV, APE, MPC, FLAC, MP3, //can't be ID'd very readily.. @@ -376,6 +379,9 @@ namespace BizHawk.Emulation.Cores // DOS Floppy Disks { "IMA", new ExtensionInfo(FileIDType.DOS_FLOPPY, null ) }, { "IMG", new ExtensionInfo(FileIDType.DOS_FLOPPY, null ) }, + + // Doom IWad / PWad + { "WAD", new ExtensionInfo(FileIDType.WAD, null ) }, //for now { "ROM", new ExtensionInfo(FileIDType.Multiple, null ) }, //could be MSX too diff --git a/src/BizHawk.Emulation.Cores/Properties/Resources.cs b/src/BizHawk.Emulation.Cores/Properties/Resources.cs index c916f5b566..4ce3a973af 100644 --- a/src/BizHawk.Emulation.Cores/Properties/Resources.cs +++ b/src/BizHawk.Emulation.Cores/Properties/Resources.cs @@ -30,5 +30,6 @@ namespace BizHawk.Emulation.Cores.Properties { internal static readonly Lazy JAGUAR_KSERIES_ROM = new(() => ReadEmbeddedByteArray("JAGUAR_KSERIES.ROM.zst")); internal static readonly Lazy JAGUAR_MSERIES_ROM = new(() => ReadEmbeddedByteArray("JAGUAR_MSERIES.ROM.zst")); internal static readonly Lazy JAGUAR_MEMTRACK_ROM = new(() => ReadEmbeddedByteArray("JAGUAR_MEMTRACK.ROM.zst")); + internal static readonly Lazy DSDA_DOOM_WAD = new(() => ReadEmbeddedByteArray("dsda-doom.zst")); } } diff --git a/src/BizHawk.Emulation.Cores/Resources/dsda-doom.zst b/src/BizHawk.Emulation.Cores/Resources/dsda-doom.zst new file mode 100644 index 0000000000..a358c7a2a8 Binary files /dev/null and b/src/BizHawk.Emulation.Cores/Resources/dsda-doom.zst differ diff --git a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs index 6aa3ff9601..e627f88878 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscHasher.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Text; using BizHawk.Common; using BizHawk.Common.BufferExtensions; +using BizHawk.Common.CollectionExtensions; namespace BizHawk.Emulation.DiscSystem { @@ -137,7 +138,7 @@ namespace BizHawk.Emulation.DiscSystem { const string _jaguarHeader = "ATARI APPROVED DATA HEADER ATRI"; const string _jaguarBSHeader = "TARA IPARPVODED TA AEHDAREA RT"; - var buffer = new List(); + List> bitsToHash = new(); var buf2352 = new byte[2352]; // find the boot track header @@ -199,7 +200,7 @@ namespace BizHawk.Emulation.DiscSystem EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); } - buffer.AddRange(new ArraySegment(buf2352, bootOff, Math.Min(2352 - bootOff, bootLen))); + bitsToHash.Add(new(buf2352, offset: bootOff, count: Math.Min(2352 - bootOff, bootLen))); bootLen -= 2352 - bootOff; while (bootLen > 0) @@ -211,11 +212,11 @@ namespace BizHawk.Emulation.DiscSystem EndiannessUtils.MutatingByteSwap16(buf2352.AsSpan()); } - buffer.AddRange(new ArraySegment(buf2352, 0, Math.Min(2352, bootLen))); + bitsToHash.Add(new(buf2352, offset: 0, count: Math.Min(2352, bootLen))); bootLen -= 2352; } - return MD5Checksum.ComputeDigestHex(buffer.ToArray()); + return MD5Checksum.ComputeDigestHex(CollectionExtensions.ConcatArrays(bitsToHash)); } var jaguarHash = HashJaguar(disc.Sessions[2].Tracks[1], dsr, false); diff --git a/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs b/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs index a9eec1cc26..eef2a840a7 100644 --- a/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs +++ b/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs @@ -72,6 +72,19 @@ namespace BizHawk.Tests.Common.CollectionExtensions Assert.AreEqual(0, Array.Empty().ConcatArray(Array.Empty()).Length); } + [TestMethod] + public void TestConcatArrays() + { + Assert.IsTrue( + CE.ConcatArrays([ [ 1, 2 ], [ 3 ], [ ], [ 4, 5, 6 ] ]) + .SequenceEqual([ 1, 2, 3, 4, 5, 6 ]), + "array"); + Assert.IsTrue( + CE.ConcatArrays([ new ArraySegment([ 1, 2 ]), new ArraySegment([ 3 ]), [ ], new ArraySegment([ 4, 5, 6 ]) ]) + .SequenceEqual([ 1, 2, 3, 4, 5, 6 ]), + "ArraySegment"); + } + [TestMethod] public void TestLowerBoundBinarySearch() { diff --git a/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs b/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs index 83835f741b..f15333a887 100644 --- a/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs +++ b/src/BizHawk.Tests/Common/CustomCollections/CustomCollectionTests.cs @@ -26,6 +26,23 @@ namespace BizHawk.Tests.Common.CustomCollections Assert.IsTrue(list.Contains(11)); // `Contains` when `BinarySearch` returns non-negative } + [TestMethod] + public void TestSortedListInsert() + { + SortedList list = new([ 1, 4, 7 ]); + Assert.ThrowsException(() => list.Insert(index: 3, item: 0), "setting [^0] (appending) out-of-order should throw"); + list.Insert(index: 3, item: 10); + Assert.IsTrue(list.SequenceEqual([ 1, 4, 7, 10 ]), "expecting [ 1, 4, 7, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 3, item: 0), "setting [^1] out-of-order should throw"); + list.Insert(index: 3, item: 9); + Assert.IsTrue(list.SequenceEqual([ 1, 4, 7, 9, 10 ]), "expecting [ 1, 4, 7, 9, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 1, item: 9), "setting [1] out-of-order should throw"); + list.Insert(index: 1, item: 3); + Assert.IsTrue(list.SequenceEqual([ 1, 3, 4, 7, 9, 10 ]), "expecting [ 1, 3, 4, 7, 9, 10 ]"); + Assert.ThrowsException(() => list.Insert(index: 0, item: 9), "setting [0] out-of-order should throw"); + list.Insert(index: 0, item: 0); + Assert.IsTrue(list.SequenceEqual([ 0, 1, 3, 4, 7, 9, 10 ]), "expecting [ 0, 1, 3, 4, 7, 9, 10 ]"); + } [TestMethod] [DataRow(new[] {1, 5, 9, 10, 11, 12}, new[] {1, 5, 9}, 9)] @@ -37,5 +54,21 @@ namespace BizHawk.Tests.Common.CustomCollections sortlist.RemoveAfter(removeItem); Assert.IsTrue(sortlist.ToArray().SequenceEqual(after)); } + + [TestMethod] + public void TestSortedListSetIndexer() + { + SortedList list = new([ 1, 3, 4 ]); + Assert.ThrowsException(() => list[1] = 9, "setting [1] out-of-order should throw"); + list[1] = 2; + Assert.IsTrue(list.SequenceEqual([ 1, 2, 4 ]), "expecting [ 1, 2, 4 ]"); + Assert.ThrowsException(() => list[0] = 9, "setting [0] out-of-order should throw"); + list[0] = 0; + Assert.ThrowsException(() => list[2] = 0, "setting [^1] out-of-order should throw"); + list[2] = 9; + Assert.ThrowsException(() => list[3] = 0, "setting [^0] (appending) out-of-order should throw"); + Assert.ThrowsException(() => list[3] = 10, "setting [^0] (appending) properly should throw"); // to match BCL `List` + Assert.IsTrue(list.SequenceEqual([ 0, 2, 9 ]), "expecting [ 0, 2, 9 ]"); + } } } diff --git a/waterbox/dsda/BizhawkInterface.cxx b/waterbox/dsda/BizhawkInterface.cxx new file mode 100644 index 0000000000..7a1196c4a5 --- /dev/null +++ b/waterbox/dsda/BizhawkInterface.cxx @@ -0,0 +1,344 @@ +#include "BizhawkInterface.hxx" + +ECL_EXPORT void dsda_get_audio(int *n, void **buffer) +{ + int nSamples = 0; + void* audioBuffer = nullptr; + audioBuffer = I_CaptureAudio(&nSamples); + // printf("audioBuffer: %p - nSamples: %d\n", audioBuffer, nSamples); + + if (n) + *n = nSamples; + if (buffer) + *buffer = audioBuffer; +} + +ECL_EXPORT void dsda_get_video(int& w, int& h, int& pitch, uint8_t*& buffer, int& paletteSize, uint32_t*& paletteBuffer) +{ + buffer = (uint8_t*)headlessGetVideoBuffer(); + w = headlessGetVideoWidth(); + h = headlessGetVideoHeight(); + pitch = headlessGetVideoPitch(); + paletteSize = PALETTE_SIZE; + + auto palette = headlessGetPallette(); + for (size_t i = 0; i < PALETTE_SIZE; i++) + { + uint8_t* srcColor = (uint8_t*)&palette[i]; + uint8_t* dstColor = (uint8_t*)&_convertedPaletteBuffer[i]; + dstColor[0] = srcColor[2]; + dstColor[1] = srcColor[1]; + dstColor[2] = srcColor[0]; + dstColor[3] = srcColor[3]; + } + + paletteBuffer = _convertedPaletteBuffer; +} + +ECL_EXPORT void dsda_frame_advance(struct PackedPlayerInput *player1Inputs, struct PackedPlayerInput *player2Inputs, struct PackedPlayerInput *player3Inputs, struct PackedPlayerInput *player4Inputs, struct PackedRenderInfo *renderInfo) +{ + // Setting inputs + headlessClearTickCommand(); + + // Setting Player 1 inputs + headlessSetTickCommand + ( + 0, + player1Inputs->_RunSpeed, + player1Inputs->_StrafingSpeed, + player1Inputs->_TurningSpeed, + player1Inputs->_Fire, + player1Inputs->_Action, + player1Inputs->_WeaponSelect, + player1Inputs->_AltWeapon, + player1Inputs->_FlyLook, + player1Inputs->_ArtifactUse, + player1Inputs->_Jump, + player1Inputs->_EndPlayer + ); + + // Setting Player 2 inputs + headlessSetTickCommand + ( + 1, + player2Inputs->_RunSpeed, + player2Inputs->_StrafingSpeed, + player2Inputs->_TurningSpeed, + player2Inputs->_Fire, + player2Inputs->_Action, + player2Inputs->_WeaponSelect, + player2Inputs->_AltWeapon, + player2Inputs->_FlyLook, + player2Inputs->_ArtifactUse, + player2Inputs->_Jump, + player2Inputs->_EndPlayer + ); + + // Setting Player 3 inputs + headlessSetTickCommand + ( + 2, + player3Inputs->_RunSpeed, + player3Inputs->_StrafingSpeed, + player3Inputs->_TurningSpeed, + player3Inputs->_Fire, + player3Inputs->_Action, + player3Inputs->_WeaponSelect, + player3Inputs->_AltWeapon, + player3Inputs->_FlyLook, + player3Inputs->_ArtifactUse, + player3Inputs->_Jump, + player3Inputs->_EndPlayer + ); + + // Setting Player 4 inputs + headlessSetTickCommand + ( + 3, + player4Inputs->_RunSpeed, + player4Inputs->_StrafingSpeed, + player4Inputs->_TurningSpeed, + player4Inputs->_Fire, + player4Inputs->_Action, + player4Inputs->_WeaponSelect, + player4Inputs->_AltWeapon, + player4Inputs->_FlyLook, + player4Inputs->_ArtifactUse, + player4Inputs->_Jump, + player4Inputs->_EndPlayer + ); + + // Enabling/Disabling rendering, as required + if (renderInfo->_RenderVideo == 0) headlessDisableVideoRendering(); + if (renderInfo->_RenderVideo == 1) headlessEnableVideoRendering(); + if (renderInfo->_RenderAudio == 0) headlessDisableAudioRendering(); + if (renderInfo->_RenderAudio == 1) headlessEnableAudioRendering(); + + // Running a single tick + headlessRunSingleTick(); + + // Updating video + if (renderInfo->_RenderVideo == 1) + { + displayplayer = consoleplayer = renderInfo->_PlayerPointOfView; + headlessUpdateVideo(); + } +} + +ECL_ENTRY void (*input_callback_cb)(void); + +void real_input_callback(void) +{ + if (input_callback_cb) + input_callback_cb(); +} + +ECL_EXPORT void dsda_set_input_callback(ECL_ENTRY void (*fecb)(void)) +{ + input_callback_cb = fecb; +} + +bool foundIWAD = false; + +ECL_EXPORT int dsda_init(struct InitSettings *settings) +{ + // Creating arguments + int argc = 0; + char** argv = (char**) alloc_invisible (sizeof(char*) * 512); + + bool _noMonsters = false; + bool _monstersRespawn = false; + + // Specifying executable name + char arg0[] = "dsda"; + argv[argc++] = arg0; + + // Eliminating restrictions to TAS inputs + if (settings->_StrictMode == 0) + { + char arg2[] = "-tas"; + argv[argc++] = arg2; + } + + // Specifying skill level + char arg3[] = "-skill"; + argv[argc++] = arg3; + char argSkill[512]; + sprintf(argSkill, "%d", settings->_SkillLevel); + argv[argc++] = argSkill; + + // Specifying episode and map + char arg4[] = "-warp"; + argv[argc++] = arg4; + char argEpisode[512]; + { + sprintf(argEpisode, "%d", settings->_InitialEpisode); + argv[argc++] = argEpisode; + } + char argMap[512]; + sprintf(argMap, "%d", settings->_InitialMap); + argv[argc++] = argMap; + + // Specifying comp level + char arg5[] = "-complevel"; + argv[argc++] = arg5; + char argCompatibilityLevel[512]; + sprintf(argCompatibilityLevel, "%d", settings->_CompatibilityMode); + argv[argc++] = argCompatibilityLevel; + + // Specifying fast monsters + char arg6[] = "-fast"; + if (settings->_FastMonsters == 1) argv[argc++] = arg6; + + // Specifying monsters respawn + char arg7[] = "-respawn"; + if (settings->_MonstersRespawn == 1) argv[argc++] = arg7; + + // Specifying no monsters + char arg8[] = "-nomonsters"; + if (settings->_NoMonsters == 1) argv[argc++] = arg8; + + char arg9[] = "-chain_episodes"; + if (settings->_ChainEpisodes == 1) argv[argc++] = arg9; + + // Specifying Turbo + char arg10[] = "-turbo"; + char argTurbo[512]; + if (settings->_Turbo >= 0) + { + sprintf(argTurbo, "%d", settings->_Turbo); + argv[argc++] = arg10; + argv[argc++] = argTurbo; + } + + printf("Passing arguments: \n"); + for (int i = 0; i < argc; i++) printf("%s ", argv[i]); + printf("\n"); + + // Setting players in game + playeringame[0] = settings->_Player1Present; + playeringame[1] = settings->_Player2Present; + playeringame[2] = settings->_Player3Present; + playeringame[3] = settings->_Player4Present; + + // Getting player count + auto playerCount = settings->_Player1Present + settings->_Player2Present + settings->_Player3Present + settings->_Player4Present; + char arg12[] = "-solo-net"; + if (playerCount > 1) argv[argc++] = arg12; + + // Set multiplayer mode + char arg13[] = "-deathmatch"; + if (settings->_MultiplayerMode == 1) argv[argc++] = arg13; + char arg14[] = "-altdeath"; + if (settings->_MultiplayerMode == 2) argv[argc++] = arg14; + + // Handle class + PlayerClass[0] = (pclass_t)settings->_Player1Class; + PlayerClass[1] = (pclass_t)settings->_Player2Class; + PlayerClass[2] = (pclass_t)settings->_Player3Class; + PlayerClass[3] = (pclass_t)settings->_Player4Class; + + // Initializing DSDA core + headlessMain(argc, argv); + printf("DSDA Initialized\n"); + + // Initializing audio + I_SetSoundCap(); + I_InitSound(); + printf("Audio Initialized\n"); + + // If, required prevent level exit and game end triggers + preventLevelExit = settings->_PreventLevelExit; + preventGameEnd = settings->_PreventGameEnd; + + printf("Prevent Level Exit: %d\n", preventLevelExit); + printf("Prevent Game End: %d\n", preventGameEnd); + + // Enabling DSDA output, for debugging + enableOutput = 1; + + return 1; +} + + +ECL_EXPORT int dsda_add_wad_file(const char *filename, const int size, ECL_ENTRY int (*feload_archive_cb)(const char *filename, unsigned char *buffer, int maxsize)) +{ + printf("Loading WAD '%s' of size %d...\n", filename, size); + auto wadFileBuffer = (unsigned char*) alloc_invisible(size); + + if (wadFileBuffer == NULL) { fprintf(stderr, "Error creating buffer. Do we have enough memory in the waterbox?\n"); return 0; } + else printf("Created buffer at address: %p\n", wadFileBuffer); + + int loadSize = feload_archive_cb(filename, wadFileBuffer, size); + if (loadSize != size) { fprintf(stderr, "Error loading '%s': read %d bytes, but expected %d bytes\n", filename, loadSize, size); return 0; } + + // Check size is enough + if (size < 5) { fprintf(stderr, "Error loading '%s': read %d bytes, which is too small\n", filename, size); return 0; } + + // Getting wad header + char header[5]; + header[0] = wadFileBuffer[0]; + header[1] = wadFileBuffer[1]; + header[2] = wadFileBuffer[2]; + header[3] = wadFileBuffer[3]; + header[4] = '\0'; + + // Getting string + std::string headerString(header); + + // Safety checks + bool recognizedFormat = false; + + // Loading PWAD + if (headerString == "PWAD") + { + recognizedFormat = true; + + // Loading PWAD + D_AddFile(filename, source_pwad, wadFileBuffer, size); + printf("Loaded PWAD '%s' correctly\n", filename); + } + + // Loading IWAD + if (headerString == "IWAD") + { + recognizedFormat = true; + + // Checking for repeated IWAD + if (foundIWAD == true) { fprintf(stderr, "Error with '%s': an IWAD was already loaded before\n", filename); return 0; } + foundIWAD = true; + + // Loading IWAD + printf("Loading IWAD '%s'...\n", filename); + AddIWAD(filename, wadFileBuffer, size); + printf("Loaded IWAD '%s' correctly\n", filename); + } + + // Checking for correct header + if (recognizedFormat == false) { fprintf(stderr, "Error with '%s': it contains an unrecognized header '%s'\n", filename, header); return 0; } + + // Return 1 for all ok + return 1; +} + +// the Doom engine doesn't have traditional memory regions because it's not an emulator +// but there's still useful data in memory that we can expose +// so we turn it into artificial memory domains, one for each entity array +// TODO: expose sectors and linedefs like xdre does (but better) +ECL_EXPORT char dsda_read_memory_array(int type, unsigned int addr) +{ + char out_of_bounts = 0xFF; + char null_thing = 0x88; + int padded_size = 512; // sizeof(mobj_t) is 464 but we pad for nice representation + + if (addr >= numthings * padded_size) return out_of_bounts; + + int index = addr / padded_size; + int offset = addr % padded_size; + mobj_t *mobj = mobj_ptrs[index]; + + if (mobj == NULL) return null_thing; + + char *data = (char *)mobj + offset; + return *data; +} \ No newline at end of file diff --git a/waterbox/dsda/BizhawkInterface.hxx b/waterbox/dsda/BizhawkInterface.hxx new file mode 100644 index 0000000000..3a4dea2259 --- /dev/null +++ b/waterbox/dsda/BizhawkInterface.hxx @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include + +#include "emulibc.h" +#include "d_player.h" +#include "w_wad.h" +#include "p_mobj.h" + +extern "C" +{ + int headlessMain(int argc, char **argv); + void headlessRunSingleTick(); + void headlessUpdateSounds(void); + void headlessClearTickCommand(); + void headlessSetTickCommand(int playerId, int forwardSpeed, int strafingSpeed, int turningSpeed, int fire, int action, int weapon, int altWeapon, int lookfly, int artifact, int jump, int endPlayer); + + // Video-related functions + void headlessUpdateVideo(void); + void* headlessGetVideoBuffer(); + int headlessGetVideoPitch(); + int headlessGetVideoWidth(); + int headlessGetVideoHeight(); + void headlessEnableVideoRendering(); + void headlessDisableVideoRendering(); + void headlessEnableAudioRendering(); + void headlessDisableAudioRendering(); + uint32_t* headlessGetPallette(); + + void headlessSetSaveStatePointer(void* savePtr, int saveStateSize); + size_t headlessGetEffectiveSaveSize(); + void dsda_ArchiveAll(void); + void dsda_UnArchiveAll(void); + void headlessGetMapName(char* outString); + + void D_AddFile (const char *file, wad_source_t source, void* const buffer, const size_t size); + void AddIWAD(const char *iwad, void* const buffer, const size_t size); + unsigned char * I_CaptureAudio (int* nsamples); + void I_InitSound(void); + void I_SetSoundCap (void); +} + +// Players information +extern "C" int enableOutput; +extern "C" player_t players[MAX_MAXPLAYERS]; +extern "C" int preventLevelExit; +extern "C" int preventGameEnd; +extern "C" int reachedLevelExit; +extern "C" int reachedGameEnd; +extern "C" int gamemap; +extern "C" int gametic; +extern "C" dboolean playeringame[MAX_MAXPLAYERS]; +extern "C" int consoleplayer; +extern "C" int displayplayer; +extern "C" pclass_t PlayerClass[MAX_MAXPLAYERS]; +extern int numthings; +extern mobj_t **mobj_ptrs; + +#define PALETTE_SIZE 256 +uint32_t _convertedPaletteBuffer[PALETTE_SIZE]; + +enum MemoryArrayType +{ + ARRAY_THINGS = 0, + ARRAY_LINES = 1, + ARRAY_SECTORS = 2 +}; + +struct InitSettings +{ + int _Player1Present; + int _Player2Present; + int _Player3Present; + int _Player4Present; + int _CompatibilityMode; + int _SkillLevel; + int _MultiplayerMode; + int _InitialEpisode; + int _InitialMap; + int _Turbo; + int _FastMonsters; + int _MonstersRespawn; + int _NoMonsters; + int _Player1Class; + int _Player2Class; + int _Player3Class; + int _Player4Class; + int _ChainEpisodes; + int _StrictMode; + int _PreventLevelExit; + int _PreventGameEnd; +} __attribute__((packed)); + +struct PackedPlayerInput +{ + int _RunSpeed; + int _StrafingSpeed; + int _TurningSpeed; + int _WeaponSelect; + int _Fire; + int _Action; + int _AltWeapon; + int _FlyLook; + int _ArtifactUse; + int _Jump; + int _EndPlayer; +} __attribute__((packed)); + +struct PackedRenderInfo +{ + int _RenderVideo; + int _RenderAudio; + int _PlayerPointOfView; +} __attribute__((packed)); \ No newline at end of file diff --git a/waterbox/dsda/Makefile b/waterbox/dsda/Makefile new file mode 100644 index 0000000000..78485604a7 --- /dev/null +++ b/waterbox/dsda/Makefile @@ -0,0 +1,269 @@ +CCFLAGS := \ + -I. \ + -I./core/prboom2/src \ + -Wfatal-errors \ + -DHAVE_CONFIG_H \ + -Dstricmp=strcasecmp \ + -Dstrnicmp=strncasecmp \ + -DNDEBUG \ + -ffast-math \ + -Wno-unused-function \ + -Wno-switch \ + -Wno-pointer-sign \ + -Wno-sign-compare \ + -Wno-stringop-overflow \ + -Wno-format-truncation \ + -Wno-unused-but-set-variable \ + -Wno-unused-variable + +CXXFLAGS := \ + -I. \ + -I./core/prboom2/src \ + -Wfatal-errors \ + -DHAVE_CONFIG_H \ + -Dstricmp=strcasecmp \ + -Dstrnicmp=strncasecmp \ + -DNDEBUG \ + -ffast-math \ + -Wno-unused-function \ + -Wno-switch \ + -Wno-pointer-sign \ + -Wno-sign-compare \ + -Wno-stringop-overflow \ + -Wno-format-truncation \ + -Wno-unused-but-set-variable \ + -Wno-unused-variable + +LDFLAGS := + +TARGET := dsda.wbx + +SRCS = \ + BizhawkInterface.cxx \ + core/prboom2/src/am_map.c \ + core/prboom2/src/doomdef.c \ + core/prboom2/src/doomstat.c \ + core/prboom2/src/dsda.c \ + core/prboom2/src/dsda/aim.c \ + core/prboom2/src/dsda/ambient.cpp \ + core/prboom2/src/dsda/analysis.c \ + core/prboom2/src/dsda/args.c \ + core/prboom2/src/dsda/brute_force.c \ + core/prboom2/src/dsda/build.c \ + core/prboom2/src/dsda/compatibility.c \ + core/prboom2/src/dsda/configuration.c \ + core/prboom2/src/dsda/console.c \ + core/prboom2/src/dsda/cr_table.c \ + core/prboom2/src/dsda/data_organizer.c \ + core/prboom2/src/dsda/death.c \ + core/prboom2/src/dsda/deh_hash.c \ + core/prboom2/src/dsda/demo.c \ + core/prboom2/src/dsda/destructible.c \ + core/prboom2/src/dsda/endoom.c \ + core/prboom2/src/dsda/episode.c \ + core/prboom2/src/dsda/excmd.c \ + core/prboom2/src/dsda/exdemo.c \ + core/prboom2/src/dsda/exhud.c \ + core/prboom2/src/dsda/features.c \ + core/prboom2/src/dsda/font.c \ + core/prboom2/src/dsda/game_controller.c \ + core/prboom2/src/dsda/ghost.c \ + core/prboom2/src/dsda/global.c \ + core/prboom2/src/dsda/hud_components/ammo_text.c \ + core/prboom2/src/dsda/hud_components/armor_text.c \ + core/prboom2/src/dsda/hud_components/attempts.c \ + core/prboom2/src/dsda/hud_components/base.c \ + core/prboom2/src/dsda/hud_components/big_ammo.c \ + core/prboom2/src/dsda/hud_components/big_armor.c \ + core/prboom2/src/dsda/hud_components/big_armor_text.c \ + core/prboom2/src/dsda/hud_components/big_artifact.c \ + core/prboom2/src/dsda/hud_components/big_health.c \ + core/prboom2/src/dsda/hud_components/big_health_text.c \ + core/prboom2/src/dsda/hud_components/color_test.c \ + core/prboom2/src/dsda/hud_components/command_display.c \ + core/prboom2/src/dsda/hud_components/composite_time.c \ + core/prboom2/src/dsda/hud_components/coordinate_display.c \ + core/prboom2/src/dsda/hud_components/event_split.c \ + core/prboom2/src/dsda/hud_components/fps.c \ + core/prboom2/src/dsda/hud_components/free_text.c \ + core/prboom2/src/dsda/hud_components/health_text.c \ + core/prboom2/src/dsda/hud_components/keys.c \ + core/prboom2/src/dsda/hud_components/level_splits.c \ + core/prboom2/src/dsda/hud_components/line_display.c \ + core/prboom2/src/dsda/hud_components/line_distance_tracker.c \ + core/prboom2/src/dsda/hud_components/line_tracker.c \ + core/prboom2/src/dsda/hud_components/local_time.c \ + core/prboom2/src/dsda/hud_components/map_coordinates.c \ + core/prboom2/src/dsda/hud_components/map_time.c \ + core/prboom2/src/dsda/hud_components/map_title.c \ + core/prboom2/src/dsda/hud_components/map_totals.c \ + core/prboom2/src/dsda/hud_components/message.c \ + core/prboom2/src/dsda/hud_components/minimap.c \ + core/prboom2/src/dsda/hud_components/mobj_tracker.c \ + core/prboom2/src/dsda/hud_components/null.c \ + core/prboom2/src/dsda/hud_components/player_tracker.c \ + core/prboom2/src/dsda/hud_components/ready_ammo_text.c \ + core/prboom2/src/dsda/hud_components/render_stats.c \ + core/prboom2/src/dsda/hud_components/secret_message.c \ + core/prboom2/src/dsda/hud_components/sector_tracker.c \ + core/prboom2/src/dsda/hud_components/speed_text.c \ + core/prboom2/src/dsda/hud_components/stat_totals.c \ + core/prboom2/src/dsda/hud_components/tracker.c \ + core/prboom2/src/dsda/hud_components/weapon_text.c \ + core/prboom2/src/dsda/id_list.c \ + core/prboom2/src/dsda/input.c \ + core/prboom2/src/dsda/key_frame.c \ + core/prboom2/src/dsda/map_format.c \ + core/prboom2/src/dsda/mapinfo.c \ + core/prboom2/src/dsda/mapinfo/doom.c \ + core/prboom2/src/dsda/mapinfo/doom/parser.cpp \ + core/prboom2/src/dsda/mapinfo/hexen.c \ + core/prboom2/src/dsda/mapinfo/legacy.c \ + core/prboom2/src/dsda/mapinfo/u.c \ + core/prboom2/src/dsda/memory.c \ + core/prboom2/src/dsda/messenger.c \ + core/prboom2/src/dsda/mobjinfo.c \ + core/prboom2/src/dsda/mouse.c \ + core/prboom2/src/dsda/msecnode.c \ + core/prboom2/src/dsda/music.c \ + core/prboom2/src/dsda/name.c \ + core/prboom2/src/dsda/options.c \ + core/prboom2/src/dsda/palette.c \ + core/prboom2/src/dsda/pause.c \ + core/prboom2/src/dsda/pclass.c \ + core/prboom2/src/dsda/playback.c \ + core/prboom2/src/dsda/preferences.c \ + core/prboom2/src/dsda/quake.c \ + core/prboom2/src/dsda/render_stats.c \ + core/prboom2/src/dsda/save.c \ + core/prboom2/src/dsda/scroll.c \ + core/prboom2/src/dsda/settings.c \ + core/prboom2/src/dsda/sfx.c \ + core/prboom2/src/dsda/skill_info.c \ + core/prboom2/src/dsda/skip.c \ + core/prboom2/src/dsda/sndinfo.c \ + core/prboom2/src/dsda/spawn_number.c \ + core/prboom2/src/dsda/split_tracker.c \ + core/prboom2/src/dsda/sprite.c \ + core/prboom2/src/dsda/state.c \ + core/prboom2/src/dsda/stretch.c \ + core/prboom2/src/dsda/text_color.c \ + core/prboom2/src/dsda/text_file.c \ + core/prboom2/src/dsda/thing_id.c \ + core/prboom2/src/dsda/time.c \ + core/prboom2/src/dsda/tracker.c \ + core/prboom2/src/dsda/tranmap.c \ + core/prboom2/src/dsda/udmf.cpp \ + core/prboom2/src/dsda/utility.c \ + core/prboom2/src/dsda/utility/string_view.c \ + core/prboom2/src/dsda/wad_stats.c \ + core/prboom2/src/dstrings.c \ + core/prboom2/src/d_deh.c \ + core/prboom2/src/d_items.c \ + core/prboom2/src/d_main.c \ + core/prboom2/src/e6y.c \ + core/prboom2/src/f_finale.c \ + core/prboom2/src/f_wipe.c \ + core/prboom2/src/g_game.c \ + core/prboom2/src/g_overflow.c \ + core/prboom2/src/heretic/d_main.c \ + core/prboom2/src/heretic/f_finale.c \ + core/prboom2/src/heretic/info.c \ + core/prboom2/src/heretic/in_lude.c \ + core/prboom2/src/heretic/level_names.c \ + core/prboom2/src/heretic/mn_menu.c \ + core/prboom2/src/heretic/sb_bar.c \ + core/prboom2/src/heretic/sounds.c \ + core/prboom2/src/hexen/a_action.c \ + core/prboom2/src/hexen/info.c \ + core/prboom2/src/hexen/f_finale.c \ + core/prboom2/src/hexen/h2_main.c \ + core/prboom2/src/hexen/in_lude.c \ + core/prboom2/src/hexen/p_acs.c \ + core/prboom2/src/hexen/p_anim.c \ + core/prboom2/src/hexen/p_things.c \ + core/prboom2/src/hexen/po_man.c \ + core/prboom2/src/hexen/sn_sonix.c \ + core/prboom2/src/hexen/sounds.c \ + core/prboom2/src/hexen/sv_save.c \ + core/prboom2/src/hu_lib.c \ + core/prboom2/src/hu_stuff.c \ + core/prboom2/src/info.c \ + core/prboom2/src/i_capture.c \ + core/prboom2/src/i_glob.c \ + core/prboom2/src/lprintf.c \ + core/prboom2/src/md5.c \ + core/prboom2/src/m_argv.c \ + core/prboom2/src/m_bbox.c \ + core/prboom2/src/m_cheat.c \ + core/prboom2/src/m_file.c \ + core/prboom2/src/m_menu.c \ + core/prboom2/src/m_misc.c \ + core/prboom2/src/m_random.c \ + core/prboom2/src/p_ceilng.c \ + core/prboom2/src/p_doors.c \ + core/prboom2/src/p_enemy.c \ + core/prboom2/src/p_floor.c \ + core/prboom2/src/p_genlin.c \ + core/prboom2/src/p_inter.c \ + core/prboom2/src/p_lights.c \ + core/prboom2/src/p_map.c \ + core/prboom2/src/p_maputl.c \ + core/prboom2/src/p_mobj.c \ + core/prboom2/src/p_plats.c \ + core/prboom2/src/p_pspr.c \ + core/prboom2/src/p_saveg.c \ + core/prboom2/src/p_setup.c \ + core/prboom2/src/p_sight.c \ + core/prboom2/src/p_spec.c \ + core/prboom2/src/p_switch.c \ + core/prboom2/src/p_telept.c \ + core/prboom2/src/p_tick.c \ + core/prboom2/src/p_user.c \ + core/prboom2/src/r_bsp.c \ + core/prboom2/src/r_data.c \ + core/prboom2/src/r_draw.c \ + core/prboom2/src/r_fps.c \ + core/prboom2/src/r_main.c \ + core/prboom2/src/r_patch.c \ + core/prboom2/src/r_plane.c \ + core/prboom2/src/r_segs.c \ + core/prboom2/src/r_sky.c \ + core/prboom2/src/r_things.c \ + core/prboom2/src/scanner.cpp \ + core/prboom2/src/sc_man.c \ + core/prboom2/src/smooth.c \ + core/prboom2/src/sounds.c \ + core/prboom2/src/st_lib.c \ + core/prboom2/src/st_stuff.c \ + core/prboom2/src/s_advsound.c \ + core/prboom2/src/s_sound.c \ + core/prboom2/src/tables.c \ + core/prboom2/src/umapinfo.cpp \ + core/prboom2/src/v_video.c \ + core/prboom2/src/wadtbl.c \ + core/prboom2/src/wi_stuff.c \ + core/prboom2/src/w_wad.c \ + core/prboom2/src/z_bmalloc.c \ + core/prboom2/src/z_zone.c \ + core/prboom2/src/d_client.c \ + core/prboom2/src/w_mmap.c \ + core/prboom2/src/memio.c \ + core/prboom2/src/mus2mid.c \ + core/prboom2/src/SDL/i_main.c \ + core/prboom2/src/SDL/i_sound.c \ + core/prboom2/src/SDL/i_sshot.c \ + core/prboom2/src/SDL/i_system.c \ + core/prboom2/src/SDL/i_video.c \ + core/prboom2/src/MUSIC/dumbplayer.c \ + core/prboom2/src/MUSIC/flplayer.c \ + core/prboom2/src/MUSIC/madplayer.c \ + core/prboom2/src/MUSIC/midifile.c \ + core/prboom2/src/MUSIC/opl.c \ + core/prboom2/src/MUSIC/opl3.c \ + core/prboom2/src/MUSIC/oplplayer.c \ + core/prboom2/src/MUSIC/opl_queue.c \ + core/prboom2/src/MUSIC/portmidiplayer.c \ + core/prboom2/src/MUSIC/vorbisplayer.c + +include ../common.mak diff --git a/waterbox/dsda/config.h b/waterbox/dsda/config.h new file mode 100644 index 0000000000..ae42d19399 --- /dev/null +++ b/waterbox/dsda/config.h @@ -0,0 +1,35 @@ +#define PACKAGE_NAME "dsda-doom" +#define PACKAGE_TARNAME "dsda-doom" +#define WAD_DATA "dsda-doom.wad" +#define PACKAGE_VERSION "0.28.2" +#define PACKAGE_STRING "dsda-doom 0.28.2" + +#define DOOMWADDIR "/usr/local/share/games/doom" +#define DSDA_ABSOLUTE_PWAD_PATH "/usr/local/share/games/doom" + +/* #undef WORDS_BIGENDIAN */ + +#define HAVE_GETOPT +#define HAVE_MMAP +/* #undef HAVE_CREATE_FILE_MAPPING */ +#define HAVE_STRSIGNAL +#define HAVE_MKSTEMP +#define HAVE_GETPWUID + +#define HAVE_SYS_WAIT_H +#define HAVE_UNISTD_H +//#define HAVE_ASM_BYTEORDER_H +#define HAVE_DIRENT_H + +/* #undef HAVE_LIBSDL2_IMAGE */ +/* #undef HAVE_LIBSDL2_MIXER */ + +/* #undef HAVE_LIBMAD */ +/* #undef HAVE_LIBFLUIDSYNTH */ +/* #undef HAVE_LIBDUMB */ +/* #undef HAVE_LIBVORBISFILE */ +/* #undef HAVE_LIBPORTMIDI */ + +/* #undef SIMPLECHECKS */ + +/* #undef RANGECHECK */ diff --git a/waterbox/dsda/core b/waterbox/dsda/core new file mode 160000 index 0000000000..e1a352844f --- /dev/null +++ b/waterbox/dsda/core @@ -0,0 +1 @@ +Subproject commit e1a352844f260e61ac0aa75d2a63941b26c4ab0e diff --git a/waterbox/gpgx/Makefile b/waterbox/gpgx/Makefile index fff5c30db5..112f8dd039 100644 --- a/waterbox/gpgx/Makefile +++ b/waterbox/gpgx/Makefile @@ -24,6 +24,7 @@ CCFLAGS := -Iutil \ -DHOOK_CPU \ -DINLINE=static\ __inline__ \ -DcdStream=cdStream \ + -DMAXROMSIZE=33554432 \ -fcommon LDFLAGS := diff --git a/waterbox/make-all-cores.sh b/waterbox/make-all-cores.sh index 7965f6e71e..6b8c00f5c9 100755 --- a/waterbox/make-all-cores.sh +++ b/waterbox/make-all-cores.sh @@ -5,6 +5,7 @@ cd emulibc && make -f Makefile $1 -j && cd - cd libco && make -f Makefile $1 -j && cd - cd ares64 && ./make-both.sh $1 && cd - cd bsnescore && make -f Makefile $1 -j && cd - +cd dsda && make -f Makefile $1 -j && cd - cd gpgx && make -f Makefile $1 -j && cd - cd libsnes && make -f Makefile $1 -j && cd - cd melon && make -f Makefile $1 -j && cd - diff --git a/waterbox/waterboxhost/src/cinterface.rs b/waterbox/waterboxhost/src/cinterface.rs index bd3fc04ced..dccf2dae95 100644 --- a/waterbox/waterboxhost/src/cinterface.rs +++ b/waterbox/waterboxhost/src/cinterface.rs @@ -80,7 +80,7 @@ impl Return { /// write bytes. Return 0 on success, or < 0 on failure. /// Must write all provided bytes in one call or fail, not permitted to write less (unlike reader). -pub type WriteCallback = extern fn(userdata: usize, data: *const u8, size: usize) -> i32; +pub type WriteCallback = extern "C" fn(userdata: usize, data: *const u8, size: usize) -> i32; struct CWriter { /// will be passed to callback pub userdata: usize, @@ -107,7 +107,7 @@ impl Write for CWriter { /// Read bytes into the buffer. Return number of bytes read on success, or < 0 on failure. /// permitted to read less than the provided buffer size, but must always read at least 1 /// byte if EOF is not reached. If EOF is reached, should return 0. -pub type ReadCallback = extern fn(userdata: usize, data: *mut u8, size: usize) -> isize; +pub type ReadCallback = extern "C" fn(userdata: usize, data: *mut u8, size: usize) -> isize; struct CReader { pub userdata: usize, pub callback: ReadCallback, @@ -126,7 +126,7 @@ impl Read for CReader { // #[repr(C)] // pub struct MissingFileCallback { // pub userdata: usize, -// pub callback: extern fn(userdata: usize, name: *const c_char) -> *mut MissingFileResult, +// pub callback: extern "C" fn(userdata: usize, name: *const c_char) -> *mut MissingFileResult, // } // #[repr(C)] @@ -152,7 +152,7 @@ fn read_whole_file(reader: &mut CReader) -> anyhow::Result> { /// Given a guest executable and a memory layout, create a new host environment. All data will be immediately consumed from the reader, /// which will not be used after this call. #[no_mangle] -pub extern fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const c_char, callback: ReadCallback, userdata: usize, ret: &mut Return<*mut WaterboxHost>) { +pub extern "C" fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const c_char, callback: ReadCallback, userdata: usize, ret: &mut Return<*mut WaterboxHost>) { let mut reader = CReader { userdata, callback @@ -166,7 +166,7 @@ pub extern fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const /// Tear down a host environment. If called while the environment is active, will deactivate it first. #[no_mangle] -pub extern fn wbx_destroy_host(obj: *mut WaterboxHost, ret: &mut Return<()>) { +pub extern "C" fn wbx_destroy_host(obj: *mut WaterboxHost, ret: &mut Return<()>) { let res = (|| { unsafe { drop(Box::from_raw(obj)); @@ -181,7 +181,7 @@ pub extern fn wbx_destroy_host(obj: *mut WaterboxHost, ret: &mut Return<()>) { /// while active. Uses a mutex internally so as to not stomp over other host environments in the same 4GiB slice. /// Ignored if host is already active. #[no_mangle] -pub extern fn wbx_activate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) { +pub extern "C" fn wbx_activate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) { let res = (|| { obj.activate(); Ok(()) @@ -192,7 +192,7 @@ pub extern fn wbx_activate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) { /// Deactivates a host environment, and releases the mutex. /// Ignored if host is not active #[no_mangle] -pub extern fn wbx_deactivate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) { +pub extern "C" fn wbx_deactivate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) { obj.deactivate(); ret.put(Ok(())); } @@ -201,7 +201,7 @@ pub extern fn wbx_deactivate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) /// while the host is active. A missing proc is not an error and simply returns 0. The guest function must be, /// and the returned callback will be, sysv abi, and will only pass up to 6 int/ptr args and no other arg types. #[no_mangle] -pub extern fn wbx_get_proc_addr(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return) { +pub extern "C" fn wbx_get_proc_addr(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return) { match arg_to_str(name) { Ok(s) => { ret.put(obj.get_proc_addr(&s)); @@ -216,14 +216,14 @@ pub extern fn wbx_get_proc_addr(obj: &mut WaterboxHost, name: *const c_char, ret /// only needed if the guest exposes callin pointers that aren't named exports (for instance, if a function returns /// a pointer to another function). #[no_mangle] -pub extern fn wbx_get_callin_addr(obj: &mut WaterboxHost, ptr: usize, ret: &mut Return) { +pub extern "C" fn wbx_get_callin_addr(obj: &mut WaterboxHost, ptr: usize, ret: &mut Return) { ret.put(obj.get_external_callin_ptr(ptr)); } /// Returns the raw address of a function exported from the guest. `wbx_get_proc_addr()` is equivalent to /// `wbx_get_callin_addr(wbx_get_proc_addr_raw()). Most things should not use this directly, as the returned /// pointer will not have proper stack hygiene and will crash on syscalls from the guest. #[no_mangle] -pub extern fn wbx_get_proc_addr_raw(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return) { +pub extern "C" fn wbx_get_proc_addr_raw(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return) { match arg_to_str(name) { Ok(s) => { ret.put(obj.get_proc_addr_raw(&s)); @@ -240,13 +240,13 @@ pub extern fn wbx_get_proc_addr_raw(obj: &mut WaterboxHost, name: *const c_char, /// in the guest because `foo` was bound to the same slot and a particular slot gives a consistent pointer. /// The returned thunk will be, and the callback must be, sysv abi and will only pass up to 6 int/ptr args and no other arg types. #[no_mangle] -pub extern fn wbx_get_callback_addr(obj: &mut WaterboxHost, callback: ExternalCallback, slot: usize, ret: &mut Return) { +pub extern "C" fn wbx_get_callback_addr(obj: &mut WaterboxHost, callback: ExternalCallback, slot: usize, ret: &mut Return) { ret.put(obj.get_external_callback_ptr(callback, slot)); } /// Calls the seal operation, which is a one time action that prepares the host to save states. #[no_mangle] -pub extern fn wbx_seal(obj: &mut WaterboxHost, ret: &mut Return<()>) { +pub extern "C" fn wbx_seal(obj: &mut WaterboxHost, ret: &mut Return<()>) { ret.put(obj.seal()); } @@ -254,7 +254,7 @@ pub extern fn wbx_seal(obj: &mut WaterboxHost, ret: &mut Return<()>) { /// To prevent nondeterminism, adding and removing files is very limited WRT savestates. Every file added must either exist /// in every savestate, or never appear in any savestates. All savestateable files must be added in the same order for every run. #[no_mangle] -pub extern fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callback: ReadCallback, userdata: usize, writable: bool, ret: &mut Return<()>) { +pub extern "C" fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callback: ReadCallback, userdata: usize, writable: bool, ret: &mut Return<()>) { let mut reader = CReader { userdata, callback @@ -270,7 +270,7 @@ pub extern fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callba /// It is an error to remove a file which is currently open in the guest. /// If the file has been used in savestates, it does not make sense to remove it here, but nothing will stop you. #[no_mangle] -pub extern fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, callback_opt: Option, userdata: usize, ret: &mut Return<()>) { +pub extern "C" fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, callback_opt: Option, userdata: usize, ret: &mut Return<()>) { let res: anyhow::Result<()> = (|| { let data = obj.unmount_file(&arg_to_str(name)?)?; if let Some(callback) = callback_opt { @@ -291,7 +291,7 @@ pub extern fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, call /// in the callback. If the MissingFileResult is provided, it will be consumed immediately and will have the same effect /// as wbx_mount_file(). You may free resources associated with the MissingFileResult whenever control next returns to your code. // #[no_mangle] -// pub extern fn wbx_set_missing_file_callback(obj: &mut WaterboxHost, mfc_o: Option<&MissingFileCallback>) { +// pub extern "C" fn wbx_set_missing_file_callback(obj: &mut WaterboxHost, mfc_o: Option<&MissingFileCallback>) { // match mfc_o { // None => obj.set_missing_file_callback(None), // Some(mfc) => { @@ -321,7 +321,7 @@ pub extern fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, call /// Save state. Must not be called before seal. Must not be called with any writable files mounted. /// Must always be called with the same sequence and contents of readonly files. #[no_mangle] -pub extern fn wbx_save_state(obj: &mut WaterboxHost, callback: WriteCallback, userdata: usize, ret: &mut Return<()>) { +pub extern "C" fn wbx_save_state(obj: &mut WaterboxHost, callback: WriteCallback, userdata: usize, ret: &mut Return<()>) { let mut writer = CWriter { userdata, callback @@ -337,7 +337,7 @@ pub extern fn wbx_save_state(obj: &mut WaterboxHost, callback: WriteCallback, us /// Must be called with the same wbx executable and memory layout as in the savestate. /// Errors generally poison the environment; sorry! #[no_mangle] -pub extern fn wbx_load_state(obj: &mut WaterboxHost, callback: ReadCallback, userdata: usize, ret: &mut Return<()>) { +pub extern "C" fn wbx_load_state(obj: &mut WaterboxHost, callback: ReadCallback, userdata: usize, ret: &mut Return<()>) { let mut reader = CReader { userdata, callback @@ -349,7 +349,7 @@ pub extern fn wbx_load_state(obj: &mut WaterboxHost, callback: ReadCallback, use /// this should be set to false. Set to true to help catch dangling pointer issues. Will be ignored (and forced to true) /// if waterboxhost was built in debug mode. This is a single global setting. #[no_mangle] -pub extern fn wbx_set_always_evict_blocks(_val: bool) { +pub extern "C" fn wbx_set_always_evict_blocks(_val: bool) { #[cfg(not(debug_assertions))] { unsafe { ALWAYS_EVICT_BLOCKS = _val; } @@ -358,7 +358,7 @@ pub extern fn wbx_set_always_evict_blocks(_val: bool) { /// Retrieve the number of pages of guest memory that this host is tracking #[no_mangle] -pub extern fn wbx_get_page_len(obj: &mut WaterboxHost, ret: &mut Return) { +pub extern "C" fn wbx_get_page_len(obj: &mut WaterboxHost, ret: &mut Return) { ret.put(Ok(obj.page_len())) } @@ -371,7 +371,7 @@ pub extern fn wbx_get_page_len(obj: &mut WaterboxHost, ret: &mut Return) /// 0x40 - invisible /// 0x80 - dirty #[no_mangle] -pub extern fn wbx_get_page_data(obj: &mut WaterboxHost, index: usize, ret: &mut Return) { +pub extern "C" fn wbx_get_page_data(obj: &mut WaterboxHost, index: usize, ret: &mut Return) { if index >= obj.page_len() { ret.put(Err(anyhow!("Index out of range"))) } else { diff --git a/waterbox/waterboxhost/src/gdb.rs b/waterbox/waterboxhost/src/gdb.rs index f13e49118a..e088d79348 100644 --- a/waterbox/waterboxhost/src/gdb.rs +++ b/waterbox/waterboxhost/src/gdb.rs @@ -31,7 +31,7 @@ unsafe impl Sync for jit_descriptor {} #[no_mangle] #[inline(never)] -extern fn __jit_debug_register_code() {} +extern "C" fn __jit_debug_register_code() {} #[no_mangle] static mut __jit_debug_descriptor: jit_descriptor = jit_descriptor { diff --git a/waterbox/waterboxhost/src/host.rs b/waterbox/waterboxhost/src/host.rs index e49175b27e..f01f3f5173 100644 --- a/waterbox/waterboxhost/src/host.rs +++ b/waterbox/waterboxhost/src/host.rs @@ -196,7 +196,7 @@ impl IStateable for WaterboxHost { fn unimp(nr: SyscallNumber) -> SyscallResult { eprintln!("Stopped on unimplemented syscall {}", lookup_syscall(&nr)); - unsafe { std::intrinsics::breakpoint() } + std::intrinsics::breakpoint(); Err(ENOSYS) } diff --git a/waterbox/waterboxhost/src/memory_block/tripguard.rs b/waterbox/waterboxhost/src/memory_block/tripguard.rs index ec2c8356b7..e2e5c7f315 100644 --- a/waterbox/waterboxhost/src/memory_block/tripguard.rs +++ b/waterbox/waterboxhost/src/memory_block/tripguard.rs @@ -118,14 +118,14 @@ mod trip_pal { use libc::*; use super::*; - type SaHandler = unsafe extern fn(i32) -> (); - type SaSigaction = unsafe extern fn(i32, *const siginfo_t, *const ucontext_t) -> (); + type SaHandler = unsafe extern "C" fn(i32) -> (); + type SaSigaction = unsafe extern "C" fn(i32, *const siginfo_t, *const ucontext_t) -> (); static mut SA_OLD: Option> = None; pub fn initialize() { use std::mem::{transmute, zeroed}; - unsafe extern fn handler(sig: i32, info: *const siginfo_t, ucontext: *const ucontext_t) { + unsafe extern "C" fn handler(sig: i32, info: *const siginfo_t, ucontext: *const ucontext_t) { let fault_address = (*info).si_addr() as usize; let write = (*ucontext).uc_mcontext.gregs[REG_ERR as usize] & 2 != 0; let rethrow = !write || match trip(fault_address) { diff --git a/waterbox/waterboxhost/src/threading.rs b/waterbox/waterboxhost/src/threading.rs index a37080677a..c319764604 100644 --- a/waterbox/waterboxhost/src/threading.rs +++ b/waterbox/waterboxhost/src/threading.rs @@ -176,7 +176,7 @@ impl GuestThreadSet { pub fn exit(&mut self, context: &mut Context) -> SyscallReturn { if self.active_tid == 1 { - unsafe { std::intrinsics::breakpoint() } + std::intrinsics::breakpoint() } let addr = self.threads.get_mut(&self.active_tid).unwrap().tid_address; if addr != 0 {