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