gstreamer/gst/nsf/nsf.c
Tim-Philipp Müller 1e5dc348d4 gst/nsf/nsf.c: Really fix compilation. Apparently it's not enough to just check the return value for errors, but we n...
Original commit message from CVS:
* gst/nsf/nsf.c: (nsf_load):
Really fix compilation. Apparently it's not enough to
just check the return value for errors, but we need to
check for short reads as well (now if only we handled
them too ...). Fixes #347935.
2006-07-19 11:43:50 +00:00

664 lines
16 KiB
C

/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** Library General Public License for more details. To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
**
** nsf.c
**
** NSF loading/saving related functions
** $Id$
*/
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "nsf.h"
#include "log.h"
#include "nes6502.h"
#include "nes_apu.h"
#include "vrcvisnd.h"
#include "vrc7_snd.h"
#include "mmc5_snd.h"
#include "fds_snd.h"
/* TODO: bleh! should encapsulate in NSF */
#define MAX_ADDRESS_HANDLERS 32
static nes6502_memread nsf_readhandler[MAX_ADDRESS_HANDLERS];
static nes6502_memwrite nsf_writehandler[MAX_ADDRESS_HANDLERS];
static nsf_t *cur_nsf = NULL;
static void
nsf_setcontext (nsf_t * nsf)
{
ASSERT (nsf);
cur_nsf = nsf;
}
static uint8
read_mirrored_ram (uint32 address)
{
return cur_nsf->cpu->mem_page[0][address & 0x7FF];
}
static void
write_mirrored_ram (uint32 address, uint8 value)
{
cur_nsf->cpu->mem_page[0][address & 0x7FF] = value;
}
/* can be used for both banked and non-bankswitched NSFs */
static void
nsf_bankswitch (uint32 address, uint8 value)
{
int cpu_page;
uint8 *offset;
cpu_page = address & 0x0F;
offset = (cur_nsf->data - (cur_nsf->load_addr & 0x0FFF)) + (value << 12);
nes6502_getcontext (cur_nsf->cpu);
cur_nsf->cpu->mem_page[cpu_page] = offset;
nes6502_setcontext (cur_nsf->cpu);
}
static nes6502_memread default_readhandler[] = {
{0x0800, 0x1FFF, read_mirrored_ram},
{0x4000, 0x4017, apu_read},
{-1, -1, NULL}
};
static nes6502_memwrite default_writehandler[] = {
{0x0800, 0x1FFF, write_mirrored_ram},
{0x4000, 0x4017, apu_write},
{0x5FF6, 0x5FFF, nsf_bankswitch},
{-1, -1, NULL}
};
static uint8
invalid_read (uint32 address)
{
#ifdef NOFRENDO_DEBUG
log_printf ("filthy NSF read from $%04X\n", address);
#endif /* NOFRENDO_DEBUG */
return 0xFF;
}
static void
invalid_write (uint32 address, uint8 value)
{
#ifdef NOFRENDO_DEBUG
log_printf ("filthy NSF tried to write $%02X to $%04X\n", value, address);
#endif /* NOFRENDO_DEBUG */
}
/* set up the address handlers that the CPU uses */
static void
build_address_handlers (nsf_t * nsf)
{
int count, num_handlers;
memset (nsf_readhandler, 0, sizeof (nsf_readhandler));
memset (nsf_writehandler, 0, sizeof (nsf_writehandler));
num_handlers = 0;
for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; count++, num_handlers++) {
if (NULL == default_readhandler[count].read_func)
break;
memcpy (&nsf_readhandler[num_handlers], &default_readhandler[count],
sizeof (nes6502_memread));
}
if (nsf->apu->ext) {
if (NULL != nsf->apu->ext->mem_read) {
for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS;
count++, num_handlers++) {
if (NULL == nsf->apu->ext->mem_read[count].read_func)
break;
memcpy (&nsf_readhandler[num_handlers], &nsf->apu->ext->mem_read[count],
sizeof (nes6502_memread));
}
}
}
/* catch-all for bad reads */
nsf_readhandler[num_handlers].min_range = 0x2000; /* min address */
nsf_readhandler[num_handlers].max_range = 0x5BFF; /* max address */
nsf_readhandler[num_handlers].read_func = invalid_read; /* handler */
num_handlers++;
nsf_readhandler[num_handlers].min_range = -1;
nsf_readhandler[num_handlers].max_range = -1;
nsf_readhandler[num_handlers].read_func = NULL;
num_handlers++;
ASSERT (num_handlers <= MAX_ADDRESS_HANDLERS);
num_handlers = 0;
for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS; count++, num_handlers++) {
if (NULL == default_writehandler[count].write_func)
break;
memcpy (&nsf_writehandler[num_handlers], &default_writehandler[count],
sizeof (nes6502_memwrite));
}
if (nsf->apu->ext) {
if (NULL != nsf->apu->ext->mem_write) {
for (count = 0; num_handlers < MAX_ADDRESS_HANDLERS;
count++, num_handlers++) {
if (NULL == nsf->apu->ext->mem_write[count].write_func)
break;
memcpy (&nsf_writehandler[num_handlers],
&nsf->apu->ext->mem_write[count], sizeof (nes6502_memwrite));
}
}
}
/* catch-all for bad writes */
nsf_writehandler[num_handlers].min_range = 0x2000; /* min address */
nsf_writehandler[num_handlers].max_range = 0x5BFF; /* max address */
nsf_writehandler[num_handlers].write_func = invalid_write; /* handler */
num_handlers++;
/* protect region at $8000-$FFFF */
nsf_writehandler[num_handlers].min_range = 0x8000; /* min address */
nsf_writehandler[num_handlers].max_range = 0xFFFF; /* max address */
nsf_writehandler[num_handlers].write_func = invalid_write; /* handler */
num_handlers++;
nsf_writehandler[num_handlers].min_range = -1;
nsf_writehandler[num_handlers].max_range = -1;
nsf_writehandler[num_handlers].write_func = NULL;
num_handlers++;
ASSERT (num_handlers <= MAX_ADDRESS_HANDLERS);
}
#define NSF_ROUTINE_LOC 0x5000
/* sets up a simple loop that calls the desired routine and spins */
static void
nsf_setup_routine (uint32 address, uint8 a_reg, uint8 x_reg)
{
uint8 *mem;
nes6502_getcontext (cur_nsf->cpu);
mem =
cur_nsf->cpu->mem_page[NSF_ROUTINE_LOC >> 12] +
(NSF_ROUTINE_LOC & 0x0FFF);
/* our lovely 4-byte 6502 NSF player */
mem[0] = 0x20; /* JSR address */
mem[1] = address & 0xFF;
mem[2] = address >> 8;
mem[3] = 0xF2; /* JAM (cpu kill op) */
cur_nsf->cpu->pc_reg = NSF_ROUTINE_LOC;
cur_nsf->cpu->a_reg = a_reg;
cur_nsf->cpu->x_reg = x_reg;
cur_nsf->cpu->y_reg = 0;
cur_nsf->cpu->s_reg = 0xFF;
nes6502_setcontext (cur_nsf->cpu);
}
/* retrieve any external soundchip driver */
static apuext_t *
nsf_getext (nsf_t * nsf)
{
switch (nsf->ext_sound_type) {
case EXT_SOUND_VRCVI:
return &vrcvi_ext;
case EXT_SOUND_VRCVII:
return &vrc7_ext;
case EXT_SOUND_FDS:
return &fds_ext;
case EXT_SOUND_MMC5:
return &mmc5_ext;
case EXT_SOUND_NAMCO106:
case EXT_SOUND_SUNSOFT_FME07:
case EXT_SOUND_NONE:
default:
return NULL;
}
}
static void
nsf_inittune (nsf_t * nsf)
{
uint8 bank, x_reg;
uint8 start_bank, num_banks;
memset (nsf->cpu->mem_page[0], 0, 0x800);
memset (nsf->cpu->mem_page[6], 0, 0x1000);
memset (nsf->cpu->mem_page[7], 0, 0x1000);
if (nsf->bankswitched) {
/* the first hack of the NSF spec! */
if (EXT_SOUND_FDS == nsf->ext_sound_type) {
nsf_bankswitch (0x5FF6, nsf->bankswitch_info[6]);
nsf_bankswitch (0x5FF7, nsf->bankswitch_info[7]);
}
for (bank = 0; bank < 8; bank++)
nsf_bankswitch (0x5FF8 + bank, nsf->bankswitch_info[bank]);
} else {
/* not bankswitched, just page in our standard stuff */
ASSERT (nsf->load_addr + nsf->length <= 0x10000);
/* avoid ripper filth */
for (bank = 0; bank < 8; bank++)
nsf_bankswitch (0x5FF8 + bank, bank);
start_bank = nsf->load_addr >> 12;
num_banks = ((nsf->load_addr + nsf->length - 1) >> 12) - start_bank + 1;
for (bank = 0; bank < num_banks; bank++)
nsf_bankswitch (0x5FF0 + start_bank + bank, bank);
}
/* determine PAL/NTSC compatibility shite */
if (nsf->pal_ntsc_bits & NSF_DEDICATED_PAL)
x_reg = 1;
else
x_reg = 0;
/* execute 1 frame or so; let init routine run free */
nsf_setup_routine (nsf->init_addr, (uint8) (nsf->current_song - 1), x_reg);
nes6502_execute ((int) NES_FRAME_CYCLES);
}
void
nsf_frame (nsf_t * nsf)
{
/* future expansion =) */
/*nsf_setcontext(nsf); */
/* one frame of NES processing */
nsf_setup_routine (nsf->play_addr, 0, 0);
nes6502_execute ((int) NES_FRAME_CYCLES);
}
/* Deallocate memory */
void
nes_shutdown (nsf_t * nsf)
{
int i;
void *mem;
ASSERT (nsf);
if (nsf->cpu) {
if (nsf->cpu->mem_page[0])
free (nsf->cpu->mem_page[0]);
for (i = 5; i <= 7; i++) {
if (nsf->cpu->mem_page[i])
free (nsf->cpu->mem_page[i]);
}
mem = nsf->cpu;
free (mem);
}
}
void
nsf_init (void)
{
nes6502_init ();
}
/* Initialize NES CPU, hardware, etc. */
static int
nsf_cpuinit (nsf_t * nsf)
{
int i;
nsf->cpu = malloc (sizeof (nes6502_context));
if (NULL == nsf->cpu)
return -1;
memset (nsf->cpu, 0, sizeof (nes6502_context));
nsf->cpu->mem_page[0] = malloc (0x800);
if (NULL == nsf->cpu->mem_page[0])
return -1;
/* allocate some space for the NSF "player" MMC5 EXRAM, and WRAM */
for (i = 5; i <= 7; i++) {
nsf->cpu->mem_page[i] = malloc (0x1000);
if (NULL == nsf->cpu->mem_page[i])
return -1;
}
nsf->cpu->read_handler = nsf_readhandler;
nsf->cpu->write_handler = nsf_writehandler;
return 0;
}
static void
nsf_setup (nsf_t * nsf)
{
int i;
nsf->current_song = nsf->start_song;
if (nsf->pal_ntsc_bits & NSF_DEDICATED_PAL) {
if (nsf->pal_speed)
nsf->playback_rate = 1000000 / nsf->pal_speed;
else
nsf->playback_rate = 50; /* 50 Hz */
} else {
if (nsf->ntsc_speed)
nsf->playback_rate = 1000000 / nsf->ntsc_speed;
else
nsf->playback_rate = 60; /* 60 Hz */
}
nsf->bankswitched = FALSE;
for (i = 0; i < 8; i++) {
if (nsf->bankswitch_info[i]) {
nsf->bankswitched = TRUE;
break;
}
}
}
#ifdef HOST_LITTLE_ENDIAN
#define SWAP_16(x) (x)
#else /* !HOST_LITTLE_ENDIAN */
#define SWAP_16(x) (((uint16) x >> 8) | (((uint16) x & 0xFF) << 8))
#endif /* !HOST_LITTLE_ENDIAN */
/* Load a ROM image into memory */
nsf_t *
nsf_load (char *filename, void *source, int length)
{
FILE *fp = NULL;
char *new_fn = NULL;
nsf_t *temp_nsf;
if (NULL == filename && NULL == source)
return NULL;
if (NULL == source) {
fp = fopen (filename, "rb");
/* Didn't find the file? Maybe the .NSF extension was omitted */
if (NULL == fp) {
new_fn = malloc (strlen (filename) + 5);
if (NULL == new_fn)
return NULL;
strcpy (new_fn, filename);
if (NULL == strrchr (new_fn, '.'))
strcat (new_fn, ".nsf");
fp = fopen (new_fn, "rb");
if (NULL == fp) {
void *t = new_fn;
log_printf ("could not find file '%s'\n", new_fn);
free (t);
return NULL;
}
}
}
temp_nsf = malloc (sizeof (nsf_t));
if (NULL == temp_nsf)
return NULL;
/* Read in the header */
if (NULL == source) {
if (fread (temp_nsf, 1, NSF_HEADER_SIZE, fp) != NSF_HEADER_SIZE) {
log_printf ("error reading file\n");
free (temp_nsf);
return NULL;
}
} else
memcpy (temp_nsf, source, NSF_HEADER_SIZE);
if (memcmp (temp_nsf->id, NSF_MAGIC, 5)) {
if (NULL == source) {
void *t = new_fn;
log_printf ("%s is not an NSF format file\n", new_fn);
fclose (fp);
free (t);
}
nsf_free (&temp_nsf);
return NULL;
}
/* fixup endianness */
temp_nsf->load_addr = SWAP_16 (temp_nsf->load_addr);
temp_nsf->init_addr = SWAP_16 (temp_nsf->init_addr);
temp_nsf->play_addr = SWAP_16 (temp_nsf->play_addr);
temp_nsf->ntsc_speed = SWAP_16 (temp_nsf->ntsc_speed);
temp_nsf->pal_speed = SWAP_16 (temp_nsf->pal_speed);
/* we're now at position 80h */
if (NULL == source) {
fseek (fp, 0, SEEK_END);
temp_nsf->length = ftell (fp) - NSF_HEADER_SIZE;
} else {
temp_nsf->length = length - NSF_HEADER_SIZE;
}
/* Allocate NSF space, and load it up! */
temp_nsf->data = malloc (temp_nsf->length);
if (NULL == temp_nsf->data) {
log_printf ("error allocating memory for NSF data\n");
nsf_free (&temp_nsf);
return NULL;
}
/* seek to end of header, read in data */
if (NULL == source) {
fseek (fp, NSF_HEADER_SIZE, SEEK_SET);
if (fread (temp_nsf->data, temp_nsf->length, 1, fp) != 1)
log_printf ("error reading end of header\n");
fclose (fp);
if (new_fn) {
void *t = new_fn;
free (t);
}
} else
memcpy (temp_nsf->data, (uint8 *) source + NSF_HEADER_SIZE,
temp_nsf->length);
/* Set up some variables */
nsf_setup (temp_nsf);
temp_nsf->apu = NULL; /* just make sure */
if (nsf_cpuinit (temp_nsf)) {
nsf_free (&temp_nsf);
return NULL;
}
return temp_nsf;
}
/* Free an NSF */
void
nsf_free (nsf_t ** nsf)
{
if (*nsf) {
if ((*nsf)->apu)
apu_destroy ((*nsf)->apu);
nes_shutdown (*nsf);
if ((*nsf)->data) {
void *mem = (*nsf)->data;
free (mem);
}
free (*nsf);
}
}
void
nsf_setchan (nsf_t * nsf, int chan, boolean enabled)
{
if (nsf) {
nsf_setcontext (nsf);
apu_setchan (chan, enabled);
}
}
void
nsf_playtrack (nsf_t * nsf, int track, int sample_rate, int sample_bits,
boolean stereo)
{
ASSERT (nsf);
/* make this NSF the current context */
nsf_setcontext (nsf);
/* create the APU */
if (nsf->apu)
apu_destroy (nsf->apu);
nsf->apu = apu_create (sample_rate, nsf->playback_rate, sample_bits, stereo);
if (NULL == nsf->apu) {
nsf_free (&nsf);
return;
}
apu_setext (nsf->apu, nsf_getext (nsf));
/* go ahead and init all the read/write handlers */
build_address_handlers (nsf);
/* convenience? */
nsf->process = nsf->apu->process;
nes6502_setcontext (nsf->cpu);
if (track > nsf->num_songs)
track = nsf->num_songs;
else if (track < 1)
track = 1;
nsf->current_song = track;
apu_reset ();
nsf_inittune (nsf);
}
void
nsf_setfilter (nsf_t * nsf, int filter_type)
{
if (nsf) {
nsf_setcontext (nsf);
apu_setfilter (filter_type);
}
}
/*
** $Log$
** Revision 1.3 2006/07/19 11:43:50 tpm
** * gst/nsf/nsf.c: (nsf_load):
** Really fix compilation. Apparently it's not enough to
** just check the return value for errors, but we need to
** check for short reads as well (now if only we handled
** them too ...). Fixes #347935.
**
** Revision 1.2 2006/07/18 09:36:46 wtay
** * gst/nsf/nsf.c: (nsf_load):
** Fix compilation by not ignoring return values of fread.
**
** Revision 1.1 2006/07/13 15:07:28 wtay
** Based on patches by: Johan Dahlin <johan at gnome dot org>
** Ronald Bultje <rbultje at ronald dot bitfreak dot net>
** * configure.ac:
** * gst/nsf/Makefile.am:
** * gst/nsf/dis6502.h:
** * gst/nsf/fds_snd.c:
** * gst/nsf/fds_snd.h:
** * gst/nsf/fmopl.c:
** * gst/nsf/fmopl.h:
** * gst/nsf/gstnsf.c:
** * gst/nsf/gstnsf.h:
** * gst/nsf/log.c:
** * gst/nsf/log.h:
** * gst/nsf/memguard.c:
** * gst/nsf/memguard.h:
** * gst/nsf/mmc5_snd.c:
** * gst/nsf/mmc5_snd.h:
** * gst/nsf/nes6502.c:
** * gst/nsf/nes6502.h:
** * gst/nsf/nes_apu.c:
** * gst/nsf/nes_apu.h:
** * gst/nsf/nsf.c:
** * gst/nsf/nsf.h:
** * gst/nsf/osd.h:
** * gst/nsf/types.h:
** * gst/nsf/vrc7_snd.c:
** * gst/nsf/vrc7_snd.h:
** * gst/nsf/vrcvisnd.c:
** * gst/nsf/vrcvisnd.h:
** Added NSF decoder plugin. Fixes 151192.
**
** Revision 1.14 2000/07/05 14:54:45 matt
** fix for naughty Crystalis rip
**
** Revision 1.13 2000/07/04 04:59:38 matt
** removed DOS-specific stuff, fixed bug in address handlers
**
** Revision 1.12 2000/07/03 02:19:36 matt
** dynamic address range handlers, cleaner and faster
**
** Revision 1.11 2000/06/23 03:27:58 matt
** cleaned up external sound inteface
**
** Revision 1.10 2000/06/20 20:42:47 matt
** accuracy changes
**
** Revision 1.9 2000/06/20 00:05:58 matt
** changed to driver-based external sound generation
**
** Revision 1.8 2000/06/13 03:51:54 matt
** update API to take freq/sample data on nsf_playtrack
**
** Revision 1.7 2000/06/12 03:57:14 matt
** more robust checking for winamp plugin
**
** Revision 1.6 2000/06/12 01:13:00 matt
** added CPU/APU as members of the nsf struct
**
** Revision 1.5 2000/06/11 16:09:21 matt
** nsf_free is more robust
**
** Revision 1.4 2000/06/09 15:12:26 matt
** initial revision
**
*/