Files
kq4-decompile/sci_pic_render.c
2026-02-20 14:00:40 -08:00

960 lines
32 KiB
C

/*
* SCI Picture Resource Renderer
*
* Renders PIC resources from Sierra SCI games (SCI0 format) to PNG images.
* Based on rendering algorithms from SCICompanion by Philip Fortier.
*
* Usage: sci_pic_render <room_number> <resource_directory> [output_directory]
*
* Compile: gcc -o sci_pic_render sci_pic_render.c -lpng -lz -lm -O2
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <png.h>
#include <zlib.h>
#define WIDTH 320
#define HEIGHT 190
#define MAX_PATH 4096
/* PIC opcodes */
#define PIC_OP_SET_COLOR 0xf0
#define PIC_OP_DISABLE_VISUAL 0xf1
#define PIC_OP_SET_PRIORITY 0xf2
#define PIC_OP_DISABLE_PRIORITY 0xf3
#define PIC_OP_RELATIVE_PATTERNS 0xf4
#define PIC_OP_RELATIVE_MEDIUM_LINES 0xf5
#define PIC_OP_RELATIVE_LONG_LINES 0xf6
#define PIC_OP_RELATIVE_SHORT_LINES 0xf7
#define PIC_OP_FILL 0xf8
#define PIC_OP_SET_PATTERN 0xf9
#define PIC_OP_ABSOLUTE_PATTERNS 0xfa
#define PIC_OP_SET_CONTROL 0xfb
#define PIC_OP_DISABLE_CONTROL 0xfc
#define PIC_OP_RELATIVE_MEDIUM_PATTERNS 0xfd
#define PIC_OP_OPX 0xfe
#define PIC_OP_END 0xff
#define PATTERN_FLAG_RECTANGLE 0x10
#define PATTERN_FLAG_USE_PATTERN 0x20
#define PALETTE_SIZE 40
/* Circle patterns for pen tool */
static const uint8_t circles[][30] = {
{0x80},
{0x4e, 0x40},
{0x73, 0xef, 0xbe, 0x70},
{0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x00},
{0x1c, 0x1f, 0xcf, 0xfb, 0xfe, 0xff, 0xbf, 0xef, 0xf9, 0xfc, 0x1c},
{0x0e, 0x03, 0xf8, 0x7f, 0xc7, 0xfc, 0xff, 0xef, 0xfe, 0xff, 0xe7,
0xfc, 0x7f, 0xc3, 0xf8, 0x1f, 0x00},
{0x0f, 0x80, 0xff, 0x87, 0xff, 0x1f, 0xfc, 0xff, 0xfb, 0xff, 0xef,
0xff, 0xbf, 0xfe, 0xff, 0xf9, 0xff, 0xc7, 0xff, 0x0f, 0xf8, 0x0f, 0x80},
{0x07, 0xc0, 0x1f, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0xff,
0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f,
0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x07, 0xc0}
};
static const uint8_t junq[32] = {
0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2, 0x82, 0x09, 0x0a, 0x22,
0x12, 0x10, 0x42, 0x14, 0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10,
0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04
};
static const uint8_t junqindex[128] = {
0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48,
0x60, 0xbd, 0x89, 0x05, 0x0a, 0xf4, 0x7d, 0x7d,
0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf,
0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1,
0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce,
0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed,
0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6,
0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51,
0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7,
0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf,
0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0,
0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49,
0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2,
0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3,
0x06, 0x6f, 0xc6, 0x4a, 0xa4, 0x75, 0x97, 0xe1
};
static const uint8_t default_palette_init[PALETTE_SIZE] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x88,
0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88,
0x88, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
0x08, 0x91, 0x2a, 0x3b, 0x4c, 0x5d, 0x6e, 0x88
};
typedef struct {
uint8_t color1;
uint8_t color2;
} EGACOLOR;
typedef struct {
uint8_t visual[HEIGHT][WIDTH];
uint8_t priority[HEIGHT][WIDTH];
uint8_t control[HEIGHT][WIDTH];
uint8_t aux[HEIGHT][WIDTH];
EGACOLOR palettes[4][PALETTE_SIZE];
uint8_t locked[PALETTE_SIZE];
uint8_t palette_number;
uint8_t palette_offset;
EGACOLOR ega_color;
uint8_t current_priority;
uint8_t current_control;
uint8_t draw_enable;
uint8_t palette_to_draw;
uint8_t pattern_size;
uint8_t pattern_nr;
bool pattern_use_pattern;
bool pattern_is_rect;
} PicRenderer;
#define PIC_SCREEN_VISUAL 0x01
#define PIC_SCREEN_PRIORITY 0x02
#define PIC_SCREEN_CONTROL 0x04
static void renderer_init(PicRenderer *r) {
memset(r, 0, sizeof(*r));
memset(r->visual, 0x0f, sizeof(r->visual));
for (int p = 0; p < 4; p++) {
for (int i = 0; i < PALETTE_SIZE; i++) {
r->palettes[p][i].color1 = (default_palette_init[i] >> 4) & 0x0f;
r->palettes[p][i].color2 = default_palette_init[i] & 0x0f;
}
}
r->draw_enable = PIC_SCREEN_VISUAL;
}
static bool is_white(PicRenderer *r) {
return r->ega_color.color1 == 15 && r->ega_color.color2 == 15;
}
static uint8_t get_aux_set(PicRenderer *r) {
uint8_t aux = r->draw_enable;
if (r->ega_color.color1 == 15 && r->ega_color.color2 == 15) {
aux &= ~PIC_SCREEN_VISUAL;
}
if (r->current_priority == 0) {
aux &= ~PIC_SCREEN_PRIORITY;
}
if (r->current_control == 0) {
aux &= ~PIC_SCREEN_CONTROL;
}
return aux;
}
#define BUFFEROFFSET(cx, cy, x, y) (((cy) - 1 - (y)) * (cx) + (x))
static void plot_pixel(PicRenderer *r, int x, int y) {
if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) return;
uint8_t aux_set = get_aux_set(r);
if (r->draw_enable & PIC_SCREEN_VISUAL) {
uint8_t color_idx = ((x ^ y) & 1) ? r->ega_color.color1 : r->ega_color.color2;
r->visual[y][x] = color_idx;
}
if (r->draw_enable & PIC_SCREEN_PRIORITY) {
r->priority[y][x] = r->current_priority;
}
if (r->draw_enable & PIC_SCREEN_CONTROL) {
r->control[y][x] = r->current_control;
}
r->aux[y][x] |= aux_set;
}
static void draw_line(PicRenderer *r, int x1, int y1, int x2, int y2) {
int dx = abs(x2 - x1);
int dy = abs(y2 - y1);
int sx = (x1 < x2) ? 1 : -1;
int sy = (y1 < y2) ? 1 : -1;
int err = dx - dy;
while (1) {
plot_pixel(r, x1, y1);
if (x1 == x2 && y1 == y2) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x1 += sx; }
if (e2 < dx) { err += dx; y1 += sy; }
}
}
static void draw_pattern(PicRenderer *r, int x, int y, int size, int pattern_nr, bool use_pattern, bool is_rect) {
int wsize = size;
int x_max = WIDTH - 1;
int y_max = HEIGHT - 1;
if (x < wsize) x = wsize;
if (x + wsize > x_max) x = x_max - wsize;
if (y < wsize) y = wsize;
if (y + wsize > y_max) y = y_max - wsize;
if (pattern_nr >= 128) return;
uint8_t junqbit = junqindex[pattern_nr];
if (is_rect) {
for (int ly = y - wsize; ly <= y + wsize; ly++) {
for (int lx = x - wsize; lx <= x + wsize + 1; lx++) {
if (use_pattern) {
if ((junq[junqbit >> 3] >> (7 - (junqbit & 7))) & 1) {
plot_pixel(r, lx, ly);
}
junqbit = (junqbit + 1) & 0xFF;
} else {
plot_pixel(r, lx, ly);
}
}
}
} else {
int circlebit = 0;
const uint8_t *circle_data = circles[size];
for (int ly = y - wsize; ly <= y + wsize; ly++) {
for (int lx = x - wsize; lx <= x + wsize + 1; lx++) {
bool circle_on = (circle_data[circlebit >> 3] >> (7 - (circlebit & 7))) & 1;
if (circle_on) {
if (use_pattern) {
if ((junq[junqbit >> 3] >> (7 - (junqbit & 7))) & 1) {
plot_pixel(r, lx, ly);
}
junqbit = (junqbit + 1) & 0xFF;
} else {
plot_pixel(r, lx, ly);
}
}
circlebit++;
}
}
}
}
static bool fill_bounds(PicRenderer *r, int x, int y, uint8_t draw_enable) {
uint8_t aux = r->aux[y][x];
uint8_t overlap = draw_enable & aux;
if (overlap) {
if ((draw_enable & PIC_SCREEN_VISUAL) && (r->visual[y][x] == 0x0f)) {
return false;
}
return true;
}
return false;
}
static bool ok_to_fill(PicRenderer *r, int x, int y, uint8_t draw_enable) {
if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) return false;
return !fill_bounds(r, x, y, draw_enable);
}
typedef struct { int x, y; } Point;
#define MAX_FILL_STACK (WIDTH * HEIGHT)
static void flood_fill(PicRenderer *r, int start_x, int start_y) {
if (start_x < 0 || start_x >= WIDTH || start_y < 0 || start_y >= HEIGHT) return;
uint8_t aux_set = get_aux_set(r);
uint8_t draw_enable = aux_set;
if (is_white(r)) return;
if (draw_enable == 0) return;
if (fill_bounds(r, start_x, start_y, draw_enable)) return;
Point *stack = malloc(MAX_FILL_STACK * sizeof(Point));
if (!stack) return;
int stack_pos = 0;
stack[stack_pos++] = (Point){start_x, start_y};
int x_max = WIDTH - 1;
int y_max = HEIGHT - 1;
while (stack_pos > 0) {
Point p = stack[--stack_pos];
int x = p.x, y = p.y;
if (!ok_to_fill(r, x, y, draw_enable)) continue;
if (draw_enable & PIC_SCREEN_VISUAL) {
r->visual[y][x] = ((x ^ y) & 1) ? r->ega_color.color1 : r->ega_color.color2;
}
if (draw_enable & PIC_SCREEN_PRIORITY) {
r->priority[y][x] = r->current_priority;
}
if (draw_enable & PIC_SCREEN_CONTROL) {
r->control[y][x] = r->current_control;
}
r->aux[y][x] |= aux_set;
if (y > 0 && ok_to_fill(r, x, y - 1, draw_enable) && stack_pos < MAX_FILL_STACK)
stack[stack_pos++] = (Point){x, y - 1};
if (x > 0 && ok_to_fill(r, x - 1, y, draw_enable) && stack_pos < MAX_FILL_STACK)
stack[stack_pos++] = (Point){x - 1, y};
if (x < x_max && ok_to_fill(r, x + 1, y, draw_enable) && stack_pos < MAX_FILL_STACK)
stack[stack_pos++] = (Point){x + 1, y};
if (y < y_max && ok_to_fill(r, x, y + 1, draw_enable) && stack_pos < MAX_FILL_STACK)
stack[stack_pos++] = (Point){x, y + 1};
}
free(stack);
}
static void read_abs_coords(const uint8_t *data, size_t *i, int *x, int *y) {
uint8_t prefix = data[(*i)++];
*x = data[(*i)++] | ((prefix & 0xF0) << 4);
*y = data[(*i)++] | ((prefix & 0x0F) << 8);
}
static void render_pic(PicRenderer *r, const uint8_t *data, size_t len) {
size_t i = 0;
int x = 0, y = 0;
while (i < len) {
uint8_t opcode = data[i++];
if (opcode == PIC_OP_END) break;
switch (opcode) {
case PIC_OP_SET_COLOR: {
uint8_t color_code = data[i++];
uint8_t palette_num = color_code / PALETTE_SIZE;
uint8_t palette_offset = color_code % PALETTE_SIZE;
uint8_t palette_to_use = palette_num ? palette_num : r->palette_to_draw;
r->palette_number = palette_num;
r->palette_offset = palette_offset;
if (r->locked[palette_offset]) {
r->ega_color = r->palettes[0][palette_offset];
} else {
r->ega_color = r->palettes[palette_to_use][palette_offset];
}
r->draw_enable |= PIC_SCREEN_VISUAL;
break;
}
case PIC_OP_DISABLE_VISUAL:
r->draw_enable &= ~PIC_SCREEN_VISUAL;
break;
case PIC_OP_SET_PRIORITY:
r->current_priority = data[i++] & 0x0F;
r->draw_enable |= PIC_SCREEN_PRIORITY;
break;
case PIC_OP_DISABLE_PRIORITY:
r->draw_enable &= ~PIC_SCREEN_PRIORITY;
break;
case PIC_OP_SET_CONTROL:
r->current_control = data[i++];
r->draw_enable |= PIC_SCREEN_CONTROL;
break;
case PIC_OP_DISABLE_CONTROL:
r->draw_enable &= ~PIC_SCREEN_CONTROL;
break;
case PIC_OP_RELATIVE_MEDIUM_LINES:
read_abs_coords(data, &i, &x, &y);
while (i < len && data[i] < 0xF0) {
uint8_t by = data[i++];
uint8_t bx = data[i++];
int dy = (by & 0x80) ? -(by & 0x7F) : (by & 0x7F);
int dx = (int8_t)bx;
draw_line(r, x, y, x + dx, y + dy);
x += dx;
y += dy;
}
break;
case PIC_OP_RELATIVE_LONG_LINES:
read_abs_coords(data, &i, &x, &y);
while (i < len && data[i] < 0xF0) {
int x2, y2;
read_abs_coords(data, &i, &x2, &y2);
draw_line(r, x, y, x2, y2);
x = x2;
y = y2;
}
break;
case PIC_OP_RELATIVE_SHORT_LINES:
read_abs_coords(data, &i, &x, &y);
while (i < len && data[i] < 0xF0) {
uint8_t b = data[i++];
int dx = (b & 0x80) ? -((b >> 4) & 0x7) : ((b >> 4) & 0x7);
int dy = (b & 0x08) ? -(b & 0x7) : (b & 0x7);
draw_line(r, x, y, x + dx, y + dy);
x += dx;
y += dy;
}
break;
case PIC_OP_FILL:
while (i < len && data[i] < 0xF0) {
int fx, fy;
read_abs_coords(data, &i, &fx, &fy);
flood_fill(r, fx, fy);
}
break;
case PIC_OP_RELATIVE_PATTERNS: {
uint8_t pattern_nr_byte = 0;
if (r->pattern_use_pattern && i < len && data[i] < 0xF0) {
pattern_nr_byte = data[i++];
}
read_abs_coords(data, &i, &x, &y);
draw_pattern(r, x, y, r->pattern_size, (pattern_nr_byte >> 1) & 0x7f,
r->pattern_use_pattern, r->pattern_is_rect);
while (i < len && data[i] < 0xF0) {
pattern_nr_byte = 0;
if (r->pattern_use_pattern && i < len && data[i] < 0xF0) {
pattern_nr_byte = data[i++];
}
uint8_t b = data[i++];
int dx = (b & 0x80) ? -((b >> 4) & 0x7) : ((b >> 4) & 0x7);
int dy = (b & 0x08) ? -(b & 0x7) : (b & 0x7);
x += dx;
y += dy;
draw_pattern(r, x, y, r->pattern_size, (pattern_nr_byte >> 1) & 0x7f,
r->pattern_use_pattern, r->pattern_is_rect);
}
break;
}
case PIC_OP_ABSOLUTE_PATTERNS:
while (i < len && data[i] < 0xF0) {
uint8_t pattern_nr_byte = 0;
if (r->pattern_use_pattern && i < len && data[i] < 0xF0) {
pattern_nr_byte = data[i++];
}
read_abs_coords(data, &i, &x, &y);
draw_pattern(r, x, y, r->pattern_size, (pattern_nr_byte >> 1) & 0x7f,
r->pattern_use_pattern, r->pattern_is_rect);
}
break;
case PIC_OP_RELATIVE_MEDIUM_PATTERNS: {
uint8_t pattern_nr_byte = 0;
if (r->pattern_use_pattern && i < len && data[i] < 0xF0) {
pattern_nr_byte = data[i++];
}
read_abs_coords(data, &i, &x, &y);
draw_pattern(r, x, y, r->pattern_size, (pattern_nr_byte >> 1) & 0x7f,
r->pattern_use_pattern, r->pattern_is_rect);
while (i < len && data[i] < 0xF0) {
pattern_nr_byte = 0;
if (r->pattern_use_pattern && i < len && data[i] < 0xF0) {
pattern_nr_byte = data[i++];
}
uint8_t by = data[i++];
uint8_t bx = data[i++];
int dy = (by & 0x80) ? -(by & 0x7F) : (by & 0x7F);
int dx = (int8_t)bx;
x += dx;
y += dy;
draw_pattern(r, x, y, r->pattern_size, (pattern_nr_byte >> 1) & 0x7f,
r->pattern_use_pattern, r->pattern_is_rect);
}
break;
}
case PIC_OP_SET_PATTERN: {
if (i < len && data[i] < 0xF0) {
uint8_t val = data[i++] & 0x37;
r->pattern_size = val & 0x07;
r->pattern_is_rect = (val & PATTERN_FLAG_RECTANGLE) != 0;
r->pattern_use_pattern = (val & PATTERN_FLAG_USE_PATTERN) != 0;
}
break;
}
case PIC_OP_OPX: {
if (i >= len) break;
uint8_t ext = data[i++];
if (ext == 0x00) {
while (i < len && data[i] < 0xF0) {
uint8_t b_index = data[i++];
uint8_t b_color = data[i++];
uint8_t pal_num = b_index / PALETTE_SIZE;
uint8_t pal_offset = b_index % PALETTE_SIZE;
if (pal_num < 4) {
r->palettes[pal_num][pal_offset].color1 = (b_color >> 4) & 0x0f;
r->palettes[pal_num][pal_offset].color2 = b_color & 0x0f;
if (pal_num == 0) {
r->locked[pal_offset] = 0xff;
}
}
}
} else if (ext == 0x01) {
uint8_t pal_num = data[i++];
for (int j = 0; j < PALETTE_SIZE; j++) {
uint8_t b_color = data[i++];
r->palettes[pal_num][j].color1 = (b_color >> 4) & 0x0f;
r->palettes[pal_num][j].color2 = b_color & 0x0f;
}
if (pal_num == r->palette_number) {
r->ega_color = r->palettes[pal_num][r->palette_offset];
}
} else if (ext == 0x08) {
i += 14;
}
break;
}
default:
break;
}
}
}
static uint8_t *decompress_lzw(const uint8_t *src, size_t src_len, size_t dest_len) {
uint8_t *dest = calloc(dest_len, 1);
if (!dest) return NULL;
int bitlen = 9;
int bitmask = 0x01ff;
int bitctr = 0;
size_t bytectr = 0;
uint16_t tokenlist[4096];
uint16_t tokenlengthlist[4096];
int tokenctr = 0x102;
int maxtoken = 0x200;
int tokenlastlength = 0;
size_t destctr = 0;
while (bytectr < src_len && destctr < dest_len) {
uint32_t tokenmaker = src[bytectr] >> bitctr;
if (bytectr + 1 < src_len) tokenmaker |= (src[bytectr + 1] << (8 - bitctr));
if (bytectr + 2 < src_len) tokenmaker |= (src[bytectr + 2] << (16 - bitctr));
uint16_t token = tokenmaker & bitmask;
bitctr += bitlen - 8;
while (bitctr >= 8) { bitctr -= 8; bytectr++; }
bytectr++;
if (token == 0x101) break;
if (token == 0x100) {
maxtoken = 0x200;
bitlen = 9;
bitmask = 0x01ff;
tokenctr = 0x102;
} else {
if (token > 0xff) {
if (token < tokenctr) {
tokenlastlength = tokenlengthlist[token] + 1;
for (int i = 0; i < tokenlastlength && destctr < dest_len; i++) {
dest[destctr++] = dest[tokenlist[token] + i];
}
}
} else {
tokenlastlength = 1;
dest[destctr++] = token;
}
if (tokenctr == maxtoken && bitlen < 12) {
bitlen++;
bitmask = (bitmask << 1) | 1;
maxtoken <<= 1;
}
if (tokenctr < 4096) {
tokenlist[tokenctr] = destctr - tokenlastlength;
tokenlengthlist[tokenctr] = tokenlastlength;
tokenctr++;
}
}
}
return dest;
}
static uint8_t *decompress_huffman(const uint8_t *src, size_t src_len, size_t dest_len) {
uint8_t *dest = calloc(dest_len, 1);
if (!dest || src_len < 2) return dest;
size_t destctr = 0;
uint8_t numnodes = src[0];
uint8_t terminator = src[1];
size_t bytectr = 2 + (numnodes << 1);
int bitctr = 0;
const uint8_t *nodes = &src[2];
while (bytectr < src_len && destctr < dest_len) {
int node_idx = 0;
while (nodes[node_idx * 2 + 1] != 0) {
if (bytectr >= src_len) break;
uint8_t value = (src[bytectr] << bitctr) & 0xFF;
bitctr++;
if (bitctr == 8) { bitctr = 0; bytectr++; }
int next_node;
if (value & 0x80) {
next_node = nodes[node_idx * 2 + 1] & 0x0f;
if (next_node == 0) {
if (bytectr >= src_len) break;
uint16_t result = (src[bytectr] << bitctr) & 0xFF;
bytectr++;
if (bytectr < src_len) result |= src[bytectr] >> (8 - bitctr);
result &= 0xff;
if (result == terminator) goto done;
if (destctr < dest_len) dest[destctr++] = result;
break;
}
} else {
next_node = nodes[node_idx * 2 + 1] >> 4;
}
node_idx += next_node;
}
if (nodes[node_idx * 2 + 1] == 0) {
uint16_t value = nodes[node_idx * 2] | (nodes[node_idx * 2 + 1] << 8);
if (value == (0x100 | terminator)) break;
if (destctr < dest_len) dest[destctr++] = value & 0xFF;
}
}
done:
return dest;
}
typedef struct {
int type;
int number;
int package;
uint32_t offset;
} ResourceEntry;
typedef struct {
ResourceEntry *entries;
int count;
int capacity;
} ResourceMap;
static int resource_map_add(ResourceMap *map, int type, int number, int package, uint32_t offset) {
if (map->count >= map->capacity) {
int new_cap = map->capacity ? map->capacity * 2 : 256;
ResourceEntry *new_entries = realloc(map->entries, new_cap * sizeof(ResourceEntry));
if (!new_entries) return -1;
map->entries = new_entries;
map->capacity = new_cap;
}
map->entries[map->count].type = type;
map->entries[map->count].number = number;
map->entries[map->count].package = package;
map->entries[map->count].offset = offset;
return map->count++;
}
static bool read_resource_map(const char *game_dir, ResourceMap *map) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/RESOURCE.MAP", game_dir);
FILE *f = fopen(path, "rb");
if (!f) return false;
uint8_t buf[6];
size_t n;
while ((n = fread(buf, 1, 6, f)) == 6) {
uint16_t word = buf[0] | (buf[1] << 8);
uint32_t dword = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
if (word == 0xFFFF && dword == 0xFFFFFFFF) break;
int number = word & 0x7FF;
int type = (word >> 11) & 0x1F;
uint32_t offset = dword & 0x3FFFFFF;
int package = (dword >> 26) & 0x3F;
resource_map_add(map, type, number, package, offset);
}
fclose(f);
return true;
}
typedef struct {
uint8_t method;
uint16_t compressed_size;
uint16_t decompressed_size;
} ResourceHeader;
static bool read_resource_header(const char *game_dir, int package, uint32_t offset, ResourceHeader *hdr) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/RESOURCE.%03d", game_dir, package);
FILE *f = fopen(path, "rb");
if (!f) return false;
if (fseek(f, offset, SEEK_SET) != 0) {
fclose(f);
return false;
}
uint8_t buf[8];
if (fread(buf, 1, 8, f) != 8) {
fclose(f);
return false;
}
uint16_t method_word = buf[6] | (buf[7] << 8);
hdr->method = method_word;
hdr->compressed_size = buf[2] | (buf[3] << 8);
hdr->decompressed_size = buf[4] | (buf[5] << 8);
fclose(f);
return true;
}
static uint8_t *read_resource_data(const char *game_dir, int package, uint32_t offset,
uint16_t comp_size, uint16_t decomp_size, uint8_t method) {
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/RESOURCE.%03d", game_dir, package);
FILE *f = fopen(path, "rb");
if (!f) return NULL;
if (fseek(f, offset + 8, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
size_t actual_comp_size = comp_size - 4;
uint8_t *comp_data = malloc(actual_comp_size);
if (!comp_data) {
fclose(f);
return NULL;
}
if (fread(comp_data, 1, actual_comp_size, f) != actual_comp_size) {
free(comp_data);
fclose(f);
return NULL;
}
fclose(f);
uint8_t *data;
if (method == 0) {
data = malloc(decomp_size);
if (data) memcpy(data, comp_data, decomp_size);
} else if (method == 1) {
data = decompress_lzw(comp_data, actual_comp_size, decomp_size);
} else if (method == 2) {
data = decompress_huffman(comp_data, actual_comp_size, decomp_size);
} else {
data = NULL;
}
free(comp_data);
return data;
}
static const uint8_t ega_colors[16][3] = {
{0x00, 0x00, 0x00}, {0x00, 0x00, 0xA0}, {0x00, 0xA0, 0x00}, {0x00, 0xA0, 0xA0},
{0xA0, 0x00, 0x00}, {0xA0, 0x00, 0xA0}, {0xA0, 0x50, 0x00}, {0xA0, 0xA0, 0xA0},
{0x50, 0x50, 0x50}, {0x50, 0x50, 0xFF}, {0x00, 0xFF, 0x50}, {0x50, 0xFF, 0xFF},
{0xFF, 0x50, 0x50}, {0xFF, 0x50, 0xFF}, {0xFF, 0xFF, 0x50}, {0xFF, 0xFF, 0xFF},
};
static bool save_png(const char *filename, uint8_t pixels[HEIGHT][WIDTH], bool use_palette) {
FILE *f = fopen(filename, "wb");
if (!f) return false;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) { fclose(f); return false; }
png_infop info = png_create_info_struct(png);
if (!info) { png_destroy_write_struct(&png, NULL); fclose(f); return false; }
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(f);
return false;
}
png_init_io(png, f);
if (use_palette) {
png_set_IHDR(png, info, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_color palette[16];
for (int i = 0; i < 16; i++) {
palette[i].red = ega_colors[i][0];
palette[i].green = ega_colors[i][1];
palette[i].blue = ega_colors[i][2];
}
png_set_PLTE(png, info, palette, 16);
} else {
png_set_IHDR(png, info, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
png_write_info(png, info);
png_bytep row = malloc(WIDTH);
for (int y = 0; y < HEIGHT; y++) {
if (use_palette) {
memcpy(row, pixels[y], WIDTH);
} else {
for (int x = 0; x < WIDTH; x++) {
row[x] = pixels[y][x] * 17;
}
}
png_write_row(png, row);
}
free(row);
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(f);
return true;
}
static bool save_rgb_png(const char *filename, uint8_t visual[HEIGHT][WIDTH]) {
FILE *f = fopen(filename, "wb");
if (!f) return false;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) { fclose(f); return false; }
png_infop info = png_create_info_struct(png);
if (!info) { png_destroy_write_struct(&png, NULL); fclose(f); return false; }
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(f);
return false;
}
png_init_io(png, f);
png_set_IHDR(png, info, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
png_bytep row = malloc(WIDTH * 3);
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
uint8_t idx = visual[y][x] & 0x0f;
row[x * 3 + 0] = ega_colors[idx][0];
row[x * 3 + 1] = ega_colors[idx][1];
row[x * 3 + 2] = ega_colors[idx][2];
}
png_write_row(png, row);
}
free(row);
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(f);
return true;
}
static ResourceEntry *find_resource(ResourceMap *map, int type, int number) {
for (int i = 0; i < map->count; i++) {
if (map->entries[i].type == type && map->entries[i].number == number) {
return &map->entries[i];
}
}
return NULL;
}
static void print_usage(const char *prog) {
fprintf(stderr, "Usage: %s <room_number> <resource_dir> [output_dir]\n", prog);
fprintf(stderr, "\nRenders SCI PIC resources to PNG images.\n");
fprintf(stderr, "\nArguments:\n");
fprintf(stderr, " room_number PIC resource number to render\n");
fprintf(stderr, " resource_dir Directory containing RESOURCE.MAP and RESOURCE.* files\n");
fprintf(stderr, " output_dir Output directory (default: rooms/)\n");
}
int main(int argc, char *argv[]) {
if (argc < 3) {
print_usage(argv[0]);
return 1;
}
int room_number = atoi(argv[1]);
const char *game_dir = argv[2];
const char *output_dir = (argc > 3) ? argv[3] : "rooms";
ResourceMap map = {0};
if (!read_resource_map(game_dir, &map)) {
fprintf(stderr, "Error: Could not read RESOURCE.MAP from %s\n", game_dir);
return 1;
}
ResourceEntry *entry = find_resource(&map, 1, room_number);
if (!entry) {
fprintf(stderr, "Error: PIC %d not found\n", room_number);
free(map.entries);
return 1;
}
ResourceHeader hdr;
if (!read_resource_header(game_dir, entry->package, entry->offset, &hdr)) {
fprintf(stderr, "Error: Could not read resource header\n");
free(map.entries);
return 1;
}
printf("PIC %d: package=%d, method=%d, compressed=%d, decompressed=%d\n",
room_number, entry->package, hdr.method, hdr.compressed_size, hdr.decompressed_size);
uint8_t *data = read_resource_data(game_dir, entry->package, entry->offset,
hdr.compressed_size, hdr.decompressed_size, hdr.method);
if (!data) {
fprintf(stderr, "Error: Could not decompress resource\n");
free(map.entries);
return 1;
}
PicRenderer renderer;
renderer_init(&renderer);
render_pic(&renderer, data, hdr.decompressed_size);
free(data);
free(map.entries);
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/pic_%03d_visual.png", output_dir, room_number);
if (!save_rgb_png(path, renderer.visual)) {
fprintf(stderr, "Error: Could not save %s\n", path);
return 1;
}
printf("Saved: %s\n", path);
snprintf(path, sizeof(path), "%s/pic_%03d_priority.png", output_dir, room_number);
save_png(path, renderer.priority, false);
snprintf(path, sizeof(path), "%s/pic_%03d_control.png", output_dir, room_number);
save_png(path, renderer.control, false);
return 0;
}