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