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.