Progress on documentation
This commit is contained in:
959
sci_pic_render.c
Normal file
959
sci_pic_render.c
Normal file
@@ -0,0 +1,959 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user