using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; #pragma warning disable 219 namespace BizHawk.MultiClient { public static class MovieConvert { public static Movie ConvertFCM(string path, out string errorMsg) { errorMsg = ""; try { Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); BinaryReader r = new BinaryReader(fs); byte[] signatureBytes = new byte[4]; for (int x = 0; x < 4; x++) signatureBytes[x] = r.ReadByte(); string signature = System.Text.Encoding.UTF8.GetString(signatureBytes); if (signature.Substring(0, 3) != "FCM") { errorMsg = "This is not a valid FCM file!"; return null; } UInt32 version = r.ReadUInt32(); m.Header.SetHeaderLine(MovieHeader.MovieVersion, "FCEU movie version " + version.ToString() + " (.fcm)"); byte[] flags = new byte[4]; for (int x = 0; x < 4; x++) flags[x] = r.ReadByte(); UInt32 frameCount = r.ReadUInt32(); m.SetRerecords((int)r.ReadUInt32()); UInt32 movieDataSize = r.ReadUInt32(); UInt32 savestateOffset = r.ReadUInt32(); UInt32 firstFrameOffset = r.ReadUInt32(); byte[] romCheckSum = r.ReadBytes(16); //TODO: ROM checksum movie header line (MD5) UInt32 EmuVersion = r.ReadUInt32(); m.Header.SetHeaderLine(MovieHeader.EMULATIONVERSION, "FCEU " + EmuVersion.ToString()); List romBytes = new List(); while (true) { if (r.PeekChar() == 0) break; else romBytes.Add(r.ReadByte()); } string rom = System.Text.Encoding.UTF8.GetString(romBytes.ToArray()); m.Header.SetHeaderLine(MovieHeader.GAMENAME, rom); r.ReadByte(); //Advance past null byte List authorBytes = new List(); while (true) { if (r.PeekChar() == 0) break; else authorBytes.Add(r.ReadByte()); } string author = System.Text.Encoding.UTF8.GetString(authorBytes.ToArray()); m.Header.SetHeaderLine(MovieHeader.AUTHOR, author); r.ReadByte(); //Advance past null byte bool movieSyncHackOn = true; if ((int)(flags[0] & 16) > 0) movieSyncHackOn = false; bool pal = false; if ((int)(flags[0] & 4) > 0) pal = true; m.Header.SetHeaderLine("SyncHack", movieSyncHackOn.ToString()); m.Header.SetHeaderLine("PAL", pal.ToString()); //Power on vs reset if ((int)(flags[0] & 8) > 0) { } //Power-on = default else if ((int)(flags[0] & 2) > 0) { } //we don't support start from reset, do some kind of notification here else { } //this movie starts from savestate, freak out here //Advance to first byte of input data //byte[] throwaway = new byte[firstFrameOffset]; //r.Read(throwaway, 0, (int)firstFrameOffset); r.BaseStream.Position = firstFrameOffset; //moviedatasize stuff //read frame data //TODO: special commands like fds disk switch, etc, and power/reset //TODO: use stringbuilder class for speed //string ButtonLookup = "RLDUSsBARLDUSsBARLDUSsBARLDUSsBA"; //TODO: This assumes input data is the same in fcm as bizhawk, which it isn't string frame = "|0|"; //TODO: read reset command rather than hard code it off for (int x = 0; x < frameCount; x++) { byte joy = r.ReadByte(); //Read each byte of controller one data frame += "|"; r.ReadBytes(3); //Lose remaining controllers for now m.AppendFrame(frame); } //set 4 score flag if necessary r.Close(); return m; } catch { errorMsg = "Error opening file."; return null; } } public static Movie ConvertMMV(string path, out string errorMsg) { errorMsg = ""; Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); BinaryReader r = new BinaryReader(fs); byte[] signatureBytes = new byte[4]; for (int x = 0; x < 4; x++) signatureBytes[x] = r.ReadByte(); string signature = System.Text.Encoding.UTF8.GetString(signatureBytes); if (signature != "MMV\0") { errorMsg = "This is not a valid MMV file."; return null; } UInt32 version = r.ReadUInt32(); m.Header.SetHeaderLine(MovieHeader.MOVIEVERSION, "Dega version " + version.ToString()); UInt32 framecount = r.ReadUInt32(); m.SetRerecords((int)r.ReadUInt32()); UInt32 IsFromReset = r.ReadUInt32(); if (IsFromReset == 0) { errorMsg = "Movies that begin with a savestate are not supported."; return null; } UInt32 stateOffset = r.ReadUInt32(); UInt32 inputDataOffset = r.ReadUInt32(); UInt32 inputPacketSize = r.ReadUInt32(); byte[] authorBytes = new byte[64]; for (int x = 0; x < 64; x++) authorBytes[x] = r.ReadByte(); string author = System.Text.Encoding.UTF8.GetString(authorBytes); //TODO: remove null characters m.Header.SetHeaderLine(MovieHeader.AUTHOR, author); //4-byte little endian flags byte flags = r.ReadByte(); bool pal; if ((int)(flags & 2) > 0) pal = true; else pal = false; m.Header.SetHeaderLine("PAL", pal.ToString()); bool japan; if ((int)(flags & 4) > 0) japan = true; else japan = false; m.Header.SetHeaderLine("Japan", japan.ToString()); bool gamegear; if ((int)(flags & 8) > 0) { gamegear = true; m.Header.SetHeaderLine(MovieHeader.PLATFORM, "GG"); } else { gamegear = false; m.Header.SetHeaderLine(MovieHeader.PLATFORM, "SMS"); } r.ReadBytes(3); //Unused flags byte[] romnameBytes = new byte[128]; for (int x = 0; x < 128; x++) romnameBytes[x] = r.ReadByte(); string romname = System.Text.Encoding.UTF8.GetString(romnameBytes.ToArray()); //TODO: remove null characters m.Header.SetHeaderLine(MovieHeader.GAMENAME, romname); byte[] MD5Bytes = new byte[16]; for (int x = 0; x < 16; x++) MD5Bytes[x] = r.ReadByte(); string MD5 = System.Text.Encoding.UTF8.GetString(MD5Bytes.ToArray()); //TODO: format correctly m.Header.SetHeaderLine("MD5", MD5); for (int x = 0; x < (framecount); x++) { //TODO: use StringBuilder string frame = "|"; char start; byte tmp; tmp = r.ReadByte(); if ((int)(tmp & 1) > 0) frame += "U"; else frame += "."; if ((int)(tmp & 2) > 0) frame += "D"; else frame += "."; if ((int)(tmp & 4) > 0) frame += "L"; else frame += "."; if ((int)(tmp & 8) > 0) frame += "R"; else frame += "."; if ((int)(tmp & 16) > 0) frame += "1"; else frame += "."; if ((int)(tmp & 32) > 0) frame += "2|"; else frame += ".|"; if ((int)(tmp & 64) > 0 && (!gamegear)) start = 'P'; else start = '.'; if ((int)(tmp & 128) > 0 && gamegear) start = 'P'; else start = '.'; //Controller 2 tmp = r.ReadByte(); if ((int)(tmp & 1) > 0) frame += "U"; else frame += "."; if ((int)(tmp & 2) > 0) frame += "D"; else frame += "."; if ((int)(tmp & 4) > 0) frame += "L"; else frame += "."; if ((int)(tmp & 8) > 0) frame += "R"; else frame += "."; if ((int)(tmp & 16) > 0) frame += "1"; else frame += "."; if ((int)(tmp & 32) > 0) frame += "2|"; else frame += ".|"; frame += start; frame += ".|"; m.AppendFrame(frame); } m.WriteMovie(); return m; } public static string ConvertMCM(string path) { string converted = Path.ChangeExtension(path, ".tas"); return converted; } public static Movie ConvertSMV(string path, out string errorMSG) { errorMSG = ""; FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); BinaryReader r = new BinaryReader(fs); byte[] signatureBytes = new byte[4]; for (int x = 0; x < 4; x++) signatureBytes[x] = r.ReadByte(); string signature = System.Text.Encoding.UTF8.GetString(signatureBytes); if (signature.Substring(0, 3) != "SMV") { errorMSG = "This is not a valid SMV file."; return null; } UInt32 version = r.ReadUInt32(); switch (version) { case 1: return ConvertSMV143(r, path); case 4: return ConvertSMV151(r, path); case 5: return ConvertSMV152(r, path); default: { errorMSG = "SMV version not recognized, 143, 151, and 152 are currently supported"; return null; } } } private static Movie ConvertSMV152(BinaryReader r, string path) { Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); UInt32 GUID = r.ReadUInt32(); return m; } private static Movie ConvertSMV151(BinaryReader r, string path) { Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); return m; } private static Movie ConvertSMV143(BinaryReader r, string path) { Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); UInt32 GUID = r.ReadUInt32(); m.Header.SetHeaderLine(MovieHeader.GUID, GUID.ToString()); //TODO: format to hex string m.SetRerecords((int)r.ReadUInt32()); UInt32 framecount = r.ReadUInt32(); byte ControllerFlags = r.ReadByte(); int numControllers; if ((int)(ControllerFlags & 16) > 0) numControllers = 5; else if ((int)(ControllerFlags & 8) > 0) numControllers = 4; else if ((int)(ControllerFlags & 4) > 0) numControllers = 3; else if ((int)(ControllerFlags & 2) > 0) numControllers = 2; else numControllers = 1; byte MovieFlags = r.ReadByte(); if ((int)(MovieFlags & 1) == 0) return null; //TODO: Savestate movies not supported error if ((int)(MovieFlags & 2) > 0) { m.Header.SetHeaderLine("PAL", "True"); } byte SyncOptions = r.ReadByte(); byte SyncOptions2 = r.ReadByte(); //TODO: these UInt32 SavestateOffset = r.ReadUInt32(); UInt32 FrameDataOffset = r.ReadUInt32(); //TODO: get extra rom info r.BaseStream.Position = FrameDataOffset; for (int x = 0; x < framecount; x++) { //TODO: FF FF for all controllers = Reset //string frame = "|0|"; for (int y = 0; y < numControllers; y++) { UInt16 fd = r.ReadUInt16(); } } return m; } public static Movie ConvertGMV(string path, out string errorMsg) { errorMsg = ""; Movie m = new Movie(Path.ChangeExtension(path, ".tas"), MOVIEMODE.PLAY); return m; } public static Movie ConvertVBM(string path, out string errorMsg) { errorMsg = ""; //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 UInt32 signature = r.ReadUInt32(); //always 56 42 4D 1A (VBM\x1A) if (signature != 0x56424D1A) { errorMsg = "This is not a valid VBM file."; return null; } UInt32 versionno = r.ReadUInt32(); //always 1 UInt32 uid = r.ReadUInt32(); //time of recording m.Header.SetHeaderLine(MovieHeader.GUID, uid.ToString()); UInt32 framecount = r.ReadUInt32(); //0x10 UInt32 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) { errorMsg = "Movies that begin with a savestate are not supported."; 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) { errorMsg = "Not a valid VBM platform type."; 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) { errorMsg = "Invalid VBM file"; return null; } if ((flags & 0x04) > 0) rtcenable = true; if ((flags & 0x02) > 0) skipbiosfile = true; if ((flags & 0x01) > 0) usebiosfile = true; //0x18 UInt32 winsavetype = r.ReadUInt32(); UInt32 winflashsize = r.ReadUInt32(); //0x20 UInt32 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(); UInt16 internalchacksum = r.ReadUInt16(); UInt32 unitcode = r.ReadUInt32(); UInt32 saveoffset = r.ReadUInt32(); //set to 0 if unused UInt32 controllerdataoffset = r.ReadUInt32(); //0x40 start info. char[] authorsname = r.ReadChars(0x40); //vbm specification states these strings string author = new String(authorsname); //are locale dependant. 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. UInt32 framesleft = framecount; r.BaseStream.Position = controllerdataoffset; //advances to controller data. int currentoffset = (int)controllerdataoffset; for (int i = 1; i <= framecount; i++) { UInt16 controllerstate = r.ReadUInt16(); string frame = "|.|"; //TODO: reset goes here if ((controllerstate & 0x0010) > 0) frame += "R"; else frame += "."; if ((controllerstate & 0x0020) > 0) frame += "L"; else frame += "."; if ((controllerstate & 0x0080) > 0) frame += "D"; else frame += "."; if ((controllerstate & 0x0040) > 0) frame += "U"; else frame += "."; if ((controllerstate & 0x0008) > 0) frame += "S"; else frame += "."; if ((controllerstate & 0x0004) > 0) frame += "s"; else frame += "."; if ((controllerstate & 0x0002) > 0) frame += "B"; else frame += "."; if ((controllerstate & 0x0001) > 0) frame += "A"; else frame += "."; frame += "|"; m.AppendFrame(frame); } m.WriteMovie(); //format: |.|RLDUSsBA| according to "GetControllersAsMnemonic()" //note: this is GBC or less ONLY, not GBA (no L or R button) //we need to change this when we add reset or whatever. //VBM file format: http://code.google.com/p/vba-rerecording/wiki/VBM return m; } } }