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/core.h"
30#include "gba/audio.h"
31#include "gba/cheats.h"
32#include "gba/core.h"
33#include "gba/cheats/gameshark.h"
34#include "gba/gba.h"
35#include "gba/input.h"
36#include "gba/serialize.h"
37#include "util/circle-buffer.h"
38#include "util/memory.h"
39#include "util/vfs.h"
40
41#import <OpenEmuBase/OERingBuffer.h>
42#import "OEGBASystemResponderClient.h"
43#import <OpenGL/gl.h>
44
45#define SAMPLES 1024
46
47@interface mGBAGameCore () <OEGBASystemResponderClient>
48{
49 struct mCore* core;
50 struct GBA* gba;
51 struct GBACheatDevice cheats;
52 void* outputBuffer;
53 NSMutableDictionary *cheatSets;
54}
55@end
56
57@implementation mGBAGameCore
58
59- (id)init
60{
61 if ((self = [super init]))
62 {
63 core = GBACoreCreate();
64 mCoreInitConfig(core, nil);
65
66 struct mCoreOptions opts = {
67 .useBios = true,
68 };
69 mCoreConfigLoadDefaults(&core->config, &opts);
70 core->init(core);
71
72 unsigned width, height;
73 core->desiredVideoDimensions(core, &width, &height);
74 outputBuffer = malloc(width * height * BYTES_PER_PIXEL);
75 core->setVideoBuffer(core, outputBuffer, width);
76 core->setAudioBufferSize(core, SAMPLES);
77
78 gba = core->board;
79 GBACheatDeviceCreate(&cheats);
80 GBACheatAttachDevice(gba, &cheats);
81 cheatSets = [[NSMutableDictionary alloc] init];
82 }
83
84 return self;
85}
86
87- (void)dealloc
88{
89 core->deinit(core);
90 [cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
91 UNUSED(key);
92 UNUSED(stop);
93 GBACheatRemoveSet(&cheats, [obj pointerValue]);
94 }];
95 GBACheatDeviceDestroy(&cheats);
96 [cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
97 UNUSED(key);
98 UNUSED(stop);
99 GBACheatSetDeinit([obj pointerValue]);
100 }];
101 [cheatSets release];
102 free(outputBuffer);
103
104 [super dealloc];
105}
106
107#pragma mark - Execution
108
109- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
110{
111 NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
112 [[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
113 withIntermediateDirectories:YES
114 attributes:nil
115 error:nil];
116 if (core->dirs.save) {
117 core->dirs.save->close(core->dirs.save);
118 }
119 core->dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
120
121 if (!mCoreLoadFile(core, [path UTF8String])) {
122 *error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
123 return NO;
124 }
125 mCoreAutoloadSave(core);
126
127 core->reset(core);
128 return YES;
129}
130
131- (void)executeFrame
132{
133 core->runFrame(core);
134
135 int16_t samples[SAMPLES * 2];
136 size_t available = 0;
137 available = blip_samples_avail(core->getAudioChannel(core, 0));
138 blip_read_samples(core->getAudioChannel(core, 0), samples, available, true);
139 blip_read_samples(core->getAudioChannel(core, 1), samples + 1, available, true);
140 [[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
141}
142
143- (void)resetEmulation
144{
145 core->reset(core);
146}
147
148- (void)setupEmulation
149{
150 blip_set_rates(core->getAudioChannel(core, 0), GBA_ARM7TDMI_FREQUENCY, 32768);
151 blip_set_rates(core->getAudioChannel(core, 1), GBA_ARM7TDMI_FREQUENCY, 32768);
152}
153
154#pragma mark - Video
155
156- (OEIntSize)aspectSize
157{
158 return OEIntSizeMake(3, 2);
159}
160
161- (OEIntRect)screenRect
162{
163 unsigned width, height;
164 core->desiredVideoDimensions(core, &width, &height);
165 return OEIntRectMake(0, 0, width, height);
166}
167
168- (OEIntSize)bufferSize
169{
170 unsigned width, height;
171 core->desiredVideoDimensions(core, &width, &height);
172 return OEIntSizeMake(width, height);
173}
174
175- (const void *)videoBuffer
176{
177 return outputBuffer;
178}
179
180- (GLenum)pixelFormat
181{
182 return GL_RGBA;
183}
184
185- (GLenum)pixelType
186{
187 return GL_UNSIGNED_INT_8_8_8_8_REV;
188}
189
190- (GLenum)internalPixelFormat
191{
192 return GL_RGB8;
193}
194
195- (NSTimeInterval)frameInterval
196{
197 return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
198}
199
200#pragma mark - Audio
201
202- (NSUInteger)channelCount
203{
204 return 2;
205}
206
207- (double)audioSampleRate
208{
209 return 32768;
210}
211
212#pragma mark - Save State
213
214- (NSData *)serializeStateWithError:(NSError **)outError
215{
216 struct VFile* vf = VFileMemChunk(nil, 0);
217 if (!core->saveState(core, vf, SAVESTATE_SAVEDATA)) {
218 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
219 vf->close(vf);
220 return nil;
221 }
222 size_t size = vf->size(vf);
223 void* data = vf->map(vf, size, MAP_READ);
224 NSData *nsdata = [NSData dataWithBytes:data length:size];
225 vf->unmap(vf, data, size);
226 vf->close(vf);
227 return nsdata;
228}
229
230- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
231{
232 struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
233 if (!core->loadState(core, vf, SAVESTATE_SAVEDATA)) {
234 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
235 vf->close(vf);
236 return NO;
237 }
238 vf->close(vf);
239 return YES;
240}
241
242- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
243{
244 struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
245 block(core->saveState(core, vf, 0), nil);
246 vf->close(vf);
247}
248
249- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
250{
251 struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
252 block(core->loadState(core, vf, 0), nil);
253 vf->close(vf);
254}
255
256#pragma mark - Input
257
258const int GBAMap[] = {
259 GBA_KEY_UP,
260 GBA_KEY_DOWN,
261 GBA_KEY_LEFT,
262 GBA_KEY_RIGHT,
263 GBA_KEY_A,
264 GBA_KEY_B,
265 GBA_KEY_L,
266 GBA_KEY_R,
267 GBA_KEY_START,
268 GBA_KEY_SELECT
269};
270
271- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
272{
273 UNUSED(player);
274 core->addKeys(core, 1 << GBAMap[button]);
275}
276
277- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
278{
279 UNUSED(player);
280 core->clearKeys(core, 1 << GBAMap[button]);
281}
282
283#pragma mark - Cheats
284
285- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
286{
287 code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
288 code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
289
290 NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
291 struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
292 if (cheatSet) {
293 cheatSet->enabled = enabled;
294 return;
295 }
296 cheatSet = malloc(sizeof(*cheatSet));
297 GBACheatSetInit(cheatSet, [codeId UTF8String]);
298 if ([type isEqual:@"GameShark"]) {
299 GBACheatSetGameSharkVersion(cheatSet, 1);
300 } else if ([type isEqual:@"Action Replay"]) {
301 GBACheatSetGameSharkVersion(cheatSet, 3);
302 }
303 NSArray *codeSet = [code componentsSeparatedByString:@"+"];
304 for (id c in codeSet) {
305 GBACheatAddLine(cheatSet, [c UTF8String]);
306 }
307 cheatSet->enabled = enabled;
308 [cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
309 GBACheatAddSet(&cheats, cheatSet);
310}
311@end
312