sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

n64graphics.c (32751B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <strings.h>
      4 
      5 #define STBI_NO_LINEAR
      6 #define STBI_NO_HDR
      7 #define STBI_NO_TGA
      8 #define STB_IMAGE_IMPLEMENTATION
      9 #include <stb/stb_image.h>
     10 #define STB_IMAGE_WRITE_IMPLEMENTATION
     11 #include <stb/stb_image_write.h>
     12 
     13 #include "n64graphics.h"
     14 #include "utils.h"
     15 
     16 // SCALE_M_N: upscale/downscale M-bit integer to N-bit
     17 #define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F)
     18 #define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF)
     19 #define SCALE_4_8(VAL_) ((VAL_) * 0x11)
     20 #define SCALE_8_4(VAL_) ((VAL_) / 0x11)
     21 #define SCALE_3_8(VAL_) ((VAL_) * 0x24)
     22 #define SCALE_8_3(VAL_) ((VAL_) / 0x24)
     23 
     24 
     25 typedef struct
     26 {
     27     enum
     28     {
     29        IMG_FORMAT_RGBA,
     30        IMG_FORMAT_IA,
     31        IMG_FORMAT_I,
     32        IMG_FORMAT_CI,
     33     } format;
     34     int depth;
     35 } img_format;
     36 
     37 //---------------------------------------------------------
     38 // N64 RGBA/IA/I/CI -> internal RGBA/IA
     39 //---------------------------------------------------------
     40 
     41 rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth)
     42 {
     43    rgba *img;
     44    int img_size;
     45 
     46    img_size = width * height * sizeof(*img);
     47    img = malloc(img_size);
     48    if (!img) {
     49       ERROR("Error allocating %d bytes\n", img_size);
     50       return NULL;
     51    }
     52 
     53    if (depth == 16) {
     54       for (int i = 0; i < width * height; i++) {
     55          img[i].red   = SCALE_5_8((raw[i*2] & 0xF8) >> 3);
     56          img[i].green = SCALE_5_8(((raw[i*2] & 0x07) << 2) | ((raw[i*2+1] & 0xC0) >> 6));
     57          img[i].blue  = SCALE_5_8((raw[i*2+1] & 0x3E) >> 1);
     58          img[i].alpha = (raw[i*2+1] & 0x01) ? 0xFF : 0x00;
     59       }
     60    } else if (depth == 32) {
     61       for (int i = 0; i < width * height; i++) {
     62          img[i].red   = raw[i*4];
     63          img[i].green = raw[i*4+1];
     64          img[i].blue  = raw[i*4+2];
     65          img[i].alpha = raw[i*4+3];
     66       }
     67    }
     68 
     69    return img;
     70 }
     71 
     72 ia *raw2ia(const uint8_t *raw, int width, int height, int depth)
     73 {
     74    ia *img;
     75    int img_size;
     76 
     77    img_size = width * height * sizeof(*img);
     78    img = malloc(img_size);
     79    if (!img) {
     80       ERROR("Error allocating %u bytes\n", img_size);
     81       return NULL;
     82    }
     83 
     84    switch (depth) {
     85       case 16:
     86          for (int i = 0; i < width * height; i++) {
     87             img[i].intensity = raw[i*2];
     88             img[i].alpha     = raw[i*2+1];
     89          }
     90          break;
     91       case 8:
     92          for (int i = 0; i < width * height; i++) {
     93             img[i].intensity = SCALE_4_8((raw[i] & 0xF0) >> 4);
     94             img[i].alpha     = SCALE_4_8(raw[i] & 0x0F);
     95          }
     96          break;
     97       case 4:
     98          for (int i = 0; i < width * height; i++) {
     99             uint8_t bits;
    100             bits = raw[i/2];
    101             if (i % 2) {
    102                bits &= 0xF;
    103             } else {
    104                bits >>= 4;
    105             }
    106             img[i].intensity = SCALE_3_8((bits >> 1) & 0x07);
    107             img[i].alpha     = (bits & 0x01) ? 0xFF : 0x00;
    108          }
    109          break;
    110       case 1:
    111          for (int i = 0; i < width * height; i++) {
    112             uint8_t bits;
    113             uint8_t mask;
    114             bits = raw[i/8];
    115             mask = 1 << (7 - (i % 8)); // MSb->LSb
    116             bits = (bits & mask) ? 0xFF : 0x00;
    117             img[i].intensity = bits;
    118             img[i].alpha     = bits;
    119          }
    120          break;
    121       default:
    122          ERROR("Error invalid depth %d\n", depth);
    123          break;
    124    }
    125 
    126    return img;
    127 }
    128 
    129 ia *raw2i(const uint8_t *raw, int width, int height, int depth)
    130 {
    131    ia *img = NULL;
    132    int img_size;
    133 
    134    img_size = width * height * sizeof(*img);
    135    img = malloc(img_size);
    136    if (!img) {
    137       ERROR("Error allocating %u bytes\n", img_size);
    138       return NULL;
    139    }
    140 
    141    switch (depth) {
    142       case 8:
    143          for (int i = 0; i < width * height; i++) {
    144             img[i].intensity = raw[i];
    145             img[i].alpha     = 0xFF;
    146          }
    147          break;
    148       case 4:
    149          for (int i = 0; i < width * height; i++) {
    150             uint8_t bits;
    151             bits = raw[i/2];
    152             if (i % 2) {
    153                bits &= 0xF;
    154             } else {
    155                bits >>= 4;
    156             }
    157             img[i].intensity = SCALE_4_8(bits);
    158             img[i].alpha     = img[i].intensity; // alpha copy intensity
    159             // TODO: modes
    160             // img[i].alpha     = 0xFF; // alpha = 1
    161             // img[i].alpha     = img[i].intensity ? 0xFF : 0x00; // binary
    162          }
    163          break;
    164       default:
    165          ERROR("Error invalid depth %d\n", depth);
    166          break;
    167    }
    168 
    169    return img;
    170 }
    171 
    172 // convert CI raw data and palette to raw data (either RGBA16 or IA16)
    173 uint8_t *ci2raw(const uint8_t *rawci, const uint8_t *palette, int width, int height, int ci_depth)
    174 {
    175    uint8_t *raw;
    176    int raw_size;
    177 
    178    // first convert to raw RGBA
    179    raw_size = sizeof(uint16_t) * width * height;
    180    raw = malloc(raw_size);
    181    if (!raw) {
    182       ERROR("Error allocating %u bytes\n", raw_size);
    183       return NULL;
    184    }
    185 
    186    for (int i = 0; i < width * height; i++) {
    187       int pal_idx = rawci[i];
    188       if (ci_depth == 4) {
    189          int byte_idx = i / 2;
    190          int nibble = 1 - (i % 2);
    191          int shift = 4 * nibble;
    192          pal_idx = (rawci[byte_idx] >> shift) & 0xF;
    193       }
    194       raw[2*i]   = palette[2*pal_idx];
    195       raw[2*i+1] = palette[2*pal_idx+1];
    196    }
    197 
    198    return raw;
    199 }
    200 
    201 
    202 //---------------------------------------------------------
    203 // internal RGBA/IA -> N64 RGBA/IA/I/CI
    204 // returns length written to 'raw' used or -1 on error
    205 //---------------------------------------------------------
    206 
    207 int rgba2raw(uint8_t *raw, const rgba *img, int width, int height, int depth)
    208 {
    209    int size = (width * height * depth + 7) / 8;
    210    INFO("Converting RGBA%d %dx%d to raw\n", depth, width, height);
    211 
    212    if (depth == 16) {
    213       for (int i = 0; i < width * height; i++) {
    214          uint8_t r, g, b, a;
    215          r = SCALE_8_5(img[i].red);
    216          g = SCALE_8_5(img[i].green);
    217          b = SCALE_8_5(img[i].blue);
    218          a = img[i].alpha ? 0x1 : 0x0;
    219          raw[i*2]   = (r << 3) | (g >> 2);
    220          raw[i*2+1] = ((g & 0x3) << 6) | (b << 1) | a;
    221       }
    222    } else if (depth == 32) {
    223       for (int i = 0; i < width * height; i++) {
    224          raw[i*4]   = img[i].red;
    225          raw[i*4+1] = img[i].green;
    226          raw[i*4+2] = img[i].blue;
    227          raw[i*4+3] = img[i].alpha;
    228       }
    229    } else {
    230       ERROR("Error invalid depth %d\n", depth);
    231       size = -1;
    232    }
    233 
    234    return size;
    235 }
    236 
    237 int ia2raw(uint8_t *raw, const ia *img, int width, int height, int depth)
    238 {
    239    int size = (width * height * depth + 7) / 8;
    240    INFO("Converting IA%d %dx%d to raw\n", depth, width, height);
    241    memset(raw, 0, size);
    242 
    243    switch (depth) {
    244       case 16:
    245          for (int i = 0; i < width * height; i++) {
    246             raw[i*2]   = img[i].intensity;
    247             raw[i*2+1] = img[i].alpha;
    248          }
    249          break;
    250       case 8:
    251          for (int i = 0; i < width * height; i++) {
    252             uint8_t val = SCALE_8_4(img[i].intensity);
    253             uint8_t alpha = SCALE_8_4(img[i].alpha);
    254             raw[i] = (val << 4) | alpha;
    255          }
    256          break;
    257       case 4:
    258          for (int i = 0; i < width * height; i++) {
    259             uint8_t val = SCALE_8_3(img[i].intensity);
    260             uint8_t alpha = img[i].alpha ? 0x01 : 0x00;
    261             uint8_t old = raw[i/2];
    262             if (i % 2) {
    263                raw[i/2] = (old & 0xF0) | (val << 1) | alpha;
    264             } else {
    265                raw[i/2] = (old & 0x0F) | (((val << 1) | alpha) << 4);
    266             }
    267          }
    268          break;
    269       case 1:
    270          for (int i = 0; i < width * height; i++) {
    271             uint8_t val = img[i].intensity;
    272             uint8_t old = raw[i/8];
    273             uint8_t bit = 1 << (7 - (i % 8));
    274             if (val) {
    275                raw[i/8] = old | bit;
    276             } else {
    277                raw[i/8] = old & (~bit);
    278             }
    279          }
    280          break;
    281       default:
    282          ERROR("Error invalid depth %d\n", depth);
    283          size = -1;
    284          break;
    285    }
    286 
    287    return size;
    288 }
    289 
    290 int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth)
    291 {
    292    int size = (width * height * depth + 7) / 8;
    293    INFO("Converting I%d %dx%d to raw\n", depth, width, height);
    294    memset(raw, 0, size);
    295 
    296    switch (depth) {
    297       case 8:
    298          for (int i = 0; i < width * height; i++) {
    299             raw[i] = img[i].intensity;
    300          }
    301          break;
    302       case 4:
    303          for (int i = 0; i < width * height; i++) {
    304             uint8_t val = SCALE_8_4(img[i].intensity);
    305             uint8_t old = raw[i/2];
    306             if (i % 2) {
    307                raw[i/2] = (old & 0xF0) | val;
    308             } else {
    309                raw[i/2] = (old & 0x0F) | (val << 4);
    310             }
    311          }
    312          break;
    313       default:
    314          ERROR("Error invalid depth %d\n", depth);
    315          size = -1;
    316          break;
    317    }
    318 
    319    return size;
    320 }
    321 
    322 
    323 //---------------------------------------------------------
    324 // internal RGBA/IA -> PNG
    325 //---------------------------------------------------------
    326 
    327 int rgba2png(const char *png_filename, const rgba *img, int width, int height)
    328 {
    329    int ret = 0;
    330    INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename);
    331 
    332    // convert to format stb_image_write expects
    333    uint8_t *data = malloc(4*width*height);
    334    if (data) {
    335       for (int j = 0; j < height; j++) {
    336          for (int i = 0; i < width; i++) {
    337             int idx = j*width + i;
    338             data[4*idx]     = img[idx].red;
    339             data[4*idx + 1] = img[idx].green;
    340             data[4*idx + 2] = img[idx].blue;
    341             data[4*idx + 3] = img[idx].alpha;
    342          }
    343       }
    344 
    345       ret = stbi_write_png(png_filename, width, height, 4, data, 0);
    346 
    347       free(data);
    348    }
    349 
    350    return ret;
    351 }
    352 
    353 int ia2png(const char *png_filename, const ia *img, int width, int height)
    354 {
    355    int ret = 0;
    356    INFO("Saving IA %dx%d to \"%s\"\n", width, height, png_filename);
    357 
    358    // convert to format stb_image_write expects
    359    uint8_t *data = malloc(2*width*height);
    360    if (data) {
    361       for (int j = 0; j < height; j++) {
    362          for (int i = 0; i < width; i++) {
    363             int idx = j*width + i;
    364             data[2*idx]     = img[idx].intensity;
    365             data[2*idx + 1] = img[idx].alpha;
    366          }
    367       }
    368 
    369       ret = stbi_write_png(png_filename, width, height, 2, data, 0);
    370 
    371       free(data);
    372    }
    373 
    374    return ret;
    375 }
    376 
    377 //---------------------------------------------------------
    378 // PNG -> internal RGBA/IA
    379 //---------------------------------------------------------
    380 
    381 rgba *png2rgba(const char *png_filename, int *width, int *height)
    382 {
    383    rgba *img = NULL;
    384    int w = 0;
    385    int h = 0;
    386    int channels = 0;
    387    int img_size;
    388 
    389    stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
    390    if (!data || w <= 0 || h <= 0) {
    391       ERROR("Error loading \"%s\"\n", png_filename);
    392       return NULL;
    393    }
    394    INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
    395 
    396    img_size = w * h * sizeof(*img);
    397    img = malloc(img_size);
    398    if (!img) {
    399       ERROR("Error allocating %u bytes\n", img_size);
    400       return NULL;
    401    }
    402 
    403    switch (channels) {
    404       case 3: // red, green, blue
    405       case 4: // red, green, blue, alpha
    406          for (int j = 0; j < h; j++) {
    407             for (int i = 0; i < w; i++) {
    408                int idx = j*w + i;
    409                img[idx].red   = data[channels*idx];
    410                img[idx].green = data[channels*idx + 1];
    411                img[idx].blue  = data[channels*idx + 2];
    412                if (channels == 4) {
    413                   img[idx].alpha = data[channels*idx + 3];
    414                } else {
    415                   img[idx].alpha = 0xFF;
    416                }
    417             }
    418          }
    419          break;
    420       case 2: // grey, alpha
    421          for (int j = 0; j < h; j++) {
    422             for (int i = 0; i < w; i++) {
    423                int idx = j*w + i;
    424                img[idx].red   = data[2*idx];
    425                img[idx].green = data[2*idx];
    426                img[idx].blue  = data[2*idx];
    427                img[idx].alpha = data[2*idx + 1];
    428             }
    429          }
    430          break;
    431       default:
    432          ERROR("Don't know how to read channels: %d\n", channels);
    433          free(img);
    434          img = NULL;
    435    }
    436 
    437    // cleanup
    438    stbi_image_free(data);
    439 
    440    *width = w;
    441    *height = h;
    442    return img;
    443 }
    444 
    445 ia *png2ia(const char *png_filename, int *width, int *height)
    446 {
    447    ia *img = NULL;
    448    int w = 0, h = 0;
    449    int channels = 0;
    450    int img_size;
    451 
    452    stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
    453    if (!data || w <= 0 || h <= 0) {
    454       ERROR("Error loading \"%s\"\n", png_filename);
    455       return NULL;
    456    }
    457    INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
    458 
    459    img_size = w * h * sizeof(*img);
    460    img = malloc(img_size);
    461    if (!img) {
    462       ERROR("Error allocating %d bytes\n", img_size);
    463       return NULL;
    464    }
    465 
    466    switch (channels) {
    467       case 3: // red, green, blue
    468       case 4: // red, green, blue, alpha
    469          ERROR("Warning: averaging RGB PNG to create IA\n");
    470          for (int j = 0; j < h; j++) {
    471             for (int i = 0; i < w; i++) {
    472                int idx = j*w + i;
    473                int sum = data[channels*idx] + data[channels*idx + 1] + data[channels*idx + 2];
    474                img[idx].intensity = (sum + 1) / 3; // add 1 to round up where appropriate
    475                if (channels == 4) {
    476                   img[idx].alpha = data[channels*idx + 3];
    477                } else {
    478                   img[idx].alpha = 0xFF;
    479                }
    480             }
    481          }
    482          break;
    483       case 2: // grey, alpha
    484          for (int j = 0; j < h; j++) {
    485             for (int i = 0; i < w; i++) {
    486                int idx = j*w + i;
    487                img[idx].intensity = data[2*idx];
    488                img[idx].alpha     = data[2*idx + 1];
    489             }
    490          }
    491          break;
    492       default:
    493          ERROR("Don't know how to read channels: %d\n", channels);
    494          free(img);
    495          img = NULL;
    496    }
    497 
    498    // cleanup
    499    stbi_image_free(data);
    500 
    501    *width = w;
    502    *height = h;
    503    return img;
    504 }
    505 
    506 // find index of palette color
    507 // return -1 if not found
    508 static int pal_find_color(const palette_t *pal, uint16_t val)
    509 {
    510    for (int i = 0; i < pal->used; i++) {
    511       if (pal->data[i] == val) {
    512          return i;
    513       }
    514    }
    515    return -1;
    516 }
    517 
    518 // find value in palette, or add if not there
    519 // returns palette index entered or -1 if palette full
    520 static int pal_add_color(palette_t *pal, uint16_t val)
    521 {
    522    int idx;
    523    idx = pal_find_color(pal, val);
    524    if (idx < 0) {
    525       if (pal->used == pal->max) {
    526          ERROR("Error: trying to use more than %d\n", pal->max);
    527       } else {
    528          idx = pal->used;
    529          pal->data[pal->used] = val;
    530          pal->used++;
    531       }
    532    }
    533    return idx;
    534 }
    535 
    536 // convert from raw (RGBA16 or IA16) format to CI + palette
    537 // returns 1 on success
    538 int raw2ci(uint8_t *rawci, palette_t *pal, const uint8_t *raw, int raw_len, int ci_depth)
    539 {
    540    // assign colors to palette
    541    pal->used = 0;
    542    memset(pal->data, 0, sizeof(pal->data));
    543    int ci_idx = 0;
    544    for (int i = 0; i < raw_len; i += sizeof(uint16_t)) {
    545       uint16_t val = read_u16_be(&raw[i]);
    546       int pal_idx = pal_add_color(pal, val);
    547       if (pal_idx < 0) {
    548          ERROR("Error adding color @ (%d): %d (used: %d/%d)\n", i, pal_idx, pal->used, pal->max);
    549          return 0;
    550       } else {
    551          switch (ci_depth) {
    552             case 8:
    553                rawci[ci_idx] = (uint8_t)pal_idx;
    554                break;
    555             case 4:
    556             {
    557                int byte_idx = ci_idx / 2;
    558                int nibble = 1 - (ci_idx % 2);
    559                uint8_t mask = 0xF << (4 * (1 - nibble));
    560                rawci[byte_idx] = (rawci[byte_idx] & mask) | (pal_idx << (4 * nibble));
    561                break;
    562             }
    563          }
    564          ci_idx++;
    565       }
    566    }
    567    return 1;
    568 }
    569 
    570 const char *n64graphics_get_read_version(void)
    571 {
    572    return "stb_image 2.19";
    573 }
    574 
    575 const char *n64graphics_get_write_version(void)
    576 {
    577    return "stb_image_write 1.09";
    578 }
    579 
    580 #ifdef N64GRAPHICS_STANDALONE
    581 #define N64GRAPHICS_VERSION "0.4"
    582 #include <string.h>
    583 
    584 typedef enum
    585 {
    586    MODE_EXPORT,
    587    MODE_IMPORT,
    588 } tool_mode;
    589 
    590 typedef struct
    591 {
    592    char *img_filename;
    593    char *bin_filename;
    594    char *pal_filename;
    595    tool_mode mode;
    596    write_encoding encoding;
    597    unsigned int bin_offset;
    598    unsigned int pal_offset;
    599    img_format format;
    600    img_format pal_format;
    601    int width;
    602    int height;
    603    int bin_truncate;
    604    int pal_truncate;
    605 } graphics_config;
    606 
    607 static const graphics_config default_config =
    608 {
    609    .img_filename = NULL,
    610    .bin_filename = NULL,
    611    .pal_filename = NULL,
    612    .mode = MODE_EXPORT,
    613    .encoding = ENCODING_RAW,
    614    .bin_offset = 0,
    615    .pal_offset = 0,
    616    .format = {IMG_FORMAT_RGBA, 16},
    617    .pal_format = {IMG_FORMAT_RGBA, 16},
    618    .width = 32,
    619    .height = 32,
    620    .bin_truncate = 1,
    621    .pal_truncate = 1,
    622 };
    623 
    624 typedef struct
    625 {
    626    const char *name;
    627    img_format format;
    628 } format_entry;
    629 
    630 static const format_entry format_table[] =
    631 {
    632    {"rgba16", {IMG_FORMAT_RGBA, 16}},
    633    {"rgba32", {IMG_FORMAT_RGBA, 32}},
    634    {"ia1",    {IMG_FORMAT_IA,    1}},
    635    {"ia4",    {IMG_FORMAT_IA,    4}},
    636    {"ia8",    {IMG_FORMAT_IA,    8}},
    637    {"ia16",   {IMG_FORMAT_IA,   16}},
    638    {"i4",     {IMG_FORMAT_I,     4}},
    639    {"i8",     {IMG_FORMAT_I,     8}},
    640    {"ci8",    {IMG_FORMAT_CI,    8}},
    641    {"ci4",    {IMG_FORMAT_CI,    4}},
    642 };
    643 
    644 static const char *format2str(const img_format *format)
    645 {
    646    for (unsigned i = 0; i < DIM(format_table); i++) {
    647       if (format->format == format_table[i].format.format && format->depth == format_table[i].format.depth) {
    648          return format_table[i].name;
    649       }
    650    }
    651    return "unknown";
    652 }
    653 
    654 static int parse_format(img_format *format, const char *str)
    655 {
    656    for (unsigned i = 0; i < DIM(format_table); i++) {
    657       if (!strcasecmp(str, format_table[i].name)) {
    658          format->format = format_table[i].format.format;
    659          format->depth = format_table[i].format.depth;
    660          return 1;
    661       }
    662    }
    663    return 0;
    664 }
    665 
    666 typedef struct
    667 {
    668    const char *name;
    669    write_encoding encoding;
    670 } scheme_entry;
    671 
    672 static const scheme_entry encoding_table[] =
    673 {
    674    {"raw", ENCODING_RAW},
    675    {"u8",  ENCODING_U8},
    676    {"u16", ENCODING_U16},
    677    {"u32", ENCODING_U32},
    678    {"u64", ENCODING_U64},
    679 };
    680 
    681 static const char *encoding2str(write_encoding encoding)
    682 {
    683    for (unsigned i = 0; i < DIM(encoding_table); i++) {
    684       if (encoding == encoding_table[i].encoding) {
    685          return encoding_table[i].name;
    686       }
    687    }
    688    return "unknown";
    689 }
    690 
    691 static int parse_encoding(write_encoding *encoding, const char *str)
    692 {
    693    for (unsigned i = 0; i < DIM(encoding_table); i++) {
    694       if (!strcasecmp(str, encoding_table[i].name)) {
    695          *encoding = encoding_table[i].encoding;
    696          return 1;
    697       }
    698    }
    699    return 0;
    700 }
    701 
    702 static void print_usage(void)
    703 {
    704    ERROR("Usage: n64graphics -e/-i BIN_FILE -g IMG_FILE [-p PAL_FILE] [-o BIN_OFFSET] [-P PAL_OFFSET] [-f FORMAT] [-c CI_FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n"
    705          "\n"
    706          "n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n"
    707          "\n"
    708          "Required arguments:\n"
    709          " -e BIN_FILE   export from BIN_FILE to PNG_FILE\n"
    710          " -i BIN_FILE   import from PNG_FILE to BIN_FILE\n"
    711          " -g IMG_FILE   graphics file to import/export (.png)\n"
    712          "Optional arguments:\n"
    713          " -o BIN_OFFSET starting offset in BIN_FILE (prevents truncation during import)\n"
    714          " -f FORMAT     texture format: rgba16, rgba32, ia1, ia4, ia8, ia16, i4, i8, ci4, ci8 (default: %s)\n"
    715          " -s SCHEME     output scheme: raw, u8 (hex), u64 (hex) (default: %s)\n"
    716          " -w WIDTH      export texture width (default: %d)\n"
    717          " -h HEIGHT     export texture height (default: %d)\n"
    718          "CI arguments:\n"
    719          " -c CI_FORMAT  CI palette format: rgba16, ia16 (default: %s)\n"
    720          " -p PAL_FILE   palette binary file to import/export from/to\n"
    721          " -P PAL_OFFSET starting offset in PAL_FILE (prevents truncation during import)\n"
    722          "Other arguments:\n"
    723          " -v            verbose logging\n"
    724          " -V            print version information\n",
    725          format2str(&default_config.format),
    726          encoding2str(default_config.encoding),
    727          default_config.width,
    728          default_config.height,
    729          format2str(&default_config.pal_format));
    730 }
    731 
    732 static void print_version(void)
    733 {
    734    ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n"
    735          "  %s\n"
    736          "  %s\n",
    737          n64graphics_get_read_version(), n64graphics_get_write_version());
    738 }
    739 
    740 // parse command line arguments
    741 static int parse_arguments(int argc, char *argv[], graphics_config *config)
    742 {
    743    for (int i = 1; i < argc; i++) {
    744       if (argv[i][0] == '-') {
    745          switch (argv[i][1]) {
    746             case 'c':
    747                if (++i >= argc) return 0;
    748                if (!parse_format(&config->pal_format, argv[i])) {
    749                   return 0;
    750                }
    751                break;
    752             case 'e':
    753                if (++i >= argc) return 0;
    754                config->bin_filename = argv[i];
    755                config->mode = MODE_EXPORT;
    756                break;
    757             case 'f':
    758                if (++i >= argc) return 0;
    759                if (!parse_format(&config->format, argv[i])) {
    760                   return 0;
    761                }
    762                break;
    763             case 'g':
    764                if (++i >= argc) return 0;
    765                config->img_filename = argv[i];
    766                break;
    767             case 'h':
    768                if (++i >= argc) return 0;
    769                config->height = strtoul(argv[i], NULL, 0);
    770                break;
    771             case 'i':
    772                if (++i >= argc) return 0;
    773                config->bin_filename = argv[i];
    774                config->mode = MODE_IMPORT;
    775                break;
    776             case 'o':
    777                if (++i >= argc) return 0;
    778                config->bin_offset = strtoul(argv[i], NULL, 0);
    779                config->bin_truncate = 0;
    780                break;
    781             case 'p':
    782                if (++i >= argc) return 0;
    783                config->pal_filename = argv[i];
    784                break;
    785             case 'P':
    786                if (++i >= argc) return 0;
    787                config->pal_offset = strtoul(argv[i], NULL, 0);
    788                config->pal_truncate = 0;
    789                break;
    790             case 's':
    791                if (++i >= argc) return 0;
    792                if (!parse_encoding(&config->encoding, argv[i])) {
    793                   return 0;
    794                }
    795                break;
    796             case 'v':
    797                g_verbosity = 1;
    798                break;
    799             case 'V':
    800                print_version();
    801                exit(0);
    802                break;
    803             case 'w':
    804                if (++i >= argc) return 0;
    805                config->width = strtoul(argv[i], NULL, 0);
    806                break;
    807             default:
    808                return 0;
    809                break;
    810          }
    811       } else {
    812          return 0;
    813       }
    814    }
    815    return 1;
    816 }
    817 
    818 // returns 1 if config is valid
    819 static int valid_config(const graphics_config *config)
    820 {
    821    if (!config->bin_filename || !config->img_filename) {
    822       return 0;
    823    }
    824    if (config->format.format == IMG_FORMAT_CI) {
    825       if (!config->pal_filename || (config->pal_format.depth != 16) ||
    826          (config->pal_format.format != IMG_FORMAT_RGBA && config->pal_format.format != IMG_FORMAT_IA)) {
    827          return 0;
    828       }
    829    }
    830    return 1;
    831 }
    832 
    833 int main(int argc, char *argv[])
    834 {
    835    graphics_config config = default_config;
    836    rgba *imgr;
    837    ia   *imgi;
    838    FILE *bin_fp;
    839    uint8_t *raw;
    840    int raw_size;
    841    int length = 0;
    842    int flength;
    843    int res;
    844 
    845    int valid = parse_arguments(argc, argv, &config);
    846    if (!valid || !valid_config(&config)) {
    847       print_usage();
    848       exit(EXIT_FAILURE);
    849    }
    850 
    851    if (config.mode == MODE_IMPORT) {
    852       if (0 == strcmp("-", config.bin_filename)) {
    853          bin_fp = stdout;
    854       } else {
    855          if (config.bin_truncate) {
    856             bin_fp = fopen(config.bin_filename, "wb");
    857          } else {
    858             bin_fp = fopen(config.bin_filename, "r+b");
    859          }
    860       }
    861       if (!bin_fp) {
    862          ERROR("Error opening \"%s\"\n", config.bin_filename);
    863          return -1;
    864       }
    865       if (!config.bin_truncate) {
    866          fseek(bin_fp, config.bin_offset, SEEK_SET);
    867       }
    868       switch (config.format.format) {
    869          case IMG_FORMAT_RGBA:
    870             imgr = png2rgba(config.img_filename, &config.width, &config.height);
    871             raw_size = (config.width * config.height * config.format.depth + 7) / 8;
    872             raw = malloc(raw_size);
    873             if (!raw) {
    874                ERROR("Error allocating %u bytes\n", raw_size);
    875             }
    876             length = rgba2raw(raw, imgr, config.width, config.height, config.format.depth);
    877             break;
    878          case IMG_FORMAT_IA:
    879             imgi = png2ia(config.img_filename, &config.width, &config.height);
    880             raw_size = (config.width * config.height * config.format.depth + 7) / 8;
    881             raw = malloc(raw_size);
    882             if (!raw) {
    883                ERROR("Error allocating %u bytes\n", raw_size);
    884             }
    885             length = ia2raw(raw, imgi, config.width, config.height, config.format.depth);
    886             break;
    887          case IMG_FORMAT_I:
    888             imgi = png2ia(config.img_filename, &config.width, &config.height);
    889             raw_size = (config.width * config.height * config.format.depth + 7) / 8;
    890             raw = malloc(raw_size);
    891             if (!raw) {
    892                ERROR("Error allocating %u bytes\n", raw_size);
    893             }
    894             length = i2raw(raw, imgi, config.width, config.height, config.format.depth);
    895             break;
    896          case IMG_FORMAT_CI:
    897          {
    898             palette_t pal = {0};
    899             FILE *pal_fp;
    900             uint8_t *raw16;
    901             int raw16_size;
    902             int raw16_length;
    903             uint8_t *ci;
    904             int ci_length;
    905             int pal_success;
    906             int pal_length;
    907 
    908             if (config.pal_truncate) {
    909                pal_fp = fopen(config.pal_filename, "wb");
    910             } else {
    911                pal_fp = fopen(config.pal_filename, "r+b");
    912             }
    913             if (!pal_fp) {
    914                ERROR("Error opening \"%s\"\n", config.pal_filename);
    915                return EXIT_FAILURE;
    916             }
    917             if (!config.pal_truncate) {
    918                fseek(pal_fp, config.bin_offset, SEEK_SET);
    919             }
    920 
    921             raw16_size = config.width * config.height * config.pal_format.depth / 8;
    922             raw16 = malloc(raw16_size);
    923             if (!raw16) {
    924                ERROR("Error allocating %d bytes\n", raw16_size);
    925                return EXIT_FAILURE;
    926             }
    927             switch (config.pal_format.format) {
    928                case IMG_FORMAT_RGBA:
    929                   imgr = png2rgba(config.img_filename, &config.width, &config.height);
    930                   raw16_length = rgba2raw(raw16, imgr, config.width, config.height, config.pal_format.depth);
    931                   break;
    932                case IMG_FORMAT_IA:
    933                   imgi = png2ia(config.img_filename, &config.width, &config.height);
    934                   raw16_length = ia2raw(raw16, imgi, config.width, config.height, config.pal_format.depth);
    935                   break;
    936                default:
    937                   ERROR("Unsupported palette format: %s\n", format2str(&config.pal_format));
    938                   exit(EXIT_FAILURE);
    939             }
    940 
    941             // convert raw to palette
    942             pal.max = (1 << config.format.depth);
    943             ci_length = config.width * config.height * config.format.depth / 8;
    944             ci = malloc(ci_length);
    945             pal_success = raw2ci(ci, &pal, raw16, raw16_length, config.format.depth);
    946             if (!pal_success) {
    947                ERROR("Error converting palette\n");
    948                exit(EXIT_FAILURE);
    949             }
    950 
    951             // pack the bytes
    952             uint8_t raw_pal[sizeof(pal.data)];
    953             for (int i = 0; i < pal.max; i++) {
    954                write_u16_be(&raw_pal[2*i], pal.data[i]);
    955             }
    956             pal_length = pal.max * sizeof(pal.data[0]);
    957             INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", pal_length, config.pal_offset, config.pal_filename);
    958             flength = fprint_write_output(pal_fp, config.encoding, raw_pal, pal_length);
    959             if (config.encoding == ENCODING_RAW && flength != pal_length) {
    960                ERROR("Error writing %d bytes to \"%s\"\n", pal_length, config.pal_filename);
    961             }
    962             INFO("Wrote 0x%X bytes to \"%s\"\n", flength, config.pal_filename);
    963 
    964             raw = ci;
    965             length = ci_length;
    966 
    967             free(raw16);
    968             fclose(pal_fp);
    969             break;
    970          }
    971          default:
    972             return EXIT_FAILURE;
    973       }
    974       if (length <= 0) {
    975          ERROR("Error converting to raw format\n");
    976          return EXIT_FAILURE;
    977       }
    978       INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.bin_offset, config.bin_filename);
    979       flength = fprint_write_output(bin_fp, config.encoding, raw, length);
    980       if (config.encoding == ENCODING_RAW && flength != length) {
    981          ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename);
    982       }
    983       INFO("Wrote 0x%X bytes to \"%s\"\n", flength, config.bin_filename);
    984       if (bin_fp != stdout) {
    985          fclose(bin_fp);
    986       }
    987 
    988    } else {
    989       if (config.width <= 0 || config.height <= 0 || config.format.depth <= 0) {
    990          ERROR("Error: must set position width and height for export\n");
    991          return EXIT_FAILURE;
    992       }
    993       bin_fp = fopen(config.bin_filename, "rb");
    994       if (!bin_fp) {
    995          ERROR("Error opening \"%s\"\n", config.bin_filename);
    996          return -1;
    997       }
    998       raw_size = (config.width * config.height * config.format.depth + 7) / 8;
    999       raw = malloc(raw_size);
   1000       if (config.bin_offset > 0) {
   1001          fseek(bin_fp, config.bin_offset, SEEK_SET);
   1002       }
   1003       flength = fread(raw, 1, raw_size, bin_fp);
   1004       if (flength != raw_size) {
   1005          ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename);
   1006       }
   1007       switch (config.format.format) {
   1008          case IMG_FORMAT_RGBA:
   1009             imgr = raw2rgba(raw, config.width, config.height, config.format.depth);
   1010             res = rgba2png(config.img_filename, imgr, config.width, config.height);
   1011             break;
   1012          case IMG_FORMAT_IA:
   1013             imgi = raw2ia(raw, config.width, config.height, config.format.depth);
   1014             res = ia2png(config.img_filename, imgi, config.width, config.height);
   1015             break;
   1016          case IMG_FORMAT_I:
   1017             imgi = raw2i(raw, config.width, config.height, config.format.depth);
   1018             res = ia2png(config.img_filename, imgi, config.width, config.height);
   1019             break;
   1020          case IMG_FORMAT_CI:
   1021          {
   1022             FILE *pal_fp;
   1023             uint8_t *pal;
   1024             uint8_t *raw_fmt;
   1025             int pal_size;
   1026 
   1027             INFO("Extracting %s offset 0x%X, pal.offset 0x%0X, pal.format %s\n", format2str(&config.format),
   1028                  config.bin_offset, config.pal_offset, format2str(&config.pal_format));
   1029 
   1030             pal_fp = fopen(config.pal_filename, "rb");
   1031             if (!pal_fp) {
   1032                ERROR("Error opening \"%s\"\n", config.bin_filename);
   1033                return EXIT_FAILURE;
   1034             }
   1035             if (config.pal_offset > 0) {
   1036                fseek(pal_fp, config.pal_offset, SEEK_SET);
   1037             }
   1038 
   1039             pal_size = sizeof(uint16_t) * (1 << config.format.depth);
   1040             INFO("Palette size: %d\n", pal_size);
   1041             pal = malloc(pal_size);
   1042             flength = fread(pal, 1, pal_size, pal_fp);
   1043             if (flength != pal_size) {
   1044                ERROR("Error reading %d bytes from \"%s\"\n", pal_size, config.pal_filename);
   1045             }
   1046             raw_fmt = ci2raw(raw, pal, config.width, config.height, config.format.depth);
   1047             switch (config.pal_format.format) {
   1048                case IMG_FORMAT_RGBA:
   1049                   INFO("Converting raw to RGBA16\n");
   1050                   imgr = raw2rgba(raw_fmt, config.width, config.height, config.pal_format.depth);
   1051                   res = rgba2png(config.img_filename, imgr, config.width, config.height);
   1052                   break;
   1053                case IMG_FORMAT_IA:
   1054                   INFO("Converting raw to IA16\n");
   1055                   imgi = raw2ia(raw_fmt, config.width, config.height, config.pal_format.depth);
   1056                   res = ia2png(config.img_filename, imgi, config.width, config.height);
   1057                   break;
   1058                default:
   1059                   ERROR("Unsupported palette format: %s\n", format2str(&config.pal_format));
   1060                   return EXIT_FAILURE;
   1061             }
   1062             free(raw_fmt);
   1063             free(pal);
   1064             break;
   1065          }
   1066          default:
   1067             return EXIT_FAILURE;
   1068       }
   1069       if (!res) {
   1070          ERROR("Error writing to \"%s\"\n", config.img_filename);
   1071          return EXIT_FAILURE;
   1072       }
   1073    }
   1074 
   1075    return EXIT_SUCCESS;
   1076 }
   1077 #endif // N64GRAPHICS_STANDALONE