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/PJMImport.cs b/BizHawk.Client.Common/movie/import/PJMImport.cs index a404a8b302..c1c4f0a4cd 100644 --- a/BizHawk.Client.Common/movie/import/PJMImport.cs +++ b/BizHawk.Client.Common/movie/import/PJMImport.cs @@ -1,14 +1,10 @@ using BizHawk.Emulation.Cores.Sony.PSX; -using BizHawk.Emulation.Common; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -namespace BizHawk.Client.Common -{ - [ImportExtension(".pjm")] +using System; +using System.IO; + +namespace BizHawk.Client.Common { + [ImportExtension(".pjm")] public class PJMImport : MovieImporter { protected override void RunImport() @@ -20,38 +16,33 @@ namespace BizHawk.Client.Common using (var fs = SourceFile.OpenRead()) { using (var br = new BinaryReader(fs)) { - info = parseHeader(movie, br); + info = parseHeader(movie, "PJM ", br); fs.Seek(info.controllerDataOffset, SeekOrigin.Begin); if(info.binaryFormat) { parseBinaryInputLog(br, movie, info); - return; + } else { + parseTextInputLog(br, movie, info); } } - - if (!info.parseSuccessful) { - return; - } - - using (var sr = new StreamReader(fs)) { - parseTextInputLog(sr, movie, info); - } } + + movie.Save(); } - private MiscHeaderInfo parseHeader(Bk2Movie movie, BinaryReader br) { + protected MiscHeaderInfo parseHeader(Bk2Movie movie, string expectedMagic, BinaryReader br) { var info = new MiscHeaderInfo(); string magic = new string(br.ReadChars(4)); - if (magic != "PJM ") { - Result.Errors.Add("Not a PJM file: invalid magic number in file header."); + 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 PJM format version: got {0}, expecting 2", movieVersionNumber)); + Result.Warnings.Add(String.Format("Unexpected movie version: got {0}, expecting 2", movieVersionNumber)); } // 008: UInt32 emulator version. @@ -60,30 +51,34 @@ namespace BizHawk.Client.Common byte flags = br.ReadByte(); byte flags2 = br.ReadByte(); if ((flags & 0x02) != 0) { - Result.Errors.Add("PJM file starts from savestate; this is currently unsupported."); + Result.Errors.Add("Movie starts from savestate; this is currently unsupported."); } if ((flags & 0x04) != 0) { movie.HeaderEntries.Add(HeaderKeys.PAL, "1"); } if ((flags & 0x08) != 0) { - Result.Errors.Add("PJM file contains embedded memory cards; this is currently unsupported."); + Result.Errors.Add("Movie contains embedded memory cards; this is currently unsupported."); } if ((flags & 0x10) != 0) { - Result.Errors.Add("PJM file contains embedded cheat list; this is currently unsupported."); + Result.Errors.Add("Movie contains embedded cheat list; this is currently unsupported."); } if ((flags & 0x20) != 0 || (flags2 & 0x06) != 0) { - Result.Errors.Add("PJM file relies on emulator hacks; this is currently unsupported."); + 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("PJM file uses multitap; this is currently unsupported."); + 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.IsConnected = false; break; case 4: @@ -93,12 +88,14 @@ namespace BizHawk.Client.Common info.player1Type.Type = Octoshock.ControllerSetting.ControllerType.DualShock; break; default: - Result.Errors.Add("PJM file has unrecognised controller type for Player 1."); + 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.player2Type.IsConnected = false; break; case 4: @@ -108,30 +105,24 @@ namespace BizHawk.Client.Common info.player2Type.Type = Octoshock.ControllerSetting.ControllerType.DualShock; break; default: - Result.Errors.Add("PJM file has unrecognised controller type for Player 2."); + Result.Errors.Add("Movie has unrecognised controller type for Player 2."); return info; } info.frameCount = br.ReadUInt32(); UInt32 rerecordCount = br.ReadUInt32(); - movie.HeaderEntries.Add(HeaderKeys.RERECORDS, rerecordCount.ToString()); + movie.HeaderEntries[HeaderKeys.RERECORDS] = rerecordCount.ToString(); // 018: UInt32 savestateOffset - br.ReadUInt32(); - // 01C: UInt32 memoryCard1Offset - br.ReadUInt32(); - // 020: UInt32 memoryCard2Offset - br.ReadUInt32(); - // 024: UInt32 cheatListOffset - br.ReadUInt32(); // 028: UInt32 cdRomIdOffset // Source format is just the first up-to-8 alphanumeric characters of the CD label, // so not so useful. - br.ReadUInt32(); + + br.ReadBytes(20); info.controllerDataOffset = br.ReadUInt32(); @@ -144,7 +135,7 @@ namespace BizHawk.Client.Common return info; } - private void parseBinaryInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) { + protected void parseBinaryInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) { Octoshock.SyncSettings settings = new Octoshock.SyncSettings(); SimpleController controllers = new SimpleController(); settings.Controllers = new[] { info.player1Type, info.player2Type }; @@ -155,10 +146,15 @@ namespace BizHawk.Client.Common bool isCdTrayOpen = false; - for (int frame = 0; frame < movie.FrameCount; ++frame) { + for (int frame = 0; frame < info.frameCount; ++frame) { if (info.player1Type.IsConnected) { UInt16 controllerState = br.ReadUInt16(); - for (int button = 0; button < buttons.Length; button++) { + + // 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; @@ -166,6 +162,8 @@ namespace BizHawk.Client.Common } if(info.player1Type.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) { + 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()); @@ -216,12 +214,113 @@ namespace BizHawk.Client.Common } } - private void parseTextInputLog(TextReader tr, Bk2Movie movie, MiscHeaderInfo info) { - throw new NotImplementedException(); + protected void parseTextInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) { + Octoshock.SyncSettings settings = new Octoshock.SyncSettings(); + SimpleController controllers = new SimpleController(); + settings.Controllers = new[] { info.player1Type, info.player2Type }; + 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; + + for (int frame = 0; frame < info.frameCount; ++frame) { + if (info.player1Type.IsConnected) { + // 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.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) { + 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.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) { + // 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.IsConnected) { + // 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.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) { + 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.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) { + // 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; + } else { + controllers["Open"] = true; + } + isCdTrayOpen = !isCdTrayOpen; + } else { + controllers["Close"] = false; + controllers["Open"] = false; + } + + 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); + } } - private class MiscHeaderInfo { - public bool binaryFormat; + protected class MiscHeaderInfo { + public bool binaryFormat = true; public UInt32 controllerDataOffset; public UInt32 frameCount; public Octoshock.ControllerSetting player1Type = new Octoshock.ControllerSetting() { IsConnected = true }; diff --git a/BizHawk.Client.Common/movie/import/PXMImport.cs b/BizHawk.Client.Common/movie/import/PXMImport.cs new file mode 100644 index 0000000000..61cebf85e8 --- /dev/null +++ b/BizHawk.Client.Common/movie/import/PXMImport.cs @@ -0,0 +1,36 @@ +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.Add(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(); + } + } +}