1 // Game_Music_Emu $vers. http://www.slack.net/~ant/
2
3 #include "Gbs_Core.h"
4
5 #include "blargg_endian.h"
6
7 /* Copyright (C) 2003-2009 Shay Green. This module is free software; you
8 can redistribute it and/or modify it under the terms of the GNU Lesser
9 General Public License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version. This
11 module is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 details. You should have received a copy of the GNU Lesser General Public
15 License along with this module; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
17
18 #include "blargg_source.h"
19
20 int const tempo_unit = 16;
21 int const idle_addr = 0xF00D;
22 int const bank_size = 0x4000;
23
Gbs_Core()24 Gbs_Core::Gbs_Core() : rom( bank_size )
25 {
26 tempo = tempo_unit;
27 assert( offsetof (header_t,copyright [32]) == header_t::size );
28 }
29
~Gbs_Core()30 Gbs_Core::~Gbs_Core() { }
31
unload()32 void Gbs_Core::unload()
33 {
34 header_.timer_mode = 0; // set_tempo() reads this
35 rom.clear();
36 Gme_Loader::unload();
37 }
38
valid_tag() const39 bool Gbs_Core::header_t::valid_tag() const
40 {
41 return 0 == memcmp( tag, "GBS", 3 );
42 }
43
load_(Data_Reader & in)44 blargg_err_t Gbs_Core::load_( Data_Reader& in )
45 {
46 RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
47
48 if ( !header_.valid_tag() )
49 return blargg_err_file_type;
50
51 if ( header_.vers != 1 )
52 set_warning( "Unknown file version" );
53
54 if ( header_.timer_mode & 0x78 )
55 set_warning( "Invalid timer mode" );
56
57 addr_t load_addr = get_le16( header_.load_addr );
58 if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
59 load_addr < 0x400 )
60 set_warning( "Invalid load/init/play address" );
61
62 cpu.rst_base = load_addr;
63 rom.set_addr( load_addr );
64
65 return blargg_ok;
66 }
67
set_bank(int n)68 void Gbs_Core::set_bank( int n )
69 {
70 addr_t addr = rom.mask_addr( n * bank_size );
71 if ( addr == 0 && rom.size() > bank_size )
72 addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1
73 cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) );
74 }
75
update_timer()76 void Gbs_Core::update_timer()
77 {
78 play_period_ = 70224 / tempo_unit; // 59.73 Hz
79
80 if ( header_.timer_mode & 0x04 )
81 {
82 // Using custom rate
83 static byte const rates [4] = { 6, 0, 2, 4 };
84 // TODO: emulate double speed CPU mode rather than halving timer rate
85 int double_speed = header_.timer_mode >> 7;
86 int shift = rates [ram [hi_page + 7] & 3] - double_speed;
87 play_period_ = (256 - ram [hi_page + 6]) << shift;
88 }
89
90 play_period_ *= tempo;
91 }
92
set_tempo(double t)93 void Gbs_Core::set_tempo( double t )
94 {
95 tempo = (int) (tempo_unit / t + 0.5);
96 apu_.set_tempo( t );
97 update_timer();
98 }
99
100 // Jumps to routine, given pointer to address in file header. Pushes idle_addr
101 // as return address, NOT old PC.
jsr_then_stop(byte const addr[])102 void Gbs_Core::jsr_then_stop( byte const addr [] )
103 {
104 check( cpu.r.sp == get_le16( header_.stack_ptr ) );
105 cpu.r.pc = get_le16( addr );
106 write_mem( --cpu.r.sp, idle_addr >> 8 );
107 write_mem( --cpu.r.sp, idle_addr );
108 }
109
start_track(int track,Gb_Apu::mode_t mode)110 blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode )
111 {
112 // Reset APU to state expected by most rips
113 static byte const sound_data [] = {
114 0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled
115 0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled
116 0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled
117 0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled
118 0x77, 0xFF, 0x80, // max volume, all chans in center, power on
119 };
120 apu_.reset( mode );
121 apu_.write_register( 0, 0xFF26, 0x80 ); // power on
122 for ( int i = 0; i < (int) sizeof sound_data; i++ )
123 apu_.write_register( 0, i + apu_.io_addr, sound_data [i] );
124 apu_.end_frame( 1 ); // necessary to get click out of the way
125
126 // Init memory and I/O registers
127 memset( ram, 0, 0x4000 );
128 memset( ram + 0x4000, 0xFF, 0x1F80 );
129 memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
130 ram [hi_page] = 0; // joypad reads back as 0
131 ram [idle_addr - ram_addr] = 0xED; // illegal instruction
132 ram [hi_page + 6] = header_.timer_modulo;
133 ram [hi_page + 7] = header_.timer_mode;
134
135 // Map memory
136 cpu.reset( rom.unmapped() );
137 cpu.map_code( ram_addr, 0x10000 - ram_addr, ram );
138 cpu.map_code( 0, bank_size, rom.at_addr( 0 ) );
139 set_bank( rom.size() > bank_size );
140
141 // CPU registers, timing
142 update_timer();
143 next_play = play_period_;
144 cpu.r.fa = track;
145 cpu.r.sp = get_le16( header_.stack_ptr );
146 jsr_then_stop( header_.init_addr );
147
148 return blargg_ok;
149 }
150
run_until(int end)151 blargg_err_t Gbs_Core::run_until( int end )
152 {
153 end_time = end;
154 cpu.set_time( cpu.time() - end );
155 while ( true )
156 {
157 run_cpu();
158 if ( cpu.time() >= 0 )
159 break;
160
161 if ( cpu.r.pc == idle_addr )
162 {
163 if ( next_play > end_time )
164 {
165 cpu.set_time( 0 );
166 break;
167 }
168
169 if ( cpu.time() < next_play - end_time )
170 cpu.set_time( next_play - end_time );
171 next_play += play_period_;
172 jsr_then_stop( header_.play_addr );
173 }
174 else if ( cpu.r.pc > 0xFFFF )
175 {
176 dprintf( "PC wrapped around\n" );
177 cpu.r.pc &= 0xFFFF;
178 }
179 else
180 {
181 set_warning( "Emulation error (illegal/unsupported instruction)" );
182 dprintf( "Bad opcode $%02X at $%04X\n",
183 (int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc );
184 cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF;
185 cpu.set_time( cpu.time() + 6 );
186 }
187 }
188
189 return blargg_ok;
190 }
191
end_frame(int end)192 blargg_err_t Gbs_Core::end_frame( int end )
193 {
194 RETURN_ERR( run_until( end ) );
195
196 next_play -= end;
197 if ( next_play < 0 ) // happens when play routine takes too long
198 {
199 #if !GBS_IGNORE_STARVED_PLAY
200 check( false );
201 #endif
202 next_play = 0;
203 }
204
205 apu_.end_frame( end );
206
207 return blargg_ok;
208 }
209