author: caol64 title: Analysis of the PS2 Game Save 3D Icons slug: parsing-ps2-3d-icon description: Dive deep into the realm of PS2 game development as we demonstrate how to unpack 3D icon data from save files. Our intricate guide covers vertices, model animation, textures, lighting, and backgrounds, all with accurate coding details. date: 2023-10-04 18:00:34 draft: false ShowToc: true TocOpen: true tags:
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.
A: What can we parse from the save file?
B: What do we need to do?
Completing the entire functionality will likely require two articles, with this one mainly focusing on A.
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:
Offset | Length | Description |
---|---|---|
0 | byte[4] | magic : PS2D |
4 | uint16 | 0 |
6 | uint16 | Position of the newline character in the game title, Note 1 |
8 | uint32 | 0 |
12 | uint32 | bg_transparency : Background transparency, 0-255 |
16 | uint32[4] | bg_color : Background color at the top-left corner (RGB, 0-255) |
32 | uint32[4] | bg_color : Background color at the top-right corner (RGB, 0-255) |
48 | uint32[4] | bg_color : Background color at the bottom-left corner (RGB, 0-255) |
64 | uint32[4] | bg_color : Background color at the bottom-right corner (RGB, 0-255) |
80 | uint32[4] | light_pos1 : Light position 1 (XYZ, 0-1) |
96 | uint32[4] | light_pos2 : Light position 2 (XYZ, 0-1) |
112 | uint32[4] | light_pos3 : Light position 3 (XYZ, 0-1) |
128 | uint32[4] | light_color1 : Light color 1 (RGB, 0-1) |
144 | uint32[4] | light_color2 : Light color 2 (RGB, 0-1) |
160 | uint32[4] | light_color3 : Light color 3 (RGB, 0-1) |
176 | uint32[4] | ambient : Ambient light (RGB, 0-1) |
192 | byte[68] | sub_title : Game title (null-terminated, SJIS encoding) |
260 | byte[64] | icon_file_normal : Normal icon filename (null-terminated), Note 2 |
324 | byte[64] | icon_file_copy : Copy icon filename (null-terminated), Note 2 |
388 | byte[64] | icon_file_delete : Delete icon filename (null-terminated), Note 2 |
452 | byte[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.
icon
FileUnlike 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.
Name | Description |
---|---|
Icon Header | Fixed size, 20 bytes |
Vertex Segment | Contains all vertices and normals data of the icon model |
Animation Segment | Stores information about animation frames of the icon model |
Texture Segment | Stores texture data of the icon model |
Icon
HeaderThe Icon
header stores all the essential information needed to decode the different data segments. This includes:
In the icon file, the Icon
header is always located at offset 0. Here's the structure of the Icon
header:
Offset | Length | Description |
---|---|---|
0000 | uint32 | magic : 0x010000 |
0004 | uint32 | animation_shapes : Number of animation shapes, Note 1 |
0008 | uint32 | tex_type : Texture type, Note 2 |
0012 | uint32 | Unknown, fixed value 0x3F800000 |
0016 | uint32 | vertex_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:
Mask | Description |
---|---|
0001 | Unknown |
0010 | Unknown |
0100 | Texture data exists in the icon file. Some games (like ICO) have no texture data, resulting in a fully black icon. |
1000 | Texture data in the icon file is compressed. |
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:
Each vertex coordinate occupies 8 bytes and has the following structure:
Offset | Length | Description |
---|---|---|
0000 | int16 | X-coordinate (divide by 4096 when in use) |
0002 | int16 | Y-coordinate (divide by 4096 when in use) |
0004 | int16 | Z-coordinate (divide by 4096 when in use) |
0006 | uint16 | Unknown |
Each normal coordinate has the same structure as the vertex coordinate data.
Each texture coordinate occupies 4 bytes and has the following structure:
Offset | Length | Description |
---|---|---|
0000 | int16 | U-coordinate (divide by 4096 when in use) |
0002 | int16 | V-coordinate (divide by 4096 when in use) |
Each vertex color occupies 4 bytes and has the following structure:
Offset | Length | Description |
---|---|---|
0000 | uint8 | Red (0-255) |
0001 | uint8 | Green (0-255) |
0002 | uint8 | Blue (0-255) |
0003 | uint8 | Alpha (0-255) |
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".
The structure of the "Animation Header" is as follows:
Offset | Length | Description |
---|---|---|
0000 | uint32 | Magic: 0x01 |
0004 | uint32 | Frame 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". |
0008 | float32 | Anim Speed: Play speed, purpose unknown |
0012 | uint32 | Play Offset: Starting frame, purpose unknown |
0016 | uint32 | Frame Count: Total number of "animation frames" in the animation segment, typically one "animation frame" corresponds to one "shape". |
The "Frame Data" immediately follows the "Animation Header".
Offset | Type | Description |
---|---|---|
0000 | u32 | Shape id |
0004 | u32 | Number of keys |
0008 | u32 | UNKNOWN |
0012 | u32 | UNKNOWN |
Offset | Type | Description |
---|---|---|
0000 | f32 | Time |
0004 | f32 | Value |
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 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 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.
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.