all repos — mgba @ cf8868d5cb5cfd8244713328f96d8fdd814f1f83

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