all repos — mgba @ 3908b43d9327b2872be081b8ef67d70176d10d7d

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