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 unsigned width, height;
71 core->desiredVideoDimensions(core, &width, &height);
72 outputBuffer = malloc(width * height * BYTES_PER_PIXEL);
73 core->setVideoBuffer(core, outputBuffer, width);
74 core->setAudioBufferSize(core, SAMPLES);
75 cheatSets = [[NSMutableDictionary alloc] init];
76 }
77
78 return self;
79}
80
81- (void)dealloc
82{
83 mCoreConfigDeinit(&core->config);
84 core->deinit(core);
85 free(outputBuffer);
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 fileSystemRepresentation]);
101
102 if (!mCoreLoadFile(core, [path fileSystemRepresentation])) {
103 *error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
104 return NO;
105 }
106 mCoreAutoloadSave(core);
107
108 core->reset(core);
109
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), core->frequency(core), 32768);
133 blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 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 *)getVideoBufferWithHint:(void *)hint
158{
159 OEIntSize bufferSize = [self bufferSize];
160
161 if (!hint) {
162 hint = outputBuffer;
163 }
164
165 outputBuffer = hint;
166 core->setVideoBuffer(core, hint, bufferSize.width);
167
168 return hint;
169}
170
171- (GLenum)pixelFormat
172{
173 return GL_RGBA;
174}
175
176- (GLenum)pixelType
177{
178 return GL_UNSIGNED_INT_8_8_8_8_REV;
179}
180
181- (NSTimeInterval)frameInterval
182{
183 return core->frequency(core) / (double) core->frameCycles(core);
184}
185
186#pragma mark - Audio
187
188- (NSUInteger)channelCount
189{
190 return 2;
191}
192
193- (double)audioSampleRate
194{
195 return 32768;
196}
197
198#pragma mark - Save State
199
200- (NSData *)serializeStateWithError:(NSError **)outError
201{
202 struct VFile* vf = VFileMemChunk(nil, 0);
203 if (!mCoreSaveStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
204 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
205 vf->close(vf);
206 return nil;
207 }
208 size_t size = vf->size(vf);
209 void* data = vf->map(vf, size, MAP_READ);
210 NSData *nsdata = [NSData dataWithBytes:data length:size];
211 vf->unmap(vf, data, size);
212 vf->close(vf);
213 return nsdata;
214}
215
216- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
217{
218 struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
219 if (!mCoreLoadStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
220 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
221 vf->close(vf);
222 return NO;
223 }
224 vf->close(vf);
225 return YES;
226}
227
228- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
229{
230 struct VFile* vf = VFileOpen([fileName fileSystemRepresentation], O_CREAT | O_TRUNC | O_RDWR);
231 block(mCoreSaveStateNamed(core, vf, 0), nil);
232 vf->close(vf);
233}
234
235- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
236{
237 struct VFile* vf = VFileOpen([fileName fileSystemRepresentation], O_RDONLY);
238 block(mCoreLoadStateNamed(core, vf, 0), nil);
239 vf->close(vf);
240}
241
242#pragma mark - Input
243
244const int GBAMap[] = {
245 GBA_KEY_UP,
246 GBA_KEY_DOWN,
247 GBA_KEY_LEFT,
248 GBA_KEY_RIGHT,
249 GBA_KEY_A,
250 GBA_KEY_B,
251 GBA_KEY_L,
252 GBA_KEY_R,
253 GBA_KEY_START,
254 GBA_KEY_SELECT
255};
256
257- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
258{
259 UNUSED(player);
260 core->addKeys(core, 1 << GBAMap[button]);
261}
262
263- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
264{
265 UNUSED(player);
266 core->clearKeys(core, 1 << GBAMap[button]);
267}
268
269#pragma mark - Cheats
270
271- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
272{
273 code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
274 code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
275
276 NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
277 struct mCheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
278 if (cheatSet) {
279 cheatSet->enabled = enabled;
280 return;
281 }
282 struct mCheatDevice* cheats = core->cheatDevice(core);
283 cheatSet = cheats->createSet(cheats, [codeId UTF8String]);
284 size_t size = mCheatSetsSize(&cheats->cheats);
285 if (size) {
286 cheatSet->copyProperties(cheatSet, *mCheatSetsGetPointer(&cheats->cheats, size - 1));
287 }
288 int codeType = GBA_CHEAT_AUTODETECT;
289 NSArray *codeSet = [code componentsSeparatedByString:@"+"];
290 for (id c in codeSet) {
291 mCheatAddLine(cheatSet, [c UTF8String], codeType);
292 }
293 cheatSet->enabled = enabled;
294 [cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
295 mCheatAddSet(cheats, cheatSet);
296}
297@end
298