TOMBA2.IMG: Difference between revisions
No edit summary |
|||
Line 40: | Line 40: | ||
└── ... | └── ... | ||
=== ''' | === '''File Setup and Initialization''' === | ||
The script opens the <code>.IMG</code> file (in this case, <code>TOMBA2.IMG</code>) and seeks to the end to determine the size of the file. The file pointer is then reset to the beginning: | The script opens the <code>.IMG</code> file (in this case, <code>TOMBA2.IMG</code>) and seeks to the end to determine the size of the file. The file pointer is then reset to the beginning: | ||
with open(name, "rb") as rom: | with open(name, "rb") as rom: | ||
Line 60: | Line 51: | ||
* '''filelist''': A list to store the output file paths for each extracted VRAM chunk. | * '''filelist''': A list to store the output file paths for each extracted VRAM chunk. | ||
=== ''' | === '''Extracting VRAM Data''' === | ||
The script processes the <code>.IMG</code> file in chunks, extracting the compressed VRAM data based on header information. It reads the number of VRAM headers and their sizes to determine how much data to extract: | The script processes the <code>.IMG</code> file in chunks, extracting the compressed VRAM data based on header information. It reads the number of VRAM headers and their sizes to determine how much data to extract: | ||
header_amount = struct.unpack("<I", rom.read(4))[0] | header_amount = struct.unpack("<I", rom.read(4))[0] | ||
Line 68: | Line 59: | ||
out.write(rom.read(0x800 + total)) | out.write(rom.read(0x800 + total)) | ||
=== ''' | === '''Converting Compressed VRAM to Shards''' === | ||
Once all the VRAM chunks are extracted, the script moves on to converting them into individual texture "shards". It reads the <code>.cvram</code> files, processes the headers to determine the positions of the texture data, and extracts the texture data into smaller shard files: | Once all the VRAM chunks are extracted, the script moves on to converting them into individual texture "shards". It reads the <code>.cvram</code> files, processes the headers to determine the positions of the texture data, and extracts the texture data into smaller shard files: | ||
with open("./cvrams/" + filelist[filename] + ".cvram", "rb") as cvram: | with open("./cvrams/" + filelist[filename] + ".cvram", "rb") as cvram: | ||
Line 85: | Line 76: | ||
shard.write(b) # Write the byte to the shard | shard.write(b) # Write the byte to the shard | ||
=== ''' | === '''Reconstructing Full VRAM Pages''' === | ||
After all shards are created, the script reconstructs complete VRAM pages by combining the shards back into their appropriate locations within a VRAM page. It writes the full VRAM data into <code>.vram</code> files stored in the <code>./vrams/</code> directory: | After all shards are created, the script reconstructs complete VRAM pages by combining the shards back into their appropriate locations within a VRAM page. It writes the full VRAM data into <code>.vram</code> files stored in the <code>./vrams/</code> directory: | ||
with open("./vrams/" + filelist[i] + ".vram", "w+b") as vram: | with open("./vrams/" + filelist[i] + ".vram", "w+b") as vram: |
Revision as of 16:01, 10 January 2025
In Tomba! 2: The Evil Swine Return, the IMG file format is used to store compressed VRAM (Video RAM) data used for textures and graphical elements of the game. This format is specific to the game and plays an essential role in managing the game's graphical assets, such as background images, UI elements, and textures for in-game models.
The IMG file contains multiple chunks of compressed graphical data, each representing a set of textures that are used in different parts of the game world. These chunks are organized in a way that they can be loaded into the PlayStation's VRAM during gameplay.
Structure of the IMG File
While the specific structure of the IMG file may vary based on the game's content, the general layout is as follows:
- Headers: The IMG file contains headers that provide information about the data stored in the file. These headers define how many texture chunks there are, their locations, and the sizes of each chunk.
- Compressed Data: The textures within the IMG file are compressed to save space and improve performance. The compression format is proprietary to the game and requires special handling to extract and decompress.
- Shards: The IMG file may store textures in a shard format, where a texture is broken down into smaller "shards" for more efficient processing or loading into the PlayStation's VRAM.
unIMG.py
The IMG file can be extracted using unIMG.py script, which can be found here. Here is how it works:
Overview
The unIMG.py
script is used to extract and convert compressed VRAM (Video RAM) data from Tomba! 2: The Evil Swine Return’s .IMG
file format. It breaks down the .IMG
file, extracts the compressed VRAM data into separate chunks, and converts those chunks into individual texture "shards" which are then recombined into usable VRAM pages. The script processes the VRAM data to extract the textures into .cvram
, .shard
, and .vram
files for further use, such as modding or archival purposes.
Prerequisites
- Python installed on your system.
- A copy of the Tomba! 2
.IMG
file. - Basic understanding of binary data structures, particularly video memory management.
Output Structure
The script creates the following directory structure for the extracted and converted VRAM data:
outputfolder/ ├── cvrams/ │ ├── 0-0000.cvram │ └── ... ├── shards/ │ ├── 0-0000-0.shard │ ├── 0-0000-1.shard │ └── ... └── vrams/ ├── 0-0000.vram └── ...
File Setup and Initialization
The script opens the .IMG
file (in this case, TOMBA2.IMG
) and seeks to the end to determine the size of the file. The file pointer is then reset to the beginning:
with open(name, "rb") as rom: rom.seek(0, 2) # Seek to the end to get file size imgsize = rom.tell() rom.seek(0) # Reset to the start of the file
The script initializes several variables:
- vc: A counter for VRAM sections.
- filelist: A list to store the output file paths for each extracted VRAM chunk.
Extracting VRAM Data
The script processes the .IMG
file in chunks, extracting the compressed VRAM data based on header information. It reads the number of VRAM headers and their sizes to determine how much data to extract:
header_amount = struct.unpack("<I", rom.read(4))[0] header_size = header_amount * 0xC + 4 # Header size calculation
For each VRAM chunk, the script reads a header containing the VRAM's dimensions and data size, then stores the data in .cvram
files. The extracted VRAM is written to the directory ./cvrams/
:
with open("./cvrams/" + outpath + ".cvram", "wb") as out: out.write(rom.read(0x800 + total))
Converting Compressed VRAM to Shards
Once all the VRAM chunks are extracted, the script moves on to converting them into individual texture "shards". It reads the .cvram
files, processes the headers to determine the positions of the texture data, and extracts the texture data into smaller shard files:
with open("./cvrams/" + filelist[filename] + ".cvram", "rb") as cvram: c_header_amount = struct.unpack("<I", cvram.read(4))[0] c_header_size = c_header_amount * 0xC + 4 skip = 0x800 - c_header_size # Padding to align the header
Each shard is then written to a separate file, saved in the ./shards/
directory:
with open("./shards/" + filelist[filename] + "-{:d}.shard".format(i), "wb+") as shard: shard.write(cvram.read(amount)) # Write texture data to shard
The script handles the special data format of each shard, including the decompression logic using offsets and extra data handling (e.g., negative offsets):
if extra != 0: for i in range(amount): shard.seek(extras[extra], 2) # Seek with the specific offset b = shard.read(1) # Read the byte at the offset shard.seek(0, 2) # Seek back to the end of the file shard.write(b) # Write the byte to the shard
Reconstructing Full VRAM Pages
After all shards are created, the script reconstructs complete VRAM pages by combining the shards back into their appropriate locations within a VRAM page. It writes the full VRAM data into .vram
files stored in the ./vrams/
directory:
with open("./vrams/" + filelist[i] + ".vram", "w+b") as vram: vram.seek(0x100000 - 1) vram.write(b"\0") # Initialize VRAM with zero padding
The script then places each shard in the correct position within the VRAM page based on the coordinates (x, y), width (w), and height (h) stored in the shard headers:
vram.seek((x * 2) + (y * 0x800)) # Seek to the correct VRAM position for k in range(h): vram.write(inshard.read(w * 2)) # Write shard data to VRAM vram.read(0x800 - w * 2) # Read padding between rows