1 // Game_Music_Emu $vers. http://www.slack.net/~ant/
2
3 #include "Nsfe_Emu.h"
4
5 #include "blargg_endian.h"
6 #include <ctype.h>
7
8 /* Copyright (C) 2005-2009 Shay Green. This module is free software; you
9 can redistribute it and/or modify it under the terms of the GNU Lesser
10 General Public License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version. This
12 module is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 details. You should have received a copy of the GNU Lesser General Public
16 License along with this module; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
18
19 #include "blargg_source.h"
20
Nsfe_Info()21 Nsfe_Info::Nsfe_Info() { playlist_disabled = false; }
22
~Nsfe_Info()23 Nsfe_Info::~Nsfe_Info() { }
24
unload()25 inline void Nsfe_Info::unload()
26 {
27 data.clear();
28 track_name_data.clear();
29 track_names.clear();
30 playlist.clear();
31 track_times.clear();
32 }
33
34 // TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ?
disable_playlist(bool b)35 void Nsfe_Info::disable_playlist( bool b )
36 {
37 playlist_disabled = b;
38 info.track_count = playlist.size();
39 if ( !info.track_count || playlist_disabled )
40 info.track_count = actual_track_count_;
41 }
42
remap_track(int track) const43 int Nsfe_Info::remap_track( int track ) const
44 {
45 if ( !playlist_disabled && (unsigned) track < playlist.size() )
46 track = playlist [track];
47 return track;
48 }
49
50 // Read multiple strings and separate into individual strings
read_strs(Data_Reader & in,int size,blargg_vector<char> & chars,blargg_vector<const char * > & strs)51 static blargg_err_t read_strs( Data_Reader& in, int size, blargg_vector<char>& chars,
52 blargg_vector<const char*>& strs )
53 {
54 RETURN_ERR( chars.resize( size + 1 ) );
55 chars [size] = 0; // in case last string doesn't have terminator
56 RETURN_ERR( in.read( &chars [0], size ) );
57
58 RETURN_ERR( strs.resize( 128 ) );
59 int count = 0;
60 for ( int i = 0; i < size; i++ )
61 {
62 if ( (int) strs.size() <= count )
63 RETURN_ERR( strs.resize( count * 2 ) );
64 strs [count++] = &chars [i];
65 while ( i < size && chars [i] )
66 i++;
67 }
68
69 return strs.resize( count );
70 }
71
72 // Copy in to out, where out has out_max characters allocated. Truncate to
73 // out_max - 1 characters.
copy_str(const char in[],char out[],int out_max)74 static void copy_str( const char in [], char out [], int out_max )
75 {
76 out [out_max - 1] = 0;
77 strncpy( out, in, out_max - 1 );
78 }
79
80 struct nsfe_info_t
81 {
82 byte load_addr [2];
83 byte init_addr [2];
84 byte play_addr [2];
85 byte speed_flags;
86 byte chip_flags;
87 byte track_count;
88 byte first_track;
89 byte unused [6];
90 };
91
load(Data_Reader & in,Nsfe_Emu * nsf_emu)92 blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsfe_Emu* nsf_emu )
93 {
94 int const nsfe_info_size = 16;
95 assert( offsetof (nsfe_info_t,unused [6]) == nsfe_info_size );
96
97 // check header
98 byte signature [4];
99 blargg_err_t err = in.read( signature, sizeof signature );
100 if ( err )
101 return (blargg_is_err_type( err, blargg_err_file_eof ) ? blargg_err_file_type : err);
102 if ( memcmp( signature, "NSFE", 4 ) )
103 return blargg_err_file_type;
104
105 // free previous info
106 track_name_data.clear();
107 track_names.clear();
108 playlist.clear();
109 track_times.clear();
110
111 // default nsf header
112 static const Nsf_Emu::header_t base_header =
113 {
114 {'N','E','S','M','\x1A'},// tag
115 1, // version
116 1, 1, // track count, first track
117 {0,0},{0,0},{0,0}, // addresses
118 "","","", // strings
119 {0x1A, 0x41}, // NTSC rate
120 {0,0,0,0,0,0,0,0}, // banks
121 {0x20, 0x4E}, // PAL rate
122 0, 0, // flags
123 {0,0,0,0} // unused
124 };
125 Nsf_Emu::header_t& header = info;
126 header = base_header;
127
128 // parse tags
129 int phase = 0;
130 while ( phase != 3 )
131 {
132 // read size and tag
133 byte block_header [2] [4];
134 RETURN_ERR( in.read( block_header, sizeof block_header ) );
135 int size = get_le32( block_header [0] );
136 int tag = get_le32( block_header [1] );
137
138 //dprintf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) );
139
140 switch ( tag )
141 {
142 case BLARGG_4CHAR('O','F','N','I'): {
143 check( phase == 0 );
144 if ( size < 8 )
145 return blargg_err_file_corrupt;
146
147 nsfe_info_t finfo;
148 finfo.track_count = 1;
149 finfo.first_track = 0;
150
151 RETURN_ERR( in.read( &finfo, min( size, (int) nsfe_info_size ) ) );
152 if ( size > nsfe_info_size )
153 RETURN_ERR( in.skip( size - nsfe_info_size ) );
154 phase = 1;
155 info.speed_flags = finfo.speed_flags;
156 info.chip_flags = finfo.chip_flags;
157 info.track_count = finfo.track_count;
158 this->actual_track_count_ = finfo.track_count;
159 info.first_track = finfo.first_track;
160 memcpy( info.load_addr, finfo.load_addr, 2 * 3 );
161 break;
162 }
163
164 case BLARGG_4CHAR('K','N','A','B'):
165 if ( size > (int) sizeof info.banks )
166 return blargg_err_file_corrupt;
167 RETURN_ERR( in.read( info.banks, size ) );
168 break;
169
170 case BLARGG_4CHAR('h','t','u','a'): {
171 blargg_vector<char> chars;
172 blargg_vector<const char*> strs;
173 RETURN_ERR( read_strs( in, size, chars, strs ) );
174 int n = strs.size();
175
176 if ( n > 3 )
177 copy_str( strs [3], info.dumper, sizeof info.dumper );
178
179 if ( n > 2 )
180 copy_str( strs [2], info.copyright, sizeof info.copyright );
181
182 if ( n > 1 )
183 copy_str( strs [1], info.author, sizeof info.author );
184
185 if ( n > 0 )
186 copy_str( strs [0], info.game, sizeof info.game );
187
188 break;
189 }
190
191 case BLARGG_4CHAR('e','m','i','t'):
192 RETURN_ERR( track_times.resize( size / 4 ) );
193 RETURN_ERR( in.read( track_times.begin(), track_times.size() * 4 ) );
194 break;
195
196 case BLARGG_4CHAR('l','b','l','t'):
197 RETURN_ERR( read_strs( in, size, track_name_data, track_names ) );
198 break;
199
200 case BLARGG_4CHAR('t','s','l','p'):
201 RETURN_ERR( playlist.resize( size ) );
202 RETURN_ERR( in.read( &playlist [0], size ) );
203 break;
204
205 case BLARGG_4CHAR('A','T','A','D'): {
206 check( phase == 1 );
207 phase = 2;
208 if ( !nsf_emu )
209 {
210 RETURN_ERR( data.resize( size ) );
211 RETURN_ERR( in.read( data.begin(), size ) );
212 }
213 else
214 {
215 Subset_Reader sub( &in, size ); // limit emu to nsf data
216 Remaining_Reader rem( &header, header.size, &sub );
217 RETURN_ERR( nsf_emu->Nsf_Emu::load_( rem ) );
218 check( rem.remain() == 0 );
219 }
220 break;
221 }
222
223 case BLARGG_4CHAR('D','N','E','N'):
224 check( phase == 2 );
225 phase = 3;
226 break;
227
228 default:
229 // tags that can be skipped start with a lowercase character
230 check( islower( (tag >> 24) & 0xFF ) );
231 RETURN_ERR( in.skip( size ) );
232 break;
233 }
234 }
235
236 return blargg_ok;
237 }
238
track_info_(track_info_t * out,int track) const239 blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const
240 {
241 int remapped = remap_track( track );
242 if ( (unsigned) remapped < track_times.size() )
243 {
244 int length = (BOOST::int32_t) get_le32( track_times [remapped] );
245 if ( length > 0 )
246 out->length = length;
247 }
248 if ( (unsigned) remapped < track_names.size() )
249 Gme_File::copy_field_( out->song, track_names [remapped] );
250
251 GME_COPY_FIELD( info, out, game );
252 GME_COPY_FIELD( info, out, author );
253 GME_COPY_FIELD( info, out, copyright );
254 GME_COPY_FIELD( info, out, dumper );
255 return blargg_ok;
256 }
257
Nsfe_Emu()258 Nsfe_Emu::Nsfe_Emu()
259 {
260 set_type( gme_nsfe_type );
261 }
262
~Nsfe_Emu()263 Nsfe_Emu::~Nsfe_Emu() { }
264
unload()265 void Nsfe_Emu::unload()
266 {
267 info.unload();
268 Nsf_Emu::unload();
269 }
270
track_info_(track_info_t * out,int track) const271 blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const
272 {
273 return info.track_info_( out, track );
274 }
275
276 struct Nsfe_File : Gme_Info_
277 {
278 Nsfe_Info info;
279
Nsfe_FileNsfe_File280 Nsfe_File() { set_type( gme_nsfe_type ); }
281
load_Nsfe_File282 blargg_err_t load_( Data_Reader& in )
283 {
284 RETURN_ERR( info.load( in, 0 ) );
285 info.disable_playlist( false );
286 set_track_count( info.info.track_count );
287 return blargg_ok;
288 }
289
track_info_Nsfe_File290 blargg_err_t track_info_( track_info_t* out, int track ) const
291 {
292 return info.track_info_( out, track );
293 }
294
hash_Nsfe_File295 blargg_err_t hash_( Hash_Function& out ) const
296 {
297 hash_nsf_file( info.info, info.data.begin(), info.data.end() - info.data.begin(), out );
298 return blargg_ok;
299 }
300 };
301
new_nsfe_emu()302 static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; }
new_nsfe_file()303 static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; }
304
305 gme_type_t_ const gme_nsfe_type [1] = {{ "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 }};
306
load_(Data_Reader & in)307 blargg_err_t Nsfe_Emu::load_( Data_Reader& in )
308 {
309 RETURN_ERR( info.load( in, this ) );
310 disable_playlist_( false );
311 return blargg_ok;
312 }
313
disable_playlist_(bool b)314 void Nsfe_Emu::disable_playlist_( bool b )
315 {
316 info.disable_playlist( b );
317 set_track_count( info.info.track_count );
318 }
319
clear_playlist_()320 void Nsfe_Emu::clear_playlist_()
321 {
322 disable_playlist_( true );
323 Nsf_Emu::clear_playlist_();
324 }
325
start_track_(int track)326 blargg_err_t Nsfe_Emu::start_track_( int track )
327 {
328 return Nsf_Emu::start_track_( info.remap_track( track ) );
329 }
330