git.y1.nz

gbdk-2020

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

gbdk-support/ihxcheck/ihx_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 "areas.h"
     13 #include "ihx_file.h"
     14 
     15 // Example data to parse from a .ihx file
     16 // No area names
     17 // : 01 0020 00 E9 F6
     18 // S BB AAAA RR DD CC
     19 //
     20 // S:    Start (":") (1 char)
     21 // BB:   ByteCount   (2 chars, one byte)
     22 // AAAA: Address     (4 chars, two bytes)
     23 // RR:   Record Type (2 chars, one byte. 00=data, 01=EOF, Others)
     24 // DD:   Data        (<ByteCount>  bytes)
     25 // CC:   Checksum    (2 chars, one byte. Sum record bytes, 2's complement of LSByte)
     26 /*
     27 :01002000E9F6
     28 :05002800220D20FCC9BF
     29 :070030001A22130D20FAC98A
     30 .
     31 .
     32 .
     33 :00000001FF (EOF indicator)
     34 */
     35 
     36 /* TODO/WARNING/BUG: 100% full banks
     37 It may not be possible to easily tell the difference between a perfectly filled
     38 bank (100% and no more) and an adjacent partially filled bank that starts at
     39 zero - versus - the first bank overflowed into the second that is empty.
     40 
     41 Currently the 100% bank will get merged into the next one and present as overflow
     42 */
     43 
     44 bank_info banks[BANKS_MAX_COUNT] = {
     45     {.overflowed_into = false,
     46      .overflow_from   = 0,
     47      .had_multiple_write_warning = false } };
     48 
     49 #define ADDR_UNSET      0xFFFFFFFEU
     50 
     51 #define MAX_STR_LEN     4096
     52 #define IHX_DATA_LEN_MAX 255
     53 #define IHX_REC_LEN_MIN  (1 + 2 + 4 + 2 + 0 + 2) // Start(1), ByteCount(2), Addr(4), Rec(2), Data(0..255x2), Checksum(2)
     54 
     55 // IHX record types
     56 #define IHX_REC_DATA     0x00U
     57 #define IHX_REC_EOF      0x01U
     58 #define IHX_REC_EXTSEG   0x02U
     59 #define IHX_REC_STARTSEG 0x03U
     60 #define IHX_REC_EXTLIN   0x04U
     61 #define IHX_REC_STARTLIN 0x05U
     62 
     63 typedef struct ihx_record {
     64     uint16_t length;
     65     uint32_t byte_count;
     66     uint32_t address;
     67     uint32_t address_end;
     68     uint32_t type;
     69     uint32_t checksum; // Would prefer this be a uint8_t, but mingw sscanf("%2hhx") has a buffer overflow that corrupts adjacent data
     70 } ihx_record;
     71 
     72 uint32_t g_address_upper;
     73 bool     g_option_warnings_as_errors = false;
     74 
     75 void set_option_warnings_as_errors(bool new_val) {
     76     g_option_warnings_as_errors = new_val;
     77 }
     78 
     79 
     80 // Return false if any character isn't a valid hex digit
     81 static int check_hex(char * c) {
     82     while (*c != '\0') {
     83         if ((*c >= '0') && (*c <= '9'))
     84             c++;
     85         if ((*c >= 'A') && (*c <= 'F'))
     86             c++;
     87         if ((*c >= 'a') && (*c <= 'f'))
     88             c++;
     89         else
     90             return false;
     91     }
     92 
     93     return true;
     94 }
     95 
     96 
     97 // Check for bank overflows under specific conditions
     98 // (fragmented writes of the ihx format make overflows hard to detect reliably)
     99 //
    100 // Call this after processing of all areas has been completed
    101 //
    102 // In order to avoid falsely flagging overflows (including 32K only roms
    103 // where bank 0 is normally allowed to overflow into bank 1) the following
    104 // criteria must be met:
    105 //
    106 // * A multiple write to the same address must occur. The address
    107 //   where the overlap ends is used as the CURRENT BANK.
    108 //
    109 // * There must also be a write which spans multiple banks, the
    110 //   ending address of that must match CURRENT BANK.
    111 //   The starting addresses is the OVERFLOW-FROM BANK.
    112 //
    113 static void ihx_check_for_overflows(void) {
    114 
    115     for (int c = 0; c < BANKS_MAX_COUNT; c++) {
    116         if (banks[c].overflowed_into && banks[c].had_multiple_write_warning) {
    117 
    118             printf("Warning: Possible overflow from Bank %d into Bank %d\n", banks[c].overflow_from, c);
    119         }
    120     }
    121 }
    122 
    123 
    124 // Parse and validate an IHX record
    125 static int ihx_parse_and_validate_record(char * p_str, ihx_record * p_rec) {
    126 
    127         int calc_length = 0;
    128         int c;
    129         uint32_t ctemp, checksum_calc = 0; // Avoid mingw sscanf("%2hhx") buffer overflow with uint8_t
    130 
    131         // Remove trailing CR and LF
    132         p_rec->length = strlen(p_str);
    133         for (c = 0;c < p_rec->length;c++) {
    134             if (p_str[c] == '\n' || p_str[c] == '\r') {
    135                 p_str[c] = '\0';   // Replace char with string terminator
    136                 p_rec->length = c; // Shrink length to truncated size
    137                 break;             // Exit loop after finding first CR or LF
    138             }
    139         }
    140 
    141         // Only parse lines that start with ':' character (Start token for IHX record)
    142         if (p_str[0] != ':') {
    143             printf("Warning: IHX: Invalid start of line token for line: %s \n", p_str);
    144             return false;
    145         }
    146 
    147        // Require minimum length
    148         if (p_rec->length < IHX_REC_LEN_MIN) {
    149             printf("Warning: IHX: Invalid line, too few characters: %s. Is %d, needs at least %d \n", p_str, p_rec->length, IHX_REC_LEN_MIN);
    150             return false;
    151         }
    152 
    153         // Only hex characters are allowed after start token
    154         p_str++; // Advance past Start code
    155         if (check_hex(p_str)) {
    156             printf("Warning: IHX: Invalid line, non-hex characters present: %s\n", p_str);
    157             return false;
    158         }
    159 
    160         // Read record header: byte count, start address, type
    161         sscanf(p_str, "%2x%4x%2x", &p_rec->byte_count, &p_rec->address, &p_rec->type);
    162         p_str += (2 + 4 + 2);
    163 
    164 
    165         // Require expected data byte count to fit within record length (at 2 chars per hex byte)
    166         calc_length = IHX_REC_LEN_MIN + (p_rec->byte_count * 2);
    167         if (p_rec->length != calc_length) {
    168             printf("Warning: IHX: byte count doesn't match length available in record! Record length = %d, Calc length = %d, bytecount = %d \n", p_rec->length, calc_length, p_rec->byte_count);
    169             return false;
    170         }
    171 
    172         // Is this an extended linear address record? Read in offset address if so
    173         if (p_rec->type == IHX_REC_EXTLIN) {
    174             sscanf(p_str, "%4x", &g_address_upper);
    175             g_address_upper <<= 16; // Shift into upper 16 bits of address space
    176         }
    177         else if (p_rec->type == IHX_REC_DATA) {
    178 
    179             // Don't process records with zero bytes of length
    180             if (p_rec->byte_count == 0) {
    181                 printf("Warning: IHX: Zero length record starting at %x\n", p_rec->address);
    182                 return false;
    183             }
    184 
    185             // Apply extended linear address (upper 16 bits of address space)
    186             // Calculate end address
    187             p_rec->address |= g_address_upper;
    188             p_rec->address_end = p_rec->address + p_rec->byte_count - 1;
    189         }
    190 
    191 
    192         // Read data segment and calculate checsum of data + headers
    193         checksum_calc = p_rec->byte_count + (p_rec->address & 0xFF) + ((p_rec->address >> 8) & 0xFF) + p_rec->type;
    194         for (c = 0;c < p_rec->byte_count;c++) {
    195             sscanf(p_str, "%2x", &ctemp);
    196             p_str += 2;
    197             checksum_calc += ctemp;
    198         }
    199 
    200         // Final calculated checeksum is 2's complement of LSByte
    201         checksum_calc = (((checksum_calc & 0xFF) ^ 0xFF) + 1) & 0xFF;
    202 
    203         // Read checksum from data
    204         sscanf(p_str, "%2x", &p_rec->checksum);
    205         p_str += 2;
    206 
    207         if (p_rec->checksum != checksum_calc) {
    208             printf("Warning: IHX: record checksum %x didn't match calculated checksum %x\n", p_rec->checksum, checksum_calc);
    209             return false;
    210         }
    211 
    212         // Make sure banked data is within the supported range
    213         // Currently this caps ROM size at the standard 8MB
    214         if ((BANK_NUM(p_rec->address)     >= BANKS_MAX_COUNT) ||
    215             (BANK_NUM(p_rec->address_end) >= BANKS_MAX_COUNT)) {
    216             printf("Error: IHX: bank start or end number (%d,%d) larger than max %d\n", BANK_NUM(p_rec->address), BANK_NUM(p_rec->address_end), BANKS_MAX_COUNT);
    217             exit(EXIT_FAILURE);
    218         }
    219 
    220         // For records that start in banks above the nonbanked region (0x000 - 0x3FFF)
    221         // Warn (but don't error) if they cross the boundary between different banks
    222         if ((p_rec->address & 0xFFFFC000U) != (p_rec->address_end & 0xFFFFC000U)) {
    223 
    224             // If the start address is above the nonbanked region then it's
    225             // banked data and shouldn't ever occupy two banks at once
    226             if (p_rec->address >= 0x00004000U) {
    227                 printf("Warning: Write from one bank spans into the next. 0x%x -> 0x%x (bank %d -> %d)\n",
    228                        p_rec->address, p_rec->address_end, BANK_NUM(p_rec->address), BANK_NUM(p_rec->address_end));
    229             }
    230             // Log all writes that spans multiple banks, including bank 0 -> 1
    231             // Used later to help check for overflow
    232             banks[BANK_NUM(p_rec->address_end)].overflowed_into = true;
    233             banks[BANK_NUM(p_rec->address_end)].overflow_from = BANK_NUM(p_rec->address);
    234 
    235         }
    236 
    237         return true;
    238 }
    239 
    240 
    241 int ihx_file_process_areas(char * filename_in) {
    242 
    243     int  ret = EXIT_SUCCESS; // default to success
    244     char cols;
    245     char strline_in[MAX_STR_LEN] = "";
    246     FILE * ihx_file = fopen(filename_in, "r");
    247     area_item area;
    248     ihx_record ihx_rec;
    249 
    250     areas_init();
    251 
    252     // Initialize global upper address modifier
    253     g_address_upper = 0x0000;
    254 
    255     // Initialize area record
    256     area.start = ADDR_UNSET;
    257     area.end   = ADDR_UNSET;
    258 
    259 
    260     if (ihx_file) {
    261 
    262         // Read one line at a time into \0 terminated string
    263         while (fgets(strline_in, sizeof(strline_in), ihx_file) != NULL) {
    264 
    265             // Parse record, skip if fails validation
    266             if (!ihx_parse_and_validate_record(strline_in, &ihx_rec))
    267                 continue;
    268 
    269             // Process the pending record and exit if last record (EOF)
    270             // Also ignore non-default data records (don't seem to occur for gbz80)
    271             if (ihx_rec.type == IHX_REC_EOF) {
    272                 if (!areas_add(&area) && g_option_warnings_as_errors)
    273                     ret = EXIT_FAILURE;
    274                 continue;
    275             } else if (ihx_rec.type == IHX_REC_EXTLIN) {
    276                 // printf("Extended linear address changed to %08x %s\n\n\n", g_address_upper, strline_in);
    277                 continue;
    278             } else if (ihx_rec.type != IHX_REC_DATA) {
    279                 printf("Warning: IHX: dropped record %s of type %d\n", strline_in, ihx_rec.type);
    280                 continue;
    281             }
    282 
    283             // Records are left pending (non-processed) until they don't merge
    284             // with the current incoming record *or* the final (EOF) record is found.
    285 
    286             // Try to merge with (pending) previous record if it's address-adjacent,
    287             // except when the new record starts or ends on a bank boundary
    288             // (this reduces count from 1000's since most are only 32 bytes long)
    289             if ((ihx_rec.address == area.end + 1) && ((ihx_rec.address & 0x00003FFFU) != 0x00000000U)) {
    290                 area.end = ihx_rec.address_end;  // append to previous area
    291             } else if ((ihx_rec.address_end == area.start + 1) && !((ihx_rec.address_end & 0x00003FFFU) != 0x00003FFFU)) {
    292                 area.start = ihx_rec.address;    // pre-pend to previous area
    293             } else {
    294                 // New record was *not* adjacent to last,
    295                 // so process the last/pending record
    296                 if (area.start != ADDR_UNSET) {
    297                     if (!areas_add(&area) && g_option_warnings_as_errors)
    298                         ret = EXIT_FAILURE;
    299                 }
    300                 // Now queue current record as pending for next loop
    301                 area.start = ihx_rec.address;
    302                 area.end   = ihx_rec.address + ihx_rec.byte_count - 1;
    303             }
    304 
    305         } // end: while still lines to process
    306 
    307         fclose(ihx_file);
    308 
    309     } // end: if valid file
    310     else {
    311         printf("Problem with filename or unable to open file! %s\n", filename_in);
    312         ret = EXIT_FAILURE;
    313     }
    314 
    315     // Check and warn for possible overflows
    316     ihx_check_for_overflows();
    317 
    318     areas_cleanup();
    319     return ret;
    320 }
    321 

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