all repos — mgba @ b37761327ed545da997934c18630ff80d68dc71c

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