all repos — mgba @ a53c3e0628461b179ed2b605339c314617d1aa66

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		GBAConfigLoadDefaults(&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#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
134	available = blip_samples_avail(context.gba->audio.left);
135	blip_read_samples(context.gba->audio.left, samples, available, true);
136	blip_read_samples(context.gba->audio.right, samples + 1, available, true);
137#else
138#error BLIP_BUF is required for now
139#endif
140	[[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
141}
142
143- (void)resetEmulation
144{
145	ARMReset(context.cpu);
146}
147
148- (void)stopEmulation
149{
150	GBAContextStop(&context);
151	[super stopEmulation];
152}
153
154- (void)setupEmulation
155{
156#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
157	blip_set_rates(context.gba->audio.left,  GBA_ARM7TDMI_FREQUENCY, 32768);
158	blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
159#endif
160}
161
162#pragma mark - Video
163
164- (OEIntSize)aspectSize
165{
166	return OEIntSizeMake(3, 2);
167}
168
169- (OEIntRect)screenRect
170{
171    return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
172}
173
174- (OEIntSize)bufferSize
175{
176    return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
177}
178
179- (const void *)videoBuffer
180{
181	return renderer.outputBuffer;
182}
183
184- (GLenum)pixelFormat
185{
186    return GL_RGBA;
187}
188
189- (GLenum)pixelType
190{
191    return GL_UNSIGNED_INT_8_8_8_8_REV;
192}
193
194- (GLenum)internalPixelFormat
195{
196    return GL_RGB8;
197}
198
199- (NSTimeInterval)frameInterval
200{
201	return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
202}
203
204#pragma mark - Audio
205
206- (NSUInteger)channelCount
207{
208    return 2;
209}
210
211- (double)audioSampleRate
212{
213    return 32768;
214}
215
216#pragma mark - Save State
217
218- (NSData *)serializeStateWithError:(NSError **)outError
219{
220	struct VFile* vf = VFileMemChunk(nil, 0);
221	if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
222		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
223		vf->close(vf);
224		return nil;
225	}
226	size_t size = vf->size(vf);
227	void* data = vf->map(vf, size, MAP_READ);
228	NSData *nsdata = [NSData dataWithBytes:data length:size];
229	vf->unmap(vf, data, size);
230	vf->close(vf);
231	return nsdata;
232}
233
234- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
235{
236	struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
237	if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
238		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
239		vf->close(vf);
240		return NO;
241	}
242	vf->close(vf);
243	return YES;
244}
245
246- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
247{
248	struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
249	block(GBASaveStateNamed(context.gba, vf, 0), nil);
250	vf->close(vf);
251}
252
253- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
254{
255	struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
256	block(GBALoadStateNamed(context.gba, vf, 0), nil);
257	vf->close(vf);
258}
259
260#pragma mark - Input
261
262const int GBAMap[] = {
263	GBA_KEY_UP,
264	GBA_KEY_DOWN,
265	GBA_KEY_LEFT,
266	GBA_KEY_RIGHT,
267	GBA_KEY_A,
268	GBA_KEY_B,
269	GBA_KEY_L,
270	GBA_KEY_R,
271	GBA_KEY_START,
272	GBA_KEY_SELECT
273};
274
275- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
276{
277	UNUSED(player);
278	keys |= 1 << GBAMap[button];
279}
280
281- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
282{
283	UNUSED(player);
284	keys &= ~(1 << GBAMap[button]);
285}
286
287#pragma mark - Cheats
288
289- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
290{
291	code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
292	code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
293
294	NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
295	struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
296	if (cheatSet) {
297		cheatSet->enabled = enabled;
298		return;
299	}
300	cheatSet = malloc(sizeof(*cheatSet));
301	GBACheatSetInit(cheatSet, [codeId UTF8String]);
302	if ([type isEqual:@"GameShark"]) {
303		GBACheatSetGameSharkVersion(cheatSet, 1);
304	} else if ([type isEqual:@"Action Replay"]) {
305		GBACheatSetGameSharkVersion(cheatSet, 3);
306	}
307	NSArray *codeSet = [code componentsSeparatedByString:@"+"];
308	for (id c in codeSet) {
309		GBACheatAddLine(cheatSet, [c UTF8String]);
310	}
311	cheatSet->enabled = enabled;
312	[cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
313	GBACheatAddSet(&cheats, cheatSet);
314}
315@end
316