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