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/serialize.h"
30#include "core/core.h"
31#include "gba/cheats.h"
32#include "gba/core.h"
33#include "gba/gba.h"
34#include "gba/input.h"
35#include "util/circle-buffer.h"
36#include "util/memory.h"
37#include "util/vfs.h"
38
39#import <OpenEmuBase/OERingBuffer.h>
40#import "OEGBASystemResponderClient.h"
41#import <OpenGL/gl.h>
42
43#define SAMPLES 1024
44
45@interface mGBAGameCore () <OEGBASystemResponderClient>
46{
47 struct mCore* core;
48 void* outputBuffer;
49 NSMutableDictionary *cheatSets;
50}
51@end
52
53@implementation mGBAGameCore
54
55- (id)init
56{
57 if ((self = [super init]))
58 {
59 core = GBACoreCreate();
60 mCoreInitConfig(core, nil);
61
62 struct mCoreOptions opts = {
63 .useBios = true,
64 };
65 mCoreConfigLoadDefaults(&core->config, &opts);
66 core->init(core);
67
68 unsigned width, height;
69 core->desiredVideoDimensions(core, &width, &height);
70 outputBuffer = malloc(width * height * BYTES_PER_PIXEL);
71 core->setVideoBuffer(core, outputBuffer, width);
72 core->setAudioBufferSize(core, SAMPLES);
73
74 cheatSets = [[NSMutableDictionary alloc] init];
75 }
76
77 return self;
78}
79
80- (void)dealloc
81{
82 core->deinit(core);
83 [cheatSets release];
84 free(outputBuffer);
85
86 [super dealloc];
87}
88
89#pragma mark - Execution
90
91- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
92{
93 NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
94 [[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
95 withIntermediateDirectories:YES
96 attributes:nil
97 error:nil];
98 if (core->dirs.save) {
99 core->dirs.save->close(core->dirs.save);
100 }
101 core->dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
102
103 if (!mCoreLoadFile(core, [path UTF8String])) {
104 *error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
105 return NO;
106 }
107 mCoreAutoloadSave(core);
108
109 core->reset(core);
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), GBA_ARM7TDMI_FREQUENCY, 32768);
133 blip_set_rates(core->getAudioChannel(core, 1), GBA_ARM7TDMI_FREQUENCY, 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 *)videoBuffer
158{
159 return outputBuffer;
160}
161
162- (GLenum)pixelFormat
163{
164 return GL_RGBA;
165}
166
167- (GLenum)pixelType
168{
169 return GL_UNSIGNED_INT_8_8_8_8_REV;
170}
171
172- (GLenum)internalPixelFormat
173{
174 return GL_RGB8;
175}
176
177- (NSTimeInterval)frameInterval
178{
179 return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
180}
181
182#pragma mark - Audio
183
184- (NSUInteger)channelCount
185{
186 return 2;
187}
188
189- (double)audioSampleRate
190{
191 return 32768;
192}
193
194#pragma mark - Save State
195
196- (NSData *)serializeStateWithError:(NSError **)outError
197{
198 struct VFile* vf = VFileMemChunk(nil, 0);
199 if (!core->saveState(core, vf, SAVESTATE_SAVEDATA)) {
200 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
201 vf->close(vf);
202 return nil;
203 }
204 size_t size = vf->size(vf);
205 void* data = vf->map(vf, size, MAP_READ);
206 NSData *nsdata = [NSData dataWithBytes:data length:size];
207 vf->unmap(vf, data, size);
208 vf->close(vf);
209 return nsdata;
210}
211
212- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
213{
214 struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
215 if (!core->loadState(core, vf, SAVESTATE_SAVEDATA)) {
216 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
217 vf->close(vf);
218 return NO;
219 }
220 vf->close(vf);
221 return YES;
222}
223
224- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
225{
226 struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
227 block(core->saveState(core, vf, 0), nil);
228 vf->close(vf);
229}
230
231- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
232{
233 struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
234 block(core->loadState(core, vf, 0), nil);
235 vf->close(vf);
236}
237
238#pragma mark - Input
239
240const int GBAMap[] = {
241 GBA_KEY_UP,
242 GBA_KEY_DOWN,
243 GBA_KEY_LEFT,
244 GBA_KEY_RIGHT,
245 GBA_KEY_A,
246 GBA_KEY_B,
247 GBA_KEY_L,
248 GBA_KEY_R,
249 GBA_KEY_START,
250 GBA_KEY_SELECT
251};
252
253- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
254{
255 UNUSED(player);
256 core->addKeys(core, 1 << GBAMap[button]);
257}
258
259- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
260{
261 UNUSED(player);
262 core->clearKeys(core, 1 << GBAMap[button]);
263}
264
265#pragma mark - Cheats
266
267- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
268{
269 code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
270 code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
271
272 NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
273 struct mCheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
274 if (cheatSet) {
275 cheatSet->enabled = enabled;
276 return;
277 }
278 struct mCheatDevice* cheats = core->cheatDevice(core);
279 cheatSet = cheats->createSet(cheats, [codeId UTF8String]);
280 int codeType = GBA_CHEAT_AUTODETECT;
281 if ([type isEqual:@"GameShark"]) {
282 codeType = GBA_CHEAT_GAMESHARK;
283 } else if ([type isEqual:@"Action Replay"]) {
284 codeType = GBA_CHEAT_PRO_ACTION_REPLAY;
285 }
286 NSArray *codeSet = [code componentsSeparatedByString:@"+"];
287 for (id c in codeSet) {
288 mCheatAddLine(cheatSet, [c UTF8String], codeType);
289 }
290 cheatSet->enabled = enabled;
291 [cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
292 mCheatAddSet(cheats, cheatSet);
293}
294@end
295