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