170 lines
5.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|