-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:
parent
e3ba08fefd
commit
c9b5f9989a
|
@ -179,7 +179,7 @@ namespace BizHawk.MultiClient
|
|||
|
||||
if (ControlType == "Gameboy Controller")
|
||||
{
|
||||
input.Append(".|"); //TODO: reset goes here
|
||||
input.Append("."); // TODO: reset goes here
|
||||
}
|
||||
if (ControlType == "NES Controller")
|
||||
{
|
||||
|
|
|
@ -11,6 +11,9 @@ namespace BizHawk.MultiClient
|
|||
{
|
||||
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.
|
||||
public static Movie ImportFile(string path, out string errorMsg, out string warningMsg)
|
||||
{
|
||||
|
@ -81,8 +84,8 @@ namespace BizHawk.MultiClient
|
|||
}
|
||||
|
||||
/*
|
||||
Read bytes from a BinaryReader and translate them into a string for either the hexidecimal representation of the
|
||||
binary numbers or the UTF-8 string they represent.
|
||||
Read bytes from a BinaryReader and translate them into a string for either the hexidecimal representation of the
|
||||
binary numbers or the UTF-8 string they represent.
|
||||
*/
|
||||
private static string BytesToString(BinaryReader r, int size, bool hexadecimal = false)
|
||||
{
|
||||
|
@ -130,10 +133,10 @@ namespace BizHawk.MultiClient
|
|||
if (line == "")
|
||||
continue;
|
||||
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"))
|
||||
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"))
|
||||
m.Header.SetHeaderLine(MovieHeader.GAMENAME, ParseHeader(line, "romFilename"));
|
||||
|
@ -240,8 +243,8 @@ namespace BizHawk.MultiClient
|
|||
warningMsg = "Unable to import " + warningMsg + " command on line " + lineNum + ".";
|
||||
}
|
||||
/*
|
||||
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.
|
||||
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.
|
||||
*/
|
||||
for (int section = 2; section < sections.Length - 1; section++)
|
||||
{
|
||||
|
@ -309,44 +312,49 @@ namespace BizHawk.MultiClient
|
|||
}
|
||||
// 004 4-byte little-endian unsigned int: version number, must be 2
|
||||
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
|
||||
byte flags = r.ReadByte();
|
||||
// bit 0: reserved, set to 0
|
||||
/*
|
||||
* bit 0: reserved, set to 0
|
||||
* bit 1:
|
||||
** if "0", movie begins from an embedded "quicksave" snapshot
|
||||
** 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
|
||||
bit 1:
|
||||
* if "0", movie begins from an embedded "quicksave" snapshot
|
||||
* if "1", movie begins from reset or power-on[1]
|
||||
*/
|
||||
if ((int)(flags & 2) == 0)
|
||||
if ((flags & 2) == 0)
|
||||
{
|
||||
errorMsg = "Movies that begin with a savestate are not supported.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
bool pal = false;
|
||||
if ((int)(flags & 4) != 0)
|
||||
pal = true;
|
||||
/*
|
||||
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.
|
||||
bit 2:
|
||||
* 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
|
||||
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.
|
||||
*/
|
||||
m.Header.SetHeaderLine("PAL", pal.ToString());
|
||||
bool movieSyncHackOn = true;
|
||||
if ((int)(flags & 16) != 0)
|
||||
movieSyncHackOn = false;
|
||||
bool movieSyncHackOn = ((flags & 16) != 0);
|
||||
m.Header.SetHeaderLine("SyncHack", movieSyncHackOn.ToString());
|
||||
/*
|
||||
009 1-byte flags: reserved, set to 0
|
||||
00A 1-byte flags: reserved, set to 0
|
||||
00B 1-byte flags: reserved, set to 0
|
||||
009 1-byte flags: reserved, set to 0
|
||||
00A 1-byte flags: reserved, set to 0
|
||||
00B 1-byte flags: reserved, set to 0
|
||||
*/
|
||||
r.ReadBytes(3);
|
||||
// 00C 4-byte little-endian unsigned int: number of frames
|
||||
|
@ -357,10 +365,10 @@ namespace BizHawk.MultiClient
|
|||
// 014 4-byte little-endian unsigned int: length of controller data in bytes
|
||||
uint movieDataSize = r.ReadUInt32();
|
||||
/*
|
||||
018 4-byte little-endian unsigned int: offset to the savestate inside file
|
||||
The savestate offset is <header_size + length_of_metadata_in_bytes + padding>. The savestate offset should be
|
||||
4-byte aligned. At the savestate offset there is a savestate file. The savestate exists even if the movie is
|
||||
reset-based.
|
||||
018 4-byte little-endian unsigned int: offset to the savestate inside file
|
||||
The savestate offset is <header_size + length_of_metadata_in_bytes + padding>. The savestate offset should be
|
||||
4-byte aligned. At the savestate offset there is a savestate file. The savestate exists even if the movie is
|
||||
reset-based.
|
||||
*/
|
||||
r.ReadUInt32();
|
||||
// 01C 4-byte little-endian unsigned int: offset to the controller data inside file
|
||||
|
@ -370,7 +378,7 @@ namespace BizHawk.MultiClient
|
|||
m.Header.SetHeaderLine("MD5", MD5);
|
||||
// 030 4-byte little-endian unsigned int: version of the emulator used
|
||||
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.
|
||||
List<byte> gameBytes = new List<byte>();
|
||||
while (r.PeekChar() != 0)
|
||||
|
@ -380,9 +388,9 @@ namespace BizHawk.MultiClient
|
|||
string gameName = System.Text.Encoding.UTF8.GetString(gameBytes.ToArray());
|
||||
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
|
||||
and ends at the savestate offset. This string is displayed as "Author Info" in the Windows version of the
|
||||
emulator.
|
||||
After the header comes "metadata", which is UTF8-coded movie title string. The metadata begins after the ROM
|
||||
name and ends at the savestate offset. This string is displayed as "Author Info" in the Windows version of the
|
||||
emulator.
|
||||
*/
|
||||
List<byte> authorBytes = new List<byte>();
|
||||
while (r.PeekChar() != 0)
|
||||
|
@ -409,13 +417,13 @@ namespace BizHawk.MultiClient
|
|||
int delta = (update >> 5) & 3;
|
||||
int frames = 0;
|
||||
/*
|
||||
The delta byte(s) indicate the number of emulator frames between this update and the next update. It is
|
||||
encoded in little-endian format and its size depends on the magnitude of the delta:
|
||||
Delta of: Number of bytes:
|
||||
0 0
|
||||
1-255 1
|
||||
256-65535 2
|
||||
65536-(2^24-1) 3
|
||||
The delta byte(s) indicate the number of emulator frames between this update and the next update. It is
|
||||
encoded in little-endian format and its size depends on the magnitude of the delta:
|
||||
Delta of: Number of bytes:
|
||||
0 0
|
||||
1-255 1
|
||||
256-65535 2
|
||||
65536-(2^24-1) 3
|
||||
*/
|
||||
for (int b = 0; b < delta; b++)
|
||||
frames += r.ReadByte() * (int)Math.Pow(2, b * 8);
|
||||
|
@ -435,58 +443,57 @@ namespace BizHawk.MultiClient
|
|||
// Control update: 1aabbbbb
|
||||
bool reset = false;
|
||||
int command = update & 0x1F;
|
||||
/*
|
||||
bbbbb:
|
||||
** 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)
|
||||
// bbbbb:
|
||||
if (warningMsg == "")
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
reset = !controllers["Reset"];
|
||||
controllers["Reset"] = reset;
|
||||
break;
|
||||
case 2:
|
||||
reset = true;
|
||||
if (frame != 1)
|
||||
{
|
||||
warningMsg = "hard reset";
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
warningMsg = "VS System Insert Coin";
|
||||
break;
|
||||
case 8:
|
||||
warningMsg = "VS System Dipswitch 0 Toggle";
|
||||
break;
|
||||
case 24:
|
||||
warningMsg = "FDS Insert";
|
||||
break;
|
||||
case 25:
|
||||
warningMsg = "FDS Eject";
|
||||
break;
|
||||
case 26:
|
||||
warningMsg = "FDS Select Side";
|
||||
break;
|
||||
default:
|
||||
warningMsg = "unknown";
|
||||
break;
|
||||
switch (command)
|
||||
{
|
||||
// Do nothing
|
||||
case 0:
|
||||
break;
|
||||
// Reset
|
||||
case 1:
|
||||
reset = !controllers["Reset"];
|
||||
controllers["Reset"] = reset;
|
||||
break;
|
||||
// Power cycle
|
||||
case 2:
|
||||
reset = true;
|
||||
if (frame != 1)
|
||||
warningMsg = "hard reset";
|
||||
break;
|
||||
// VS System Insert Coin
|
||||
case 7:
|
||||
warningMsg = "VS System Insert Coin";
|
||||
break;
|
||||
// VS System Dipswitch 0 Toggle
|
||||
case 8:
|
||||
warningMsg = "VS System Dipswitch 0 Toggle";
|
||||
break;
|
||||
// FDS Insert
|
||||
case 24:
|
||||
warningMsg = "FDS Insert";
|
||||
break;
|
||||
// FDS Eject
|
||||
case 25:
|
||||
warningMsg = "FDS Eject";
|
||||
break;
|
||||
// FDS Select Side
|
||||
case 26:
|
||||
warningMsg = "FDS Select Side";
|
||||
break;
|
||||
default:
|
||||
warningMsg = "unknown";
|
||||
break;
|
||||
}
|
||||
if (warningMsg != "")
|
||||
warningMsg = "Unable to import " + warningMsg + " command at frame " + frame + ".";
|
||||
}
|
||||
if (warningMsg != "")
|
||||
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
|
||||
quicksave is actually loaded. This flag can't therefore be trusted. To check if the movie actually begins
|
||||
from reset, one must analyze the controller data and see if the first non-idle command in the file is a
|
||||
Reset or Power Cycle type control command.
|
||||
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 from reset, one must analyze the controller data and see if the first non-idle command in the
|
||||
file is a Reset or Power Cycle type control command.
|
||||
*/
|
||||
if (!reset && frame == 1)
|
||||
{
|
||||
|
@ -516,8 +523,8 @@ namespace BizHawk.MultiClient
|
|||
*/
|
||||
int button = update & 7;
|
||||
/*
|
||||
The controller update toggles the affected input. Controller update data is emitted to the movie file
|
||||
only when the state of the controller changes.
|
||||
The controller update toggles the affected input. Controller update data is emitted to the movie file
|
||||
only when the state of the controller changes.
|
||||
*/
|
||||
controllers["P" + player + " " + buttons[button]] = !controllers["P" + player + " " + buttons[button]];
|
||||
}
|
||||
|
@ -556,27 +563,20 @@ namespace BizHawk.MultiClient
|
|||
}
|
||||
// 004 1-byte flags:
|
||||
byte flags = r.ReadByte();
|
||||
/*
|
||||
* bit 7: 0=reset-based, 1=savestate-based
|
||||
* other bits: unknown, set to 0
|
||||
*/
|
||||
if ((int)(flags & 0x80) != 0)
|
||||
// bit 7: 0=reset-based, 1=savestate-based
|
||||
if ((flags & 0x80) != 0)
|
||||
{
|
||||
errorMsg = "Movies that begin with a savestate are not supported.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
// other bits: unknown, set to 0
|
||||
// 005 1-byte flags:
|
||||
flags = r.ReadByte();
|
||||
/*
|
||||
* bit 5: is a FDS recording
|
||||
* bit 6: uses controller 2
|
||||
* bit 7: uses controller 1
|
||||
* other bits: unknown, set to 0
|
||||
*/
|
||||
// bit 5: is a FDS recording
|
||||
bool FDS;
|
||||
if ((int)(flags & 0x20) != 0)
|
||||
if ((flags & 0x20) != 0)
|
||||
{
|
||||
FDS = true;
|
||||
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "FDS");
|
||||
|
@ -586,30 +586,26 @@ namespace BizHawk.MultiClient
|
|||
FDS = false;
|
||||
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "NES");
|
||||
}
|
||||
bool controller2 = false;
|
||||
if ((int)(flags & 0x40) != 0)
|
||||
{
|
||||
controller2 = true;
|
||||
}
|
||||
bool controller1 = false;
|
||||
if ((int)(flags & 0x80) != 0)
|
||||
{
|
||||
controller1 = true;
|
||||
}
|
||||
// bit 6: uses controller 2
|
||||
bool controller2 = ((flags & 0x40) != 0);
|
||||
// bit 7: uses controller 1
|
||||
bool controller1 = ((flags & 0x80) != 0);
|
||||
// other bits: unknown, set to 0
|
||||
// 006 4-byte little-endian unsigned int: unknown, set to 00000000
|
||||
r.ReadInt32();
|
||||
// 00A 4-byte little-endian unsigned int: rerecord count minus 1
|
||||
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 number is 0. Famtasia however displays "1" in such case. It always adds 1 to the number found in the file.
|
||||
The rerecord count stored in the file is the number of times a savestate was loaded. If a savestate was never
|
||||
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);
|
||||
// 00E 2-byte little-endian unsigned int: unknown, set to 0000
|
||||
r.ReadInt16();
|
||||
// 010 64-byte zero-terminated emulator identifier string
|
||||
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
|
||||
string gameName = RemoveNull(BytesToString(r, 64));
|
||||
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gameName);
|
||||
|
@ -621,14 +617,14 @@ 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
|
||||
fps.
|
||||
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");
|
||||
/*
|
||||
090 frame data begins here
|
||||
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>.
|
||||
090 frame data begins here
|
||||
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)
|
||||
|
@ -655,9 +651,9 @@ namespace BizHawk.MultiClient
|
|||
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
|
||||
data takes 1 byte. If all three exist, the frame is 3 bytes. For example, if the movie is a regular NES game
|
||||
with only controller 1 data, a frame is 1 byte.
|
||||
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
|
||||
with only controller 1 data, a frame is 1 byte.
|
||||
*/
|
||||
int player = 1;
|
||||
while (player <= 3)
|
||||
|
@ -668,11 +664,11 @@ namespace BizHawk.MultiClient
|
|||
player++;
|
||||
if (player != 3)
|
||||
{
|
||||
byte controllerstate = r.ReadByte();
|
||||
byte controllerState = r.ReadByte();
|
||||
byte and = 0x1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -889,7 +885,7 @@ namespace BizHawk.MultiClient
|
|||
}
|
||||
// 0004: 4-byte little endian unsigned int: dega version
|
||||
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
|
||||
uint frameCount = r.ReadUInt32();
|
||||
// 000c: 4-byte little endian unsigned int: rerecord count
|
||||
|
@ -915,22 +911,16 @@ namespace BizHawk.MultiClient
|
|||
m.Header.SetHeaderLine(MovieHeader.AUTHOR, author);
|
||||
// 0060: 4-byte little endian flags
|
||||
byte flags = r.ReadByte();
|
||||
/*
|
||||
* bit 0: unused
|
||||
* bit 1: PAL
|
||||
* bit 2: Japan
|
||||
* bit 3: Game Gear (version 1.16+)
|
||||
*/
|
||||
bool pal = false;
|
||||
if ((int)(flags & 2) != 0)
|
||||
pal = true;
|
||||
// bit 0: unused
|
||||
// bit 1: PAL
|
||||
bool pal = ((flags & 2) != 0);
|
||||
m.Header.SetHeaderLine("PAL", pal.ToString());
|
||||
bool japan = false;
|
||||
if ((int)(flags & 4) != 0)
|
||||
japan = true;
|
||||
bool japan = ((flags & 4) != 0);
|
||||
// bit 2: Japan
|
||||
m.Header.SetHeaderLine("Japan", japan.ToString());
|
||||
// bit 3: Game Gear (version 1.16+)
|
||||
bool gamegear;
|
||||
if ((int)(flags & 8) != 0)
|
||||
if ((flags & 8) != 0)
|
||||
{
|
||||
gamegear = true;
|
||||
m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG");
|
||||
|
@ -966,23 +956,23 @@ namespace BizHawk.MultiClient
|
|||
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
|
||||
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.
|
||||
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();
|
||||
byte controllerState = r.ReadByte();
|
||||
byte and = 1;
|
||||
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;
|
||||
}
|
||||
if (player == 1)
|
||||
controllers["Pause"] = (
|
||||
((int)(controllerstate & 0x40) != 0 && (!gamegear)) ||
|
||||
((int)(controllerstate & 0x80) != 0 && gamegear)
|
||||
((int)(controllerState & 0x40) != 0 && (!gamegear)) ||
|
||||
((int)(controllerState & 0x80) != 0 && gamegear)
|
||||
);
|
||||
}
|
||||
MnemonicsGenerator mg = new MnemonicsGenerator();
|
||||
|
@ -1046,23 +1036,23 @@ namespace BizHawk.MultiClient
|
|||
byte ControllerFlags = r.ReadByte();
|
||||
|
||||
int numControllers;
|
||||
if ((int)(ControllerFlags & 16) != 0)
|
||||
if ((ControllerFlags & 16) != 0)
|
||||
numControllers = 5;
|
||||
else if ((int)(ControllerFlags & 8) != 0)
|
||||
else if ((ControllerFlags & 8) != 0)
|
||||
numControllers = 4;
|
||||
else if ((int)(ControllerFlags & 4) != 0)
|
||||
else if ((ControllerFlags & 4) != 0)
|
||||
numControllers = 3;
|
||||
else if ((int)(ControllerFlags & 2) != 0)
|
||||
else if ((ControllerFlags & 2) != 0)
|
||||
numControllers = 2;
|
||||
else
|
||||
numControllers = 1;
|
||||
|
||||
byte MovieFlags = r.ReadByte();
|
||||
|
||||
if ((int)(MovieFlags & 1) == 0)
|
||||
if ((MovieFlags & 1) == 0)
|
||||
return null; //TODO: Savestate movies not supported error
|
||||
|
||||
if ((int)(MovieFlags & 2) != 0)
|
||||
if ((MovieFlags & 2) != 0)
|
||||
{
|
||||
m.Header.SetHeaderLine("PAL", "True");
|
||||
}
|
||||
|
@ -1108,167 +1098,232 @@ namespace BizHawk.MultiClient
|
|||
{
|
||||
errorMsg = "";
|
||||
warningMsg = "";
|
||||
//Converts vbm to native text based format.
|
||||
Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY);
|
||||
|
||||
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
BinaryReader r = new BinaryReader(fs);
|
||||
|
||||
//0xoffset
|
||||
//0x00
|
||||
uint signature = r.ReadUInt32(); //always 56 42 4D 1A (VBM\x1A)
|
||||
if (signature != 0x56424D1A)
|
||||
// 000 4-byte signature: 56 42 4D 1A "VBM\x1A"
|
||||
string signature = BytesToString(r, 4);
|
||||
if (signature != "VBM\x1A")
|
||||
{
|
||||
errorMsg = "This is not a valid .VBM file.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
|
||||
uint versionno = r.ReadUInt32(); //always 1
|
||||
uint uid = r.ReadUInt32(); //time of recording
|
||||
// 004 4-byte little-endian unsigned int: major version number, must be "1"
|
||||
uint majorVersion = r.ReadUInt32();
|
||||
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());
|
||||
// 00C 4-byte little-endian unsigned int: number of frames
|
||||
uint frameCount = r.ReadUInt32();
|
||||
|
||||
//0x10
|
||||
// 010 4-byte little-endian unsigned int: rerecord count
|
||||
uint rerecordCount = r.ReadUInt32();
|
||||
m.SetRerecords((int)rerecordCount);
|
||||
m.Header.SetHeaderLine(MovieHeader.RERECORDS, m.Rerecords.ToString());
|
||||
Byte moviestartflags = r.ReadByte();
|
||||
|
||||
bool startfromquicksave = false;
|
||||
bool startfromsram = false;
|
||||
|
||||
if ((moviestartflags & 0x01) != 0) startfromquicksave = true;
|
||||
if ((moviestartflags & 0x02) != 0) startfromsram = true;
|
||||
|
||||
if (startfromquicksave & startfromsram)
|
||||
// 014 1-byte flags: (movie start flags)
|
||||
byte flags = r.ReadByte();
|
||||
// bit 0: if "1", movie starts from an embedded "quicksave" snapshot
|
||||
bool startfromquicksave = ((flags & 1) != 0);
|
||||
// bit 1: if "1", movie starts from reset with an embedded SRAM
|
||||
bool startfromsram = ((flags & 2) != 0);
|
||||
// other: reserved, set to 0
|
||||
// We can't start from either save option.
|
||||
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();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
|
||||
//0x15
|
||||
Byte controllerflags = r.ReadByte();
|
||||
|
||||
int numControllers; //number of controllers changes for SGB
|
||||
|
||||
if ((controllerflags & 0x08) != 0) numControllers = 4;
|
||||
else if ((controllerflags & 0x04) != 0) numControllers = 3;
|
||||
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)
|
||||
//015 1-byte flags: controller flags
|
||||
flags = r.ReadByte();
|
||||
// TODO: Handle the additional controllers.
|
||||
int players = 0;
|
||||
// bit 0: controller 1 in use
|
||||
if ((flags & 1) != 0)
|
||||
players++;
|
||||
else
|
||||
{
|
||||
errorMsg = "Not a valid .VBM platform type.";
|
||||
errorMsg = "Controller 1 must be in use.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
//TODO: set platform in header
|
||||
|
||||
//0x17
|
||||
Byte flags = r.ReadByte(); //emulation flags
|
||||
|
||||
//placeholder for reserved bit (set to 0)
|
||||
bool echoramfix = false;
|
||||
bool gbchdma5fix = false;
|
||||
bool lagreduction = false;
|
||||
//placeholder for unsupported bit
|
||||
bool rtcenable = false;
|
||||
bool skipbiosfile = false;
|
||||
bool usebiosfile = false;
|
||||
|
||||
if ((flags & 0x40) != 0) echoramfix = true;
|
||||
if ((flags & 0x20) != 0) gbchdma5fix = true;
|
||||
if ((flags & 0x10) != 0) lagreduction = true;
|
||||
if ((flags & 0x08) != 0)
|
||||
// bit 1: controller 2 in use (SGB games can be 2-player multiplayer)
|
||||
if ((flags & 2) != 0)
|
||||
players++;
|
||||
// bit 2: controller 3 in use (SGB games can be 3- or 4-player multiplayer with multitap)
|
||||
if ((flags & 4) != 0)
|
||||
players++;
|
||||
// bit 3: controller 4 in use (SGB games can be 3- or 4-player multiplayer with multitap)
|
||||
if ((flags & 8) != 0)
|
||||
players++;
|
||||
// other: reserved
|
||||
// 016 1-byte flags: system flags (game always runs at 60 frames/sec)
|
||||
flags = r.ReadByte();
|
||||
// bit 0: if "1", movie is for the GBA system
|
||||
bool is_gba = ((flags & 1) != 0);
|
||||
// bit 1: if "1", movie is for the GBC system
|
||||
bool is_gbc = ((flags & 2) != 0);
|
||||
// bit 2: if "1", movie is for the SGB system
|
||||
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();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
if ((flags & 0x04) != 0) rtcenable = true;
|
||||
if ((flags & 0x02) != 0) skipbiosfile = true;
|
||||
if ((flags & 0x01) != 0) usebiosfile = true;
|
||||
|
||||
//0x18
|
||||
uint winsavetype = r.ReadUInt32();
|
||||
uint winflashsize = r.ReadUInt32();
|
||||
|
||||
//0x20
|
||||
uint gbemulatortype = r.ReadUInt32();
|
||||
|
||||
char[] internalgamename = r.ReadChars(0x0C);
|
||||
string gamename = new String(internalgamename);
|
||||
m.Header.SetHeaderLine(MovieHeader.GAMENAME, gamename);
|
||||
|
||||
//0x30
|
||||
Byte minorversion = r.ReadByte();
|
||||
Byte internalcrc = r.ReadByte();
|
||||
ushort internalchacksum = r.ReadUInt16();
|
||||
uint unitcode = r.ReadUInt32();
|
||||
uint saveoffset = r.ReadUInt32(); //set to 0 if unused
|
||||
uint controllerdataoffset = r.ReadUInt32();
|
||||
|
||||
//0x40 start info.
|
||||
char[] authorsname = r.ReadChars(0x40); //vbm specification states these strings
|
||||
string author = new String(authorsname); //are locale dependant.
|
||||
// (If all 3 of these bits are "0", it is for regular GB.)
|
||||
string platform = "GB";
|
||||
if (is_gba)
|
||||
platform = "GBA";
|
||||
if (is_gbc)
|
||||
platform = "GBC";
|
||||
if (is_sgb)
|
||||
platform = "SGB";
|
||||
m.Header.SetHeaderLine(MovieHeader.PLATFORM, platform);
|
||||
// 017 1-byte flags: (values of some boolean emulator options)
|
||||
flags = r.ReadByte();
|
||||
/*
|
||||
* bit 0: (useBiosFile) if "1" and the movie is of a GBA game, the movie was made using a GBA BIOS file.
|
||||
* bit 1: (skipBiosFile) if "0" and the movie was made with a GBA BIOS file, the BIOS intro is included in the
|
||||
* movie.
|
||||
* 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).
|
||||
if ((flags & 8) != 0)
|
||||
{
|
||||
errorMsg = "This is not a valid .VBM file.";
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
* 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);
|
||||
|
||||
//0x80
|
||||
char[] moviedescription = r.ReadChars(0x80);
|
||||
|
||||
//0x0100
|
||||
//End of VBM header
|
||||
|
||||
//if there is no SRAM or savestate, the controller data should start at 0x0100 by default,
|
||||
//but this is not buaranteed
|
||||
|
||||
//TODO: implement start data. There are no specifics on the googlecode page as to how long
|
||||
//the SRAM or savestate should be.
|
||||
|
||||
uint framesleft = frameCount;
|
||||
|
||||
r.BaseStream.Position = controllerdataoffset; //advances to controller data.
|
||||
|
||||
int currentoffset = (int)controllerdataoffset;
|
||||
|
||||
SimpleController controllers = new SimpleController();
|
||||
controllers.Type = new ControllerDefinition();
|
||||
controllers.Type.Name = "Gameboy Controller";
|
||||
string[] buttons = new string[8] {"A", "B", "Select", "Start", "Right", "Left", "Up", "Down"};
|
||||
|
||||
// The following 128 bytes are for a description of the movie. Both parts must be null-terminated.
|
||||
string movieDescription = RemoveNull(BytesToString(r, 128));
|
||||
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
|
||||
should be.
|
||||
*/
|
||||
// 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;
|
||||
/*
|
||||
* 01 00 A
|
||||
* 02 00 B
|
||||
* 04 00 Select
|
||||
* 08 00 Start
|
||||
* 10 00 Right
|
||||
* 20 00 Left
|
||||
* 40 00 Up
|
||||
* 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++)
|
||||
{
|
||||
ushort controllerstate = r.ReadUInt16();
|
||||
// TODO: reset, GBA buttons go here
|
||||
byte and = 0x1;
|
||||
/*
|
||||
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();
|
||||
controllers.Type = new ControllerDefinition();
|
||||
controllers.Type.Name = "Gameboy Controller";
|
||||
ushort and = 0x1;
|
||||
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;
|
||||
}
|
||||
MnemonicsGenerator mg = new MnemonicsGenerator();
|
||||
mg.SetSource(controllers);
|
||||
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);
|
||||
}
|
||||
MnemonicsGenerator mg = new MnemonicsGenerator();
|
||||
mg.SetSource(controllers);
|
||||
m.AppendFrame(mg.GetControllersAsMnemonic());
|
||||
r.Close();
|
||||
fs.Close();
|
||||
return m;
|
||||
|
|
Loading…
Reference in New Issue