-Added EMULATIONORIGIN and MOVIEORIGIN as constants ImportMovie instead of using magic strings.

-Fixed some error and warning messages:
--Made it so that the first .FCM warning is shown instead of the last.
--Added an error for .FCM files that aren't version 2.
--Did the same for ImportVBM, but with version 1.
-Cleaned up comments / code throughout.
-Fixed the file format for GBx; it had an extra bar which made reading Player 1 impossible.
-Seemingly finished ImportVBM.
--The results seem same. I have no way of testing whether they sync up or not.
--As I can't actually handle additional controllers, I just skip the bytes for them.
--As I can't actually handle the miscellaneous buttons and commands (Ex. L, R, Reset), I just give warnings whenever they come up in the file.

TODO: ImportGMV, ImportVMV, ImportNMV, and maybe ImportSMV...I'm going to need a new assignment soon! :)
This commit is contained in:
brandman211 2012-03-06 03:41:11 +00:00
parent e3ba08fefd
commit c9b5f9989a
2 changed files with 338 additions and 283 deletions

View File

@ -179,7 +179,7 @@ namespace BizHawk.MultiClient
if (ControlType == "Gameboy Controller") if (ControlType == "Gameboy Controller")
{ {
input.Append(".|"); //TODO: reset goes here input.Append("."); // TODO: reset goes here
} }
if (ControlType == "NES Controller") if (ControlType == "NES Controller")
{ {

View File

@ -11,6 +11,9 @@ namespace BizHawk.MultiClient
{ {
public static class MovieImport public static class MovieImport
{ {
public const string EMULATIONORIGIN = "emuVersion";
public const string MOVIEORIGIN = "MovieOrigin";
// 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)
{ {
@ -130,10 +133,10 @@ namespace BizHawk.MultiClient
if (line == "") if (line == "")
continue; continue;
if (line.StartsWith("emuVersion")) if (line.StartsWith("emuVersion"))
m.Header.Comments.Add("emuOrigin " + emulator + " version " + ParseHeader(line, "emuVersion")); m.Header.Comments.Add(EMULATIONORIGIN + " " + emulator + " version " + ParseHeader(line, "emuVersion"));
else if (line.StartsWith("version")) else if (line.StartsWith("version"))
m.Header.Comments.Add( m.Header.Comments.Add(
"MovieOrigin " + Path.GetExtension(path) + " version " + ParseHeader(line, "version") MOVIEORIGIN + " " + Path.GetExtension(path) + " version " + ParseHeader(line, "version")
); );
else if (line.StartsWith("romFilename")) else if (line.StartsWith("romFilename"))
m.Header.SetHeaderLine(MovieHeader.GAMENAME, ParseHeader(line, "romFilename")); m.Header.SetHeaderLine(MovieHeader.GAMENAME, ParseHeader(line, "romFilename"));
@ -309,39 +312,44 @@ namespace BizHawk.MultiClient
} }
// 004 4-byte little-endian unsigned int: version number, must be 2 // 004 4-byte little-endian unsigned int: version number, must be 2
uint version = r.ReadUInt32(); uint version = r.ReadUInt32();
m.Header.Comments.Add("MovieOrigin .FCM version " + version); if (version != 2)
{
errorMsg = ".FCM movie version must always be 2.";
r.Close();
fs.Close();
return null;
}
m.Header.Comments.Add(MOVIEORIGIN + " .FCM version " + version);
// 008 1-byte flags // 008 1-byte flags
byte flags = r.ReadByte(); byte flags = r.ReadByte();
// bit 0: reserved, set to 0
/* /*
* bit 0: reserved, set to 0 bit 1:
* bit 1: * 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]
* bit 2:
** if "0", NTSC timing
** if "1", PAL timing
** see notes below
* other: reserved, set to 0
*/ */
if ((int)(flags & 2) == 0) if ((flags & 2) == 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();
fs.Close(); fs.Close();
return null; return null;
} }
bool pal = false; /*
if ((int)(flags & 4) != 0) bit 2:
pal = true; * if "0", NTSC 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.
*/ */
m.Header.SetHeaderLine("PAL", pal.ToString()); m.Header.SetHeaderLine("PAL", pal.ToString());
bool movieSyncHackOn = true; bool movieSyncHackOn = ((flags & 16) != 0);
if ((int)(flags & 16) != 0)
movieSyncHackOn = false;
m.Header.SetHeaderLine("SyncHack", movieSyncHackOn.ToString()); m.Header.SetHeaderLine("SyncHack", movieSyncHackOn.ToString());
/* /*
009 1-byte flags: reserved, set to 0 009 1-byte flags: reserved, set to 0
@ -370,7 +378,7 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine("MD5", MD5); m.Header.SetHeaderLine("MD5", MD5);
// 030 4-byte little-endian unsigned int: version of the emulator used // 030 4-byte little-endian unsigned int: version of the emulator used
uint emuVersion = r.ReadUInt32(); uint emuVersion = r.ReadUInt32();
m.Header.Comments.Add("emuOrigin FCEU " + emuVersion.ToString()); m.Header.Comments.Add(EMULATIONORIGIN + " FCEU " + emuVersion.ToString());
// 034 name of the ROM used - UTF8 encoded nul-terminated string. // 034 name of the ROM used - UTF8 encoded nul-terminated string.
List<byte> gameBytes = new List<byte>(); List<byte> gameBytes = new List<byte>();
while (r.PeekChar() != 0) while (r.PeekChar() != 0)
@ -380,8 +388,8 @@ namespace BizHawk.MultiClient
string gameName = System.Text.Encoding.UTF8.GetString(gameBytes.ToArray()); string gameName = System.Text.Encoding.UTF8.GetString(gameBytes.ToArray());
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName); m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName);
/* /*
After the header comes "metadata", which is UTF8-coded movie title string. The metadata begins after the ROM name After the header comes "metadata", which is UTF8-coded movie title string. The metadata begins after the ROM
and ends at the savestate offset. This string is displayed as "Author Info" in the Windows version of the name and ends at the savestate offset. This string is displayed as "Author Info" in the Windows version of the
emulator. emulator.
*/ */
List<byte> authorBytes = new List<byte>(); List<byte> authorBytes = new List<byte>();
@ -435,44 +443,42 @@ namespace BizHawk.MultiClient
// Control update: 1aabbbbb // Control update: 1aabbbbb
bool reset = false; bool reset = false;
int command = update & 0x1F; int command = update & 0x1F;
/* // bbbbb:
bbbbb: if (warningMsg == "")
** 0 Do nothing {
** 1 Reset
** 2 Power cycle
** 7 VS System Insert Coin
** 8 VS System Dipswitch 0 Toggle
** 24 FDS Insert
** 25 FDS Eject
** 26 FDS Select Side
*/
switch (command) switch (command)
{ {
// Do nothing
case 0: case 0:
break; break;
// Reset
case 1: case 1:
reset = !controllers["Reset"]; reset = !controllers["Reset"];
controllers["Reset"] = reset; controllers["Reset"] = reset;
break; break;
// Power cycle
case 2: case 2:
reset = true; reset = true;
if (frame != 1) if (frame != 1)
{
warningMsg = "hard reset"; warningMsg = "hard reset";
}
break; break;
// VS System Insert Coin
case 7: case 7:
warningMsg = "VS System Insert Coin"; warningMsg = "VS System Insert Coin";
break; break;
// VS System Dipswitch 0 Toggle
case 8: case 8:
warningMsg = "VS System Dipswitch 0 Toggle"; warningMsg = "VS System Dipswitch 0 Toggle";
break; break;
// FDS Insert
case 24: case 24:
warningMsg = "FDS Insert"; warningMsg = "FDS Insert";
break; break;
// FDS Eject
case 25: case 25:
warningMsg = "FDS Eject"; warningMsg = "FDS Eject";
break; break;
// FDS Select Side
case 26: case 26:
warningMsg = "FDS Select Side"; warningMsg = "FDS Select Side";
break; break;
@ -482,11 +488,12 @@ namespace BizHawk.MultiClient
} }
if (warningMsg != "") if (warningMsg != "")
warningMsg = "Unable to import " + warningMsg + " command at frame " + frame + "."; warningMsg = "Unable to import " + warningMsg + " command at frame " + frame + ".";
}
/* /*
1 Even if the header says "movie begins from reset", the file still contains a quicksave, and the 1 Even if the header says "movie begins from reset", the file still contains a quicksave, and the
quicksave is actually loaded. This flag can't therefore be trusted. To check if the movie actually begins quicksave is actually loaded. This flag can't therefore be trusted. To check if the movie actually
from reset, one must analyze the controller data and see if the first non-idle command in the file is a begins from reset, one must analyze the controller data and see if the first non-idle command in the
Reset or Power Cycle type control command. file is a Reset or Power Cycle type control command.
*/ */
if (!reset && frame == 1) if (!reset && frame == 1)
{ {
@ -556,27 +563,20 @@ 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)
* other bits: unknown, set to 0
*/
if ((int)(flags & 0x80) != 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();
fs.Close(); fs.Close();
return null; return null;
} }
// other bits: unknown, set to 0
// 005 1-byte flags: // 005 1-byte flags:
flags = r.ReadByte(); flags = r.ReadByte();
/* // bit 5: is a FDS recording
* bit 5: is a FDS recording
* bit 6: uses controller 2
* bit 7: uses controller 1
* other bits: unknown, set to 0
*/
bool FDS; bool FDS;
if ((int)(flags & 0x20) != 0) if ((flags & 0x20) != 0)
{ {
FDS = true; FDS = true;
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "FDS"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "FDS");
@ -586,30 +586,26 @@ namespace BizHawk.MultiClient
FDS = false; FDS = false;
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "NES"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "NES");
} }
bool controller2 = false; // bit 6: uses controller 2
if ((int)(flags & 0x40) != 0) bool controller2 = ((flags & 0x40) != 0);
{ // bit 7: uses controller 1
controller2 = true; bool controller1 = ((flags & 0x80) != 0);
} // other bits: unknown, set to 0
bool controller1 = false;
if ((int)(flags & 0x80) != 0)
{
controller1 = true;
}
// 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();
// 00A 4-byte little-endian unsigned int: rerecord count minus 1 // 00A 4-byte little-endian unsigned int: rerecord count minus 1
uint rerecordCount = r.ReadUInt32(); uint rerecordCount = r.ReadUInt32();
/* /*
The rerecord count stored in the file is the number of times a savestate was loaded. If a savestate was never loaded, The rerecord count stored in the file is the number of times a savestate was loaded. If a savestate was never
the number is 0. Famtasia however displays "1" in such case. It always adds 1 to the number found in the file. loaded, the number is 0. Famtasia however displays "1" in such case. It always adds 1 to the number found in
the file.
*/ */
m.SetRerecords(((int)rerecordCount) + 1); m.SetRerecords(((int)rerecordCount) + 1);
// 00E 2-byte little-endian unsigned int: unknown, set to 0000 // 00E 2-byte little-endian unsigned int: unknown, set to 0000
r.ReadInt16(); r.ReadInt16();
// 010 64-byte zero-terminated emulator identifier string // 010 64-byte zero-terminated emulator identifier string
string emuVersion = RemoveNull(BytesToString(r, 64)); string emuVersion = RemoveNull(BytesToString(r, 64));
m.Header.Comments.Add("emuOrigin 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(BytesToString(r, 64)); string gameName = RemoveNull(BytesToString(r, 64));
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName); m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName);
@ -668,11 +664,11 @@ namespace BizHawk.MultiClient
player++; player++;
if (player != 3) if (player != 3)
{ {
byte controllerstate = r.ReadByte(); byte controllerState = r.ReadByte();
byte and = 0x1; byte and = 0x1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ {
controllers["P" + player + " " + buttons[button]] = ((int)(controllerstate & and) != 0); controllers["P" + player + " " + buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1; and <<= 1;
} }
} }
@ -889,7 +885,7 @@ namespace BizHawk.MultiClient
} }
// 0004: 4-byte little endian unsigned int: dega version // 0004: 4-byte little endian unsigned int: dega version
uint emuVersion = r.ReadUInt32(); uint emuVersion = r.ReadUInt32();
m.Header.Comments.Add("emuOrigin Dega version " + emuVersion.ToString()); m.Header.Comments.Add(EMULATIONORIGIN + " Dega version " + emuVersion.ToString());
// 0008: 4-byte little endian unsigned int: frame count // 0008: 4-byte little endian unsigned int: frame count
uint frameCount = r.ReadUInt32(); uint frameCount = r.ReadUInt32();
// 000c: 4-byte little endian unsigned int: rerecord count // 000c: 4-byte little endian unsigned int: rerecord count
@ -915,22 +911,16 @@ namespace BizHawk.MultiClient
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author); m.Header.SetHeaderLine(MovieHeader.AUTHOR, author);
// 0060: 4-byte little endian flags // 0060: 4-byte little endian flags
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);
* bit 2: Japan
* bit 3: Game Gear (version 1.16+)
*/
bool pal = false;
if ((int)(flags & 2) != 0)
pal = true;
m.Header.SetHeaderLine("PAL", pal.ToString()); m.Header.SetHeaderLine("PAL", pal.ToString());
bool japan = false; bool japan = ((flags & 4) != 0);
if ((int)(flags & 4) != 0) // bit 2: Japan
japan = true;
m.Header.SetHeaderLine("Japan", japan.ToString()); m.Header.SetHeaderLine("Japan", japan.ToString());
// bit 3: Game Gear (version 1.16+)
bool gamegear; bool gamegear;
if ((int)(flags & 8) != 0) if ((flags & 8) != 0)
{ {
gamegear = true; gamegear = true;
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG"); m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG");
@ -972,17 +962,17 @@ namespace BizHawk.MultiClient
*/ */
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; byte and = 1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ {
controllers["P" + player + " " + buttons[button]] = ((int)(controllerstate & and) != 0); controllers["P" + player + " " + buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1; and <<= 1;
} }
if (player == 1) if (player == 1)
controllers["Pause"] = ( controllers["Pause"] = (
((int)(controllerstate & 0x40) != 0 && (!gamegear)) || ((int)(controllerState & 0x40) != 0 && (!gamegear)) ||
((int)(controllerstate & 0x80) != 0 && gamegear) ((int)(controllerState & 0x80) != 0 && gamegear)
); );
} }
MnemonicsGenerator mg = new MnemonicsGenerator(); MnemonicsGenerator mg = new MnemonicsGenerator();
@ -1046,23 +1036,23 @@ namespace BizHawk.MultiClient
byte ControllerFlags = r.ReadByte(); byte ControllerFlags = r.ReadByte();
int numControllers; int numControllers;
if ((int)(ControllerFlags & 16) != 0) if ((ControllerFlags & 16) != 0)
numControllers = 5; numControllers = 5;
else if ((int)(ControllerFlags & 8) != 0) else if ((ControllerFlags & 8) != 0)
numControllers = 4; numControllers = 4;
else if ((int)(ControllerFlags & 4) != 0) else if ((ControllerFlags & 4) != 0)
numControllers = 3; numControllers = 3;
else if ((int)(ControllerFlags & 2) != 0) else if ((ControllerFlags & 2) != 0)
numControllers = 2; numControllers = 2;
else else
numControllers = 1; numControllers = 1;
byte MovieFlags = r.ReadByte(); byte MovieFlags = r.ReadByte();
if ((int)(MovieFlags & 1) == 0) if ((MovieFlags & 1) == 0)
return null; //TODO: Savestate movies not supported error return null; //TODO: Savestate movies not supported error
if ((int)(MovieFlags & 2) != 0) if ((MovieFlags & 2) != 0)
{ {
m.Header.SetHeaderLine("PAL", "True"); m.Header.SetHeaderLine("PAL", "True");
} }
@ -1108,167 +1098,232 @@ namespace BizHawk.MultiClient
{ {
errorMsg = ""; errorMsg = "";
warningMsg = ""; warningMsg = "";
//Converts vbm to native text based format.
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 4-byte signature: 56 42 4D 1A "VBM\x1A"
//0xoffset string signature = BytesToString(r, 4);
//0x00 if (signature != "VBM\x1A")
uint signature = r.ReadUInt32(); //always 56 42 4D 1A (VBM\x1A)
if (signature != 0x56424D1A)
{ {
errorMsg = "This is not a valid .VBM file."; errorMsg = "This is not a valid .VBM file.";
r.Close(); r.Close();
fs.Close(); fs.Close();
return null; return null;
} }
// 004 4-byte little-endian unsigned int: major version number, must be "1"
uint versionno = r.ReadUInt32(); //always 1 uint majorVersion = r.ReadUInt32();
uint uid = r.ReadUInt32(); //time of recording if (majorVersion != 1)
{
errorMsg = ".VBM major movie version must be 1.";
r.Close();
fs.Close();
return null;
}
/*
008 4-byte little-endian integer: movie "uid" - identifies the movie-savestate relationship, also used as the
recording time in Unix epoch format
*/
uint uid = r.ReadUInt32();
m.Header.SetHeaderLine(MovieHeader.GUID, uid.ToString()); m.Header.SetHeaderLine(MovieHeader.GUID, uid.ToString());
// 00C 4-byte little-endian unsigned int: number of frames
uint frameCount = r.ReadUInt32(); uint frameCount = r.ReadUInt32();
// 010 4-byte little-endian unsigned int: rerecord count
//0x10
uint rerecordCount = r.ReadUInt32(); uint rerecordCount = r.ReadUInt32();
m.SetRerecords((int)rerecordCount); m.SetRerecords((int)rerecordCount);
m.Header.SetHeaderLine(MovieHeader.RERECORDS, m.Rerecords.ToString()); // 014 1-byte flags: (movie start flags)
Byte moviestartflags = r.ReadByte(); byte flags = r.ReadByte();
// bit 0: if "1", movie starts from an embedded "quicksave" snapshot
bool startfromquicksave = false; bool startfromquicksave = ((flags & 1) != 0);
bool startfromsram = false; // bit 1: if "1", movie starts from reset with an embedded SRAM
bool startfromsram = ((flags & 2) != 0);
if ((moviestartflags & 0x01) != 0) startfromquicksave = true; // other: reserved, set to 0
if ((moviestartflags & 0x02) != 0) startfromsram = true; // We can't start from either save option.
if (startfromquicksave || startfromsram)
if (startfromquicksave & startfromsram)
{ {
errorMsg = "Movies that begin with a savestate are not supported."; 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 = "The movie file is invalid.";
r.Close(); r.Close();
fs.Close(); fs.Close();
return null; return null;
} }
//015 1-byte flags: controller flags
//0x15 flags = r.ReadByte();
Byte controllerflags = r.ReadByte(); // TODO: Handle the additional controllers.
int players = 0;
int numControllers; //number of controllers changes for SGB // bit 0: controller 1 in use
if ((flags & 1) != 0)
if ((controllerflags & 0x08) != 0) numControllers = 4; players++;
else if ((controllerflags & 0x04) != 0) numControllers = 3; else
else if ((controllerflags & 0x02) != 0) numControllers = 2;
else numControllers = 1;
//0x16
Byte systemflags = r.ReadByte(); //what system is it?
bool is_gba = false;
bool is_gbc = false;
bool is_sgb = false;
bool is_gb = false;
if ((systemflags & 0x04) != 0) is_sgb = true;
if ((systemflags & 0x02) != 0) is_gbc = true;
if ((systemflags & 0x01) != 0) is_gba = true;
else is_gb = true;
if (is_gb & is_gbc & is_gba & is_sgb)
{ {
errorMsg = "Not a valid .VBM platform type."; errorMsg = "Controller 1 must be in use.";
r.Close(); r.Close();
fs.Close(); fs.Close();
return null; return null;
} }
//TODO: set platform in header // bit 1: controller 2 in use (SGB games can be 2-player multiplayer)
if ((flags & 2) != 0)
//0x17 players++;
Byte flags = r.ReadByte(); //emulation flags // bit 2: controller 3 in use (SGB games can be 3- or 4-player multiplayer with multitap)
if ((flags & 4) != 0)
//placeholder for reserved bit (set to 0) players++;
bool echoramfix = false; // bit 3: controller 4 in use (SGB games can be 3- or 4-player multiplayer with multitap)
bool gbchdma5fix = false; if ((flags & 8) != 0)
bool lagreduction = false; players++;
//placeholder for unsupported bit // other: reserved
bool rtcenable = false; // 016 1-byte flags: system flags (game always runs at 60 frames/sec)
bool skipbiosfile = false; flags = r.ReadByte();
bool usebiosfile = false; // bit 0: if "1", movie is for the GBA system
bool is_gba = ((flags & 1) != 0);
if ((flags & 0x40) != 0) echoramfix = true; // bit 1: if "1", movie is for the GBC system
if ((flags & 0x20) != 0) gbchdma5fix = true; bool is_gbc = ((flags & 2) != 0);
if ((flags & 0x10) != 0) lagreduction = true; // bit 2: if "1", movie is for the SGB system
if ((flags & 0x08) != 0) bool is_sgb = ((flags & 4) != 0);
// other: reserved, set to 0
// (At most one of bits 0, 1, 2 can be "1")
if (!(is_gba ^ is_gbc ^ is_sgb) && (is_gba || is_gbc || is_sgb))
{ {
errorMsg = "Invalid .VBM file."; errorMsg = "This is not a valid .VBM file.";
r.Close(); r.Close();
fs.Close(); fs.Close();
return null; return null;
} }
if ((flags & 0x04) != 0) rtcenable = true; // (If all 3 of these bits are "0", it is for regular GB.)
if ((flags & 0x02) != 0) skipbiosfile = true; string platform = "GB";
if ((flags & 0x01) != 0) usebiosfile = true; if (is_gba)
platform = "GBA";
//0x18 if (is_gbc)
uint winsavetype = r.ReadUInt32(); platform = "GBC";
uint winflashsize = r.ReadUInt32(); if (is_sgb)
platform = "SGB";
//0x20 m.Header.SetHeaderLine(MovieHeader.PLATFORM, platform);
uint gbemulatortype = r.ReadUInt32(); // 017 1-byte flags: (values of some boolean emulator options)
flags = r.ReadByte();
char[] internalgamename = r.ReadChars(0x0C); /*
string gamename = new String(internalgamename); * bit 0: (useBiosFile) if "1" and the movie is of a GBA game, the movie was made using a GBA BIOS file.
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gamename); * bit 1: (skipBiosFile) if "0" and the movie was made with a GBA BIOS file, the BIOS intro is included in the
* movie.
//0x30 * bit 2: (rtcEnable) if "1", the emulator "real time clock" feature was enabled.
Byte minorversion = r.ReadByte(); */
Byte internalcrc = r.ReadByte(); // bit 3: (unsupported) must be "0" or the movie file is considered invalid (legacy).
ushort internalchacksum = r.ReadUInt16(); if ((flags & 8) != 0)
uint unitcode = r.ReadUInt32(); {
uint saveoffset = r.ReadUInt32(); //set to 0 if unused errorMsg = "This is not a valid .VBM file.";
uint controllerdataoffset = r.ReadUInt32(); r.Close();
fs.Close();
//0x40 start info. return null;
char[] authorsname = r.ReadChars(0x40); //vbm specification states these strings }
string author = new String(authorsname); //are locale dependant. /*
* bit 4: (lagReduction) if "0" and the movie is of a GBA game, the movie was made using the old excessively
* laggy GBA timing.
* bit 5: (gbcHdma5Fix) if "0" and the movie is of a GBC game, the movie was made using the old buggy HDMA5
* timing.
* bit 6: (echoRAMFix) if "1" and the movie is of a GB, GBC, or SGB game, the movie was made with Echo RAM Fix
* on, otherwise it was made with Echo RAM Fix off.
* bit 7: reserved, set to 0.
*/
/*
018 4-byte little-endian unsigned int: theApp.winSaveType (value of that emulator option)
01C 4-byte little-endian unsigned int: theApp.winFlashSize (value of that emulator option)
020 4-byte little-endian unsigned int: gbEmulatorType (value of that emulator option)
*/
r.ReadBytes(12);
/*
024 12-byte character array: the internal game title of the ROM used while recording, not necessarily
null-terminated (ASCII?)
*/
string gameName = RemoveNull(BytesToString(r, 12));
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName);
// 030 1-byte unsigned char: minor version/revision number of current VBM version, the latest is "1"
byte minorVersion = r.ReadByte();
m.Header.Comments.Add(MOVIEORIGIN + " .VBM version " + majorVersion + "." + minorVersion);
/*
031 1-byte unsigned char: the internal CRC of the ROM used while recording
032 2-byte little-endian unsigned short: the internal Checksum of the ROM used while recording, or a calculated
CRC16 of the BIOS if GBA
034 4-byte little-endian unsigned int: the Game Code of the ROM used while recording, or the Unit Code if not
GBA
038 4-byte little-endian unsigned int: offset to the savestate or SRAM inside file, set to 0 if unused
*/
r.ReadBytes(11);
// 03C 4-byte little-endian unsigned int: offset to the controller data inside file
uint firstFrameOffset = r.ReadUInt32();
// After the header is 192 bytes of text. The first 64 of these 192 bytes are for the author's name (or names).
string author = RemoveNull(BytesToString(r, 64));
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.
//0x80 string movieDescription = RemoveNull(BytesToString(r, 128));
char[] moviedescription = r.ReadChars(0x80); m.Header.Comments.Add("comment " + movieDescription);
/*
//0x0100 TODO: implement start data. There are no specifics on the googlecode page as to how long the SRAM or savestate
//End of VBM header should be.
*/
//if there is no SRAM or savestate, the controller data should start at 0x0100 by default, // If there is no "Start Data", this will probably begin at byte 0x100 in the file, but this is not guaranteed.
//but this is not buaranteed r.BaseStream.Position = firstFrameOffset;
/*
//TODO: implement start data. There are no specifics on the googlecode page as to how long * 01 00 A
//the SRAM or savestate should be. * 02 00 B
* 04 00 Select
uint framesleft = frameCount; * 08 00 Start
* 10 00 Right
r.BaseStream.Position = controllerdataoffset; //advances to controller data. * 20 00 Left
* 40 00 Up
int currentoffset = (int)controllerdataoffset; * 80 00 Down
*/
string[] buttons = new string[8] { "A", "B", "Select", "Start", "Right", "Left", "Up", "Down" };
/*
* 00 01 R
* 00 02 L
* 00 04 Reset (old timing)
* 00 08 Reset (new timing since version 1.1)
* 00 10 Left motion sensor
* 00 20 Right motion sensor
* 00 40 Down motion sensor
* 00 80 Up motion sensor
*/
string[] other = new string[8] {
"R", "L", "Reset (old timing)" , "Reset (new timing since version 1.1)", "Left motion sensor",
"Right motion sensor", "Down motion sensor", "Up motion sensor"
};
for (int frame = 1; frame <= frameCount; frame++)
{
/*
A stream of 2-byte bitvectors which indicate which buttons are pressed at each point in time. They will come
in groups of however many controllers are active, in increasing order.
*/
ushort controllerState = r.ReadUInt16();
SimpleController controllers = new SimpleController(); SimpleController controllers = new SimpleController();
controllers.Type = new ControllerDefinition(); controllers.Type = new ControllerDefinition();
controllers.Type.Name = "Gameboy Controller"; controllers.Type.Name = "Gameboy Controller";
string[] buttons = new string[8] {"A", "B", "Select", "Start", "Right", "Left", "Up", "Down"}; ushort and = 0x1;
for (int frame = 1; frame <= frameCount; frame++)
{
ushort controllerstate = r.ReadUInt16();
// TODO: reset, GBA buttons go here
byte and = 0x1;
for (int button = 0; button < buttons.Length; button++) for (int button = 0; button < buttons.Length; button++)
{ {
controllers["P1 " + buttons[button]] = ((int)(controllerstate & and) != 0); controllers[buttons[button]] = ((int)(controllerState & and) != 0);
and <<= 1; 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.
and = 0x100;
if (warningMsg == "")
{
for (int button = 0; button < other.Length; button++)
{
if ((controllerState & and) != 0)
{
warningMsg = other[button];
break;
}
and <<= 1;
}
if (warningMsg != "")
warningMsg = "Unable to import " + warningMsg + " at frame " + frame + ".";
}
// TODO: Handle the additional controllers.
r.ReadBytes((players - 1) * 2);
}
r.Close(); r.Close();
fs.Close(); fs.Close();
return m; return m;