git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

gbdk-support/romusage/src/rom_file.c

      1 // This is free and unencumbered software released into the public domain.
      2 // For more information, please refer to <https://unlicense.org>
      3 // bbbbbr 2020
      4 
      5 #include <stdio.h>
      6 #include <string.h>
      7 #include <stdlib.h>
      8 #include <unistd.h>
      9 #include <stdbool.h>
     10 #include <stdint.h>
     11 
     12 #include "common.h"
     13 #include "logging.h"
     14 #include "list.h"
     15 #include "banks.h"
     16 #include "rom_file.h"
     17 
     18 
     19 #define ADDR_UNSET 0xFFFFFFFF
     20 
     21 #define EMPTY_RUN_TOO_SHORT(length, threshold) ((length > 0) && (length < threshold))
     22 
     23 #define EMPTY_VALUE_MAX_COUNT               256
     24 #define EMPTY_DEFAULT_CONSECUTIVE_THRESHOLD 17
     25 #define EMPTY_0x00_CONSECUTIVE_THRESHOLD    128  // Larger threshold for 0x00 empty values due to possible sparse arrays
     26 bool     empty_values[EMPTY_VALUE_MAX_COUNT];
     27 uint32_t empty_consecutive_thresholds[EMPTY_VALUE_MAX_COUNT];
     28 
     29 #define BANK_SIZE 0x4000
     30 #define BANK_ADDR_MASK 0x00003FFF
     31 #define BANK_NUM_MASK  0xFFFFC000
     32 #define BANK_NUM_UPSHIFT 2
     33 
     34 #define ROM_ADDR_TO_BANKED(addr) ((addr & BANK_ADDR_MASK) | ((addr & BANK_NUM_MASK) << BANK_NUM_UPSHIFT))
     35 
     36 
     37 
     38 // Clear the empty values table to all values disabled
     39 // should be called to remove defaults
     40 void romfile_empty_value_table_clear(void) {
     41     for (int c = 0; c < EMPTY_VALUE_MAX_COUNT; c++)
     42         empty_values[c] = false;
     43 }
     44 
     45 
     46 // Set a byte value in the empty values table to true, range is 0-255 (byte)
     47 void romfile_empty_value_table_add_entry(uint8_t value) {
     48     empty_values[value] = true;
     49 }
     50 
     51 
     52 // Call this before processing option arguments
     53 void romfile_init_defaults(void) {
     54 
     55     // Default is: only value considered empty is 0xFF
     56     romfile_empty_value_table_clear();
     57     empty_values[0xFF] = true;
     58 
     59     // "Empty" 0x00 byte values use a longer run length threshold than other values due to possible sparse arrays
     60     for (int c = 0; c < EMPTY_VALUE_MAX_COUNT; c++) {
     61         empty_consecutive_thresholds[c] = EMPTY_DEFAULT_CONSECUTIVE_THRESHOLD;
     62     }
     63     empty_consecutive_thresholds[0x00] = EMPTY_0x00_CONSECUTIVE_THRESHOLD;
     64 }
     65 
     66 
     67 // Read from a file into a buffer (will allocate needed memory)
     68 // Returns NULL if reading file didn't succeed
     69 uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) {
     70 
     71     long fsize;
     72     FILE * file_in = fopen(filename, "rb");
     73     uint8_t * filedata = NULL;
     74 
     75     if (file_in) {
     76         // Get file size
     77         fseek(file_in, 0, SEEK_END);
     78         fsize = ftell(file_in);
     79         if (fsize != -1L) {
     80             fseek(file_in, 0, SEEK_SET);
     81 
     82             filedata = (uint8_t *)malloc(fsize);
     83             if (filedata) {
     84                 if (fsize != fread(filedata, 1, fsize, file_in)) {
     85                     log_warning("Warning: File read size didn't match expected for %s\n", filename);
     86                     filedata = NULL;
     87                 }
     88                 // Read was successful, set return size
     89                 *ret_size = fsize;
     90             } else log_error("Error: Failed to allocate memory to read file %s\n", filename);
     91 
     92         } else log_error("Error: Failed to read size of file %s\n", filename);
     93 
     94         fclose(file_in);
     95     } else log_error("Error: Failed to open input file %s\n", filename);
     96 
     97     return filedata;
     98 }
     99 
    100 
    101 // Calculate a range, adjust it's bank num and add try adding to banks if valid
    102 static void rom_add_range(area_item range, bool romsize_32K_or_less) {
    103 
    104     // If active range ended, add to areas
    105     if (range.start != ADDR_UNSET) {
    106 
    107         // Don't try to virtual translate address for binary ROM
    108         // files 32K or smaller, most likely they're unbanked.
    109         if (!romsize_32K_or_less) {
    110             // convert from ROM banked format (linear, bits in .14+)
    111             // to expected banked format (bank in bits .16+)
    112             range.start = ROM_ADDR_TO_BANKED(range.start);
    113             range.end = ROM_ADDR_TO_BANKED(range.end);
    114 
    115             // Banks 1+ all start at 0x4000
    116             if (BANK_GET_NUM(range.start) >= 1) {
    117                 range.start += BANK_SIZE;
    118                 range.end += BANK_SIZE;
    119             }
    120         }
    121 
    122         // Calc length and add to banks
    123         if (range.end >= range.start) {
    124             range.length = range.end - range.start + 1;
    125             // printf("ROM ADD Range: %8x -> %8x, %d (at %8x)\n", range.start, range.end, range.length, cur_idx);
    126             banks_check(range);
    127         }
    128     }
    129 }
    130 
    131 
    132 
    133 int rom_file_process(char * filename_in) {
    134 
    135     char cols;
    136     uint8_t * p_buf = NULL;
    137     uint32_t buf_idx = 0;
    138     uint32_t buf_length = 0;
    139     uint32_t empty_run_length;
    140     uint32_t empty_run_length_threshold;
    141     uint8_t  empty_run_value;
    142     uint32_t bank_bytes = 0;
    143     area_item used_rom_range;
    144     bool romsize_32K_or_less;
    145 
    146     set_option_input_source(OPT_INPUT_SRC_ROM);
    147 
    148     // Read in ROM image
    149     p_buf = file_read_into_buffer(filename_in, &buf_length);
    150     romsize_32K_or_less = (buf_length <= 0x8000);
    151 
    152     used_rom_range.name[0] = '\0';  // Rom file ranges don't have names, set string to empty
    153 
    154     if (p_buf) {
    155 
    156         // Loop through all ROM bytes
    157         while (buf_idx < buf_length) {
    158 
    159             // This is looking for "Used" ranges broken up by non-"Empty" ranges
    160             //
    161             // Process each bank (0x4000 bytes in a row) separately
    162             // and close out any ranges that might span between them
    163 
    164             if (buf_length > BANK_SIZE) bank_bytes = BANK_SIZE;
    165             else bank_bytes = buf_length;
    166 
    167             // Reset range state values for each bank pass
    168             used_rom_range.start = ADDR_UNSET;
    169             empty_run_length = 0;
    170 
    171             while (bank_bytes) {
    172 
    173                 // Split buffer up into potential runs of "Used" bytes with a
    174                 // threshold of N non-empty same value bytes in a row to split them up
    175 
    176                 uint8_t cur_byte_value = p_buf[buf_idx];
    177 
    178                 // Continue an existing run if the value matches the current runs value (0x00 or 0xFF)
    179                 if ((empty_run_length > 0) && (cur_byte_value == empty_run_value)) {
    180                     empty_run_length++;
    181 
    182                     // If the current "Empty" range is over the threshold it means
    183                     // any existing "Used" data range needs to be closed out and submitted.
    184                     //
    185                     // Otherwise the "Empty" values may be part of data and can be ignored
    186                     if ((empty_run_length >= empty_run_length_threshold) &&
    187                         (used_rom_range.start != ADDR_UNSET)) {
    188 
    189                         // Add range and back-calculate last "Used" range address using start of "empty" address
    190                         used_rom_range.end = buf_idx - empty_run_length;
    191                         rom_add_range(used_rom_range, romsize_32K_or_less);
    192                         // Clear as ready for new range
    193                         used_rom_range.start = ADDR_UNSET;
    194                     }
    195                 }
    196                 else {
    197                     // Close out pending failed (too short) "Empty" run
    198                     if (EMPTY_RUN_TOO_SHORT(empty_run_length, empty_run_length_threshold)) {
    199                         // If no range started, then convert it to a "Used" range since the bytes aren't actually empty
    200                         if (used_rom_range.start == ADDR_UNSET) used_rom_range.start = buf_idx - empty_run_length;
    201                     }
    202 
    203                     // Start a potential "Empty" new run
    204                     if (empty_values[cur_byte_value] == true) {
    205                         empty_run_length = 1;
    206                         empty_run_value = cur_byte_value;
    207                         // "Empty" 0x00 byte values use a longer run length threshold due to possible sparse arrays
    208                         empty_run_length_threshold = empty_consecutive_thresholds[cur_byte_value];
    209                     }
    210                     // Not "Empty", so reset "Empty" length
    211                     else {
    212                         empty_run_length = 0;
    213                         // Start a potential "Used" (non-empty) data range if one isn't active.
    214                         if (used_rom_range.start == ADDR_UNSET) used_rom_range.start = buf_idx;
    215                     }
    216 
    217                 }
    218 
    219                 buf_idx++;
    220                 bank_bytes--;
    221             } // end: while still _bank_ bytes to process
    222 
    223             // End of Bank Cleanup
    224 
    225             // Potentially Empty bytes at the end of a bank that don't meet threshold... might be empty?
    226             //
    227             // // Convert "Empty" run to "Used" if it didn't cross the "Empty" threshold
    228             // if (EMPTY_RUN_TOO_SHORT(empty_run_length, empty_run_length_threshold)) {
    229             //     if (used_rom_range.start == ADDR_UNSET) used_rom_range.start = buf_idx - empty_run_length;
    230             // }
    231 
    232             // Close pending "Used" run if needed (at last byte which is -1 of current)
    233             if (used_rom_range.start != ADDR_UNSET) {
    234                 used_rom_range.end = (buf_idx - 1);
    235                 rom_add_range(used_rom_range, romsize_32K_or_less);
    236             }
    237 
    238         } // End main buffer loop
    239 
    240         if (p_buf) {
    241             free(p_buf);
    242             p_buf = NULL;
    243         }
    244 
    245     } // end: if valid file
    246     else {
    247         log_error("Error: Failed to open input file %s\n", filename_in);
    248         return false;
    249     }
    250 
    251    return true;
    252 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.