2015-09-21 21:33:29 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Sony.PSX;
|
2015-09-21 23:47:03 +00:00
|
|
|
|
|
2015-09-21 21:33:29 +00:00
|
|
|
|
using System;
|
2015-02-03 00:24:38 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
namespace BizHawk.Client.Common {
|
|
|
|
|
[ImportExtension(".pjm")]
|
2015-02-05 02:19:46 +00:00
|
|
|
|
public class PJMImport : MovieImporter
|
2015-02-03 00:24:38 +00:00
|
|
|
|
{
|
2015-09-21 21:33:29 +00:00
|
|
|
|
protected override void RunImport()
|
2015-02-03 00:24:38 +00:00
|
|
|
|
{
|
2015-09-21 21:33:29 +00:00
|
|
|
|
Bk2Movie movie = Result.Movie;
|
|
|
|
|
MiscHeaderInfo info;
|
|
|
|
|
|
|
|
|
|
movie.HeaderEntries.Add(HeaderKeys.PLATFORM, "PSX");
|
|
|
|
|
|
|
|
|
|
using (var fs = SourceFile.OpenRead()) {
|
|
|
|
|
using (var br = new BinaryReader(fs)) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
info = parseHeader(movie, "PJM ", br);
|
2015-09-21 21:33:29 +00:00
|
|
|
|
|
|
|
|
|
fs.Seek(info.controllerDataOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
if(info.binaryFormat) {
|
|
|
|
|
parseBinaryInputLog(br, movie, info);
|
2015-09-21 23:47:03 +00:00
|
|
|
|
} else {
|
|
|
|
|
parseTextInputLog(br, movie, info);
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-21 23:47:03 +00:00
|
|
|
|
|
|
|
|
|
movie.Save();
|
2015-02-03 00:24:38 +00:00
|
|
|
|
}
|
2015-09-21 21:33:29 +00:00
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
protected MiscHeaderInfo parseHeader(Bk2Movie movie, string expectedMagic, BinaryReader br) {
|
2015-09-21 21:33:29 +00:00
|
|
|
|
var info = new MiscHeaderInfo();
|
|
|
|
|
|
|
|
|
|
string magic = new string(br.ReadChars(4));
|
2015-09-21 23:47:03 +00:00
|
|
|
|
if (magic != expectedMagic) {
|
|
|
|
|
Result.Errors.Add("Not a " + expectedMagic + "file: invalid magic number in file header.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt32 movieVersionNumber = br.ReadUInt32();
|
|
|
|
|
if (movieVersionNumber != 2) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Warnings.Add(String.Format("Unexpected movie version: got {0}, expecting 2", movieVersionNumber));
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 008: UInt32 emulator version.
|
|
|
|
|
br.ReadUInt32();
|
|
|
|
|
|
|
|
|
|
byte flags = br.ReadByte();
|
|
|
|
|
byte flags2 = br.ReadByte();
|
|
|
|
|
if ((flags & 0x02) != 0) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie starts from savestate; this is currently unsupported.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x04) != 0) {
|
|
|
|
|
movie.HeaderEntries.Add(HeaderKeys.PAL, "1");
|
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x08) != 0) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie contains embedded memory cards; this is currently unsupported.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x10) != 0) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie contains embedded cheat list; this is currently unsupported.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x20) != 0 || (flags2 & 0x06) != 0) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie relies on emulator hacks; this is currently unsupported.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x40) != 0) {
|
|
|
|
|
info.binaryFormat = false;
|
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x80) != 0 || (flags2 & 0x01) != 0) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie uses multitap; this is currently unsupported.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
// Player 1 controller type
|
2015-09-21 21:33:29 +00:00
|
|
|
|
switch (br.ReadByte()) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
// It seems to be inconsistent in the files I looked at which of these is used
|
|
|
|
|
// to mean no controller present.
|
2015-09-21 21:33:29 +00:00
|
|
|
|
case 0:
|
2015-09-21 23:47:03 +00:00
|
|
|
|
case 8:
|
2015-09-21 21:33:29 +00:00
|
|
|
|
info.player1Type.IsConnected = false;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
info.player1Type.Type = Octoshock.ControllerSetting.ControllerType.Gamepad;
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
info.player1Type.Type = Octoshock.ControllerSetting.ControllerType.DualShock;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie has unrecognised controller type for Player 1.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
// Player 2 controller type
|
2015-09-21 21:33:29 +00:00
|
|
|
|
switch (br.ReadByte()) {
|
|
|
|
|
case 0:
|
2015-09-21 23:47:03 +00:00
|
|
|
|
case 8:
|
2015-09-21 21:33:29 +00:00
|
|
|
|
info.player2Type.IsConnected = false;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
info.player2Type.Type = Octoshock.ControllerSetting.ControllerType.Gamepad;
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
info.player2Type.Type = Octoshock.ControllerSetting.ControllerType.DualShock;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2015-09-21 23:47:03 +00:00
|
|
|
|
Result.Errors.Add("Movie has unrecognised controller type for Player 2.");
|
2015-09-21 21:33:29 +00:00
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info.frameCount = br.ReadUInt32();
|
|
|
|
|
UInt32 rerecordCount = br.ReadUInt32();
|
2015-09-21 23:47:03 +00:00
|
|
|
|
movie.HeaderEntries[HeaderKeys.RERECORDS] = rerecordCount.ToString();
|
2015-09-21 21:33:29 +00:00
|
|
|
|
|
|
|
|
|
// 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.
|
2015-09-21 23:47:03 +00:00
|
|
|
|
|
|
|
|
|
br.ReadBytes(20);
|
2015-09-21 21:33:29 +00:00
|
|
|
|
|
|
|
|
|
info.controllerDataOffset = br.ReadUInt32();
|
|
|
|
|
|
|
|
|
|
UInt32 authorNameLength = br.ReadUInt32();
|
|
|
|
|
char[] authorName = br.ReadChars((int)authorNameLength);
|
|
|
|
|
|
|
|
|
|
movie.HeaderEntries.Add(HeaderKeys.AUTHOR, new string(authorName));
|
|
|
|
|
|
|
|
|
|
info.parseSuccessful = true;
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
protected void parseBinaryInputLog(BinaryReader br, Bk2Movie movie, MiscHeaderInfo info) {
|
2015-09-21 21:33:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
for (int frame = 0; frame < info.frameCount; ++frame) {
|
2015-09-21 21:33:29 +00:00
|
|
|
|
if (info.player1Type.IsConnected) {
|
|
|
|
|
UInt16 controllerState = br.ReadUInt16();
|
2015-09-21 23:47:03 +00:00
|
|
|
|
|
|
|
|
|
// 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++) {
|
2015-09-21 21:33:29 +00:00
|
|
|
|
controllers["P1 " + buttons[button]] = (((controllerState >> button) & 0x1) != 0);
|
|
|
|
|
if (((controllerState >> button) & 0x1) != 0 && button > 15) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(info.player1Type.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) {
|
2015-09-21 23:47:03 +00:00
|
|
|
|
controllers["P1 L3"] = (controllerState & 0x2) != 0;
|
|
|
|
|
controllers["P1 R3"] = (controllerState & 0x4) != 0;
|
2015-09-21 21:33:29 +00:00
|
|
|
|
Tuple<string, float> leftX = new Tuple<string, float>("P1 LStick X", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> leftY = new Tuple<string, float>("P1 LStick Y", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> rightX = new Tuple<string, float>("P1 RStick X", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> rightY = new Tuple<string, float>("P1 RStick Y", (float)br.ReadByte());
|
|
|
|
|
|
|
|
|
|
controllers.AcceptNewFloats(new[] { leftX, leftY, rightX, rightY });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.player2Type.IsConnected) {
|
|
|
|
|
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.Type != Octoshock.ControllerSetting.ControllerType.Gamepad) {
|
|
|
|
|
Tuple<string, float> leftX = new Tuple<string, float>("P2 LStick X", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> leftY = new Tuple<string, float>("P2 LStick Y", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> rightX = new Tuple<string, float>("P2 RStick X", (float)br.ReadByte());
|
|
|
|
|
Tuple<string, float> rightY = new Tuple<string, float>("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;
|
|
|
|
|
} 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());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
movie.AppendFrame(controllers);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
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<string, float> leftX = new Tuple<string, float>("P1 LStick X", float.Parse(leftXRaw));
|
|
|
|
|
Tuple<string, float> leftY = new Tuple<string, float>("P1 LStick Y", float.Parse(leftYRaw));
|
|
|
|
|
Tuple<string, float> rightX = new Tuple<string, float>("P1 RStick X", float.Parse(rightXRaw));
|
|
|
|
|
Tuple<string, float> rightY = new Tuple<string, float>("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<string, float> leftX = new Tuple<string, float>("P2 LStick X", float.Parse(leftXRaw));
|
|
|
|
|
Tuple<string, float> leftY = new Tuple<string, float>("P2 LStick Y", float.Parse(leftYRaw));
|
|
|
|
|
Tuple<string, float> rightX = new Tuple<string, float>("P2 RStick X", float.Parse(rightXRaw));
|
|
|
|
|
Tuple<string, float> rightY = new Tuple<string, float>("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);
|
|
|
|
|
}
|
2015-09-21 21:33:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 23:47:03 +00:00
|
|
|
|
protected class MiscHeaderInfo {
|
|
|
|
|
public bool binaryFormat = true;
|
2015-09-21 21:33:29 +00:00
|
|
|
|
public UInt32 controllerDataOffset;
|
|
|
|
|
public UInt32 frameCount;
|
|
|
|
|
public Octoshock.ControllerSetting player1Type = new Octoshock.ControllerSetting() { IsConnected = true };
|
|
|
|
|
public Octoshock.ControllerSetting player2Type = new Octoshock.ControllerSetting() { IsConnected = true };
|
|
|
|
|
|
|
|
|
|
public bool parseSuccessful = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2015-02-03 00:24:38 +00:00
|
|
|
|
}
|