all repos — mgba @ 3ade5188db2c36c8ed850847d2925abdb5198bd5

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 mCoreOptions opts = {
 63			.useBios = true,
 64		};
 65		mCoreConfigLoadDefaults(&context.config, &opts);
 66		GBAVideoSoftwareRendererCreate(&renderer);
 67		renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
 68		renderer.outputBufferStride = 256;
 69		context.renderer = &renderer.d;
 70		GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
 71		GBACheatDeviceCreate(&cheats);
 72		GBACheatAttachDevice(context.gba, &cheats);
 73		cheatSets = [[NSMutableDictionary alloc] init];
 74		keys = 0;
 75	}
 76
 77	return self;
 78}
 79
 80- (void)dealloc
 81{
 82	GBAContextDeinit(&context);
 83	[cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 84		UNUSED(key);
 85		UNUSED(stop);
 86		GBACheatRemoveSet(&cheats, [obj pointerValue]);
 87	}];
 88	GBACheatDeviceDestroy(&cheats);
 89	[cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 90		UNUSED(key);
 91		UNUSED(stop);
 92		GBACheatSetDeinit([obj pointerValue]);
 93	}];
 94	[cheatSets release];
 95	free(renderer.outputBuffer);
 96
 97	[super dealloc];
 98}
 99
100#pragma mark - Execution
101
102- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
103{
104	NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
105	[[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
106	                                withIntermediateDirectories:YES
107	                                attributes:nil
108	                                error:nil];
109	if (context.dirs.save) {
110		context.dirs.save->close(context.dirs.save);
111	}
112	context.dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
113
114	if (!GBAContextLoadROM(&context, [path UTF8String], true)) {
115		*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
116		return NO;
117	}
118
119	if (!GBAContextStart(&context)) {
120		*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotStartCoreError userInfo:nil];
121		return NO;
122	}
123	return YES;
124}
125
126- (void)executeFrame
127{
128	GBAContextFrame(&context, keys);
129
130	int16_t samples[SAMPLES * 2];
131	size_t available = 0;
132	available = blip_samples_avail(context.gba->audio.psg.left);
133	blip_read_samples(context.gba->audio.psg.left, samples, available, true);
134	blip_read_samples(context.gba->audio.psg.right, samples + 1, available, true);
135	[[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
136}
137
138- (void)resetEmulation
139{
140	ARMReset(context.cpu);
141}
142
143- (void)stopEmulation
144{
145	GBAContextStop(&context);
146	[super stopEmulation];
147}
148
149- (void)setupEmulation
150{
151	blip_set_rates(context.gba->audio.psg.left,  GBA_ARM7TDMI_FREQUENCY, 32768);
152	blip_set_rates(context.gba->audio.psg.right, GBA_ARM7TDMI_FREQUENCY, 32768);
153}
154
155#pragma mark - Video
156
157- (OEIntSize)aspectSize
158{
159	return OEIntSizeMake(3, 2);
160}
161
162- (OEIntRect)screenRect
163{
164    return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
165}
166
167- (OEIntSize)bufferSize
168{
169    return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
170}
171
172- (const void *)videoBuffer
173{
174	return renderer.outputBuffer;
175}
176
177- (GLenum)pixelFormat
178{
179    return GL_RGBA;
180}
181
182- (GLenum)pixelType
183{
184    return GL_UNSIGNED_INT_8_8_8_8_REV;
185}
186
187- (GLenum)internalPixelFormat
188{
189    return GL_RGB8;
190}
191
192- (NSTimeInterval)frameInterval
193{
194	return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
195}
196
197#pragma mark - Audio
198
199- (NSUInteger)channelCount
200{
201    return 2;
202}
203
204- (double)audioSampleRate
205{
206    return 32768;
207}
208
209#pragma mark - Save State
210
211- (NSData *)serializeStateWithError:(NSError **)outError
212{
213	struct VFile* vf = VFileMemChunk(nil, 0);
214	if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
215		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
216		vf->close(vf);
217		return nil;
218	}
219	size_t size = vf->size(vf);
220	void* data = vf->map(vf, size, MAP_READ);
221	NSData *nsdata = [NSData dataWithBytes:data length:size];
222	vf->unmap(vf, data, size);
223	vf->close(vf);
224	return nsdata;
225}
226
227- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
228{
229	struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
230	if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
231		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
232		vf->close(vf);
233		return NO;
234	}
235	vf->close(vf);
236	return YES;
237}
238
239- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
240{
241	struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
242	block(GBASaveStateNamed(context.gba, vf, 0), nil);
243	vf->close(vf);
244}
245
246- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
247{
248	struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
249	block(GBALoadStateNamed(context.gba, vf, 0), nil);
250	vf->close(vf);
251}
252
253#pragma mark - Input
254
255const int GBAMap[] = {
256	GBA_KEY_UP,
257	GBA_KEY_DOWN,
258	GBA_KEY_LEFT,
259	GBA_KEY_RIGHT,
260	GBA_KEY_A,
261	GBA_KEY_B,
262	GBA_KEY_L,
263	GBA_KEY_R,
264	GBA_KEY_START,
265	GBA_KEY_SELECT
266};
267
268- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
269{
270	UNUSED(player);
271	keys |= 1 << GBAMap[button];
272}
273
274- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
275{
276	UNUSED(player);
277	keys &= ~(1 << GBAMap[button]);
278}
279
280#pragma mark - Cheats
281
282- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
283{
284	code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
285	code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
286
287	NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
288	struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
289	if (cheatSet) {
290		cheatSet->enabled = enabled;
291		return;
292	}
293	cheatSet = malloc(sizeof(*cheatSet));
294	GBACheatSetInit(cheatSet, [codeId UTF8String]);
295	if ([type isEqual:@"GameShark"]) {
296		GBACheatSetGameSharkVersion(cheatSet, 1);
297	} else if ([type isEqual:@"Action Replay"]) {
298		GBACheatSetGameSharkVersion(cheatSet, 3);
299	}
300	NSArray *codeSet = [code componentsSeparatedByString:@"+"];
301	for (id c in codeSet) {
302		GBACheatAddLine(cheatSet, [c UTF8String]);
303	}
304	cheatSet->enabled = enabled;
305	[cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
306	GBACheatAddSet(&cheats, cheatSet);
307}
308@end
309