all repos — mgba @ 92c6b90b03f8c04b53312bf2273d86a9783ee073

mGBA Game Boy Advance Emulator

src/platform/openemu/mGBAGameCore.m (view raw)

  1/*
  2 Copyright (c) 2016, Jeffrey Pfau
  3
  4 Redistribution and use in source and binary forms, with or without
  5 modification, are permitted provided that the following conditions are met:
  6     * Redistributions of source code must retain the above copyright
  7       notice, this list of conditions and the following disclaimer.
  8     * Redistributions in binary form must reproduce the above copyright
  9       notice, this list of conditions and the following disclaimer in the
 10       documentation and/or other materials provided with the distribution.
 11
 12 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
 13 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 14 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 15 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 16 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 17 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 18 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 19 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 20 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 21 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 22 POSSIBILITY OF SUCH DAMAGE.
 23 */
 24
 25#import "mGBAGameCore.h"
 26
 27#include "util/common.h"
 28
 29#include "gba/cheats.h"
 30#include "gba/cheats/gameshark.h"
 31#include "gba/renderers/video-software.h"
 32#include "gba/serialize.h"
 33#include "gba/context/context.h"
 34#include "util/circle-buffer.h"
 35#include "util/memory.h"
 36#include "util/vfs.h"
 37
 38#import <OpenEmuBase/OERingBuffer.h>
 39#import "OEGBASystemResponderClient.h"
 40#import <OpenGL/gl.h>
 41
 42#define SAMPLES 1024
 43
 44@interface mGBAGameCore () <OEGBASystemResponderClient>
 45{
 46	struct GBAContext context;
 47	struct GBAVideoSoftwareRenderer renderer;
 48	struct GBACheatDevice cheats;
 49	NSMutableDictionary *cheatSets;
 50	uint16_t keys;
 51}
 52@end
 53
 54@implementation mGBAGameCore
 55
 56- (id)init
 57{
 58	if ((self = [super init]))
 59	{
 60		// TODO: Add a log handler
 61		GBAContextInit(&context, 0);
 62		struct GBAOptions opts = {
 63			.useBios = true,
 64			.idleOptimization = IDLE_LOOP_REMOVE
 65		};
 66		mCoreConfigLoadDefaults(&context.config, &opts);
 67		GBAVideoSoftwareRendererCreate(&renderer);
 68		renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
 69		renderer.outputBufferStride = 256;
 70		context.renderer = &renderer.d;
 71		GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
 72		GBACheatDeviceCreate(&cheats);
 73		GBACheatAttachDevice(context.gba, &cheats);
 74		cheatSets = [[NSMutableDictionary alloc] init];
 75		keys = 0;
 76	}
 77
 78	return self;
 79}
 80
 81- (void)dealloc
 82{
 83	GBAContextDeinit(&context);
 84	[cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 85		UNUSED(key);
 86		UNUSED(stop);
 87		GBACheatRemoveSet(&cheats, [obj pointerValue]);
 88	}];
 89	GBACheatDeviceDestroy(&cheats);
 90	[cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 91		UNUSED(key);
 92		UNUSED(stop);
 93		GBACheatSetDeinit([obj pointerValue]);
 94	}];
 95	[cheatSets release];
 96	free(renderer.outputBuffer);
 97
 98	[super dealloc];
 99}
100
101#pragma mark - Execution
102
103- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
104{
105	NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
106	[[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
107	                                withIntermediateDirectories:YES
108	                                attributes:nil
109	                                error:nil];
110	if (context.dirs.save) {
111		context.dirs.save->close(context.dirs.save);
112	}
113	context.dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
114
115	if (!GBAContextLoadROM(&context, [path UTF8String], true)) {
116		*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
117		return NO;
118	}
119
120	if (!GBAContextStart(&context)) {
121		*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotStartCoreError userInfo:nil];
122		return NO;
123	}
124	return YES;
125}
126
127- (void)executeFrame
128{
129	GBAContextFrame(&context, keys);
130
131	int16_t samples[SAMPLES * 2];
132	size_t available = 0;
133	available = blip_samples_avail(context.gba->audio.psg.left);
134	blip_read_samples(context.gba->audio.psg.left, samples, available, true);
135	blip_read_samples(context.gba->audio.psg.right, samples + 1, available, true);
136	[[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
137}
138
139- (void)resetEmulation
140{
141	ARMReset(context.cpu);
142}
143
144- (void)stopEmulation
145{
146	GBAContextStop(&context);
147	[super stopEmulation];
148}
149
150- (void)setupEmulation
151{
152	blip_set_rates(context.gba->audio.psg.left,  GBA_ARM7TDMI_FREQUENCY, 32768);
153	blip_set_rates(context.gba->audio.psg.right, GBA_ARM7TDMI_FREQUENCY, 32768);
154}
155
156#pragma mark - Video
157
158- (OEIntSize)aspectSize
159{
160	return OEIntSizeMake(3, 2);
161}
162
163- (OEIntRect)screenRect
164{
165    return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
166}
167
168- (OEIntSize)bufferSize
169{
170    return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
171}
172
173- (const void *)videoBuffer
174{
175	return renderer.outputBuffer;
176}
177
178- (GLenum)pixelFormat
179{
180    return GL_RGBA;
181}
182
183- (GLenum)pixelType
184{
185    return GL_UNSIGNED_INT_8_8_8_8_REV;
186}
187
188- (GLenum)internalPixelFormat
189{
190    return GL_RGB8;
191}
192
193- (NSTimeInterval)frameInterval
194{
195	return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
196}
197
198#pragma mark - Audio
199
200- (NSUInteger)channelCount
201{
202    return 2;
203}
204
205- (double)audioSampleRate
206{
207    return 32768;
208}
209
210#pragma mark - Save State
211
212- (NSData *)serializeStateWithError:(NSError **)outError
213{
214	struct VFile* vf = VFileMemChunk(nil, 0);
215	if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
216		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
217		vf->close(vf);
218		return nil;
219	}
220	size_t size = vf->size(vf);
221	void* data = vf->map(vf, size, MAP_READ);
222	NSData *nsdata = [NSData dataWithBytes:data length:size];
223	vf->unmap(vf, data, size);
224	vf->close(vf);
225	return nsdata;
226}
227
228- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
229{
230	struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
231	if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
232		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
233		vf->close(vf);
234		return NO;
235	}
236	vf->close(vf);
237	return YES;
238}
239
240- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
241{
242	struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
243	block(GBASaveStateNamed(context.gba, vf, 0), nil);
244	vf->close(vf);
245}
246
247- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
248{
249	struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
250	block(GBALoadStateNamed(context.gba, vf, 0), nil);
251	vf->close(vf);
252}
253
254#pragma mark - Input
255
256const int GBAMap[] = {
257	GBA_KEY_UP,
258	GBA_KEY_DOWN,
259	GBA_KEY_LEFT,
260	GBA_KEY_RIGHT,
261	GBA_KEY_A,
262	GBA_KEY_B,
263	GBA_KEY_L,
264	GBA_KEY_R,
265	GBA_KEY_START,
266	GBA_KEY_SELECT
267};
268
269- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
270{
271	UNUSED(player);
272	keys |= 1 << GBAMap[button];
273}
274
275- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
276{
277	UNUSED(player);
278	keys &= ~(1 << GBAMap[button]);
279}
280
281#pragma mark - Cheats
282
283- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
284{
285	code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
286	code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
287
288	NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
289	struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
290	if (cheatSet) {
291		cheatSet->enabled = enabled;
292		return;
293	}
294	cheatSet = malloc(sizeof(*cheatSet));
295	GBACheatSetInit(cheatSet, [codeId UTF8String]);
296	if ([type isEqual:@"GameShark"]) {
297		GBACheatSetGameSharkVersion(cheatSet, 1);
298	} else if ([type isEqual:@"Action Replay"]) {
299		GBACheatSetGameSharkVersion(cheatSet, 3);
300	}
301	NSArray *codeSet = [code componentsSeparatedByString:@"+"];
302	for (id c in codeSet) {
303		GBACheatAddLine(cheatSet, [c UTF8String]);
304	}
305	cheatSet->enabled = enabled;
306	[cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
307	GBACheatAddSet(&cheats, cheatSet);
308}
309@end
310