ys2-intro/loader/tools/wcrush/wca/main.cpp
2025-11-13 19:07:39 +03:00

210 lines
5.8 KiB
C++

// this pretty much does the same as the taboo depacker on the C64-end
// (aka decrush.tas), except it doesn't run on a C64 but a PC and it's
// sole purpose is to walk through a raw crushed binary and tell you
// how the depacker would interprete the information stored in it.
// or in other words, it's just a debugging aid for the levelcrusher I wrote.
// latest changes: added some sanity checks to keep operations within buffer limits (hopefully)
// Note: does currently only support files crushed with max compression ("speed"=6)
// #include <SDKDDKVer.h> //VS 2013 stuff, remove or change to whatever your dev-tools require
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define maxsize 0xffff
const uint8_t packbits[56] = {
2, 2, 1, 1, 2, 2, 1, 1, // worst compression (speed 0)
3, 1, 2, 2, 3, 1, 2, 2,
3, 2, 2, 2, 3, 2, 2, 2,
4, 2, 2, 2, 4, 2, 2, 2,
4, 2, 2, 3, 4, 2, 2, 2,
4, 2, 3, 3, 4, 2, 2, 2,
4, 3, 3, 3, 4, 2, 2, 2 }; // best compression (speed 6)
uint8_t inbuf[maxsize+1];
uint8_t outbuf[maxsize+1]; // dummy output, only there to be able to show repeat byte sequences
uint32_t srcidx, tgtidx, refidx, offset, ctrlbyte;
int32_t chunkofs, len, chunks, chunkwidth, bitnum, columns;
uint64_t fsize; // just in case someone stupid (aka me) tries to run a TB-sized file through the analyzer
int speed = 6;
// show the data bytes currently handled by depacker in somewhat formatted fashion:
void movedata(uint8_t buffer1[], uint8_t buffer2[], uint32_t& idx1, uint32_t& idx2, int32_t len)
{
columns = 16;
printf("%04x: ", idx2);
while (0 < len--) // to filter out negative lens as well, just in case...
{
printf(" %02x", buffer1[idx1]);
buffer2[idx2++] = buffer1[idx1++];
if (--columns == 8)
printf(" ");
if (!columns)
{
printf("\n");
if (len)
printf("%04x: ", idx2);
columns = 16;
}
}
printf("\n");
if (columns != 16)
printf("\n");
}
// extract control bit from input buffer, return 1 if set, else 0.
// also show value [and address] of current control byte
uint16_t getbit()
{
if (--bitnum <0)
{
ctrlbyte = inbuf[srcidx];
bitnum = 7;
printf("(%04X:%02X)", srcidx,ctrlbyte);
if (srcidx < fsize) // sanity check
srcidx++;
}
return (ctrlbyte &(1<<bitnum))>>bitnum;
}
int main(int argc, char* argv[])
{
printf("\ncrushfile analyzer v0.1b by manic mailman\n\n");
// some help for the end user if input parameter is missing:
if (argc < 2)
{
printf("usage: wca <raw crushed binary> [speed 0..6]\n");
printf("example: wca crushed 6 > log.txt\n");
printf("if no speed is given speed 6 is assumed.\n");
return 1;
}
// if filename is provided try to open file, if possible:
printf("trying to analyze %s...\n\n", argv[1]);
FILE * infile;
infile = fopen(argv[1], "rb");
if (infile == NULL)
{
printf("can't open file!\n");
exit(EXIT_FAILURE);
}
fseek(infile, 0, SEEK_END);
fsize = ftell(infile);
rewind(infile);
// more error msgs if file is too large or too short to be a valid crushed binary
if (fsize > maxsize) // if file too large
{
printf("file too large!\n");
exit(EXIT_FAILURE);
}
if (fsize < 4)
{
printf("nothing to analyze!\n");
exit(EXIT_FAILURE);
}
if (argv[2] != NULL)
speed = atoi(argv[2]);
speed = speed * 8;
// could be valid, so here we go:
ssize_t bytes_read = fread(inbuf, 1, fsize&maxsize, infile);
(void) bytes_read;
fclose(infile);
tgtidx = inbuf[0] + 0x100 * inbuf[1];
printf("start address: %d/%04X, size: %d bytes.\n\n", tgtidx, tgtidx, (int) fsize);
srcidx = 2; // skip start address
bitnum = 0; // to read 1st control byte right away
do {
//if end of either input data or output buffer reached before reading endmark:
if (srcidx >= fsize || tgtidx >= maxsize)
{
printf("no valid endmark found, aborting.\n");
exit(EXIT_FAILURE);
}
// copy uncompressed bytes if control bit == 0, else repeat bytes:
if (!getbit())
{
len = 1;
while (!getbit() && len < maxsize) // get length of run from stopbit,bit... sequence
len = len * 2 + getbit();
printf(" COPY %04X uncompressed bytes from %04X to %04X\n", len, srcidx, tgtidx);
if (tgtidx + len > maxsize) // sanity check
len = maxsize - tgtidx;
if (srcidx + len > fsize) // sanity check
len = fsize&maxsize - srcidx;
movedata(inbuf, outbuf, srcidx, tgtidx, len);
}
// repeat a sequence of bytes that has already been decompressed:
len = 1;
if (getbit()) // 3+ byte sequence
{
chunkofs = speed;
while (!getbit() && len < 0x100) // get len from stopbit,bit...sequence
len = len * 2 + getbit();
len += 2;
}
else // 2+ byte sequence
{
chunkofs = speed + 4;
len++;
}
// if reference length is valid repeat 2 or more bytes:
if (len < 0x100)
{
offset = 0;
chunks = 2 * getbit() + getbit(); // get # of bitchunks (2 bits)
do {
chunkwidth = packbits[chunkofs + chunks]; // bitwidth of current chunk
while (chunkwidth--)
offset = 2 * offset + getbit(); //shift in offset bits of current chunk
if (chunks) // +1 if more bitchunks remaining
offset++;
} while (chunks--);
refidx = tgtidx - (offset + len);
printf(" REPEAT %02X bytes from %04X at %04X (offset %d)\n", len, refidx, tgtidx, offset);
if (tgtidx + len > maxsize) // sanity check
len = maxsize - tgtidx;
movedata(outbuf, outbuf, refidx, tgtidx, len);
}
// else depacker has reached end of input data (or crapped out somewhere before)
else
{
printf(" endmark found (%d/%04X)\n", len-2, len-2);
if (srcidx < fsize)
printf("warning: endmark is not at end of input data!\n");
}
} while (len < 0x100);
return EXIT_SUCCESS;
}