-Applied micro500's flag ANDing syntax to all of my functions...I concede that this is a much simpler way to do this.

-Fixed ImportFMV's movie title; before, I mistook it for the game name.
-Made "comment" and "SyncHack" constants.
-Moved the "ControllerDefinition"s outside of all of the loops.
-ImportGMV:
--Emulation Version => Movie Version.
--Added the PAL header.
--Made it so that the 6-button error is a warning; considering that the code already properly handles the controller properly and all that needs to be done is have Bizhawk actually support that kind of input, I don't feel there's a reason to go so far as to kill the conversion in this case.
--Reformatting.
---Notably, I added a player loop to simplify the frame data.
--It seems that 1937M uses a 6-button recording and 1731M has 2-player input...bizarre. Anyway, this means that ImportGMV should be good now.

TODO: ImportVMV, ImportNMV, ImportMCM, ImportSMV, Atari core? :)
This commit is contained in:
brandman211 2012-03-11 12:24:56 +00:00
parent 575c44f470
commit 79944b2d26
1 changed files with 126 additions and 190 deletions

View File

@ -11,8 +11,10 @@ namespace BizHawk.MultiClient
{ {
public static class MovieImport public static class MovieImport
{ {
public const string COMMENT = "comment";
public const string EMULATIONORIGIN = "emuOrigin"; public const string EMULATIONORIGIN = "emuOrigin";
public const string MOVIEORIGIN = "MovieOrigin"; public const string MOVIEORIGIN = "MovieOrigin";
public const string SYNCHACK = "SyncHack";
// Attempt to import another type of movie file into a movie object. // Attempt to import another type of movie file into a movie object.
public static Movie ImportFile(string path, out string errorMsg, out string warningMsg) public static Movie ImportFile(string path, out string errorMsg, out string warningMsg)
@ -111,6 +113,9 @@ namespace BizHawk.MultiClient
break; break;
} }
m.Header.SetHeaderLine(MovieHeader.PLATFORM, platform); m.Header.SetHeaderLine(MovieHeader.PLATFORM, platform);
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = controller;
int lineNum = 0; int lineNum = 0;
string line = ""; string line = "";
while ((line = sr.ReadLine()) != null) while ((line = sr.ReadLine()) != null)
@ -204,10 +209,6 @@ namespace BizHawk.MultiClient
ArrayList frame = new ArrayList(); ArrayList frame = new ArrayList();
// Split up the sections of the frame. // Split up the sections of the frame.
string[] sections = line.Split('|'); string[] sections = line.Split('|');
// Start building the data for the controllers.
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = controller;
// Get the first invalid command warning message that arises. // Get the first invalid command warning message that arises.
if (Path.GetExtension(path).ToUpper() == ".FM2" && warningMsg == "" && sections[1].Length != 0) if (Path.GetExtension(path).ToUpper() == ".FM2" && warningMsg == "" && sections[1].Length != 0)
{ {
@ -344,7 +345,7 @@ namespace BizHawk.MultiClient
* if "0", movie begins from an embedded "quicksave" snapshot * if "0", movie begins from an embedded "quicksave" snapshot
* if "1", movie begins from reset or power-on[1] * if "1", movie begins from reset or power-on[1]
*/ */
if ((flags & 2) == 0) if (((flags >> 1) & 1) == 0)
{ {
errorMsg = "Movies that begin with a savestate are not supported."; errorMsg = "Movies that begin with a savestate are not supported.";
r.Close(); r.Close();
@ -355,18 +356,15 @@ namespace BizHawk.MultiClient
bit 2: bit 2:
* if "0", NTSC timing * if "0", NTSC timing
* if "1", PAL timing * if "1", PAL timing
* see notes below
*/
bool pal = ((flags & 4) != 0);
// other: reserved, set to 0
/*
Starting with version 0.98.12 released on September 19, 2004, a PAL flag was added to the header but 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 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. preference. This means that this site cannot calculate movie lengths reliably.
*/ */
bool pal = (((flags >> 2) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString()); m.Header.SetHeaderLine("PAL", pal.ToString());
bool movieSyncHackOn = ((flags & 16) != 0); // other: reserved, set to 0
m.Header.Comments.Add("SyncHack " + movieSyncHackOn.ToString()); bool syncHack = (((flags >> 4) & 1) == 1);
m.Header.Comments.Add(SYNCHACK + " " + syncHack.ToString());
/* /*
009 1-byte flags: reserved, set to 0 009 1-byte flags: reserved, set to 0
00A 1-byte flags: reserved, set to 0 00A 1-byte flags: reserved, set to 0
@ -417,13 +415,13 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author); m.Header.SetHeaderLine(MovieHeader.AUTHOR, author);
// Advance to first byte of input data. // Advance to first byte of input data.
r.BaseStream.Position = firstFrameOffset; r.BaseStream.Position = firstFrameOffset;
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "NES Controller";
string[] buttons = new string[8] { "A", "B", "Select", "Start", "Up", "Down", "Left", "Right" }; string[] buttons = new string[8] { "A", "B", "Select", "Start", "Up", "Down", "Left", "Right" };
bool fds = false; bool fds = false;
bool fourscore = false; bool fourscore = false;
int frame = 1; int frame = 1;
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "NES Controller";
while (frame <= frameCount) while (frame <= frameCount)
{ {
MnemonicsGenerator mg = new MnemonicsGenerator(); MnemonicsGenerator mg = new MnemonicsGenerator();
@ -455,7 +453,7 @@ namespace BizHawk.MultiClient
frame++; frame++;
frames--; frames--;
} }
if ((int)(update & 0x80) != 0) if (((update >> 7) & 1) == 1)
{ {
// Control update: 1aabbbbb // Control update: 1aabbbbb
bool reset = false; bool reset = false;
@ -584,7 +582,7 @@ namespace BizHawk.MultiClient
// 004 1-byte flags: // 004 1-byte flags:
byte flags = r.ReadByte(); byte flags = r.ReadByte();
// bit 7: 0=reset-based, 1=savestate-based // bit 7: 0=reset-based, 1=savestate-based
if ((flags & 0x80) != 0) if (((flags >> 2) & 1) == 1)
{ {
errorMsg = "Movies that begin with a savestate are not supported."; errorMsg = "Movies that begin with a savestate are not supported.";
r.Close(); r.Close();
@ -596,7 +594,7 @@ namespace BizHawk.MultiClient
flags = r.ReadByte(); flags = r.ReadByte();
// bit 5: is a FDS recording // bit 5: is a FDS recording
bool FDS; bool FDS;
if ((flags & 0x20) != 0) if (((flags >> 5) & 1) == 1)
{ {
FDS = true; FDS = true;
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "FDS"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "FDS");
@ -607,9 +605,9 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "NES"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "NES");
} }
// bit 6: uses controller 2 // bit 6: uses controller 2
bool controller2 = ((flags & 0x40) != 0); bool controller2 = (((flags >> 6) & 1) == 1);
// bit 7: uses controller 1 // bit 7: uses controller 1
bool controller1 = ((flags & 0x80) != 0); bool controller1 = (((flags >> 7) & 1) == 1);
// other bits: unknown, set to 0 // other bits: unknown, set to 0
// 006 4-byte little-endian unsigned int: unknown, set to 00000000 // 006 4-byte little-endian unsigned int: unknown, set to 00000000
r.ReadInt32(); r.ReadInt32();
@ -627,8 +625,8 @@ namespace BizHawk.MultiClient
string emuVersion = RemoveNull(r.ReadStringFixedAscii(64)); string emuVersion = RemoveNull(r.ReadStringFixedAscii(64));
m.Header.Comments.Add(EMULATIONORIGIN + " Famtasia version " + emuVersion); m.Header.Comments.Add(EMULATIONORIGIN + " Famtasia version " + emuVersion);
// 050 64-byte zero-terminated movie title string // 050 64-byte zero-terminated movie title string
string gameName = RemoveNull(r.ReadStringFixedAscii(64)); string description = RemoveNull(r.ReadStringFixedAscii(64));
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName); m.Header.Comments.Add(COMMENT + " " + description);
if (!controller1 && !controller2 && !FDS) if (!controller1 && !controller2 && !FDS)
{ {
warningMsg = "No input recorded."; warningMsg = "No input recorded.";
@ -641,19 +639,10 @@ namespace BizHawk.MultiClient
fps. fps.
*/ */
m.Header.SetHeaderLine("PAL", "False"); m.Header.SetHeaderLine("PAL", "False");
/* // 090 frame data begins here
090 frame data begins here SimpleController controllers = new SimpleController();
The file has no terminator byte or frame count. The number of frames is the <filesize minus 144> divided by controllers.Type = new ControllerDefinition();
<number of bytes per frame>. controllers.Type.Name = "NES Controller";
*/
int bytesPerFrame = 0;
if (controller1)
bytesPerFrame++;
if (controller2)
bytesPerFrame++;
if (FDS)
bytesPerFrame++;
long frames = (fs.Length - 144) / bytesPerFrame;
/* /*
* 01 Right * 01 Right
* 02 Left * 02 Left
@ -665,11 +654,20 @@ namespace BizHawk.MultiClient
* 80 Start * 80 Start
*/ */
string[] buttons = new string[8] { "Right", "Left", "Up", "Down", "B", "A", "Select", "Start" }; string[] buttons = new string[8] { "Right", "Left", "Up", "Down", "B", "A", "Select", "Start" };
/*
The file has no terminator byte or frame count. The number of frames is the <filesize minus 144> divided by
<number of bytes per frame>.
*/
int bytesPerFrame = 0;
if (controller1)
bytesPerFrame++;
if (controller2)
bytesPerFrame++;
if (FDS)
bytesPerFrame++;
long frames = (fs.Length - 144) / bytesPerFrame;
for (long frame = 1; frame <= frames; frame++) for (long frame = 1; frame <= frames; frame++)
{ {
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "NES Controller";
/* /*
Each frame consists of 1 or more bytes. Controller 1 takes 1 byte, controller 2 takes 1 byte, and the FDS Each frame consists of 1 or more bytes. Controller 1 takes 1 byte, controller 2 takes 1 byte, and the FDS
data takes 1 byte. If all three exist, the frame is 3 bytes. For example, if the movie is a regular NES game data takes 1 byte. If all three exist, the frame is 3 bytes. For example, if the movie is a regular NES game
@ -685,12 +683,8 @@ namespace BizHawk.MultiClient
if (player != 3) if (player != 3)
{ {
byte controllerState = r.ReadByte(); byte controllerState = r.ReadByte();
byte and = 0x1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ controllers["P" + player + " " + buttons[button]] = (((controllerState >> button) & 1) == 1);
controllers["P" + player + " " + buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1;
}
} }
else if (FDS) else if (FDS)
{ {
@ -714,10 +708,8 @@ namespace BizHawk.MultiClient
errorMsg = ""; errorMsg = "";
warningMsg = ""; warningMsg = "";
Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs); BinaryReader r = new BinaryReader(fs);
// 000 16-byte signature and format version: "Gens Movie TEST9" // 000 16-byte signature and format version: "Gens Movie TEST9"
string signature = r.ReadStringFixedAscii(15); string signature = r.ReadStringFixedAscii(15);
if (signature != "Gens Movie TEST") if (signature != "Gens Movie TEST")
@ -727,35 +719,28 @@ namespace BizHawk.MultiClient
fs.Close(); fs.Close();
return null; return null;
} }
// 00F ASCII-encoded GMV file format version. The most recent is 'A'. (?) // 00F ASCII-encoded GMV file format version. The most recent is 'A'. (?)
string emuVersion = r.ReadStringFixedAscii(1); string version = r.ReadStringFixedAscii(1);
m.Header.Comments.Add(EMULATIONORIGIN + " Gens version " + emuVersion); m.Header.Comments.Add(MOVIEORIGIN + " .GMV version " + version);
// 010 4-byte little-endian unsigned int: rerecord count // 010 4-byte little-endian unsigned int: rerecord count
uint rerecordCount = r.ReadUInt32(); uint rerecordCount = r.ReadUInt32();
m.SetRerecords((int)rerecordCount); m.SetRerecords((int)rerecordCount);
// 014 ASCII-encoded controller config for player 1. '3' or '6'. // 014 ASCII-encoded controller config for player 1. '3' or '6'.
string player1Config = r.ReadStringFixedAscii(1); string player1Config = r.ReadStringFixedAscii(1);
// 015 ASCII-encoded controller config for player 2. '3' or '6'. // 015 ASCII-encoded controller config for player 2. '3' or '6'.
string player2Config = r.ReadStringFixedAscii(1); string player2Config = r.ReadStringFixedAscii(1);
if (player1Config == "6" || player2Config == "6") if (player1Config == "6" || player2Config == "6")
{ warningMsg = "6 button controllers are not properly supported.";
errorMsg = "6 button controllers are not supported.";
r.Close();
fs.Close();
return null;
}
// 016 special flags (Version A and up only) // 016 special flags (Version A and up only)
byte MovieFlags = r.ReadByte(); 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 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.
*/ */
bool pal = (((flags >> 7) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString());
// bit 6: if "1", movie requires a savestate. // bit 6: if "1", movie requires a savestate.
if (((MovieFlags >> 6) & 1) == 1) if (((flags >> 6) & 1) == 1)
{ {
errorMsg = "Movies that begin with a savestate are not supported."; errorMsg = "Movies that begin with a savestate are not supported.";
r.Close(); r.Close();
@ -763,14 +748,15 @@ namespace BizHawk.MultiClient
return null; return null;
} }
// bit 5: if "1", movie is 3-player movie; if "0", movie is 2-player movie // bit 5: if "1", movie is 3-player movie; if "0", movie is 2-player movie
bool twoPlayerMovie = ((MovieFlags >> 5) & 1) == 0; bool threePlayers = (((flags >> 5) & 1) == 1);
// Unknown. // Unknown.
r.ReadByte(); r.ReadByte();
// 018 40-byte zero-terminated ASCII movie name string // 018 40-byte zero-terminated ASCII movie name string
string movieName = RemoveNull(r.ReadStringFixedAscii(40)); string description = RemoveNull(r.ReadStringFixedAscii(40));
m.Header.Comments.Add(COMMENT + " " + description);
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "Genesis 3-Button Controller";
/* /*
040 frame data 040 frame data
For controller bytes, each value is determined by OR-ing together values for whichever of the following are left For controller bytes, each value is determined by OR-ing together values for whichever of the following are left
@ -784,7 +770,7 @@ namespace BizHawk.MultiClient
* 0x40 C * 0x40 C
* 0x80 Start * 0x80 Start
*/ */
string[] stdButtons = new string[8] { "Up", "Down", "Left", "Right", "A", "B", "C", "Start" }; string[] buttons = new string[8] { "Up", "Down", "Left", "Right", "A", "B", "C", "Start" };
/* /*
For XYZ-mode, each value is determined by OR-ing together values for whichever of the following are left For XYZ-mode, each value is determined by OR-ing together values for whichever of the following are left
unpressed: unpressed:
@ -797,90 +783,32 @@ namespace BizHawk.MultiClient
* 0x40 Controller 2 Z * 0x40 Controller 2 Z
* 0x80 Controller 2 Mode * 0x80 Controller 2 Mode
*/ */
string[] xyzButtons = new string[4] { "X", "Y", "Z", "Mode" }; string[] other = new string[4] { "X", "Y", "Z", "Mode" };
int frame = 1; // The file has no terminator byte or frame count. The number of frames is the <filesize minus 64> divided by 3.
SimpleController controllers = new SimpleController(); long frames = (fs.Length - 64) / 3;
controllers.Type = new ControllerDefinition(); for (long frame = 1; frame <= frames; frame++)
controllers.Type.Name = "Genesis 3-Button Controller";
while (true)
{ {
// Each frame consists of 3 bytes. // Each frame consists of 3 bytes.
byte controller1Data; for (int player = 1; player <= 3; player++)
byte controller2Data;
byte starData;
try
{ {
controller1Data = r.ReadByte(); byte controllerState = r.ReadByte();
// * is controller 3 if a 3-player movie, or XYZ-mode if a 2-player movie.
if (player != 3 || threePlayers)
for (int button = 0; button < buttons.Length; button++)
controllers["P" + player + " " + buttons[button]] = (((controllerState >> button) & 1) == 0);
else
for (int button = 0; button < other.Length; button++)
{
if (player1Config == "6")
controllers["P1 " + other[button]] = (((controllerState >> button) & 1) == 0);
if (player2Config == "6")
controllers["P2 " + other[button]] = (((controllerState >> (button + 4)) & 1) == 0);
}
} }
catch (EndOfStreamException)
{
// Valid end of file reached.
break;
}
try
{
controller2Data = r.ReadByte();
starData = r.ReadByte();
}
catch (EndOfStreamException)
{
errorMsg = "Unexpected end of file.";
r.Close();
fs.Close();
return null;
}
for (int button = 0; button < stdButtons.Length; button++)
{
controllers["P1 " + stdButtons[button]] = ((controller1Data >> button) & 1) == 0;
controllers["P2 " + stdButtons[button]] = ((controller2Data >> button) & 1) == 0;
}
// * is controller 3 if a 3-player movie, or XYZ-mode if a 2-player movie.
if (!twoPlayerMovie)
for (int button = 0; button < stdButtons.Length; button++)
controllers["P3 " + stdButtons[button]] = ((starData >> button) & 1) == 0;
else
for (int button = 0; button < 4; button++)
{
if (player1Config == "6")
controllers["P1 " + xyzButtons[button]] = ((starData >> button) & 1) == 0;
if (player2Config == "6")
controllers["P2 " + xyzButtons[button]] = ((starData >> (button + 4)) & 1) == 0;
}
MnemonicsGenerator mg = new MnemonicsGenerator(); MnemonicsGenerator mg = new MnemonicsGenerator();
mg.SetSource(controllers); mg.SetSource(controllers);
m.AppendFrame(mg.GetControllersAsMnemonic()); m.AppendFrame(mg.GetControllersAsMnemonic());
frame++;
} }
return m;
}
// NMV file format: http://tasvideos.org/NMV.html
private static Movie ImportNMV(string path, out string errorMsg, out string warningMsg)
{
errorMsg = "";
warningMsg = "";
Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs);
// 000 4-byte signature: 4E 53 53 1A "NSS\x1A"
string signature = r.ReadStringFixedAscii(4);
if (signature != "NSS\x1A")
{
errorMsg = "This is not a valid .NMV file.";
r.Close();
fs.Close();
return null;
}
r.Close();
fs.Close();
return m; return m;
} }
@ -950,20 +878,20 @@ namespace BizHawk.MultiClient
r.ReadBytes(103); // discard r.ReadBytes(103); // discard
// 100 variable: input data // 100 variable: input data
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = CTRL_TYPE_PCE;
// For PCE, BizHawk does not have any control command. So I ignore any command. // For PCE, BizHawk does not have any control command. So I ignore any command.
for (byte[] input = r.ReadBytes(INPUT_SIZE_PCE); for (byte[] input = r.ReadBytes(INPUT_SIZE_PCE);
input.Length == INPUT_SIZE_PCE; input.Length == INPUT_SIZE_PCE;
input = r.ReadBytes(INPUT_SIZE_PCE)) input = r.ReadBytes(INPUT_SIZE_PCE))
{ {
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = CTRL_TYPE_PCE;
for (int player = 1; player <= INPUT_PORT_COUNT; ++player) for (int player = 1; player <= INPUT_PORT_COUNT; ++player)
{ {
byte pad = input[1+2*(player-1)]; byte pad = input[1+2*(player-1)];
for(int i = 0; i < 8; ++i) for(int i = 0; i < 8; ++i)
{ {
if ((pad & (1<<i)) != 0) if ((pad & (1 << i)) != 0)
controllers["P" + player + " " + CTRL_BUTTONS[i]] = true; controllers["P" + player + " " + CTRL_BUTTONS[i]] = true;
} }
} }
@ -1037,14 +965,14 @@ FAIL:
byte flags = r.ReadByte(); byte flags = r.ReadByte();
// bit 0: unused // bit 0: unused
// bit 1: PAL // bit 1: PAL
bool pal = ((flags & 2) != 0); bool pal = (((flags >> 1) & 1) == 1);
m.Header.SetHeaderLine("PAL", pal.ToString()); m.Header.SetHeaderLine("PAL", pal.ToString());
bool japan = ((flags & 4) != 0);
// bit 2: Japan // bit 2: Japan
bool japan = (((flags >> 2) & 1) == 1);
m.Header.SetHeaderLine("Japan", japan.ToString()); m.Header.SetHeaderLine("Japan", japan.ToString());
// bit 3: Game Gear (version 1.16+) // bit 3: Game Gear (version 1.16+)
bool gamegear; bool gamegear;
if ((flags & 8) != 0) if (((flags >> 3) & 1) == 1)
{ {
gamegear = true; gamegear = true;
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG");
@ -1062,6 +990,9 @@ FAIL:
// 00e4-00f3: binary: rom MD5 digest // 00e4-00f3: binary: rom MD5 digest
byte[] MD5 = r.ReadBytes(16); byte[] MD5 = r.ReadBytes(16);
m.Header.SetHeaderLine("MD5", String.Format("{0:x8}", BizHawk.Util.BytesToHexString(MD5).ToLower())); m.Header.SetHeaderLine("MD5", String.Format("{0:x8}", BizHawk.Util.BytesToHexString(MD5).ToLower()));
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "SMS Controller";
/* /*
76543210 76543210
* bit 0 (0x01): up * bit 0 (0x01): up
@ -1076,9 +1007,6 @@ FAIL:
string[] buttons = new string[6] { "Up", "Down", "Left", "Right", "B1", "B2" }; string[] buttons = new string[6] { "Up", "Down", "Left", "Right", "B1", "B2" };
for (int frame = 1; frame <= frameCount; frame++) for (int frame = 1; frame <= frameCount; frame++)
{ {
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "SMS Controller";
/* /*
Controller data is made up of one input packet per frame. Each packet currently consists of 2 bytes. The 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 first byte is for controller 1 and the second controller 2. The Game Gear only uses the controller 1 input
@ -1087,16 +1015,12 @@ FAIL:
for (int player = 1; player <= 2; player++) for (int player = 1; player <= 2; player++)
{ {
byte controllerState = r.ReadByte(); byte controllerState = r.ReadByte();
byte and = 1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ controllers["P" + player + " " + buttons[button]] = (((controllerState >> button) & 1) == 1);
controllers["P" + player + " " + buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1;
}
if (player == 1) if (player == 1)
controllers["Pause"] = ( controllers["Pause"] = (
((int)(controllerState & 0x40) != 0 && (!gamegear)) || (((controllerState >> 6) & 1) == 1 && (!gamegear)) ||
((int)(controllerState & 0x80) != 0 && gamegear) (((controllerState >> 7) & 1) == 1 && gamegear)
); );
} }
MnemonicsGenerator mg = new MnemonicsGenerator(); MnemonicsGenerator mg = new MnemonicsGenerator();
@ -1108,6 +1032,28 @@ FAIL:
return m; return m;
} }
// NMV file format: http://tasvideos.org/NMV.html
private static Movie ImportNMV(string path, out string errorMsg, out string warningMsg)
{
errorMsg = "";
warningMsg = "";
Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs);
// 000 4-byte signature: 4E 53 53 1A "NSS\x1A"
string signature = r.ReadStringFixedAscii(4);
if (signature != "NSS\x1A")
{
errorMsg = "This is not a valid .NMV file.";
r.Close();
fs.Close();
return null;
}
r.Close();
fs.Close();
return m;
}
private static Movie ImportSMV(string path, out string errorMsg, out string warningMsg) private static Movie ImportSMV(string path, out string errorMsg, out string warningMsg)
{ {
errorMsg = ""; errorMsg = "";
@ -1160,13 +1106,13 @@ FAIL:
byte ControllerFlags = r.ReadByte(); byte ControllerFlags = r.ReadByte();
int numControllers; int numControllers;
if ((ControllerFlags & 16) != 0) if (((ControllerFlags >> 4) & 1) == 1)
numControllers = 5; numControllers = 5;
else if ((ControllerFlags & 8) != 0) else if (((ControllerFlags >> 3) & 1) == 1)
numControllers = 4; numControllers = 4;
else if ((ControllerFlags & 4) != 0) else if (((ControllerFlags >> 2) & 1) == 1)
numControllers = 3; numControllers = 3;
else if ((ControllerFlags & 2) != 0) else if (((ControllerFlags >> 1) & 1) == 1)
numControllers = 2; numControllers = 2;
else else
numControllers = 1; numControllers = 1;
@ -1176,10 +1122,8 @@ FAIL:
if ((MovieFlags & 1) == 0) if ((MovieFlags & 1) == 0)
return null; //TODO: Savestate movies not supported error return null; //TODO: Savestate movies not supported error
if ((MovieFlags & 2) != 0) if (((MovieFlags >> 1) & 1) == 1)
{
m.Header.SetHeaderLine("PAL", "True"); m.Header.SetHeaderLine("PAL", "True");
}
byte SyncOptions = r.ReadByte(); byte SyncOptions = r.ReadByte();
byte SyncOptions2 = r.ReadByte(); byte SyncOptions2 = r.ReadByte();
@ -1257,9 +1201,9 @@ FAIL:
// 014 1-byte flags: (movie start flags) // 014 1-byte flags: (movie start flags)
byte flags = r.ReadByte(); byte flags = r.ReadByte();
// bit 0: if "1", movie starts from an embedded "quicksave" snapshot // bit 0: if "1", movie starts from an embedded "quicksave" snapshot
bool startfromquicksave = ((flags & 1) != 0); bool startfromquicksave = ((flags & 1) == 1);
// bit 1: if "1", movie starts from reset with an embedded SRAM // bit 1: if "1", movie starts from reset with an embedded SRAM
bool startfromsram = ((flags & 2) != 0); bool startfromsram = (((flags >> 1) & 1) == 1);
// other: reserved, set to 0 // other: reserved, set to 0
// We can't start from either save option. // We can't start from either save option.
if (startfromquicksave || startfromsram) if (startfromquicksave || startfromsram)
@ -1277,7 +1221,7 @@ FAIL:
// TODO: Handle the additional controllers. // TODO: Handle the additional controllers.
int players = 0; int players = 0;
// bit 0: controller 1 in use // bit 0: controller 1 in use
if ((flags & 1) != 0) if ((flags & 1) == 1)
players++; players++;
else else
{ {
@ -1287,23 +1231,23 @@ FAIL:
return null; return null;
} }
// bit 1: controller 2 in use (SGB games can be 2-player multiplayer) // bit 1: controller 2 in use (SGB games can be 2-player multiplayer)
if ((flags & 2) != 0) if (((flags >> 1) & 1) == 1)
players++; players++;
// bit 2: controller 3 in use (SGB games can be 3- or 4-player multiplayer with multitap) // bit 2: controller 3 in use (SGB games can be 3- or 4-player multiplayer with multitap)
if ((flags & 4) != 0) if (((flags >> 2) & 1) == 1)
players++; players++;
// bit 3: controller 4 in use (SGB games can be 3- or 4-player multiplayer with multitap) // bit 3: controller 4 in use (SGB games can be 3- or 4-player multiplayer with multitap)
if ((flags & 8) != 0) if (((flags >> 3) & 1) == 1)
players++; players++;
// other: reserved // other: reserved
// 016 1-byte flags: system flags (game always runs at 60 frames/sec) // 016 1-byte flags: system flags (game always runs at 60 frames/sec)
flags = r.ReadByte(); flags = r.ReadByte();
// bit 0: if "1", movie is for the GBA system // bit 0: if "1", movie is for the GBA system
bool is_gba = ((flags & 1) != 0); bool is_gba = ((flags & 1) == 1);
// bit 1: if "1", movie is for the GBC system // bit 1: if "1", movie is for the GBC system
bool is_gbc = ((flags & 2) != 0); bool is_gbc = (((flags >> 1) & 1) == 1);
// bit 2: if "1", movie is for the SGB system // bit 2: if "1", movie is for the SGB system
bool is_sgb = ((flags & 4) != 0); bool is_sgb = (((flags >> 2) & 1) == 1);
// other: reserved, set to 0 // other: reserved, set to 0
// (At most one of bits 0, 1, 2 can be "1") // (At most one of bits 0, 1, 2 can be "1")
if (!(is_gba ^ is_gbc ^ is_sgb) && (is_gba || is_gbc || is_sgb)) if (!(is_gba ^ is_gbc ^ is_sgb) && (is_gba || is_gbc || is_sgb))
@ -1331,7 +1275,7 @@ FAIL:
* bit 2: (rtcEnable) if "1", the emulator "real time clock" feature was enabled. * bit 2: (rtcEnable) if "1", the emulator "real time clock" feature was enabled.
*/ */
// bit 3: (unsupported) must be "0" or the movie file is considered invalid (legacy). // bit 3: (unsupported) must be "0" or the movie file is considered invalid (legacy).
if ((flags & 8) != 0) if (((flags >> 3) & 1) == 1)
{ {
errorMsg = "This is not a valid .VBM file."; errorMsg = "This is not a valid .VBM file.";
r.Close(); r.Close();
@ -1378,13 +1322,16 @@ FAIL:
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author); m.Header.SetHeaderLine(MovieHeader.AUTHOR, author);
// The following 128 bytes are for a description of the movie. Both parts must be null-terminated. // The following 128 bytes are for a description of the movie. Both parts must be null-terminated.
string movieDescription = RemoveNull(r.ReadStringFixedAscii(128)); string movieDescription = RemoveNull(r.ReadStringFixedAscii(128));
m.Header.Comments.Add("comment " + movieDescription); m.Header.Comments.Add(COMMENT + " " + movieDescription);
/* /*
TODO: implement start data. There are no specifics on the googlecode page as to how long the SRAM or savestate TODO: implement start data. There are no specifics on the googlecode page as to how long the SRAM or savestate
should be. should be.
*/ */
// If there is no "Start Data", this will probably begin at byte 0x100 in the file, but this is not guaranteed. // If there is no "Start Data", this will probably begin at byte 0x100 in the file, but this is not guaranteed.
r.BaseStream.Position = firstFrameOffset; r.BaseStream.Position = firstFrameOffset;
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "Gameboy Controller";
/* /*
* 01 00 A * 01 00 A
* 02 00 B * 02 00 B
@ -1417,31 +1364,20 @@ FAIL:
in groups of however many controllers are active, in increasing order. in groups of however many controllers are active, in increasing order.
*/ */
ushort controllerState = r.ReadUInt16(); ushort controllerState = r.ReadUInt16();
SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition();
controllers.Type.Name = "Gameboy Controller";
ushort and = 0x1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ controllers[buttons[button]] = (((controllerState >> button) & 1) == 1);
controllers[buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1;
}
MnemonicsGenerator mg = new MnemonicsGenerator(); MnemonicsGenerator mg = new MnemonicsGenerator();
mg.SetSource(controllers); mg.SetSource(controllers);
m.AppendFrame(mg.GetControllersAsMnemonic()); m.AppendFrame(mg.GetControllersAsMnemonic());
// TODO: Handle the other buttons. // TODO: Handle the other buttons.
and = 0x100;
if (warningMsg == "") if (warningMsg == "")
{ {
for (int button = 0; button < other.Length; button++) for (int button = 0; button < other.Length; button++)
{ if (((controllerState >> (button + 8)) & 1) == 1)
if ((controllerState & and) != 0)
{ {
warningMsg = other[button]; warningMsg = other[button];
break; break;
} }
and <<= 1;
}
if (warningMsg != "") if (warningMsg != "")
warningMsg = "Unable to import " + warningMsg + " at frame " + frame + "."; warningMsg = "Unable to import " + warningMsg + " at frame " + frame + ".";
} }