This is an alternate version of OPL2’s wiki page.

Overview

Binary specifications are written as C-like 010 Editor Templates. Most data types should be self-explanatory.

Text specifications are written as ABNF + RFC 7405.

FileTypePurpose
attributeTextAdd-on definition
*.BINBinaryFont
*.OGGBinaryAudio
*.PL2BinaryArchive
*.PSDBinaryImage
*.TCMTextCamera animation
*.TMBBinary3D model
*.TSBBinary3D animation
*.TSMText3D model
*.TSSText3D animation
*.TXTTextScript

BIN

Monochrome font image.

//--- 010 Binary Template: PL2_FONTBIN.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
// File Mask: font.bin
//  ID Bytes: 46 4F 4E 54
RequiresVersion(3);
LittleEndian();

typedef struct {
  char  signature[4];  // "FONT"
  int32 glyph_count;
  int32 glyph_size;
  BYTE  padding0C__[4]  <hidden=true>;

  struct {
    uint16 codepoint;  // Shift-JIS
    ubyte  data[glyph_size * glyph_size];
  } glyph[glyph_count]  <optimize=true>;
} FONT;

FONT bin  <open=true>;

OGG

Lossy audio in the FOSS Xiph.Org Ogg Vorbis format.

PL2

Binary file archive.

Compression uses the 1989-01-06 LZSS implementation written by Haruhiko Okumura. Default parameters are kept (N = 4096, F = 18, THRESHOLD = 2, etc.), though the buffer is initialized to NULL instead of space characters.

//--- 010 Binary Template: PL2_PL2.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
// File Mask: *.pl2
//  ID Bytes: [+16] 61 74 74 72 69 62 75 74 65 00
RequiresVersion(3, 1);
LittleEndian();

typedef struct {
  char   name[32];
  uint32 offset;
  uint32 compressed_size;
  uint32 size;
  BYTE   padding2C__[4]  <hidden=true>;
} ARCHIVE_ITEM;
Assert(sizeof(ARCHIVE_ITEM) == 0x30);

typedef struct {
  local uint i, entry_count = (ReadUInt(16 + 32) - 16) / sizeof(ARCHIVE_ITEM);

  BYTE         reserved00__[16]  <hidden=true>;
  ARCHIVE_ITEM item[entry_count];

  for (i = 0; i < entry_count; ++i)
    struct {
      FSeek(item[i].offset);
      if (item[i].compressed_size == item[i].size)
        ubyte raw[item[i].size];
      else
        ubyte compressed[item[i].compressed_size];
    } data;
} ARCHIVE;

ARCHIVE pl2  <open=true>;

PSD

Multilayered image in the proprietary Adobe Photoshop PSD format.

The following reduced version only includes what this engine uses. Note that some versions of Photoshop may save incompatible files.

//--- 010 Binary Template: PL2_PSD.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
// File Mask: *.psd
//  ID Bytes: 38 42 50 53 00 01
//      Note: Hidden fields aren't read by the engine.
RequiresVersion(3);
BigEndian();

typedef struct {
  char   signature[4];    // Must be "8BPS"
  uint16 version;         // Must be 1
  BYTE   reserved06__[6]  <hidden=true>;
  uint16 channel_count    <hidden=true>;
  uint32 height           <hidden=true>;
  uint32 width            <hidden=true>;
  uint16 bit_depth;       // Must be 8
  uint16 color_mode;      // Must be 3 (RGB)

  uint32 color_section_size;
  BYTE   color_section[color_section_size]  <hidden=true>;

  uint32 resource_section_size;
  BYTE   resource_section[resource_section_size]  <hidden=true>;

  uint32 layer_section_size;
  uint32 layer_info_section_size  <hidden=true>;
  int16  layer_count;             // If negative, absolute value is the layer count and 1st alpha is merged result's transparency
  local uint16 real_layer_count = Abs(layer_count);
  struct {
    int32  top;
    int32  left;
    int32  bottom;
    int32  right;
    uint16 channel_count;
    struct {
      int16  id;  // -1 = A, 0 = R, 1 = G, 2 = B
      uint32 data_size;
    } channel_info[channel_count];
    char   blend_mode_signature[4]  <hidden=true>;
    char   blend_mode_key[4]        <hidden=true>;
    ubyte  opacity;
    ubyte  clipping  <hidden=true>;
    ubyte  flags     <hidden=true>;
    ubyte  filter    <hidden=true>;
    uint32 exta_size;
    union {
        BYTE ignored00__[exta_size]  <hidden=true>;
        struct {
            uint32 mask_size;
            BYTE   mask[mask_size]  <hidden=true>;
            uint32 blending_size;
            BYTE   blending[blending_size]  <hidden=true>;
            ubyte  name_size;               // Must be < 32
            char   name[name_size];
        } fields;
    } extra;
  } layer[real_layer_count]  <optimize=false>;

  local int i, j;
  for (i = 0; i < real_layer_count; ++i)
    struct {
      for (j = 0; j < layer[i].channel_count; ++j)
        struct {
          uint16 compression_mode;  // Must be 0 (raw) or 1 (RLE)
          ubyte  data[layer[i].channel_info[j].data_size - 2];
        } channel;
    } layer_channels;
} IMAGE;

IMAGE psd  <open=true>;

TCM

Text camera animation.

; Engine parser is more forgiving.

tcm =
  *el %s"@TCM100" nl
  cameraanim
  %s"<___end___>" nl

cameraanim =
  %s"[CAMERAANIM]" nl
  range-ln        ; frames
  *cameraanim-ln  ; [frames]

; Position X, Y, Z; Look X, Y, Z; FoV
cameraanim-ln = *WSP vector3 1*WSP vector3 1*WSP float *WSP nl


; begin, end; count = end - begin + 1
range-ln = *WSP integer 1*WSP integer *WSP nl

nl      = CRLF *el
el      = [comment] CRLF
comment = "#" *(%x01-0C / %x0E-FF)

vector3 = float 1*WSP float 1*WSP float
float   = ["+" / "-"] (1*DIGIT ["." *DIGIT] / "." 1*DIGIT) ["e" ["+" / "-"] 1*DIGIT]
integer = ["+" / "-"] 1*DIGIT

WSP = SP / HTAB / %x0B-0C

TMB

Binary 3D model.

//--- 010 Binary Template: PL2_TMB.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
// File Mask: *.tmb
//  ID Bytes: 54 4D 42 30
RequiresVersion(3);
LittleEndian();

typedef struct {
  ubyte r, g, b, a;
} COLOR;
typedef struct {
  float r, g, b, a;
} FCOLOR;
typedef float MATRIX[16];
typedef struct {
  float x, y, z;
} VECTOR3;

typedef struct {
  char signature[4];  // "TMB0"

  int32 texture_count;
  struct {
    char  name[32];
    int16 width;
    int16 height;
    struct {
      ubyte b, g, r, a;
    } pixel[(int)width * height];
  } texture[texture_count]  <optimize=false>;

  int32 material_count;
  struct {
    FCOLOR diffuse;
    FCOLOR ambient;
    FCOLOR specular;
    FCOLOR emissive;
    float  shininess;
    int32  texture_id;
  } material[material_count]  <optimize=true>;

  int32 object_count;
  struct {
    char   name[32];
    MATRIX transform;
    int32  face_count;
    int32  backface_culling_flag;  // enable back-face culling if 1
    int32  material_count;
    struct {
      int32 id;
      int32 face_begin;
      int32 face_count;
    } material[material_count];
    struct {
      struct {
        VECTOR3 location;
        float   bone_weight[3];
        ubyte   bone_index[3];
        BYTE    padding1B__  <hidden=true>;
        float   normal[3];
        COLOR   color;
        float   texture_uv[2];
      } vertex[3]  <optimize=true>;
    } face[face_count]  <optimize=true>;
  } object[object_count]  <optimize=false>;

  int32 bone_count;
  struct {
    MATRIX inverse_transform;
  } bone[bone_count]  <optimize=true>;

  int32 point_count;
  struct {
    uint32  padding00__  <hidden=true>;  // Pointer to name in memory
    VECTOR3 translation;
    VECTOR3 rotation;
    VECTOR3 scaling;
  } point[point_count]  <optimize=true>;
  struct {
    string name;
  } point_name[point_count]  <optimize=false>;
} MODEL;

MODEL tmb  <open=true>;

TSB

Binary 3D animation.

//--- 010 Binary Template: PL2_TSB.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
// File Mask: *.tsb
//  ID Bytes: 54 53 42 30
RequiresVersion(3, 1);
LittleEndian();

typedef float MATRIX[16];
Assert(sizeof(MATRIX) == 0x40);

typedef struct {
  float x, y, z;
} VECTOR3;
Assert(sizeof(VECTOR3) == 0x0C);

typedef struct {
  char  signature[4];    // "TSB0"
  BYTE  padding04__[12]  <hidden=true>;
  int32 bone_count;
  int32 frame_count;
  int32 loop_start_frame;
  int32 point_count;

  struct {
    struct {
      MATRIX transform;
    } bone[bone_count];
  } bone_frame[frame_count]  <optimize=true>;
  struct {
    struct {
      VECTOR3 translation;
      VECTOR3 rotation;
      VECTOR3 scaling;
    } point[point_count];
  } point_frame[frame_count]  <optimize=true>;
  struct {
    string name;
  } point_name[point_count]  <optimize=false>;
} ANIMATION;

ANIMATION tsb  <open=true>;

TSM

Text 3D model.

The only known source of files in this format is the PL2 trial (pl2_tfinal). Release versions cannot load this format.

TSS

Text 3D animation.

The only known source of files in this format is the PL2 trial (pl2_tfinal). Release versions can be modified to load this format.

; Engine parser is more forgiving.

tss =
  *el %s"@TSS100" nl
  joints
  jointanim
  locators
  locatoranim
  [looppoint]
  %s"<___end___>" nl

joints =
  %s"[JOINTS]" nl
  count-ln
  *name-ln  ; [joints]

jointanim =
  %s"[JOINTANIM]" nl
  range-ln       ; frames
  *jointanim-ln  ; [frames * joints]

; Transform matrix
jointanim-ln = *WSP matrix4x4 *WSP nl

locators =
  %s"[LOCATORS]" nl
  count-ln
  *name-ln  ; [locators]

locatoranim =
  %s"[LOCATORANIM]" nl
  range-ln         ; ignored; uses range from <jointanim>
  *locatoranim-ln  ; [frames * locators]

; Translate X, Y, Z; Rotate X, Y, Z; Scale X, Y, Z
locatoranim-ln = *WSP vector3 1*WSP vector3 1*WSP vector3 *WSP nl

looppoint =
  %s"[LOOPPOINT]" nl
  *WSP integer *WSP nl  ; frame


count-ln = *WSP integer *WSP nl
name-ln  = *(WSP / VCHAR) nl
; begin, end; count = end - begin + 1
range-ln = *WSP integer 1*WSP integer *WSP nl

nl      = CRLF *el
el      = [comment] CRLF
comment = "#" *(%x01-0C / %x0E-FF)

matrix4x4 = vector4 1*WSP vector4 1*WSP vector4 1*WSP vector4
vector3   = float 1*WSP float 1*WSP float
vector4   = float 1*WSP float 1*WSP float 1*WSP float
float     = ["+" / "-"] (1*DIGIT ["." *DIGIT] / "." 1*DIGIT) ["e" ["+" / "-"] 1*DIGIT]
integer   = ["+" / "-"] 1*DIGIT

WSP = SP / HTAB / %x0B-0C
//--- 010 Script: PL2_TSB2TSS.1sc
//  Requires: PL2_TSB.bt
//   Authors: Crazycatz00
//   Version: 1.0
//  Category: Game
RequiresVersion(3);
LittleEndian();

void FPMatrix(int fp, const MATRIX& m, const char pf[])
{
  FPrintf(
    fp, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f%s", m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9],
    m[10], m[11], m[12], m[13], m[14], m[15], pf
  );
}

void FPVector3(int fp, const VECTOR3& v, const char pf[]) { FPrintf(fp, "%f %f %f%s", v.x, v.y, v.z, pf); }

void tsb2tss(const ANIMATION& tsb, int fp)
{
  local int i, j;

  FPrintf(fp, "@TSS100\r\n");

  FPrintf(fp, "\r\n[JOINTS]\r\n");  // Line contents ignored
  FPrintf(fp, "%d\r\n", tsb.bone_count);
  for (i = 0; i < tsb.bone_count; ++i)
    FPrintf(fp, "\tjoint_%d\r\n", i);

  FPrintf(fp, "\r\n[JOINTANIM]\r\n");  // Line contents ignored
  FPrintf(fp, "%d %d\r\n", 0, tsb.frame_count - 1);
  for (i = 0; i < tsb.frame_count; ++i) {
    FPrintf(fp, "# frame %d\r\n", i);
    for (j = 0; j < tsb.bone_count; ++j)
      FPMatrix(fp, tsb.bone_frame[i].bone[j].transform, "\r\n");
  }

  // Bug: Engine fails to set `loop_start_frame` if skipping this block; trash data would be used.
  if (true /* bugfix */ || tsb.point_count != 0 || tsb.loop_start_frame != 0) {
    FPrintf(fp, "\r\n[LOCATORS]\r\n");
    FPrintf(fp, "%d\r\n", tsb.point_count);
    for (i = 0; i < tsb.point_count; ++i)
      FPrintf(fp, "\t%s\r\n", tsb.point_name[i].name);

    FPrintf(fp, "\r\n[LOCATORANIM]\r\n");              // Line contents ignored
    FPrintf(fp, "%d %d\r\n", 0, tsb.frame_count - 1);  // Line contents ignored
    for (i = 0; i < tsb.frame_count; ++i) {
      FPrintf(fp, "# frame %d\r\n", i);
      for (j = 0; j < tsb.point_count; ++j) {
        FPVector3(fp, tsb.point_frame[i].point[j].translation, " ");
        FPVector3(fp, tsb.point_frame[i].point[j].rotation, " ");
        FPVector3(fp, tsb.point_frame[i].point[j].scaling, "\r\n");
      }
    }

    if (tsb.loop_start_frame != 0) {
      FPrintf(fp, "\r\n[LOOPPOINT]\r\n");  // Line contents ignored
      FPrintf(fp, "\t%d\r\n", tsb.loop_start_frame);
    }
  }

  FPrintf(fp, "\r\n<___end___>\r\n");
}

int fp = FileNew("Text", false);
tsb2tss(tsb, fp);
FileSelect(fp);