diff --git a/CMakeLists.txt b/CMakeLists.txt index 595d56068..17b524f4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support") set(BUILD_QT ON CACHE BOOL "Build Qt frontend") set(BUILD_SDL ON CACHE BOOL "Build SDL frontend") set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core") +set(BUILD_OPENEMU OFF CACHE BOOL "Build OpenEmu core") set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_TEST OFF CACHE BOOL "Build testing harness") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") @@ -558,6 +559,21 @@ if(BUILD_LIBRETRO) install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP) endif() +if(BUILD_OPENEMU) + find_library(FOUNDATION Foundation) + find_library(OPENEMUBASE OpenEmuBase) + file(GLOB OE_SRC ${CMAKE_SOURCE_DIR}/src/platform/openemu/*.m) + add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OE_SRC}) + set_target_properties(${BINARY_NAME}-openemu PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/src/platform/openemu/Info.plist.in + BUNDLE TRUE + BUNDLE_EXTENSION oecoreplugin + OUTPUT_NAME ${PROJECT_NAME}EmuCore + COMPILE_DEFINITIONS "DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2") + target_link_libraries(${BINARY_NAME}-openemu ${OS_LIB} ${FOUNDATION} ${OPENEMUBASE}) + install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP) +endif() + if(BUILD_SDL) add_definitions(-DBUILD_SDL) add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl) diff --git a/src/platform/openemu/Info.plist.in b/src/platform/openemu/Info.plist.in new file mode 100644 index 000000000..fffbe7aa4 --- /dev/null +++ b/src/platform/openemu/Info.plist.in @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + mGBA + CFBundleIdentifier + com.endrift.mgba + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + ???? + CFBundleVersion + 1.8.0.1232 + NSPrincipalClass + OEGameCoreController + OEGameCoreClass + mGBAGameCore + OEGameCoreOptions + + openemu.system.gba + + OEGameCorePlayerCount + 1 + OEProjectURL + https://mgba.io/ + OESystemIdentifiers + + openemu.system.gba + + SUEnableAutomaticChecks + 1 + SUFeedURL + https://raw.github.com/OpenEmu/OpenEmu-Update/master/mgba_appcast.xml + + diff --git a/src/platform/openemu/OEGBASystemResponderClient.h b/src/platform/openemu/OEGBASystemResponderClient.h new file mode 100644 index 000000000..4fc0179d1 --- /dev/null +++ b/src/platform/openemu/OEGBASystemResponderClient.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2011, OpenEmu Team + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the OpenEmu Team nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@protocol OESystemResponderClient; + +typedef enum _OEGBAButton +{ + OEGBAButtonUp, + OEGBAButtonDown, + OEGBAButtonLeft, + OEGBAButtonRight, + OEGBAButtonA, + OEGBAButtonB, + OEGBAButtonL, + OEGBAButtonR, + OEGBAButtonStart, + OEGBAButtonSelect, + OEGBAButtonCount +} OEGBAButton; + +@protocol OEGBASystemResponderClient + +- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player; +- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player; + +@end diff --git a/src/platform/openemu/mGBAGameCore.h b/src/platform/openemu/mGBAGameCore.h new file mode 100644 index 000000000..744fdecfe --- /dev/null +++ b/src/platform/openemu/mGBAGameCore.h @@ -0,0 +1,6 @@ +#import +#import + +OE_EXPORTED_CLASS +@interface mGBAGameCore : OEGameCore +@end diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m new file mode 100644 index 000000000..1036d6024 --- /dev/null +++ b/src/platform/openemu/mGBAGameCore.m @@ -0,0 +1,251 @@ +/* + Copyright (c) 2016, Jeffrey Pfau + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "mGBAGameCore.h" + +#include "util/common.h" + +#include "gba/cheats.h" +#include "gba/renderers/video-software.h" +#include "gba/serialize.h" +#include "gba/context/context.h" +#include "util/circle-buffer.h" +#include "util/memory.h" +#include "util/vfs.h" + +#import +#import "OEGBASystemResponderClient.h" +#import + +#define SAMPLES 1024 + +@interface mGBAGameCore () +{ + struct GBAContext context; + struct GBAVideoSoftwareRenderer renderer; + struct GBACheatDevice cheats; + struct GBACheatSet cheatSet; + uint16_t keys; +} +@end + +@implementation mGBAGameCore + +- (id)init +{ + if ((self = [super init])) + { + // TODO: Add a log handler + GBAContextInit(&context, 0); + struct GBAOptions opts = { + .useBios = true, + .idleOptimization = IDLE_LOOP_REMOVE + }; + GBAConfigLoadDefaults(&context.config, &opts); + GBAVideoSoftwareRendererCreate(&renderer); + renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + renderer.outputBufferStride = 256; + context.renderer = &renderer.d; + GBAAudioResizeBuffer(&context.gba->audio, SAMPLES); + GBACheatDeviceCreate(&cheats); + GBACheatAttachDevice(context.gba, &cheats); + GBACheatSetInit(&cheatSet, "openemu"); + GBACheatAddSet(&cheats, &cheatSet); + keys = 0; + } + + return self; +} + +- (void)dealloc +{ + GBAContextDeinit(&context); + GBACheatRemoveSet(&cheats, &cheatSet); + GBACheatDeviceDestroy(&cheats); + GBACheatSetDeinit(&cheatSet); + free(renderer.outputBuffer); + + [super dealloc]; +} + +#pragma mark - Execution + +- (BOOL)loadFileAtPath:(NSString *)path error:(NSError **)error +{ + UNUSED(error); + if (!GBAContextLoadROM(&context, [path UTF8String], true)) { + return NO; + } + GBAContextStart(&context); + return YES; +} + +- (void)executeFrame +{ + GBAContextFrame(&context, keys); + + int16_t samples[SAMPLES * 2]; + size_t available = 0; +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + available = blip_samples_avail(context.gba->audio.left); + blip_read_samples(context.gba->audio.left, samples, available, true); + blip_read_samples(context.gba->audio.right, samples + 1, available, true); +#else +#error BLIP_BUF is required for now +#endif + [[self ringBufferAtIndex:0] write:samples maxLength:available * 4]; +} + +- (void)resetEmulation +{ + ARMReset(context.cpu); +} + +- (void)stopEmulation +{ + NSLog(@"Stopping"); + GBAContextStop(&context); + [super stopEmulation]; +} + +- (void)setupEmulation +{ +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768); + blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768); +#endif +} + +#pragma mark - Video + +- (OEIntSize)aspectSize +{ + return OEIntSizeMake(3, 2); +} + +- (OEIntRect)screenRect +{ + return OEIntRectMake(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); +} + +- (OEIntSize)bufferSize +{ + return OEIntSizeMake(256, VIDEO_VERTICAL_PIXELS); +} + +- (const void *)videoBuffer +{ + return renderer.outputBuffer; +} + +- (GLenum)pixelFormat +{ + return GL_RGBA; +} + +- (GLenum)pixelType +{ + return GL_UNSIGNED_INT_8_8_8_8_REV; +} + +- (GLenum)internalPixelFormat +{ + return GL_RGB8; +} + +- (NSTimeInterval)frameInterval +{ + return GBA_ARM7TDMI_FREQUENCY / (double) VIDEO_TOTAL_LENGTH; +} + +#pragma mark - Audio + +- (NSUInteger)channelCount +{ + return 2; +} + +- (double)audioSampleRate +{ + return 32768; +} + +#pragma mark - Save State + +- (NSData *)serializeStateWithError:(NSError **)outError +{ + UNUSED(outError); + // TODO memory VFile that self-manages memory + return nil; +} + +- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError +{ + UNUSED(outError); + // TODO VFileFromConstMemory + struct VFile* vf = VFileFromMemory((void*) state.bytes, state.length); + return GBALoadStateNamed(context.gba, vf, 0); +} + +- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block +{ + struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR); + block(GBASaveStateNamed(context.gba, vf, 0), nil); +} + +- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block +{ + struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY); + block(GBALoadStateNamed(context.gba, vf, 0), nil); +} + +#pragma mark - Input + +const int GBAMap[] = { + GBA_KEY_UP, + GBA_KEY_DOWN, + GBA_KEY_LEFT, + GBA_KEY_RIGHT, + GBA_KEY_A, + GBA_KEY_B, + GBA_KEY_L, + GBA_KEY_R, + GBA_KEY_START, + GBA_KEY_SELECT +}; + +- (oneway void)didPushGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player +{ + UNUSED(player); + keys |= 1 << GBAMap[button]; +} + +- (oneway void)didReleaseGBAButton:(OEGBAButton)button forPlayer:(NSUInteger)player +{ + UNUSED(player); + keys &= ~(1 << GBAMap[button]); +} + +@end +