all repos — mgba @ 30e0be098fdab754da67838db1c319cfea7aeb4f

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