1 // Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
2
3 #include "Nsfe_Emu.h"
4
5 #include "blargg_endian.h"
6 #include <string.h>
7 #include <ctype.h>
8
9 /* Copyright (C) 2005-2006 Shay Green. This module is free software; you
10 can redistribute it and/or modify it under the terms of the GNU Lesser
11 General Public License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version. This
13 module is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16 details. You should have received a copy of the GNU Lesser General Public
17 License along with this module; if not, write to the Free Software Foundation,
18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
19
20 #include "blargg_source.h"
21
Nsfe_Info()22 Nsfe_Info::Nsfe_Info() { playlist_disabled = false; }
23
~Nsfe_Info()24 Nsfe_Info::~Nsfe_Info() { }
25
unload()26 inline void Nsfe_Info::unload()
27 {
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,long size,blargg_vector<char> & chars,blargg_vector<const char * > & strs)51 static blargg_err_t read_strs( Data_Reader& in, long 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,Nsf_Emu * nsf_emu)92 blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_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 (err == in.eof_error ? gme_wrong_file_type : err);
102 if ( memcmp( signature, "NSFE", 4 ) )
103 return gme_wrong_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 blargg_long size = get_le32( block_header [0] );
136 blargg_long tag = get_le32( block_header [1] );
137
138 //debug_printf( "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 "Corrupt file";
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, (blargg_long) 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 "Corrupt file";
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( in.skip( size ) );
211 }
212 else
213 {
214 Subset_Reader sub( &in, size ); // limit emu to nsf data
215 Remaining_Reader rem( &header, Nsf_Emu::header_size, &sub );
216 RETURN_ERR( nsf_emu->load( rem ) );
217 check( rem.remain() == 0 );
218 }
219 break;
220 }
221
222 case BLARGG_4CHAR('D','N','E','N'):
223 check( phase == 2 );
224 phase = 3;
225 break;
226
227 default:
228 // tags that can be skipped start with a lowercase character
229 check( islower( (tag >> 24) & 0xFF ) );
230 RETURN_ERR( in.skip( size ) );
231 break;
232 }
233 }
234
235 return 0;
236 }
237
track_info_(track_info_t * out,int track) const238 blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const
239 {
240 int remapped = remap_track( track );
241 if ( (unsigned) remapped < track_times.size() )
242 {
243 long length = (BOOST::int32_t) get_le32( track_times [remapped] );
244 if ( length > 0 )
245 out->length = length;
246 }
247 if ( (unsigned) remapped < track_names.size() )
248 Gme_File::copy_field_( out->song, track_names [remapped] );
249
250 GME_COPY_FIELD( info, out, game );
251 GME_COPY_FIELD( info, out, author );
252 GME_COPY_FIELD( info, out, copyright );
253 GME_COPY_FIELD( info, out, dumper );
254 return 0;
255 }
256
Nsfe_Emu()257 Nsfe_Emu::Nsfe_Emu()
258 {
259 loading = false;
260 set_type( gme_nsfe_type );
261 }
262
~Nsfe_Emu()263 Nsfe_Emu::~Nsfe_Emu() { }
264
unload()265 void Nsfe_Emu::unload()
266 {
267 if ( !loading )
268 info.unload(); // TODO: extremely hacky!
269 Nsf_Emu::unload();
270 }
271
track_info_(track_info_t * out,int track) const272 blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const
273 {
274 return info.track_info_( out, track );
275 }
276
277 struct Nsfe_File : Gme_Info_
278 {
279 Nsfe_Info info;
280
Nsfe_FileNsfe_File281 Nsfe_File() { set_type( gme_nsfe_type ); }
282
load_Nsfe_File283 blargg_err_t load_( Data_Reader& in )
284 {
285 RETURN_ERR( info.load( in, 0 ) );
286 info.disable_playlist( false );
287 set_track_count( info.info.track_count );
288 return 0;
289 }
290
track_info_Nsfe_File291 blargg_err_t track_info_( track_info_t* out, int track ) const
292 {
293 return info.track_info_( out, track );
294 }
295 };
296
new_nsfe_emu()297 static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; }
new_nsfe_file()298 static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; }
299
300 static gme_type_t_ const gme_nsfe_type_ = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 };
301 gme_type_t const gme_nsfe_type = &gme_nsfe_type_;
302
303
load_(Data_Reader & in)304 blargg_err_t Nsfe_Emu::load_( Data_Reader& in )
305 {
306 if ( loading )
307 return Nsf_Emu::load_( in );
308
309 // TODO: this hacky recursion-avoidance could have subtle problems
310 loading = true;
311 blargg_err_t err = info.load( in, this );
312 loading = false;
313 disable_playlist( false );
314 return err;
315 }
316
disable_playlist(bool b)317 void Nsfe_Emu::disable_playlist( bool b )
318 {
319 info.disable_playlist( b );
320 set_track_count( info.info.track_count );
321 }
322
clear_playlist_()323 void Nsfe_Emu::clear_playlist_()
324 {
325 disable_playlist();
326 Nsf_Emu::clear_playlist_();
327 }
328
start_track_(int track)329 blargg_err_t Nsfe_Emu::start_track_( int track )
330 {
331 return Nsf_Emu::start_track_( info.remap_track( track ) );
332 }
333