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 mCoreConfigLoadDefaults(&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 available = blip_samples_avail(context.gba->audio.psg.left);
134 blip_read_samples(context.gba->audio.psg.left, samples, available, true);
135 blip_read_samples(context.gba->audio.psg.right, samples + 1, available, true);
136 [[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
137}
138
139- (void)resetEmulation
140{
141 ARMReset(context.cpu);
142}
143
144- (void)stopEmulation
145{
146 GBAContextStop(&context);
147 [super stopEmulation];
148}
149
150- (void)setupEmulation
151{
152 blip_set_rates(context.gba->audio.psg.left, GBA_ARM7TDMI_FREQUENCY, 32768);
153 blip_set_rates(context.gba->audio.psg.right, GBA_ARM7TDMI_FREQUENCY, 32768);
154}
155
156#pragma mark - Video
157
158- (OEIntSize)aspectSize
159{
160 return OEIntSizeMake(3, 2);
161}
162
163- (OEIntRect)screenRect
164{
165 return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
166}
167
168- (OEIntSize)bufferSize
169{
170 return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
171}
172
173- (const void *)videoBuffer
174{
175 return renderer.outputBuffer;
176}
177
178- (GLenum)pixelFormat
179{
180 return GL_RGBA;
181}
182
183- (GLenum)pixelType
184{
185 return GL_UNSIGNED_INT_8_8_8_8_REV;
186}
187
188- (GLenum)internalPixelFormat
189{
190 return GL_RGB8;
191}
192
193- (NSTimeInterval)frameInterval
194{
195 return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
196}
197
198#pragma mark - Audio
199
200- (NSUInteger)channelCount
201{
202 return 2;
203}
204
205- (double)audioSampleRate
206{
207 return 32768;
208}
209
210#pragma mark - Save State
211
212- (NSData *)serializeStateWithError:(NSError **)outError
213{
214 struct VFile* vf = VFileMemChunk(nil, 0);
215 if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
216 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
217 vf->close(vf);
218 return nil;
219 }
220 size_t size = vf->size(vf);
221 void* data = vf->map(vf, size, MAP_READ);
222 NSData *nsdata = [NSData dataWithBytes:data length:size];
223 vf->unmap(vf, data, size);
224 vf->close(vf);
225 return nsdata;
226}
227
228- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
229{
230 struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
231 if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
232 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
233 vf->close(vf);
234 return NO;
235 }
236 vf->close(vf);
237 return YES;
238}
239
240- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
241{
242 struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
243 block(GBASaveStateNamed(context.gba, vf, 0), nil);
244 vf->close(vf);
245}
246
247- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
248{
249 struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
250 block(GBALoadStateNamed(context.gba, vf, 0), nil);
251 vf->close(vf);
252}
253
254#pragma mark - Input
255
256const int GBAMap[] = {
257 GBA_KEY_UP,
258 GBA_KEY_DOWN,
259 GBA_KEY_LEFT,
260 GBA_KEY_RIGHT,
261 GBA_KEY_A,
262 GBA_KEY_B,
263 GBA_KEY_L,
264 GBA_KEY_R,
265 GBA_KEY_START,
266 GBA_KEY_SELECT
267};
268
269- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
270{
271 UNUSED(player);
272 keys |= 1 << GBAMap[button];
273}
274
275- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
276{
277 UNUSED(player);
278 keys &= ~(1 << GBAMap[button]);
279}
280
281#pragma mark - Cheats
282
283- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled
284{
285 code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
286 code = [code stringByReplacingOccurrencesOfString:@" " withString:@""];
287
288 NSString *codeId = [code stringByAppendingFormat:@"/%@", type];
289 struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue];
290 if (cheatSet) {
291 cheatSet->enabled = enabled;
292 return;
293 }
294 cheatSet = malloc(sizeof(*cheatSet));
295 GBACheatSetInit(cheatSet, [codeId UTF8String]);
296 if ([type isEqual:@"GameShark"]) {
297 GBACheatSetGameSharkVersion(cheatSet, 1);
298 } else if ([type isEqual:@"Action Replay"]) {
299 GBACheatSetGameSharkVersion(cheatSet, 3);
300 }
301 NSArray *codeSet = [code componentsSeparatedByString:@"+"];
302 for (id c in codeSet) {
303 GBACheatAddLine(cheatSet, [c UTF8String]);
304 }
305 cheatSet->enabled = enabled;
306 [cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId];
307 GBACheatAddSet(&cheats, cheatSet);
308}
309@end
310