convert .mmv import to new style, properly import game gear flag

This commit is contained in:
adelikat 2019-11-12 15:30:39 -06:00
parent 5eb54dbf9e
commit fb8111d630
3 changed files with 151 additions and 149 deletions

View File

@ -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" />

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}