all repos — mgba @ 656260c1299744d1c82cd1d9c4a8d5ec132a05d1

mGBA Game Boy Advance Emulator

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