Mercurial > hg > sidinfo
annotate sidinfo.c @ 8:cfc74ec918dc
Fix handling of "large" SID files.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 25 Sep 2014 02:07:38 +0300 |
parents | 36c43f085b28 |
children | c1fba4abf56f |
rev | line source |
---|---|
2 | 1 /* |
2 * SIDInfo - PSID/RSID information displayer | |
3 * Written by Matti 'ccr' Hämäläinen | |
4 * (C) Copyright 2014 Tecnic Software productions (TNSP) | |
5 */ | |
0 | 6 #include "th_args.h" |
7 #include "th_endian.h" | |
2 | 8 #include "th_string.h" |
0 | 9 #include "th_crypto.h" |
10 | |
2 | 11 #define PSID_MAGIC_LEN 4 |
12 #define PSID_STR_LEN 32 | |
1 | 13 #define PSID_BUFFER_SIZE (1024 * 16) |
14 | |
0 | 15 |
2 | 16 enum |
17 { | |
18 PSF_NONE = 0, | |
19 | |
20 PSF_TYPE = 0x00000001, | |
21 PSF_VERSION = 0x00000002, | |
22 PSF_DATA_OFFS = 0x00000004, | |
23 PSF_LOAD_ADDR = 0x00000008, | |
24 PSF_INIT_ADDR = 0x00000010, | |
25 PSF_PLAY_ADDR = 0x00000020, | |
26 PSF_SONGS = 0x00000040, | |
27 PSF_START_SONG = 0x00000080, | |
28 PSF_SPEEDS = 0x00000100, | |
29 PSF_SID_NAME = 0x00000200, | |
30 PSF_SID_AUTHOR = 0x00000400, | |
31 PSF_SID_COPYRIGHT = 0x00000800, | |
32 | |
33 PSF_DATA_SIZE = 0x00100000, | |
34 PSF_HASH = 0x00200000, | |
35 | |
36 PSF_ALL = 0xffffffff, | |
37 }; | |
38 | |
39 | |
0 | 40 typedef struct |
41 { | |
2 | 42 uint32_t flag; |
43 char *name; | |
44 char *lname; | |
45 } PSFOption; | |
46 | |
47 | |
48 const PSFOption optPSFlags[] = | |
49 { | |
50 { PSF_TYPE , "Type" , NULL }, | |
51 { PSF_VERSION , "Version" , NULL }, | |
52 { PSF_DATA_OFFS , "DataOffs" , "Data offset" }, | |
53 { PSF_DATA_SIZE , "DataSize" , "Data size" }, | |
54 { PSF_LOAD_ADDR , "LoadAddr" , "Load address" }, | |
55 { PSF_INIT_ADDR , "InitAddr" , "Init address" }, | |
56 { PSF_PLAY_ADDR , "PlayAddr" , "Play address" }, | |
57 { PSF_SONGS , "Songs" , "Songs" }, | |
58 { PSF_START_SONG , "StartSong" , "Start song" }, | |
59 { PSF_SID_NAME , "Name" , NULL }, | |
60 { PSF_SID_AUTHOR , "Author" , NULL }, | |
61 { PSF_SID_COPYRIGHT , "Copyright" , NULL }, | |
62 { PSF_HASH , "Hash" , NULL }, | |
63 { PSF_ALL , "All" , NULL }, | |
64 }; | |
65 | |
66 const int noptPSFlags = sizeof(optPSFlags) / sizeof(optPSFlags[0]); | |
67 | |
68 | |
69 /* Options | |
70 */ | |
71 char * optInFilename = NULL; | |
72 BOOL optParsable = FALSE, | |
7 | 73 optNoNamePrefix = FALSE, |
2 | 74 optHexadecimal = FALSE; |
75 uint32_t optFields = PSF_ALL; | |
76 | |
77 | |
78 /* Arguments | |
79 */ | |
80 static optarg_t optList[] = | |
81 { | |
82 { 0, '?', "help", "Show this help", OPT_NONE }, | |
83 { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, | |
84 { 2, 'p', "parsable", "Output in script-parsable format", OPT_NONE }, | |
7 | 85 { 5, 'n', "noprefix", "Output without field name prefix", OPT_NONE }, |
2 | 86 { 3, 'f', "fields", "Show only specified field(s)", OPT_ARGREQ }, |
87 { 4, 'x', "hex", "Use hexadecimal values", OPT_NONE }, | |
88 }; | |
89 | |
90 static const int optListN = (sizeof(optList) / sizeof(optList[0])); | |
91 | |
92 | |
93 void argShowHelp(void) | |
94 { | |
95 int index, n; | |
96 | |
97 th_print_banner(stdout, th_prog_name, "[options] <sid filename>"); | |
98 th_args_help(stdout, optList, optListN); | |
99 printf( | |
100 "\n" | |
101 "Available fields:\n"); | |
3 | 102 |
2 | 103 for (index = n = 0; index < noptPSFlags; index++) |
104 { | |
105 const PSFOption *opt = &optPSFlags[index]; | |
106 printf("%s%s", opt->name, (index < noptPSFlags - 1) ? ", " : "\n\n"); | |
107 if (++n > 5) | |
108 { | |
109 printf("\n"); | |
110 n = 0; | |
111 } | |
112 } | |
5 | 113 |
114 printf("Example: %s -x -p -f hash,copyright somesidfile.sid\n", th_prog_name); | |
2 | 115 } |
116 | |
117 | |
118 int argMatchPSField(const char *field) | |
119 { | |
120 int index, found = -1; | |
121 size_t len = strlen(field); | |
122 for (index = 0; index < noptPSFlags; index++) | |
123 { | |
124 const PSFOption *opt = &optPSFlags[index]; | |
125 if (strncasecmp(opt->name, field, len) == 0) | |
126 { | |
127 if (found >= 0) | |
128 return -2; | |
129 found = index; | |
130 } | |
131 } | |
132 | |
133 return found; | |
134 } | |
135 | |
136 | |
137 BOOL argParsePSField(char *opt, char *end, uint32_t *fields) | |
138 { | |
139 // Trim whitespace | |
140 if (end != NULL) | |
141 { | |
142 *end = 0; | |
143 for (end--; end > opt && *end && th_isspace(*end); end--) | |
144 *end = 0; | |
145 } | |
146 while (*opt && th_isspace(*opt)) opt++; | |
147 | |
148 // Match field name | |
149 int found = argMatchPSField(opt); | |
150 switch (found) | |
151 { | |
152 case -1: | |
153 THERR("No such flag '%s'.\n", opt); | |
154 return FALSE; | |
155 | |
156 case -2: | |
157 THERR("Flag '%s' is ambiguous.\n", opt); | |
158 return FALSE; | |
3 | 159 |
2 | 160 default: |
161 *fields |= optPSFlags[found].flag; | |
162 return TRUE; | |
163 } | |
164 } | |
165 | |
166 | |
167 BOOL argHandleOpt(const int optN, char *optArg, char *currArg) | |
168 { | |
169 switch (optN) | |
170 { | |
171 case 0: | |
172 argShowHelp(); | |
173 exit(0); | |
174 break; | |
175 | |
176 case 1: | |
177 th_verbosityLevel++; | |
178 break; | |
179 | |
180 case 2: | |
181 optParsable = TRUE; | |
182 break; | |
183 | |
184 case 3: | |
185 { | |
186 char *start = optArg; | |
187 optFields = PSF_NONE; | |
188 | |
189 while (*start) | |
190 { | |
191 char *end = strchr(start, ','); | |
192 | |
193 if (!argParsePSField(start, end, &optFields)) | |
194 return FALSE; | |
195 | |
196 if (!end) | |
197 break; | |
198 | |
199 start = end + 1; | |
200 } | |
3 | 201 |
2 | 202 //fprintf(stderr, "%08x\n", optFields); |
203 } | |
204 break; | |
205 | |
206 case 4: | |
207 optHexadecimal = TRUE; | |
208 break; | |
209 | |
7 | 210 case 5: |
211 optNoNamePrefix = TRUE; | |
212 break; | |
213 | |
2 | 214 default: |
215 THERR("Unknown option '%s'.\n", currArg); | |
216 return FALSE; | |
217 } | |
218 | |
219 return TRUE; | |
220 } | |
221 | |
222 | |
223 BOOL argHandleFile(char *currArg) | |
224 { | |
225 if (!optInFilename) | |
226 optInFilename = currArg; | |
227 else | |
228 { | |
229 THERR("Filename already specified on commandline!\n"); | |
230 return FALSE; | |
231 } | |
232 | |
233 return TRUE; | |
234 } | |
235 | |
236 | |
237 typedef struct | |
238 { | |
239 char magic[PSID_MAGIC_LEN + 1]; // "PSID" / "RSID" magic identifier | |
240 uint16_t | |
241 version, // Version number | |
0 | 242 dataOffset, // Start of actual c64 data in file |
243 loadAddress, // Loading address | |
244 initAddress, // Initialization address | |
245 playAddress, // Play one frame | |
246 nSongs, // Number of subsongs | |
247 startSong; // Default starting song | |
248 uint32_t speed; // Speed | |
2 | 249 char sidName[PSID_STR_LEN + 1]; // Descriptive text-fields, ASCIIZ |
250 char sidAuthor[PSID_STR_LEN + 1]; | |
251 char sidCopyright[PSID_STR_LEN + 1]; | |
0 | 252 |
253 // PSIDv2 data | |
254 uint16_t flags; // Flags | |
255 uint8_t startPage, pageLength; | |
256 uint16_t reserved; | |
257 | |
1 | 258 // Extra data |
0 | 259 BOOL isRSID; |
1 | 260 size_t dataSize; // Total size of data - header |
261 th_md5hash_t hash; // Songlength database hash | |
0 | 262 |
263 } PSIDHeader; | |
264 | |
265 | |
2 | 266 static void siAppendHash16(th_md5state_t *state, uint16_t data) |
267 { | |
268 uint8_t ib8[2]; | |
269 ib8[0] = data & 0xff; | |
270 ib8[1] = data >> 8; | |
271 th_md5_append(state, (uint8_t *) &ib8, sizeof(ib8)); | |
272 } | |
273 | |
274 | |
275 int siReadPSIDFile(FILE *inFile, PSIDHeader *psid) | |
0 | 276 { |
1 | 277 th_md5state_t state; |
278 uint8_t tmp8, *fileData = NULL; | |
279 int index, ret = -1; | |
280 size_t read; | |
281 BOOL first; | |
282 | |
283 memset(psid, 0, sizeof(*psid)); | |
284 | |
285 if ((fileData = (uint8_t *) th_malloc(PSID_BUFFER_SIZE)) == NULL) | |
286 { | |
287 THERR("Error allocating temporary data buffer of %d bytes.\n", PSID_BUFFER_SIZE); | |
288 goto error; | |
289 } | |
3 | 290 |
0 | 291 // Read PSID header in |
2 | 292 if (!th_fread_str(inFile, (uint8_t *) psid->magic, PSID_MAGIC_LEN) || |
1 | 293 !th_fread_be16(inFile, &psid->version) || |
294 !th_fread_be16(inFile, &psid->dataOffset) || | |
295 !th_fread_be16(inFile, &psid->loadAddress) || | |
296 !th_fread_be16(inFile, &psid->initAddress) || | |
297 !th_fread_be16(inFile, &psid->playAddress) || | |
298 !th_fread_be16(inFile, &psid->nSongs) || | |
299 !th_fread_be16(inFile, &psid->startSong) || | |
300 !th_fread_be32(inFile, &psid->speed)) | |
0 | 301 { |
1 | 302 THERR("Could not read PSID/RSID header.\n"); |
0 | 303 goto error; |
304 } | |
305 | |
2 | 306 psid->magic[PSID_MAGIC_LEN] = 0; |
307 | |
0 | 308 if ((psid->magic[0] != 'R' && psid->magic[0] != 'P') || |
309 psid->magic[1] != 'S' || psid->magic[2] != 'I' || psid->magic[3] != 'D' || | |
310 psid->version < 1 || psid->version > 3) | |
311 { | |
1 | 312 THERR("Not a supported PSID or RSID file.\n"); |
0 | 313 goto error; |
314 } | |
315 | |
1 | 316 psid->isRSID = psid->magic[0] == 'R'; |
0 | 317 |
2 | 318 if (!th_fread_str(inFile, (uint8_t *)psid->sidName, PSID_STR_LEN) || |
319 !th_fread_str(inFile, (uint8_t *)psid->sidAuthor, PSID_STR_LEN) || | |
320 !th_fread_str(inFile, (uint8_t *)psid->sidCopyright, PSID_STR_LEN)) | |
0 | 321 { |
1 | 322 THERR("Error reading SID file header.\n"); |
0 | 323 goto error; |
324 } | |
325 | |
2 | 326 psid->sidName[PSID_STR_LEN] = 0; |
327 psid->sidAuthor[PSID_STR_LEN] = 0; | |
328 psid->sidCopyright[PSID_STR_LEN] = 0; | |
329 | |
0 | 330 // Check if we need to load PSIDv2NG header ... |
331 if (psid->version >= 2) | |
332 { | |
333 // Yes, we need to | |
1 | 334 if (!th_fread_be16(inFile, &psid->flags) || |
335 !th_fread_byte(inFile, &psid->startPage) || | |
336 !th_fread_byte(inFile, &psid->pageLength) || | |
337 !th_fread_be16(inFile, &psid->reserved)) | |
0 | 338 { |
1 | 339 THERR("Error reading PSID/RSID v2+ extra header data.\n"); |
0 | 340 goto error; |
341 } | |
342 } | |
343 | |
1 | 344 // Initialize MD5-hash calculation |
345 th_md5_init(&state); | |
0 | 346 |
1 | 347 // Process actual data |
348 psid->dataSize = 0; | |
349 first = TRUE; | |
2 | 350 do |
351 { | |
1 | 352 read = fread(fileData, sizeof(uint8_t), PSID_BUFFER_SIZE, inFile); |
353 psid->dataSize += read; | |
0 | 354 |
1 | 355 if (first && psid->loadAddress == 0) |
356 { | |
8
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
357 if (read < 4) |
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
358 { |
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
359 THERR("Error reading song data, unexpectedly small file.\n"); |
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
360 goto error; |
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
361 } |
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
362 |
1 | 363 // Strip load address (2 first bytes) |
364 th_md5_append(&state, &fileData[2], read - 2); | |
365 first = FALSE; | |
366 } | |
367 else | |
8
cfc74ec918dc
Fix handling of "large" SID files.
Matti Hamalainen <ccr@tnsp.org>
parents:
7
diff
changeset
|
368 if (read > 0) |
1 | 369 { |
370 // Append "as is" | |
371 th_md5_append(&state, fileData, read); | |
372 } | |
373 } while (read > 0 && !feof(inFile)); | |
0 | 374 |
1 | 375 // Append header data to hash |
2 | 376 siAppendHash16(&state, psid->initAddress); |
377 siAppendHash16(&state, psid->playAddress); | |
378 siAppendHash16(&state, psid->nSongs); | |
0 | 379 |
380 // Append song speed data to hash | |
1 | 381 tmp8 = psid->isRSID ? 60 : 0; |
0 | 382 for (index = 0; index < psid->nSongs && index < 32; index++) |
383 { | |
1 | 384 if (psid->isRSID) |
385 tmp8 = 60; | |
0 | 386 else |
1 | 387 tmp8 = (psid->speed & (1 << index)) ? 60 : 0; |
0 | 388 |
1 | 389 th_md5_append(&state, &tmp8, sizeof(tmp8)); |
0 | 390 } |
391 | |
392 // Rest of songs (more than 32) | |
393 for (index = 32; index < psid->nSongs; index++) | |
1 | 394 th_md5_append(&state, &tmp8, sizeof(tmp8)); |
0 | 395 |
396 // PSIDv2NG specific | |
397 if (psid->version >= 2) | |
398 { | |
399 // REFER TO SIDPLAY HEADERS FOR MORE INFORMATION | |
1 | 400 tmp8 = (psid->flags >> 2) & 3; |
401 if (tmp8 == 2) | |
402 th_md5_append(&state, &tmp8, sizeof(tmp8)); | |
0 | 403 } |
404 | |
405 // Calculate the hash | |
1 | 406 th_md5_finish(&state, psid->hash); |
407 ret = 0; | |
0 | 408 |
1 | 409 error: |
0 | 410 // Free buffer |
1 | 411 th_free(fileData); |
412 return ret; | |
0 | 413 } |
414 | |
415 | |
2 | 416 static void siPrintLinePrefix(FILE *outFile, const BOOL parsable, const char *name) |
417 { | |
7 | 418 if (!optNoNamePrefix) |
419 fprintf(outFile, parsable ? "%s=" : "%-20s", name); | |
2 | 420 } |
421 | |
422 | |
423 static void siPrintPSIDInfoLine(FILE *outFile, const BOOL parsable, const BOOL hex, | |
424 const uint32_t flags, const int xindex, const char *xfmt, const char *xaltfmt, ...) | |
425 { | |
426 const PSFOption *opt = &optPSFlags[xindex]; | |
427 if (flags & opt->flag) | |
428 { | |
429 va_list ap; | |
430 const char *fmt = hex ? (xaltfmt != NULL ? xaltfmt : xfmt) : xfmt; | |
431 | |
432 siPrintLinePrefix(outFile, parsable, (parsable || opt->lname == NULL) ? opt->name : opt->lname); | |
433 | |
434 va_start(ap, xaltfmt); | |
435 | |
436 if (parsable) | |
437 vfprintf(outFile, fmt, ap); | |
438 else | |
439 vfprintf(outFile, fmt, ap); | |
440 | |
441 va_end(ap); | |
3 | 442 |
2 | 443 fprintf(outFile, "\n"); |
444 } | |
445 } | |
446 | |
447 #define PR(xindex, xfmt, xaltfmt, ...) siPrintPSIDInfoLine(outFile, parsable, hex, flags, xindex, xfmt, xaltfmt, __VA_ARGS__ ) | |
448 | |
449 | |
450 void siPrintPSIDInformation(FILE *outFile, const BOOL parsable, const BOOL hex, const uint32_t flags, const PSIDHeader *psid) | |
451 { | |
452 PR( 0, "%s", NULL, psid->magic); | |
453 PR( 1, "%d.%d", NULL, (psid->version & 0xff), (psid->version >> 8)); | |
454 PR( 2, "%d", "$%08x", psid->dataOffset); | |
455 PR( 3, "%d", "$%08x", psid->dataSize); | |
456 PR( 4, "%d", "$%04x", psid->loadAddress); | |
457 PR( 5, "%d", "$%04x", psid->initAddress); | |
458 PR( 6, "%d", "$%04x", psid->playAddress); | |
459 PR( 7, "%d", "$%04x", psid->nSongs); | |
460 PR( 8, "%d", "$%04x", psid->startSong); | |
461 | |
462 PR( 9, "%s", NULL, psid->sidName); | |
463 PR(10, "%s", NULL, psid->sidAuthor); | |
3 | 464 PR(11, "%s", NULL, psid->sidCopyright); |
2 | 465 |
466 if (flags & PSF_HASH) | |
467 { | |
468 siPrintLinePrefix(outFile, parsable, "Hash"); | |
469 th_md5_print(outFile, psid->hash); | |
470 fprintf(outFile, "\n"); | |
471 } | |
472 } | |
473 | |
0 | 474 |
475 int main(int argc, char *argv[]) | |
476 { | |
2 | 477 FILE *inFile = NULL; |
478 int ret = -1; | |
479 PSIDHeader psid; | |
480 | |
0 | 481 // Initialize |
482 th_init("SIDInfo", "PSID/RSID information displayer", "0.1", NULL, NULL); | |
483 th_verbosityLevel = 0; | |
484 | |
2 | 485 // Parse command line arguments |
486 if (!th_args_process(argc, argv, optList, optListN, | |
487 argHandleOpt, argHandleFile, FALSE)) | |
488 return -1; | |
489 | |
490 if (optInFilename == NULL) | |
491 { | |
492 argShowHelp(); | |
493 THERR("No filename specified.\n"); | |
494 goto error; | |
495 } | |
0 | 496 |
2 | 497 // Try to open the file |
498 if ((inFile = fopen(optInFilename, "rb")) == NULL) | |
499 { | |
500 THERR("Could not open file '%s'.\n", optInFilename); | |
501 goto error; | |
502 } | |
1 | 503 |
2 | 504 // Read PSID data |
505 if ((ret = siReadPSIDFile(inFile, &psid)) != 0) | |
506 goto error; | |
507 | |
508 // Output | |
509 fprintf(stdout, "%s\n", optInFilename); | |
510 siPrintPSIDInformation(stdout, optParsable, optHexadecimal, optFields, &psid); | |
511 | |
512 ret = 0; | |
513 | |
514 // Shutdown | |
515 error: | |
516 if (inFile != NULL) | |
517 fclose(inFile); | |
518 | |
519 return ret; | |
0 | 520 } |