mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-20 23:36:38 +00:00
1121 lines
28 KiB
C
1121 lines
28 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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)
|
|
{
|
|
nes6502_chk_mem_access (&cur_nsf->cpu->acc_mem_page[0][address & 0x7FF],
|
|
NES6502_READ_ACCESS);
|
|
return cur_nsf->cpu->mem_page[0][address & 0x7FF];
|
|
}
|
|
|
|
static void
|
|
write_mirrored_ram (uint32 address, uint8 value)
|
|
{
|
|
nes6502_chk_mem_access (&cur_nsf->cpu->acc_mem_page[0][address & 0x7FF],
|
|
NES6502_WRITE_ACCESS);
|
|
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;
|
|
int roffset;
|
|
uint8 *offset;
|
|
|
|
cpu_page = address & 0x0F;
|
|
roffset = -(cur_nsf->load_addr & 0x0FFF) + ((int) value << 12);
|
|
offset = cur_nsf->data + roffset;
|
|
|
|
nes6502_getcontext (cur_nsf->cpu);
|
|
cur_nsf->cpu->mem_page[cpu_page] = offset;
|
|
#ifdef NES6502_MEM_ACCESS_CTRL
|
|
cur_nsf->cpu->acc_mem_page[cpu_page] = offset + cur_nsf->length;
|
|
#endif
|
|
nes6502_setcontext (cur_nsf->cpu);
|
|
}
|
|
|
|
static nes6502_memread default_readhandler[] = {
|
|
{0x0800, 0x1FFF, read_mirrored_ram},
|
|
{0x4000, 0x4017, apu_read},
|
|
{(uint32) - 1, (uint32) - 1, NULL}
|
|
};
|
|
|
|
static nes6502_memwrite default_writehandler[] = {
|
|
{0x0800, 0x1FFF, write_mirrored_ram},
|
|
{0x4000, 0x4017, apu_write},
|
|
{0x5FF6, 0x5FFF, nsf_bankswitch},
|
|
{(uint32) - 1, (uint32) - 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);
|
|
|
|
#ifdef NES6502_MEM_ACCESS_CTRL
|
|
memset (nsf->cpu->acc_mem_page[0], 0, 0x800);
|
|
memset (nsf->cpu->acc_mem_page[6], 0, 0x1000);
|
|
memset (nsf->cpu->acc_mem_page[7], 0, 0x1000);
|
|
memset (nsf->data + nsf->length, 0, nsf->length);
|
|
#endif
|
|
nsf->cur_frame = 0;
|
|
/* nsf->last_access_frame = 0; */
|
|
nsf->cur_frame_end = !nsf->song_frames
|
|
? 0 : nsf->song_frames[nsf->current_song];
|
|
|
|
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)
|
|
{
|
|
/* This is how Matthew Conte left it */
|
|
/* nsf_setcontext(nsf); *//* future expansion =) */
|
|
|
|
/* This was suggested by Arne Morten Kvarving, who says: */
|
|
/* Also, I fixed a bug that prevented Nosefart to play multiple tunes at
|
|
one time (actually it was just a few missing setcontext calls in the
|
|
playback routine, it had a nice TODO commented beside it. You had to set
|
|
the cpu and apu contexts not just the nsf context).
|
|
|
|
it will affect any player that tries to use nosefart to play more than one
|
|
tune at a time.
|
|
*/
|
|
nsf_setcontext (nsf);
|
|
apu_setcontext (nsf->apu);
|
|
nes6502_setcontext (nsf->cpu);
|
|
|
|
/* one frame of NES processing */
|
|
nsf_setup_routine (nsf->play_addr, 0, 0);
|
|
nes6502_execute ((int) NES_FRAME_CYCLES);
|
|
|
|
++nsf->cur_frame;
|
|
#if defined(NES6502_MEM_ACCESS_CTRL) && 0
|
|
if (nes6502_mem_access) {
|
|
uint32 sec =
|
|
(nsf->last_access_frame + nsf->playback_rate - 1) / nsf->playback_rate;
|
|
nsf->last_access_frame = nsf->cur_frame;
|
|
fprintf (stderr, "nsf : memory access [%x] at frame #%u [%u:%02u]\n",
|
|
nes6502_mem_access, nsf->last_access_frame, sec / 60, sec % 60);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/* Deallocate memory */
|
|
static void
|
|
nes_shutdown (nsf_t * nsf)
|
|
{
|
|
int i;
|
|
|
|
ASSERT (nsf);
|
|
|
|
if (nsf->cpu) {
|
|
if (nsf->cpu->mem_page[0]) {
|
|
free (nsf->cpu->mem_page[0]); /*tracks 1 and 2 of lifeforce hang here. */
|
|
}
|
|
for (i = 5; i <= 7; i++) {
|
|
if (nsf->cpu->mem_page[i]) {
|
|
free (nsf->cpu->mem_page[i]);
|
|
}
|
|
}
|
|
|
|
#ifdef NES6502_MEM_ACCESS_CTRL
|
|
if (nsf->cpu->acc_mem_page[0]) {
|
|
free (nsf->cpu->acc_mem_page[0]);
|
|
}
|
|
for (i = 5; i <= 7; i++) {
|
|
if (nsf->cpu->acc_mem_page[i]) {
|
|
free (nsf->cpu->acc_mem_page[i]);
|
|
}
|
|
}
|
|
#endif
|
|
free (nsf->cpu);
|
|
}
|
|
}
|
|
|
|
int
|
|
nsf_init (void)
|
|
{
|
|
nes6502_init ();
|
|
return 0;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#ifdef NES6502_MEM_ACCESS_CTRL
|
|
nsf->cpu->acc_mem_page[0] = malloc (0x800);
|
|
if (NULL == nsf->cpu->acc_mem_page[0])
|
|
return -1;
|
|
/* allocate some space for the NSF "player" MMC5 EXRAM, and WRAM */
|
|
for (i = 5; i <= 7; i++) {
|
|
nsf->cpu->acc_mem_page[i] = malloc (0x1000);
|
|
if (NULL == nsf->cpu->acc_mem_page[i])
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
nsf->cpu->read_handler = nsf_readhandler;
|
|
nsf->cpu->write_handler = nsf_writehandler;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int
|
|
nsf_playback_rate (nsf_t * nsf)
|
|
{
|
|
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 */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
nsf_setup (nsf_t * nsf)
|
|
{
|
|
int i;
|
|
|
|
nsf->current_song = nsf->start_song;
|
|
nsf_playback_rate (nsf);
|
|
|
|
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 */
|
|
|
|
/* $$$ ben : find extension. Should be OK with DOS, but not with some
|
|
* OS like RiscOS ... */
|
|
static char *
|
|
find_ext (char *fn)
|
|
{
|
|
char *a, *b, *c;
|
|
|
|
a = strrchr (fn, '.');
|
|
b = strrchr (fn, '/');
|
|
c = strrchr (fn, '\\');
|
|
if (a <= b || a <= c) {
|
|
a = 0;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/* $$$ ben : FILE loader */
|
|
struct nsf_file_loader_t
|
|
{
|
|
struct nsf_loader_t loader;
|
|
FILE *fp;
|
|
char *fname;
|
|
int name_allocated;
|
|
};
|
|
|
|
static int
|
|
nfs_open_file (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
|
|
floader->name_allocated = 0;
|
|
floader->fp = 0;
|
|
if (!floader->fname) {
|
|
return -1;
|
|
}
|
|
floader->fp = fopen (floader->fname, "rb");
|
|
if (!floader->fp) {
|
|
char *fname, *ext;
|
|
|
|
ext = find_ext (floader->fname);
|
|
if (ext) {
|
|
/* There was an extension, so we do not change it */
|
|
return -1;
|
|
}
|
|
fname = malloc (strlen (floader->fname) + 5);
|
|
if (!fname) {
|
|
return -1;
|
|
}
|
|
/* try with .nsf extension. */
|
|
strcpy (fname, floader->fname);
|
|
strcat (fname, ".nsf");
|
|
floader->fp = fopen (fname, "rb");
|
|
if (!floader->fp) {
|
|
free (fname);
|
|
return -1;
|
|
}
|
|
floader->fname = fname;
|
|
floader->name_allocated = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
nfs_close_file (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
|
|
if (floader->fp) {
|
|
fclose (floader->fp);
|
|
floader->fp = 0;
|
|
}
|
|
if (floader->fname && floader->name_allocated) {
|
|
free (floader->fname);
|
|
floader->fname = 0;
|
|
floader->name_allocated = 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
nfs_read_file (struct nsf_loader_t *loader, void *data, int n)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
int r = fread (data, 1, n, floader->fp);
|
|
|
|
if (r >= 0) {
|
|
r = n - r;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
nfs_length_file (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
long save, pos;
|
|
|
|
save = ftell (floader->fp);
|
|
fseek (floader->fp, 0, SEEK_END);
|
|
pos = ftell (floader->fp);
|
|
fseek (floader->fp, save, SEEK_SET);
|
|
return pos;
|
|
}
|
|
|
|
static int
|
|
nfs_skip_file (struct nsf_loader_t *loader, int n)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
int r;
|
|
|
|
r = fseek (floader->fp, n, SEEK_CUR);
|
|
return r;
|
|
}
|
|
|
|
static const char *
|
|
nfs_fname_file (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_file_loader_t *floader = (struct nsf_file_loader_t *) loader;
|
|
|
|
return floader->fname ? floader->fname : "<null>";
|
|
}
|
|
|
|
static struct nsf_file_loader_t nsf_file_loader = {
|
|
{
|
|
nfs_open_file,
|
|
nfs_close_file,
|
|
nfs_read_file,
|
|
nfs_length_file,
|
|
nfs_skip_file,
|
|
nfs_fname_file},
|
|
0, 0, 0
|
|
};
|
|
|
|
struct nsf_mem_loader_t
|
|
{
|
|
struct nsf_loader_t loader;
|
|
uint8 *data;
|
|
unsigned long cur;
|
|
unsigned long len;
|
|
char fname[32];
|
|
};
|
|
|
|
static int
|
|
nfs_open_mem (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
|
|
if (!mloader->data) {
|
|
return -1;
|
|
}
|
|
mloader->cur = 0;
|
|
sprintf (mloader->fname, "<mem(%p,%u)>",
|
|
mloader->data, (unsigned int) mloader->len);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
nfs_close_mem (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
|
|
mloader->data = 0;
|
|
mloader->cur = 0;
|
|
mloader->len = 0;
|
|
}
|
|
|
|
static int
|
|
nfs_read_mem (struct nsf_loader_t *loader, void *data, int n)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
int rem;
|
|
|
|
if (n <= 0) {
|
|
return n;
|
|
}
|
|
if (!mloader->data) {
|
|
return -1;
|
|
}
|
|
rem = mloader->len - mloader->cur;
|
|
if (rem > n) {
|
|
rem = n;
|
|
}
|
|
memcpy (data, mloader->data + mloader->cur, rem);
|
|
mloader->cur += rem;
|
|
return n - rem;
|
|
}
|
|
|
|
static int
|
|
nfs_length_mem (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
|
|
return mloader->len;
|
|
}
|
|
|
|
static int
|
|
nfs_skip_mem (struct nsf_loader_t *loader, int n)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
unsigned long goal = mloader->cur + n;
|
|
|
|
mloader->cur = (goal > mloader->len) ? mloader->len : goal;
|
|
return goal - mloader->cur;
|
|
}
|
|
|
|
/* FIXME: not used anywhere */
|
|
#if 0
|
|
static const char *
|
|
nfs_fname_mem (struct nsf_loader_t *loader)
|
|
{
|
|
struct nsf_mem_loader_t *mloader = (struct nsf_mem_loader_t *) loader;
|
|
|
|
return mloader->fname;
|
|
}
|
|
#endif
|
|
|
|
static struct nsf_mem_loader_t nsf_mem_loader = {
|
|
{nfs_open_mem, nfs_close_mem, nfs_read_mem, nfs_length_mem, nfs_skip_mem},
|
|
0, 0, 0
|
|
};
|
|
|
|
nsf_t *
|
|
nsf_load_extended (struct nsf_loader_t *loader)
|
|
{
|
|
nsf_t *temp_nsf = 0;
|
|
int length;
|
|
char id[6];
|
|
|
|
struct
|
|
{
|
|
uint8 magic[4]; /* always "NESM" */
|
|
uint8 type[4]; /* defines extension type */
|
|
uint8 size[4]; /* extension data size (this struct include) */
|
|
} nsf_file_ext;
|
|
|
|
/* no loader ! */
|
|
if (!loader) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Open the "file" */
|
|
if (loader->open (loader) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Get file size, and exit if there is not enough data for NSF header
|
|
* and more since it does not make sens to have header without data.
|
|
*/
|
|
length = loader->length (loader);
|
|
/* For version 2, we do not need file length. just check error later. */
|
|
#if 0
|
|
if (length <= NSF_HEADER_SIZE) {
|
|
log_printf ("nsf : [%s] not an NSF format file\n", loader->fname (loader));
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
/* Read magic */
|
|
if (loader->read (loader, id, 5)) {
|
|
log_printf ("nsf : [%s] error reading magic number\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* Check magic */
|
|
if (memcmp (id, NSF_MAGIC, 5)) {
|
|
log_printf ("nsf : [%s] is not an NSF format file\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* $$$ ben : Now the file should be an NSF, we can start allocating.
|
|
* first : the nsf struct
|
|
*/
|
|
temp_nsf = malloc (sizeof (nsf_t));
|
|
|
|
if (NULL == temp_nsf) {
|
|
log_printf ("nsf : [%s] error allocating nsf header\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
/* $$$ ben : safety net */
|
|
memset (temp_nsf, 0, sizeof (nsf_t));
|
|
/* Copy magic ID */
|
|
memcpy (temp_nsf, id, 5);
|
|
|
|
/* Read header (without MAGIC) */
|
|
if (loader->read (loader, (int8 *) temp_nsf + 5, NSF_HEADER_SIZE - 5)) {
|
|
log_printf ("nsf : [%s] error reading nsf header\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* 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 */
|
|
|
|
|
|
/* Here comes the specific codes for spec version 2 */
|
|
|
|
temp_nsf->length = 0;
|
|
|
|
if (temp_nsf->version > 1) {
|
|
/* Get specified data size in reserved field (3 bytes). */
|
|
temp_nsf->length = 0 + temp_nsf->reserved[0]
|
|
+ (temp_nsf->reserved[1] << 8)
|
|
+ (temp_nsf->reserved[2] << 16);
|
|
|
|
}
|
|
/* no specified size : try to guess with file length. */
|
|
if (!temp_nsf->length) {
|
|
temp_nsf->length = length - NSF_HEADER_SIZE;
|
|
}
|
|
|
|
if (temp_nsf->length <= 0) {
|
|
log_printf ("nsf : [%s] not an NSF format file (missing data)\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* Allocate NSF space, and load it up! */
|
|
{
|
|
int len = temp_nsf->length;
|
|
|
|
#ifdef NES6502_MEM_ACCESS_CTRL
|
|
/* $$$ twice memory for access control shadow mem. */
|
|
len <<= 1;
|
|
#endif
|
|
temp_nsf->data = malloc (len);
|
|
}
|
|
if (NULL == temp_nsf->data) {
|
|
log_printf ("nsf : [%s] error allocating nsf data\n",
|
|
loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* Read data */
|
|
if (loader->read (loader, temp_nsf->data, temp_nsf->length)) {
|
|
log_printf ("nsf : [%s] error reading NSF data\n", loader->fname (loader));
|
|
goto error;
|
|
}
|
|
|
|
/* Here comes the second part of spec > 1 : get extension */
|
|
while (!loader->read (loader, &nsf_file_ext, sizeof (nsf_file_ext))
|
|
&& !memcmp (nsf_file_ext.magic, id, 4)) {
|
|
/* Got a NESM extension here. Checks for known extension type :
|
|
* right now, the only extension is "TIME" which give songs length.
|
|
* in frames.
|
|
*/
|
|
int size;
|
|
|
|
size = 0 + nsf_file_ext.size[0]
|
|
+ (nsf_file_ext.size[1] << 8)
|
|
+ (nsf_file_ext.size[2] << 16)
|
|
+ (nsf_file_ext.size[3] << 24);
|
|
|
|
if (size < sizeof (nsf_file_ext)) {
|
|
log_printf ("nsf : [%s] corrupt extension size (%d)\n",
|
|
loader->fname (loader), size);
|
|
/* Not a fatal error here. Just skip extension loading. */
|
|
break;
|
|
}
|
|
size -= sizeof (nsf_file_ext);
|
|
|
|
if (!temp_nsf->song_frames && !memcmp (nsf_file_ext.type, "TIME", 4)
|
|
&& !(size & 3)
|
|
&& (size >= 2 * 4)
|
|
&& (size <= 256 * 4)) {
|
|
|
|
uint8 tmp_time[256][4];
|
|
int tsongs = size >> 2;
|
|
int i;
|
|
int songs = temp_nsf->num_songs;
|
|
|
|
/* Add 1 for 0 which contains total time for all songs. */
|
|
++songs;
|
|
|
|
if (loader->read (loader, tmp_time, size)) {
|
|
log_printf ("nsf : [%s] missing extension data\n",
|
|
loader->fname (loader));
|
|
/* Not a fatal error here. Just skip extension loading. */
|
|
break;
|
|
}
|
|
/* Alloc song_frames for songs (not tsongs). */
|
|
temp_nsf->song_frames = malloc (sizeof (*temp_nsf->song_frames) * songs);
|
|
if (!temp_nsf->song_frames) {
|
|
log_printf ("nsf : [%s] extension alloc failed\n",
|
|
loader->fname (loader));
|
|
/* Not a fatal error here. Just skip extension loading. */
|
|
break;
|
|
}
|
|
|
|
if (tsongs > songs) {
|
|
tsongs = songs;
|
|
}
|
|
|
|
/* Copy time info. */
|
|
for (i = 0; i < tsongs; ++i) {
|
|
temp_nsf->song_frames[i] = 0 | tmp_time[i][0]
|
|
| (tmp_time[i][1] << 8)
|
|
| (tmp_time[i][2] << 16)
|
|
| (tmp_time[i][2] << 24);
|
|
}
|
|
/* Clear missing (safety net). */
|
|
for (; i < songs; ++i) {
|
|
temp_nsf->song_frames[i] = 0;
|
|
}
|
|
} else if (loader->skip (loader, size)) {
|
|
log_printf ("nsf : [%s] extension skip failed\n", loader->fname (loader));
|
|
/* Not a fatal error here. Just skip extension loading. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Close "file" */
|
|
loader->close (loader);
|
|
loader = 0;
|
|
|
|
/* Set up some variables */
|
|
nsf_setup (temp_nsf);
|
|
temp_nsf->apu = NULL; /* just make sure */
|
|
|
|
if (nsf_cpuinit (temp_nsf)) {
|
|
log_printf ("nsf : error cpu init\n");
|
|
goto error;
|
|
}
|
|
return temp_nsf;
|
|
|
|
/* $$$ ben : some people tell that goto are not clean. I am not agree with
|
|
* them. In most case, it allow to avoid code duplications, which are as
|
|
* most people know a source of error... Here we are sure of being clean
|
|
*/
|
|
error:
|
|
if (loader) {
|
|
loader->close (loader);
|
|
}
|
|
if (temp_nsf) {
|
|
nsf_free (&temp_nsf);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Load a ROM image into memory */
|
|
nsf_t *
|
|
nsf_load (const char *filename, void *source, int length)
|
|
{
|
|
struct nsf_loader_t *loader = 0;
|
|
|
|
/* $$$ ben : new loader */
|
|
if (filename) {
|
|
nsf_file_loader.fname = (char *) filename;
|
|
loader = &nsf_file_loader.loader;
|
|
} else {
|
|
nsf_mem_loader.data = source;
|
|
nsf_mem_loader.len = length;
|
|
nsf_mem_loader.fname[0] = 0;
|
|
loader = &nsf_mem_loader.loader;
|
|
}
|
|
return nsf_load_extended (loader);
|
|
}
|
|
|
|
/* Free an NSF */
|
|
void
|
|
nsf_free (nsf_t ** pnsf)
|
|
{
|
|
nsf_t *nsf;
|
|
|
|
if (!pnsf) {
|
|
return;
|
|
}
|
|
|
|
nsf = *pnsf;
|
|
/* $$$ ben : Don't see why passing a pointer to pointer
|
|
* is not to clear it :) */
|
|
*pnsf = 0;
|
|
|
|
if (nsf) {
|
|
if (nsf->apu)
|
|
apu_destroy (nsf->apu);
|
|
|
|
nes_shutdown (nsf);
|
|
|
|
if (nsf->data)
|
|
free (nsf->data);
|
|
|
|
if (nsf->song_frames)
|
|
free (nsf->song_frames);
|
|
|
|
free (nsf);
|
|
}
|
|
}
|
|
|
|
int
|
|
nsf_setchan (nsf_t * nsf, int chan, boolean enabled)
|
|
{
|
|
if (!nsf)
|
|
return -1;
|
|
|
|
nsf_setcontext (nsf);
|
|
return apu_setchan (chan, enabled);
|
|
}
|
|
|
|
int
|
|
nsf_playtrack (nsf_t * nsf, int track, int sample_rate, int sample_bits,
|
|
boolean stereo)
|
|
{
|
|
if (!nsf) {
|
|
return -1;
|
|
}
|
|
|
|
/* 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) {
|
|
/* $$$ ben : from my point of view this is not clean. Function should
|
|
* never destroy object it has not created...
|
|
*/
|
|
/* nsf_free(&nsf); */
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
|
|
return nsf->current_song;
|
|
}
|
|
|
|
int
|
|
nsf_setfilter (nsf_t * nsf, int filter_type)
|
|
{
|
|
if (!nsf) {
|
|
return -1;
|
|
}
|
|
nsf_setcontext (nsf);
|
|
return apu_setfilter (filter_type);
|
|
}
|
|
|
|
/*
|
|
** $Log$
|
|
** Revision 1.6 2008/03/26 07:40:55 slomo
|
|
** * gst/nsf/Makefile.am:
|
|
** * gst/nsf/fds_snd.c:
|
|
** * gst/nsf/mmc5_snd.c:
|
|
** * gst/nsf/nsf.c:
|
|
** * gst/nsf/types.h:
|
|
** * gst/nsf/vrc7_snd.c:
|
|
** * gst/nsf/vrcvisnd.c:
|
|
** * gst/nsf/memguard.c:
|
|
** * gst/nsf/memguard.h:
|
|
** Remove memguard again and apply hopefully all previously dropped
|
|
** local patches. Should be really better than the old version now.
|
|
**
|
|
** Revision 1.5 2008-03-25 15:56:12 slomo
|
|
** Patch by: Andreas Henriksson <andreas at fatal dot set>
|
|
** * 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/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:
|
|
** Update our internal nosefart to nosefart-2.7-mls to fix segfaults
|
|
** on some files. Fixes bug #498237.
|
|
** Remove some // comments, fix some compiler warnings and use pow()
|
|
** instead of a slow, selfmade implementation.
|
|
**
|
|
** Revision 1.3 2003/05/01 22:34:20 benjihan
|
|
** New NSF plugin
|
|
**
|
|
** Revision 1.2 2003/04/09 14:50:32 ben
|
|
** Clean NSF api.
|
|
**
|
|
** Revision 1.1 2003/04/08 20:53:00 ben
|
|
** Adding more files...
|
|
**
|
|
** 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
|
|
**
|
|
*/
|