1 /*
2  * psid.c - PSID file handling.
3  *
4  * Written by
5  *  Dag Lem <resid@nimrod.no>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 #include "vice.h"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include "archdep.h"
34 #include "c64mem.h"
35 #include "c64rom.h"
36 #include "c64-resources.h"
37 #include "charset.h"
38 #include "cmdline.h"
39 #include "lib.h"
40 #include "log.h"
41 #include "machine.h"
42 #include "patchrom.h"
43 #include "psid.h"
44 #include "resources.h"
45 #include "types.h"
46 #include "uiapi.h"
47 #include "vsidui.h"
48 #include "vsync.h"
49 #include "zfile.h"
50 
51 static int mus_load_file(const char* filename, int ispsid);
52 
53 static log_t vlog = LOG_ERR;
54 
55 typedef struct psid_s {
56     /* PSID data */
57     uint8_t is_rsid;
58     uint16_t version;
59     uint16_t data_offset;
60     uint16_t load_addr;
61     uint16_t init_addr;
62     uint16_t play_addr;
63     uint16_t songs;
64     uint16_t start_song;
65     uint32_t speed;
66     /* psid v3 allows all 32 bytes to be used with no zero termination */
67     uint8_t name[32 + 1];
68     uint8_t author[32 + 1];
69     uint8_t copyright[32 + 1];
70     uint16_t flags;
71     uint8_t start_page;
72     uint8_t max_pages;
73     uint16_t reserved;
74     uint16_t data_size;
75     uint8_t data[65536];
76 
77     /* Non-PSID data */
78     uint32_t frames_played;
79     uint16_t load_last_addr;
80 } psid_t;
81 
82 #define PSID_V1_DATA_OFFSET 0x76
83 #define PSID_V2_DATA_OFFSET 0x7c
84 
85 
86 static psid_t* psid = NULL;
87 static int psid_tune = 0;       /* currently selected tune, 0: default 1: first, 2: second, etc */
88 static int keepenv = 0;
89 
90 static int firstfile = 0;
91 static int psid_tune_cmdline = 0;
92 
93 struct kernal_s {
94     const char *name;
95     int rev;
96 };
97 
98 static struct kernal_s kernal_match[] = {
99     { "1", C64_KERNAL_REV1 },
100     { "2", C64_KERNAL_REV2 },
101     { "3", C64_KERNAL_REV3 },
102     { "67", C64_KERNAL_SX64 },
103     { "sx", C64_KERNAL_SX64 },
104     { "100", C64_KERNAL_4064 },
105     { "4064", C64_KERNAL_4064 },
106     { NULL, C64_KERNAL_UNKNOWN }
107 };
108 
set_kernal_revision(const char * param,void * extra_param)109 static int set_kernal_revision(const char *param, void *extra_param)
110 {
111     uint16_t sum;                   /* ROM checksum */
112     int id;                     /* ROM identification number */
113     int rev = C64_KERNAL_UNKNOWN;
114     int i = 0;
115 
116     if (!param) {
117         return -1;
118     }
119 
120     do {
121         if (strcmp(kernal_match[i].name, param) == 0) {
122             rev = kernal_match[i].rev;
123         }
124         i++;
125     } while ((rev == C64_KERNAL_UNKNOWN) && (kernal_match[i].name != NULL));
126 
127     if(!c64rom_isloaded()) {
128         kernal_revision = rev;
129         return 0;
130     }
131 
132     if (c64rom_get_kernal_chksum_id(&sum, &id) < 0) {
133         id = C64_KERNAL_UNKNOWN;
134         kernal_revision = id;
135     } else {
136         if (patch_rom_idx(rev) >= 0) {
137             kernal_revision = rev;
138         } else {
139             kernal_revision = id;
140         }
141     }
142     return 0;
143 }
144 
145 
set_keepenv(int val,void * param)146 static int set_keepenv(int val, void *param)
147 {
148     keepenv = val ? 1 : 0;
149     return 0;
150 }
151 
152 static const resource_int_t resources_int[] = {
153     { "PSIDKeepEnv", 0, RES_EVENT_NO, NULL,
154       &keepenv, set_keepenv, NULL },
155     { "PSIDTune", 0, RES_EVENT_NO, NULL,
156       &psid_tune, psid_ui_set_tune, NULL },
157     RESOURCE_INT_LIST_END
158 };
159 
psid_resources_init(void)160 int psid_resources_init(void)
161 {
162     return resources_register_int(resources_int);
163 }
164 
cmdline_keepenv(const char * param,void * extra_param)165 static int cmdline_keepenv(const char *param, void *extra_param)
166 {
167     keepenv = 1;
168     return 0;
169 }
170 
cmdline_psid_tune(const char * param,void * extra_param)171 static int cmdline_psid_tune(const char *param, void *extra_param)
172 {
173     psid_tune_cmdline = atoi(param);
174     if (psid_tune_cmdline < 0) {
175         psid_tune_cmdline = 0;
176     }
177     return 0;
178 }
179 
180 static const cmdline_option_t cmdline_options[] =
181 {
182     /* The Video Standard options are copied from the machine files. */
183     { "-pal", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
184       NULL, NULL, "MachineVideoStandard", (resource_value_t)MACHINE_SYNC_PAL,
185       NULL, "Use PAL sync factor" },
186     { "-ntsc", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
187       NULL, NULL, "MachineVideoStandard", (resource_value_t)MACHINE_SYNC_NTSC,
188       NULL, "Use NTSC sync factor" },
189     { "-ntscold", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
190       NULL, NULL, "MachineVideoStandard", (resource_value_t)MACHINE_SYNC_NTSCOLD,
191       NULL, "Use old NTSC sync factor" },
192     { "-paln", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
193       NULL, NULL, "MachineVideoStandard", (resource_value_t)MACHINE_SYNC_PALN,
194       NULL, "Use PAL-N sync factor" },
195     { "-keepenv", CALL_FUNCTION, CMDLINE_ATTRIB_NONE,
196       cmdline_keepenv, NULL, NULL, NULL,
197       NULL, "Override PSID settings for Video standard and SID model" },
198     { "-tune", CALL_FUNCTION, CMDLINE_ATTRIB_NEED_ARGS,
199       cmdline_psid_tune, NULL, NULL, NULL,
200       "<number>", "Specify PSID tune <number>" },
201     { "-kernal", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
202       NULL, NULL, "KernalName", NULL,
203       "<Name>", "Specify name of Kernal ROM image" },
204     { "-basic", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
205       NULL, NULL, "BasicName", NULL,
206       "<Name>", "Specify name of BASIC ROM image" },
207     { "-chargen", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
208       NULL, NULL, "ChargenName", NULL,
209       "<Name>", "Specify name of character generator ROM image" },
210     { "-kernalrev", CALL_FUNCTION, CMDLINE_ATTRIB_NONE,
211       set_kernal_revision, NULL, NULL, NULL,
212       "<Revision>", "Patch the Kernal ROM to the specified <revision> (1: rev. 1, 2: rev. 2, 3: rev. 3, 67/sx: sx64, 100/4064: 4064)" },
213     CMDLINE_LIST_END
214 };
215 
psid_cmdline_options_init(void)216 int psid_cmdline_options_init(void)
217 {
218     return cmdline_register_options(cmdline_options);
219 }
220 
psid_extract_word(uint8_t ** buf)221 static uint16_t psid_extract_word(uint8_t **buf)
222 {
223     uint16_t word = (*buf)[0] << 8 | (*buf)[1];
224     *buf += 2;
225     return word;
226 }
227 
psid_load_file(const char * filename)228 int psid_load_file(const char* filename)
229 {
230     FILE* f;
231     uint8_t buf[PSID_V2_DATA_OFFSET + 2];
232     uint8_t *ptr = buf;
233     unsigned int length;
234 
235     /* HACK: the selected tune number is handled by the "PSIDtune" resource, which
236      *       is actually saved in the ini file, and thus loaded and restored at
237      *       startup. however, we do not want that. instead we want the default
238      *       tune of the respective .sid file to be used, or the explicit tune
239      *       number given on commandline (if any).
240      */
241     if (!firstfile) {
242         if (psid_tune_cmdline) {
243             psid_tune = psid_tune_cmdline;
244         } else {
245             psid_tune = 0;
246         }
247         firstfile = 1;
248     }
249 
250     if (vlog == LOG_ERR) {
251         vlog = log_open("Vsid");
252     }
253 
254     if (!(f = zfile_fopen(filename, MODE_READ))) {
255         return -1;
256     }
257 
258     lib_free(psid);
259     psid = lib_calloc(sizeof(psid_t), 1);
260 
261     if (fread(ptr, 1, 6, f) != 6 || (memcmp(ptr, "PSID", 4) != 0 && memcmp(ptr, "RSID", 4) != 0)) {
262         goto fail;
263     }
264     psid->is_rsid = (ptr[0] == 'R');
265 
266     ptr += 4;
267     psid->version = psid_extract_word(&ptr);
268 
269     if (psid->version < 1 || psid->version > 4) {
270         log_error(vlog, "Unknown PSID version number: %d.", (int)psid->version);
271         goto fail;
272     }
273     log_message(vlog, "PSID version number: %d.", (int)psid->version);
274 
275     length = (unsigned int)((psid->version == 1 ? PSID_V1_DATA_OFFSET : PSID_V2_DATA_OFFSET) - 6);
276 
277     if (fread(ptr, 1, length, f) != length) {
278         log_error(vlog, "Reading PSID header.");
279         goto fail;
280     }
281 
282     psid->data_offset = psid_extract_word(&ptr);
283     psid->load_addr = psid_extract_word(&ptr);
284     psid->init_addr = psid_extract_word(&ptr);
285     psid->play_addr = psid_extract_word(&ptr);
286     psid->songs = psid_extract_word(&ptr);
287     psid->start_song = psid_extract_word(&ptr);
288     psid->speed = psid_extract_word(&ptr) << 16;
289     psid->speed |= psid_extract_word(&ptr);
290     psid->frames_played = 0;
291     memcpy(psid->name, ptr, 32);
292     psid->name[32] = '\0';
293     ptr += 32;
294     memcpy(psid->author, ptr, 32);
295     psid->author[32] = '\0';
296     ptr += 32;
297     memcpy(psid->copyright, ptr, 32);
298     psid->copyright[32] = '\0';
299     ptr += 32;
300     if (psid->version >= 2) {
301         psid->flags = psid_extract_word(&ptr);
302         psid->start_page = *ptr++;
303         psid->max_pages = *ptr++;
304         psid->reserved = psid_extract_word(&ptr);
305     } else {
306         psid->flags = 0;
307         psid->start_page = 0;
308         psid->max_pages = 0;
309         psid->reserved = 0;
310     }
311 
312     if ((psid->start_song < 1) || (psid->start_song > psid->songs)) {
313         log_error(vlog, "Default tune out of range (%d of %d ?), using 1 instead.", psid->start_song, psid->songs);
314         psid->start_song = 1;
315     }
316 
317     vsid_ui_display_nr_of_tunes(psid->songs);
318     vsid_ui_set_default_tune(psid->start_song);
319 
320     /* Check for SIDPLAYER MUS files. */
321     if (psid->flags & 0x01) {
322         zfile_fclose(f);
323         return mus_load_file(filename, 1);
324     }
325 
326     /* Zero load address => the load address is stored in the
327        first two bytes of the binary C64 data. */
328     if (psid->load_addr == 0) {
329         if (fread(ptr, 1, 2, f) != 2) {
330             log_error(vlog, "Reading PSID load address.");
331             goto fail;
332         }
333         psid->load_addr = ptr[0] | ptr[1] << 8;
334     }
335 
336     /* Zero init address => use load address. */
337     if (psid->init_addr == 0) {
338         psid->init_addr = psid->load_addr;
339     }
340 
341     /* Read binary C64 data. */
342     psid->data_size = (uint16_t)fread(psid->data, 1, sizeof(psid->data), f);
343     psid->load_last_addr = (psid->load_addr + psid->data_size - 1);
344 
345     if (ferror(f)) {
346         log_error(vlog, "Reading PSID data.");
347         goto fail;
348     }
349 
350     if (!feof(f)) {
351         log_error(vlog, "More than 64KiB PSID data.");
352         goto fail;
353     }
354 
355     /* Relocation setup. */
356     if (psid->start_page == 0x00) {
357         /* Start and end pages. */
358         int startp = psid->load_addr >> 8;
359         int endp = psid->load_last_addr >> 8;
360 
361         /* Used memory ranges. */
362         unsigned int used[] = {
363             0x00,
364             0x03,
365             0xa0,
366             0xbf,
367             0xd0,
368             0xff,
369             0x00,
370             0x00
371         };        /* calculated below */
372 
373         unsigned int pages[256];
374         unsigned int last_page = 0;
375         unsigned int i, page, tmp;
376 
377         log_message(vlog, "No PSID freepages set, recalculating...");
378 
379         /* finish initialization */
380         used[6] = startp; used[7] = endp;
381 
382         /* Mark used pages in table. */
383         memset(pages, 0, sizeof(pages));
384         for (i = 0; i < sizeof(used) / sizeof(*used); i += 2) {
385             for (page = used[i]; page <= used[i + 1]; page++) {
386                 pages[page] = 1;
387             }
388         }
389 
390         /* Find largest free range. */
391         psid->max_pages = 0x00;
392         for (page = 0; page < sizeof(pages) / sizeof(*pages); page++) {
393             if (!pages[page]) {
394                 continue;
395             }
396             tmp = page - last_page;
397             if (tmp > psid->max_pages) {
398                 psid->start_page = last_page;
399                 psid->max_pages = tmp;
400             }
401             last_page = page + 1;
402         }
403 
404         if (psid->max_pages == 0x00) {
405             psid->start_page = 0xff;
406         }
407     }
408 
409     if (psid->start_page == 0xff || psid->max_pages < 2) {
410         log_error(vlog, "No space for driver.");
411         goto fail;
412     }
413 
414     zfile_fclose(f);
415 
416     return 0;
417 
418 fail:
419     zfile_fclose(f);
420     lib_free(psid);
421     psid = NULL;
422 
423     /* if the file wasnt in PSID format, try MUS/STR */
424     if (memcmp(ptr, "PSID", 4) != 0 && memcmp(ptr, "RSID", 4) != 0) {
425         return mus_load_file(filename, 0);
426     }
427 
428     return -1;
429 }
430 
psid_shutdown(void)431 void psid_shutdown(void)
432 {
433     lib_free(psid);
434     psid = NULL;
435 }
436 
437 /* Use CBM80 vector to start PSID driver. This is a simple method to
438    transfer control to the PSID driver while running in a pure C64
439    environment. */
psid_set_cbm80(uint16_t vec,uint16_t addr)440 static int psid_set_cbm80(uint16_t vec, uint16_t addr)
441 {
442     unsigned int i;
443     uint8_t cbm80[] = { 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc2, 0xcd, 0x38, 0x30 };
444 
445     cbm80[0] = vec & 0xff;
446     cbm80[1] = vec >> 8;
447 
448     for (i = 0; i < sizeof(cbm80); i++) {
449         /* make backup of original content at 0x8000 */
450         ram_store((uint16_t)(addr + i), ram_read((uint16_t)(0x8000 + i)));
451         /* copy header */
452         ram_store((uint16_t)(0x8000 + i), cbm80[i]);
453     }
454 
455     return i;
456 }
457 
psid_init_tune(int install_driver_hook)458 void psid_init_tune(int install_driver_hook)
459 {
460     int start_song = psid_tune;
461     int sync, sid_model;
462     int i;
463     uint16_t reloc_addr;
464     uint16_t addr;
465     int speedbit;
466     char* irq;
467     char irq_str[20];
468     const char csidflag[4][8] = { "UNKNOWN", "6581", "8580", "ANY"};
469 
470     if (!psid) {
471         return;
472     }
473 
474     psid->frames_played = 0;
475 
476     reloc_addr = psid->start_page << 8;
477 
478     log_message(vlog, "Driver=$%04X, Image=$%04X-$%04X, Init=$%04X, Play=$%04X",
479             reloc_addr, psid->load_addr,
480             (unsigned int)(psid->load_addr + psid->data_size - 1),
481             psid->init_addr, psid->play_addr);
482 
483     /* PAL/NTSC. */
484     resources_get_int("MachineVideoStandard", &sync);
485 
486     /* MOS6581/MOS8580. */
487     resources_get_int("SidModel", &sid_model);
488 
489     /* Check tune number. */
490     /* printf("start_song: %d psid->start_song %d\n", start_song, psid->start_song); */
491 
492     if (start_song == 0) {
493         start_song = psid->start_song;
494     } else if (start_song < 1 || start_song > psid->songs) {
495         log_warning(vlog, "Tune out of range.");
496         start_song = psid->start_song;
497     }
498 
499     /* Check for PlaySID specific file. */
500     if (psid->flags & 0x02 && !psid->is_rsid) {
501         log_warning(vlog, "Image is PlaySID specific - trying anyway.");
502     }
503 
504     /* Check tune speed. */
505     speedbit = 1;
506     for (i = 1; i < start_song && i < 32; i++) {
507         speedbit <<= 1;
508     }
509 
510     irq = psid->speed & speedbit ? "CIA 1" : "VICII";
511 
512     if (psid->play_addr) {
513         strcpy(irq_str, irq);
514     } else {
515         sprintf(irq_str, "custom (%s ?)", irq);
516     }
517 
518     if (console_mode) {
519         log_message(vlog, "   Title: %s", (char *) psid->name);
520         log_message(vlog, "  Author: %s", (char *) psid->author);
521         log_message(vlog, "Released: %s", (char *) psid->copyright);
522         log_message(vlog, "Using %s sync", sync == MACHINE_SYNC_PAL ? "PAL" : "NTSC");
523         log_message(vlog, "SID model: %s", csidflag[(psid->flags >> 4) & 3]);
524         log_message(vlog, "Using %s interrupt", irq_str);
525         log_message(vlog, "Playing tune %d out of %d (default=%d)", start_song, psid->songs, psid->start_song);
526     } else {
527         if (machine_class == VICE_MACHINE_VSID) {
528             char * driver_info_text;
529             driver_info_text = lib_msprintf("Driver=$%04X, Image=$%04X-$%04X, Init=$%04X, Play=$%04X", reloc_addr, psid->load_addr,
530                                             psid->load_addr + psid->data_size - 1, psid->init_addr, psid->play_addr);
531             vsid_ui_setdrv(driver_info_text);
532             vsid_ui_set_driver_addr(reloc_addr);
533             vsid_ui_set_load_addr(psid->load_addr);
534             vsid_ui_set_init_addr(psid->init_addr);
535             vsid_ui_set_play_addr(psid->play_addr);
536             vsid_ui_set_data_size(psid->data_size);
537             lib_free(driver_info_text);
538         }
539         vsid_ui_display_name((char *)(psid->name));
540         vsid_ui_display_author((char *)(psid->author));
541         vsid_ui_display_copyright((char *)(psid->copyright));
542 
543         vsid_ui_display_sync(sync);
544         vsid_ui_display_sid_model(sid_model);
545         vsid_ui_display_irqtype(irq_str);
546         vsid_ui_display_tune_nr(start_song);
547         vsid_ui_display_time(0);
548     }
549 
550     /* Store parameters for PSID player. */
551     if (install_driver_hook) {
552         /* Skip JMP. */
553         addr = reloc_addr + 3 + 9;
554 
555         /* CBM80 reset vector. */
556         addr += psid_set_cbm80((uint16_t)(reloc_addr + 9), addr);
557 
558         ram_store(addr, (uint8_t)(start_song));
559     }
560 
561     /* put song number into address 780/1/2 (A/X/Y) for use by BASIC tunes */
562     ram_store(780, (uint8_t)(start_song - 1));
563     ram_store(781, (uint8_t)(start_song - 1));
564     ram_store(782, (uint8_t)(start_song - 1));
565     /* force flag in c64 memory, many sids reads it and must be set AFTER the sid flag is read */
566     ram_store((uint16_t)(0x02a6), (uint8_t)(sync == MACHINE_SYNC_NTSC ? 0 : 1));
567 }
568 
psid_basic_rsid_to_autostart(uint16_t * address,uint8_t ** data,uint16_t * length)569 int psid_basic_rsid_to_autostart(uint16_t *address, uint8_t **data, uint16_t *length)
570 {
571     if (psid && psid->is_rsid && psid->flags & 0x02) {
572         *address = psid->load_addr;
573         *data = psid->data;
574         *length = psid->data_size;
575         return 1;
576     }
577     return 0;
578 }
579 
580 /* called from machine_play_psid */
psid_set_tune(int tune)581 void psid_set_tune(int tune)
582 {
583     if (tune == -1) {
584         psid_tune = 0;
585         lib_free(psid);
586         psid = NULL;
587     } else {
588         psid_tune = tune;
589     }
590 }
591 
592 /* used for setting the PSIDtune resource */
psid_ui_set_tune(int tune,void * param)593 int psid_ui_set_tune(int tune, void *param)
594 {
595     psid_tune = (tune == -1) ? 0 : tune;
596 
597     psid_set_tune(psid_tune);
598     vsync_suspend_speed_eval();
599     machine_trigger_reset(MACHINE_RESET_MODE_SOFT);
600 
601     return 0;
602 }
603 
psid_tunes(int * default_tune)604 int psid_tunes(int* default_tune)
605 {
606     *default_tune = psid ? psid->start_song : 0;
607 
608     return psid ? psid->songs : 0;
609 }
610 
psid_init_driver(void)611 void psid_init_driver(void)
612 {
613     uint8_t psid_driver[] = {
614 #include "psiddrv.h"
615     };
616     char *psid_reloc = (char *)psid_driver;
617     int psid_size;
618 
619     uint16_t reloc_addr;
620     uint16_t addr;
621     int i;
622     int sync;
623     int sid2loc, sid3loc;
624 
625     if (!psid) {
626         return;
627     }
628 
629     /* C64 PAL/NTSC flag. */
630     resources_get_int("MachineVideoStandard", &sync);
631     if (!keepenv) {
632         switch ((psid->flags >> 2) & 0x03) {
633             case 0x01:
634                 sync = MACHINE_SYNC_PAL;
635                 resources_set_int("MachineVideoStandard", sync);
636                 break;
637             case 0x02:
638                 sync = MACHINE_SYNC_NTSC;
639                 resources_set_int("MachineVideoStandard", sync);
640                 break;
641             default:
642                 /* Keep settings (00 = unknown, 11 = any) */
643                 break;
644         }
645     }
646 
647     /* Stereo SID specification support from Wilfred Bos.
648      * Top byte of reserved holds the middle nybbles of
649      * the 2nd chip address. */
650     resources_set_int("SidStereo", 0);
651     if (psid->version >= 3) {
652         sid2loc = 0xd000 | ((psid->reserved >> 4) & 0x0ff0);
653         log_message(vlog, "2nd SID at $%04x", (unsigned int)sid2loc);
654         if (((sid2loc >= 0xd420 && sid2loc < 0xd800) || sid2loc >= 0xde00)
655             && (sid2loc & 0x10) == 0) {
656             resources_set_int("SidStereo", 1);
657             resources_set_int("Sid2AddressStart", sid2loc);
658         }
659         sid3loc = 0xd000 | ((psid->reserved << 4) & 0x0ff0);
660         if (sid3loc != 0xd000) {
661             log_message(vlog, "3rd SID at $%04x", (unsigned int)sid3loc);
662             if (((sid3loc >= 0xd420 && sid3loc < 0xd800) || sid3loc >= 0xde00)
663                 && (sid3loc & 0x10) == 0) {
664                 resources_set_int("SidStereo", 2);
665                 resources_set_int("Sid3AddressStart", sid3loc);
666             }
667         }
668     }
669 
670     /* MOS6581/MOS8580 flag. */
671     if (!keepenv) {
672         switch ((psid->flags >> 4) & 0x03) {
673             case 0x01:
674                 resources_set_int("SidModel", 0);
675                 break;
676             case 0x02:
677                 resources_set_int("SidModel", 1);
678                 break;
679             default:
680                 /* Keep settings (00 = unknown, 11 = any) */
681                 break;
682         }
683         /* FIXME: second chip model is ignored,
684          * but it is stored at (flags >> 6) & 3. */
685     }
686 
687     /* Clear low memory to minimize the damage of PSIDs doing bad reads. */
688     for (addr = 0; addr < 0x0800; addr++) {
689         ram_store(addr, (uint8_t)0x00);
690     }
691 
692     /* Relocation of C64 PSID driver code. */
693     reloc_addr = psid->start_page << 8;
694     psid_size = sizeof(psid_driver);
695     log_message(vlog, "PSID free pages: $%04x-$%04x",
696             reloc_addr, (reloc_addr + (psid->max_pages << 8)) - 1U);
697 
698     if (!reloc65((char **)&psid_reloc, &psid_size, reloc_addr)) {
699         log_error(vlog, "Relocation.");
700         psid_set_tune(-1);
701         return;
702     }
703 
704     for (i = 0; i < psid_size; i++) {
705         ram_store((uint16_t)(reloc_addr + i), psid_reloc[i]);
706     }
707 
708     /* Store binary C64 data. */
709     for (i = 0; i < psid->data_size; i++) {
710         ram_store((uint16_t)(psid->load_addr + i), psid->data[i]);
711     }
712 
713     /* Skip JMP and CBM80 reset vector. */
714     addr = reloc_addr + 3 + 9 + 9;
715 
716     /* Store parameters for PSID player. */
717     ram_store(addr++, (uint8_t)(0));
718     ram_store(addr++, (uint8_t)(psid->songs));
719     ram_store(addr++, (uint8_t)(psid->load_addr & 0xff));
720     ram_store(addr++, (uint8_t)(psid->load_addr >> 8));
721     ram_store(addr++, (uint8_t)(psid->init_addr & 0xff));
722     ram_store(addr++, (uint8_t)(psid->init_addr >> 8));
723     ram_store(addr++, (uint8_t)(psid->play_addr & 0xff));
724     ram_store(addr++, (uint8_t)(psid->play_addr >> 8));
725     ram_store(addr++, (uint8_t)(psid->speed & 0xff));
726     ram_store(addr++, (uint8_t)((psid->speed >> 8) & 0xff));
727     ram_store(addr++, (uint8_t)((psid->speed >> 16) & 0xff));
728     ram_store(addr++, (uint8_t)(psid->speed >> 24));
729     ram_store(addr++, (uint8_t)((int)sync == MACHINE_SYNC_PAL ? 1 : 0));
730     ram_store(addr++, (uint8_t)(psid->load_last_addr & 0xff));
731     ram_store(addr++, (uint8_t)(psid->load_last_addr >> 8));
732 }
733 
psid_increment_frames(void)734 unsigned int psid_increment_frames(void)
735 {
736     if (!psid) {
737         return 0;
738     }
739 
740     (psid->frames_played)++;
741 
742     return (unsigned int)(psid->frames_played);
743 }
744 
745 /******************************************************************************
746  * compute sidplayer (.mus/.str) support
747  *
748  * to minimize code duplication and to simplify the integration with the rest
749  * of the code, the sidplayer data is simply converted into PSID like format
750  * at load time. heavily inspired by the respective code in libsidplay2.
751  ******************************************************************************/
752 
753 #define MUS_HLT_CMD      0x014F
754 
755 #define MUS_IMAGE_START  0x0900
756 #define MUS_DATA_ADDR    0x0900
757 #define MUS_DATA2_ADDR   0x6900
758 #define MUS_DATA_MAXLEN  0x6000
759 
760 #define MUS_DRIVER_ADDR  0xe000
761 #define MUS_DRIVER2_ADDR 0xf000
762 
763 #define MUS_SID1_BASE_ADDR   0xd400
764 #define MUS_SID2_BASE_ADDR   0xd500
765 
766 #include "musdrv.h"
767 
mus_install(void)768 static void mus_install(void)
769 {
770     uint16_t dest;
771     /* Install MUS player #1. */
772     dest = ((mus_driver[1] << 8) | mus_driver[0]) - MUS_IMAGE_START;
773     memcpy(psid->data + dest, mus_driver + 2, sizeof(mus_driver) - 2);
774     /* Point player #1 to data #1. */
775     psid->data[dest + 0xc6e] = MUS_DATA_ADDR & 0xFF;
776     psid->data[dest + 0xc70] = MUS_DATA_ADDR >> 8;
777 
778     /* Install MUS player #2. It doesnt hurt to do it also for mono tunes. */
779     dest = ((mus_stereo_driver[1] << 8) | mus_stereo_driver[0]) - MUS_IMAGE_START;
780     memcpy(psid->data + dest, mus_stereo_driver + 2, sizeof(mus_stereo_driver) - 2);
781     /* Point player #2 to data #2. */
782     psid->data[dest + 0xc6e] = MUS_DATA2_ADDR & 0xFF;
783     psid->data[dest + 0xc70] = MUS_DATA2_ADDR >> 8;
784 }
785 
mus_check(const unsigned char * buf)786 static int mus_check(const unsigned char *buf)
787 {
788     unsigned int voice1Index, voice2Index, voice3Index;
789 
790     /* Skip 3x length entry. */
791     voice1Index = ((buf[1] << 8) | buf[0]) + (3 * 2);
792     voice2Index = voice1Index + ((buf[3] << 8) | buf[2]);
793     voice3Index = voice2Index + ((buf[5] << 8) | buf[4]);
794     return (((buf[voice1Index - 2] << 8) | buf[voice1Index - 1]) == MUS_HLT_CMD)
795         && (((buf[voice2Index - 2] << 8) | buf[voice2Index - 1]) == MUS_HLT_CMD)
796         && (((buf[voice3Index - 2] << 8) | buf[voice3Index - 1]) == MUS_HLT_CMD);
797 }
798 
799 /* check for graphic characters */
ispetchar(unsigned char c)800 static int ispetchar(unsigned char c)
801 {
802     if ((c >= 32) && (c <= 93)) {
803         return 1;
804     }
805     return 0;
806 }
807 
copystring(unsigned char * d,const unsigned char * s)808 static const unsigned char *copystring(unsigned char *d, const unsigned char *s)
809 {
810     unsigned char *end = d + 32;
811     int n = 0;
812     /* skip leading spaces and special characters */
813     while (((*s == 0x20) || !ispetchar(*s)) && (*s != 0x00)) {
814         ++s;
815     }
816     /* copy until end of line, omit special characters */
817     while ((*s != 0x0d) && (*s != 0x00)) {
818         if (ispetchar(*s)) {
819             *d++ = *s;
820             n = (*s == 0x20);
821         } else {
822             /* for special chars, insert one space */
823             if (!n) {
824                 *d++ = 0x20;
825                 n = 1;
826             }
827         }
828         /* if max len of destination is reached, skip until end of source */
829         if (d == end) {
830             while ((*s != 0x0d) && (*s != 0x00)) {
831                 ++s;
832             }
833             break;
834         }
835         ++s;
836     }
837     *d = 0;
838     charset_petconvstring(d, 1);
839     return s + 1;
840 }
841 
mus_extract_credits(const unsigned char * buf,int datalen)842 static void mus_extract_credits(const unsigned char *buf, int datalen)
843 {
844     const unsigned char *end;
845     int n;
846 
847     /* get offset behind note data */
848     n = ((buf[1] << 8) | buf[0]) + (3 * 2);
849     n += ((buf[3] << 8) | buf[2]);
850     n += ((buf[5] << 8) | buf[4]);
851 
852     end = buf + datalen;
853     buf += n;
854 
855     psid->name[0] = psid->author[0] = psid->copyright[0] = 0;
856 
857     if (buf < end) {
858         buf = copystring(psid->name, buf);
859     }
860     if (buf < end) {
861         buf = copystring(psid->author, buf);
862     }
863     if (buf < end) {
864         buf = copystring(psid->copyright, buf);
865     }
866 }
867 
mus_load_file(const char * filename,int ispsid)868 static int mus_load_file(const char* filename, int ispsid)
869 {
870     char *strname;
871     FILE *f;
872     int stereo = 0;
873     size_t n;
874     size_t mus_datalen;
875 
876     if (!(f = zfile_fopen(filename, MODE_READ))) {
877         return -1;
878     }
879 
880     if (!ispsid) {
881         lib_free(psid);
882         psid = lib_calloc(sizeof(psid_t), 1);
883     }
884 
885     /* skip header & original load address */
886     fseek(f, psid->data_offset + (psid->load_addr ? 0 : 2), SEEK_SET);
887     /* read .mus data */
888     mus_datalen = fread(psid->data, 1, 0xffff - MUS_DATA_MAXLEN, f);
889 
890     if (!mus_check(psid->data)) {
891         log_error(vlog, "not a valid .mus file.");
892         goto fail;
893     }
894     zfile_fclose(f);
895 
896     /* read additional stereo (.str) data if available */
897     /* FIXME: the psid file format specification does not tell how to handle
898               stereo sidplayer tunes when they are in psid format */
899     if (!ispsid) {
900         strname = lib_strdup(filename);
901         n = strlen(strname) - 4;
902         strcpy(strname + n, ".str");
903 
904         if ((f = zfile_fopen(strname, MODE_READ))) {
905             fseek(f, 2, SEEK_SET); /* skip original load address */
906             if (fread(psid->data + (MUS_DATA2_ADDR - MUS_IMAGE_START), 1, 0xffff - MUS_DATA_MAXLEN, f) < (3 * 2)) {
907                 goto fail;
908             }
909             zfile_fclose(f);
910             stereo = 1;
911         }
912         lib_free(strname);
913         /* only extract credits if this is NOT a psid file */
914         mus_extract_credits(psid->data, (int)mus_datalen);
915     }
916 
917     mus_install();
918 
919     psid->version = 3;  /* v3 so we get stereo support */
920     psid->flags = 2 << 2; /* NTSC */
921     psid->start_page = 0x04;
922     psid->max_pages = (MUS_DATA_ADDR - 0x0400) >> 8;
923 
924     psid->load_addr = MUS_IMAGE_START;
925     psid->data_size = 0x10000 - MUS_IMAGE_START;
926 
927     psid->songs = 1;
928     psid->start_song = 1;
929     psid->speed = 1;    /* play at 60Hz */
930 
931     if (stereo) {
932         /* Player #1 + #2. */
933         psid->reserved = (MUS_SID2_BASE_ADDR << 4) & 0xff00; /* second SID at 0xd500 */
934         psid->init_addr = 0xfc90;
935         psid->play_addr = 0xfc96;
936     } else {
937         /* Player #1. */
938         psid->init_addr = 0xec60;
939         psid->play_addr = 0xec80;
940     }
941 
942     return 0;
943 
944 fail:
945     zfile_fclose(f);
946     lib_free(psid);
947     psid = NULL;
948     return -1;
949 }
950