Coverage Report

Created: 2026-09-01 07:42

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