diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj index 9817297aa8..3be66de629 100644 --- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -158,6 +158,7 @@ Bk2Movie.cs + diff --git a/BizHawk.Client.Common/movie/import/MovieImport.cs b/BizHawk.Client.Common/movie/import/MovieImport.cs index 3be588f999..94059338f6 100644 --- a/BizHawk.Client.Common/movie/import/MovieImport.cs +++ b/BizHawk.Client.Common/movie/import/MovieImport.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; +using System.Reflection; using BizHawk.Common; using BizHawk.Common.BufferExtensions; @@ -53,7 +54,7 @@ namespace BizHawk.Client.Common if (!string.IsNullOrWhiteSpace(warningMsg)) { messageCallback(warningMsg); - + } else { @@ -73,23 +74,72 @@ namespace BizHawk.Client.Common warningMsg = string.Empty; string ext = path != null ? Path.GetExtension(path).ToUpper() : string.Empty; - // TODO: reflect off the assembly and find an IMovieImporter with the appropriate ImportExtension metadata - //if (ext == ".FM2") - //{ - // var result = new Fm2Import().Import(path); - // errorMsg = result.Errors.First(); - // warningMsg = result.Errors.First(); - // return result.Movie; - //} - - if (ext == ".PJM") + if (UsesLegacyImporter(ext)) { - var result = new PJMImport().Import(path); - errorMsg = result.Errors.First(); - warningMsg = result.Errors.First(); - return result.Movie; + return LegacyImportFile(ext, path, out errorMsg, out warningMsg).ToBk2(); } + var importers = ImportersForExtension(ext); + var importerType = importers.FirstOrDefault(); + + if (importerType == default(Type)) + { + errorMsg = "No importer found for file type " + ext; + return null; + } + + // Create a new instance of the importer class using the no-argument constructor + IMovieImport importer = importerType.GetConstructor(new Type[] { }) + .Invoke(new object[] { }) as IMovieImport; + + Bk2Movie movie = null; + + try + { + var result = importer.Import(path); + if (result.Errors.Count() > 0) errorMsg = result.Errors.First(); + if (result.Warnings.Count() > 0) warningMsg = result.Warnings.First(); + movie = result.Movie; + } + catch (Exception ex) + { + errorMsg = ex.ToString(); + } + + return movie; + } + + private static IEnumerable ImportersForExtension(string ext) + { + var info = typeof(MovieImport).Module; + var importers = from t in info.GetTypes() + where typeof(IMovieImport).IsAssignableFrom(t) + && TypeImportsExtension(t, ext) + select t; + + return importers; + } + + private static bool TypeImportsExtension(Type t, string ext) + { + var attrs = (ImportExtension[])t.GetCustomAttributes(typeof(ImportExtension), inherit: false); + + if (attrs.Any(a => a.Extension.ToUpper() == ext.ToUpper())) + { + return true; + } + else + { + return false; + } + } + + private static BkmMovie LegacyImportFile(string ext, string path, out string errorMsg, out string warningMsg) + { + errorMsg = string.Empty; + warningMsg = string.Empty; + + BkmMovie m = new BkmMovie(); try @@ -149,16 +199,23 @@ namespace BizHawk.Client.Common errorMsg = except.ToString(); } - // Hack - return m.ToBk2(); + return m; } // Return whether or not the type of file provided can currently be imported. public static bool IsValidMovieExtension(string extension) + { + // TODO: Other movie formats that don't use a legacy importer (PJM/PXM, etc), + // when those are implemented + return UsesLegacyImporter(extension); + } + + // Return whether or not the type of file provided is currently imported by a legacy (i.e. to BKM not BK2) importer + public static bool UsesLegacyImporter(string extension) { string[] extensions = { - "FCM", "FM2", "FMV", "GMV", "MCM", "MC2", "MMV", "NMV", "LSMV", "SMV", "VBM", "VMV", "YMV", "ZMV" + "BKM", "FCM", "FM2", "FMV", "GMV", "MCM", "MC2", "MMV", "NMV", "LSMV", "SMV", "VBM", "VMV", "YMV", "ZMV" }; return extensions.Any(ext => extension.ToUpper() == "." + ext); } @@ -213,7 +270,7 @@ namespace BizHawk.Client.Common controller = "Saturn Controller"; break; } - var controllers = new SimpleController {Type = new ControllerDefinition {Name = controller}}; + var controllers = new SimpleController { Type = new ControllerDefinition { Name = controller } }; // Split up the sections of the frame. string[] sections = line.Split('|'); if (ext == ".FM2" && sections.Length >= 2 && sections[1].Length != 0) @@ -643,7 +700,7 @@ namespace BizHawk.Client.Common m.Header[HeaderKeys.AUTHOR] = author; // Advance to first byte of input data. r.BaseStream.Position = firstFrameOffset; - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "NES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "NES Controller" } }; string[] buttons = { "A", "B", "Select", "Start", "Up", "Down", "Left", "Right" }; bool fds = false; bool fourscore = false; @@ -832,7 +889,7 @@ namespace BizHawk.Client.Common else { FDS = false; - + } m.Header[HeaderKeys.PLATFORM] = "NES"; @@ -874,7 +931,7 @@ namespace BizHawk.Client.Common */ m.Header[HeaderKeys.PAL] = "False"; // 090 frame data begins here - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "NES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "NES Controller" } }; /* * 01 Right * 02 Left @@ -1378,7 +1435,7 @@ namespace BizHawk.Client.Common r.ReadBytes(103); // TODO: Verify if NTSC/"PAL" mode used for the movie can be detected or not. // 100 variable Input data - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = name + " Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = name + " Controller" } }; int bytes = 256; // The input stream consists of 1 byte for power-on and reset, and then X bytes per each input port per frame. if (platform == "nes") @@ -1499,7 +1556,7 @@ namespace BizHawk.Client.Common // 00e4-00f3: binary: rom MD5 digest byte[] md5 = r.ReadBytes(16); m.Header[MD5] = string.Format("{0:x8}", md5.BytesToHexString().ToLower()); - var controllers = new SimpleController { Type = new ControllerDefinition { Name = "SMS Controller" }}; + var controllers = new SimpleController { Type = new ControllerDefinition { Name = "SMS Controller" } }; /* 76543210 * bit 0 (0x01): up @@ -1724,7 +1781,7 @@ namespace BizHawk.Client.Common // ... 4-byte little-endian unsigned int: length of controller data in bytes uint length = r.ReadUInt32(); // ... (variable) controller data - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "NES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "NES Controller" } }; /* Standard controllers store data in the following format: * 01: A @@ -1827,7 +1884,7 @@ namespace BizHawk.Client.Common * bit 4: controller 5 in use * other: reserved, set to 0 */ - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "SNES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "SNES Controller" } }; bool[] controllersUsed = new bool[5]; for (int controller = 1; controller <= controllersUsed.Length; controller++) { @@ -1944,7 +2001,7 @@ namespace BizHawk.Client.Common { "Right", "Left", "Down", "Up", "Start", "Select", "Y", "B", "R", "L", "X", "A" }; - + for (int frame = 0; frame <= frameCount; frame++) { controllers["Reset"] = true; @@ -2139,16 +2196,16 @@ namespace BizHawk.Client.Common // bit 2: if "1", movie is for the SGB system bool is_sgb = (((flags >> 2) & 0x1) != 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)) //TODO: adelikat: this doesn't do what the comment above suggests it is trying to check for, it is always false! //{ - //errorMsg = "This is not a valid .VBM file."; - //r.Close(); - //fs.Close(); - //return null; + //errorMsg = "This is not a valid .VBM file."; + //r.Close(); + //fs.Close(); + //return null; //} - + // (If all 3 of these bits are "0", it is for regular GB.) string platform = "GB"; if (is_gba) @@ -2238,7 +2295,7 @@ namespace BizHawk.Client.Common string movieDescription = NullTerminated(r.ReadStringFixedAscii(128)); m.Comments.Add(COMMENT + " " + movieDescription); r.BaseStream.Position = firstFrameOffset; - SimpleController controllers = new SimpleController {Type = new ControllerDefinition()}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition() }; if (platform != "GBA") { controllers.Type.Name = "Gameboy Controller"; @@ -2268,7 +2325,7 @@ namespace BizHawk.Client.Common * 00 40 Down motion sensor * 00 80 Up motion sensor */ - string[] other = + string[] other = { "Reset (old timing)" , "Reset (new timing since version 1.1)", "Left motion sensor", "Right motion sensor", "Down motion sensor", "Up motion sensor" @@ -2416,7 +2473,7 @@ namespace BizHawk.Client.Common return m; } r.BaseStream.Position = firstFrameOffset; - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "NES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "NES Controller" } }; /* * 01 A * 02 B @@ -2653,7 +2710,7 @@ namespace BizHawk.Client.Common uint savestateSize = (uint)((r.ReadByte() | (r.ReadByte() << 8) | (r.ReadByte() << 16)) & 0x7FFFFF); // Next follows a ZST format savestate. r.ReadBytes((int)savestateSize); - SimpleController controllers = new SimpleController {Type = new ControllerDefinition {Name = "SNES Controller"}}; + SimpleController controllers = new SimpleController { Type = new ControllerDefinition { Name = "SNES Controller" } }; /* * bit 11: A * bit 10: X diff --git a/BizHawk.Client.Common/movie/import/PJMImport.cs b/BizHawk.Client.Common/movie/import/PJMImport.cs index 342a12d492..6102242f6f 100644 --- a/BizHawk.Client.Common/movie/import/PJMImport.cs +++ b/BizHawk.Client.Common/movie/import/PJMImport.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; +using BizHawk.Emulation.Cores.Sony.PSX; +using Newtonsoft.Json; +using System; using System.IO; -using System.Linq; -using System.Text; namespace BizHawk.Client.Common { @@ -11,7 +10,418 @@ namespace BizHawk.Client.Common { protected override void RunImport() { - // TODO + Bk2Movie movie = Result.Movie; + MiscHeaderInfo info; + + movie.HeaderEntries[HeaderKeys.PLATFORM] = "PSX"; + + using (var fs = SourceFile.OpenRead()) + { + using (var br = new BinaryReader(fs)) + { + info = parseHeader(movie, "PJM ", br); + + fs.Seek(info.controllerDataOffset, SeekOrigin.Begin); + + if (info.binaryFormat) + { + parseBinaryInputLog(br, movie, info); + } + else + { + parseTextInputLog(br, movie, info); + } + } + } + + movie.Save(); } + + protected MiscHeaderInfo parseHeader(Bk2Movie movie, string expectedMagic, BinaryReader br) + { + var info = new MiscHeaderInfo(); + + string magic = new string(br.ReadChars(4)); + if (magic != expectedMagic) + { + Result.Errors.Add("Not a " + expectedMagic + "file: invalid magic number in file header."); + return info; + } + + UInt32 movieVersionNumber = br.ReadUInt32(); + if (movieVersionNumber != 2) + { + Result.Warnings.Add(String.Format("Unexpected movie version: got {0}, expecting 2", movieVersionNumber)); + } + + // 008: UInt32 emulator version. + br.ReadUInt32(); + + byte flags = br.ReadByte(); + byte flags2 = br.ReadByte(); + if ((flags & 0x02) != 0) + { + Result.Errors.Add("Movie starts from savestate; this is currently unsupported."); + } + if ((flags & 0x04) != 0) + { + movie.HeaderEntries[HeaderKeys.PAL] = "1"; + } + if ((flags & 0x08) != 0) + { + Result.Errors.Add("Movie contains embedded memory cards; this is currently unsupported."); + } + if ((flags & 0x10) != 0) + { + Result.Errors.Add("Movie contains embedded cheat list; this is currently unsupported."); + } + if ((flags & 0x20) != 0 || (flags2 & 0x06) != 0) + { + Result.Errors.Add("Movie relies on emulator hacks; this is currently unsupported."); + } + if ((flags & 0x40) != 0) + { + info.binaryFormat = false; + } + if ((flags & 0x80) != 0 || (flags2 & 0x01) != 0) + { + Result.Errors.Add("Movie uses multitap; this is currently unsupported."); + return info; + } + + // Player 1 controller type + switch (br.ReadByte()) + { + // It seems to be inconsistent in the files I looked at which of these is used + // to mean no controller present. + case 0: + case 8: + info.player1Type = OctoshockDll.ePeripheralType.None; + break; + case 4: + info.player1Type = OctoshockDll.ePeripheralType.Pad; + break; + case 7: + info.player1Type = OctoshockDll.ePeripheralType.DualShock; + break; + default: + Result.Errors.Add("Movie has unrecognised controller type for Player 1."); + return info; + } + + // Player 2 controller type + switch (br.ReadByte()) + { + case 0: + case 8: + info.player1Type = OctoshockDll.ePeripheralType.None; + break; + case 4: + info.player1Type = OctoshockDll.ePeripheralType.Pad; + break; + case 7: + info.player1Type = OctoshockDll.ePeripheralType.DualShock; + break; + default: + Result.Errors.Add("Movie has unrecognised controller type for Player 2."); + return info; + } + + Octoshock.SyncSettings syncsettings = new Octoshock.SyncSettings(); + syncsettings.FIOConfig.Devices8 = + new[] { + info.player1Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None, + info.player2Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None + }; + + // Annoying kludge to force the json serializer to serialize the type name for "o" object. + // For just the "o" object to have type information, it must be cast to a superclass such + // that the TypeNameHandling.Auto decides to serialize the type as well as the object + // contents. As such, the object cast is NOT redundant + var jsonSettings = new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto + }; + movie.SyncSettingsJson = JsonConvert.SerializeObject(new { o = (object)syncsettings }, jsonSettings); + + info.frameCount = br.ReadUInt32(); + UInt32 rerecordCount = br.ReadUInt32(); + movie.HeaderEntries[HeaderKeys.RERECORDS] = rerecordCount.ToString(); + + // 018: UInt32 savestateOffset + // 01C: UInt32 memoryCard1Offset + // 020: UInt32 memoryCard2Offset + // 024: UInt32 cheatListOffset + + // 028: UInt32 cdRomIdOffset + // Source format is just the first up-to-8 alphanumeric characters of the CD label, + // so not so useful. + + br.ReadBytes(20); + + info.controllerDataOffset = br.ReadUInt32(); + + UInt32 authorNameLength = br.ReadUInt32(); + char[] authorName = br.ReadChars((int)authorNameLength); + + movie.HeaderEntries[HeaderKeys.AUTHOR] = new string(authorName); + + info.parseSuccessful = true; + return info; + } + + protected void parseBinaryInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) + { + Octoshock.SyncSettings settings = new Octoshock.SyncSettings(); + SimpleController controllers = new SimpleController(); + settings.FIOConfig.Devices8 = + new[] { + info.player1Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None, + info.player2Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None + }; + controllers.Type = Octoshock.CreateControllerDefinition(settings); + + string[] buttons = { "Select", "L3", "R3", "Start", "Up", "Right", "Down", "Left", + "L2", "R2", "L1", "R1", "Triangle", "Circle", "Cross", "Square"}; + + bool isCdTrayOpen = false; + int cdNumber = 1; + + for (int frame = 0; frame < info.frameCount; ++frame) + { + if (info.player1Type != OctoshockDll.ePeripheralType.None) + { + UInt16 controllerState = br.ReadUInt16(); + + // As L3 and R3 don't exist on a standard gamepad, handle them separately later. Unfortunately + // due to the layout, we handle select separately too first. + controllers["P1 Select"] = (controllerState & 0x1) != 0; + + for (int button = 3; button < buttons.Length; button++) + { + controllers["P1 " + buttons[button]] = (((controllerState >> button) & 0x1) != 0); + if (((controllerState >> button) & 0x1) != 0 && button > 15) + { + continue; + } + } + + if (info.player1Type == OctoshockDll.ePeripheralType.DualShock) + { + controllers["P1 L3"] = (controllerState & 0x2) != 0; + controllers["P1 R3"] = (controllerState & 0x4) != 0; + Tuple leftX = new Tuple("P1 LStick X", (float)br.ReadByte()); + Tuple leftY = new Tuple("P1 LStick Y", (float)br.ReadByte()); + Tuple rightX = new Tuple("P1 RStick X", (float)br.ReadByte()); + Tuple rightY = new Tuple("P1 RStick Y", (float)br.ReadByte()); + + controllers.AcceptNewFloats(new[] { leftX, leftY, rightX, rightY }); + } + } + + if (info.player2Type != OctoshockDll.ePeripheralType.None) + { + UInt16 controllerState = br.ReadUInt16(); + for (int button = 0; button < buttons.Length; button++) + { + controllers["P2 " + buttons[button]] = (((controllerState >> button) & 0x1) != 0); + if (((controllerState >> button) & 0x1) != 0 && button > 15) + { + continue; + } + } + + if (info.player2Type == OctoshockDll.ePeripheralType.DualShock) + { + Tuple leftX = new Tuple("P2 LStick X", (float)br.ReadByte()); + Tuple leftY = new Tuple("P2 LStick Y", (float)br.ReadByte()); + Tuple rightX = new Tuple("P2 RStick X", (float)br.ReadByte()); + Tuple rightY = new Tuple("P2 RStick Y", (float)br.ReadByte()); + + controllers.AcceptNewFloats(new[] { leftX, leftY, rightX, rightY }); + } + } + + byte controlState = br.ReadByte(); + controllers["Reset"] = (controlState & 0x02) != 0; + if ((controlState & 0x04) != 0) + { + if (isCdTrayOpen) + { + controllers["Close"] = true; + cdNumber++; + } + else + { + controllers["Open"] = true; + } + isCdTrayOpen = !isCdTrayOpen; + } + else + { + controllers["Close"] = false; + controllers["Open"] = false; + } + + Tuple discSelect = new Tuple("Disc Select", cdNumber); + controllers.AcceptNewFloats(new[] { discSelect }); + + if ((controlState & 0xFC) != 0) + { + Result.Warnings.Add("Ignored toggle hack flag on frame " + frame.ToString()); + } + + movie.AppendFrame(controllers); + } + } + + protected void parseTextInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) + { + Octoshock.SyncSettings settings = new Octoshock.SyncSettings(); + SimpleController controllers = new SimpleController(); + settings.FIOConfig.Devices8 = + new[] { + info.player1Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None, + info.player2Type, + OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None,OctoshockDll.ePeripheralType.None + }; + controllers.Type = Octoshock.CreateControllerDefinition(settings); + + string[] buttons = { "Select", "L3", "R3", "Start", "Up", "Right", "Down", "Left", + "L2", "R2", "L1", "R1", "Triangle", "Circle", "Cross", "Square"}; + + bool isCdTrayOpen = false; + int cdNumber = 1; + + for (int frame = 0; frame < info.frameCount; ++frame) + { + if (info.player1Type != OctoshockDll.ePeripheralType.None) + { + // As L3 and R3 don't exist on a standard gamepad, handle them separately later. Unfortunately + // due to the layout, we handle select separately too first. + controllers["P1 Select"] = br.ReadChar() != '.'; + + if (info.player1Type == OctoshockDll.ePeripheralType.DualShock) + { + controllers["P1 L3"] = br.ReadChar() != '.'; + controllers["P1 R3"] = br.ReadChar() != '.'; + } + + for (int button = 3; button < buttons.Length; button++) + { + controllers["P1 " + buttons[button]] = br.ReadChar() != '.'; + } + + if (info.player1Type == OctoshockDll.ePeripheralType.DualShock) + { + // The analog controls are encoded as four space-separated numbers with a leading space + string leftXRaw = new string(br.ReadChars(4)).Trim(); + string leftYRaw = new string(br.ReadChars(4)).Trim(); + string rightXRaw = new string(br.ReadChars(4)).Trim(); + string rightYRaw = new string(br.ReadChars(4)).Trim(); + + + Tuple leftX = new Tuple("P1 LStick X", float.Parse(leftXRaw)); + Tuple leftY = new Tuple("P1 LStick Y", float.Parse(leftYRaw)); + Tuple rightX = new Tuple("P1 RStick X", float.Parse(rightXRaw)); + Tuple rightY = new Tuple("P1 RStick Y", float.Parse(rightYRaw)); + + controllers.AcceptNewFloats(new[] { leftX, leftY, rightX, rightY }); + } + } + + // Each controller is terminated with a pipeline. + br.ReadChar(); + + if (info.player2Type != OctoshockDll.ePeripheralType.None) + { + // As L3 and R3 don't exist on a standard gamepad, handle them separately later. Unfortunately + // due to the layout, we handle select separately too first. + controllers["P2 Select"] = br.ReadChar() != '.'; + + if (info.player2Type == OctoshockDll.ePeripheralType.DualShock) + { + controllers["P2 L3"] = br.ReadChar() != '.'; + controllers["P2 R3"] = br.ReadChar() != '.'; + } + + for (int button = 3; button < buttons.Length; button++) + { + controllers["P2 " + buttons[button]] = br.ReadChar() != '.'; + } + + if (info.player2Type == OctoshockDll.ePeripheralType.DualShock) + { + // The analog controls are encoded as four space-separated numbers with a leading space + string leftXRaw = new string(br.ReadChars(4)).Trim(); + string leftYRaw = new string(br.ReadChars(4)).Trim(); + string rightXRaw = new string(br.ReadChars(4)).Trim(); + string rightYRaw = new string(br.ReadChars(4)).Trim(); + + + Tuple leftX = new Tuple("P2 LStick X", float.Parse(leftXRaw)); + Tuple leftY = new Tuple("P2 LStick Y", float.Parse(leftYRaw)); + Tuple rightX = new Tuple("P2 RStick X", float.Parse(rightXRaw)); + Tuple rightY = new Tuple("P2 RStick Y", float.Parse(rightYRaw)); + + controllers.AcceptNewFloats(new[] { leftX, leftY, rightX, rightY }); + } + } + + // Each controller is terminated with a pipeline. + br.ReadChar(); + + byte controlState = br.ReadByte(); + controllers["Reset"] = (controlState & 0x02) != 0; + if ((controlState & 0x04) != 0) + { + if (isCdTrayOpen) + { + controllers["Close"] = true; + cdNumber++; + } + else + { + controllers["Open"] = true; + } + isCdTrayOpen = !isCdTrayOpen; + } + else + { + controllers["Close"] = false; + controllers["Open"] = false; + } + + Tuple discSelect = new Tuple("Disc Select", cdNumber); + controllers.AcceptNewFloats(new[] { discSelect }); + + if ((controlState & 0xFC) != 0) + { + Result.Warnings.Add("Ignored toggle hack flag on frame " + frame.ToString()); + } + + // Each controller is terminated with a pipeline. + br.ReadChar(); + + movie.AppendFrame(controllers); + } + } + + protected class MiscHeaderInfo + { + public bool binaryFormat = true; + public UInt32 controllerDataOffset; + public UInt32 frameCount; + public OctoshockDll.ePeripheralType player1Type; + public OctoshockDll.ePeripheralType player2Type; + + public bool parseSuccessful = false; + } + } } diff --git a/BizHawk.Client.Common/movie/import/PXMImport.cs b/BizHawk.Client.Common/movie/import/PXMImport.cs new file mode 100644 index 0000000000..a4ef2ebdd3 --- /dev/null +++ b/BizHawk.Client.Common/movie/import/PXMImport.cs @@ -0,0 +1,44 @@ +using System.IO; + +namespace BizHawk.Client.Common.movie.import +{ + + // PXM files are directly compatible with binary-format PJM files, with the only + // difference being fewer flags implemented in the header, hence just calling the + // base class methods via a subclass. + // + // However, the magic number/file signature is slightly different, requiring some + // refactoring to avoid PXM-specific code in the PJMImport class. + [ImportExtension(".pxm")] + class PXMImport : PJMImport + { + protected override void RunImport() + { + Bk2Movie movie = Result.Movie; + MiscHeaderInfo info; + + movie.HeaderEntries[HeaderKeys.PLATFORM] = "PSX"; + + using (var fs = SourceFile.OpenRead()) + { + using (var br = new BinaryReader(fs)) + { + info = parseHeader(movie, "PXM ", br); + + fs.Seek(info.controllerDataOffset, SeekOrigin.Begin); + + if (info.binaryFormat) + { + parseBinaryInputLog(br, movie, info); + } + else + { + parseTextInputLog(br, movie, info); + } + } + } + + movie.Save(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 39adb03adf..6a3d32371c 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -455,7 +455,7 @@ namespace BizHawk.Client.EmuHawk InitialDirectory = PathManager.GetRomsPath(Global.Emulator.SystemId), Multiselect = true, Filter = FormatFilter( - "Movie Files", "*.fm2;*.mc2;*.mcm;*.mmv;*.gmv;*.vbm;*.lsmv;*.fcm;*.fmv;*.vmv;*.nmv;*.smv;*.ymv;*.zmv;*.bkm", + "Movie Files", "*.fm2;*.mc2;*.mcm;*.mmv;*.gmv;*.vbm;*.lsmv;*.fcm;*.fmv;*.vmv;*.nmv;*.smv;*.ymv;*.zmv;*.bkm;*.pjm;*.pxm", "FCEUX", "*.fm2", "PCEjin/Mednafen", "*.mc2;*.mcm", "Dega", "*.mmv", @@ -469,6 +469,8 @@ namespace BizHawk.Client.EmuHawk "Snes9x", "*.smv", "Yabause", "*.ymv", "ZSNES", "*.zmv", + "PSXjin", "*.pjm", + "PCSX", "*.pxm", "BizHawk Bkm", "*.bkm", "All Files", "*.*"), RestoreDirectory = false diff --git a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs index ca16d2ce99..e36fc0eadc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs @@ -36,21 +36,18 @@ namespace BizHawk.Emulation.Cores.Sony.PSX { public string SystemId { get { return "PSX"; } } - private void SetControllerButtons() + public static ControllerDefinition CreateControllerDefinition(SyncSettings syncSettings) { - ControllerDefinition = new ControllerDefinition(); - ControllerDefinition.Name = "PSX DualShock Controller"; // <-- for compatibility + ControllerDefinition definition = new ControllerDefinition(); + definition.Name = "PSX DualShock Controller"; // <-- for compatibility //ControllerDefinition.Name = "PSX FrontIO"; // TODO - later rename to this, I guess, so it's less misleading. don't want to wreck keybindings yet. - ControllerDefinition.BoolButtons.Clear(); - ControllerDefinition.FloatControls.Clear(); - - var cfg = _SyncSettings.FIOConfig.ToLogical(); + var cfg = syncSettings.FIOConfig.ToLogical(); for (int i = 0; i < cfg.NumPlayers; i++) { int pnum = i + 1; - ControllerDefinition.BoolButtons.AddRange(new[] + definition.BoolButtons.AddRange(new[] { "P" + pnum + " Up", "P" + pnum + " Down", @@ -72,11 +69,11 @@ namespace BizHawk.Emulation.Cores.Sony.PSX if (type == OctoshockDll.ePeripheralType.DualShock || type == OctoshockDll.ePeripheralType.DualAnalog) { - ControllerDefinition.BoolButtons.Add("P" + pnum + " L3"); - ControllerDefinition.BoolButtons.Add("P" + pnum + " R3"); - ControllerDefinition.BoolButtons.Add("P" + pnum + " MODE"); + definition.BoolButtons.Add("P" + pnum + " L3"); + definition.BoolButtons.Add("P" + pnum + " R3"); + definition.BoolButtons.Add("P" + pnum + " MODE"); - ControllerDefinition.FloatControls.AddRange(new[] + definition.FloatControls.AddRange(new[] { "P" + pnum + " LStick X", "P" + pnum + " LStick Y", @@ -84,27 +81,34 @@ namespace BizHawk.Emulation.Cores.Sony.PSX "P" + pnum + " RStick Y" }); - ControllerDefinition.FloatRanges.Add(new[] { 0.0f, 128.0f, 255.0f }); - ControllerDefinition.FloatRanges.Add(new[] { 255.0f, 128.0f, 0.0f }); - ControllerDefinition.FloatRanges.Add(new[] { 0.0f, 128.0f, 255.0f }); - ControllerDefinition.FloatRanges.Add(new[] { 255.0f, 128.0f, 0.0f }); + definition.FloatRanges.Add(new[] { 0.0f, 128.0f, 255.0f }); + definition.FloatRanges.Add(new[] { 255.0f, 128.0f, 0.0f }); + definition.FloatRanges.Add(new[] { 0.0f, 128.0f, 255.0f }); + definition.FloatRanges.Add(new[] { 255.0f, 128.0f, 0.0f }); } } - ControllerDefinition.BoolButtons.AddRange(new[] + definition.BoolButtons.AddRange(new[] { "Open", "Close", "Reset" }); - ControllerDefinition.FloatControls.Add("Disc Select"); + definition.FloatControls.Add("Disc Select"); - ControllerDefinition.FloatRanges.Add( + definition.FloatRanges.Add( //new[] {-1f,-1f,-1f} //this is carefully chosen so that we end up with a -1 disc by default (indicating that it's never been set) //hmm.. I don't see why this wouldn't work new[] { 0f, 1f, 1f } ); + + return definition; + } + + private void SetControllerButtons() + { + ControllerDefinition = CreateControllerDefinition(_SyncSettings); } public string BoardName { get { return null; } }