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