From 95cbbecb97cab0b07da0e0c021373234e564155c Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Thu, 13 Feb 2025 16:49:16 +0100
Subject: [PATCH 01/27] harden TasView_QueryItemText exception handling
by immediately throwing out the current movie. This should prevent this message box from coming up repeatedly, not providing any additional useful information and just blocking all other windows.
should resolve #2332
---
.../tools/TAStudio/TAStudio.ListView.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
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();
}
}
From 582a25b85137272c2e9aec707f5b1c2334907fdd Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 08:22:40 +0100
Subject: [PATCH 02/27] handle missing return gracefully in
tastudio.onqueryitem* functions
- closes #4219
---
.../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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();
}
From 89ea856ba47f8f1cc515a61c64c362f6719fa73f Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 13:18:17 +0100
Subject: [PATCH 03/27] implement command field in DsmImport
see #3555
---
.../movie/import/DsmImport.cs | 36 ++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
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--;
+ }
}
}
}
From 8bc446bfa35dd1e56797b6f5e8928cd944fa2d39 Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 14:20:35 +0100
Subject: [PATCH 04/27] rename target frame length option for more clarity
- closes #3309
---
src/BizHawk.Client.Common/config/RewindConfig.cs | 2 +-
.../movie/tasproj/ZwinderStateManagerSettings.cs | 6 +++---
src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs | 2 +-
src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs | 4 ++--
4 files changed, 7 insertions(+), 7 deletions(-)
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/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.EmuHawk/config/RewindConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
index e354bd0283..baee3cdc4f 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,
@@ -537,7 +537,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
From 84c79b9053c60d76219f6fba09213f2f28a055af Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 14:41:20 +0100
Subject: [PATCH 05/27] fix some edge case display values in RewindConfig
specifically when rewind is disabled or if it's enabled and no state exists.
---
src/BizHawk.Client.EmuHawk/config/RewindConfig.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs b/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs
index 2667da4b69..a6c6f1a1f8 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,10 +155,10 @@ namespace BizHawk.Client.EmuHawk
private void CalculateEstimates()
{
double estFrames = 0.0;
+ var bufferSize = 1L << (int) BufferSizeUpDown.Value;
+ labelEx1.Text = bufferSize.ToString();
if (_avgStateSize is not 0UL)
{
- var bufferSize = 1L << (int) BufferSizeUpDown.Value;
- labelEx1.Text = bufferSize.ToString();
bufferSize *= 1024 * 1024;
estFrames = bufferSize / (double) _avgStateSize;
}
From 24641433e49ee9f0f1d59d56f1338a65a881b02c Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 15:13:32 +0100
Subject: [PATCH 06/27] improve estimated time display in RewindConfig
now takes the estimated rewind interval into account to display the actual estimated rewind range, which is what people care about
---
.../config/RewindConfig.Designer.cs | 5 ++++-
src/BizHawk.Client.EmuHawk/config/RewindConfig.cs | 11 ++++++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
index baee3cdc4f..56e15d6cba 100755
--- a/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
+++ b/src/BizHawk.Client.EmuHawk/config/RewindConfig.Designer.cs
@@ -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
//
@@ -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 a6c6f1a1f8..6076cac6af 100755
--- a/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs
+++ b/src/BizHawk.Client.EmuHawk/config/RewindConfig.cs
@@ -157,15 +157,24 @@ namespace BizHawk.Client.EmuHawk
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)
{
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();
From 426c743da952b783d18137c875fc9c208904fe97 Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 18:28:42 +0100
Subject: [PATCH 07/27] fix SpanSplit for .net8
removed the NETCOREAPP3_0 code because it didn't even compile; lots of internals missing and we probably wouldn't need the 1% speedup anyway (the function using it is unused currently too)
---
src/BizHawk.Common/Extensions/SpanSplit.cs | 126 +--------------------
1 file changed, 1 insertion(+), 125 deletions(-)
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.
///
From f12f90366bb78577f165955f018760c28db6f93e Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 18:48:10 +0100
Subject: [PATCH 08/27] Revert "Add more `GetValueOrDefault` overloads"
This reverts commit 34d71e90bcd7ace6736363553083b925b5bf888f.
This was hardly used and caused trouble in .net 8 with the way it was defined.
---
src/BizHawk.Common/Extensions/CollectionExtensions.cs | 8 --------
1 file changed, 8 deletions(-)
diff --git a/src/BizHawk.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
index 7f49167b62..d58397ba81 100644
--- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs
+++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
@@ -165,10 +165,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 +173,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
From 5d6b35e2c977183fc1f1a14def4d8fa9f638aa7d Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 18:50:34 +0100
Subject: [PATCH 09/27] fix single usage of GetValueOrDefault(IDictionary)
---
.../Base Implementations/ControllerDefinition.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
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;
}
}
From 283ed40f33cb6927219990157f8c2577343d659f Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 19:07:51 +0100
Subject: [PATCH 10/27] fix ToDictionary extension
fixes a9bfd96027ef5be56df3aefe3c81b02dfe68ed4a
---
src/BizHawk.Client.Common/movie/tasproj/TasLagLog.cs | 3 +++
src/BizHawk.Common/Extensions/CollectionExtensions.cs | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
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.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
index d58397ba81..e37ac7f988 100644
--- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs
+++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
@@ -302,9 +302,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
From 084497a28a5637d2383dd57dfb997ce377bb7452 Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 19:10:29 +0100
Subject: [PATCH 11/27] fix UserDataApi key getter
---
src/BizHawk.Client.Common/Api/Classes/UserDataApi.cs | 4 ++++
1 file changed, 4 insertions(+)
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
}
}
From 33571d4cd3880010546c96d9f5cde2126afeed17 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Sat, 15 Feb 2025 04:36:34 +1000
Subject: [PATCH 12/27] Set `GPGX.PCRegisterName` correctly in SMS/etc. mode
(resolves #4226)
---
.../Consoles/Sega/gpgx64/GPGX.IDisassembler.cs | 2 +-
src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
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..1f640ec56c 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
@@ -48,6 +48,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
_ => throw new InvalidOperationException("Invalid system id")
};
}
+ PCRegisterName = SystemId is VSystemID.Raw.GEN ? "M68K PC" : "Z80 pc";
// 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
From 5a236fdbee5e75afbcdff42bc9b3a0dbb7eff488 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Sat, 15 Feb 2025 05:01:31 +1000
Subject: [PATCH 13/27] Implement remaining `IList` methods on
`SortedList`
---
src/BizHawk.Common/CustomCollections.cs | 38 ++++++++++++++++++-
.../CustomCollectionTests.cs | 33 ++++++++++++++++
2 files changed, 69 insertions(+), 2 deletions(-)
diff --git a/src/BizHawk.Common/CustomCollections.cs b/src/BizHawk.Common/CustomCollections.cs
index eb2738c1a0..8e596b7fc1 100644
--- a/src/BizHawk.Common/CustomCollections.cs
+++ b/src/BizHawk.Common/CustomCollections.cs
@@ -3,9 +3,11 @@ using System.Collections.Generic;
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;
@@ -20,7 +22,24 @@ namespace BizHawk.Common
_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 +74,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];
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*NotSupportedException*/ArgumentOutOfRangeException>(() => 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 ]");
+ }
}
}
From d7b0af9f4eb4b3ab9e2527c857f96c0ebff0d6d8 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Sat, 15 Feb 2025 05:54:49 +1000
Subject: [PATCH 14/27] Resolve a TODO in `InputRoll.RowCount`
---
.../CustomControls/InputRoll/Cell.cs | 9 +++++++++
.../CustomControls/InputRoll/InputRoll.cs | 6 +++---
src/BizHawk.Common/CustomCollections.cs | 15 +++++++++++++--
3 files changed, 25 insertions(+), 5 deletions(-)
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.Common/CustomCollections.cs b/src/BizHawk.Common/CustomCollections.cs
index 8e596b7fc1..8f0eec5c63 100644
--- a/src/BizHawk.Common/CustomCollections.cs
+++ b/src/BizHawk.Common/CustomCollections.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Linq;
namespace BizHawk.Common
{
@@ -14,11 +15,15 @@ namespace BizHawk.Common
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();
}
@@ -131,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();
}
}
From 6184afddc994b2585144a135107830f7027cd865 Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Fri, 14 Feb 2025 22:10:10 +0100
Subject: [PATCH 15/27] don't call disengage when tastudio load failed
---
src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
index 9372d7165b..46d55e7abf 100644
--- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
@@ -288,7 +288,6 @@ namespace BizHawk.Client.EmuHawk
// Attempts to load failed, abort
if (!success)
{
- Disengage();
return false;
}
From 59b2dba901f01e408393b97d675ca079c5e02491 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Sat, 15 Feb 2025 09:50:53 +1000
Subject: [PATCH 16/27] When GPGX is in SMS mode, don't advertise
`IDisassemblable`
see #4226/33571d4cd
---
src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
index 1f640ec56c..030a4a03a4 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
@@ -49,6 +49,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
};
}
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
From 9df29c036eea4ecca9f9ee632a65bf7b09549832 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Sun, 16 Feb 2025 05:58:19 +1000
Subject: [PATCH 17/27] Replace non-mutated `List`s w/
arrays/`Span`s/nothing
see #2927 re: `IMemoryApi`
---
.../lua/CommonLibs/CommLuaLibrary.cs | 2 +-
.../RetroAchievements.GameVerification.cs | 22 ++++---
.../Extensions/CollectionExtensions.cs | 30 +++++++++
.../CPUs/Intel8048/Disassembler.cs | 18 ++----
.../CPUs/LR35902/NewDisassembler.cs | 43 ++++++-------
.../CPUs/MC6800/Disassembler.cs | 28 +++------
.../CPUs/MC6809/Disassembler.cs | 62 ++++++++-----------
.../AmstradCPC/Media/Disk/FloppyDisk.cs | 35 ++++-------
.../SinclairSpectrum/Media/Disk/FloppyDisk.cs | 35 ++++-------
.../Media/Tape/PZX/PzxConverter.cs | 11 +---
.../Atari/2600/Atari2600.RomHeuristics.cs | 11 ++--
.../Consoles/Nintendo/NDS/MelonDS.cs | 2 +-
.../Consoles/Nintendo/NES/Boards/Mapper244.cs | 37 +++++------
.../DiscHasher.cs | 9 +--
.../CollectionExtensionTests.cs | 13 ++++
15 files changed, 170 insertions(+), 188 deletions(-)
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.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.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
index e37ac7f988..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
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/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.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()
{
From 46c1273dbde05e452f33dbb3effa378acc21bbd4 Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Mon, 17 Feb 2025 10:25:16 +0100
Subject: [PATCH 18/27] don't restart ram search when changing watch size
- closes #2974
---
.../tools/RamSearchEngine/RamSearchEngine.cs | 49 +++++++++++++++++++
.../tools/Watch/RamSearch.cs | 10 +++-
2 files changed, 57 insertions(+), 2 deletions(-)
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/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()
From 72f880db270a21a440aef8ce24d3858906ad007d Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Mon, 17 Feb 2025 10:58:01 +0100
Subject: [PATCH 19/27] add default rumble binds
- closes #4081
---
Assets/defctrl.json | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
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
+ }
}
}
}
From d3362f5e0c3454a0c3b1deface94e68045055093 Mon Sep 17 00:00:00 2001
From: YoshiRulz
Date: Tue, 18 Feb 2025 05:12:06 +1000
Subject: [PATCH 20/27] Add novel throttle experiment (locked behind build flag
for now)
:)
---
src/BizHawk.Client.Common/Controller.cs | 5 +++++
src/BizHawk.Client.Common/config/Config.cs | 3 +++
src/BizHawk.Client.EmuHawk/MainForm.cs | 13 +++++++++++++
3 files changed, 21 insertions(+)
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/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs
index e41402915f..c58a6383de 100644
--- a/src/BizHawk.Client.Common/config/Config.cs
+++ b/src/BizHawk.Client.Common/config/Config.cs
@@ -203,6 +203,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.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs
index 8d33094981..42cc3d1feb 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) };
@@ -2976,7 +2985,11 @@ namespace BizHawk.Client.EmuHawk
_frameAdvanceTimestamp = 0;
}
+#if BIZHAWKBUILD_SUPERHAWK
+ if (!EmulatorPaused && (!Config.SuperHawkThrottle || InputManager.ClientControls.AnyInputHeld))
+#else
if (!EmulatorPaused)
+#endif
{
runFrame = true;
}
From 85e2d18780d598b24e18f0bdda78422c508d031a Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Tue, 18 Feb 2025 01:07:11 +0100
Subject: [PATCH 21/27] fix .bkm import
- closes #3909
Effectively best-effort for now, may or may not work for some movies.
---
.../movie/import/BkmImport.cs | 14 +-
.../movie/import/bkm/BkmControllerAdapter.cs | 662 +++---------------
.../import/bkm/BkmControllerDefinition.cs | 19 +
.../movie/import/bkm/BkmMnemonicConstants.cs | 307 ++++----
.../movie/import/bkm/BkmMovie.cs | 13 +-
5 files changed, 281 insertions(+), 734 deletions(-)
create mode 100644 src/BizHawk.Client.Common/movie/import/bkm/BkmControllerDefinition.cs
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/bkm/BkmControllerAdapter.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs
index 797795f505..9c631f092a 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("Power", 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];
From b6a175bddfd53e4e23241531d585e3291b08ef7d Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Tue, 18 Feb 2025 10:38:10 +0100
Subject: [PATCH 22/27] fix SMS controller definition (in Bkm)
---
.../movie/import/bkm/BkmControllerAdapter.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs
index 9c631f092a..9e164cafa5 100644
--- a/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs
+++ b/src/BizHawk.Client.Common/movie/import/bkm/BkmControllerAdapter.cs
@@ -100,7 +100,7 @@ namespace BizHawk.Client.Common
break;
case "SMS Controller":
SetFromMnemonic(mnemonic.AsSpan(1));
- Force("Power", mnemonic[^3] == 'P');
+ Force("Pause", mnemonic[^3] == 'p');
Force("Reset", mnemonic[^2] == 'r');
break;
case "TI83 Controller":
From d386a4fdcb9f2ea5be1d01651ff3998c5b82d7eb Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Tue, 18 Feb 2025 14:26:40 +0100
Subject: [PATCH 23/27] attempt to make rewind perform well while paused
attempts to resolve #3053 by shuffling some bools around.
Specifically, `_runloopFrameProgress` is now set when either (continuously) frame advancing or rewinding.
---
src/BizHawk.Client.EmuHawk/MainForm.cs | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs
index 42cc3d1feb..efb3a14a53 100644
--- a/src/BizHawk.Client.EmuHawk/MainForm.cs
+++ b/src/BizHawk.Client.EmuHawk/MainForm.cs
@@ -2922,7 +2922,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;
@@ -2946,14 +2945,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)
@@ -2975,13 +2981,6 @@ namespace BizHawk.Client.EmuHawk
}
else
{
- // handle release of frame advance: do we need to deactivate FrameProgress?
- if (_runloopFrameProgress)
- {
- _runloopFrameProgress = false;
- PauseEmulator();
- }
-
_frameAdvanceTimestamp = 0;
}
@@ -2995,6 +2994,7 @@ namespace BizHawk.Client.EmuHawk
}
bool isRewinding = Rewind(ref runFrame, currentTimestamp, out var returnToRecording);
+ _runloopFrameProgress |= isRewinding;
float atten = 0;
From 52766e67fcf0df1c32ec3c15325302a728e156db Mon Sep 17 00:00:00 2001
From: Morilli <35152647+Morilli@users.noreply.github.com>
Date: Tue, 18 Feb 2025 16:54:05 +0100
Subject: [PATCH 24/27] fix recent waterboxhost warnings
---
waterbox/waterboxhost/src/cinterface.rs | 40 +++++++++----------
waterbox/waterboxhost/src/gdb.rs | 2 +-
waterbox/waterboxhost/src/host.rs | 2 +-
.../src/memory_block/tripguard.rs | 6 +--
waterbox/waterboxhost/src/threading.rs | 2 +-
5 files changed, 26 insertions(+), 26 deletions(-)
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 | |