User Tools

Site Tools


hoteldusk

Resource Reverse Engineering: Hotel Dusk: Room 215

Nearly solved enough to move onto Last Window: The Secret of Cape West, which appears to use completely different compression techniques!

You'll find code at: https://github.com/Jas2o/KyleHyde

Text

Files such as EP1_Book.txt and System.txt are 123DDA01 compressed, see below how to deal with these.

Images

WPF *.bin and most *.dtx (0x123DDA00 and 0x123DDA01)

Some DTX don't load properly in GT, some work fine using the existing WPF Bin viewer. Needs more research.

  • 4 bytes = header (00 is uncompressed, 01 is compressed)
  • 4 bytes = uncompressed size
  • 4 bytes = compressed size (0xFFFFFFFF if not compressed)
  • 4 bytes = 0x00000000
  • Data of length (use compressed if you can, otherwise uncompressed size)

Sometimes a *.bin file won't have that header, in that case just ignore that first 16 bytes and treat as uncompressed.

  • 16 bytes = 0x00
  • 2 bytes = height
  • 2 bytes = width
  • 2 bytes = height again (use to verify a *.bin is a renderable image)
  • 2 bytes = width again (use to verify)
  • 2 bytes = ??? I've seen 1/3/4 so far.
  • 2 bytes = Palette entries (x2 of this is the data length)
  • 4 bytes = ??? 32 (why?)
  • Palette data
  • Bitmap data

*.anm (Anime/Animation)

Todo, MTCs are overlay on top to add animated colour.

MTC

MTCs are a low resolution colour animation overlay. Each frame appears to always be 17 x 33 pixels (however the first frame includes the header so it's missing the first row). Colour format is RGBA1555. Slightly different to the Last Window MTC format.

  • 4 bytes = Number of frames
  • 2 bytes = Width to upscale to (not the width of the MTC)
  • 2 bytes = Height to upscale to (not the height of the MTC)
  • 2 bytes = unknown
  • 2 bytes = unknown
  • 18 bytes = zeroes

I have a feeling those last two unknowns describe how the animation loops, or the speed.

*.anm Frame

This should work nearly 90% for most ANMs except for Br_bracelet_.anm. Colour palette works. However only the first frame will display correctly, would make sense that all the following frames would be based off the previous but I'll work on that later.

class FRM {
 
	public int Width, Height;
	private byte[] rawbytes;
	public Bitmap bitmap;
 
	public FRM(GTFS fs) {
		Width = 192;
		Height = 256;
 
		//--
 
		bool flip = false;
		int zero = GT.ReadInt32(fs, 4, flip);
		int len = GT.ReadInt32(fs, 4, flip);
		int paletteLen = GT.ReadInt32(fs, 4, flip);
		int next = GT.ReadInt32(fs, 4, flip);
 
		long offsetPalette = 16 + len;
 
		List<byte> listBytes = new List<byte>();
 
		while (fs.Position - 16 < len) {
			byte first = GT.ReadByte(fs);
 
			if (first < 0x40) { //0x7F for Br_bracelet_.anm
				for (int i = 0; i < first; i++) {
					byte b = GT.ReadByte(fs);
					listBytes.Add(b);
				}
			} else if (first == 0x40) {
				throw new Exception();
			} else if (first < 0x80) {
				int repeatlen = first - 0x40;
				byte second = GT.ReadByte(fs);
				for (int i = 0; i < repeatlen; i++) {
					listBytes.Add(second);
				}
			} else if (first == 0x80) {
				throw new Exception();
			} else {
				int gap = first - 0x80;
				for (int i = 0; i < gap; i++) {
					listBytes.Add(0xFF);
				}
			}
 
		}
 
		rawbytes = listBytes.ToArray();
 
		if (rawbytes.Length != 49152)
			Console.WriteLine();
 
		File.WriteAllBytes("GT-KH-FRM.bin", rawbytes);
 
		bitmap = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
		int k = 0;
		for (int x = 0; x < Width; x++) {
			for (int y = 0; y < Height; y++) {
				int b = 255 - rawbytes[k++];
 
				if(b > 32) { //Unsure about this, just placeholder for now
					Color c = Color.FromArgb(b, b, b);
					bitmap.SetPixel(x, Height - 1 - y, c);
				} else {
					fs.Position = offsetPalette + (62 - b*2);
 
					byte left = (byte)fs.ReadByte();
					byte right = (byte)fs.ReadByte();
 
					ushort palette = (ushort)(right | left << 8);
 
					Color c = Palette2Color(palette);
					bitmap.SetPixel(x, Height - 1 - y, c);
				}
			}
		}
	}
 
	private Color Palette2Color(ushort palette) {
		ushort hex = Tools.SwapBytes(palette);
		int R = (hex & 0x1F) * 8;
		int G = (hex >> 5 & 0x1F) * 8;
		int B = (hex >> 10 & 0x1F) * 8;
 
		return Color.FromArgb(R, G, B);
	}
}

Decompressing 12 3D DA 01

The following is a backup of my best attempt at a 123DDA01 decompressor. From my late night testing, it should work flawlessly for *.txt and image *.bin files up to 126 KB (which is the largest 123DDA01 file in Hotel Dusk). However there have been some compressed DTX files that failed to render (I didn't check the decompression output) - it's probably just a format I've not already seen.

byte[] header = GT.ReadBytes(fs, 4, false);
int sizeun = GT.ReadInt32(fs, 4, false);
int sizeco = GT.ReadInt32(fs, 4, false);
int zero = GT.ReadInt32(fs, 4, false);
 
byte[] uncompressed = new byte[sizeun];
int pos = 0;
 
while (fs.Position < sizeco + 16) {
	byte input = GT.ReadByte(fs);
	BitArray bits = new BitArray(new byte[] { input });
 
	for (int i = 0; i < 8; i++) {
		if (fs.Position >= sizeco + 16)
			break;
 
		if (bits[i]) {
			byte b = GT.ReadByte(fs);
			uncompressed[pos] = b;
			pos++;
		} else {
                        byte[] bOff = GT.ReadBytes(fs, 2, false);
                        int len = 4 + GT.ReadByte(fs);
                        int offset = (bOff[1] << 8) + bOff[0];
 
                        int posHi = hibit(pos - 259);
                        int offHi = hibit(offset);
                        if (pos < 0x10000) {
                            int signed = (short)offset;
                            if(signed < 0 && signed + 259 >= 0)
                                offset = signed;
                        } else if (posHi >= 0x20000 && offHi < posHi) {
                            if(offset + posHi < pos)
                                offset += posHi;
                            else if (offset + 0x10000 < pos)
                                offset += 0x10000;
                        } else if (posHi >= 0x10000 && offHi < posHi && offset + posHi < pos)
                            offset += posHi;
                        offset += 259;
 
                        if (offset < 0 || offset + len >= uncompressed.Length) {
                            //Console.WriteLine("Shouldn't happen: " + GT.ByteArrayToString(bOff, " ") + " (" + offset + ") at pos: " + pos);
                            for (int x = 0; x < len; x++)
                                uncompressed[pos + x] = 0;
                        } else {
                            for (int x = 0; x < len; x++)
                                uncompressed[pos + x] = uncompressed[offset + x];
                        }
 
			pos += len;
		}
	}
}

Unknown Bin "LRIM" 4C 52 49 4D

Unsure what these are yet.

  • 4 bytes = Magic header
  • 4 bytes = Number of things
  • 4 bytes = 16
  • 4 bytes = Unknown (seems related to “number of things”)
  • For number of things, 4 bytes = offset to thing
  • For number of things, 4 bytes = size of thing

LRIM Things

At a glance, not seeing an obvious structure or colours, probably compressed.

Current Progress and Things To Do

GT/GameTools is now able to render most ANMs continuously looping with MTC overlays. This is now also used to display sprite animations from Yesterday.

ANM rendering isn't perfect, such as artifacts on An_TanaMove_.anm (however I must say it's pretty damn close). Also Br_bracelet_.anm isn't supported at all anymore, perhaps there's a flag I missed.

ANMs should be able to loop as they do on the Nintendo DS (i.e. the loop doesn't go back to the start). Controls for the speed and frame by frame would be nice.

MTCs don't seem to be aligned properly. There might be something in their header that I ignored that fixed up the alignment before/after scaling the MTC up to the same resolution as the ANM.

MTCs don't keep blacks in the ANM dark, need to blend them together better.

The only toggle for MTCs is if they exist, need a control for it.

Can't yet export ANM/MTC to a more useful format, perhaps APNG?

hoteldusk.txt · Last modified: 2023/03/22 11:24 by jas2o

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki