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