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/renderers/video-software.h"
31#include "gba/serialize.h"
32#include "gba/context/context.h"
33#include "util/circle-buffer.h"
34#include "util/memory.h"
35#include "util/vfs.h"
36
37#import <OpenEmuBase/OERingBuffer.h>
38#import "OEGBASystemResponderClient.h"
39#import <OpenGL/gl.h>
40
41#define SAMPLES 1024
42
43@interface mGBAGameCore () <OEGBASystemResponderClient>
44{
45 struct GBAContext context;
46 struct GBAVideoSoftwareRenderer renderer;
47 struct GBACheatDevice cheats;
48 struct GBACheatSet cheatSet;
49 uint16_t keys;
50}
51@end
52
53@implementation mGBAGameCore
54
55- (id)init
56{
57 if ((self = [super init]))
58 {
59 // TODO: Add a log handler
60 GBAContextInit(&context, 0);
61 struct GBAOptions opts = {
62 .useBios = true,
63 .idleOptimization = IDLE_LOOP_REMOVE
64 };
65 GBAConfigLoadDefaults(&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 GBACheatSetInit(&cheatSet, "openemu");
74 GBACheatAddSet(&cheats, &cheatSet);
75 keys = 0;
76 }
77
78 return self;
79}
80
81- (void)dealloc
82{
83 GBAContextDeinit(&context);
84 GBACheatRemoveSet(&cheats, &cheatSet);
85 GBACheatDeviceDestroy(&cheats);
86 GBACheatSetDeinit(&cheatSet);
87 free(renderer.outputBuffer);
88
89 [super dealloc];
90}
91
92#pragma mark - Execution
93
94- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error
95{
96 NSString *batterySavesDirectory = [self batterySavesDirectoryPath];
97 [[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:batterySavesDirectory]
98 withIntermediateDirectories:YES
99 attributes:nil
100 error:nil];
101 if (context.dirs.save) {
102 context.dirs.save->close(context.dirs.save);
103 }
104 context.dirs.save = VDirOpen([batterySavesDirectory UTF8String]);
105
106 if (!GBAContextLoadROM(&context, [path UTF8String], true)) {
107 *error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadROMError userInfo:nil];
108 return NO;
109 }
110
111 if (!GBAContextStart(&context)) {
112 *error = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotStartCoreError userInfo:nil];
113 return NO;
114 }
115 return YES;
116}
117
118- (void)executeFrame
119{
120 GBAContextFrame(&context, keys);
121
122 int16_t samples[SAMPLES * 2];
123 size_t available = 0;
124#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
125 available = blip_samples_avail(context.gba->audio.left);
126 blip_read_samples(context.gba->audio.left, samples, available, true);
127 blip_read_samples(context.gba->audio.right, samples + 1, available, true);
128#else
129#error BLIP_BUF is required for now
130#endif
131 [[self ringBufferAtIndex:0] write:samples maxLength:available * 4];
132}
133
134- (void)resetEmulation
135{
136 ARMReset(context.cpu);
137}
138
139- (void)stopEmulation
140{
141 GBAContextStop(&context);
142 [super stopEmulation];
143}
144
145- (void)setupEmulation
146{
147#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
148 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
149 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
150#endif
151}
152
153#pragma mark - Video
154
155- (OEIntSize)aspectSize
156{
157 return OEIntSizeMake(3, 2);
158}
159
160- (OEIntRect)screenRect
161{
162 return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
163}
164
165- (OEIntSize)bufferSize
166{
167 return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS);
168}
169
170- (const void *)videoBuffer
171{
172 return renderer.outputBuffer;
173}
174
175- (GLenum)pixelFormat
176{
177 return GL_RGBA;
178}
179
180- (GLenum)pixelType
181{
182 return GL_UNSIGNED_INT_8_8_8_8_REV;
183}
184
185- (GLenum)internalPixelFormat
186{
187 return GL_RGB8;
188}
189
190- (NSTimeInterval)frameInterval
191{
192 return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH;
193}
194
195#pragma mark - Audio
196
197- (NSUInteger)channelCount
198{
199 return 2;
200}
201
202- (double)audioSampleRate
203{
204 return 32768;
205}
206
207#pragma mark - Save State
208
209- (NSData *)serializeStateWithError:(NSError **)outError
210{
211 struct VFile* vf = VFileMemChunk(nil, 0);
212 if (!GBASaveStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
213 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
214 vf->close(vf);
215 return nil;
216 }
217 size_t size = vf->size(vf);
218 void* data = vf->map(vf, size, MAP_READ);
219 NSData *nsdata = [NSData dataWithBytes:data length:size];
220 vf->unmap(vf, data, size);
221 vf->close(vf);
222 return nsdata;
223}
224
225- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
226{
227 struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
228 if (!GBALoadStateNamed(context.gba, vf, SAVESTATE_SAVEDATA)) {
229 *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
230 vf->close(vf);
231 return NO;
232 }
233 vf->close(vf);
234 return YES;
235}
236
237- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
238{
239 struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
240 block(GBASaveStateNamed(context.gba, vf, 0), nil);
241 vf->close(vf);
242}
243
244- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
245{
246 struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
247 block(GBALoadStateNamed(context.gba, vf, 0), nil);
248 vf->close(vf);
249}
250
251#pragma mark - Input
252
253const int GBAMap[] = {
254 GBA_KEY_UP,
255 GBA_KEY_DOWN,
256 GBA_KEY_LEFT,
257 GBA_KEY_RIGHT,
258 GBA_KEY_A,
259 GBA_KEY_B,
260 GBA_KEY_L,
261 GBA_KEY_R,
262 GBA_KEY_START,
263 GBA_KEY_SELECT
264};
265
266- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
267{
268 UNUSED(player);
269 keys |= 1 << GBAMap[button];
270}
271
272- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player
273{
274 UNUSED(player);
275 keys &= ~(1 << GBAMap[button]);
276}
277
278@end
279