cog/Frameworks/GME/gme/Nsfe_Emu.cpp

340 lines
8.6 KiB
C++

// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Nsfe_Emu.h"
#include "blargg_endian.h"
#include <string.h>
#include <ctype.h>
#include <algorithm>
/* 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"
using std::min;
using std::max;
Nsfe_Info::Nsfe_Info() { playlist_disabled = false; }
Nsfe_Info::~Nsfe_Info() { }
inline void Nsfe_Info::unload()
{
track_name_data.clear();
track_names.clear();
playlist.clear();
track_times.clear();
}
// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ?
void Nsfe_Info::disable_playlist( bool b )
{
playlist_disabled = b;
info.track_count = playlist.size();
if ( !info.track_count || playlist_disabled )
info.track_count = actual_track_count_;
}
int Nsfe_Info::remap_track( int track ) const
{
if ( !playlist_disabled && (unsigned) track < playlist.size() )
track = playlist [track];
return track;
}
// Read multiple strings and separate into individual strings
static blargg_err_t read_strs( Data_Reader& in, long size, blargg_vector<char>& chars,
blargg_vector<const char*>& strs )
{
RETURN_ERR( chars.resize( size + 1 ) );
chars [size] = 0; // in case last string doesn't have terminator
RETURN_ERR( in.read( &chars [0], size ) );
RETURN_ERR( strs.resize( 128 ) );
int count = 0;
for ( int i = 0; i < size; i++ )
{
if ( (int) strs.size() <= count )
RETURN_ERR( strs.resize( count * 2 ) );
strs [count++] = &chars [i];
while ( i < size && chars [i] )
i++;
}
return strs.resize( count );
}
// Copy in to out, where out has out_max characters allocated. Truncate to
// out_max - 1 characters.
static void copy_str( const char* in, char* out, int out_max )
{
out [out_max - 1] = 0;
strncpy( out, in, out_max - 1 );
}
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 Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu )
{
int const nsfe_info_size = 16;
assert( offsetof (nsfe_info_t,unused [6]) == nsfe_info_size );
// check header
byte signature [4];
blargg_err_t err = in.read( signature, sizeof signature );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
if ( memcmp( signature, "NSFE", 4 ) )
return gme_wrong_file_type;
// free previous info
track_name_data.clear();
track_names.clear();
playlist.clear();
track_times.clear();
// default nsf header
static const Nsf_Emu::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
};
Nsf_Emu::header_t& header = info;
header = 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 ) );
blargg_long size = get_le32( block_header [0] );
blargg_long tag = get_le32( block_header [1] );
if ( size < 0 )
return "Corrupt file";
//debug_printf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) );
switch ( tag )
{
case BLARGG_4CHAR('O','F','N','I'): {
check( phase == 0 );
if ( size < 8 )
return "Corrupt file";
nsfe_info_t finfo;
finfo.track_count = 1;
finfo.first_track = 0;
RETURN_ERR( in.read( &finfo, min( size, (blargg_long) nsfe_info_size ) ) );
if ( size > nsfe_info_size )
RETURN_ERR( in.skip( size - nsfe_info_size ) );
phase = 1;
info.speed_flags = finfo.speed_flags;
info.chip_flags = finfo.chip_flags;
info.track_count = finfo.track_count;
this->actual_track_count_ = finfo.track_count;
info.first_track = finfo.first_track;
memcpy( info.load_addr, finfo.load_addr, 2 * 3 );
break;
}
case BLARGG_4CHAR('K','N','A','B'):
if ( size > (int) sizeof info.banks )
return "Corrupt file";
RETURN_ERR( in.read( info.banks, size ) );
break;
case BLARGG_4CHAR('h','t','u','a'): {
blargg_vector<char> chars;
blargg_vector<const char*> strs;
RETURN_ERR( read_strs( in, size, chars, strs ) );
int n = strs.size();
if ( n > 3 )
copy_str( strs [3], info.dumper, sizeof info.dumper );
if ( n > 2 )
copy_str( strs [2], info.copyright, sizeof info.copyright );
if ( n > 1 )
copy_str( strs [1], info.author, sizeof info.author );
if ( n > 0 )
copy_str( strs [0], info.game, sizeof info.game );
break;
}
case BLARGG_4CHAR('e','m','i','t'):
RETURN_ERR( track_times.resize( size / 4 ) );
RETURN_ERR( in.read( track_times.begin(), track_times.size() * 4 ) );
break;
case BLARGG_4CHAR('l','b','l','t'):
RETURN_ERR( read_strs( in, size, track_name_data, track_names ) );
break;
case BLARGG_4CHAR('t','s','l','p'):
RETURN_ERR( playlist.resize( size ) );
RETURN_ERR( in.read( &playlist [0], size ) );
break;
case BLARGG_4CHAR('A','T','A','D'): {
check( phase == 1 );
phase = 2;
if ( !nsf_emu )
{
RETURN_ERR( in.skip( size ) );
}
else
{
Subset_Reader sub( &in, size ); // limit emu to nsf data
Remaining_Reader rem( &header, Nsf_Emu::header_size, &sub );
RETURN_ERR( nsf_emu->load( rem ) );
check( rem.remain() == 0 );
}
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( size ) );
break;
}
}
return 0;
}
blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const
{
int remapped = remap_track( track );
if ( (unsigned) remapped < track_times.size() )
{
long length = (int32_t) get_le32( track_times [remapped] );
if ( length > 0 )
out->length = length;
}
if ( (unsigned) remapped < track_names.size() )
Gme_File::copy_field_( out->song, track_names [remapped] );
GME_COPY_FIELD( info, out, game );
GME_COPY_FIELD( info, out, author );
GME_COPY_FIELD( info, out, copyright );
GME_COPY_FIELD( info, out, dumper );
return 0;
}
Nsfe_Emu::Nsfe_Emu()
{
loading = false;
set_type( gme_nsfe_type );
}
Nsfe_Emu::~Nsfe_Emu() { }
void Nsfe_Emu::unload()
{
if ( !loading )
info.unload(); // TODO: extremely hacky!
Nsf_Emu::unload();
}
blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const
{
return info.track_info_( out, track );
}
struct Nsfe_File : Gme_Info_
{
Nsfe_Info info;
Nsfe_File() { set_type( gme_nsfe_type ); }
blargg_err_t load_( Data_Reader& in )
{
RETURN_ERR( info.load( in, 0 ) );
info.disable_playlist( false );
set_track_count( info.info.track_count );
return 0;
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
return info.track_info_( out, track );
}
};
static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; }
static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; }
static gme_type_t_ const gme_nsfe_type_ = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 };
extern gme_type_t const gme_nsfe_type = &gme_nsfe_type_;
blargg_err_t Nsfe_Emu::load_( Data_Reader& in )
{
if ( loading )
return Nsf_Emu::load_( in );
// TODO: this hacky recursion-avoidance could have subtle problems
loading = true;
blargg_err_t err = info.load( in, this );
loading = false;
disable_playlist( false );
return err;
}
void Nsfe_Emu::disable_playlist( bool b )
{
info.disable_playlist( b );
set_track_count( info.info.track_count );
}
void Nsfe_Emu::clear_playlist_()
{
disable_playlist();
Nsf_Emu::clear_playlist_();
}
blargg_err_t Nsfe_Emu::start_track_( int track )
{
return Nsf_Emu::start_track_( info.remap_track( track ) );
}