BizHawk/BizHawk.Client.Common/movie/import/GmvImport.cs

170 lines
5.0 KiB
C#

using System.IO;
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
namespace BizHawk.Client.Common.movie.import
{
// GMV file format: http://code.google.com/p/gens-rerecording/wiki/GMV
// ReSharper disable once UnusedMember.Global
[ImporterFor("GENS", ".gmv")]
internal class GmvImport : MovieImporter
{
protected override void RunImport()
{
using var fs = SourceFile.Open(FileMode.Open, FileAccess.Read);
using var r = new BinaryReader(fs);
// 000 16-byte signature and format version: "Gens Movie TEST9"
string signature = new string(r.ReadChars(15));
if (signature != "Gens Movie TEST")
{
Result.Errors.Add("This is not a valid .GMV file.");
return;
}
Result.Movie.HeaderEntries[HeaderKeys.Platform] = "GEN";
// 00F ASCII-encoded GMV file format version. The most recent is 'A'. (?)
string version = new string(r.ReadChars(1));
Result.Movie.Comments.Add($"{MovieOrigin} .GMV version {version}");
Result.Movie.Comments.Add($"{EmulationOrigin} Gens");
// 010 4-byte little-endian unsigned int: rerecord count
uint rerecordCount = r.ReadUInt32();
Result.Movie.Rerecords = rerecordCount;
// 014 ASCII-encoded controller config for player 1. '3' or '6'.
char player1Config = r.ReadChar();
// 015 ASCII-encoded controller config for player 2. '3' or '6'.
char player2Config = r.ReadChar();
// 016 special flags (Version A and up only)
byte flags = r.ReadByte();
/*
bit 7 (most significant): if "1", movie runs at 50 frames per second; if "0", movie runs at 60 frames per
second The file format has no means of identifying NTSC/"PAL", but the FPS can still be derived from the
header.
*/
bool pal = ((flags >> 7) & 0x1) != 0;
Result.Movie.HeaderEntries[HeaderKeys.Pal] = pal.ToString();
// bit 6: if "1", movie requires a savestate.
if (((flags >> 6) & 0x1) != 0)
{
Result.Errors.Add("Movies that begin with a savestate are not supported.");
return;
}
// bit 5: if "1", movie is 3-player movie; if "0", movie is 2-player movie
bool threePlayers = ((flags >> 5) & 0x1) != 0;
LibGPGX.InputData input = new LibGPGX.InputData();
input.dev[0] = player1Config == '6'
? LibGPGX.INPUT_DEVICE.DEVICE_PAD6B
: LibGPGX.INPUT_DEVICE.DEVICE_PAD3B;
input.dev[1] = player2Config == '6'
? LibGPGX.INPUT_DEVICE.DEVICE_PAD6B
: LibGPGX.INPUT_DEVICE.DEVICE_PAD3B;
var ss = new GPGX.GPGXSyncSettings
{
UseSixButton = player1Config == '6' || player2Config == '6',
ControlType = GPGX.ControlType.Normal
};
input.dev[2] = input.dev[3] = input.dev[4] = input.dev[5] = input.dev[6] = input.dev[7] = LibGPGX.INPUT_DEVICE.DEVICE_NONE;
if (threePlayers)
{
input.dev[2] = ss.UseSixButton
? LibGPGX.INPUT_DEVICE.DEVICE_PAD6B
: LibGPGX.INPUT_DEVICE.DEVICE_PAD3B;
}
var controlConverter = new GPGXControlConverter(input, false);
var controller = new SimpleController
{
Definition = controlConverter.ControllerDef
};
// Unknown.
r.ReadByte();
// 018 40-byte zero-terminated ASCII movie name string
string description = NullTerminated(new string(r.ReadChars(40)));
Result.Movie.Comments.Add(description);
/*
040 frame data
For controller bytes, each value is determined by OR-ing together values for whichever of the following are
left unpressed:
* 0x01 Up
* 0x02 Down
* 0x04 Left
* 0x08 Right
* 0x10 A
* 0x20 B
* 0x40 C
* 0x80 Start
*/
string[] buttons = { "Up", "Down", "Left", "Right", "A", "B", "C", "Start" };
/*
For XYZ-mode, each value is determined by OR-ing together values for whichever of the following are left
unpressed:
* 0x01 Controller 1 X
* 0x02 Controller 1 Y
* 0x04 Controller 1 Z
* 0x08 Controller 1 Mode
* 0x10 Controller 2 X
* 0x20 Controller 2 Y
* 0x40 Controller 2 Z
* 0x80 Controller 2 Mode
*/
string[] other = { "X", "Y", "Z", "Mode" };
// The file has no terminator byte or frame count. The number of frames is the <filesize minus 64> divided by 3.
long frameCount = (fs.Length - 64) / 3;
for (long frame = 1; frame <= frameCount; frame++)
{
// Each frame consists of 3 bytes.
for (int player = 1; player <= 3; player++)
{
byte controllerState = r.ReadByte();
// * is controller 3 if a 3-player movie, or XYZ-mode if a 2-player movie.
if (player != 3 || threePlayers)
{
for (int button = 0; button < buttons.Length; button++)
{
controller[$"P{player} {buttons[button]}"] = ((controllerState >> button) & 0x1) == 0;
}
}
else
{
for (int button = 0; button < other.Length; button++)
{
if (player1Config == '6')
{
controller[$"P1 {other[button]}"] = ((controllerState >> button) & 0x1) == 0;
}
if (player2Config == '6')
{
controller[$"P2 {other[button]}"] = ((controllerState >> (button + 4)) & 0x1) == 0;
}
}
}
}
Result.Movie.AppendFrame(controller);
}
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(ss);
}
}
}