Seeing this image, it shouldn’t be unfamiliar to seasoned players familiar with PS2. It’s the 3D icon of a game save file from the PS2 memory card management interface. In this article, we will introduce how to extract the character model from the save file.

01 Parsing Objectives

A: What can we parse from the save file?

  • All vertices and normals of the icon model
  • Animation frames of the icon model
  • Lighting information
  • Textures and texture coordinates
  • Background color and transparency

B: What do we need to do?

  • Write shaders to render the background and icons
  • Create animations from the animation frames of the icon model
  • Build model matrices, view matrices, and perspective matrices to achieve a display close to the original PS2 effect

Completing the entire functionality will likely require two articles, with this one mainly focusing on A.

02 Parsing icon.sys

In the previous article, we discussed how to export the game’s save files. In fact, each save file contains an icon.sys file, which can be considered as the configuration file for the icon. icon.sys is a fixed-size file (964 bytes), with the following structure:

0byte[4]magic: PS2D
6uint16Position of the newline character in the game title, Note 1
12uint32bg_transparency: Background transparency, 0-255
16uint32[4]bg_color: Background color at the top-left corner (RGB, 0-255)
32uint32[4]bg_color: Background color at the top-right corner (RGB, 0-255)
48uint32[4]bg_color: Background color at the bottom-left corner (RGB, 0-255)
64uint32[4]bg_color: Background color at the bottom-right corner (RGB, 0-255)
80uint32[4]light_pos1: Light position 1 (XYZ, 0-1)
96uint32[4]light_pos2: Light position 2 (XYZ, 0-1)
112uint32[4]light_pos3: Light position 3 (XYZ, 0-1)
128uint32[4]light_color1: Light color 1 (RGB, 0-1)
144uint32[4]light_color2: Light color 2 (RGB, 0-1)
160uint32[4]light_color3: Light color 3 (RGB, 0-1)
176uint32[4]ambient: Ambient light (RGB, 0-1)
192byte[68]sub_title: Game title (null-terminated, SJIS encoding)
260byte[64]icon_file_normal: Normal icon filename (null-terminated), Note 2
324byte[64]icon_file_copy: Copy icon filename (null-terminated), Note 2
388byte[64]icon_file_delete: Delete icon filename (null-terminated), Note 2
452byte[512]All zero

Note 1: The game title sub_title is displayed in 2 lines, and this value indicates at which byte the newline occurs in the title.

Note 2: Each game save file can correspond to 3 icon files, which are displayed in different scenes.

As you can see, the icon.sys file mainly provides data such as background and lighting. Another important part is the filename where the 3D icon is located.

03 Parsing the icon File

Unlike the icon.sys file, the icon file for each game is variable in size and quantity, but there is always at least one. Some games may use the same icon for both copying and deleting icons as the regular icon.

3.1 File Structure

Icon HeaderFixed size, 20 bytes
Vertex SegmentContains all vertices and normals data of the icon model
Animation SegmentStores information about animation frames of the icon model
Texture SegmentStores texture data of the icon model

3.2 Icon Header

The Icon header stores all the essential information needed to decode the different data segments. This includes:

  • Number of vertices contained in the “Vertex Segment” and the number of animation shapes
  • Whether the texture data is compressed

In the icon file, the Icon header is always located at offset 0. Here’s the structure of the Icon header:

0000uint32magic: 0x010000
0004uint32animation_shapes: Number of animation shapes, Note 1
0008uint32tex_type: Texture type, Note 2
0012uint32Unknown, fixed value 0x3F800000
0016uint32vertex_count: Number of vertices, always a multiple of 3

Note 1: The icon model has different sets of vertex data for different actions, called “shapes.” Rendering different shapes in a loop creates animation effects.

Note 2: The purpose of the “Texture type” part is not yet clear. This value is a 4-byte integer. Below is a summary of the functionality of each bit, which may not be accurate:

0100Texture data exists in the icon file. Some games (like ICO) have no texture data, resulting in a fully black icon.
1000Texture data in the icon file is compressed.

3.3 Vertex Segment

Polygons in PS2 icons are always composed of triangles formed by three vertices. Since the vertices are arranged according to a certain pattern, simply reading the vertex data according to this pattern can easily construct the polygons. Rendering this data using OpenGL or similar tools produces a beautiful wireframe icon.

The “Vertex Segment” contains data for all the vertices in the icon. Each vertex data includes a set of vertex coordinates, normal coordinates, texture coordinates, and a set of RGBA data. Therefore, the data structure of the “Vertex Segment” with m vertices and n shapes is as follows:

Vertex Coordinates

Each vertex coordinate occupies 8 bytes and has the following structure:

0000int16X-coordinate (divide by 4096 when in use)
0002int16Y-coordinate (divide by 4096 when in use)
0004int16Z-coordinate (divide by 4096 when in use)

Normal Coordinates

Each normal coordinate has the same structure as the vertex coordinate data.

Texture Coordinates

Each texture coordinate occupies 4 bytes and has the following structure:

0000int16U-coordinate (divide by 4096 when in use)
0002int16V-coordinate (divide by 4096 when in use)

Vertex RGBA

Each vertex color occupies 4 bytes and has the following structure:

0000uint8Red (0-255)
0001uint8Green (0-255)
0002uint8Blue (0-255)
0003uint8Alpha (0-255)

3.4 Animation Segment

Unfortunately, I haven’t fully understood the meaning of most of the content in the “Animation Segment” yet. However, it’s not a big concern as animation actions can still be accomplished using “Vertex Coordinate Interpolation”.

Below is the data structure of the “Animation Segment”:

The “Animation Segment” consists of an “Animation Header” and several “Animation Frames”, with each “Animation Frame” containing several “Key Frames”.

Animation Header

The structure of the “Animation Header” is as follows:

0000uint32Magic: 0x01
0004uint32Frame Length: The number of frames needed to complete one cycle of the animation. This value helps calculate the number of “play frames” corresponding to each “animation frame”.
0008float32Anim Speed: Play speed, purpose unknown
0012uint32Play Offset: Starting frame, purpose unknown
0016uint32Frame Count: Total number of “animation frames” in the animation segment, typically one “animation frame” corresponds to one “shape”.

Frame Data

The “Frame Data” immediately follows the “Animation Header”.

0000u32Shape id
0004u32Number of keys

Key Frame


3.5 Texture Segment

Textures are images with dimensions of 128x128 pixels, encoded using the TIM image format. Depending on the tex_type field in the Icon Header, textures can be classified into two types: uncompressed and compressed.

Uncompressed Texture

Uncompressed textures have a pixel format of BGR555, where each of B, G, and R occupies 5 bits, totaling 15 bits, and occupying 2 bytes (with 1 bit redundancy). The format is as follows:

High-order byte:    Low-order byte:
X B B B B B G G     G G G R R R R R

X = Don't care, R = Red, G = Green, B = Blue

Therefore, the original image size is fixed at 128x128x2 bytes. To convert its pixel format to RGB24, the following method can be used:

High-order byte:     Middle-order byte:    Low-order byte:
R R R R R 0 0 0      G G G G G 0 0 0       B B B B B 0 0 0

When converting 5-bit color values to 8-bit, the lower 3 bits need to be padded with zeros. After the above conversion, the number of bytes per pixel becomes 3 bytes. Similarly, the format can also be converted to RGBA32, where the number of bytes per pixel becomes 4 bytes.

Compressed Texture

Compressed textures use a very simple RLE algorithm for compression. The first u32 is the size of the compressed texture data. The data that follows alternates between u16 rle_code and rle_data until the end. rle_data has two variables: the number of data (denoted as x) and the repetition count (denoted as y). The rle_code serves as a counter. If it is less than 0xFF00, then x = 1 and y = rle_code; if it is greater than or equal to 0xFF00, then x = (0x10000 - rle_code) and y = 1. See the diagram below.

After decompressing the compressed texture, it can be converted to an RGB24 or RGBA32 image based on the content of the previous section.

04 Conclusion

With the completion of the analysis of the relevant icon files, everything is ready except for the east wind. In the next article, we will start rendering mode and use PyGame and ModernGL to display the rendered animation.

05 References