/* * 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 [output_directory] * * Compile: gcc -o sci_pic_render sci_pic_render.c -lpng -lz -lm -O2 */ #include #include #include #include #include #include #include #include #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 [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; }