all repos — mgba @ 9150a79efd85450df02ba70908dbfcc3f27924b2

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