convert .mmv import to new style, properly import game gear flag
This commit is contained in:
parent
5eb54dbf9e
commit
fb8111d630
|
@ -159,6 +159,7 @@
|
|||
<Compile Include="movie\import\GmvImport.cs" />
|
||||
<Compile Include="movie\import\LsmvImport.cs" />
|
||||
<Compile Include="movie\import\Mc2Import.cs" />
|
||||
<Compile Include="movie\import\MmvImport.cs" />
|
||||
<Compile Include="movie\import\PxmImport.cs" />
|
||||
<Compile Include="movie\import\SmvImport.cs" />
|
||||
<Compile Include="movie\import\VbmImport.cs" />
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
using System.IO;
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
using BizHawk.Emulation.Cores.Sega.MasterSystem;
|
||||
|
||||
namespace BizHawk.Client.Common.movie.import
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
// MMV file format: http://tasvideos.org/MMV.html
|
||||
[ImportExtension(".mmv")]
|
||||
public class MmvImport : MovieImporter
|
||||
{
|
||||
protected override void RunImport()
|
||||
{
|
||||
using var fs = SourceFile.Open(FileMode.Open, FileAccess.Read);
|
||||
using var r = new BinaryReader(fs);
|
||||
|
||||
// 0000: 4-byte signature: "MMV\0"
|
||||
string signature = new string(r.ReadChars(4));
|
||||
if (signature != "MMV\0")
|
||||
{
|
||||
Result.Errors.Add("This is not a valid .MMV file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 0004: 4-byte little endian unsigned int: dega version
|
||||
uint emuVersion = r.ReadUInt32();
|
||||
Result.Movie.Comments.Add($"{MovieOrigin} .MMV");
|
||||
Result.Movie.Comments.Add($"{EmulationOrigin} Dega version {emuVersion}");
|
||||
|
||||
// 0008: 4-byte little endian unsigned int: frame count
|
||||
uint frameCount = r.ReadUInt32();
|
||||
|
||||
// 000c: 4-byte little endian unsigned int: rerecord count
|
||||
uint rerecordCount = r.ReadUInt32();
|
||||
Result.Movie.Rerecords = rerecordCount;
|
||||
|
||||
|
||||
// 0010: 4-byte little endian flag: begin from reset?
|
||||
uint reset = r.ReadUInt32();
|
||||
if (reset == 0)
|
||||
{
|
||||
Result.Errors.Add("Movies that begin with a savestate are not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 0014: 4-byte little endian unsigned int: offset of state information
|
||||
r.ReadUInt32();
|
||||
|
||||
// 0018: 4-byte little endian unsigned int: offset of input data
|
||||
r.ReadUInt32();
|
||||
|
||||
// 001c: 4-byte little endian unsigned int: size of input packet
|
||||
r.ReadUInt32();
|
||||
|
||||
// 0020-005f: string: author info (UTF-8)
|
||||
string author = NullTerminated(new string(r.ReadChars(64)));
|
||||
Result.Movie.HeaderEntries[HeaderKeys.AUTHOR] = author;
|
||||
|
||||
// 0060: 4-byte little endian flags
|
||||
byte flags = r.ReadByte();
|
||||
|
||||
// bit 0: unused
|
||||
// bit 1: "PAL"
|
||||
bool pal = ((flags >> 1) & 0x1) != 0;
|
||||
Result.Movie.HeaderEntries[HeaderKeys.PAL] = pal.ToString();
|
||||
|
||||
// bit 2: Japan
|
||||
bool japan = ((flags >> 2) & 0x1) != 0;
|
||||
Result.Movie.HeaderEntries["Japan"] = japan.ToString();
|
||||
|
||||
// bit 3: Game Gear (version 1.16+)
|
||||
bool isGameGear;
|
||||
if (((flags >> 3) & 0x1) != 0)
|
||||
{
|
||||
isGameGear = true;
|
||||
Result.Movie.HeaderEntries.Add("IsGGMode", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
isGameGear = false;
|
||||
}
|
||||
|
||||
Result.Movie.HeaderEntries[HeaderKeys.PLATFORM] = "SMS"; // System Id is still SMS even if game gear
|
||||
|
||||
// bits 4-31: unused
|
||||
r.ReadBytes(3);
|
||||
|
||||
// 0064-00e3: string: rom name (ASCII)
|
||||
string gameName = NullTerminated(new string(r.ReadChars(128)));
|
||||
Result.Movie.HeaderEntries[HeaderKeys.GAMENAME] = gameName;
|
||||
|
||||
// 00e4-00f3: binary: rom MD5 digest
|
||||
byte[] md5 = r.ReadBytes(16);
|
||||
Result.Movie.HeaderEntries[MD5] = md5.BytesToHexString().ToLower();
|
||||
|
||||
var ss = new SMS.SMSSyncSettings
|
||||
{
|
||||
ControllerType = "Standard"
|
||||
};
|
||||
|
||||
var controllers = new SimpleController
|
||||
{
|
||||
Definition = isGameGear
|
||||
? SMS.SmsController
|
||||
: SMS.GGController
|
||||
};
|
||||
|
||||
/*
|
||||
76543210
|
||||
* bit 0 (0x01): up
|
||||
* bit 1 (0x02): down
|
||||
* bit 2 (0x04): left
|
||||
* bit 3 (0x08): right
|
||||
* bit 4 (0x10): 1
|
||||
* bit 5 (0x20): 2
|
||||
* bit 6 (0x40): start (Master System)
|
||||
* bit 7 (0x80): start (Game Gear)
|
||||
*/
|
||||
string[] buttons = { "Up", "Down", "Left", "Right", "B1", "B2" };
|
||||
for (int frame = 1; frame <= frameCount; frame++)
|
||||
{
|
||||
/*
|
||||
Controller data is made up of one input packet per frame. Each packet currently consists of 2 bytes. The
|
||||
first byte is for controller 1 and the second controller 2. The Game Gear only uses the controller 1 input
|
||||
however both bytes are still present.
|
||||
*/
|
||||
for (int player = 1; player <= 2; player++)
|
||||
{
|
||||
byte controllerState = r.ReadByte();
|
||||
for (int button = 0; button < buttons.Length; button++)
|
||||
{
|
||||
controllers[$"P{player} {buttons[button]}"] = ((controllerState >> button) & 0x1) != 0;
|
||||
}
|
||||
|
||||
if (player == 1)
|
||||
{
|
||||
controllers["Pause"] =
|
||||
(((controllerState >> 6) & 0x1) != 0 && (!isGameGear))
|
||||
|| (((controllerState >> 7) & 0x1) != 0 && isGameGear);
|
||||
}
|
||||
}
|
||||
|
||||
Result.Movie.AppendFrame(controllers);
|
||||
}
|
||||
|
||||
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(ss);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -128,9 +128,6 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
switch (ext)
|
||||
{
|
||||
case ".MMV":
|
||||
m = ImportMmv(path, out errorMsg, out warningMsg);
|
||||
break;
|
||||
case ".BKM":
|
||||
m.Filename = path;
|
||||
m.Load(false);
|
||||
|
@ -171,154 +168,9 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
string[] extensions =
|
||||
{
|
||||
"BKM", "MMV"
|
||||
"BKM"
|
||||
};
|
||||
return extensions.Any(ext => extension.ToUpper() == $".{ext}");
|
||||
}
|
||||
|
||||
// Ends the string where a NULL character is found.
|
||||
private static string NullTerminated(string str)
|
||||
{
|
||||
int pos = str.IndexOf('\0');
|
||||
if (pos != -1)
|
||||
{
|
||||
str = str.Substring(0, pos);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// MMV file format: http://tasvideos.org/MMV.html
|
||||
private static BkmMovie ImportMmv(string path, out string errorMsg, out string warningMsg)
|
||||
{
|
||||
errorMsg = warningMsg = "";
|
||||
BkmMovie m = new BkmMovie(path);
|
||||
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
BinaryReader r = new BinaryReader(fs);
|
||||
|
||||
// 0000: 4-byte signature: "MMV\0"
|
||||
string signature = r.ReadStringFixedAscii(4);
|
||||
if (signature != "MMV\0")
|
||||
{
|
||||
errorMsg = "This is not a valid .MMV file.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
|
||||
// 0004: 4-byte little endian unsigned int: dega version
|
||||
uint emuVersion = r.ReadUInt32();
|
||||
m.Comments.Add($"{EMULATIONORIGIN} Dega version {emuVersion}");
|
||||
m.Comments.Add($"{MOVIEORIGIN} .MMV");
|
||||
|
||||
// 0008: 4-byte little endian unsigned int: frame count
|
||||
uint frameCount = r.ReadUInt32();
|
||||
|
||||
// 000c: 4-byte little endian unsigned int: rerecord count
|
||||
uint rerecordCount = r.ReadUInt32();
|
||||
m.Rerecords = rerecordCount;
|
||||
|
||||
// 0010: 4-byte little endian flag: begin from reset?
|
||||
uint reset = r.ReadUInt32();
|
||||
if (reset == 0)
|
||||
{
|
||||
errorMsg = "Movies that begin with a savestate are not supported.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
|
||||
// 0014: 4-byte little endian unsigned int: offset of state information
|
||||
r.ReadUInt32();
|
||||
|
||||
// 0018: 4-byte little endian unsigned int: offset of input data
|
||||
r.ReadUInt32();
|
||||
|
||||
// 001c: 4-byte little endian unsigned int: size of input packet
|
||||
r.ReadUInt32();
|
||||
|
||||
// 0020-005f: string: author info (UTF-8)
|
||||
string author = NullTerminated(r.ReadStringFixedAscii(64));
|
||||
m.Header[HeaderKeys.AUTHOR] = author;
|
||||
|
||||
// 0060: 4-byte little endian flags
|
||||
byte flags = r.ReadByte();
|
||||
|
||||
// bit 0: unused
|
||||
// bit 1: "PAL"
|
||||
bool pal = ((flags >> 1) & 0x1) != 0;
|
||||
m.Header[HeaderKeys.PAL] = pal.ToString();
|
||||
|
||||
// bit 2: Japan
|
||||
bool japan = ((flags >> 2) & 0x1) != 0;
|
||||
m.Header[JAPAN] = japan.ToString();
|
||||
|
||||
// bit 3: Game Gear (version 1.16+)
|
||||
bool gamegear;
|
||||
if (((flags >> 3) & 0x1) != 0)
|
||||
{
|
||||
gamegear = true;
|
||||
m.Header[HeaderKeys.PLATFORM] = "GG";
|
||||
}
|
||||
else
|
||||
{
|
||||
gamegear = false;
|
||||
m.Header[HeaderKeys.PLATFORM] = "SMS";
|
||||
}
|
||||
|
||||
// bits 4-31: unused
|
||||
r.ReadBytes(3);
|
||||
|
||||
// 0064-00e3: string: rom name (ASCII)
|
||||
string gameName = NullTerminated(r.ReadStringFixedAscii(128));
|
||||
m.Header[HeaderKeys.GAMENAME] = gameName;
|
||||
|
||||
// 00e4-00f3: binary: rom MD5 digest
|
||||
byte[] md5 = r.ReadBytes(16);
|
||||
m.Header[MD5] = $"{md5.BytesToHexString().ToLower():x8}";
|
||||
var controllers = new SimpleController { Definition = new ControllerDefinition { Name = "SMS Controller" } };
|
||||
|
||||
/*
|
||||
76543210
|
||||
* bit 0 (0x01): up
|
||||
* bit 1 (0x02): down
|
||||
* bit 2 (0x04): left
|
||||
* bit 3 (0x08): right
|
||||
* bit 4 (0x10): 1
|
||||
* bit 5 (0x20): 2
|
||||
* bit 6 (0x40): start (Master System)
|
||||
* bit 7 (0x80): start (Game Gear)
|
||||
*/
|
||||
string[] buttons = { "Up", "Down", "Left", "Right", "B1", "B2" };
|
||||
for (int frame = 1; frame <= frameCount; frame++)
|
||||
{
|
||||
/*
|
||||
Controller data is made up of one input packet per frame. Each packet currently consists of 2 bytes. The
|
||||
first byte is for controller 1 and the second controller 2. The Game Gear only uses the controller 1 input
|
||||
however both bytes are still present.
|
||||
*/
|
||||
for (int player = 1; player <= 2; player++)
|
||||
{
|
||||
byte controllerState = r.ReadByte();
|
||||
for (int button = 0; button < buttons.Length; button++)
|
||||
{
|
||||
controllers[$"P{player} {buttons[button]}"] = ((controllerState >> button) & 0x1) != 0;
|
||||
}
|
||||
|
||||
if (player == 1)
|
||||
{
|
||||
controllers["Pause"] =
|
||||
(((controllerState >> 6) & 0x1) != 0 && (!gamegear))
|
||||
|| (((controllerState >> 7) & 0x1) != 0 && gamegear);
|
||||
}
|
||||
}
|
||||
|
||||
m.AppendFrame(controllers);
|
||||
}
|
||||
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue