Table of Contents
Resource Reverse Engineering: Last Window: The Secret of Cape West
Oh boy… At a glace, pretty much all of the files inside this game are completely different to the previous game, Hotel Dusk: Room 215.
You'll find some code from 2023 at: https://github.com/Jas2o/KyleHyde
Pack
A fairly typical file container format.
- 4 bytes = nothing?
- 4 bytes = number of files
- 4 bytes = an offset? Didn't use it
- 4 bytes = unknown
Then for the number of files:
- 1 byte = Filename length
- ASCII string of that length
- 4 bytes = File length
Then all the files follow.
BIN / BPG / EBP / IFB / IBA
Appears the first 4 bytes is the uncompressed size, and then 2 bytes is a zlib header (78 9c).
Confirmed, offzip was able to decompress a EN_Common.bin
.NET DeflateStream appears to work fine for these, just skip the first 6 bytes before you run it.
Sadly, don't think BRA files use this. The search continues…
BIN
Usually text.
BPG (Uncompressed)
A tile based images using a palette. A 256 x 192 image would have 48 tiles, 32 x 32 pixels each. Left to right, top to bottom, easy enough. Large images work fine in GameTools. A lot of the smaller or odd sized ones do not display correctly without forcing some changes.
bool flip = false; byte[] magic = GT.ReadBytes(fs, 4, flip); //BPG1 int paletteNum = GT.ReadInt16(fs, 2, flip); int unknown2 = GT.ReadInt16(fs, 2, flip); //Should be 8 ? Width = GT.ReadInt16(fs, 2, flip); Height = GT.ReadInt16(fs, 2, flip); int tileWidth = GT.ReadInt16(fs, 2, flip); int tileHeight = GT.ReadInt16(fs, 2, flip); // Small/odd sized BPGs will need their Width/Height and tileW/H variables // altered, you could do that here. I was working on a Dictionary to do it, // but they're not very interesting to look at. int numTilesX = Width / tileWidth; int numTilesY = Height / tileHeight; Color[] palette = new Color[paletteNum]; for(int i = 0; i < paletteNum; i++) { byte left = GT.ReadByte(fs); byte right = GT.ReadByte(fs); palette[i] = HotelDusk.FRM.Palette2Color(left, right); } bitmap = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); for (int y = 0; y < numTilesY; y++) { for (int x = 0; x < numTilesX; x++) { for (int ty = 0; ty < tileHeight; ty++) { for (int tx = 0; tx < tileWidth; tx++) { byte lookup = GT.ReadByte(fs); bitmap.SetPixel(x * tileWidth + tx, y * tileHeight + ty, palette[lookup]); } } } }
EBP
Might be the same as BPG (as in tile based). These seem less clear, will need to spend some time on them.
Appears to be a fairly standard bitmap using a palette. Unsure if it has animation/smaller scaled frames. However sometimes I can't recognize what's in them using a raw image viewer.
Assumptions:
- 4 bytes = Width
- 4 bytes = Height
- 4 bytes = Flag (bits per pixel?)
- If Flag is 4, then the palette length is 16 and each bitmap pixel is 4 bits
- If Flag is 8, then palette is 256 x 4 (BGRA8888?)
- If Flag is 53, then palette is 32 x 2 (RGBA1555?)
- (i.e. flag, then the palette and so on)
- If Flag is 17476, then palette total length is the next 4 bytes (RGBA1555?), and then there's 2 x 4 bytes that sum to end of file - so sizes of two different sized images?
- (i.e. flag, pal length, img1 len, img2 len, then the palette and so on)
When I can be bothered I'll look into how the Flag actually determines how the palette and extra bytes and image works.
Currently in GameTools most EBPs with 8 flags work fine, 53's work without the palette, and all the others are either broken or fail to load entirely. I'm still far more intrigued by the BRA files however them being the most unknown format I might come back to EBPs as a distraction.
BRA / Animation
As of July 26th 2020 these are pretty much solved. You'll find these files inside files inside pack files. They use a form of LZ compression, doesn't appear to be off-the-shelf.
Assume all the lengths are 4 bytes unless mentioned otherwise:
- NumFrames
- unknown1 (likely size of the largest compressed tile data length)
- paletteLen
- TotalFrameSize (i.e. Width x Height)
- unknown2 (likely the frame number to jump to loop from)
- Width (expect 192)
- Height (expect 256)
- paletteBlob (paletteLen) - seems to always be RGBA1555
- For number of frames:
- Offset (start off coordinate table, which is followed by compressed tile data)
- Coordinate table length
- Compressed tile data length
After the co-ordinate table, the pixel data goes something like this:
- 1 byte
- First 4 bits - Method to use for length/offset, until it gets told to use a different method.
- If this is 0x0, then the length is 2 bits, and the offset is 14 bits.
- If this is 0xC, then the length is 3 bits, and the offset is 13 bits.
- If this is 0x8, then the length is 4 bits, and offset is 12 bits. Japanese version files use this more than European version files.
- If this is 0x4, then the length is 5 bits, and offset is 11 bits.
- Last 4 bits - Bitmask for the next four 'reads'.
- Those four reads will occur.
- 1 byte - bitmask for the next eight 'reads'.
- 1: 'read' 1 byte, put into buffer.
- 0: 'read' 2 bytes to get length/offset. For example if the method is 0x8 (length=4 bits, offset=12 bits):
- Mask 00L0 is the length (minus 2). Therefore shortest run length is 2, and the longest can be 17.
- Mask LL0H is a 12-bit signed number for the offset in the decompressed data to copy to the current position. Negative values are backwards from the current position. Positive values are forwards from the start of the decompressed buffer.
- The 8-bit bitmask and following 8 reads will continue until a 0 bit read results in 0xFFFF, then it cancels the remaining reads and goes back to the start to get a new method and next 4 reads.
Some of the European version's files achieve better compression by using the 0x8 length/offset method less frequently.
If the palette length was 256, then just use the decompressed values to lookup the palette. Otherwise each byte value is used to determine alpha transparency and the palette offset:
- alpha = (value » 5) * 36
- looukup = (value & 0x1F)
MTC
MTCs are a low resolution colour animation overlay. Each frame appears to always be 25 x 29 pixels (however the first one includes the header so it's missing the first row - covered by the characters name anyway). Colour format is RGBA1555. Not the same as Hotel Dusk.
- 4 bytes = Number of frames
- 4 bytes = Width to upscale to (not the width of the MTC)
- 4 bytes = Height to upscale to (not the height of the MTC)
- 4 bytes = “Betty_Up.mtc” had 7, “test.mtc” had 1. Animation speed or loop maybe?
- 16 bytes = zeroes