-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")
|
if (ControlType == "Gameboy Controller")
|
||||||
{
|
{
|
||||||
input.Append(".|"); //TODO: reset goes here
|
input.Append("."); // TODO: reset goes here
|
||||||
}
|
}
|
||||||
if (ControlType == "NES Controller")
|
if (ControlType == "NES Controller")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
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.
|
binary numbers or the UTF-8 string they represent.
|
||||||
*/
|
*/
|
||||||
private static string BytesToString(BinaryReader r, int size, bool hexadecimal = false)
|
private static string BytesToString(BinaryReader r, int size, bool hexadecimal = false)
|
||||||
{
|
{
|
||||||
|
@ -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"));
|
||||||
|
@ -240,8 +243,8 @@ namespace BizHawk.MultiClient
|
||||||
warningMsg = "Unable to import " + warningMsg + " command on line " + lineNum + ".";
|
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
|
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.
|
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++)
|
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
|
// 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)
|
|
||||||
pal = true;
|
|
||||||
/*
|
/*
|
||||||
Starting with version 0.98.12 released on September 19, 2004, a PAL flag was added to the header but
|
bit 2:
|
||||||
unfortunately it is not reliable - the emulator does not take the PAL setting from the ROM, but from a user
|
* if "0", NTSC timing
|
||||||
preference. This means that this site cannot calculate movie lengths reliably.
|
* 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());
|
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
|
||||||
00A 1-byte flags: reserved, set to 0
|
00A 1-byte flags: reserved, set to 0
|
||||||
00B 1-byte flags: reserved, set to 0
|
00B 1-byte flags: reserved, set to 0
|
||||||
*/
|
*/
|
||||||
r.ReadBytes(3);
|
r.ReadBytes(3);
|
||||||
// 00C 4-byte little-endian unsigned int: number of frames
|
// 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
|
// 014 4-byte little-endian unsigned int: length of controller data in bytes
|
||||||
uint movieDataSize = r.ReadUInt32();
|
uint movieDataSize = r.ReadUInt32();
|
||||||
/*
|
/*
|
||||||
018 4-byte little-endian unsigned int: offset to the savestate inside file
|
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
|
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
|
4-byte aligned. At the savestate offset there is a savestate file. The savestate exists even if the movie is
|
||||||
reset-based.
|
reset-based.
|
||||||
*/
|
*/
|
||||||
r.ReadUInt32();
|
r.ReadUInt32();
|
||||||
// 01C 4-byte little-endian unsigned int: offset to the controller data inside file
|
// 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);
|
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,9 +388,9 @@ 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>();
|
||||||
while (r.PeekChar() != 0)
|
while (r.PeekChar() != 0)
|
||||||
|
@ -409,13 +417,13 @@ namespace BizHawk.MultiClient
|
||||||
int delta = (update >> 5) & 3;
|
int delta = (update >> 5) & 3;
|
||||||
int frames = 0;
|
int frames = 0;
|
||||||
/*
|
/*
|
||||||
The delta byte(s) indicate the number of emulator frames between this update and the next update. It is
|
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:
|
encoded in little-endian format and its size depends on the magnitude of the delta:
|
||||||
Delta of: Number of bytes:
|
Delta of: Number of bytes:
|
||||||
0 0
|
0 0
|
||||||
1-255 1
|
1-255 1
|
||||||
256-65535 2
|
256-65535 2
|
||||||
65536-(2^24-1) 3
|
65536-(2^24-1) 3
|
||||||
*/
|
*/
|
||||||
for (int b = 0; b < delta; b++)
|
for (int b = 0; b < delta; b++)
|
||||||
frames += r.ReadByte() * (int)Math.Pow(2, b * 8);
|
frames += r.ReadByte() * (int)Math.Pow(2, b * 8);
|
||||||
|
@ -435,58 +443,57 @@ 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)
|
|
||||||
{
|
{
|
||||||
case 0:
|
switch (command)
|
||||||
break;
|
{
|
||||||
case 1:
|
// Do nothing
|
||||||
reset = !controllers["Reset"];
|
case 0:
|
||||||
controllers["Reset"] = reset;
|
break;
|
||||||
break;
|
// Reset
|
||||||
case 2:
|
case 1:
|
||||||
reset = true;
|
reset = !controllers["Reset"];
|
||||||
if (frame != 1)
|
controllers["Reset"] = reset;
|
||||||
{
|
break;
|
||||||
warningMsg = "hard reset";
|
// Power cycle
|
||||||
}
|
case 2:
|
||||||
break;
|
reset = true;
|
||||||
case 7:
|
if (frame != 1)
|
||||||
warningMsg = "VS System Insert Coin";
|
warningMsg = "hard reset";
|
||||||
break;
|
break;
|
||||||
case 8:
|
// VS System Insert Coin
|
||||||
warningMsg = "VS System Dipswitch 0 Toggle";
|
case 7:
|
||||||
break;
|
warningMsg = "VS System Insert Coin";
|
||||||
case 24:
|
break;
|
||||||
warningMsg = "FDS Insert";
|
// VS System Dipswitch 0 Toggle
|
||||||
break;
|
case 8:
|
||||||
case 25:
|
warningMsg = "VS System Dipswitch 0 Toggle";
|
||||||
warningMsg = "FDS Eject";
|
break;
|
||||||
break;
|
// FDS Insert
|
||||||
case 26:
|
case 24:
|
||||||
warningMsg = "FDS Select Side";
|
warningMsg = "FDS Insert";
|
||||||
break;
|
break;
|
||||||
default:
|
// FDS Eject
|
||||||
warningMsg = "unknown";
|
case 25:
|
||||||
break;
|
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
|
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)
|
||||||
{
|
{
|
||||||
|
@ -516,8 +523,8 @@ namespace BizHawk.MultiClient
|
||||||
*/
|
*/
|
||||||
int button = update & 7;
|
int button = update & 7;
|
||||||
/*
|
/*
|
||||||
The controller update toggles the affected input. Controller update data is emitted to the movie file
|
The controller update toggles the affected input. Controller update data is emitted to the movie file
|
||||||
only when the state of the controller changes.
|
only when the state of the controller changes.
|
||||||
*/
|
*/
|
||||||
controllers["P" + player + " " + buttons[button]] = !controllers["P" + player + " " + buttons[button]];
|
controllers["P" + player + " " + buttons[button]] = !controllers["P" + player + " " + buttons[button]];
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -621,14 +617,14 @@ namespace BizHawk.MultiClient
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
The file format has no means of identifying NTSC/PAL. It is always assumed that the game is NTSC - that is, 60
|
The file format has no means of identifying NTSC/PAL. It is always assumed that the game is NTSC - that is, 60
|
||||||
fps.
|
fps.
|
||||||
*/
|
*/
|
||||||
m.Header.SetHeaderLine("PAL", "False");
|
m.Header.SetHeaderLine("PAL", "False");
|
||||||
/*
|
/*
|
||||||
090 frame data begins here
|
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
|
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>.
|
<number of bytes per frame>.
|
||||||
*/
|
*/
|
||||||
int bytesPerFrame = 0;
|
int bytesPerFrame = 0;
|
||||||
if (controller1)
|
if (controller1)
|
||||||
|
@ -655,9 +651,9 @@ namespace BizHawk.MultiClient
|
||||||
controllers.Type = new ControllerDefinition();
|
controllers.Type = new ControllerDefinition();
|
||||||
controllers.Type.Name = "NES Controller";
|
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
|
||||||
with only controller 1 data, a frame is 1 byte.
|
with only controller 1 data, a frame is 1 byte.
|
||||||
*/
|
*/
|
||||||
int player = 1;
|
int player = 1;
|
||||||
while (player <= 3)
|
while (player <= 3)
|
||||||
|
@ -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");
|
||||||
|
@ -966,23 +956,23 @@ namespace BizHawk.MultiClient
|
||||||
controllers.Type = new ControllerDefinition();
|
controllers.Type = new ControllerDefinition();
|
||||||
controllers.Type.Name = "SMS Controller";
|
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
|
||||||
however both bytes are still present.
|
however both bytes are still present.
|
||||||
*/
|
*/
|
||||||
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
|
||||||
|
*/
|
||||||
SimpleController controllers = new SimpleController();
|
string[] buttons = new string[8] { "A", "B", "Select", "Start", "Right", "Left", "Up", "Down" };
|
||||||
controllers.Type = new ControllerDefinition();
|
/*
|
||||||
controllers.Type.Name = "Gameboy Controller";
|
* 00 01 R
|
||||||
string[] buttons = new string[8] {"A", "B", "Select", "Start", "Right", "Left", "Up", "Down"};
|
* 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++)
|
for (int frame = 1; frame <= frameCount; frame++)
|
||||||
{
|
{
|
||||||
ushort controllerstate = r.ReadUInt16();
|
/*
|
||||||
// TODO: reset, GBA buttons go here
|
A stream of 2-byte bitvectors which indicate which buttons are pressed at each point in time. They will come
|
||||||
byte and = 0x1;
|
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++)
|
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();
|
||||||
|
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();
|
r.Close();
|
||||||
fs.Close();
|
fs.Close();
|
||||||
return m;
|
return m;
|
||||||
|
|
Loading…
Reference in New Issue