/*
 * 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;
}
