273 lines
7.5 KiB
C
273 lines
7.5 KiB
C
|
// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/
|
||
|
|
||
|
#include "nsf_emu.h"
|
||
|
|
||
|
#include "blargg_endian.h"
|
||
|
#include <string.h>
|
||
|
|
||
|
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
|
||
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||
|
General Public License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version. This
|
||
|
module 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 Lesser General Public License for more
|
||
|
details. You should have received a copy of the GNU Lesser General Public
|
||
|
License along with this module; if not, write to the Free Software Foundation,
|
||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||
|
|
||
|
#include "blargg_source.h"
|
||
|
|
||
|
void Info_init( struct Nsfe_Info* this )
|
||
|
{
|
||
|
this->playlist_disabled = false;
|
||
|
}
|
||
|
|
||
|
void Info_unload( struct Nsfe_Info* this )
|
||
|
{
|
||
|
memset(this->playlist, 0, 256);
|
||
|
memset(this->track_times, 0, 256 * sizeof(int32_t));
|
||
|
|
||
|
this->playlist_size = 0;
|
||
|
this->track_times_size = 0;
|
||
|
}
|
||
|
|
||
|
// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ?
|
||
|
void Info_disable_playlist( struct Nsfe_Info* this, bool b )
|
||
|
{
|
||
|
this->playlist_disabled = b;
|
||
|
this->track_count = this->playlist_size;
|
||
|
if ( !this->track_count || this->playlist_disabled )
|
||
|
this->track_count = this->actual_track_count_;
|
||
|
}
|
||
|
|
||
|
int Info_remap_track( struct Nsfe_Info* this, int track )
|
||
|
{
|
||
|
if ( !this->playlist_disabled && (unsigned) track < (unsigned) this->playlist_size )
|
||
|
track = this->playlist [track];
|
||
|
return track;
|
||
|
}
|
||
|
|
||
|
const char eof_error [] = "Unexpected end of file";
|
||
|
|
||
|
// Read n bytes from memory buffer
|
||
|
static blargg_err_t in_read( void* dst, long bytes, void* data, long* offset, long size )
|
||
|
{
|
||
|
if ((*offset + bytes) > size) return eof_error;
|
||
|
|
||
|
memcpy(dst, (char*) data + *offset, bytes);
|
||
|
*offset += bytes;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static blargg_err_t in_skip( long bytes, long *offset, long size )
|
||
|
{
|
||
|
if ((*offset + bytes) > size) return eof_error;
|
||
|
|
||
|
*offset += bytes;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Skip n bytes from memory buffer
|
||
|
|
||
|
// Read multiple strings and separate into individual strings
|
||
|
static int read_strs( void* data, long bytes, long* offset, long size,
|
||
|
const char* strs [4] )
|
||
|
{
|
||
|
char* chars = (char*) data + *offset;
|
||
|
chars [bytes - 1] = 0; // in case last string doesn't have terminator
|
||
|
|
||
|
if ( in_skip( bytes, offset, size) )
|
||
|
return -1;
|
||
|
|
||
|
int count = 0, i;
|
||
|
for ( i = 0; i < bytes; i++ )
|
||
|
{
|
||
|
strs [count] = &chars [i];
|
||
|
while ( i < bytes && chars [i] )
|
||
|
i++;
|
||
|
|
||
|
count++;
|
||
|
if (count >= 4)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
struct nsfe_info_t
|
||
|
{
|
||
|
byte load_addr [2];
|
||
|
byte init_addr [2];
|
||
|
byte play_addr [2];
|
||
|
byte speed_flags;
|
||
|
byte chip_flags;
|
||
|
byte track_count;
|
||
|
byte first_track;
|
||
|
byte unused [6];
|
||
|
};
|
||
|
|
||
|
blargg_err_t Info_load( struct Nsfe_Info* this, void* data, long size, struct Nsf_Emu* nsf_emu )
|
||
|
{
|
||
|
long offset = 0;
|
||
|
int const nsfe_info_size = 16;
|
||
|
assert( offsetof (struct nsfe_info_t,unused [6]) == nsfe_info_size );
|
||
|
|
||
|
// check header
|
||
|
byte signature [4];
|
||
|
blargg_err_t err = in_read( signature, sizeof signature, data, &offset, size );
|
||
|
if ( err )
|
||
|
return (err == eof_error ? gme_wrong_file_type : err);
|
||
|
if ( memcmp( signature, "NSFE", 4 ) ) {
|
||
|
}
|
||
|
|
||
|
// free previous info
|
||
|
/* TODO: clear track_names */
|
||
|
memset(this->playlist, 0, 256);
|
||
|
memset(this->track_times, 0, 256 * sizeof(int32_t));
|
||
|
|
||
|
this->playlist_size = 0;
|
||
|
this->track_times_size = 0;
|
||
|
|
||
|
// default nsf header
|
||
|
static const struct header_t base_header =
|
||
|
{
|
||
|
{'N','E','S','M','\x1A'},// tag
|
||
|
1, // version
|
||
|
1, 1, // track count, first track
|
||
|
{0,0},{0,0},{0,0}, // addresses
|
||
|
"","","", // strings
|
||
|
{0x1A, 0x41}, // NTSC rate
|
||
|
{0,0,0,0,0,0,0,0}, // banks
|
||
|
{0x20, 0x4E}, // PAL rate
|
||
|
0, 0, // flags
|
||
|
{0,0,0,0} // unused
|
||
|
};
|
||
|
|
||
|
memcpy( &nsf_emu->header, &base_header, sizeof base_header );
|
||
|
|
||
|
// parse tags
|
||
|
int phase = 0;
|
||
|
while ( phase != 3 )
|
||
|
{
|
||
|
// read size and tag
|
||
|
byte block_header [2] [4];
|
||
|
RETURN_ERR( in_read( block_header, sizeof block_header, data, &offset, size ) );
|
||
|
|
||
|
blargg_long chunk_size = get_le32( block_header [0] );
|
||
|
blargg_long tag = get_le32( block_header [1] );
|
||
|
|
||
|
switch ( tag )
|
||
|
{
|
||
|
case BLARGG_4CHAR('O','F','N','I'): {
|
||
|
check( phase == 0 );
|
||
|
if ( chunk_size < 8 )
|
||
|
return "Corrupt file";
|
||
|
|
||
|
struct nsfe_info_t finfo;
|
||
|
finfo.track_count = 1;
|
||
|
finfo.first_track = 0;
|
||
|
|
||
|
RETURN_ERR( in_read( &finfo, min( chunk_size, (blargg_long) nsfe_info_size ),
|
||
|
(char*) data, &offset, size ) );
|
||
|
|
||
|
if ( chunk_size > nsfe_info_size )
|
||
|
RETURN_ERR( in_skip( chunk_size - nsfe_info_size, &offset, size ) );
|
||
|
|
||
|
phase = 1;
|
||
|
nsf_emu->header.speed_flags = finfo.speed_flags;
|
||
|
nsf_emu->header.chip_flags = finfo.chip_flags;
|
||
|
nsf_emu->header.track_count = finfo.track_count;
|
||
|
this->actual_track_count_ = finfo.track_count;
|
||
|
nsf_emu->header.first_track = finfo.first_track;
|
||
|
memcpy( nsf_emu->header.load_addr, finfo.load_addr, 2 * 3 );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case BLARGG_4CHAR('K','N','A','B'):
|
||
|
if ( chunk_size > (int) sizeof nsf_emu->header.banks )
|
||
|
return "Corrupt file";
|
||
|
RETURN_ERR( in_read( nsf_emu->header.banks, chunk_size, data, &offset, size ) );
|
||
|
break;
|
||
|
|
||
|
case BLARGG_4CHAR('h','t','u','a'): {
|
||
|
const char* strs [4];
|
||
|
int n = read_strs( data, chunk_size, &offset, size, strs );
|
||
|
if ( n < 0 )
|
||
|
return eof_error;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case BLARGG_4CHAR('e','m','i','t'):
|
||
|
this->track_times_size = chunk_size / 4;
|
||
|
RETURN_ERR( in_read( this->track_times, this->track_times_size * 4, data, &offset, size ) );
|
||
|
break;
|
||
|
|
||
|
case BLARGG_4CHAR('l','b','l','t'):
|
||
|
RETURN_ERR( in_skip( chunk_size, &offset, size ) );
|
||
|
break;
|
||
|
|
||
|
case BLARGG_4CHAR('t','s','l','p'):
|
||
|
this->playlist_size = chunk_size;
|
||
|
RETURN_ERR( in_read( &this->playlist [0], chunk_size, data, &offset, size ) );
|
||
|
break;
|
||
|
|
||
|
case BLARGG_4CHAR('A','T','A','D'): {
|
||
|
check( phase == 1 );
|
||
|
phase = 2;
|
||
|
if ( !nsf_emu )
|
||
|
{
|
||
|
RETURN_ERR( in_skip( chunk_size, &offset, size ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Avoid unexpected end of file
|
||
|
if ( (offset + chunk_size) > size )
|
||
|
return eof_error;
|
||
|
|
||
|
RETURN_ERR( Rom_load( &nsf_emu->rom, (char*) data + offset, chunk_size, 0, 0, 0 ) );
|
||
|
RETURN_ERR( Nsf_post_load( nsf_emu ) );
|
||
|
offset += chunk_size;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case BLARGG_4CHAR('D','N','E','N'):
|
||
|
check( phase == 2 );
|
||
|
phase = 3;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// tags that can be skipped start with a lowercase character
|
||
|
check( islower( (tag >> 24) & 0xFF ) );
|
||
|
RETURN_ERR( in_skip( chunk_size, &offset, size ) );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
long Track_length( struct Nsf_Emu* this, int n )
|
||
|
{
|
||
|
long length = 0;
|
||
|
if ( (this->m3u.size > 0) && (n < this->m3u.size) ) {
|
||
|
struct entry_t* entry = &this->m3u.entries [n];
|
||
|
length = entry->length;
|
||
|
}
|
||
|
else if ( (this->info.playlist_size > 0) && (n < this->info.playlist_size) ) {
|
||
|
int remapped = Info_remap_track( &this->info, n );
|
||
|
if ( (unsigned) remapped < (unsigned) this->info.track_times_size )
|
||
|
length = (int32_t) get_le32( &this->info.track_times [remapped] );
|
||
|
}
|
||
|
else if( (unsigned) n < (unsigned) this->info.track_times_size )
|
||
|
length = (int32_t) get_le32( &this->info.track_times [n] );
|
||
|
|
||
|
/* Length will be 2,30 minutes for one track songs,
|
||
|
and 1,45 minutes for multitrack songs */
|
||
|
if ( length <= 0 )
|
||
|
length = (this->track_count > 1 ? 105 : 150) * 1000;
|
||
|
|
||
|
return length;
|
||
|
}
|