all repos — mgba @ 36c1fb59be4710543c4a5476a554757efe306ccd

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