11 March 2016 - Forum Rules

Main Menu

(PSP) FF4 - LZTX Compression

Started by travisty, March 25, 2020, 01:35:21 AM

Previous topic - Next topic


I'm writing an image loader in the Rust programming language to load the TM2 sprite files from the PSP version of FF4. I've been successful in loading the uncompressed files, but a bunch of those TM2 files are compressed with an LZTX header at the very beginning of the file. I did some research, and came across this thread from 2012 over at XeNTaX. From what I could tell, one of the posters on that thread was successful in decompressing those LZTX-compressed TM2 files. Not only that, but the poster posted a C# project that did all of this. The link is dead, however... If I had access to that source, then it'd be trivial to write a Rust adaptation.

According to that user, it's standard LZSS compression starting at byte 0x8. I looked through one of the compressed images, and I could see that there was a control byte for LZSS compression starting at 0x8. So, I loaded that entire file into memory, trimmed the first 8 bytes off, and tried using an LZSS compression library. Things is, this library requires me to convert each byte into an LzssCode enum value: a LzssCode::Symbol(u8) for uncompressed bytes, or a LzssCode::Reference(len, pos) variant that contains the length and position.

Now, the uncompressed bytes are pretty obvious: copy that value into LzssCode::Symbol(), and you're done. The reference is the tough part. I'm not sure if references are made up of a single byte or multiple. I've seen all kinds of encoding strategies as I looked online, but the most comprehensive article I could find was the encoding for LZSS in FF7. It suggests that references are 2 bytes where 12 of those bits are for the offset, and 4 bits are for the length. I tried using that encoding scheme, and it crashes. I tried different variants of that because other resources online mention the use of 2 bytes to describe references. No luck... Could anyone shed some light on how those references are encoded?

Here's the first 48 bytes of the file I'm trying to decode:

4C 5A 54 58  40 10 01 00    7F 54 49 4D  32 04 00 01
E5 F6 EB 30  10 F4 F0 04    EB F0 0C 01  00 FF 30 00
00 01 86 01  83 05 3B 00    01 07 00 00  80 3F EA F1

The 8th byte appears to be a control byte with a value of 7F (0111 1111). That looks like the first 7 chunks are all uncompressed, so they're all 1 byte and already the correct value. The 8th chunk is a reference, and I'm not sure if it should be 1 or 2 bytes. I know that the first 7 bytes are uncompressed because the first 4 bytes make up the "TIM2" identifier followed by a 16-bit version that's 4 (should always be 4 for this game). The next 2 bytes should make up how many images are contained in this file. The first bit is uncompressed with a value of 1. The next byte, which makes up the more significant byte in the 16-bit value (little-endian) is a reference, but I'm pretty sure that the next uncompressed byte should be zero because that'd make the 16-bit "count" value in the header 1 (usually is).

Here's my code so far:

fn decode_lztx(buffer: &[u8]) -> Vec<u8> {
let mut pos = 0;
let mut control = 0;
let mut codes = Vec::new();

while pos < buffer.len() - 1 {
control = buffer[pos];
pos += 1;

for i in 0..8 {
if ((control >> i) & 0x1) == 0 {
let byte1 = buffer[pos] as usize;
let byte2 = buffer[pos+1] as usize;
let code = LzssCode::Reference {
len: (byte2 & 0x0F) + 3,
pos: ((byte2 & 0xF0) << 4) | byte1,

pos += 2;
} else {
pos += 1;

if pos >= buffer.len() {

.decode(&mut LzssDecoder::new(0x1_0000))
.collect::<Result<Vec<_>, _>>()


Looking at the compressed data, I would guess that the references are 16 bits each. That would mean that the references visible in the first 48 bytes that you posted are 0xF6E5, 0xF0F4, 0xF0EB, 0x0007, and 0xF1EA. I know that the LZSS formats used in FF5 and FF6 for SFC/SNES have a slightly different reference format, so I wouldn't be surprised if this one is different too.

One thing to keep in mind is that the reference offset can be either relative or absolute. So either a direct pointer to the decompression buffer or a value to add or subtract from the current offset. Also, the bits in the reference could also be split in some way other than 12 bits for the offset and 4 bits for the length. In FF5 it's 11 for the offset and 5 for the length and I think I've seen another case where it was 13 for the offset and 3 for the length.

It's also possible that the references are big-endian, in which case you have 0xE5F6, 0xF4F0, 0xEBF0, 0x0700, and 0xEAF1.

Any chance you have an example of what one of these image files looks like after it is decompressed? Also, it would be helpful to see a larger example of a compressed file, maybe 256 bytes or so, and also to know some more info about the image like height/width, color format, etc. if possible. The TIM2 format seems to be pretty well documented, so knowing some of the header values will make it easier to know what the decompressed data will look like.


Ok, cool. It made more sense for the references to be 16 bits, but I did try with 8-bit refs. No luck there. I also tried big-endian, little-endian, and weird bit-splits, but only for my 8-bit ref attempts. I'll try 13-bit/3-bit and 11-bit/5-bit. The permutations are adding up quickly, but it's all worth the try.

The relative and absolute positioning has been throwing me as well. I really do think my math is off by 1, or I'm working from the wrong side of the buffer. I've tried every permutation of that as well to no luck just yet.

I have uncompressed TM2 files that my image loader can load and render, but I don't have an uncompressed version of one of the compressed files. That would make things WAY easier though lol. That XeNTax link I posted mentioned that some one was able to find an uncompressed version of one of the compressed files, but I haven't been able to find any of them. Would an uncompressed version of any TM2 file be helpful?

I don't know what the header information should be since I don't know what the uncompressed values should be yet. I know that most of the header values should be zero for the most part.

That said, here's the header output for one of the tile sets that is already uncompressed:

Header {
    ident: 0x54494d32, // 4 bytes 'TIM2'
    version: 4,        // 2 bytes
    count: 1,          // 2 bytes

ImageHeader {
    total_size: 263216,                    // 4 bytes
    palette_size: 1024,                    // 4 bytes
    image_size: 262144,                    // 4 bytes
    header_size: 48,                       // 2 bytes
    color_entry_count: 256,                // 2 bytes
    paletted: 134,                         // 1 bytes
    mipmap_count: 1,                       // 1 bytes
    clut_format: 131,                      // 1 bytes
    bpp: 8,                                // 1 bytes
    width: 512,                            // 2 bytes
    height: 512,                           // 2 bytes
    gs_tex_0: [0, 0, 128, 63, 0, 0, 0, 0], // 8 bytes
    gs_tex_1: [1, 0, 0, 1, 1, 0, 1, 5],    // 8 bytes
    gs_regs: 0,                            // 4 bytes
    gs_tex_clut: 3,                        // 4 bytes

Here's the first 512-bytes of that data:

4C 5A 54 58  40 10 01 00    7F 54 49 4D  32 04 00 01
E5 F6 EB 30  10 F4 F0 04    EB F0 0C 01  00 FF 30 00
00 01 86 01  83 05 3B 00    01 07 00 00  80 3F EA F1
F4 F0 5F 01  01 00 01 05    EA F1 03 EB  F0 FF 44 4A

53 94 FA FE  FE FA FF F0    DF B7 90 70  70 5E 5E FF
53 4A 53 A2  FE FF FE FD    77 F2 E2 C3  39 03 44 53
AC 42 01 EF  F5 E8 C3 9A    3A 00 70 5E  6D FF 7A AC
FF FF FF FD  FA ED F7 D6    A2 83 3B 01  57 B7 B7 FD

DD FE 33 00  F2 DF AC 39    01 63 44 AF  53 A2 FD FF
43 00 F8 57  02 70 F6 3E    00 90 FA 83  01 FA ED D4
AC FF 83 70  70 4A 44 4A    83 F2 3C 83  00 54 00 DF
C3 9A 7A 3B  00 AE 00 07    53 5E 53 B3  01 B8 01 AE

02 B2 00 B7  04 00 BD 04    B2 01 B7 03  CE 04 D4 04
C3 01 AD 03  B1 03 80 DC    07 D4 03 C3  01 FE 08 B4
00 F0 05 D1  05 44 FE 4E    00 53 5E 70  6D 48 62 79
DF 48 31 59  72 42 B9 03    70 8D FF 94  91 A1 59 42

25 35 48 FE  B9 03 7A 8D    A1 A8 8B 9B  A1 F7 42 51
62 BA 02 63  83 94 82 3F    80 62 96 C1  77 59 5D 11
EE 00 FF 94  A2 4A 3A 87    69 79 42 FB  3A 79 B4 01
63 7A AC A2  4A 7F 31 51    72 A1 A1 62  9B BD 01 FF

70 94 C3 A3  51 31 44 87    EF E1 77 79  62 8F 11 8D
B7 D4 FF 94  59 48 44 72    87 62 42 FF  69 42 31 59
A1 62 2E 25  FF 2E 25 31    62 2E 35 2E  35 FF 2F 42
48 35 59 31  42 31 7B 31    25 B4 10 76  35 17 17 C4

10 FF 35 25  69 42 42 25    25 31 FF 72  59 42 42 17
25 51 35 FD  2E D6 10 42    31 42 25 8B  42 7F 25 2F
17 42 9C 42  62 4A 10 FF    59 48 51 48  42 2F 35 22
FF 17 51 31  42 A1 72 2E    17 FF 51 6F  6F 2F 42 60

6F 17 FF 22  42 59 79 9C    76 35 51 FF  17 35 2F 2F
79 A4 7E 2F  FF 40 87 42    72 48 31 17  6F FF 59 0E
2F 50 46 88  35 58 FF 58    17 76 80 0E  2F 88 60 FF
14 2D 38 23  38 38 7E 23    FF 8E 67 50  2F 17 0E 38

That's where the decoding starts, anyway (first byte is the control byte). There are 4 other bytes that come before it, but it was implied in the XeNTax forum that they don't do anything. That said, could this be header content?

40 10 01 00


I think I've hit success! I was able to decode some images, and they loaded up in Game Graphic Studio. I'd post a screenshot, but it doesn't look like I have permission to attach images yet. Anyhow, I also got rid of the compression crate I was going to use because that library required me to decipher whether or not each byte was uncompressed, or a reference, then also decipher the reference values lol... LZSS feels way more like a concept rather than a spec anyway. I ended up solving this by using FF7's version of LZSS compression in this article since the bit layout that it described seemed to match what I was seeing in those files perfectly. Looks like I was interpreting the symbol and references correctly, but I wasn't applying the offsets correctly. I took a leap of faith, and translated the Python script from Github.

Here's my Rust code that decodes the whole thing:

fn decode_lztx(buffer: &[u8]) -> Vec<u8> {
let mut pos = 0;
let mut dec_pos = 0;
let mut control = 0;
let mut result = Vec::new();

while pos < buffer.len() - 1 {
control = buffer[pos];
pos += 1;

for i in 0..8 {
if ((control >> i) & 0x1) == 0 {
let byte1 = buffer[pos] as usize;
let byte2 = buffer[pos+1] as usize;
let length = (byte2 & 0x0F) as i32 + 3;
let offset = (((byte2 & 0xF0) << 4) | byte1) as i32;
let mut r = dec_pos - ((dec_pos + 0xFEE - offset) & 0xFFF);
pos += 2;

for _ in 0..length {
if r >= 0 {
result.push(result[r as usize]);
} else {

dec_pos += 1;
r += 1;
} else {
dec_pos += 1;
pos += 1;

if pos >= buffer.len() {


I decoded 100 compressed images from this one directory, and they all look pretty accurate palette-wise. I read that palettes can be off sometimes, which is weird because LZSS is a form of lossless compression. I think I might be set, but I wanted to at least document this on the Internet somewhere in case it helps anyone else. I know that if the link to Aspire's C# project source wasn't dead, it could have really helped me over the weekend. This feels rewarding though. Here's the Github project containing all of the (poorly-written) Rust code I'm working on in case anyone wants to borrow pieces of the code from it.

Thanks for the advice too, Everything. I was beginning to doubt 16-bit references, but your explanation of FF5 and FF6 using 16-bit references, and weird bit-lengths motivated me to keep trying to decode 16-bit refs instead of 8-bit ones. I think that was key.


Great, I'm glad to hear that you figured it out! I've been thinking about adding support for the PSP version of FF4 to my editing utility FF6Tools, and this gives me some incentive.


Thanks! This is my first ROM hack-initiative. It's something that I've been putting off for year, so I'll take any advice I can get. Are your tools open sourced? If so, can you post a link? I'll follow them on Github.

Btw, it looks like the color palette seems to be off for some images. Here's what the image looks like in Game Graphic Studio:

2012 sti quarter mile

Here's what that enemy sprite's colors should probably look like:

quick image upload

Maybe I'm supposed to run some sort of filter over the image, or palette swap it? That's my only guess since it's a common thing in RPGs to make different levels of the same enemy with different color palettes. That said, there are exactly 900 enemy images, so it seems like maybe all variations should be included in those files...

Have you encountered anything like this in FF5 or FF6?


You might find my two repositories below useful. We have been using it with my translation group for an italian translation of the game. (toolset to dearchive and decompress essentially everything in FF4 PSP, supports LZTX as well)

FF4Archiver is the (de)archiver of the PAC0/1.bin archive inside the game. Its command line usage might be a bit complex, but it is quite fast in rearchiving everyting, as it does not create the archive from scratch, but only inserts the modified files.

FF4Decompressor: decompresses every file compressed with the LZSS variant used by the game (without the LZTX header)

FF4MiniArchiver: (de)archives the small archive files found inside the main game archive. It automatically decompresses and recompresses LZTX textures, whenever it finds any in the given archive.

CompressionLibrary: contains the main LZSS (de)compression functions, in case you might need them.

Unfortunately I did not ship any binary for the above tools, but I can provide, in case you need them.

and (opens and converts TIM2 files back and forth to png)

You might use the ImgLib subproject in Rainbow, and embed it in your tool.
Latest binaries are provided in the project readme, under "latest dev build".

P.S. Strangely enough, even GameGraphic Studio is somehow limited on TIM2 support.
In fact, the color palette is messed up for some images, because it has been reordered for fast access on hardware. Take a look at on how to reorder it.

Rainbow takes care of everything, and supports opening TIM2 files either in swizzled or linear form (which are both used by the game).


The GitHub page for FF6Tools is It doesn't support any PSP versions of games yet, but it does support the SNES/SFC and GBA versions of FF4. The full list of supported games is listed in the readme.

I'm guessing the palette issue is specific to the PSP so I don't think I can help there. I've never seen that issue with palettes on other systems. Hopefully PhOeNiX's info will be helpful.


Here is the Goblin image previewed in Rainbow

You can see the palette is not "linear", and you need to "linearize" it with the code I mentioned in my reply above.


@PhOeNiX: Seriously, thank you! Your response not only explained my palette issue, but answered questions that I was thinking about starting separate threads for such as learning how pac0.bin and pac1.bin work. I used Vash's infamous unpacker for extracting all of the .lzs files from the large .bin, but I wanted to learn how that all worked. I've been scouring the web trying to figure out LXTZ and how FF4's implements its encoding scheme for LZSS-compressed data, in yet, there's been a resource out there already. It's ironic that it never came up in any of my own searches *facepalm*. I could have swore I came across this Github once or twice, and merely assumed it was for the SNES/GBA versions of the game.

Is marco-calautti your Github account? If so, I've actually been using the source code for your Rainbow image editor over the past week to figure out how to write a TM2 loader in Rust. I'm learning a programming language and a new file format, so I'm working through with 2 steep learning curves at the same time. Having a solid implementation of what I'm trying to build in another language that I know really well really makes things a lot easier.

The linear palette makes total sense too. I even came across the pseudo-code for the linear palette the TM2 docs, and in the Rainbow editor's code. It was functionality that I meant to implement once I ran into a need for it, but I didn't realize the need for it when palettes were off. Just totally went over my head lol...

Judging by the code for the Rainbow Editor, it seemed like it may even support things that Game Graphic Studio doesn't support yet. I wasn't sure because I haven't actually built the project yet. I heard somewhere that GGS doesn't support TM2 files with multiple palettes in them. Maybe I'll do that tonight after work...

One minor question: is it extremely rare for a TM2 file to have multiple images packed into them? I haven't come across a file with multiple images in FF4 yet, but it looks like tons of games across multiple console gens use TM2.

@Everything: Awesome! Your Github repo looks awesome. Is there anything you haven't deciphered yet for the SNES and GBA releases of FF5 and FF6? That's basically when I'd like to do for just the PSP release of FF4, except I'm still building reference knowledge for deciphering things, and it looks like Marco Calautti's already got that covered.

I'm new to all this ROM stuff, and honestly I'm only doing it to scrape things, but I've learned a lot in the past week. That was the main purpose of everything. That, and to look for a way to build a web-based editor in LitElement...


Yes, that's my github repository. Regarding TIM2 files with multiple frames (images), yes they are reasonably common, and I know at least two games that make extensive use of multi-frame TIM2 files. Regarding multi-palette images, yes Rainbow supports them (FF4 PSP actually contains some, see e.g., the TIM2's containing characters portrait like Golbez). Actually, Rainbow supports almost any configuration of TIM2 files. The only thing it does not support at the moment, although it is not difficult to implement, is mipmapped TIM2's, which contain different scaled down versions of the same image. These are quite rare, and generally not worth modifying (at least for translation purposes), as they are usually used for texturing materials, objects from the distance, etc.


Well hey, I'm thankful that you developed all of that. Those two repos have many lessons that they can teach me, and I'm looking forward to that over the weekend.

I'm going the OpenGL route for rendering the contents of those tm2 files. OpenGL provides a function to auto-generate mipmaps, so would there be any benefit to using the image's own mipmaps in my case?

Also, I noticed that Rainbow has filter support, and that they're achieved in software by computing the final pixel values, and baking them into the image data. Are these filters purely meant as an editor feature as a means for the user to manipulate the image, or are some of those filters required to display certain images as they were originally designed because maybe the artist wanted to make some sort of image that's meant to be filtered/colorized?


The reason why mipmaps are stored in the tm2 files is just efficiency. It does not make much sense for a game to generate them on the fly, all the time. Now, images can be scaled down with different approaches, and the resulting images can be slightly different. If your goal is 1:1 fidelity with the original game, it makes sense using the original mipmaps. However, I don't remember any mipmapped tm2 in ff4 psp, being mostly a 2d game.

Regarding filters, I think I chose the wrong term for these objects. They are meant to preprocess image and palette data, to make them all uniform w.r.t. the internal representation used by rainbow. For example, tile-based images need a TileFilter to be converted in linear form (i.e. pixel rows stored one after the other), which is how Rainbow interprets image data internally. Similarly, TIM2PaletteFilter is needed to preprocess the scrambled palettes to linear (which is how Rainbow interprets palettes internally).

That's why it is all software, it is not some kind of "aesthetic" filter, but more "structural".

Generally, the user of Rainbow should not be concerned with filters. They are used internally to support the graphics format at hand. An exception is when the file format provides not information on the filter needed, and then the user needs to choose one. An example is swizzling for tm2. The tm2 header has no flag indicating if swizzling is used. This is solved e.g. in Game Graphic Studio, by choosing the interlace mode (PS2, PSP, etc.).

Nevertheless, the interface is general enough to allow also for "aesthetic" filters, in case the specific file format allows for such a thing.


I always thought that storing mipmaps in the image file was meant for hardware that didn't have linear texture filtering like the PS1 and some PC hardware back in the earlier/mid-90s. Then, devs started writing their own mipmap generators by rendering their textures to a quad on a smaller texture with bi or tri-linear filtering enabled. Then, graphics APIs eventually started supporting that, so devs wouldn't need to write their own. The 1:1 fidelity's a good point since graphics APIs are geared more towards real-time graphics, so round-off errors are greater, and graphics was far less flexible than doing everything in software at the time.

Using the graphics API had benefits in terms of load-times and footprints because it usually faster to calculate mipmaps than loading them from from disk. Especially if the graphics API was capable of doing it all within its own driver's memory space, which saves the game from reading more data from storage and less memory copies. In cases where images didn't use hardware-accelerated compressions (typically for large, detailed images with gradients or with transparent pixels) it's common to load the entire compressed image into memory, allocate an additional buffer for the uncompressed data (which can be many times larger than the compressed buffer), then decode it into that buffer. Then, make an API call to OpenGL/DirectX/PSGL to copy the uncompressed image data to the graphics driver's memory space (which may or may not be in GPU memory). Finally, the original compressed and uncompressed buffers can be freed. That process involves loading each mipmap level into memory once, and allocating memory to hold the uncompressed version of it twice: once for decoding and another when it gets uploaded to the GPU.

Ok, so the filters necessary for reconstructing the image properly, and not just an aesthetic. I had a feeling it was 100% necessary, but I wasn't sure. I haven't had a chance to go through all of Rainbow's source, but it looked like you were doing everything manually via software instead of leveraging a graphics API like OpenGL or DirectX. That's pretty hardcore stuff! Did you go the pure software route, so that you could have the best accuracy? As much as I like using graphics APIs, they're not the most portable. Especially with the next-gen APIs (Metal, Vulkan, and DX12).


I might be wrong, I am not an expert in Graphics APIs, but as far as I know (at least according to all sources I came up during my research) mipmaps are *precomputed*. In fact, It is also a big deal how much more storage mipmaps require (1/3 of the original image).

Of course no one prevents the game to generate them on the fly with HW, but what probably a game does at run-time is to interpolate between two mipmaps, to obtain one that is of the right resolution, given the distance from the camera. But the base power-of-two smaller images are usually stored on disk, I believe.
This would also allow the game to have very high quality scaled-down versions of the original texture, since they are computed offline, and time is not a constraint in that case.

I went all software because I wanted as less dependencies as possible on external APIs, so to have a portable codebase. Given that Rainbow is not a real-time tool, but mostly meant for format conversion, there was no need to rely on HW acceleration.

Moreover, for some formats, like Gamecube's DXT1, there were some subtle differences with the PC one that using the graphics API (e.g., DirectX) would result in not accurate decoded images.

This is actually the same exact reason why the dolphin-emu went the software route as well, according to their comments in their texture decoder:


Yeah, you could precomputed mipmaps at load-time if the graphics API supports it. For example, OpenGL has glGenerateMipmap. I misworded that earlier: by on-the-fly, I meant that once the initial (largest) image is loaded into your graphics driver's memory, you can call glGenerateMipmap() against that texture, and the driver will allocate and create everything for you. The big win is that you don't need to have each mip level existing in main memory only for it to be copied again into driver memory (which may or may not be VRAM). Load times are faster because the API's just generating the mipmaps within its own memory space instead of reading each mip level from a file, and copying between the main CPU thread and the graphics driver's memory space which are slower operations.

The downside is like you said though: the results can be different from the source material because your graphics driver's filtering algorithm and/or precision doesn't always match the source material which will yield different results.

Doing everything in software is really impressive. I've been spoiled by the graphics API that do a lot of this for me, so I don't have that deep level of knowledge there. I totally get where you're coming from with external APIs. As portable as OpenGL can be, vendor implementations can differ a lot across hardware.