-
Notifications
You must be signed in to change notification settings - Fork 6
IMY
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.
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.
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.
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.
Case 1:
- The info byte holds two informations:
- Amout of shorts to copy
-
=
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
-
=
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_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
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