1 #define _GNU_SOURCE
2 #include <string.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <assert.h>
6 #include <stdint.h>
7 #include <ctype.h>
8 
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12 
13 #include "songinfo.h"
14 #include "uadeutils.h"
15 #include "ossupport.h"
16 #include "amifilemagic.h"
17 #include "support.h"
18 
19 
asciiline(char * dst,unsigned char * buf)20 static void asciiline(char *dst, unsigned char *buf)
21 {
22 	int i, c;
23 	for (i = 0; i < 16; i++) {
24 		c = buf[i];
25 		if (isgraph(c) || c == ' ') {
26 			dst[i] = c;
27 		} else {
28 			dst[i] = '.';
29 		}
30 	}
31 	dst[i] = 0;
32 }
33 
hexdump(char * info,size_t maxlen,char * filename,size_t toread)34 static int hexdump(char *info, size_t maxlen, char *filename, size_t toread)
35 {
36 	FILE *f;
37 	size_t rb, ret;
38 	uint8_t *buf;
39 
40 	assert(maxlen >= 8192);
41 
42 	f = fopen(filename, "rb");
43 	if (f == NULL)
44 		return 0;
45 
46 	buf = malloc(toread);
47 	if (buf == NULL)
48 		return 0;
49 
50 	rb = 0;
51 	while (rb < toread) {
52 		ret = fread(&buf[rb], 1, toread - rb, f);
53 		if (ret == 0)
54 			break;
55 		rb += ret;
56 	}
57 
58 	if (rb > 0) {
59 		size_t roff = 0;
60 		size_t woff = 0;
61 
62 		while (roff < rb) {
63 			int iret;
64 
65 			if (woff >= maxlen)
66 				break;
67 
68 			if (woff + 32 >= maxlen) {
69 				strcpy(&info[woff], "\nbuffer overflow...\n");
70 				woff += strlen(&info[woff]);
71 				break;
72 			}
73 
74 			iret = snprintf(&info[woff], maxlen - woff, "%.3zx:  ",
75 					roff);
76 			assert(iret > 0);
77 			woff += iret;
78 
79 			if (woff >= maxlen)
80 				break;
81 
82 			if (roff + 16 > rb) {
83 				iret = snprintf(&info[woff], maxlen - woff,
84 						"Aligned line  ");
85 				assert(iret > 0);
86 				woff += iret;
87 
88 			} else {
89 				char dbuf[17];
90 				asciiline(dbuf, &buf[roff]);
91 				iret = snprintf(&info[woff], maxlen - woff,
92 						"%.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x  %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x  |%s|",
93 						buf[roff + 0], buf[roff + 1],
94 						buf[roff + 2], buf[roff + 3],
95 						buf[roff + 4], buf[roff + 5],
96 						buf[roff + 6], buf[roff + 7],
97 						buf[roff + 8], buf[roff + 9],
98 						buf[roff + 10], buf[roff + 11],
99 						buf[roff + 12], buf[roff + 13],
100 						buf[roff + 14], buf[roff + 15],
101 						dbuf);
102 				assert(iret > 0);
103 				woff += iret;
104 			}
105 
106 			if (woff >= maxlen)
107 				break;
108 
109 			iret = snprintf(&info[woff], maxlen - woff, "\n");
110 			woff += iret;
111 
112 			roff += 16;
113 		}
114 
115 		if (woff >= maxlen)
116 			woff = maxlen - 1;
117 		info[woff] = 0;
118 	}
119 
120 	fclose(f);
121 	free(buf);
122 	return rb == 0;
123 }
124 
find_tag(uint8_t * buf,size_t startoffset,size_t buflen,uint8_t * tag,size_t taglen)125 static size_t find_tag(uint8_t * buf, size_t startoffset, size_t buflen,
126 		       uint8_t * tag, size_t taglen)
127 {
128 	uint8_t *treasure;
129 
130 	if (startoffset >= buflen)
131 		return -1;
132 
133 	treasure = memmem(buf + startoffset, buflen - startoffset, tag, taglen);
134 	if (treasure == NULL)
135 		return -1;
136 
137 	return (size_t) (treasure - buf);
138 }
139 
string_checker(unsigned char * str,size_t off,size_t maxoff)140 static int string_checker(unsigned char *str, size_t off, size_t maxoff)
141 {
142 	assert(maxoff > 0);
143 	while (off < maxoff) {
144 		if (*str == 0)
145 			return 1;
146 		off++;
147 		str++;
148 	}
149 	return 0;
150 }
151 
152 /* Wanted Team's loadseg modules */
process_WTWT_mod(char * credits,size_t credits_len,unsigned char * buf,size_t len,char * lo,char * hi,int rel)153 static void process_WTWT_mod(char *credits, size_t credits_len,
154 			     unsigned char *buf, size_t len, char *lo,
155 			     char *hi, int rel)
156 {
157 	int offset, txt_offset, chunk;
158 	char tmpstr[256];
159 
160 	/* check for Magic ID */
161 	offset = find_tag((uint8_t *) buf, 0, len, (uint8_t *) lo, 4);
162 	if (offset == -1)
163 		return;
164 
165 	offset =
166 	    find_tag((uint8_t *) buf, offset + 4, offset + 8, (uint8_t *) hi,
167 		     4);
168 	if (offset == -1)
169 		return;
170 
171 	chunk = offset - 8;	/* here's where our first chunk should be */
172 	offset = offset + rel;	/* offset to our info pointers */
173 
174 	if (chunk < len && offset < len) {
175 		txt_offset = read_be_u32(buf + offset) + chunk;
176 		if (txt_offset < len && txt_offset != chunk) {
177 			if (!string_checker(buf, txt_offset, len))
178 				return;
179 			snprintf(tmpstr, sizeof tmpstr, "\nMODULENAME:\n %s\n",
180 				 buf + txt_offset);
181 			strlcat(credits, tmpstr, credits_len);
182 
183 		}
184 		txt_offset = read_be_u32(buf + offset + 4) + chunk;
185 		if (txt_offset < len && txt_offset != chunk) {
186 			if (!string_checker(buf, txt_offset, len))
187 				return;
188 			snprintf(tmpstr, sizeof tmpstr, "\nAUTHORNAME:\n %s\n",
189 				 buf + txt_offset);
190 			strlcat(credits, tmpstr, credits_len);
191 		}
192 
193 		txt_offset = read_be_u32(buf + offset + 8) + chunk;
194 		if (txt_offset < len && txt_offset != chunk) {
195 			if (!string_checker(buf, txt_offset, len))
196 				return;
197 			snprintf(tmpstr, sizeof tmpstr, "\nSPECIALINFO:\n %s",
198 				 buf + txt_offset);
199 			strlcat(credits, tmpstr, credits_len);
200 		}
201 	}
202 }
203 
204 /* Get the info out of the AHX  module data*/
process_ahx_mod(char * credits,size_t credits_len,unsigned char * buf,size_t len)205 static void process_ahx_mod(char *credits, size_t credits_len,
206 			    unsigned char *buf, size_t len)
207 {
208 	int i;
209 	size_t offset;
210 	char tmpstr[256];
211 
212 	if (len < 13)
213 		return;
214 
215 	offset = read_be_u16(buf + 4);
216 
217 	if (offset >= len)
218 		return;
219 
220 	if (!string_checker(buf, offset, len))
221 		return;
222 
223 	snprintf(tmpstr, sizeof tmpstr, "\nSong title:     %s\n", buf + offset);
224 	strlcat(credits, tmpstr, credits_len);
225 
226 	for (i = 0; i < buf[12]; i++) {
227 		if (!string_checker(buf, offset, len))
228 			break;
229 		offset = offset + 1 + strlen((char *)buf + offset);
230 		if (offset < len) {
231 			snprintf(tmpstr, 256, "\n           %s", buf + offset);
232 			strlcat(credits, tmpstr, credits_len);
233 		}
234 	}
235 }
236 
237 /* Get the info out of the protracker module data*/
process_ptk_mod(char * credits,size_t credits_len,int inst,uint8_t * buf,size_t len)238 static void process_ptk_mod(char *credits, size_t credits_len, int inst,
239 			    uint8_t * buf, size_t len)
240 {
241 	int i;
242 	char tmpstr[256];
243 
244 	if (!string_checker(buf, 0, len))
245 		return;
246 
247 	snprintf(tmpstr, 35, "\nSong title:     %s", buf);
248 	strlcat(credits, tmpstr, credits_len);
249 
250 	if (inst == 31) {
251 		if (len >= 0x43c) {
252 			snprintf(tmpstr, sizeof tmpstr,
253 				 "\nmax positions:  %d\n", buf[0x3b6]);
254 			strlcat(credits, tmpstr, credits_len);
255 		}
256 	} else {
257 		if (len >= 0x1da) {
258 			snprintf(tmpstr, sizeof tmpstr,
259 				 "\nmax positions:  %d\n", buf[0x1d6]);
260 			strlcat(credits, tmpstr, credits_len);
261 		}
262 	}
263 
264 	snprintf(tmpstr, sizeof tmpstr, "\nINST - NAME                     SIZE VOL FINE LSTART LSIZE\n");
265 	strlcat(credits, tmpstr, credits_len);
266 	if (len >= (0x14 + inst * 0x1e)) {
267 		for (i = 0; i < inst; i++) {
268 			if (!string_checker(buf, 0x14 + i * 0x1e, len))
269 				break;
270 			snprintf(tmpstr, sizeof tmpstr, "[%2d] - ", i + 1);
271 			strlcat(credits, tmpstr, credits_len);
272 			snprintf(tmpstr, 23, "%-23s", buf + 0x14 + (i * 0x1e));
273 			strlcat(credits, tmpstr, credits_len);
274 			snprintf(tmpstr, sizeof tmpstr,
275 				 " %6d  %2d  %2d %6d %6d\n",
276 				 read_be_u16(buf + 42 + i * 0x1e) * 2,
277 				 buf[45 + i * 0x1e], buf[44 + i * 0x1e],
278 				 read_be_u16(buf + 46 + i * 0x1e) * 2,
279 				 read_be_u16(buf + 48 + i * 0x1e) * 2);
280 			strlcat(credits, tmpstr, credits_len);
281 		}
282 	}
283 }
284 
285 /* Get the info out of the digibooster module data*/
process_digi_mod(char * credits,size_t credits_len,uint8_t * buf,size_t len)286 static void process_digi_mod(char *credits, size_t credits_len,
287 			     uint8_t * buf, size_t len)
288 {
289 	int i;
290 	char tmpstr[256];
291 
292 	if (len < (642 + 0x30 * 0x1e))
293 		return;
294 
295 	if (!string_checker(buf, 610, len))
296 		return;
297 
298 	snprintf(tmpstr, 0x2f, "\nSong title:     %s \n", buf + 610);
299 	strlcat(credits, tmpstr, credits_len);
300 
301 	snprintf(tmpstr, sizeof tmpstr, "max positions:  %d\n", buf[47]);
302 	strlcat(credits, tmpstr, credits_len);
303 
304 	snprintf(tmpstr, sizeof tmpstr, "\nINST - NAME                                 SIZE VOL  FINE      LSTART       LSIZE\n");
305 	strlcat(credits, tmpstr, credits_len);
306 	if (len >= (642 + 0x1f * 0x1e)) {
307 		for (i = 0; i < 0x1f; i++) {
308 			if (!string_checker(buf, 642 + i * 0x1e, len))
309 				break;
310 			snprintf(tmpstr, sizeof tmpstr, "[%2d] - ", i + 1);
311 			strlcat(credits, tmpstr, credits_len);
312 			snprintf(tmpstr, 30, "%-30s", buf + 642 + (i * 0x1e));
313 			strlcat(credits, tmpstr, credits_len);
314 			snprintf(tmpstr, sizeof tmpstr,
315 				 " %11d  %2d   %3d %11d %11d\n",
316 				 read_be_u32(buf + 176 + i * 4), buf[548 + i],
317 				 buf[579 + i], read_be_u32(buf + 300 + i * 4),
318 				 read_be_u32(buf + 424 + i * 4));
319 			strlcat(credits, tmpstr, credits_len);
320 		}
321 	}
322 }
323 
324 /* Get the info out of custom song. FIX ME, clean this function. */
process_custom(char * credits,size_t credits_len,unsigned char * buf,size_t len)325 static void process_custom(char *credits, size_t credits_len,
326 			   unsigned char *buf, size_t len)
327 {
328 	char tmpstr[1024];
329 	unsigned char *hunk;
330 	unsigned char *tag_table;
331 	int hunk_size;
332 	int table_size;
333 
334 	int i;
335 	int offset;
336 	unsigned int x, y;
337 	unsigned char startpattern[4] = { 0x70, 0xff, 0x4e, 0x75 };
338 
339 	if (len < 4)
340 		return;
341 
342 	if (read_be_u32(buf) != 0x000003f3)
343 		return;
344 
345 	i = find_tag(buf, 0, len, startpattern, sizeof startpattern);
346 	if (i == -1 || (i + 12) >= len)
347 		return;
348 
349 	if (strncmp((char *)buf + i + 4, "DELIRIUM", 8) != 0 &&
350 	    strncmp((char *)buf + i + 4, "EPPLAYER", 8) != 0) {
351 		return;
352 	}
353 
354 	/* Hunk found */
355 	hunk = buf + i;
356 	hunk_size = len - i;
357 
358 	if (16 + i + 5 >= hunk_size)
359 		return;
360 
361 	/* Check if $VER is available */
362 	if (!memcmp(&hunk[16], "$VER:", 5)) {
363 		offset = 16 + 5;
364 		while (offset < hunk_size) {
365 			if (memcmp(&hunk[offset], " ", 1))
366 				break;
367 			offset++;
368 		}
369 		if (offset >= hunk_size)
370 			return;
371 
372 		if ((offset + strlen((char *)hunk + offset) + 1) >
373 		    ((size_t) hunk_size))
374 			return;
375 
376 		snprintf(tmpstr, sizeof tmpstr, "\nVERSION:\n%s\n\n",
377 			 hunk + offset);
378 		strlcat(credits, tmpstr, credits_len);
379 	}
380 
381 	offset = read_be_u32(hunk + 12);
382 	if (offset < 0) {
383 		return;
384 	}
385 
386 	tag_table = hunk + offset;
387 
388 	if (tag_table >= &buf[len])
389 		return;
390 
391 	table_size = ((int)(&buf[len] - tag_table)) / 8;
392 
393 	if (table_size <= 0)
394 		return;
395 
396 	/* check all tags in this loop */
397 	for (i = 0; i < table_size; i += 2) {
398 		x = read_be_u32(tag_table + 4 * i);
399 		y = read_be_u32(tag_table + 4 * (i + 1));
400 
401 		if (!x)
402 			break;
403 
404 		switch (x) {
405 		case 0x8000445a:
406 			if (y >= ((unsigned int)hunk_size))
407 				return;
408 			if ((y + strlen((char *)hunk + y) + 1) >
409 			    ((size_t) hunk_size))
410 				return;
411 			snprintf(tmpstr, sizeof tmpstr, "\nCREDITS:\n%s\n\n",
412 				 hunk + y);
413 			strlcat(credits, tmpstr, credits_len);
414 			break;
415 		default:
416 			break;
417 		}
418 	}
419 }
420 
421 /*
422  * Get the info out of the Deltamusic 2 module data
423  */
process_dm2_mod(char * credits,size_t credits_len,unsigned char * buf,size_t len)424 static void process_dm2_mod(char *credits, size_t credits_len,
425 			    unsigned char *buf, size_t len)
426 {
427 	char tmpstr[256];
428 	if (!string_checker(buf, 0x148, len))
429 		return;
430 	snprintf(tmpstr, sizeof tmpstr, "\nRemarks:\n%s", buf + 0x148);
431 	strlcat(credits, tmpstr, credits_len);
432 }
433 
process_module(char * credits,size_t credits_len,char * filename)434 static int process_module(char *credits, size_t credits_len, char *filename)
435 {
436 	FILE *modfile;
437 	struct stat st;
438 	size_t modfilelen;
439 	unsigned char *buf;
440 	char pre[11];
441 	char tmpstr[256];
442 	size_t rb;
443 
444 	if (!(modfile = fopen(filename, "rb")))
445 		return 0;
446 
447 	if (fstat(fileno(modfile), &st))
448 		return 0;
449 
450 	modfilelen = st.st_size;
451 
452 	if ((buf = malloc(modfilelen)) == NULL) {
453 		fprintf(stderr, "uade: can't allocate mem in process_module()");
454 		fclose(modfile);
455 		return 0;
456 	}
457 
458 	rb = 0;
459 	while (rb < modfilelen) {
460 		size_t ret = fread(&buf[rb], 1, modfilelen - rb, modfile);
461 		if (ret == 0)
462 			break;
463 		rb += ret;
464 	}
465 
466 	fclose(modfile);
467 
468 	if (rb < modfilelen) {
469 		fprintf(stderr, "uade: song info could not read %s fully\n",
470 			filename);
471 		free(buf);
472 		return 0;
473 	}
474 
475 	snprintf(tmpstr, sizeof tmpstr, "UADE2 MODINFO:\n\nFile name:      %s\nFile length:    %zd bytes\n", filename, modfilelen);
476 	strlcpy(credits, tmpstr, credits_len);
477 
478 	/* Get filetype in pre */
479 	uade_filemagic(buf, modfilelen, pre, modfilelen, filename, 0);
480 
481 	snprintf(tmpstr, sizeof tmpstr, "File prefix:    %s.*\n", pre);
482 	strlcat(credits, tmpstr, credits_len);
483 
484 	if (strcasecmp(pre, "CUST") == 0) {
485 		/* CUST */
486 		process_custom(credits, credits_len, buf, modfilelen);
487 
488 	} else if (strcasecmp(pre, "DM2") == 0) {
489 		/* DM2 */
490 		process_dm2_mod(credits, credits_len, buf, modfilelen);
491 
492 	} else if (strcasecmp(pre, "DIGI") == 0) {
493 		/* DIGIBooster */
494 		process_digi_mod(credits, credits_len, buf, modfilelen);
495 
496 	} else if ((strcasecmp(pre, "AHX") == 0) ||
497 		   (strcasecmp(pre, "THX") == 0)) {
498 		/* AHX */
499 		process_ahx_mod(credits, credits_len, buf, modfilelen);
500 
501 	} else if ((strcasecmp(pre, "MOD15") == 0) ||
502 		   (strcasecmp(pre, "MOD15_UST") == 0) ||
503 		   (strcasecmp(pre, "MOD15_MST") == 0) ||
504 		   (strcasecmp(pre, "MOD15_ST-IV") == 0)) {
505 		/*MOD15 */
506 		process_ptk_mod(credits, credits_len, 15, buf, modfilelen);
507 
508 	} else if ((strcasecmp(pre, "MOD") == 0) ||
509 		   (strcasecmp(pre, "MOD_DOC") == 0) ||
510 		   (strcasecmp(pre, "MOD_NTK") == 0) ||
511 		   (strcasecmp(pre, "MOD_NTK1") == 0) ||
512 		   (strcasecmp(pre, "MOD_NTK2") == 0) ||
513 		   (strcasecmp(pre, "MOD_FLT4") == 0) ||
514 		   (strcasecmp(pre, "MOD_FLT8") == 0) ||
515 		   (strcasecmp(pre, "MOD_ADSC4") == 0) ||
516 		   (strcasecmp(pre, "MOD_ADSC8") == 0) ||
517 		   (strcasecmp(pre, "MOD_COMP") == 0) ||
518 		   (strcasecmp(pre, "MOD_NTKAMP") == 0) ||
519 		   (strcasecmp(pre, "PPK") == 0) ||
520 		   (strcasecmp(pre, "MOD_PC") == 0) ||
521 		   (strcasecmp(pre, "ICE") == 0) ||
522 		   (strcasecmp(pre, "ADSC") == 0)) {
523 		 /*MOD*/
524 		    process_ptk_mod(credits, credits_len, 31, buf, modfilelen);
525 	} else if (strcasecmp(pre, "DL") == 0) {
526 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "UNCL",
527 				 "EART", 0x28);
528 	} else if (strcasecmp(pre, "BSS") == 0) {
529 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "BEAT",
530 				 "HOVE", 0x1c);
531 	} else if (strcasecmp(pre, "GRAY") == 0) {
532 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "FRED",
533 				 "GRAY", 0x10);
534 	} else if (strcasecmp(pre, "JMF") == 0) {
535 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "J.FL",
536 				 "OGEL", 0x14);
537 	} else if (strcasecmp(pre, "SPL") == 0) {
538 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "!SOP",
539 				 "ROL!", 0x10);
540 	} else if (strcasecmp(pre, "HD") == 0) {
541 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "H.DA",
542 				 "VIES", 24);
543 	} else if (strcasecmp(pre, "RIFF") == 0) {
544 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "RIFF",
545 				 "RAFF", 0x14);
546 	} else if (strcasecmp(pre, "FP") == 0) {
547 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "F.PL",
548 				 "AYER", 0x8);
549 	} else if (strcasecmp(pre, "CORE") == 0) {
550 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "S.PH",
551 				 "IPPS", 0x20);
552 	} else if (strcasecmp(pre, "BDS") == 0) {
553 		process_WTWT_mod(credits, credits_len, buf, modfilelen, "DAGL",
554 				 "ISH!", 0x14);
555 	}
556 
557 	free(buf);
558 
559 	return 0;
560 }
561 
uade_generate_song_title(char * title,size_t dstlen,struct uade_state * state)562 int uade_generate_song_title(char *title, size_t dstlen,
563 			     struct uade_state *state)
564 {
565 	size_t srcoffs;
566 	size_t dstoffs;
567 	size_t srclen;
568 	char *format;
569 	char *bname;
570 	char p[64];
571 	char *default_format = "%F %X [%P]";
572 	struct uade_song *us = state->song;
573 	struct uade_config *uc = &state->config;
574 
575 	/* %A min subsong
576 	   %B cur subsong
577 	   %C max subsong
578 	   %F file base name (us->module_filename)
579 	   %P player name
580 	   %T title
581 	   %X print subsong info if more than one subsong exist
582 	 */
583 
584 	format = uc->song_title;
585 
586 	if (format == NULL)
587 		format = default_format;
588 
589 	if (strcmp("default", format) == 0)
590 		format = default_format;
591 
592 	if ((srclen = strlen(format)) == 0) {
593 		fprintf(stderr, "Warning: empty song_title format string.\n");
594 		return 1;
595 	}
596 
597 	if (dstlen == 0)
598 		return 1;
599 
600 	if (strlen(us->module_filename) == 0)
601 		return 1;
602 
603 	bname = xbasename(us->module_filename);
604 
605 	p[0] = 0;
606 	if (us->formatname[0] == 0) {
607 		if (us->playername[0] == 0) {
608 			strlcpy(p, "Custom", sizeof p);
609 		} else {
610 			strlcpy(p, us->playername, sizeof p);
611 		}
612 	} else {
613 		if (strncmp(us->formatname, "type: ", 6) == 0) {
614 			strlcpy(p, us->formatname + 6, sizeof p);
615 		} else {
616 			strlcpy(p, us->formatname, sizeof p);
617 		}
618 	}
619 
620 	srcoffs = dstoffs = 0;
621 
622 	title[0] = 0;
623 
624 	while (dstoffs < dstlen) {
625 		char c;
626 		if (srcoffs >= srclen)
627 			break;
628 
629 		if ((c = format[srcoffs]) == 0)
630 			break;
631 
632 		if (c != '%') {
633 			title[dstoffs++] = format[srcoffs++];
634 		} else {
635 			size_t inc;
636 			char *dat = NULL;
637 			char tmp[32];
638 
639 			if ((srcoffs + 1) >= srclen) {
640 				fprintf(stderr, "Error: no identifier given in song title format: %s\n", format);
641 				title[dstoffs] = 0;
642 				return 1;
643 			}
644 
645 			c = format[srcoffs + 1];
646 
647 			switch (c) {
648 			case 'A':
649 				snprintf(tmp, sizeof tmp, "%d",
650 					 us->min_subsong);
651 				dat = tmp;
652 				break;
653 			case 'B':
654 				snprintf(tmp, sizeof tmp, "%d",
655 					 us->cur_subsong);
656 				dat = tmp;
657 				break;
658 			case 'C':
659 				snprintf(tmp, sizeof tmp, "%d",
660 					 us->max_subsong);
661 				dat = tmp;
662 				break;
663 			case 'F':
664 				dat = bname;
665 				break;
666 			case 'P':
667 				dat = p;
668 				break;
669 			case 'T':
670 				dat = us->modulename;
671 				if (strcmp("<no songtitle>", dat) == 0)
672 					dat[0] = 0;
673 				if (dat[0] == 0)
674 					dat = bname;
675 				break;
676 			case 'X':
677 				if (us->min_subsong == us->max_subsong) {
678 					tmp[0] = 0;
679 				} else {
680 					snprintf(tmp, sizeof tmp, "(%d/%d)",
681 						 us->cur_subsong,
682 						 us->max_subsong);
683 				}
684 				dat = tmp;
685 				break;
686 			default:
687 				fprintf(stderr,
688 					"Unknown identifier %%%c in song_title format: %s\n",
689 					c, format);
690 				title[dstoffs] = 0;
691 				return 1;
692 			}
693 			inc = strlcpy(&title[dstoffs], dat, dstlen - dstoffs);
694 			srcoffs += 2;
695 			dstoffs += inc;
696 		}
697 	}
698 
699 	if (dstoffs < dstlen)
700 		title[dstoffs] = 0;
701 	else
702 		title[dstlen - 1] = 0;
703 
704 	return 0;
705 }
706 
707 /* Returns zero on success, non-zero otherwise. */
uade_song_info(char * info,size_t maxlen,char * filename,enum song_info_type type)708 int uade_song_info(char *info, size_t maxlen, char *filename,
709 		   enum song_info_type type)
710 {
711 	switch (type) {
712 	case UADE_MODULE_INFO:
713 		return process_module(info, maxlen, filename);
714 	case UADE_HEX_DUMP_INFO:
715 		return hexdump(info, maxlen, filename, 2048);
716 	default:
717 		fprintf(stderr, "Illegal info requested.\n");
718 		exit(-1);
719 	}
720 	return 0;
721 }
722