-.LSMV importer.

--Handled authors, gametype, systemid, coreversion, rom.sha256, moviesram.*, savestate, and subtitles.
--Skipped port1, port2, controlsversion, projectid, saveframe, lagcounter, pollcounters, hostmemory, screenshot, sram.*, rrdata, starttime.*, savetime.*, and prefix.
---I don't think any of these are useful for us, at least not at this stage.
---The other *.sha256 files will be useful if / when BS-X, Sufami turbo, and SGB are implemented.
--Handled flags.
---Handled resets.
---Disallowed subframes and delayed resets.
-.LSMV is good enough for now. .SMV and .ZMV coming next.
This commit is contained in:
brandman211 2012-09-13 10:21:25 +00:00
parent 2d2af1d143
commit 7f2a18b3af
1 changed files with 187 additions and 38 deletions

View File

@ -12,6 +12,7 @@ namespace BizHawk.MultiClient
public static class MovieImport
{
public const string COMMENT = "comment";
public const string COREORIGIN = "CoreOrigin";
public const string EMULATIONORIGIN = "emuOrigin";
public const string MOVIEORIGIN = "MovieOrigin";
public const string SYNCHACK = "SyncHack";
@ -91,8 +92,26 @@ namespace BizHawk.MultiClient
return false;
}
// Reduce all whitespace to single spaces.
private static string SingleSpaces(string line)
{
line = line.Replace("\t", " ");
line = line.Replace("\n", " ");
line = line.Replace("\r", " ");
line = line.Replace("\r\n", " ");
string prev;
do
{
prev = line;
line = line.Replace(" ", " ");
}
while (prev != line);
return line;
}
// Import a frame from a text-based format.
private static Movie ImportTextFrame(string line, int lineNum, Movie m, string path, ref string warningMsg)
private static Movie ImportTextFrame(string line, int lineNum, Movie m, string path, ref string warningMsg,
ref string errorMsg)
{
string[] buttons = new string[] { };
string controller = "";
@ -119,7 +138,7 @@ namespace BizHawk.MultiClient
MnemonicsGenerator mg = new MnemonicsGenerator();
// Split up the sections of the frame.
string[] sections = line.Split('|');
if (Path.GetExtension(path).ToUpper() == ".FM2" && sections[1].Length != 0)
if (Path.GetExtension(path).ToUpper() == ".FM2" && sections.Length >= 2 && sections[1].Length != 0)
{
controllers["Reset"] = (sections[1][0] == '1');
// Get the first invalid command warning message that arises.
@ -149,6 +168,24 @@ namespace BizHawk.MultiClient
warningMsg = "Unable to import " + warningMsg + " command on line " + lineNum + ".";
}
}
if (Path.GetExtension(path).ToUpper() == ".LSMV" && sections.Length != 0)
{
string flags = sections[0];
char[] off = { '.', ' ', '\t', '\n' };
if (flags.Length == 0 || off.Contains(flags[0]))
{
errorMsg = "Subframes are not supported.";
return null;
}
bool reset = (flags.Length >= 2 && !off.Contains(flags[1]));
flags = SingleSpaces(flags.Substring(2));
if (reset && ((flags.Length >= 2 && flags[1] != '0') || (flags.Length >= 4 && flags[3] != '0')))
{
errorMsg = "Delayed resets are not supported.";
return null;
}
controllers["Reset"] = reset;
}
/*
Skip the first two sections of the split, which consist of everything before the starting | and the command.
Do not use the section after the last |. In other words, get the sections for the players.
@ -185,30 +222,30 @@ namespace BizHawk.MultiClient
}
// Import a subtitle from a text-based format.
private static Movie ImportTextSubtitle(string line, Movie m)
private static Movie ImportTextSubtitle(string line, Movie m, string path)
{
Subtitle s = new Subtitle();
// Reduce all whitespace to single spaces.
line = line.Replace("\t", " ");
line = line.Replace("\n", " ");
line = line.Replace("\r", " ");
line = line.Replace("\r\n", " ");
string prev;
do
{
prev = line;
line = line.Replace(" ", " ");
}
while (prev != line);
line = SingleSpaces(line);
// The header name, frame, and message are separated by whitespace.
int first = line.IndexOf(' ');
int second = line.IndexOf(' ', first + 1);
if (first != -1 && second != -1)
{
// Concatenate the frame and message with default values for the additional fields.
string frame = line.Substring(first + 1, second - first - 1);
string message = line.Substring(second + 1).Trim();
m.Subtitles.AddSubtitle("subtitle " + frame + " 0 0 200 FFFFFFFF " + message);
string frame;
string message;
string length;
if (Path.GetExtension(path).ToUpper() != ".LSMV")
{
frame = line.Substring(first + 1, second - first - 1);
length = "200";
}
else
{
frame = line.Substring(0, first);
length = line.Substring(first + 1, second - first - 1);
}
message = line.Substring(second + 1).Trim();
m.Subtitles.AddSubtitle("subtitle " + frame + " 0 0 " + length + " FFFFFFFF " + message);
}
return m;
}
@ -243,9 +280,16 @@ namespace BizHawk.MultiClient
if (line == "")
continue;
else if (line[0] == '|')
m = ImportTextFrame(line, lineNum, m, path, ref warningMsg);
{
m = ImportTextFrame(line, lineNum, m, path, ref warningMsg, ref errorMsg);
if (errorMsg != "")
{
sr.Close();
return null;
}
}
else if (line.ToLower().StartsWith("sub"))
m = ImportTextSubtitle(line, m);
m = ImportTextSubtitle(line, m, path);
else if (line.ToLower().StartsWith("emuversion"))
m.Header.Comments.Add(EMULATIONORIGIN + " " + emulator + " version " + ParseHeader(line, "emuVersion"));
else if (line.ToLower().StartsWith("version"))
@ -408,9 +452,9 @@ namespace BizHawk.MultiClient
/*
bit 2:
* if "0", NTSC timing
* if "1", PAL timing
Starting with version 0.98.12 released on September 19, 2004, a PAL flag was added to the header but
unfortunately it is not reliable - the emulator does not take the PAL setting from the ROM, but from a user
* if "1", "PAL" timing
Starting with version 0.98.12 released on September 19, 2004, a "PAL" flag was added to the header but
unfortunately it is not reliable - the emulator does not take the "PAL" setting from the ROM, but from a user
preference. This means that this site cannot calculate movie lengths reliably.
*/
bool pal = (((flags >> 2) & 1) == 1);
@ -692,7 +736,7 @@ namespace BizHawk.MultiClient
return m;
}
/*
The file format has no means of identifying NTSC/PAL. It is always assumed that the game is NTSC - that is, 60
The file format has no means of identifying NTSC/"PAL". It is always assumed that the game is NTSC - that is, 60
fps.
*/
m.Header.SetHeaderLine("PAL", "False");
@ -782,7 +826,7 @@ namespace BizHawk.MultiClient
byte flags = r.ReadByte();
/*
bit 7 (most significant): if "1", movie runs at 50 frames per second; if "0", movie runs at 60 frames per second
The file format has no means of identifying NTSC/PAL, but the FPS can still be derived from the header.
The file format has no means of identifying NTSC/"PAL", but the FPS can still be derived from the header.
*/
bool pal = (((flags >> 7) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString());
@ -872,9 +916,47 @@ namespace BizHawk.MultiClient
errorMsg = "This is not an archive.";
return null;
}
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "SNES");
foreach (var item in hf.ArchiveItems)
{
if (item.name == "gamename")
if (item.name == "authors")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string authors = Encoding.UTF8.GetString(Util.ReadAllBytes(stream));
string author_list = "";
string author_last = "";
using (StringReader reader = new StringReader(authors))
{
string line;
// Each author is on a different line.
while ((line = reader.ReadLine()) != null)
{
string author = line.Trim();
if (author != "")
{
if (author_last != "")
author_list += author_last + ", ";
author_last = author;
}
}
}
if (author_list != "")
author_list += "and ";
if (author_last != "")
author_list += author_last;
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author_list);
hf.Unbind();
}
else if (item.name == "coreversion")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string coreversion = Encoding.UTF8.GetString(Util.ReadAllBytes(stream)).Trim();
m.Header.Comments.Add(COREORIGIN + " " + coreversion);
hf.Unbind();
}
else if (item.name == "gamename")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
@ -882,6 +964,16 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gamename);
hf.Unbind();
}
else if (item.name == "gametype")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string gametype = Encoding.UTF8.GetString(Util.ReadAllBytes(stream)).Trim();
// TODO: Handle the other types.
bool pal = (gametype == "snes_ntsc");
m.Header.SetHeaderLine("PAL", pal.ToString());
hf.Unbind();
}
else if (item.name == "input")
{
hf.BindArchiveMember(item.index);
@ -893,10 +985,22 @@ namespace BizHawk.MultiClient
lineNum++;
string line;
while ((line = reader.ReadLine()) != null)
m = ImportTextFrame(line, lineNum, m, path, ref warningMsg);
{
m = ImportTextFrame(line, lineNum, m, path, ref warningMsg, ref errorMsg);
if (errorMsg != "")
{
hf.Unbind();
return null;
}
}
}
hf.Unbind();
}
else if (item.name.StartsWith("moviesram."))
{
errorMsg = "Movies that begin with SRAM are not supported.";
return null;
}
else if (item.name == "rerecords")
{
hf.BindArchiveMember(item.index);
@ -915,6 +1019,40 @@ namespace BizHawk.MultiClient
m.Rerecords = rerecordCount;
hf.Unbind();
}
else if (item.name == "rom.sha256")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string rom = Encoding.UTF8.GetString(Util.ReadAllBytes(stream)).Trim();
m.Header.SetHeaderLine("SHA256", rom);
hf.Unbind();
}
else if (item.name == "savestate")
{
errorMsg = "Movies that begin with a savestate are not supported.";
return null;
}
else if (item.name == "subtitles")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string subtitles = Encoding.UTF8.GetString(Util.ReadAllBytes(stream));
using (StringReader reader = new StringReader(subtitles))
{
string line;
while ((line = reader.ReadLine()) != null)
m = ImportTextSubtitle(line, m, path);
}
hf.Unbind();
}
else if (item.name == "systemid")
{
hf.BindArchiveMember(item.index);
var stream = hf.GetStream();
string systemid = Encoding.UTF8.GetString(Util.ReadAllBytes(stream)).Trim();
m.Header.Comments.Add(EMULATIONORIGIN + " " + systemid);
hf.Unbind();
}
}
return m;
}
@ -993,7 +1131,7 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author);
// 099 103-byte Padding 0s
r.ReadBytes(103);
// TODO: Verify if NTSC/PAL mode used for the movie can be detected or not.
// TODO: Verify if NTSC/"PAL" mode used for the movie can be detected or not.
// 100 variable Input data
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
@ -1092,7 +1230,7 @@ namespace BizHawk.MultiClient
// 0060: 4-byte little endian flags
byte flags = r.ReadByte();
// bit 0: unused
// bit 1: PAL
// bit 1: "PAL"
bool pal = (((flags >> 1) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString());
// bit 2: Japan
@ -1316,7 +1454,7 @@ namespace BizHawk.MultiClient
/*
bit 7: Framerate
* if "0", NTSC timing
* if "1", PAL timing
* if "1", "PAL" timing
*/
bool pal = (((data >> 7) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString());
@ -1530,13 +1668,24 @@ namespace BizHawk.MultiClient
// bit 1: if "1", movie starts from reset with an embedded SRAM
bool startfromsram = (((flags >> 1) & 1) == 1);
// other: reserved, set to 0
// We can't start from either save option.
if (startfromquicksave || startfromsram)
// (If both bits 0 and 1 are "1", the movie file is invalid)
if (startfromquicksave && startfromsram)
{
errorMsg = "Movies that begin with a save are not supported.";
// (If both bits 0 and 1 are "1", the movie file is invalid)
if (startfromquicksave && startfromsram)
errorMsg = "This is not a valid .VBM file.";
errorMsg = "This is not a valid .VBM file.";
r.Close();
fs.Close();
return null;
}
if (startfromquicksave)
{
errorMsg = "Movies that begin with a savestate are not supported.";
r.Close();
fs.Close();
return null;
}
if (startfromsram)
{
errorMsg = "Movies that begin with SRAM are not supported.";
r.Close();
fs.Close();
return null;
@ -1782,7 +1931,7 @@ namespace BizHawk.MultiClient
022 BYTE FrameIRQ // FrameIRQ not allowed
*/
r.ReadBytes(3);
// 023 1-byte flag: 0=NTSC (60 Hz), 1=PAL (50 Hz)
// 023 1-byte flag: 0=NTSC (60 Hz), 1="PAL" (50 Hz)
bool pal = (r.ReadByte() == 1);
m.Header.SetHeaderLine("PAL", pal.ToString());
/*