ihx_file.c (9743B)
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 37It may not be possible to easily tell the difference between a perfectly filled 38bank (100% and no more) and an adjacent partially filled bank that starts at 39zero - versus - the first bank overflowed into the second that is empty. 40 41Currently the 100% bank will get merged into the next one and present as overflow 42*/ 43 44 45#define BANK_NUM(addr) ((addr & 0xFFFFC000U) >> 14) 46#define ADDR_UNSET 0xFFFFFFFEU 47 48#define MAX_STR_LEN 4096 49#define IHX_DATA_LEN_MAX 255 50#define IHX_REC_LEN_MIN (1 + 2 + 4 + 2 + 0 + 2) // Start(1), ByteCount(2), Addr(4), Rec(2), Data(0..255x2), Checksum(2) 51 52// IHX record types 53#define IHX_REC_DATA 0x00U 54#define IHX_REC_EOF 0x01U 55#define IHX_REC_EXTSEG 0x02U 56#define IHX_REC_STARTSEG 0x03U 57#define IHX_REC_EXTLIN 0x04U 58#define IHX_REC_STARTLIN 0x05U 59 60typedef struct ihx_record { 61 uint16_t length; 62 uint32_t byte_count; 63 uint32_t address; 64 uint32_t address_end; 65 uint32_t type; 66 uint32_t checksum; // Would prefer this be a uint8_t, but mingw sscanf("%2hhx") has a buffer overflow that corrupts adjacent data 67} ihx_record; 68 69uint32_t g_address_upper; 70bool g_option_warnings_as_errors = false; 71 72void set_option_warnings_as_errors(bool new_val) { 73 g_option_warnings_as_errors = new_val; 74} 75 76 77// Return false if any character isn't a valid hex digit 78int check_hex(char * c) { 79 while (*c != '\0') { 80 if ((*c >= '0') && (*c <= '9')) 81 c++; 82 if ((*c >= 'A') && (*c <= 'F')) 83 c++; 84 if ((*c >= 'a') && (*c <= 'f')) 85 c++; 86 else 87 return false; 88 } 89 90 return true; 91} 92 93 94// Parse and validate an IHX record 95int ihx_parse_and_validate_record(char * p_str, ihx_record * p_rec) { 96 97 int calc_length = 0; 98 int c; 99 uint32_t ctemp, checksum_calc = 0; // Avoid mingw sscanf("%2hhx") buffer overflow with uint8_t 100 101 // Remove trailing CR and LF 102 p_rec->length = strlen(p_str); 103 for (c = 0;c < p_rec->length;c++) { 104 if (p_str[c] == '\n' || p_str[c] == '\r') { 105 p_str[c] = '\0'; // Replace char with string terminator 106 p_rec->length = c; // Shrink length to truncated size 107 break; // Exit loop after finding first CR or LF 108 } 109 } 110 111 // Only parse lines that start with ':' character (Start token for IHX record) 112 if (p_str[0] != ':') { 113 printf("Warning: IHX: Invalid start of line token for line: %s \n", p_str); 114 return false; 115 } 116 117 // Require minimum length 118 if (p_rec->length < IHX_REC_LEN_MIN) { 119 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); 120 return false; 121 } 122 123 // Only hex characters are allowed after start token 124 p_str++; // Advance past Start code 125 if (check_hex(p_str)) { 126 printf("Warning: IHX: Invalid line, non-hex characters present: %s\n", p_str); 127 return false; 128 } 129 130 // Read record header: byte count, start address, type 131 sscanf(p_str, "%2x%4x%2x", &p_rec->byte_count, &p_rec->address, &p_rec->type); 132 p_str += (2 + 4 + 2); 133 134 135 // Require expected data byte count to fit within record length (at 2 chars per hex byte) 136 calc_length = IHX_REC_LEN_MIN + (p_rec->byte_count * 2); 137 if (p_rec->length != calc_length) { 138 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); 139 return false; 140 } 141 142 // Is this an extended linear address record? Read in offset address if so 143 if (p_rec->type == IHX_REC_EXTLIN) { 144 sscanf(p_str, "%4x", &g_address_upper); 145 g_address_upper <<= 16; // Shift into upper 16 bits of address space 146 } 147 else if (p_rec->type == IHX_REC_DATA) { 148 149 // Don't process records with zero bytes of length 150 if (p_rec->byte_count == 0) { 151 printf("Warning: IHX: Zero length record starting at %x\n", p_rec->address); 152 return false; 153 } 154 155 // Apply extended linear address (upper 16 bits of address space) 156 // Calculate end address 157 p_rec->address |= g_address_upper; 158 p_rec->address_end = p_rec->address + p_rec->byte_count - 1; 159 } 160 161 162 // Read data segment and calculate checsum of data + headers 163 checksum_calc = p_rec->byte_count + (p_rec->address & 0xFF) + ((p_rec->address >> 8) & 0xFF) + p_rec->type; 164 for (c = 0;c < p_rec->byte_count;c++) { 165 sscanf(p_str, "%2x", &ctemp); 166 p_str += 2; 167 checksum_calc += ctemp; 168 } 169 170 // Final calculated checeksum is 2's complement of LSByte 171 checksum_calc = (((checksum_calc & 0xFF) ^ 0xFF) + 1) & 0xFF; 172 173 // Read checksum from data 174 sscanf(p_str, "%2x", &p_rec->checksum); 175 p_str += 2; 176 177 if (p_rec->checksum != checksum_calc) { 178 printf("Warning: IHX: record checksum %x didn't match calculated checksum %x\n", p_rec->checksum, checksum_calc); 179 return false; 180 } 181 182 // For records that start in banks above the unbanked region (0x000 - 0x3FFF) 183 // Warn (but don't error) if they cross the boundary between different banks 184 if ((p_rec->address >= 0x00004000U) && 185 ((p_rec->address & 0xFFFFC000U) != (p_rec->address_end & 0xFFFFC000U))) { 186 printf("Warning: Write from one bank spans into the next. %x -> %x (bank %d -> %d)\n", 187 p_rec->address, p_rec->address_end, BANK_NUM(p_rec->address), BANK_NUM(p_rec->address_end)); 188 } 189 190 return true; 191} 192 193 194int ihx_file_process_areas(char * filename_in) { 195 196 int ret = EXIT_SUCCESS; // default to success 197 char cols; 198 char strline_in[MAX_STR_LEN] = ""; 199 FILE * ihx_file = fopen(filename_in, "r"); 200 area_item area; 201 ihx_record ihx_rec; 202 203 areas_init(); 204 205 // Initialize global upper address modifier 206 g_address_upper = 0x0000; 207 208 // Initialize area record 209 area.start = ADDR_UNSET; 210 area.end = ADDR_UNSET; 211 212 213 if (ihx_file) { 214 215 // Read one line at a time into \0 terminated string 216 while (fgets(strline_in, sizeof(strline_in), ihx_file) != NULL) { 217 218 // Parse record, skip if fails validation 219 if (!ihx_parse_and_validate_record(strline_in, &ihx_rec)) 220 continue; 221 222 // Process the pending record and exit if last record (EOF) 223 // Also ignore non-default data records (don't seem to occur for gbz80) 224 if (ihx_rec.type == IHX_REC_EOF) { 225 if (!areas_add(&area) && g_option_warnings_as_errors) 226 ret = EXIT_FAILURE; 227 continue; 228 } else if (ihx_rec.type == IHX_REC_EXTLIN) { 229 // printf("Extended linear address changed to %08x %s\n\n\n", g_address_upper, strline_in); 230 continue; 231 } else if (ihx_rec.type != IHX_REC_DATA) { 232 printf("Warning: IHX: dropped record %s of type %d\n", strline_in, ihx_rec.type); 233 continue; 234 } 235 236 // Records are left pending (non-processed) until they don't merge 237 // with the current incoming record *or* the final (EOF) record is found. 238 239 // Try to merge with (pending) previous record if it's address-adjacent, 240 // except when the new record starts or ends on a bank boundary 241 // (this reduces count from 1000's since most are only 32 bytes long) 242 if ((ihx_rec.address == area.end + 1) && ((ihx_rec.address & 0x00003FFFU) != 0x00000000U)) { 243 area.end = ihx_rec.address_end; // append to previous area 244 } else if ((ihx_rec.address_end == area.start + 1) && !((ihx_rec.address_end & 0x00003FFFU) != 0x00003FFFU)) { 245 area.start = ihx_rec.address; // pre-pend to previous area 246 } else { 247 // New record was *not* adjacent to last, 248 // so process the last/pending record 249 if (area.start != ADDR_UNSET) { 250 if (!areas_add(&area) && g_option_warnings_as_errors) 251 ret = EXIT_FAILURE; 252 } 253 // Now queue current record as pending for next loop 254 area.start = ihx_rec.address; 255 area.end = ihx_rec.address + ihx_rec.byte_count - 1; 256 } 257 258 } // end: while still lines to process 259 260 fclose(ihx_file); 261 262 } // end: if valid file 263 else { 264 printf("Problem with filename or unable to open file! %s\n", filename_in); 265 ret = EXIT_FAILURE; 266 } 267 268 areas_cleanup(); 269 return ret; 270} 271