1 /* OpenCP Module Player
2  * copyright (c) 2019 Stian Skjelstad <stian.skjelstad@gmail.com>
3  *
4  * Utility: Dumping the raw contents of a AHX file
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 
21 
22 #include "config.h"
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <sys/mman.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include "types.h"
33 
34 #define roundup(x,y) (((x) + (y) - 1) & ~((y) - 1))
35 
36 int usecolor = 0;
37 
38 char *FONT_RESET = "";
39 char *FONT_BRIGHT_BLACK = "";
40 char *FONT_BRIGHT_RED = "";
41 char *FONT_BRIGHT_GREEN = "";
42 char *FONT_BRIGHT_YELLOW = "";
43 char *FONT_BRIGHT_BLUE = "";
44 char *FONT_BRIGHT_PURPLE = "";
45 char *FONT_BRIGHT_CYAN = "";
46 
DumpPrefix(unsigned char * mem,int len,int base,int baselen)47 int DumpPrefix (unsigned char *mem, int len, int base, int baselen)
48 {
49 	int retval = 0;
50 	int i;
51 	printf ("[%s%08x%s]%s", FONT_BRIGHT_BLUE, base, FONT_RESET, FONT_BRIGHT_PURPLE);
52 	for (i=0; i < baselen; i++)
53 	{
54 		if (base+i >= len)
55 		{
56 			printf (" \?\?");
57 			retval = -1;
58 		} else {
59 			printf (" %02x", mem[base+i]);
60 		}
61 	}
62 	switch (baselen)
63 	{
64 		case 3:  printf (                     "%s ", FONT_RESET); break;
65 		case 2:  printf (                  "%s    ", FONT_RESET); break;
66 		case 1:  printf (               "%s       ", FONT_RESET); break;
67 		case 0:  printf (            "%s          ", FONT_RESET); break;
68 		default: printf ("%s\n                    ", FONT_RESET); break;
69 	}
70 	return retval;
71 }
72 
73 uint8_t SMP;	// Number of samples
74 
75 uint8_t SS;     // SubSongs
76 uint16_t LEN;   // Number of orders (each order has 4 track references with transpose tagged to it)
77 uint8_t TRK;    // Number of tracks
78 uint8_t TRL;    // Length of each track / rows
79 
80 int HaveTrack0; // True if Track 0 is stored in the file
81 
DumpHeader(unsigned char * mem,int len)82 int DumpHeader (unsigned char *mem, int len)
83 {
84 	int i;
85 	int isAHX1 = 0;
86 	uint16_t RES;
87 
88 	printf ("[%sHEADER%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
89 
90 	if (len < 14)
91 	{
92 		fprintf (stderr, "%sERROR: len < sizeof(FileHeader)%s\n", FONT_BRIGHT_RED, FONT_RESET);
93 		return 1;
94 	}
95 
96 	/* hdr.name */
97 	DumpPrefix (mem, len, 0x00, 4);
98 	printf ("Signature: \"");
99 	for (i=0; i < 4; i++)
100 	{
101 		if ((mem[0x00+i] & 0xc0) == 0x40)
102 		{
103 			printf ("%c", (char)mem[0x00+i]);
104 		} else if (mem[0x00+i] < 9)
105 		{
106 			printf ("\\%d", mem[0x00+i]);
107 		} else {
108 			printf ("%s\\x%02x%s", FONT_BRIGHT_RED, mem[0x00+i], FONT_RESET);
109 		}
110 	}
111 	printf ("\" - ");
112 	if (!memcmp(mem + 0x00, "THX\0", 4))
113 	{
114 		printf ("%sAHX version 0.00 - 1.27%s\n", FONT_BRIGHT_GREEN, FONT_RESET);
115 	} else if (!memcmp (mem + 0x00, "THX\1", 4))
116 	{
117 		printf ("%sAHX version >= 2.00%s\n", FONT_BRIGHT_GREEN, FONT_RESET);
118 		isAHX1 = 1;
119 	} else {
120 		printf ("%sUnknown%s\n", FONT_BRIGHT_RED, FONT_RESET);
121 	}
122 
123 	DumpPrefix (mem, len, 0x04, 2);
124 	printf ("Titles and Sample names is stored at ptr [%s0xXXXX%04x%s]\n", FONT_BRIGHT_BLUE, (mem[0x04]<<8)|mem[0x05], FONT_RESET);
125 
126 	DumpPrefix (mem, len, 0x06, 1);
127 	HaveTrack0 = !(mem[0x06] & 0x80);
128 	printf ("Track 0 present: %d\n", HaveTrack0);
129 	if (isAHX1)
130 	{
131 		printf ("                    Speed: ");
132 		switch (mem[0x06] & 0x70)
133 		{
134 			case 0x00: printf ("50Hz\n"); break;
135 			case 0x10: printf ("100Hz\n"); break;
136 			case 0x20: printf ("150Hz\n"); break;
137 			case 0x30: printf ("200Hz\n"); break;
138 			default: printf ("%sInvalid value%s\n", FONT_BRIGHT_RED, FONT_RESET); break;
139 		}
140 	}
141 
142 	DumpPrefix (mem, len, 0x07, 1);
143 	LEN = ((mem[0x06] << 8) | mem[0x07]) & 0xfff;
144 	if ((LEN >= 1) && (LEN <= 999))
145 	{
146 		printf ("LEN (number of positions): %d    # Orders\n", LEN);
147 	} else {
148 		printf ("LEN (number of positions): %s%d OUT OF RANGE%s    # Orders\n", FONT_BRIGHT_RED, LEN, FONT_RESET);
149 	}
150 
151 	DumpPrefix (mem, len, 0x08, 2);
152 	RES = ((mem[0x08] << 8) | mem[0x09]);
153 	if (RES < LEN)
154 	{
155 		printf ("RES (loop position): %d\n", LEN);
156 	} else {
157 		printf ("RES (loop position): %s%d OUT OF RANGE%s\n", FONT_BRIGHT_RED, LEN, FONT_RESET);
158 	}
159 
160 	DumpPrefix (mem, len, 0x0a, 1);
161 	TRL = mem[0x0a];
162 	if ((TRL >= 1) && (TRL <= 64))
163 	{
164 		printf ("TRL (track length): %d    # Pattern Length\n", TRL);
165 	} else {
166 		printf ("TRL (track length): %s%d OUT OF RANGE    # Pattern Length%s\n", FONT_BRIGHT_RED, TRL, FONT_RESET);
167 	}
168 
169 	DumpPrefix (mem, len, 0x0b, 1);
170 	TRK = mem[0x0b];
171 	printf ("TRK (number of tracks saved): %d    # Patterns\n", (int)TRK);
172 
173 	DumpPrefix (mem, len, 0x0c, 1);
174 	SMP = mem[0x0c];
175 	if (SMP <= 63)
176 	{
177 		printf ("SMP (samples stored): %d\n", SMP);
178 	} else {
179 		printf ("SMP (samples stored): %s%d OUT OF RANGE%s\n", FONT_BRIGHT_RED, SMP, FONT_RESET);
180 	}
181 
182 	DumpPrefix (mem, len, 0x0d, 1);
183 	SS = mem[0x0d];
184 	printf ("SS (number of subsongs): %d\n", SS);
185 
186 	return 0;
187 }
188 
DumpSubSongs(unsigned char * mem,int offset,int len)189 int DumpSubSongs (unsigned char *mem, int offset, int len)
190 {
191 	int i;
192 	printf ("[%sSUBSONGS%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
193 	for (i=0; i < SS; i++)
194 	{
195 		uint16_t SL;
196 		DumpPrefix (mem, len, offset + (i<<1), 2);
197 		if (len < offset + (i<<1) + 2)
198 		{
199 			fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET);
200 			return 1;
201 		}
202 		SL = ((mem[offset+(i<<1)] << 8) | mem[offset+(i<<1)+1]);
203 
204 		if (SL >= LEN)
205 		{
206 			printf ("SubSong %d: Starts at position %s0x%04x  # OUT OF RANGE, must be <LEN%s\n", i, FONT_BRIGHT_RED, SL, FONT_RESET);
207 		} else {
208 			printf ("SubSong %d: Starts at position 0x%04x\n", i, SL);
209 		}
210 
211 	}
212 	return 0;
213 }
214 
DumpPositions(unsigned char * mem,int offset,int len)215 int DumpPositions (unsigned char *mem, int offset, int len)
216 {
217 	int i, j;
218 	printf ("[%sPOSITIONS (Orders)%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
219 	for (i=0; i < LEN; i++)
220 	{
221 		int error = 0;
222 
223 		if (DumpPrefix (mem, len, offset + (i<<3), 8))
224 		{
225 			fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET);
226 			return 1;
227 		}
228 
229 		printf ("Position %3d: ", i);
230 		for (j=0; j < 4; j++)
231 		{
232 			uint8_t Track     = mem[offset + (i<<3) + (j<<1)];
233 			int8_t  Transpose = mem[offset + (i<<3) + (j<<1) + 1];
234 			if (Track > TRK)
235 			{
236 				error = 1;
237 				printf (" | %s%3d%s transpose %-4d", FONT_BRIGHT_RED, Track, FONT_RESET, Transpose);
238 			} else {
239 				printf (" | %3d transpose %-4d", Track, Transpose);
240 			}
241 		}
242 		if (error)
243 		{
244 			printf (" | %s# TRACK REFERENCE OUT OF RANGE%s\n", FONT_BRIGHT_RED, FONT_RESET);
245 		} else {
246 			printf (" |\n");
247 		}
248 	}
249 	return 0;
250 }
251 
print_note(uint8_t note)252 void print_note (uint8_t note)
253 {
254 	char *N[12] = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"};
255 	if (!note)
256 	{
257 		printf("...");
258 		return;
259 	}
260 	if (note >= 61)
261 	{
262 		printf("%s???%s", FONT_BRIGHT_RED, FONT_RESET);
263 		return;
264 	}
265 	note--;
266 	printf ("%s%d", N[note%12], (note / 12)+1);
267 }
268 
print_sample(uint8_t sample)269 void print_sample (uint8_t sample)
270 {
271 	if (!sample)
272 	{
273 		printf ("..");
274 		return;
275 	}
276 	printf ("%02d", sample);
277 }
278 
279 /* Commands
280     0 00 no-op
281     0 .x Position jump hi
282     1 xx Portamento up (period slide down)
283     2 xx Portamento down
284     3 xx Tone portamento
285     4 xx Filter override
286     5 xx Volume Slide + Tone Portamento
287     6 ????????????
288     7 xx Panning
289     8 ????????????
290     9 xx Set squarewave offset
291     A xx Volume Slide
292     B xx Position jump
293     C xx Volume
294     D xx Pattern Break
295     E 0x ?????????????
296     E 1x Fineslide up
297     E 2x Fineslide down
298     E 3x ?????????????
299     E 4x Vibrato control
300     E 5x ?????????????
301     E 6x ?????????????
302     E 7x ?????????????
303     E 8x ?????????????
304     E 9x ?????????????
305     E Ax Fine volume up
306     E Bx Fine volume down
307     E Cx Note cut
308     E Dx Note delay
309     E Ex ?????????????
310     E Fx Misc flags
311     F xx Speed
312 */
313 
print_command(uint8_t cmd,uint8_t info)314 void print_command (uint8_t cmd, uint8_t info)
315 {
316 	if ((cmd == 0) && (info == 0))
317 	{
318 		printf ("...");
319 	} else {
320 		printf ("%X%02X", cmd, info);
321 	}
322 }
323 
DumpTracks(unsigned char * mem,int offset,int len)324 int DumpTracks (unsigned char *mem, int offset, int len)
325 {
326 	int i, j;
327 	printf ("[%sTRACKS (Patterns)%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
328 	for (i=(HaveTrack0?0:1); i <= TRK; i++)
329 	{
330 		printf ("Track: %d\n", i);
331 		for (j=0; j < TRL; j++)
332 		{
333 			uint8_t note;
334 			uint8_t sample;
335 			uint8_t command;
336 			uint8_t data;
337 
338 			if (DumpPrefix (mem, len, offset, 3))
339 			{
340 				fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET);
341 				return 1;
342 			}
343 			note    =   mem[offset    ] >> 2;
344 			sample  = ((mem[offset    ] << 4) & 0x30) |
345 			           (mem[offset + 1] >> 4);
346 			command =   mem[offset + 1] & 0x0f;
347 			data    =   mem[offset + 2];
348 
349 			offset += 3;
350 
351 			printf ("%02x: ", j);
352 			print_note (note);
353 			printf (" ");
354 			print_sample (sample);
355 			printf (" ");
356 			print_command (command, data);
357 			printf ("\n");
358 		}
359 	}
360 	return 0;
361 }
362 
DumpSamples(unsigned char * mem,int * offset,int len)363 int DumpSamples (unsigned char *mem, int *offset, int len)
364 {
365 	int i;
366 
367 	printf ("[%sSAMPLES (Instruments)%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
368 
369 	for (i=0; i < SMP; i++)
370 	{
371 		uint8_t square_modulation_lower_limit_min = 1;
372 		uint8_t PLEN;
373 		int j;
374 
375 		printf ("Sample: %d\n", i+1);
376 
377 		if (DumpPrefix (mem, len, *offset, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
378 		if (mem[*offset+0] > 64)
379 		{
380 			printf ("Master volume: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+0], FONT_RESET);
381 		} else {
382 			printf ("Master volume: %d\n", mem[*offset+0]);
383 		}
384 
385 		if (DumpPrefix (mem, len, *offset+1, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
386 		if ((mem[*offset+1] & 7) > 5)
387 		{
388 			printf ("WaveLen: %s[%d]?? # OUT OF RANGE%s\n", FONT_BRIGHT_RED, 4<<(mem[*offset+1] & 7), FONT_RESET);
389 		} else {
390 			printf ("WaveLen: %d\n", 4<<(mem[*offset+1] & 7));
391 			square_modulation_lower_limit_min = 32 >> (mem[*offset+1] & 7);
392 		}
393 
394 		if (DumpPrefix (mem, len, *offset+2, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
395 		if (mem[*offset+2] == 0)
396 		{
397 			printf ("Attack Length: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+2], FONT_RESET);
398 		} else {
399 			printf ("Attack Length: %d\n", mem[*offset+2]);
400 		}
401 
402 		if (DumpPrefix (mem, len, *offset+3, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
403 		if (mem[*offset+3] > 64)
404 		{
405 			printf ("Attack volume: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+3], FONT_RESET);
406 		} else {
407 			printf ("Attack volume: %d\n", mem[*offset+3]);
408 		}
409 
410 		if (DumpPrefix (mem, len, *offset+4, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
411 		if (mem[*offset+4] == 0)
412 		{
413 			printf ("Decay Length: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+4], FONT_RESET);
414 		} else {
415 			printf ("Decay Length: %d\n", mem[*offset+4]);
416 		}
417 
418 		if (DumpPrefix (mem, len, *offset+5, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
419 		if (mem[*offset+5] > 64)
420 		{
421 			printf ("Decay volume: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+5], FONT_RESET);
422 		} else {
423 			printf ("Decay volume: %d\n", mem[*offset+5]);
424 		}
425 
426 		if (DumpPrefix (mem, len, *offset+6, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
427 		if (mem[*offset+6] == 0)
428 		{
429 			printf ("Sustain Length: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+6], FONT_RESET);
430 		} else {
431 			printf ("Sustain Length: %d\n", mem[*offset+6]);
432 		}
433 
434 		if (DumpPrefix (mem, len, *offset+7, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
435 		if (mem[*offset+7] == 0)
436 		{
437 			printf ("Release Length: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+7], FONT_RESET);
438 		} else {
439 			printf ("Release Length: %d\n", mem[*offset+7]);
440 		}
441 
442 		if (DumpPrefix (mem, len, *offset+8, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
443 		if (mem[*offset+8] > 64)
444 		{
445 			printf ("Release volume: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+8], FONT_RESET);
446 		} else {
447 			printf ("Release volume: %d\n", mem[*offset+8]);
448 		}
449 
450 		if (DumpPrefix (mem, len, *offset+9, 3)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
451 		printf ("Reserved\n");
452 
453 		if (DumpPrefix (mem, len, *offset+12, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
454 		/* must be all zero for AHX0 */
455 		if ((!(mem[*offset+12] & 0x7f)) || ((mem[*offset+12] & 0x7f) > 63))
456 		{
457 			printf ("Filter modulation lower limit: %s %d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+12] & 0x7f, FONT_RESET);
458 		} else {
459 			printf ("Filter modulation lower limit: %d\n", mem[*offset+12] & 0x7f);
460 		}
461 
462 		if (DumpPrefix (mem, len, *offset+13, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
463 		printf ("Vibrato Delay: %d\n", mem[*offset+13]);
464 
465 		if (DumpPrefix (mem, len, *offset+14, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
466 		if (mem[*offset+14] & 0x80) /* must be all zero for AHX0 */
467 		{
468 			printf ("Hardcut: enabled %d\n", (mem[*offset+14] >> 4) & 7);
469 		} else {
470 			printf ("Hardcut: disabled (%d)\n", (mem[*offset+14] >> 4) & 7);
471 		}
472 		printf ("                    Vibrato: %d\n", mem[*offset+14] & 15);
473 
474 		if (DumpPrefix (mem, len, *offset+15, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
475 		if (mem[*offset+15] > 63)
476 		{
477 			printf ("Vibrato: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+15], FONT_RESET);
478 		} else {
479 			printf ("Vibrato: %d\n", mem[*offset+15]);
480 		}
481 
482 		if (DumpPrefix (mem, len, *offset+16, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
483 		if ((mem[*offset+16] > 63) || (mem[*offset+16] < square_modulation_lower_limit_min))
484 		{
485 			printf ("Square modulation lower limit: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+16], FONT_RESET);
486 		} else {
487 			printf ("Square modulation lower limit: %d\n", mem[*offset+16]);
488 		}
489 
490 		if (DumpPrefix (mem, len, *offset+17, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
491 		if ((mem[*offset+17] > 63) || (mem[*offset+17] < mem[*offset+16]))
492 		{
493 			printf ("Square modulation upper limit: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+17], FONT_RESET);
494 		} else {
495 			printf ("Square modulation upper limit: %d\n", mem[*offset+17]);
496 		}
497 
498 		if (DumpPrefix (mem, len, *offset+18, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
499 		printf ("Square modulation speed: %d\n", mem[*offset+18]);
500 
501 		if (DumpPrefix (mem, len, *offset+19, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
502 		/* must be all zero for AHX0 */
503 		if ((!(mem[*offset+19] & 0x7f)) || ((mem[*offset+19] & 0x7f) > 63))
504 		{
505 			printf ("Filter modulation upper limit: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+19], FONT_RESET);
506 		} else {
507 			printf ("Filter modulation upper limit: %d\n", mem[*offset+19]);
508 		}
509 
510 		/* Must be all zero for AHX0 */
511 		printf ("                    Filter modulation speed: %d\n",
512 			((mem[*offset+ 1] & 0xf8) >> 3) |
513 			((mem[*offset+12] & 0x80) >> 2) |
514 			((mem[*offset+19] & 0x80) >> 1));
515 
516 		if (DumpPrefix (mem, len, *offset+20, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
517 		/* must be all zero for AHX0 */
518 		if (!mem[*offset+20])
519 		{
520 			printf ("Playlist default speed: %s%d # OUT OF RANGE%s\n", FONT_BRIGHT_RED, mem[*offset+20], FONT_RESET);
521 		} else {
522 			printf ("Playlist default speed: %d\n", mem[*offset+20]);
523 		}
524 
525 		if (DumpPrefix (mem, len, *offset+21, 1)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
526 		if (!mem[*offset+21])
527 		{
528 			printf ("Playlist length: 0 # EMPTY SAMPLE, IGNORE\n");
529 			*offset += 22;
530 			continue;
531 		} else {
532 			PLEN = mem[*offset+21];
533 			printf ("Playlist length: %d\n", mem[*offset+21]);
534 			*offset += 22;
535 		}
536 		for (j=0; j < PLEN; j++)
537 		{
538 			int noteon, note, waveform, fx1, fx2, info1, info2;
539 
540 			if (DumpPrefix (mem, len, *offset, 4)) { fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
541 
542 			fx2 =      ((mem[*offset + 0] & 0xe0) >> 5);
543 			fx1 =      ((mem[*offset + 0] & 0x1c) >> 2);
544 			waveform = ((mem[*offset + 0] & 0x03) << 1) |
545 			           ((mem[*offset + 1] & 0x80) >> 7);
546 			noteon =     mem[*offset + 1] & 0x40;
547 			note   =     mem[*offset + 1] & 0x3f;
548 			info1  =     mem[*offset + 2];
549 			info2  =     mem[*offset + 3];
550 
551 			*offset += 4;
552 
553 			printf ("%03d | %c", j, noteon ? '*' : '.');
554 			print_note (note);
555 			if (waveform > 4)
556 			{
557 				printf (" %s%d%s", FONT_BRIGHT_RED, waveform, FONT_RESET);
558 			} else {
559 				printf (" %d", waveform);
560 			}
561 
562 			if ((!fx1) && (!info1))
563 			{
564 				printf (" ...");
565 			} else {
566 				printf (" %d%02X", fx1, info1);
567 			}
568 
569 			if ((!fx2) && (!info2))
570 			{
571 				printf (" ...");
572 			} else {
573 				printf (" %d%02X", fx2, info2);
574 			}
575 
576 			printf (" |\n");
577 		}
578 	}
579 
580 	return 0;
581 }
582 
DumpStrings(unsigned char * mem,int * offset,int len)583 int DumpStrings (unsigned char *mem, int *offset, int len)
584 {
585 	int ptr, i;
586 
587 	printf ("[%sSTRINGS%s]\n", FONT_BRIGHT_CYAN, FONT_RESET);
588 
589 	for (ptr=*offset; ;ptr++)
590 	{
591 		if (ptr > len)
592 		{
593 			{ fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
594 		}
595 		if (!mem[ptr])
596 		{
597 			break;
598 		}
599 	}
600 	DumpPrefix (mem, len, *offset, ptr-(*offset)+1);
601 	printf ("Title: \"");
602 	for (ptr=*offset; mem[ptr] ;ptr++)
603 	{
604 		if ((mem[ptr] < 0x20) || (mem[ptr] == 127))
605 		{
606 			printf ("%s\\x%02x%s", FONT_BRIGHT_RED, mem[ptr], FONT_RESET);
607 		} else if (mem[ptr] >= 128)
608 		{
609 			printf ("\\x%02x", mem[ptr]);
610 		} else {
611 			printf ("%c", mem[ptr]);
612 		}
613 	}
614 	printf ("\"\n");
615 	*offset = ptr + 1;
616 
617 	for (i=0; i < SMP; i++)
618 	{
619 		for (ptr=*offset; ;ptr++)
620 		{
621 			if (ptr > len)
622 			{
623 				{ fprintf (stderr, "\n%sERROR: ran out of data%s\n", FONT_BRIGHT_RED, FONT_RESET); return 1; }
624 			}
625 			if (!mem[ptr])
626 			{
627 				break;
628 			}
629 		}
630 		DumpPrefix (mem, len, *offset, ptr-(*offset)+1);
631 		printf ("Sample %2d: \"", i + 1);
632 		for (ptr=*offset; mem[ptr] ;ptr++)
633 		{
634 			if ((mem[ptr] < 0x20) || (mem[ptr] == 127))
635 			{
636 				printf ("%s\\x%02x%s", FONT_BRIGHT_RED, mem[ptr], FONT_RESET);
637 			} else if (mem[ptr] >= 128)
638 			{
639 				printf ("\\x%02x", mem[ptr]);
640 			} else {
641 				printf ("%c", mem[ptr]);
642 			}
643 		}
644 		printf ("\"\n");
645 		*offset = ptr + 1;
646 	}
647 
648 	return 0;
649 }
650 
ParseAHX(unsigned char * mem,int len)651 int ParseAHX (unsigned char *mem, int len)
652 {
653 	int offset;
654 
655 	if (DumpHeader (mem, len))
656 	{
657 		return -1;
658 	}
659 	if (DumpSubSongs (mem, 14, len))
660 	{
661 		return -1;
662 	}
663 	if (DumpPositions (mem, 14+SS*2, len))
664 	{
665 		return -1;
666 	}
667 	if (DumpTracks (mem, 14+SS*2 + LEN*8, len))
668 	{
669 		return -1;
670 	}
671 	offset = 14+SS*2 + LEN*8 + (TRK+HaveTrack0)*TRL*3;
672 	if (DumpSamples (mem, &offset, len))
673 	{
674 		return -1;
675 	}
676 	if (DumpStrings (mem, &offset, len))
677 	{
678 		return -1;
679 	}
680 
681 	return 0;
682 }
683 
684 
main(int argc,char * argv[])685 int main(int argc, char *argv[])
686 {
687 	struct stat st;
688 	size_t ps = sysconf(_SC_PAGE_SIZE);
689 	int fd;
690 	size_t data_mmaped_len;
691 	unsigned char *data;
692 	int c;
693 	char *color = "auto";
694 	int help = 0;
695 
696 	while (1)
697 	{
698 		int option_index = 0;
699 		static struct option long_options[] =
700 		{
701 			{"color",        optional_argument, 0, 0},
702 			{"help",         no_argument,       0, 'h'},
703 			{0,              0,                 0, 0}
704 		};
705 
706 		c = getopt_long(argc, argv, "h", long_options, &option_index);
707 		if (c == -1)
708 			break;
709 
710 		switch (c)
711 		{
712 			case 0:
713 				if (option_index == 0)
714 				{
715 					color = optarg;
716 				}
717 				break;
718 			case 'h':
719 				help = 1;
720 				break;
721 			case '?':
722 				help = 3;
723 				break;
724 			default:
725 				printf("?? getopt returned character code 0%o ??\n", c);
726 		}
727 	}
728 
729 	if (optind != (argc-1))
730 	{
731 		help = 4;
732 	}
733 
734 	if (!color)
735 	{
736 		usecolor = 1;
737 	} else if (!strcmp (color, "auto"))
738 	{
739 		usecolor = isatty ( 1 );
740 	} else if ((strcmp (color, "never")) && (strcmp (color, "no")))
741 	{
742 		usecolor = 1;
743 	} else {
744 		usecolor = 0;
745 	}
746 
747 	if (help)
748 	{
749 		fprintf (stderr, "Usage:\n%s [--color=auto/never/on] [--savesamples -s] [--savepatterns -p] [--help] file.stm  (%d)\n", argv[0], help);
750 		return 1;
751 	}
752 
753 	fd = open (argv[optind], O_RDONLY);
754 	if (fd < 0)
755 	{
756 		perror ("open()");
757 		return 0;
758 	}
759 	if (fstat(fd, &st))
760 	{
761 		perror("fstat()");
762 		close (fd);
763 		return 0;
764 	}
765 	if (!st.st_size)
766 	{
767 		fprintf (stderr, "Zero-size file\n");
768 		close (fd);
769 		return 0;
770 	}
771 
772 //	s.data_len = st.st_size;
773 	data_mmaped_len = roundup (st.st_size, ps);
774 	data = mmap (0, data_mmaped_len, PROT_READ|PROT_WRITE, MAP_FILE|MAP_PRIVATE, fd, 0);
775 
776 	if (data == MAP_FAILED)
777 	{
778 		perror ("mmap() failed");
779 		close (fd);
780 		return 0;
781 	}
782 
783 	if (usecolor)
784 	{
785 		FONT_RESET         = "\033[0m";
786 		FONT_BRIGHT_BLACK  = "\033[30;1m";
787 		FONT_BRIGHT_RED    = "\033[31;1m";
788 		FONT_BRIGHT_GREEN  = "\033[32;1m";
789 		FONT_BRIGHT_YELLOW = "\033[33;1m";
790 		FONT_BRIGHT_BLUE   = "\033[34;1m";
791 		FONT_BRIGHT_PURPLE = "\033[35;1m";
792 		FONT_BRIGHT_CYAN   = "\033[36;1m";
793 	}
794 
795 	if (ParseAHX (data, st.st_size))
796 	{
797 		goto failed;
798 	}
799 
800 failed:
801 	munmap (data, data_mmaped_len);
802 	close (fd);
803 	return 0;
804 }
805