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.