all repos — mgba @ bf2cdbacb6037f3bd07f41db62fcb17eda77de10

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 <mgba-util/common.h>
 28
 29#include <mgba/core/blip_buf.h>
 30#include <mgba/core/core.h>
 31#include <mgba/core/cheats.h>
 32#include <mgba/core/serialize.h>
 33#include <mgba/gba/core.h>
 34#include <mgba/internal/gba/cheats.h>
 35#include <mgba/internal/gba/input.h>
 36#include <mgba-util/circle-buffer.h>
 37#include <mgba-util/memory.h>
 38#include <mgba-util/vfs.h>
 39
 40#import <OpenEmuBase/OERingBuffer.h>
 41#import "OEGBASystemResponderClient.h"
 42#import <OpenGL/gl.h>
 43
 44#define SAMPLES 1024
 45
 46@interface mGBAGameCore () <OEGBASystemResponderClient>
 47{
 48	struct mCore* core;
 49	void* outputBuffer;
 50	NSMutableDictionary *cheatSets;
 51}
 52@end
 53
 54@implementation mGBAGameCore
 55
 56- (id)init
 57{
 58	if ((self = [super init]))
 59	{
 60		core = GBACoreCreate();
 61		mCoreInitConfig(core, nil);
 62
 63		struct mCoreOptions opts = {
 64			.useBios = true,
 65		};
 66		mCoreConfigLoadDefaults(&core->config, &opts);
 67		core->init(core);
 68		outputBuffer = nil;
 69
 70		unsigned width, height;
 71		core->desiredVideoDimensions(core, &width, &height);
 72		outputBuffer = malloc(width * height * BYTES_PER_PIXEL);
 73		core->setVideoBuffer(core, outputBuffer, width);
 74		core->setAudioBufferSize(core, SAMPLES);
 75		cheatSets = [[NSMutableDictionary alloc] init];
 76	}
 77
 78	return self;
 79}
 80
 81- (void)dealloc
 82{
 83	mCoreConfigDeinit(&core->config);
 84	core->deinit(core);
 85	free(outputBuffer);
 86}
 87
 88#pragma mark - Execution
 89
 90- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
 91{
 92	NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
 93	[[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
 94	                                withIntermediateDirectories:YES
 95	                                attributes:nil
 96	                                error:nil];
 97	if (core->dirs.save) {
 98		core->dirs.save->close(core->dirs.save);
 99	}
100	core->dirs.save = VDirOpen([batterySavesDirectory fileSystemRepresentation]);
101
102	if (!mCoreLoadFile(core, [path fileSystemRepresentation])) {
103		*error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
104		return NO;
105	}
106	mCoreAutoloadSave(core);
107
108	core->reset(core);
109
110	return YES;
111}
112
113- (void)executeFrame
114{
115	core->runFrame(core);
116
117	int16_t samples[SAMPLES * 2];
118	size_t available = 0;
119	available = blip_samples_avail(core->getAudioChannel(core, 0));
120	blip_read_samples(core->getAudioChannel(core, 0), samples, available, true);
121	blip_read_samples(core->getAudioChannel(core, 1), samples + 1, available, true);
122	[[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
123}
124
125- (void)resetEmulation
126{
127	core->reset(core);
128}
129
130- (void)setupEmulation
131{
132	blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768);
133	blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768);
134}
135
136#pragma mark - Video
137
138- (OEIntSize)aspectSize
139{
140	return OEIntSizeMake(3, 2);
141}
142
143- (OEIntRect)screenRect
144{
145	unsigned width, height;
146	core->desiredVideoDimensions(core, &width, &height);
147    return OEIntRectMake(0, 0, width, height);
148}
149
150- (OEIntSize)bufferSize
151{
152	unsigned width, height;
153	core->desiredVideoDimensions(core, &width, &height);
154    return OEIntSizeMake(width, height);
155}
156
157- (const void *)getVideoBufferWithHint:(void *)hint
158{
159	OEIntSize bufferSize = [self bufferSize];
160
161	if (!hint) {
162		hint = outputBuffer;
163	}
164
165	outputBuffer = hint;
166	core->setVideoBuffer(core, hint, bufferSize.width);
167
168	return hint;
169}
170
171- (GLenum)pixelFormat
172{
173    return GL_RGBA;
174}
175
176- (GLenum)pixelType
177{
178    return GL_UNSIGNED_INT_8_8_8_8_REV;
179}
180
181- (NSTimeInterval)frameInterval
182{
183	return core->frequency(core) / (double) core->frameCycles(core);
184}
185
186#pragma mark - Audio
187
188- (NSUInteger)channelCount
189{
190    return 2;
191}
192
193- (double)audioSampleRate
194{
195    return 32768;
196}
197
198#pragma mark - Save State
199
200- (NSData *)serializeStateWithError:(NSError **)outError
201{
202	struct VFile* vf = VFileMemChunk(nil, 0);
203	if (!mCoreSaveStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
204		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
205		vf->close(vf);
206		return nil;
207	}
208	size_t size = vf->size(vf);
209	void* data = vf->map(vf, size, MAP_READ);
210	NSData *nsdata = [NSData dataWithBytes:data length:size];
211	vf->unmap(vf, data, size);
212	vf->close(vf);
213	return nsdata;
214}
215
216- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
217{
218	struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
219	if (!mCoreLoadStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
220		*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
221		vf->close(vf);
222		return NO;
223	}
224	vf->close(vf);
225	return YES;
226}
227
228- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
229{
230	struct VFile* vf = VFileOpen([fileName fileSystemRepresentation], O_CREAT | O_TRUNC | O_RDWR);
231	block(mCoreSaveStateNamed(core, vf, 0), nil);
232	vf->close(vf);
233}
234
235- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
236{
237	struct VFile* vf = VFileOpen([fileName fileSystemRepresentation], O_RDONLY);
238	block(mCoreLoadStateNamed(core, vf, 0), nil);
239	vf->close(vf);
240}
241
242#pragma mark - Input
243
244const int GBAMap[] = {
245	GBA_KEY_UP,
246	GBA_KEY_DOWN,
247	GBA_KEY_LEFT,
248	GBA_KEY_RIGHT,
249	GBA_KEY_A,
250	GBA_KEY_B,
251	GBA_KEY_L,
252	GBA_KEY_R,
253	GBA_KEY_START,
254	GBA_KEY_SELECT
255};
256
257- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
258{
259	UNUSED(player);
260	core->addKeys(core, 1 << GBAMap[button]);
261}
262
263- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
264{
265	UNUSED(player);
266	core->clearKeys(core, 1 << GBAMap[button]);
267}
268
269#pragma mark - Cheats
270
271- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
272{
273	code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
274	code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
275
276	NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
277	struct mCheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
278	if (cheatSet) {
279		cheatSet->enabled = enabled;
280		return;
281	}
282	struct mCheatDevice* cheats = core->cheatDevice(core);
283	cheatSet = cheats->createSet(cheats, [codeId UTF8String]);
284	size_t size = mCheatSetsSize(&cheats->cheats);
285	if (size) {
286		cheatSet->copyProperties(cheatSet, *mCheatSetsGetPointer(&cheats->cheats, size - 1));
287	}
288	int codeType = GBA_CHEAT_AUTODETECT;
289	NSArray *codeSet = [code componentsSeparatedByString:@"+"];
290	for (id c in codeSet) {
291		mCheatAddLine(cheatSet, [c UTF8String], codeType);
292	}
293	cheatSet->enabled = enabled;
294	[cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
295	mCheatAddSet(cheats, cheatSet);
296}
297@end
298