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