Skip to content
ProgSys edited this page Sep 19, 2019 · 16 revisions

IMY is a lossles compressed file or chunk of a file. Usually entire archives are compressed by splitting it into multiple fixed size IMY files. It is based on [Run-length encoding] (https://en.wikipedia.org/wiki/Run-length_encoding) but it references different parts of the file.

A IMY file has three parts: the header, info bytes and the data.

Header

Type Size Description
String 4 byte always IMY (0x00594D49)
Unknown 2 byte
Unknown 2 byte
uShort 2 byte offset, used to create look up table
uChar 1 byte compression Type
Unknown 1 byte
Unknown 2 byte
Unknown 2 byte
Unknown 4 byte always 0
Unknown 4 byte always 0
Unknown 4 byte always 0
Unknown 4 byte always 0
uShort 2 byte number of info bytes

Curretly only compression type 8 is known.

Decompression

To decompress the file you read every info byte one by one. Every info byte codes a instruction witch needs to be performed on the stored data or the already compressed data.

Cases

There are in total 3 cases/instruction based on a read info byte:

  • if (info byte & 0xF0), is it bigger or equal 16
  • if ( (info byte & 0x80) && (info byte & 0x40) ), is it bigger or equal 192
    • Case 1: copy at least one short from the already uncompressed data by looking back n shorts
  • else
    • Case 2: copy one short from the data by looking back n shorts
  • else
  • Case 3: Copy at least one short from data and move the data pointer the read amount

Note that the data pointer, witch is initialized with end of header + number of info bytes, is only moved in case 3.

Information hold in info byte

Case 1:

  • The info byte holds two informations:
  • Amout of shorts to copy
    • shortstocopy = shorts_to_copy = (*info byte* & 0x0F) + 1;
  • The index for the look up table, witch hold the number of shorts you need to look back
    • indexbits = index = (*info byte* & 0x30) >> 4;
  • Look up table, has four entries:
Index Value
0 2
1 offset (from header)
2 offset + 2
3 offset - 2

Case 2:

  • The info byte just holds the amout of shorts you need to look back to copy the one short:
    • lookback = lookback_bytes = (*info byte*- 16)*2 + 2

Case 3:

  • The info byte just holds the amout of shorts you need to copy:
    • copy_bytes = (*info byte*+1)*2

Implementation

One possible simple implementation in c++:

struct imyHeader{
	unsigned int magic_number; // 4

	unsigned short unknown02; // 2
	unsigned short unknown03; // 2 -> 8
	unsigned short streamOffset; //was width // 2
	unsigned char compressionType; // 1
	unsigned char unknown06; // 1
	unsigned short unknown07; //was height // 2
	unsigned short unknown08; //was paletteSize // 2 -> 16
	unsigned int zero0; //padding // 4
	unsigned int zero1; //padding // 4 -> 24
	unsigned int zero2; //padding // 4
	unsigned int zero3; //padding // 4 -> 32
	unsigned short number_of_info_bytes;// 2 -> 34
} __attribute__((packed, aligned(1)));

/*!
 * @param instream, is a wrapper class witch just can read
 * @param outstream, is a wrapper class witch can read and write
 */
bool decompressIMYSimple(PG::STREAM::In* instream, PG::STREAM::InOut* outstream){
	const unsigned int startOffset = outstream->pos();

	//read the header
	imyHeader header;
	instream->read((char*) &header, sizeof(imyHeader));

	if(header.magic_number != 0x00594D49){
		std::cerr << "IMY file magic number is wrong!" << std::endl;
		return FAILURE;
	}

	if( (header.compressionType >> 4) == 1){

		const unsigned int decompressedFileSize = header.streamOffset * header.unknown07; //not sure

		unsigned int infoBytesOffset = instream->pos();
		const unsigned int infoBytesOffsetEnd = infoBytesOffset + header.number_of_info_bytes;
		unsigned int dataOffset = infoBytesOffsetEnd;

		//create look up table
		PG::UTIL::Array<unsigned int, 4> lookUpTable;
		lookUpTable[0] = 2; // go back one short
		lookUpTable[1] = header.streamOffset;
		lookUpTable[2] = lookUpTable[1] + 2;
		lookUpTable[3] = lookUpTable[1] - 2;

		while( (outstream->pos()-startOffset) < decompressedFileSize && infoBytesOffset < infoBytesOffsetEnd){

			// read every info byte
			instream->seek(infoBytesOffset);
			unsigned char infoByte = instream->readChar();
			infoBytesOffset++;

			if( infoByte & 0xF0 ){
				if( (infoByte & 0x80) && (infoByte & 0x40)){
					//copy shorts from the already uncompressed stream by looking back
					const int index = (infoByte & 0x30) >> 4; // the value can only be 0-3
					const int shorts_to_copy = (infoByte & 0x0F) + 1;

					for (int i = 0; i < shorts_to_copy; i++){
						//read
						const unsigned int currentEnd = outstream->pos();
						outstream->seek(currentEnd - lookUpTable[index]);
						const short s = outstream->readShort();
						outstream->seek(currentEnd);
						outstream->writeShort(s);
					}

				}else{
					//copy a short from the compressed stream by looking back to a short
					const unsigned int lookback_bytes = (infoByte - 16)*2 + 2;
					instream->seek(dataOffset-lookback_bytes);
					outstream->writeShort(instream->readShort());
				}
			}else{
				// just copy shorts (2 byte)
				//you always copy at least one short
				const unsigned int copy_bytes = (infoByte+1)*2;
				char c[copy_bytes];
				instream->seek(dataOffset);
				instream->read(&c[0], copy_bytes);
				outstream->write(&c[0], copy_bytes);
				dataOffset += copy_bytes;
			}
		}
	}else{
		std::cerr << "IMY compression type '"<<header.compressionType<<"' is not supported!" << std::endl;
		return FAILURE;
	}

	return SUCCESS;
}

Note: Another used compression is YKCMP_V1 https://github.com/iltrof/ykcmp

Clone this wiki locally