//BASED IN THE CODE
//https://github.com/FeiZhaixiage/yytexUnpacker
//
//BUILD 19-07-2025
//REMOVED BZIP2 FOR COMPRESSION , NOW THE TEXCTURES CAN BE LOADED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#include <bzlib.h>
#define QOI_INDEX 0x00
#define QOI_RUN_8 0x40
#define QOI_RUN_16 0x60
#define QOI_DIFF_8 0x80
#define QOI_DIFF_16 0xc0
#define QOI_DIFF_24 0xe0
#define QOI_COLOR 0xf0
#define QOI_MASK_2 0xc0
#define QOI_MASK_3 0xe0
#define QOI_MASK_4 0xf0
typedef struct {
unsigned char *data;
int width;
int height;
int channels;
} Image;
// Read entire file into memory
unsigned char* read_file(const char *filename, size_t *size) {
FILE *file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Cannot open file: %s\n", filename);
return NULL;
}
fseek(file, 0, SEEK_END);
*size = ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char *buffer = (unsigned char*)malloc(*size);
if (!buffer) {
fclose(file);
return NULL;
}
fread(buffer, 1, *size, file);
fclose(file);
return buffer;
}
// Write data to file
int write_file(const char *filename, unsigned char *data, size_t data_len) {
FILE *file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Cannot open output file: %s\n", filename);
return 0;
}
size_t written = fwrite(data, 1, data_len, file);
fclose(file);
if (written != data_len) {
fprintf(stderr, "Failed to write all data to file: %s\n", filename);
return 0;
}
return 1;
}
// Decompress BZip2 data, skipping 12-byte header
unsigned char* decompress_bzip2(const char *filename, size_t *out_len) {
size_t file_size;
unsigned char *file_data = read_file(filename, &file_size);
if (!file_data || file_size <= 12) {
fprintf(stderr, "File too short or cannot be read: %s\n", filename);
free(file_data);
return NULL;
}
unsigned char *compressed_data = file_data + 12;
size_t compressed_len = file_size - 12;
bz_stream strm = {0};
if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) {
fprintf(stderr, "BZip2 decompression initialization failed\n");
free(file_data);
return NULL;
}
unsigned char *decompressed = (unsigned char*)malloc(compressed_len * 2);
size_t decompressed_size = compressed_len * 2;
size_t decompressed_pos = 0;
strm.next_in = (char*)compressed_data;
strm.avail_in = compressed_len;
strm.next_out = (char*)decompressed;
strm.avail_out = decompressed_size;
while (1) {
int bz_result = BZ2_bzDecompress(&strm);
if (bz_result == BZ_STREAM_END) {
decompressed_pos = decompressed_size - strm.avail_out;
break;
}
if (bz_result != BZ_OK) {
fprintf(stderr, "BZip2 decompression error\n");
BZ2_bzDecompressEnd(&strm);
free(decompressed);
free(file_data);
return NULL;
}
if (strm.avail_out == 0) {
decompressed_size *= 2;
decompressed = (unsigned char*)realloc(decompressed, decompressed_size);
strm.next_out = (char*)(decompressed + decompressed_pos);
strm.avail_out = decompressed_size - decompressed_pos;
}
}
*out_len = decompressed_pos;
BZ2_bzDecompressEnd(&strm);
free(file_data);
return decompressed;
}
// Load PNG using libpng
Image* load_png(const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Cannot open PNG file: %s\n", filename);
return NULL;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
fclose(file);
return NULL;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, NULL, NULL);
fclose(file);
return NULL;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, NULL);
fclose(file);
return NULL;
}
png_init_io(png, file);
png_read_info(png, info);
int width = png_get_image_width(png, info);
int height = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
if (bit_depth == 16) png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png);
}
if (!(color_type & PNG_COLOR_MASK_ALPHA)) png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
png_set_bgr(png); // Match C# ARGB order (BGRA in memory)
png_read_update_info(png, info);
Image *img = (Image*)malloc(sizeof(Image));
img->width = width;
img->height = height;
img->channels = 4;
img->data = (unsigned char*)malloc(width * height * 4);
if (!img->data) {
png_destroy_read_struct(&png, &info, NULL);
fclose(file);
free(img);
return NULL;
}
png_bytep *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
for (int y = 0; y < height; y++) {
row_pointers[y] = img->data + y * width * 4;
}
png_read_image(png, row_pointers);
png_read_end(png, NULL);
png_destroy_read_struct(&png, &info, NULL);
free(row_pointers);
fclose(file);
return img;
}
// Save PNG using libpng
int save_png(Image *img, const char *filename) {
FILE *file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Cannot open output file: %s\n", filename);
return 0;
}
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
fclose(file);
return 0;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, NULL);
fclose(file);
return 0;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(file);
return 0;
}
png_init_io(png, file);
png_set_IHDR(png, info, img->width, img->height, 8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_bgr(png); // Match input format
png_write_info(png, info);
png_bytep *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * img->height);
for (int y = 0; y < img->height; y++) {
row_pointers[y] = img->data + y * img->width * 4;
}
png_write_image(png, row_pointers);
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
free(row_pointers);
fclose(file);
return 1;
}
// QOI Decoder
Image* qoi_get_image_from_data(unsigned char *data, size_t data_len) {
if (data_len < 12) {
fprintf(stderr, "Data too short: %zu bytes\n", data_len);
return NULL;
}
fprintf(stderr, "Magic bytes: %c%c%c%c (0x%02x 0x%02x 0x%02x 0x%02x)\n",
data[0], data[1], data[2], data[3],
data[0], data[1], data[2], data[3]);
if (data[0] != 'f' || data[1] != 'i' || data[2] != 'o' || data[3] != 'q') {
fprintf(stderr, "Invalid QOIF image magic\n");
return NULL;
}
int width = data[4] | (data[5] <<

;
int height = data[6] | (data[7] <<

;
int length = data[8] | (data[9] <<

| (data[10] << 16) | (data[11] << 24);
if (data_len < (size_t)length + 12) {
fprintf(stderr, "Invalid QOI data length\n");
return NULL;
}
Image *img = (Image*)malloc(sizeof(Image));
img->width = width;
img->height = height;
img->channels = 4;
img->data = (unsigned char*)malloc(width * height * 4);
if (!img->data) {
free(img);
return NULL;
}
unsigned char *bmp_ptr = img->data;
unsigned char *bmp_end = bmp_ptr + (width * height * 4);
size_t pos = 12;
int run = 0;
unsigned char r = 0, g = 0, b = 0, a = 255;
unsigned char index[64 * 4] = {0};
while (bmp_ptr < bmp_end && pos < data_len) {
if (run > 0) {
run--;
} else {
int b1 = data[pos++];
if ((b1 & QOI_MASK_2) == QOI_INDEX) {
int index_pos = (b1 ^ QOI_INDEX) << 2;
r = index[index_pos];
g = index[index_pos + 1];
b = index[index_pos + 2];
a = index[index_pos + 3];
} else if ((b1 & QOI_MASK_3) == QOI_RUN_8) {
run = b1 & 0x1f;
} else if ((b1 & QOI_MASK_3) == QOI_RUN_16) {
int b2 = data[pos++];
run = (((b1 & 0x1f) <<

| b2) + 32;
} else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) {
r += (unsigned char)(((b1 & 48) << 26 >> 30) & 0xff);
g += (unsigned char)(((b1 & 12) << 28 >> 22 >>

& 0xff);
b += (unsigned char)(((b1 & 3) << 30 >> 14 >> 16) & 0xff);
} else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) {
int b2 = data[pos++];
int merged = b1 << 8 | b2;
r += (unsigned char)(((merged & 7936) << 19 >> 27) & 0xff);
g += (unsigned char)(((merged & 240) << 24 >> 20 >>

& 0xff);
b += (unsigned char)(((merged & 15) << 28 >> 12 >> 16) & 0xff);
} else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) {
int b2 = data[pos++];
int b3 = data[pos++];
int merged = b1 << 16 | b2 << 8 | b3;
r += (unsigned char)(((merged & 1015808) << 12 >> 27) & 0xff);
g += (unsigned char)(((merged & 31744) << 17 >> 19 >>

& 0xff);
b += (unsigned char)(((merged & 992) << 22 >> 11 >> 16) & 0xff);
a += (unsigned char)(((merged & 31) << 27 >> 3 >> 24) & 0xff);
} else if ((b1 & QOI_MASK_4) == QOI_COLOR) {
if (b1 &

r = data[pos++];
if (b1 & 4) g = data[pos++];
if (b1 & 2) b = data[pos++];
if (b1 & 1) a = data[pos++];
}
int index_pos = ((r ^ g ^ b ^ a) & 63) << 2;
index[index_pos] = r;
index[index_pos + 1] = g;
index[index_pos + 2] = b;
index[index_pos + 3] = a;
}
*bmp_ptr++ = b;
*bmp_ptr++ = g;
*bmp_ptr++ = r;
*bmp_ptr++ = a;
}
return img;
}
// QOI Encoder
unsigned char* qoi_get_array_from_image(Image *img, size_t *out_len) {
size_t max_size = (img->width * img->height * 5) + 12;
unsigned char *res = (unsigned char*)malloc(max_size);
if (!res) return NULL;
res[0] = 'f';
res[1] = 'i';
res[2] = 'o';
res[3] = 'q';
res[4] = (unsigned char)(img->width & 0xff);
res[5] = (unsigned char)((img->width >>

& 0xff);
res[6] = (unsigned char)(img->height & 0xff);
res[7] = (unsigned char)((img->height >>

& 0xff);
size_t res_pos = 12;
unsigned char r = 0, g = 0, b = 0, a = 255;
int run = 0;
int v = 0, v_prev = 0xff;
int index[64] = {0};
unsigned char *bmp_ptr = img->data;
unsigned char *bmp_end = bmp_ptr + (img->width * img->height * 4);
while (bmp_ptr < bmp_end) {
b = *bmp_ptr++;
g = *bmp_ptr++;
r = *bmp_ptr++;
a = *bmp_ptr++;
v = (r << 24) | (g << 16) | (b <<

| a;
if (v == v_prev) {
run++;
if (run == 0x2020) {
if (run < 33) {
run -= 1;
res[res_pos++] = (unsigned char)(QOI_RUN_8 | run);
} else {
run -= 33;
res[res_pos++] = (unsigned char)(QOI_RUN_16 | (run >>

);
res[res_pos++] = (unsigned char)run;
}
run = 0;
}
continue;
}
if (run > 0) {
if (run < 33) {
run -= 1;
res[res_pos++] = (unsigned char)(QOI_RUN_8 | run);
} else {
run -= 33;
res[res_pos++] = (unsigned char)(QOI_RUN_16 | (run >>

);
res[res_pos++] = (unsigned char)run;
}
run = 0;
}
int index_pos = (r ^ g ^ b ^ a) & 63;
if (index[index_pos] == v) {
res[res_pos++] = (unsigned char)(QOI_INDEX | index_pos);
} else {
index[index_pos] = v;
int vr = r - ((v_prev >> 24) & 0xff);
int vg = g - ((v_prev >> 16) & 0xff);
int vb = b - ((v_prev >>

& 0xff);
int va = a - (v_prev & 0xff);
if (vr > -17 && vr < 16 &&
vg > -17 && vg < 16 &&
vb > -17 && vb < 16 &&
va > -17 && va < 16) {
if (va == 0 &&
vr > -3 && vr < 2 &&
vg > -3 && vg < 2 &&
vb > -3 && vb < 2) {
res[res_pos++] = (unsigned char)(QOI_DIFF_8 | ((vr & 3) << 4) | ((vg & 3) << 2) | (vb & 3));
} else if (va == 0 &&
vg > -9 && vg < 8 &&
vb > -9 && vb <

{
res[res_pos++] = (unsigned char)(QOI_DIFF_16 | (vr & 31));
res[res_pos++] = (unsigned char)(((vg & 15) << 4) | (vb & 15));
} else {
res[res_pos++] = (unsigned char)(QOI_DIFF_24 | ((vr >> 1) & 15));
res[res_pos++] = (unsigned char)(((vr << 7) & 128) | ((vg << 2) & 124) | ((vb >> 3) & 3));
res[res_pos++] = (unsigned char)(((vb << 5) & 224) | (va & 31));
}
} else {
res[res_pos++] = (unsigned char)(QOI_COLOR | (vr != 0 ? 8 : 0) | (vg != 0 ? 4 : 0) | (vb != 0 ? 2 : 0) | (va != 0 ? 1 : 0));
if (vr != 0) res[res_pos++] = r;
if (vg != 0) res[res_pos++] = g;
if (vb != 0) res[res_pos++] = b;
if (va != 0) res[res_pos++] = a;
}
}
v_prev = v;
}
if (run > 0) {
if (run < 33) {
run -= 1;
res[res_pos++] = (unsigned char)(QOI_RUN_8 | run);
} else {
run -= 33;
res[res_pos++] = (unsigned char)(QOI_RUN_16 | (run >>

);
res[res_pos++] = (unsigned char)run;
}
}
*out_len = res_pos;
unsigned char *output = (unsigned char*)malloc(res_pos);
memcpy(output, res, res_pos);
free(res);
return output;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage:\n");
printf(" To unpack .yytex → .png: %s unpack <file.yytex>\n", argv[0]);
printf(" To pack .png → .yytex: %s pack <file.png>\n", argv[0]);
return 1;
}
const char *mode = argv[1];
const char *input_filepath = argv[2];
char output_filepath[256];
snprintf(output_filepath, sizeof(output_filepath), "%s", input_filepath);
FILE *test_file = fopen(input_filepath, "rb");
if (!test_file) {
fprintf(stderr, "File does not exist: %s\n", input_filepath);
return 1;
}
fclose(test_file);
if (strcmp(mode, "unpack") == 0) {
output_filepath[strlen(output_filepath) - 6] = '\0';
strcat(output_filepath, ".png");
size_t decompressed_len;
unsigned char *decompressed_data = decompress_bzip2(input_filepath, &decompressed_len);
if (!decompressed_data) {
fprintf(stderr, "Decompression failed\n");
return 1;
}
Image *img = qoi_get_image_from_data(decompressed_data, decompressed_len);
free(decompressed_data);
if (!img) {
fprintf(stderr, "QOI decoding failed\n");
return 1;
}
if (!save_png(img, output_filepath)) {
fprintf(stderr, "Failed to save PNG\n");
free(img->data);
free(img);
return 1;
}
free(img->data);
free(img);
printf("Unpacked and saved PNG to: %s\n", output_filepath);
} else if (strcmp(mode, "pack") == 0) {
output_filepath[strlen(output_filepath) - 4] = '\0';
strcat(output_filepath, ".yytex");
Image *img = load_png(input_filepath);
if (!img) {
fprintf(stderr, "Failed to load PNG: %s\n", input_filepath);
return 1;
}
size_t qoi_len;
unsigned char *qoi_data = qoi_get_array_from_image(img, &qoi_len);
free(img->data);
free(img);
if (!qoi_data) {
fprintf(stderr, "QOI encoding failed\n");
return 1;
}
if (!write_file(output_filepath, qoi_data, qoi_len)) {
fprintf(stderr, "Failed to save .yytex\n");
free(qoi_data);
return 1;
}
free(qoi_data);
printf("Packed and saved .yytex to: %s\n", output_filepath);
} else {
printf("Invalid mode. Use 'pack' or 'unpack'.\n");
return 1;
}
return 0;
}