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