Skip to content

Commit

Permalink
input-rec: Add some half-decent documentation for the file formats
Browse files Browse the repository at this point in the history
  • Loading branch information
xTVaser committed Apr 23, 2021
1 parent 3a57822 commit 9d5d369
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 52 deletions.
8 changes: 6 additions & 2 deletions common/src/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(UtilitiesSources
pxWindowTextWriter.cpp
RwMutex.cpp
StringHelpers.cpp
StringUtils.cpp
ThreadingDialogs.cpp
ThreadTools.cpp
wxAppWithHelpers.cpp
Expand Down Expand Up @@ -74,6 +75,7 @@ set(UtilitiesHeaders
../../include/Utilities/ScopedAlloc.h
../../include/Utilities/ScopedPtrMT.h
../../include/Utilities/StringHelpers.h
../../include/Utilities/StringUtils.h
../../include/Utilities/Threading.h
../../include/Utilities/ThreadingDialogs.h
../../include/Utilities/TraceLog.h
Expand Down Expand Up @@ -111,9 +113,11 @@ set(UtilitiesFinalSources
)

set(UtilitiesFinalLibs
${LIBC_LIBRARIES} # Gold (new linux linker) does not get automatically dependency of dependency
${LIBC_LIBRARIES} # Gold (new linux linker) does not get automatically dependency of dependency
${wxWidgets_LIBRARIES}
yaml-cpp chdr-static
fmt::fmt
yaml-cpp
chdr-static
)

add_pcsx2_lib(${Output} "${UtilitiesFinalSources}" "${UtilitiesFinalLibs}" "${UtilitiesFinalFlags}")
Expand Down
195 changes: 195 additions & 0 deletions pcsx2/Recording/docs/recording-file-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<!-- This is technical documentation and hence didn't feel relevant for including in a release -->

<!-- Therefore, it's located here instead of the pcsx2/Docs folder, as there isn't any other location -->

<!-- For technical documentation in the repo -->

# Input Recording File Schema

* [Current Version (v2.X)](#current-version-v2x)
* [Overview](#overview)
* [Recording Types](#recording-types)
* [Header / Header Data](#header--header-data)
* [Frame Data](#frame-data)
* [Input Recording Macro Differences](#input-recording-macro-differences)
* [Legacy Version](#legacy-version)
* [Overview](#overview-1)
* [Header](#header)
* [Frame Data](#frame-data-1)

## Current Version (v2.X)

Normal Recording Extension - `.pir` (PS2 Input Recording)
Recording Macro Extension (Not yet Fully Implemented) - `.pirm` (PS2 Input Recording Macro)

### Overview

An improved file format from the legacy version, with future extensibillity in mind. Includes better metadata to support backwards/forwards compatibility and removes arbitrary size limits on fields that vary.

#### Recording Types

| Type (enum value) | Description |
| ---- | ----------- |
| `INPUT_RECORDING_POWER_ON (0)` | Starts the recording from booting the game, this is preferred as save-states are volatile and may break across emulator versions!
| `INPUT_RECORDING_SAVESTATE (1)` | Starts the recording from a save-state stored along-side the recording file. Volatile, but sometimes preferred for a quick recording / demo / etc.

<!-- | `INPUT_RECORDING_MACRO (2)` | **WIP** Not to be played back like a typical input recording, instead it can be played during recording creation / general use to quickly execute a series of inputs. -->

### Header / Header Data

| Version Added | Field | Description | Type | Size | Default |
| ------------- | ----- | ----------- | ---- | ---- | ------- |
| 2.0 | Magic String | File identification purposes | string | `"pcsx2-input-recording"` | -- |
| 2.0 | `m_file_version_major` | Major component of file version | unsigned byte | 1-byte | 2 |
| 2.0 | `m_file_version_minor` | Minor component of file version | unsigned byte | 1-byte | 0 |
| 2.0 | `m_offset_to_frame_counter` | Useful to jump to the frame counter within the header | signed int | 4-bytes | 0 |
| 2.0 | `m_offset_to_redo_counter` | Useful to jump to the redo counter within the header | signed int | 4-bytes | 0 |
| 2.0 | `m_offset_to_frame_data` | Useful to jump past the header and the beginning of the input data | signed int | 4-bytes | 0 |
| 2.0 | `m_emulator_version` | The version of PCSX2 used to create the recording | std::string (UTF-8) | Arbitrary size, null-terminated | `""` |
| 2.0 | `m_recording_author` | The author(s) of the input recording | std::string (UTF-8) | Arbitrary size, null-terminated | `""` |
| 2.0 | `m_game_name` | The name of the game the recording was created for | std::string (UTF-8) | Arbitrary m_game_name, null-terminated | `""` |
| 2.0 | `m_total_frames` | How many frames are a part of this input recording | signed `long` | 8-bytes | 0 |
| 2.0 | `m_redo_count` | How many times the input recording had a save-state loaded during creation | signed `long` | 8-bytes | 0 |
| 2.0 | `m_recording_type` | Identifies how the input recording should be handled / played-back / etc. See the list above for all types | signed `long` | 8-bytes | 0 (`INPUT_RECORDING_POWER_ON`) |
| 2.0 | `m_redo_count` | How many times the input recording had a save-state loaded during creation | signed `long` | 8-bytes | 0 |
| 2.0 | `m_num_controllers_per_frame` | How many controllers's input data is stored per frame | unsigned byte | 1-byte | 2 |

### Frame Data

Similar to the legacy format, immediately after the header the frame data begins. The difference is, in this format we support an arbitrary number of controllers per frame specified in the header, rather than a constant of `2`. Each frame is composed of *same* number of controllers, and their respective PAD data

Each controller has 18-bytes of input data per frame from that is intercepted from the SIO interrupts. These 18 bytes are as follows:

| Byte Index | Description | Notes |
| ---------- | ----------- | ----- |
| 0 | Pressed Bitfield (1/2):<ul><li>D-Pad Left</li><li>D-Pad Down</li><li>D-Pad Right</li><li>D-Pad Up</li><li>Start</li><li>R3</li><li>L3</li><li>Select</li></ul> | A bit being unset (`0`) indicates it is pressed
| 1 | Pressed Bitfield (2/2):<ul><li>Square</li><li>Cross</li><li>Circle</li><li>Triangle</li><li>L1</li><li>R1</li><li>L2</li><li>R2</li></ul> | A bit being unset (`0`) indicates it is pressed
| 2 | Right Analog X-Vector | 0-255 (127 is Neutral)
| 3 | Right Analog Y-Vector | 0-255 (127 is Neutral)
| 4 | Left Analog X-Vector | 0-255 (127 is Neutral)
| 5 | Left Analog Y-Vector | 0-255 (127 is Neutral)
| 6 | D-Pad Right (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 7 | D-Pad Left (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 8 | D-Pad Up (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 9 | D-Pad Down (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 10 | Triangle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 11 | Circle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 12 | Cross (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 13 | Square (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 14 | L1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 15 | R1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 16 | L2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 17 | R2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)

To further clarify/illustrate, this means the input data would be organized something like this:

* Frame 0
* Controller Port 0 - 18 bytes
* ...
* Controller Port n - 18 bytes
* Frame 1
* Controller Port 0 - 18 bytes
* ...
* Controller Port n - 18 bytes
* ...and so on...

#### Input Recording Macro Differences

For the purposes of input recording macros, its beneficial to explicitly mark certain portions of input data is ignored. This is because typically an input recording is all-or-nothing. It either overwrites everything in the SIO buffer with the content of the recording file, or nothing at all.

Macros need to be more nuanced than this. To do so, each byte that was described in the previous second is preceeded by a bool to mark whether or not it should be ignored. Or in the event of the bitfields, it will store a bitfield of it's own to do the same. This means that a macro stores twice as much data per frame:

| Byte Index | Description | Notes |
| ---------- | ----------- | ----- |
| 0 | Ignore Pressed Bitfield (1/2):<ul><li>D-Pad Left</li><li>D-Pad Down</li><li>D-Pad Right</li><li>D-Pad Up</li><li>Start</li><li>R3</li><li>L3</li><li>Select</li></ul> | A bit being set indicates it should be ignored
| 1 | Pressed Bitfield (1/2):<ul><li>D-Pad Left</li><li>D-Pad Down</li><li>D-Pad Right</li><li>D-Pad Up</li><li>Start</li><li>R3</li><li>L3</li><li>Select</li></ul> | A bit being unset (`0`) indicates it is pressed
| 2 | Ignore Pressed Bitfield (2/2):<ul><li>Square</li><li>Cross</li><li>Circle</li><li>Triangle</li><li>L1</li><li>R1</li><li>L2</li><li>R2</li></ul> | A bit being set indicates it should be ignored
| 3 | Pressed Bitfield (2/2):<ul><li>Square</li><li>Cross</li><li>Circle</li><li>Triangle</li><li>L1</li><li>R1</li><li>L2</li><li>R2</li></ul> | A bit being unset (`0`) indicates it is pressed
| 4 | Ignore Right Analog X-Vector | boolean
| 5 | Right Analog X-Vector | 0-255 (127 is Neutral)
| 6 | Ignore Right Analog Y-Vector | boolean
| 7 | Right Analog Y-Vector | 0-255 (127 is Neutral)
| 8 | Ignore Left Analog X-Vector | boolean
| 9 | Left Analog X-Vector | 0-255 (127 is Neutral)
| 10 | Ignore Left Analog Y-Vector | boolean
| 11 | Left Analog Y-Vector | 0-255 (127 is Neutral)
| 12 | Ignore D-Pad Right (Pressure) | boolean
| 13 | D-Pad Right (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 14 | Ignore D-Pad Left (Pressure) | boolean
| 15 | D-Pad Left (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 16 | Ignore D-Pad Up (Pressure) | boolean
| 17 | D-Pad Up (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 18 | Ignore D-Pad Down (Pressure) | boolean
| 19 | D-Pad Down (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 20 | Ignore Triangle (Pressure) | boolean
| 21 | Triangle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 22 | Ignore Circle (Pressure) | boolean
| 23 | Circle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 24 | Ignore Cross (Pressure) | boolean
| 25 | Cross (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 26 | Ignore Square (Pressure) | boolean
| 27 | Square (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 28 | Ignore L1 (Pressure) | boolean
| 29 | L1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 30 | Ignore R1 (Pressure) | boolean
| 31 | R1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 32 | Ignore L2 (Pressure) | boolean
| 33 | L2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 34 | Ignore R2 (Pressure) | boolean
| 35 | R2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)

## Legacy Version

Extension - `.p2m2`

### Overview

Very basic file format, only supports 2 controller ports. Basic metadata support in the header with upper-limits on string sizes.

### Header

| Field | Description | Type | Size |
| ----- | ----------- | ---- | ---- |
| `version` | Specifies version of recording file | unsigned byte | 1-byte |
| `emu` | The version of pcsx2 used when creating the recording | string | reserved length of 50 bytes |
| `author` | The author(s) of the input recording | string | reserved length of 255 bytes |
| `gameName` | Name of the game the input recording was made for | string | reserved length of 255 bytes |

TODO REST

### Frame Data

The frame data is laid out very simplistically. Immediately after the header data, the frame data begins. Each frame is composed of two controller port's respective PAD data.

Each controller has 18-bytes of input data per frame from that is intercepted from the SIO interrupts. These 18 bytes are as follows:

| Byte Index | Description | Notes |
| ---------- | ----------- | ----- |
| 0 | Pressed Bitfield (1/2):<ul><li>D-Pad Left</li><li>D-Pad Down</li><li>D-Pad Right</li><li>D-Pad Up</li><li>Start</li><li>R3</li><li>L3</li><li>Select</li></ul> | A bit being unset (`0`) indicates it is pressed
| 1 | Pressed Bitfield (2/2):<ul><li>Square</li><li>Cross</li><li>Circle</li><li>Triangle</li><li>L1</li><li>R1</li><li>L2</li><li>R2</li></ul> | A bit being unset (`0`) indicates it is pressed
| 2 | Right Analog X-Vector | 0-255 (127 is Neutral)
| 3 | Right Analog Y-Vector | 0-255 (127 is Neutral)
| 4 | Left Analog X-Vector | 0-255 (127 is Neutral)
| 5 | Left Analog Y-Vector | 0-255 (127 is Neutral)
| 6 | D-Pad Right (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 7 | D-Pad Left (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 8 | D-Pad Up (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 9 | D-Pad Down (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 10 | Triangle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 11 | Circle (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 12 | Cross (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 13 | Square (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 14 | L1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 15 | R1 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 16 | L2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)
| 17 | R2 (Pressure) | 0-255 (255 being the maximum pressure, and 0 being unpressed)

To further clarify/illustrate, this means the input data would be organized something like this:

* Frame 0
* Controller Port 0 - 18 bytes
* Controller Port 1 - 18 bytes
* Frame 1
* Controller Port 0 - 18 bytes
* Controller Port 1 - 18 bytes
* ...and so on...
5 changes: 4 additions & 1 deletion pcsx2/Recording/file/v1/InputRecordingFileV1.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
#include "System.h"
#include "Recording/PadData.h"

// Handles all operations on the input recording file
/**
* @brief Handles all operations on the input recording file.
* You can find documentation on this file format in `pcsx2/Recording/docs/recording-file-schema.md`
*/
class InputRecordingFileV1
{
public:
Expand Down
52 changes: 4 additions & 48 deletions pcsx2/Recording/file/v2/InputRecordingFileV2.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,16 @@

#ifndef DISABLE_RECORDING

// ---- Binary File Layout
// ---- Header
// v2.0 - Magic String = "pcsx2-input-recording"
// v2.0 - (u8) File Version major
// v2.0 - (u8) File Version minor
// v2.0 - (int) Offset to Header Metadata
// v2.0 - (int) Offset to Frame Counter
// v2.0 - (int) Offset to Redo Counter
// v2.0 - (int) Offset to Start of Frame Data
// ---- Header Metadata
// v2.0 - (u8) emulator version major
// v2.0 - (u8) emulator version minor
// v2.0 - (u8) emulator version patch
// v2.0 - (string) emulator version name
// v2.0 - (string) recording author
// v2.0 - (string) game name
// v2.0 - (long) - total frames
// v2.0 - (long) - redo count
// v2.0 - (int) - recording type (enum)
// v2.0 - (u8) - number of controllers per frame
// ---- Frame Data (Input Recording Type (Power-On or Savestate))
// v2.0 - controller[0] - PadData (see below)
// ...
// v2.0 - controller[num_controllers_per_frame] - See above
// ...
// ---- Frame Data (Input Recording Macro Type)
// v2.0 - controller[0] - PadData (see below).
// - each value is preceeded by a bool to indicate if it should be ignored or not
// - the exception is the first two bitfields for indicating if inputs are pressed or not,
// these are preceeded similarly by bitfields to indicate if they should be ignored
// ...
// v2.0 - controller[num_controllers_per_frame] - See above
// ...

// ---- PadData
// ---- Pressed Flags Bitfield - (0 means pressed!)
// Left, Down, Right, Up, Start, R3, L3, Select
// ---- Pressed Flags Bitfield - (0 means pressed!)
// Square, Cross, Circle, Triangle, R1, L1, R2, L2
// ---- Analog Sticks Bytes
// Right Analog X
// Right Analog Y
// Left Analog X
// Left Analog Y
// ---- Pressure Bytes
// Right, Left, Up, Down, Triangle, Circle, Cross, Square, L1, R1, L2, R2

#include "System.h"
#include "Utilities/FileUtils.h"
#include "Recording/file/v1/InputRecordingFileV1.h"

#include <string>

/**
* @brief Handles all operations on the input recording file.
* You can find documentation on this file format in `pcsx2/Recording/docs/recording-file-schema.md`
*/
class InputRecordingFileV2
{
public:
Expand Down Expand Up @@ -129,7 +86,6 @@ class InputRecordingFileV2
std::string m_emulator_version = "";
std::string m_recording_author = "";
std::string m_game_name = "";
// An signed 32-bit frame limit is equivalent to 1.13 years of continuous 60fps footage (assuming 2 controllers per frame!)
long m_total_frames = 0;
long m_redo_count = 0;
InputRecordingType m_recording_type = InputRecordingType::INPUT_RECORDING_POWER_ON;
Expand Down
1 change: 0 additions & 1 deletion pcsx2/Recording/gui/NewRecordingFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ void NewRecordingFrame::enableOkBox()
}
}

// TODO - i removed the wxWidgets fix here, double check in a linux environment!
fs::path NewRecordingFrame::getFile() const
{
return FileUtils::wxStringToPath(m_file_picker->GetPath());
Expand Down

0 comments on commit 9d5d369

Please sign in to comment.