1 // Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
2 
3 #include "Hes_Emu.h"
4 
5 #include "blargg_endian.h"
6 #include <string.h>
7 
8 /* Copyright (C) 2006 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 
21 int const timer_mask  = 0x04;
22 int const vdp_mask    = 0x02;
23 int const i_flag_mask = 0x04;
24 int const unmapped    = 0xFF;
25 
26 long const period_60hz = 262 * 455L; // scanlines * clocks per scanline
27 
Hes_Emu()28 Hes_Emu::Hes_Emu()
29 {
30 	timer.raw_load = 0;
31 	set_type( gme_hes_type );
32 
33 	static const char* const names [Hes_Apu::osc_count] = {
34 		"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2"
35 	};
36 	set_voice_names( names );
37 
38 	static int const types [Hes_Apu::osc_count] = {
39 		wave_type  | 0, wave_type  | 1, wave_type | 2, wave_type | 3,
40 		mixed_type | 0, mixed_type | 1
41 	};
42 	set_voice_types( types );
43 	set_silence_lookahead( 6 );
44 	set_gain( 1.11 );
45 }
46 
~Hes_Emu()47 Hes_Emu::~Hes_Emu() { }
48 
unload()49 void Hes_Emu::unload()
50 {
51 	rom.clear();
52 	Music_Emu::unload();
53 }
54 
55 // Track info
56 
copy_field(byte const * in,char * out)57 static byte const* copy_field( byte const* in, char* out )
58 {
59 	if ( in )
60 	{
61 		int len = 0x20;
62 		if ( in [0x1F] && !in [0x2F] )
63 			len = 0x30; // fields are sometimes 16 bytes longer (ugh)
64 
65 		// since text fields are where any data could be, detect non-text
66 		// and fields with data after zero byte terminator
67 
68 		int i = 0;
69 		for ( i = 0; i < len && in [i]; i++ )
70 			if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text
71 				return 0; // non-ASCII found
72 
73 		for ( ; i < len; i++ )
74 			if ( in [i] )
75 				return 0; // data after terminator
76 
77 		Gme_File::copy_field_( out, (char const*) in, len );
78 		in += len;
79 	}
80 	return in;
81 }
82 
copy_hes_fields(byte const * in,track_info_t * out)83 static void copy_hes_fields( byte const* in, track_info_t* out )
84 {
85 	if ( *in >= ' ' )
86 	{
87 		in = copy_field( in, out->game );
88 		in = copy_field( in, out->author );
89 		in = copy_field( in, out->copyright );
90 	}
91 }
92 
track_info_(track_info_t * out,int) const93 blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const
94 {
95 	copy_hes_fields( rom.begin() + 0x20, out );
96 	return 0;
97 }
98 
check_hes_header(void const * header)99 static blargg_err_t check_hes_header( void const* header )
100 {
101 	if ( memcmp( header, "HESM", 4 ) )
102 		return gme_wrong_file_type;
103 	return 0;
104 }
105 
106 struct Hes_File : Gme_Info_
107 {
108 	struct header_t {
109 		char header [Hes_Emu::header_size];
110 		char unused [0x20];
111 		byte fields [0x30 * 3];
112 	} h;
113 
Hes_FileHes_File114 	Hes_File() { set_type( gme_hes_type ); }
115 
load_Hes_File116 	blargg_err_t load_( Data_Reader& in )
117 	{
118 		assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20 );
119 		blargg_err_t err = in.read( &h, sizeof h );
120 		if ( err )
121 			return (err == in.eof_error ? gme_wrong_file_type : err);
122 		return check_hes_header( &h );
123 	}
124 
track_info_Hes_File125 	blargg_err_t track_info_( track_info_t* out, int ) const
126 	{
127 		copy_hes_fields( h.fields, out );
128 		return 0;
129 	}
130 };
131 
new_hes_emu()132 static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; }
new_hes_file()133 static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; }
134 
135 static gme_type_t_ const gme_hes_type_ = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 };
136 gme_type_t const gme_hes_type = &gme_hes_type_;
137 
138 
139 // Setup
140 
load_(Data_Reader & in)141 blargg_err_t Hes_Emu::load_( Data_Reader& in )
142 {
143 	assert( offsetof (header_t,unused [4]) == header_size );
144 	RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) );
145 
146 	RETURN_ERR( check_hes_header( header_.tag ) );
147 
148 	if ( header_.vers != 0 )
149 		set_warning( "Unknown file version" );
150 
151 	if ( memcmp( header_.data_tag, "DATA", 4 ) )
152 		set_warning( "Data header missing" );
153 
154 	if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
155 		set_warning( "Unknown header data" );
156 
157 	// File spec supports multiple blocks, but I haven't found any, and
158 	// many files have bad sizes in the only block, so it's simpler to
159 	// just try to load the damn data as best as possible.
160 
161 	long addr = get_le32( header_.addr );
162 	long size = get_le32( header_.size );
163 	long const rom_max = 0x100000;
164 	if ( addr & ~(rom_max - 1) )
165 	{
166 		set_warning( "Invalid address" );
167 		addr &= rom_max - 1;
168 	}
169 	if ( (unsigned long) (addr + size) > (unsigned long) rom_max )
170 		set_warning( "Invalid size" );
171 
172 	if ( size != rom.file_size() )
173 	{
174 		if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
175 			set_warning( "Multiple DATA not supported" );
176 		else if ( size < rom.file_size() )
177 			set_warning( "Extra file data" );
178 		else
179 			set_warning( "Missing file data" );
180 	}
181 
182 	rom.set_addr( addr );
183 
184 	set_voice_count( apu.osc_count );
185 
186 	apu.volume( gain() );
187 
188 	return setup_buffer( 7159091 );
189 }
190 
update_eq(blip_eq_t const & eq)191 void Hes_Emu::update_eq( blip_eq_t const& eq )
192 {
193 	apu.treble_eq( eq );
194 }
195 
set_voice(int i,Blip_Buffer * center,Blip_Buffer * left,Blip_Buffer * right)196 void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
197 {
198 	apu.osc_output( i, center, left, right );
199 }
200 
201 // Emulation
202 
recalc_timer_load()203 void Hes_Emu::recalc_timer_load()
204 {
205 	timer.load = timer.raw_load * timer_base + 1;
206 }
207 
set_tempo_(double t)208 void Hes_Emu::set_tempo_( double t )
209 {
210 	play_period = hes_time_t (period_60hz / t);
211 	timer_base = int (1024 / t);
212 	recalc_timer_load();
213 }
214 
start_track_(int track)215 blargg_err_t Hes_Emu::start_track_( int track )
216 {
217 	RETURN_ERR( Classic_Emu::start_track_( track ) );
218 
219 	memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
220 	memset( sgx, 0, sizeof sgx );
221 
222 	apu.reset();
223 	cpu::reset();
224 
225 	for ( unsigned i = 0; i < sizeof header_.banks; i++ )
226 		set_mmr( i, header_.banks [i] );
227 	set_mmr( page_count, 0xFF ); // unmapped beyond end of address space
228 
229 	irq.disables  = timer_mask | vdp_mask;
230 	irq.timer     = future_hes_time;
231 	irq.vdp       = future_hes_time;
232 
233 	timer.enabled = false;
234 	timer.raw_load= 0x80;
235 	timer.count   = timer.load;
236 	timer.fired   = false;
237 	timer.last_time = 0;
238 
239 	vdp.latch     = 0;
240 	vdp.control   = 0;
241 	vdp.next_vbl  = 0;
242 
243 	ram [0x1FF] = (idle_addr - 1) >> 8;
244 	ram [0x1FE] = (idle_addr - 1) & 0xFF;
245 	r.sp = 0xFD;
246 	r.pc = get_le16( header_.init_addr );
247 	r.a  = track;
248 
249 	recalc_timer_load();
250 	last_frame_hook = 0;
251 
252 	return 0;
253 }
254 
255 // Hardware
256 
cpu_write_vdp(int addr,int data)257 void Hes_Emu::cpu_write_vdp( int addr, int data )
258 {
259 	switch ( addr )
260 	{
261 	case 0:
262 		vdp.latch = data & 0x1F;
263 		break;
264 
265 	case 2:
266 		if ( vdp.latch == 5 )
267 		{
268 			if ( data & 0x04 )
269 				set_warning( "Scanline interrupt unsupported" );
270 			run_until( time() );
271 			vdp.control = data;
272 			irq_changed();
273 		}
274 		else
275 		{
276 			debug_printf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
277 		}
278 		break;
279 
280 	case 3:
281 		debug_printf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
282 		break;
283 	}
284 }
285 
cpu_write_(hes_addr_t addr,int data)286 void Hes_Emu::cpu_write_( hes_addr_t addr, int data )
287 {
288 	if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr )
289 	{
290 		GME_APU_HOOK( this, addr - apu.start_addr, data );
291 		// avoid going way past end when a long block xfer is writing to I/O space
292 		hes_time_t t = min( time(), end_time() + 8 );
293 		apu.write_data( t, addr, data );
294 		return;
295 	}
296 
297 	hes_time_t time = this->time();
298 	switch ( addr )
299 	{
300 	case 0x0000:
301 	case 0x0002:
302 	case 0x0003:
303 		cpu_write_vdp( addr, data );
304 		return;
305 
306 	case 0x0C00: {
307 		run_until( time );
308 		timer.raw_load = (data & 0x7F) + 1;
309 		recalc_timer_load();
310 		timer.count = timer.load;
311 		break;
312 	}
313 
314 	case 0x0C01:
315 		data &= 1;
316 		if ( timer.enabled == data )
317 			return;
318 		run_until( time );
319 		timer.enabled = data;
320 		if ( data )
321 			timer.count = timer.load;
322 		break;
323 
324 	case 0x1402:
325 		run_until( time );
326 		irq.disables = data;
327 		if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
328 			debug_printf( "Int mask: $%02X\n", data );
329 		break;
330 
331 	case 0x1403:
332 		run_until( time );
333 		if ( timer.enabled )
334 			timer.count = timer.load;
335 		timer.fired = false;
336 		break;
337 
338 #ifndef NDEBUG
339 	case 0x1000: // I/O port
340 	case 0x0402: // palette
341 	case 0x0403:
342 	case 0x0404:
343 	case 0x0405:
344 		return;
345 
346 	default:
347 		debug_printf( "unmapped write $%04X <- $%02X\n", addr, data );
348 		return;
349 #endif
350 	}
351 
352 	irq_changed();
353 }
354 
cpu_read_(hes_addr_t addr)355 int Hes_Emu::cpu_read_( hes_addr_t addr )
356 {
357 	hes_time_t time = this->time();
358 	addr &= page_size - 1;
359 	switch ( addr )
360 	{
361 	case 0x0000:
362 		if ( irq.vdp > time )
363 			return 0;
364 		irq.vdp = future_hes_time;
365 		run_until( time );
366 		irq_changed();
367 		return 0x20;
368 
369 	case 0x0002:
370 	case 0x0003:
371 		debug_printf( "VDP read not supported: %d\n", addr );
372 		return 0;
373 
374 	case 0x0C01:
375 		//return timer.enabled; // TODO: remove?
376 	case 0x0C00:
377 		run_until( time );
378 		debug_printf( "Timer count read\n" );
379 		return (unsigned) (timer.count - 1) / timer_base;
380 
381 	case 0x1402:
382 		return irq.disables;
383 
384 	case 0x1403:
385 		{
386 			int status = 0;
387 			if ( irq.timer <= time ) status |= timer_mask;
388 			if ( irq.vdp   <= time ) status |= vdp_mask;
389 			return status;
390 		}
391 
392 	#ifndef NDEBUG
393 		case 0x1000: // I/O port
394 		case 0x180C: // CD-ROM
395 		case 0x180D:
396 			break;
397 
398 		default:
399 			debug_printf( "unmapped read  $%04X\n", addr );
400 	#endif
401 	}
402 
403 	return unmapped;
404 }
405 
406 // see hes_cpu_io.h for core read/write functions
407 
408 // Emulation
409 
run_until(hes_time_t present)410 void Hes_Emu::run_until( hes_time_t present )
411 {
412 	while ( vdp.next_vbl < present )
413 		vdp.next_vbl += play_period;
414 
415 	hes_time_t elapsed = present - timer.last_time;
416 	if ( elapsed > 0 )
417 	{
418 		if ( timer.enabled )
419 		{
420 			timer.count -= elapsed;
421 			if ( timer.count <= 0 )
422 				timer.count += timer.load;
423 		}
424 		timer.last_time = present;
425 	}
426 }
427 
irq_changed()428 void Hes_Emu::irq_changed()
429 {
430 	hes_time_t present = time();
431 
432 	if ( irq.timer > present )
433 	{
434 		irq.timer = future_hes_time;
435 		if ( timer.enabled && !timer.fired )
436 			irq.timer = present + timer.count;
437 	}
438 
439 	if ( irq.vdp > present )
440 	{
441 		irq.vdp = future_hes_time;
442 		if ( vdp.control & 0x08 )
443 			irq.vdp = vdp.next_vbl;
444 	}
445 
446 	hes_time_t time = future_hes_time;
447 	if ( !(irq.disables & timer_mask) ) time = irq.timer;
448 	if ( !(irq.disables &   vdp_mask) ) time = min( time, irq.vdp );
449 
450 	set_irq_time( time );
451 }
452 
cpu_done()453 int Hes_Emu::cpu_done()
454 {
455 	check( time() >= end_time() ||
456 			(!(r.status & i_flag_mask) && time() >= irq_time()) );
457 
458 	if ( !(r.status & i_flag_mask) )
459 	{
460 		hes_time_t present = time();
461 
462 		if ( irq.timer <= present && !(irq.disables & timer_mask) )
463 		{
464 			timer.fired = true;
465 			irq.timer = future_hes_time;
466 			irq_changed(); // overkill, but not worth writing custom code
467 			#if GME_FRAME_HOOK_DEFINED
468 			{
469 				unsigned const threshold = period_60hz / 30;
470 				unsigned long elapsed = present - last_frame_hook;
471 				if ( elapsed - period_60hz + threshold / 2 < threshold )
472 				{
473 					last_frame_hook = present;
474 					GME_FRAME_HOOK( this );
475 				}
476 			}
477 			#endif
478 			return 0x0A;
479 		}
480 
481 		if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
482 		{
483 			// work around for bugs with music not acknowledging VDP
484 			//run_until( present );
485 			//irq.vdp = future_hes_time;
486 			//irq_changed();
487 			#if GME_FRAME_HOOK_DEFINED
488 				last_frame_hook = present;
489 				GME_FRAME_HOOK( this );
490 			#endif
491 			return 0x08;
492 		}
493 	}
494 	return 0;
495 }
496 
adjust_time(blargg_long & time,hes_time_t delta)497 static void adjust_time( blargg_long& time, hes_time_t delta )
498 {
499 	if ( time < future_hes_time )
500 	{
501 		time -= delta;
502 		if ( time < 0 )
503 			time = 0;
504 	}
505 }
506 
run_clocks(blip_time_t & duration_,int)507 blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int )
508 {
509 	blip_time_t const duration = duration_; // cache
510 
511 	if ( cpu::run( duration ) )
512 		set_warning( "Emulation error (illegal instruction)" );
513 
514 	check( time() >= duration );
515 	//check( time() - duration < 20 ); // Txx instruction could cause going way over
516 
517 	run_until( duration );
518 
519 	// end time frame
520 	timer.last_time -= duration;
521 	vdp.next_vbl    -= duration;
522 	#if GME_FRAME_HOOK_DEFINED
523 		last_frame_hook -= duration;
524 	#endif
525 	cpu::end_frame( duration );
526 	::adjust_time( irq.timer, duration );
527 	::adjust_time( irq.vdp,   duration );
528 	apu.end_frame( duration );
529 
530 	return 0;
531 }
532