Change SmvImport to import to Snes9x core; various fixes

the importer is still far from perfect, but it should handle some things better than before

- closes #3177
This commit is contained in:
Morilli 2023-08-13 19:59:37 +02:00
parent 0af4d125b2
commit 9131c8ab2e
4 changed files with 205 additions and 204 deletions

View File

@ -1,10 +1,11 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores;
using BizHawk.Emulation.Cores.Nintendo.SNES;
using BizHawk.Emulation.Cores.Nintendo.SNES9X;
namespace BizHawk.Client.Common.movie.import
{
@ -13,11 +14,9 @@ namespace BizHawk.Client.Common.movie.import
[ImporterFor("Snes9x", ".smv")]
internal class SmvImport : MovieImporter
{
private LibsnesControllerDeck _deck;
protected override void RunImport()
{
Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.Bsnes; //TODO set to Snes9x instead? or give the user the choice?
Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.Snes9X;
using var fs = SourceFile.Open(FileMode.Open, FileAccess.Read);
using var r = new BinaryReader(fs);
@ -74,32 +73,23 @@ namespace BizHawk.Client.Common.movie.import
controllersUsed[controller - 1] = ((controllerFlags >> (controller - 1)) & 0x1) != 0;
}
var controllerCount = controllersUsed.Count(c => c);
int highestControllerIndex = 1 + Array.LastIndexOf(controllersUsed, true);
var ss = new Snes9x.SyncSettings();
var ss = new LibsnesCore.SnesSyncSettings
switch (highestControllerIndex)
{
LeftPort = LibsnesControllerDeck.ControllerType.Gamepad,
RightPort = LibsnesControllerDeck.ControllerType.Gamepad
};
if (controllerCount == 1)
{
ss.RightPort = LibsnesControllerDeck.ControllerType.Unplugged;
case 1:
ss.RightPort = LibSnes9x.RightPortDevice.None;
break;
case 3:
case 4:
ss.LeftPort = LibSnes9x.LeftPortDevice.Multitap;
ss.RightPort = LibSnes9x.RightPortDevice.None;
break;
case 5:
ss.LeftPort = LibSnes9x.LeftPortDevice.Multitap;
break;
}
else if (controllerCount > 2)
{
// More than 2 controllers means a multi-tap on the first port
// Snes9x only supported up to 5 controllers, so right port would never be multitap
ss.LeftPort = LibsnesControllerDeck.ControllerType.Multitap;
// Unless there are exactly 5, right port is unplugged, as the multitap will handle 4 controllers
if (controllerCount < 5)
{
ss.RightPort = LibsnesControllerDeck.ControllerType.Unplugged;
}
}
_deck = new LibsnesControllerDeck(ss);
// 015 1-byte flags "movie options"
byte movieFlags = r.ReadByte();
@ -200,7 +190,9 @@ namespace BizHawk.Client.Common.movie.import
Result.Movie.HeaderEntries[HeaderKeys.GameName] = gameName;
}
SimpleController controllers = new(_deck.Definition);
var _controllers = new Snes9xControllers(ss);
Result.Movie.LogKey = new Bk2LogEntryGenerator("SNES", new Bk2Controller(_controllers.ControllerDefinition)).GenerateLogKey();
SimpleController controllers = new(_controllers.ControllerDefinition);
r.BaseStream.Position = firstFrameOffset;
/*
@ -252,15 +244,25 @@ namespace BizHawk.Client.Common.movie.import
controllers["Reset"] = false;
}
/*
While the meaning of controller data (for 1.51 and up) for a single standard SNES controller pad
remains the same, each frame of controller data can contain additional bytes if input for peripherals
is being recorded.
*/
if (version != "1.43" && player <= controllerTypes.Length)
ushort controllerState = (ushort)(((controllerState1 << 4) & 0x0F00) | controllerState2);
for (int button = 0; button < buttons.Length; button++)
{
controllers[$"P{player} {buttons[button]}"] =
((controllerState >> button) & 0x1) != 0;
}
}
/*
While the meaning of controller data (for 1.51 and up) for a single standard SNES controller pad
remains the same, each frame of controller data can contain additional bytes if input for peripherals
is being recorded.
*/
if (version != "1.43")
{
for (int i = 0; i < 2; i++)
{
var peripheral = "";
switch (controllerTypes[player - 1])
switch (controllerTypes[i])
{
case 0: // NONE
continue;
@ -292,13 +294,6 @@ namespace BizHawk.Client.Common.movie.import
Result.Warnings.Add($"Unable to import {peripheral}. Not supported yet");
}
}
ushort controllerState = (ushort)(((controllerState1 << 4) & 0x0F00) | controllerState2);
for (int button = 0; button < buttons.Length; button++)
{
controllers[$"P{player} {buttons[button]}"] =
((controllerState >> button) & 0x1) != 0;
}
}
// The controller data contains <number_of_frames + 1> frames.
@ -311,7 +306,6 @@ namespace BizHawk.Client.Common.movie.import
}
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(ss);
MaybeSetCorePreference(VSystemID.Raw.SNES, CoreNames.Bsnes, fileExt: ".smv");
}
}
}

View File

@ -7,13 +7,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X
{
public enum LeftPortDevice : uint
{
//None = 0, // something in the libretro spaghetti input goes wonky with None
Joypad = 1
None = 0,
Joypad = 1,
Multitap = 2
}
public enum RightPortDevice : uint
{
//None = 0, // something in the libretro spaghetti input goes wonky with None
None = 0,
Joypad = 1,
Multitap = 2,
Mouse = 3,

View File

@ -71,168 +71,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X
PutSettings(settings);
}
private readonly short[] _inputState = new short[16 * 8];
private List<ControlDefUnMerger> _cdums;
private readonly List<IControlDevice> _controllers = new List<IControlDevice>();
private void InitControllers()
{
_core.biz_set_port_devices(_syncSettings.LeftPort, _syncSettings.RightPort);
switch (_syncSettings.LeftPort)
{
case LibSnes9x.LeftPortDevice.Joypad:
_controllers.Add(new Joypad());
break;
}
switch (_syncSettings.RightPort)
{
case LibSnes9x.RightPortDevice.Joypad:
_controllers.Add(new Joypad());
break;
case LibSnes9x.RightPortDevice.Multitap:
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
break;
case LibSnes9x.RightPortDevice.Mouse:
_controllers.Add(new Mouse());
break;
case LibSnes9x.RightPortDevice.SuperScope:
_controllers.Add(new SuperScope());
break;
case LibSnes9x.RightPortDevice.Justifier:
_controllers.Add(new Justifier());
break;
}
_controllerDefinition = ControllerDefinitionMerger.GetMerged(
"SNES Controller",
_controllers.Select(c => c.Definition),
out _cdums);
// add buttons that the core itself will handle
_controllerDefinition.BoolButtons.Add("Reset");
_controllerDefinition.BoolButtons.Add("Power");
_controllerDefinition.MakeImmutable();
_controllers = new Snes9xControllers(_syncSettings);
}
private void UpdateControls(IController c)
{
Array.Clear(_inputState, 0, 16 * 8);
for (int i = 0, offset = 0; i < _controllers.Count; i++, offset += 16)
{
_controllers[i].ApplyState(_cdums[i].UnMerge(c), _inputState, offset);
}
}
private Snes9xControllers _controllers;
private interface IControlDevice
{
ControllerDefinition Definition { get; }
void ApplyState(IController controller, short[] input, int offset);
}
private class Joypad : IControlDevice
{
private static readonly string[] Buttons =
{
"0B",
"0Y",
"0Select",
"0Start",
"0Up",
"0Down",
"0Left",
"0Right",
"0A",
"0X",
"0L",
"0R"
};
private static int ButtonOrder(string btn)
{
var order = new Dictionary<string, int>
{
["0Up"] = 0,
["0Down"] = 1,
["0Left"] = 2,
["0Right"] = 3,
["0Select"] = 4,
["0Start"] = 5,
["0Y"] = 6,
["0B"] = 7,
["0X"] = 8,
["0A"] = 9,
["0L"] = 10,
["0R"] = 11
};
return order[btn];
}
private static readonly ControllerDefinition _definition = new("(SNES Controller fragment)")
{
BoolButtons = Buttons.OrderBy(ButtonOrder).ToList()
};
public ControllerDefinition Definition { get; } = _definition;
public void ApplyState(IController controller, short[] input, int offset)
{
for (int i = 0; i < Buttons.Length; i++)
input[offset + i] = (short)(controller.IsPressed(Buttons[i]) ? 1 : 0);
}
}
private abstract class Analog : IControlDevice
{
public abstract ControllerDefinition Definition { get; }
public void ApplyState(IController controller, short[] input, int offset)
{
foreach (var s in Definition.Axes.Keys)
input[offset++] = (short)controller.AxisValue(s);
foreach (var s in Definition.BoolButtons)
input[offset++] = (short)(controller.IsPressed(s) ? 1 : 0);
}
}
private class Mouse : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Mouse Left", "0Mouse Right" } }
.AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); //TODO verify direction against hardware
public override ControllerDefinition Definition => _definition;
}
private class SuperScope : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Trigger", "0Cursor", "0Turbo", "0Pause", "0Offscreen" } }
.AddLightGun("0Scope {0}");
public override ControllerDefinition Definition => _definition;
}
private class Justifier : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Trigger", "0Start", "0Offscreen" } }
.AddLightGun("0Justifier {0}");
public override ControllerDefinition Definition => _definition;
}
private ControllerDefinition _controllerDefinition;
public override ControllerDefinition ControllerDefinition => _controllerDefinition;
public override ControllerDefinition ControllerDefinition => _controllers.ControllerDefinition;
public DisplayType Region { get; }
@ -242,8 +90,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X
_core.biz_hard_reset();
else if (controller.IsPressed("Reset"))
_core.biz_soft_reset();
UpdateControls(controller);
_core.SetButtons(_inputState);
_controllers.UpdateControls(controller);
_core.SetButtons(_controllers.inputState);
return new LibWaterboxCore.FrameInfo();
}

View File

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Nintendo.SNES;
namespace BizHawk.Emulation.Cores.Nintendo.SNES9X
{
public class Snes9xControllers
{
private readonly List<ControlDefUnMerger> _cdums;
private readonly List<IControlDevice> _controllers = new();
internal readonly short[] inputState = new short[16 * 8];
public ControllerDefinition ControllerDefinition { get; }
public Snes9xControllers(Snes9x.SyncSettings ss)
{
switch (ss.LeftPort)
{
case LibSnes9x.LeftPortDevice.Joypad:
_controllers.Add(new Joypad());
break;
case LibSnes9x.LeftPortDevice.Multitap:
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
break;
}
switch (ss.RightPort)
{
case LibSnes9x.RightPortDevice.Joypad:
_controllers.Add(new Joypad());
break;
case LibSnes9x.RightPortDevice.Multitap:
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
_controllers.Add(new Joypad());
break;
case LibSnes9x.RightPortDevice.Mouse:
_controllers.Add(new Mouse());
break;
case LibSnes9x.RightPortDevice.SuperScope:
_controllers.Add(new SuperScope());
break;
case LibSnes9x.RightPortDevice.Justifier:
_controllers.Add(new Justifier());
break;
}
ControllerDefinition = ControllerDefinitionMerger.GetMerged(
"SNES Controller",
_controllers.Select(c => c.Definition),
out _cdums);
// add buttons that the core itself will handle
ControllerDefinition.BoolButtons.Add("Reset");
ControllerDefinition.BoolButtons.Add("Power");
ControllerDefinition.MakeImmutable();
}
internal void UpdateControls(IController c)
{
Array.Clear(inputState, 0, 16 * 8);
for (int i = 0, offset = 0; i < _controllers.Count; i++, offset += 16)
{
_controllers[i]
.ApplyState(_cdums[i]
.UnMerge(c), inputState, offset);
}
}
private interface IControlDevice
{
ControllerDefinition Definition { get; }
void ApplyState(IController controller, short[] input, int offset);
}
private class Joypad : IControlDevice
{
private static readonly string[] Buttons =
{
"0B", "0Y", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right", "0A", "0X", "0L", "0R"
};
private static int ButtonOrder(string btn)
{
var order = new Dictionary<string, int>
{
["0Up"] = 0, ["0Down"] = 1, ["0Left"] = 2, ["0Right"] = 3, ["0Select"] = 4, ["0Start"] = 5, ["0Y"] = 6, ["0B"] = 7, ["0X"] = 8, ["0A"] = 9
, ["0L"] = 10, ["0R"] = 11
};
return order[btn];
}
private static readonly ControllerDefinition _definition = new("(SNES Controller fragment)")
{
BoolButtons = Buttons.OrderBy(ButtonOrder)
.ToList()
};
public ControllerDefinition Definition { get; } = _definition;
public void ApplyState(IController controller, short[] input, int offset)
{
for (int i = 0; i < Buttons.Length; i++)
input[offset + i] = (short)(controller.IsPressed(Buttons[i]) ? 1 : 0);
}
}
private abstract class Analog : IControlDevice
{
public abstract ControllerDefinition Definition { get; }
public void ApplyState(IController controller, short[] input, int offset)
{
foreach (var s in Definition.Axes.Keys)
input[offset++] = (short)controller.AxisValue(s);
foreach (var s in Definition.BoolButtons)
input[offset++] = (short)(controller.IsPressed(s) ? 1 : 0);
}
}
private class Mouse : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Mouse Left", "0Mouse Right" } }
.AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); //TODO verify direction against hardware
public override ControllerDefinition Definition => _definition;
}
private class SuperScope : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Trigger", "0Cursor", "0Turbo", "0Pause", "0Offscreen" } }
.AddLightGun("0Scope {0}");
public override ControllerDefinition Definition => _definition;
}
private class Justifier : Analog
{
private static readonly ControllerDefinition _definition
= new ControllerDefinition("(SNES Controller fragment)") { BoolButtons = { "0Trigger", "0Start", "0Offscreen" } }
.AddLightGun("0Justifier {0}");
public override ControllerDefinition Definition => _definition;
}
}
}