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