sm64

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

skyconv.c (19956B)


      1 /* skybox generator */
      2 
      3 #define _GNU_SOURCE
      4 #include <assert.h>
      5 #include <string.h>
      6 #include <stdint.h>
      7 #include <stdlib.h>
      8 #include <limits.h>
      9 #include <stdio.h>
     10 #include <stdbool.h>
     11 #include <math.h>
     12 
     13 #include "sm64tools/n64graphics.h"
     14 #include "sm64tools/utils.h"
     15 
     16 #define SKYCONV_ENCODING ENCODING_U8
     17 
     18 typedef struct {
     19     rgba *px;
     20     bool useless;
     21     unsigned int pos;
     22 } TextureTile;
     23 
     24 typedef enum {
     25     InvalidType = -1,
     26     Skybox,
     27     Cake,
     28     CakeEU,
     29     CakeCN,
     30     ImageType_MAX
     31 } ImageType;
     32 
     33 typedef enum {
     34     InvalidMode = -1,
     35     Combine,
     36     Split
     37 } OperationMode;
     38 
     39 typedef struct {
     40     int imageWidth, imageHeight;
     41     int tileWidth, tileHeight;
     42     int numCols, numRows;
     43     bool wrapX;
     44     bool optimizePositions;
     45 } ImageProps;
     46 
     47 static const ImageProps IMAGE_PROPERTIES[ImageType_MAX][2] = {
     48     [Skybox] = {
     49         {248, 248, 31, 31, 8, 8, true, true},
     50         {256, 256, 32, 32, 8, 8, true, true},
     51     },
     52     [Cake] = {
     53         {316, 228, 79, 19, 4, 12, false, false},
     54         {320, 240, 80, 20, 4, 12, false, false},
     55     },
     56     [CakeEU] = {
     57         {320, 224, 64, 32, 5, 7, false, false},
     58         {320, 224, 64, 32, 5, 7, false, false},
     59     },
     60     [CakeCN] = {
     61         {316, 228, 79, 19, 4, 12, false, false},
     62         {320, 240, 80, 20, 4, 12, false, false},
     63     },
     64 };
     65 
     66 typedef struct {
     67     int cols, rows;
     68 } TableDimension;
     69 
     70 static const TableDimension TABLE_DIMENSIONS[ImageType_MAX] = {
     71     [Skybox]   = {8, 10},
     72     [Cake]     = {4, 12},
     73     [CakeEU]   = {5,  7},
     74     [CakeCN]   = {4, 12},
     75 };
     76 
     77 TextureTile *tiles;
     78 ImageType type = InvalidType;
     79 OperationMode mode = InvalidMode;
     80 char *programName;
     81 char *input, *output;
     82 char *writeDir;
     83 char skyboxName[256];
     84 bool expanded = false;
     85 bool writeTiles;
     86 
     87 static void allocate_tiles() {
     88     const ImageProps props = IMAGE_PROPERTIES[type][true];
     89     int tileWidth = props.tileWidth;
     90     int tileHeight = props.tileHeight;
     91     int numRows = props.numRows;
     92     int numCols = props.numCols;
     93 
     94     int tileSize = tileWidth * tileHeight * sizeof(rgba);
     95     int totalSize = numRows * numCols * tileSize;
     96     tiles = calloc(1, numRows * numCols * sizeof(TextureTile));
     97     rgba *tileData = calloc(1, totalSize);
     98     for (int row = 0; row < numRows; ++row) {
     99         for (int col = 0; col < numCols; ++col) {
    100             tiles[row * numCols + col].px = (tileData + (row * numCols + col) * (tileWidth * tileHeight));
    101         }
    102     }
    103 }
    104 
    105 static void free_tiles() {
    106     free(tiles->px);
    107     free(tiles);
    108 }
    109 
    110 static void split_tile(int col, int row, rgba *image, bool expanded) {
    111     const ImageProps props = IMAGE_PROPERTIES[type][expanded];
    112     int tileWidth = props.tileWidth;
    113     int tileHeight = props.tileHeight;
    114     int imageWidth = props.imageWidth;
    115     int numCols = props.numCols;
    116 
    117     int expandedWidth = IMAGE_PROPERTIES[type][true].tileWidth;
    118 
    119     for (int y = 0; y < tileHeight; y++) {
    120         for (int x = 0; x < tileWidth; x++) {
    121             int ny = row * tileHeight + y;
    122             int nx = col * tileWidth + x;
    123             tiles[row * numCols + col].px[y * expandedWidth + x] = image[(ny * imageWidth + nx)];
    124         }
    125     }
    126 }
    127 
    128 static void expand_tiles(ImageType imageType) {
    129     const ImageProps props = IMAGE_PROPERTIES[imageType][true];
    130     int numRows = props.numRows;
    131     int numCols = props.numCols;
    132     int tileWidth = props.tileWidth;
    133     int tileHeight = props.tileHeight;
    134 
    135     // If the image type wraps,
    136     // Copy each tile's left edge to the previous tile's right edge
    137     // Each tile's height is still tileHeight - 1
    138     if (props.wrapX) {
    139         for (int row = 0; row < numRows; ++row) {
    140             for (int col = 0; col < numCols; ++col) {
    141                 int nextCol = (col + 1) % numCols;
    142                 for (int y = 0; y < (tileHeight - 1); ++y) {
    143                     tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
    144                 }
    145             }
    146         }
    147     } else {
    148         // Don't wrap, copy the second to last column instead.
    149         for (int row = 0; row < numRows; ++row) {
    150             for (int col = 0; col < numCols - 1; ++col) {
    151                 int nextCol = (col + 1) % numCols;
    152                 for (int y = 0; y < (tileHeight - 1); ++y) {
    153                     tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
    154                 }
    155             }
    156             for (int y = 0; y < (tileHeight - 1); ++y) {
    157                 tiles[row * numCols + (numCols - 1)].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + (numCols - 1)].px[(tileWidth - 2) + y * tileWidth];
    158             }
    159 
    160         }
    161     }
    162 
    163     // Copy each tile's top edge to the previous tile's bottom edge, EXCEPT for the bottom row, which
    164     // just duplicates its second-to-last row
    165     for (int row = 0; row < numRows; ++row) {
    166         if (row < numRows - 1) {
    167             for (int col = 0; col < numCols; ++col) {
    168                 int nextRow = (row + 1) % numRows;
    169                 for (int x = 0; x < tileWidth; ++x) {
    170                     tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[nextRow * numCols + col].px[x];
    171                 }
    172             }
    173         }
    174         // For the last row of tiles, duplicate each one's second to last row
    175         else {
    176             for (int col = 0; col < numCols; ++col) {
    177                 for (int x = 0; x < tileWidth; ++x) {
    178                     tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[row * numCols + col].px[x + (tileHeight - 2) * tileWidth];
    179                 }
    180             }
    181         }
    182     }
    183 }
    184 
    185 static void init_tiles(rgba *image, bool expanded) {
    186     const ImageProps props = IMAGE_PROPERTIES[type][expanded];
    187 
    188     for (int row = 0; row < props.numRows; row++) {
    189         for (int col = 0; col < props.numCols; col++) {
    190             split_tile(col, row, image, expanded);
    191         }
    192     }
    193 
    194     // Expand the tiles to their full size
    195     if (!expanded) {
    196         expand_tiles(type);
    197     }
    198 }
    199 
    200 static void assign_tile_positions() {
    201     const ImageProps props = IMAGE_PROPERTIES[type][true];
    202     const size_t TILE_SIZE = props.tileWidth * props.tileHeight * sizeof(rgba);
    203 
    204     unsigned int newPos = 0;
    205     for (int i = 0; i < props.numRows * props.numCols; i++) {
    206         if (props.optimizePositions) {
    207             for (int j = 0; j < i; j++) {
    208                 if (!tiles[j].useless && memcmp(tiles[j].px, tiles[i].px, TILE_SIZE) == 0) {
    209                     tiles[i].useless = 1;
    210                     tiles[i].pos = j;
    211                     break;
    212                 }
    213             }
    214         }
    215 
    216         if (!tiles[i].useless) {
    217             tiles[i].pos = newPos;
    218             newPos++;
    219         }
    220     }
    221 }
    222 
    223 // Provide a replacement for realpath on Windows
    224 #ifdef _WIN32
    225 #define realpath(path, resolved_path) _fullpath(resolved_path, path, PATH_MAX)
    226 #endif
    227 
    228 /* write pngs to disc */
    229 void write_tiles() {
    230     const ImageProps props = IMAGE_PROPERTIES[type][true];
    231     char buffer[PATH_MAX];
    232     char skyboxName[PATH_MAX];
    233 
    234     if (realpath(writeDir, buffer) == NULL) {
    235         fprintf(stderr, "err: Could not find find img dir %s", writeDir);
    236         exit(EXIT_FAILURE);
    237     }
    238 
    239     strcat(buffer, "/");
    240 
    241     switch(type) {
    242         case Skybox:
    243             strcat(buffer, skyboxName);
    244         break;
    245         case Cake:
    246             strcat(buffer, "cake");
    247         break;
    248         case CakeEU:
    249             strcat(buffer, "cake_eu");
    250         break;
    251         case CakeCN:
    252             strcat(buffer, "cake_cn");
    253         break;
    254         default:
    255             exit(EXIT_FAILURE);
    256         break;
    257     }
    258 
    259     int dirLength = strlen(buffer);
    260     char *filename = buffer + dirLength;
    261     for (int i = 0; i < props.numRows * props.numCols; i++) {
    262         if (!tiles[i].useless) {
    263             *filename = 0;
    264             snprintf(filename, PATH_MAX, ".%d.rgba16.png", tiles[i].pos);
    265             rgba2png(buffer, tiles[i].px, props.tileWidth, props.tileHeight);
    266         }
    267     }
    268 }
    269 
    270 static unsigned int get_index(TextureTile *t, unsigned int i) {
    271     if (t[i].useless) {
    272         i = t[i].pos;
    273     }
    274     return t[i].pos;
    275 }
    276 
    277 static void print_raw_data(FILE *cFile, TextureTile *tile) {
    278     ImageProps props = IMAGE_PROPERTIES[type][true];
    279     uint8_t *raw = malloc(props.tileWidth * props.tileHeight * 2);
    280     int size = rgba2raw(raw, tile->px, props.tileWidth, props.tileHeight, 16);
    281     fprint_write_output(cFile, SKYCONV_ENCODING, raw, size);
    282     free(raw);
    283 }
    284 
    285 static void write_skybox_c() { /* write c data to disc */
    286     const ImageProps props = IMAGE_PROPERTIES[type][true];
    287 
    288     char fBuffer[PATH_MAX] = "";
    289     FILE *cFile;
    290 
    291     if (realpath(output, fBuffer) == NULL) {
    292         fprintf(stderr, "err: Could not find find src dir %s", output);
    293         exit(EXIT_FAILURE);
    294     }
    295 
    296     sprintf(fBuffer, "%s/%s_skybox.c", output, skyboxName);
    297     cFile = fopen(fBuffer, "w"); /* reset file */
    298 
    299     /* setup C file */
    300 
    301     if (cFile == NULL) {
    302         fprintf(stderr, "err: Could not open %s\n", fBuffer);
    303     }
    304 
    305     fprintf(cFile, "#include \"types.h\"\n\n#include \"make_const_nonconst.h\"\n\n");
    306 
    307     for (int i = 0; i < props.numRows * props.numCols; i++) {
    308         if (!tiles[i].useless) {
    309             fprintf(cFile, "ALIGNED8 static const Texture %s_skybox_texture_%05X[] = {\n", skyboxName, tiles[i].pos);
    310 
    311             print_raw_data(cFile, &tiles[i]);
    312 
    313             fputs("};\n\n", cFile);
    314         }
    315     }
    316 
    317     fprintf(cFile, "const Texture *const %s_skybox_ptrlist[] = {\n", skyboxName);
    318 
    319     for (int row = 0; row < 8; row++) {
    320         for (int col = 0; col < 10; col++) {
    321             fprintf(cFile, "%s_skybox_texture_%05X,\n", skyboxName, get_index(tiles, row * 8 + (col % 8)));
    322         }
    323     }
    324 
    325     fputs("};\n\n", cFile);
    326     fclose(cFile);
    327 }
    328 
    329 static void write_cake_c() {
    330     char buffer[PATH_MAX] = "";
    331     if (realpath(output, buffer) == NULL) {
    332         fprintf(stderr, "err: Could not find find src dir %s", output);
    333         exit(EXIT_FAILURE);
    334     }
    335 
    336     if (type == CakeCN) {
    337         strcat(buffer, "/cake_cn.inc.c");
    338     }
    339     else if (type == CakeEU) {
    340         strcat(buffer, "/cake_eu.inc.c");
    341     }
    342     else {
    343         strcat(buffer, "/cake.inc.c");
    344     }
    345 
    346     FILE *cFile = fopen(buffer, "w");
    347 
    348     const char *euSuffx = "";
    349     if (type == CakeEU) {
    350         euSuffx = "eu_";
    351     }
    352 
    353     int numTiles = TABLE_DIMENSIONS[type].cols * TABLE_DIMENSIONS[type].rows;
    354     for (int i = 0; i < numTiles; ++i) {
    355         fprintf(cFile, "ALIGNED8 static const Texture cake_end_texture_%s%d[] = {\n", euSuffx, i);
    356         print_raw_data(cFile, &tiles[i]);
    357         fputs("};\n\n", cFile);
    358     }
    359     fclose(cFile);
    360 }
    361 
    362 // input: the skybox tiles + the table = up to 64 32x32 images (rgba16) + 80 pointers (u32)
    363 // some pointers point to duplicate entries
    364 void combine_skybox(const char *input, const char *output) {
    365     enum { W = 10, H = 8, W2 = 8 };
    366 
    367     FILE *file = fopen(input, "rb");
    368     if (!file) goto fail;
    369     if (fseek(file, 0, SEEK_END)) goto fail;
    370 
    371     ssize_t fileSize = ftell(file);
    372     if (fileSize < 8*10*4) goto fail;
    373     rewind(file);
    374 
    375     size_t tableIndex = fileSize - 8*10*4;
    376     if (tableIndex % (32*32*2) != 0) goto fail;
    377 
    378     // there are at most 64 tiles before the table
    379     rgba *tiles[8*8];
    380     size_t tileIndex = 0;
    381     for (size_t pos = 0; pos < tableIndex; pos += 32*32*2) {
    382         uint8_t buf[32*32*2];
    383         if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
    384         tiles[tileIndex] = raw2rgba(buf, 32, 32, 16);
    385         tileIndex++;
    386     }
    387 
    388     uint32_t table[W*H];
    389     if (fread(table, sizeof(table), 1, file) != 1) goto fail;
    390 
    391     reverse_endian((unsigned char *) table, W*H*4);
    392 
    393     uint32_t base = table[0];
    394     for (int i = 0; i < W*H; i++) {
    395         table[i] -= base;
    396     }
    397 
    398     // Convert the 256x256 skybox to an editable 248x248 image by skipping the duplicated rows and columns
    399     // every 32nd column is a repeat of the 33rd, and
    400     // every 32nd row is a repeat of the 33rd, EXCEPT for the last row, but that only matters when
    401     // expanding the tiles
    402     rgba combined[31*H * 31*W2];
    403     for (int i = 0; i < H; i++) {
    404         for (int j = 0; j < W2; j++) {
    405             int index = table[i*W+j] / 0x800;
    406             for (int y = 0; y < 31; y++) {
    407                 for (int x = 0; x < 31; x++) {
    408                     combined[(i*31 + y) * (31*W2) + (j*31 + x)] = tiles[index][y*32 + x];
    409                 }
    410             }
    411         }
    412     }
    413     if (!rgba2png(output, combined, 31*W2, 31*H)) {
    414         fprintf(stderr, "Failed to write skybox image.\n");
    415         exit(1);
    416     }
    417     return;
    418 fail:
    419     fprintf(stderr, "Failed to read skybox binary.\n");
    420     exit(1);
    421 }
    422 
    423 void combine_cakeimg(const char *input, const char *output) {
    424     int W, H, SMALLH, SMALLW;
    425     if (type == CakeEU) {
    426         W = 5;
    427         H = 7;
    428         SMALLH = 32;
    429         SMALLW = 64;
    430     } else {
    431         W = 4;
    432         H = 12;
    433         SMALLH = 20;
    434         SMALLW = 80;
    435     }
    436 
    437     FILE *file = fopen(input, "rb");
    438     if (!file) goto fail;
    439 
    440     rgba *combined;
    441     if (type == Cake) {
    442         combined = malloc((SMALLH-1)*H * (SMALLW-1)*W * sizeof(rgba));
    443         for (int i = 0; i < H; i++) {
    444             for (int j = 0; j < W; j++) {
    445                 //Read the full tile
    446                 uint8_t buf[SMALLH * SMALLW * 2];
    447                 if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
    448                 rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
    449 
    450                 //Only write the unique parts of each tile
    451                 for (int y = 0; y < SMALLH - 1; y++) {
    452                     for (int x = 0; x < SMALLW - 1; x++) {
    453                         combined[(i*(SMALLH-1) + y) * (SMALLW-1)*W + (j*(SMALLW-1) + x)] = tile[y*(SMALLW) + x];
    454                     }
    455                 }
    456             }
    457         }
    458         if (!rgba2png(output, combined, (SMALLW-1)*W, (SMALLH-1)*H)) {
    459             fprintf(stderr, "Failed to write cake image.\n");
    460             exit(1);
    461         }
    462     }
    463     else {
    464         combined = malloc(SMALLH*H * SMALLW*W * sizeof(rgba));
    465         for (int i = 0; i < H; i++) {
    466             for (int j = 0; j < W; j++) {
    467                 uint8_t buf[SMALLH * SMALLW * 2];
    468                 if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
    469                 rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
    470                 for (int y = 0; y < SMALLH; y++) {
    471                     for (int x = 0; x < SMALLW; x++) {
    472                         combined[(i*SMALLH + y) * SMALLW*W + (j*SMALLW + x)] = tile[y*SMALLW + x];
    473                     }
    474                 }
    475             }
    476         }
    477         if (!rgba2png(output, combined, SMALLW*W, SMALLH*H)) {
    478             fprintf(stderr, "Failed to write cake image.\n");
    479             exit(1);
    480         }
    481     }
    482     return;
    483 fail:
    484     fprintf(stderr, "Failed to read cake binary.\n");
    485     exit(1);
    486 }
    487 
    488 // Modified from n64split
    489 static void usage() {
    490     fprintf(stderr,
    491             "Usage: %s --type sky|cake|cake-eu|cake-cn {--combine INPUT OUTPUT | --split INPUT OUTPUT}\n"
    492             "\n"
    493             "Optional arguments:\n"
    494             " --write-tiles OUTDIR      Also create the individual tiles' PNG files\n", programName);
    495 }
    496 
    497 // Modified from n64split
    498 static int parse_arguments(int argc, char *argv[]) {
    499     programName = argv[0];
    500     for (int i = 1; i < argc; ++i) {
    501         if (strcmp(argv[i], "--combine") == 0) {
    502             if (++i >= argc || mode != InvalidMode) {
    503                 goto invalid;
    504             }
    505 
    506             mode = Combine;
    507             input = argv[i];
    508             if (++i >= argc) {
    509                 goto invalid;
    510             }
    511 
    512             output = argv[i];
    513         }
    514 
    515         if (strcmp(argv[i], "--split") == 0) {
    516             if (++i >= argc || mode != InvalidMode) {
    517                 goto invalid;
    518             }
    519 
    520             mode = Split;
    521             input = argv[i];
    522             if (++i >= argc) {
    523                 goto invalid;
    524             }
    525 
    526             output = argv[i];
    527         }
    528 
    529         if (strcmp(argv[i], "--type") == 0) {
    530             if (++i >= argc || type != InvalidType) {
    531                 goto invalid;
    532             }
    533 
    534             if (strcmp(argv[i], "sky") == 0) {
    535                 type = Skybox;
    536             } else if(strcmp(argv[i], "cake-cn") == 0) {
    537                 type = CakeCN;
    538             } else if(strcmp(argv[i], "cake-eu") == 0) {
    539                 type = CakeEU;
    540             } else if(strcmp(argv[i], "cake") == 0) {
    541                 type = Cake;
    542             }
    543         }
    544 
    545         if (strcmp(argv[i], "--write-tiles") == 0) {
    546             if (++i >= argc || argv[i][0] == '-') {
    547                 goto invalid;
    548             }
    549 
    550             writeTiles = true;
    551             writeDir = argv[i];
    552         }
    553     }
    554 
    555     return 1;
    556 invalid:
    557     usage();
    558     return 0;
    559 }
    560 
    561 bool imageMatchesDimensions(int width, int height) {
    562     bool matchesDimensions = false;
    563     for (int expand = false; expand <= true; ++expand) {
    564         if (width  == IMAGE_PROPERTIES[type][expand].imageWidth &&
    565             height == IMAGE_PROPERTIES[type][expand].imageHeight) {
    566             matchesDimensions = true;
    567             expanded = expand;
    568             break;
    569         }
    570     }
    571     if (!matchesDimensions) {
    572         if (type != CakeEU) {
    573             fprintf(stderr, "err: That type of image must be either %d x %d or %d x %d. Yours is %d x %d.\n",
    574                     IMAGE_PROPERTIES[type][false].imageWidth, IMAGE_PROPERTIES[type][false].imageHeight,
    575                     IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
    576                     width, height);
    577         }
    578         else {
    579             fprintf(stderr, "err: That type of image must be %d x %d. Yours is %d x %d.\n",
    580                     IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
    581                     width, height);
    582 
    583         }
    584         return false;
    585     }
    586 
    587     if (type == CakeEU) {
    588         expanded = true;
    589     }
    590 
    591     return true;
    592 }
    593 
    594 int main(int argc, char *argv[]) {
    595     if (parse_arguments(argc, argv) == false) {
    596         return EXIT_FAILURE;
    597     }
    598 
    599     if (type == Skybox && mode == Split) {
    600         // Extract the skybox's name (ie: bbh, bidw) from the input png
    601         char *base = basename(input);
    602         strcpy(skyboxName, base);
    603         char *extension = strrchr(skyboxName, '.');
    604         if (extension) *extension = '\0';
    605     }
    606 
    607     switch (mode) {
    608         case Combine:
    609             switch (type) {
    610                 case Skybox:
    611                     combine_skybox(input, output);
    612                 break;
    613                 case Cake:
    614                 case CakeEU:
    615                 case CakeCN:
    616                     combine_cakeimg(input, output);
    617                 break;
    618                 default:
    619                     usage();
    620                     return EXIT_FAILURE;
    621                 break;
    622             }
    623         break;
    624 
    625         case Split: {
    626             int width, height;
    627             rgba *image = png2rgba(input, &width, &height);
    628             if (image == NULL) {
    629                 fprintf(stderr, "err: Could not load image %s\n", argv[1]);
    630                 return EXIT_FAILURE;
    631             }
    632 
    633             if (!imageMatchesDimensions(width, height)) {
    634                 return EXIT_FAILURE;
    635             }
    636 
    637             allocate_tiles();
    638             
    639             init_tiles(image, expanded);
    640             switch (type) {
    641                 case Skybox:
    642                     assign_tile_positions();
    643                     write_skybox_c();
    644                     break;
    645                 case Cake:
    646                 case CakeEU:
    647                 case CakeCN:
    648                     assign_tile_positions();
    649                     write_cake_c();
    650                     break;
    651                 default:
    652                     fprintf(stderr, "err: Unknown image type.\n");
    653                     return EXIT_FAILURE;
    654                     break;
    655             }
    656 
    657             if (writeTiles) {
    658                 write_tiles();
    659             }
    660             free_tiles();
    661             free(image);
    662         } break;
    663         default:
    664             usage();
    665             return EXIT_FAILURE;
    666         break;
    667     }
    668 
    669 
    670     return EXIT_SUCCESS;
    671 }