diff --git a/CMakeLists.txt b/CMakeLists.txt index 394799dc..79678cf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,13 @@ if( NOT ENABLE_DEBUGGER AND ENABLE_SDL ) message( SEND_ERROR "The SDL port can't be built without debugging support" ) endif( NOT ENABLE_DEBUGGER AND ENABLE_SDL ) +if ( PNACL ) + set ( ENABLE_SDL ON ) + set ( ENABLE_GTK OFF ) + set ( ENABLE_LINK OFF ) + set ( ENABLE_WX OFF ) +endif ( PNACL ) + # Set the version number with -DVERSION=X.X.X-uber IF( NOT VERSION ) SET( VERSION "https://github.com/EmperorArthur/VBA-M" ) @@ -71,7 +78,7 @@ endif( ENABLE_ASM_SCALERS ) # Look for some dependencies using CMake scripts FIND_PACKAGE ( ZLIB REQUIRED ) FIND_PACKAGE ( PNG REQUIRED ) -FIND_PACKAGE ( OpenGL REQUIRED ) +FIND_PACKAGE ( OpenGL ) FIND_PACKAGE ( SDL REQUIRED ) if( ENABLE_LINK ) @@ -87,7 +94,10 @@ SET(VBAMCORE_LIBS ${ZLIB_LIBRARY} ${PNG_LIBRARY}) - +if ( PNACL ) + # Add core NaCl libraries. + SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ppapi ppapi_cpp nacl_io) +endif ( PNACL ) # Disable looking for GTK if not going to build the GTK frontend # so that pkg-config is not required @@ -131,8 +141,12 @@ IF( NOT SYSCONFDIR ) SET( SYSCONFDIR "/etc" ) ENDIF( NOT SYSCONFDIR ) +if ( OpenGL_FOUND ) + set( USE_OPENGL_FLAG "-DUSE_OPENGL " ) +endif ( OpenGL_FOUND ) + # C defines -ADD_DEFINITIONS (-DHAVE_NETINET_IN_H -DHAVE_ARPA_INET_H -DHAVE_ZLIB_H -DFINAL_VERSION -DSDL -DUSE_OPENGL -DSYSCONFDIR='"${SYSCONFDIR}"' -DWITH_LIRC='${WITHLIRC}') +ADD_DEFINITIONS (-DHAVE_NETINET_IN_H -DHAVE_ARPA_INET_H -DHAVE_ZLIB_H -DFINAL_VERSION -DSDL ${USE_OPENGL_FLAG} -DSYSCONFDIR='"${SYSCONFDIR}"' -DWITH_LIRC='${WITHLIRC}') ADD_DEFINITIONS (-DVERSION='"${VERSION}"' -DPKGDATADIR='"${PKGDATADIR}"' -DPACKAGE='') if( ENABLE_LINK ) @@ -142,13 +156,13 @@ if( ENABLE_LINK ) SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${RT_LIB}) SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${RT_LIB}) ENDIF(RT_LIB) - + FIND_LIBRARY(PTHREAD_LIB pthread) IF(PTHREAD_LIB) SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${PTHREAD_LIB}) SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${PTHREAD_LIB}) ENDIF(PTHREAD_LIB) - + INCLUDE(CheckFunctionExists) CHECK_FUNCTION_EXISTS(sem_timedwait SEM_TIMEDWAIT) IF( SEM_TIMEDWAIT) @@ -304,6 +318,10 @@ SET(SRC_SDL src/sdl/expr-lex.cpp ) +SET(SRC_PNACL + src/pnacl/nacl_glue.cpp +) + SET(SRC_FILTERS src/filters/2xSaI.cpp src/filters/admame.cpp @@ -376,8 +394,8 @@ IF( ENABLE_SDL ) vbam WIN32 ${SRC_SDL} + ${SRC_PNACL} ) - IF( WIN32 ) SET( WIN32_LIBRARIES wsock32 ) ENDIF( WIN32 ) @@ -392,11 +410,14 @@ IF( ENABLE_SDL ) ${WIN32_LIBRARIES} ${LIRC_CLIENT_LIBRARY} ) - - INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vbam DESTINATION bin) - INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/sdl/vbam.cfg-example - DESTINATION ${SYSCONFDIR} - RENAME vbam.cfg) + IF ( PNACL ) + build_to_app( vbam ) + ELSE ( PNACL ) + INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vbam DESTINATION bin) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/sdl/vbam.cfg-example + DESTINATION ${SYSCONFDIR} + RENAME vbam.cfg) + ENDIF ( PNACL ) ENDIF( ENABLE_SDL ) IF( ENABLE_GTK ) diff --git a/CMakeScripts/PNaCl.Toolchain.cmake b/CMakeScripts/PNaCl.Toolchain.cmake new file mode 100644 index 00000000..6bd1c659 --- /dev/null +++ b/CMakeScripts/PNaCl.Toolchain.cmake @@ -0,0 +1,56 @@ +# How to build for PNaCl on Linux. +# +# Download the Native Client SDK. +# Run naclsdk update pepper_XX. The pepper version must be pepper_37. +# Set NACL_SDK_ROOT in your environment to nacl_sdk/pepper_XX. +# +# Check out the naclports repository. This was built with +# naclports@b0aaa9899316e157883ee4b54f70affbebd2c3e1. +# Set NACL_ARCH to 'pnacl' in your environment and run 'make sdl' +# from the naclports root. +# +# Come back to the root of vba-m and run +# cmake -DCMAKE_TOOLCHAIN_FILE=CMakeScripts/PNaCl.Toolchain.cmake CMakeLists.txt +# make +# +# This will build a non-finalized PNaCl port to ./vbam and a finalized version +# to src/pnacl/app/vbam.pexe. +# +# The src/pnacl/app folder can be loaded as an unpacked extension into Chrome +# which will run vba-m as a packaged app. + +include( CMakeForceCompiler ) + +set( PNACL ON ) +set( PLATFORM_PREFIX "$ENV{NACL_SDK_ROOT}/toolchain/linux_pnacl" ) +set( FINALIZED_TARGET "src/pnacl/app/vbam.pexe" ) + +set( CMAKE_SYSTEM_NAME "Linux" CACHE STRING "Target system." ) +set( CMAKE_SYSTEM_PROCESSOR "LLVM-IR" CACHE STRING "Target processor." ) +set( CMAKE_FIND_ROOT_PATH "${PLATFORM_PREFIX}/usr" ) +set( CMAKE_AR "${PLATFORM_PREFIX}/bin64/pnacl-ar" CACHE STRING "") +set( CMAKE_RANLIB "${PLATFORM_PREFIX}/bin64/pnacl-ranlib" CACHE STRING "") +set( CMAKE_C_COMPILER "${PLATFORM_PREFIX}/bin64/pnacl-clang" ) +set( CMAKE_CXX_COMPILER "${PLATFORM_PREFIX}/bin64/pnacl-clang++" ) +set( CMAKE_C_FLAGS "-Wno-non-literal-null-conversion -Wno-deprecated-writable-strings -U__STRICT_ANSI__" CACHE STRING "" ) +set( CMAKE_CXX_FLAGS "-Wno-non-literal-null-conversion -Wno-deprecated-writable-strings -U__STRICT_ANSI__" CACHE STRING "" ) + +cmake_force_c_compiler( ${CMAKE_C_COMPILER} Clang ) +cmake_force_cxx_compiler( ${CMAKE_CXX_COMPILER} Clang ) + +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY ) + +macro( build_to_app _target ) + add_custom_command( TARGET ${_target} + POST_BUILD + COMMAND "${PLATFORM_PREFIX}/bin64/pnacl-finalize" + "-o" "${FINALIZED_TARGET}" + "$" ) +endmacro() + +set( ENV{SDLDIR} "${PLATFORM_PREFIX}/usr" ) +include_directories( SYSTEM $ENV{NACL_SDK_ROOT}/include ) +link_directories( $ENV{NACL_SDK_ROOT}/lib/pnacl/Release ) diff --git a/src/common/Patch.cpp b/src/common/Patch.cpp index ac4893a7..9b8ccb6e 100644 --- a/src/common/Patch.cpp +++ b/src/common/Patch.cpp @@ -7,13 +7,13 @@ #ifdef __GNUC__ -#if defined(__APPLE__) || defined (BSD) || defined (__NetBSD__) +#if defined(__APPLE__) || defined (BSD) || defined (__NetBSD__) || defined (__native_client__) typedef off_t __off64_t; /* off_t is 64 bits on BSD. */ #define fseeko64 fseeko #define ftello64 ftello #else typedef off64_t __off64_t; -#endif /* __APPLE__ || BSD */ +#endif /* __APPLE__ || BSD || __native_client__ */ #endif /* __GNUC__ */ #ifndef _MSC_VER diff --git a/src/gba/GBAcpu.h b/src/gba/GBAcpu.h index 3b22e007..6687f267 100644 --- a/src/gba/GBAcpu.h +++ b/src/gba/GBAcpu.h @@ -5,7 +5,7 @@ extern int armExecute(); extern int thumbExecute(); #ifdef __GNUC__ -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__native_client__) # define INSN_REGPARM __attribute__((regparm(1))) #else # define INSN_REGPARM /*nothing*/ diff --git a/src/pnacl/app/background.js b/src/pnacl/app/background.js new file mode 100644 index 00000000..e9d8ab4c --- /dev/null +++ b/src/pnacl/app/background.js @@ -0,0 +1,11 @@ +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('game.html', { + bounds: { + width: 480, + height: 320 + }, + resizable: true, + minWidth: 480, + minHeight: 320 + }); +}); diff --git a/src/pnacl/app/game.html b/src/pnacl/app/game.html new file mode 100644 index 00000000..d9b0f419 --- /dev/null +++ b/src/pnacl/app/game.html @@ -0,0 +1,63 @@ + + + + + + +
+ +
+
+
Translating PNaCl to Native Architecture
+
+
+
+
+
+
Press Ctrl+O to open a rom
+
+ The filename cannot have spaces. Please rename the file and try again. +
+
+
+
+
+
Advanced
+
+
Import save
+
+ +
+
+
+
+
A: Z
+
B: X
+
L: A
+
R: S
+
D-pad: Arrow keys
+
+
+
Start: Enter
+
Select: Backspace
+
+
Save state: Shift + F1-9
+
Load state: F1-9
+
+
+
Save state: Shift + Search(🔍) + 1-9
+
Load state: Search(🔍) + 1-9
+
+
Vol down/up: -/=
+
+ + + + diff --git a/src/pnacl/app/game.nmf b/src/pnacl/app/game.nmf new file mode 100644 index 00000000..0c537963 --- /dev/null +++ b/src/pnacl/app/game.nmf @@ -0,0 +1,10 @@ +{ + "program": { + "portable" : { + "pnacl-translate" : { + "url" : "vbam.pexe", + "optlevel": 2 + } + } + } +} diff --git a/src/pnacl/app/icon.png b/src/pnacl/app/icon.png new file mode 100644 index 00000000..af50b544 Binary files /dev/null and b/src/pnacl/app/icon.png differ diff --git a/src/pnacl/app/index.js b/src/pnacl/app/index.js new file mode 100644 index 00000000..788b11be --- /dev/null +++ b/src/pnacl/app/index.js @@ -0,0 +1,237 @@ +var plugin = document.getElementById('naclModule'); +var firstLoad = false; +var naclVisible = false; +var localfs = null; +var syncfs = null; +var saveDirEntry = null; +var gamepadCheckbox = document.getElementById('gamepadCheckbox'); + +var postFileCallback = function() {}; + +function setFilenameErrorVisible(visible) { + document.getElementById('filenameErrorMessage').style.display = + visible ? 'block' : 'none'; +} + +function setHelptextsVisible(visible) { + var helptexts = document.getElementsByClassName('helptext'); + for (var i = 0; i < helptexts.length; i++) { + helptexts[i].style.display = visible ? 'block' : 'none'; + } +} + +function setNaclVisible(visible) { + naclVisible = visible; + document.getElementsByTagName('body')[0].className = + visible ? 'lightsOff' : 'lightsOn'; + setHelptextsVisible(!visible); + scaleNacl(); +} + +function scaleNacl() { + var bounds = chrome.app.window.current().getBounds(); + var scaleX = bounds.width / plugin.width; + var scaleY = bounds.height / plugin.height; + var scale = naclVisible ? Math.min(scaleX, scaleY) : 0; + plugin.style.webkitTransform = 'scale(' + scale + ')'; +} + +function makeNewPlugin() { + var newNode = plugin.cloneNode(true); + plugin.parentNode.appendChild(newNode); + plugin.parentNode.removeChild(plugin); + plugin = document.getElementById('naclModule'); + setNaclVisible(false); +} + +function showOpenFileDialog() { + setFilenameErrorVisible(false); + chrome.fileSystem.chooseEntry({ + 'type': 'openFile', + 'accepts': [{'description': 'GBA Roms', 'extensions': ['zip', 'gba']}] + }, + function(entry) { + if (!entry) + return; + + if (entry.fullPath.indexOf(' ') != -1) { + setFilenameErrorVisible(true); + return; + } + + postFileCallback = function() { + setNaclVisible(true); + plugin.postMessage({ + 'path': entry.fullPath, + 'filesystem': entry.filesystem, + 'gamepad' : gamepadCheckbox.checked + }); + postFileCallback = function() {}; + }; + makeNewPlugin(); + }); +} + +function copyToSyncFileSystem() { + if (!syncfs || !localfs) { + setTimeout(copyToSyncFileSystem, 1000); + return; + } + + reader = saveDirEntry.createReader(); + var copyIfNewer = function(entries) { + if (entries.length == 0) + return; + + for (i in entries) { + var entry = entries[i]; + if (!entry.isFile) + return; + (function() { + var e = entry; + var doCopy = function() { + console.log('Upsyncing ' + e.name); + e.copyTo(syncfs.root); + }; + e.getMetadata(function(localMeta) { + syncfs.root.getFile(e.name, {}, function(syncEntry) { + syncEntry.getMetadata(function(syncMeta) { +// syncEntry.remove(function() { + if (syncMeta.modificationTime < localMeta.modificationTime) + doCopy(); +// }); + }, doCopy); + }, doCopy); + }); + })(); + } + reader.readEntries(copyIfNewer); + }; + reader.readEntries(copyIfNewer); + + setTimeout(copyToSyncFileSystem, 30000); +} + +function initSyncFileSystem() { + syncfs = null; + chrome.syncFileSystem.getServiceStatus(function(status) { + if (status == 'running') { + chrome.syncFileSystem.requestFileSystem(function(fs) { + syncfs = fs; + }); + } else { + setTimeout(initSyncFileSystem, 5000); + } + }); +} + +window.webkitRequestFileSystem(window.PERSISTENT, 0, function(fs) { + localfs = fs; + fs.root.getDirectory( + 'states', { create: true }, function(d) { saveDirEntry = d; }); +}); + +initSyncFileSystem(); +copyToSyncFileSystem(); +chrome.syncFileSystem.onServiceStatusChanged.addListener(initSyncFileSystem); +chrome.syncFileSystem.onFileStatusChanged.addListener(function(detail) { + /* + if (saveDirEntry && detail.status == 'synced' && + detail.direction == 'remote_to_local') { + if (detail.action == 'deleted') { + saveDirEntry.getFile(detail.fileEntry.name, {}, function(entry) { + console.log('Deleting' + detail.fileEntry.name); + entry.remove(function() {}); + }); + detail.fileEntry.remove(function() {}); + } else { + detail.fileEntry.copyTo(saveDirEntry); + console.log('Downsyncing ' + detail.fileEntry.name); + } + }*/ +}); + +var is_cros = window.navigator.userAgent.toLowerCase().indexOf('cros') != -1; +document.getElementById('normalSaveStateKeys').style.display = is_cros ? 'none' : 'block'; +document.getElementById('crosSaveStateKeys').style.display = is_cros ? 'block' : 'none'; + +document.getElementById('importSaveButton').onclick = function() { + chrome.fileSystem.chooseEntry({ + 'type': 'openFile', + 'accepts': [{'description': 'VBA-M Save Files', 'extensions': ['sav', 'sgm']}] + }, + function(entry) { + if (!entry) + return; + + if (entry.fullPath.indexOf(' ') != -1) { + setFilenameErrorVisible(true); + return; + } + + entry.copyTo(saveDirEntry); + console.log('Copied ' + entry.name + ' to HTML5fs'); + }); +}; + +// Gamepad checkbox. +gamepadCheckbox.onclick = function() { + chrome.storage.local.set({'gamepad': gamepadCheckbox.checked}); +} +chrome.storage.local.get('gamepad', function(items) { + gamepadCheckbox.checked = items['gamepad']; +}); + +// Advanced settings +document.getElementById('advancedButton').onclick = function() { + document.getElementById('advancedButton').style.display = 'none'; + var advancedSettings = document.getElementsByClassName('advancedSetting'); + for (var i = 0; i < advancedSettings.length; i++) + advancedSettings[i].style.display = 'block'; +}; + +// Add window scaling. +chrome.app.window.current().onBoundsChanged.addListener(scaleNacl); + +// NaCl plugin listener +var listener = document.getElementById('listener'); +listener.addEventListener( + 'message', + function(e) { + console.log(e); + }, true); + +listener.addEventListener( + 'crash', + function(e) { + makeNewPlugin(); + }, true); + +listener.addEventListener( + 'load', + function(e) { + setHelptextsVisible(true); + document.getElementById('loadingMessage').style.display = 'none'; + postFileCallback(); + firstLoad = true; + scaleNacl(); + }, true); + +listener.addEventListener( + 'progress', + function(e) { + var percent = e.loaded / e.total; + document.getElementById('progress').style.width = (percent * 100) + '%'; + }, true); + +document.addEventListener('keydown', function(e) { + // Ctrl + o + if (firstLoad && e.ctrlKey && e.keyCode == 79) { + showOpenFileDialog(); + } + // ESC + if (e.keyCode == 27) { + // makeNewPlugin(); + e.preventDefault(); + } +}, true); diff --git a/src/pnacl/app/manifest.json b/src/pnacl/app/manifest.json new file mode 100644 index 00000000..e8eb6412 --- /dev/null +++ b/src/pnacl/app/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "VBA-M", + "description": "A native GBA emulator for Chrome", + "version": "1.3.6", + "manifest_version": 2, + "minimum_chrome_version": "34", + "offline_enabled": true, + "icons": { + "128": "icon.png" + }, + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "permissions": [ + "syncFileSystem", + "fileSystem", + "storage", + "unlimitedStorage" + ] +} diff --git a/src/pnacl/app/style.css b/src/pnacl/app/style.css new file mode 100644 index 00000000..cab7f077 --- /dev/null +++ b/src/pnacl/app/style.css @@ -0,0 +1,89 @@ +/* Structure */ + +html { + height: 100%; +} + +body { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + margin: 0; +} + +.lightsOn { + background-color: rgb(251, 251, 251); +} + +.lightsOff { + background-color: black; +} + +.centered { + left: 0; + position: absolute; + text-align: center; + top: 40%; + width: 100%; + z-index: -1; +} + +.helptext { + display: none; +} + +.errorMessage { + display: none; + color: red; +} + +#loadingBar { + background-color: black; + border-radius: 13px; + margin: 0 auto; + padding: 3px; + width: 200px; +} + +#loadingBar > div { + background-color: blue; + border-radius: 10px; + height: 10px; + width: 0; +} + +#naclModule { + -webkit-transform-origin: 50% 50%; +} + +#leftHelptext { + bottom: 5px; + left: 5px; + position: fixed; +} + +#rightHelptext { + bottom: 5px; + position: fixed; + right: 5px; + text-align: right; +} + +#topLeftHelptext { + left: 5px; + position: fixed; + text-align: left; + top: 5px; +} + +#topRightHelptext { + top: 5px; + position: fixed; + right: 5px; + text-align: right; +} + +.advancedSetting { + display: none; +} diff --git a/src/pnacl/app/vbam.cfg b/src/pnacl/app/vbam.cfg new file mode 100644 index 00000000..4b29df00 --- /dev/null +++ b/src/pnacl/app/vbam.cfg @@ -0,0 +1,5 @@ +captureDir=/store/states +saveDir=/store/states +batteryDir=/store/states +showSpeed=2 +rtcEnabled=1 diff --git a/src/pnacl/nacl_glue.cpp b/src/pnacl/nacl_glue.cpp new file mode 100644 index 00000000..44d1f805 --- /dev/null +++ b/src/pnacl/nacl_glue.cpp @@ -0,0 +1,300 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUM_BUTTONS 17 +#define AXIS_MAX 32767 + +extern int SDL_main(int argc, char **argv); + +class GameInstance : public pp::Instance { + public: + explicit GameInstance(PP_Instance instance) + : pp::Instance(instance), + game_main_thread_(NULL), + num_changed_view_(0), + width_(0), + height_(0), + gamepad_enabled_(false), + cc_factory_(this) { + // Game requires mouse and keyboard events; add more if necessary. + nacl_io_init_ppapi(instance, pp::Module::Get()->get_browser_interface()); + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | + PP_INPUTEVENT_CLASS_KEYBOARD); + ++num_instances_; + assert(num_instances_ == 1); + + gamepad_ = static_cast( + pp::Module::Get()->GetBrowserInterface(PPB_GAMEPAD_INTERFACE)); + assert(gamepad_); + } + + virtual ~GameInstance() { + // Wait for game thread to finish. + if (game_main_thread_) { + pthread_join(game_main_thread_, NULL); + } + } + + // This function is called with the HTML attributes of the embed tag, + // which can be used in lieu of command line arguments. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + for (uint32_t i = 0; i < argc; ++i) { + if (argn[i] == std::string("width")) { + width_ = strtol(argv[i], 0, 0); + } + + if (argn[i] == std::string("height")) { + height_ = strtol(argv[i], 0, 0); + } + } + return true; + } + + // This crucial function forwards PPAPI events to SDL. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + SDL_NACL_PushEvent(event.pp_resource()); + return true; + } + + virtual void HandleMessage(const pp::Var& message) { + pp::VarDictionary args(message); + pp::Var filesystem_var = args.Get("filesystem"); + if (!filesystem_var.is_resource()) + return; + + pp::Resource filesystem_resource = filesystem_var.AsResource(); + if (!pp::FileSystem::IsFileSystem(filesystem_resource)) + return; + + PP_Resource filesystem_id = filesystem_resource.pp_resource(); + if (!filesystem_id) + return; + + char buf[BUFSIZ]; + if (snprintf(buf, BUFSIZ, "type=PERSISTENT,filesystem_resource=%d", filesystem_id) > BUFSIZ) + return; + + rom_mount_string_ = buf; + + pp::Var path_var = args.Get("path"); + if (!path_var.is_string()) + return; + + rom_path_string_ = path_var.AsString(); + + pp::Var gamepad_var = args.Get("gamepad"); + if (!gamepad_var.is_bool()) + return; + + gamepad_enabled_ = gamepad_var.AsBool(); + } + + // This function is called for various reasons, e.g. visibility and page + // size changes. We ignore these calls except for the first + // invocation, which we use to start the game. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + ++num_changed_view_; + if (num_changed_view_ > 1) return; + // NOTE: It is crucial that the two calls below are run here + // and not in a thread. + SDL_NACL_SetInstance(pp_instance(), + pp::Module::Get()->get_browser_interface(), + width_, + height_); + // This is SDL_Init call which used to be in game_main() + int flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | + SDL_INIT_NOPARACHUTE | SDL_INIT_JOYSTICK; + if(SDL_Init(flags)) + exit(-1); + + StartGameInNewThread(0); + } + private: + static int num_instances_; // Ensure we only create one instance. + static bool old_pressed_[NUM_BUTTONS]; + + pthread_t game_main_thread_; // This thread will run game_main(). + int num_changed_view_; // Ensure we initialize an instance only once. + // Dimension of the SDL video screen. + int width_; + int height_; + pp::CompletionCallbackFactory cc_factory_; + bool gamepad_enabled_; + const PPB_Gamepad* gamepad_; + + std::string rom_mount_string_; + std::string rom_path_string_; + + static SDL_Event *copyEvent(SDL_Event& event) { + SDL_Event *event_copy = (SDL_Event*)malloc(sizeof(SDL_Event)); + *event_copy = event; + return event_copy; + } + + int GetSDLButton(int i) { + switch (i) { + case 2: + return 1; + case 3: + return 0; + case 4: + return 6; + case 5: + return 7; + } + return i; + } + + void GamepadInputLoop(int32_t dummy) { + PP_GamepadsSampleData gamepad_data; + gamepad_->Sample(pp_instance(), &gamepad_data); + if (gamepad_data.length) { + PP_GamepadSampleData& pad = gamepad_data.items[0]; + if (pad.connected) { + // Draw axes. + SDL_Event e; + bool pressed[NUM_BUTTONS] = {0}; + for (int i = 0; i < pad.buttons_length; i++) + pressed[i] = pad.buttons[i] > 0.3 || pressed[i]; + + if (old_pressed_[16] != pressed[16]) { + } + for (int i = 0; i < NUM_BUTTONS; i++) { + if (pressed[i] != old_pressed_[i]) { + e.type = SDL_JOYBUTTONDOWN; + e.jbutton.which = 0; + e.jbutton.button = GetSDLButton(i); + e.jbutton.state = pressed[i] ? SDL_PRESSED : SDL_RELEASED; + SDL_PushEvent(copyEvent(e)); + old_pressed_[i] = pressed[i]; + // Send keyboard SPACE instead of button 16. + if (i == 16) { + e.type = pressed[i] ? SDL_KEYDOWN : SDL_KEYUP; + e.key.type = pressed[i] ? SDL_KEYDOWN : SDL_KEYUP; + e.key.which = 1; + e.key.state = pressed[i] ? SDL_PRESSED : SDL_RELEASED; + e.key.keysym.scancode = SDLK_SPACE; + e.key.keysym.sym = SDLK_SPACE; + SDL_PushEvent(copyEvent(e)); + } + } + } + + e.type = SDL_JOYAXISMOTION; + e.jaxis.which = 0; + for (int i = 0; i < pad.axes_length; i++) { + e.jaxis.type = SDL_JOYAXISMOTION; + e.jaxis.axis = i; + e.jaxis.value = pad.axes[i] * AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[12]) { + e.jaxis.axis = 1; + e.jaxis.value = -AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[14]) { + e.jaxis.axis = 0; + e.jaxis.value = -AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[13]) { + e.jaxis.axis = 1; + e.jaxis.value = AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[15]) { + e.jaxis.axis = 0; + e.jaxis.value = AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + } + } + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::GamepadInputLoop), 0); + } + + void StartGameInNewThread(int32_t dummy) { + if (!rom_mount_string_.empty() && !rom_path_string_.empty()) { + fprintf(stderr, "Mounting %s with %s\n", rom_path_string_.c_str(), + rom_mount_string_.c_str()); + pthread_create(&game_main_thread_, NULL, &LaunchGame, this); + if (gamepad_enabled_) { + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::GamepadInputLoop), 0); + } + } else { + // Wait some more (here: 100ms). + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::StartGameInNewThread), 0); + } + } + + static void* LaunchGame(void* data) { + // Use "thiz" to get access to instance object. + GameInstance* thiz = reinterpret_cast(data); + umount("/"); + mount("", "/", "memfs", 0, ""); + + mkdir("/config", 0777); + fprintf(stderr, "mount httpfs = %d\n", mount("/", "/config", "httpfs", 0, "")); + + mkdir("/games", 0777); + fprintf(stderr, "mount rom = %d\n", mount("/", "/games", "html5fs", 0, thiz->rom_mount_string_.c_str())); + + mkdir("/store", 0777); + fprintf(stderr, "mount html5fs = %d\n", mount("/", "/store", "html5fs", 0, "")); + mkdir("/store/states", 0777); + mkdir("/store/captures", 0777); + // Craft a fake command line. + char rom_path[BUFSIZ]; + strncpy(rom_path, ("/games" + thiz->rom_path_string_).c_str(), BUFSIZ); + std::string config_string("--config=/config/vbam.cfg"); + if (thiz->gamepad_enabled_) + config_string = "--config=/config/vbam-gamepad.cfg"; + + char config[BUFSIZ]; + strncpy(config, config_string.c_str(), BUFSIZ); + char* argv[] = { "vbam", config, rom_path, NULL}; + SDL_main(3, argv); + return 0; + } +}; + +int GameInstance::num_instances_; +bool GameInstance::old_pressed_[]; + +class GameModule : public pp::Module { + public: + GameModule() : pp::Module() {} + virtual ~GameModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new GameInstance(instance); + } +}; + +namespace pp { +Module* CreateModule() { + return new GameModule(); +} + +} // namespace pp diff --git a/src/sdl/SDL.cpp b/src/sdl/SDL.cpp index 68cafd0e..782045bd 100644 --- a/src/sdl/SDL.cpp +++ b/src/sdl/SDL.cpp @@ -23,6 +23,7 @@ #include #include #include +#ifdef USE_OPENGL #ifdef __APPLE__ #include #include @@ -30,6 +31,7 @@ #include #include #endif +#endif #include @@ -134,9 +136,13 @@ int sdlPrintUsage = 0; int cartridgeType = 3; int captureFormat = 0; -int openGL = 1; int textureSize = 256; +#ifdef USE_OPENGL +int openGL = 1; GLuint screenTexture = 0; +#else +int openGL = 0; +#endif u8 *filterPix = 0; int pauseWhenInactive = 0; @@ -810,6 +816,7 @@ void sdlReadPreferences(FILE *f) void sdlOpenGLInit(int w, int h) { +#ifdef USE_OPENGL float screenAspect = (float) srcWidth / srcHeight, windowAspect = (float) w / h; @@ -861,6 +868,7 @@ void sdlOpenGLInit(int w, int h) glClearColor(0.0,0.0,0.0,1.0); glClear( GL_COLOR_BUFFER_BIT ); +#endif } void sdlReadPreferences() @@ -1277,6 +1285,9 @@ static void sdlHandleSavestateKey(int num, int shifted) // 0: load // 1: save int backuping = 1; // controls whether we are doing savestate backups +#ifdef __native_client__ + backuping = 0; +#endif if ( sdlSaveKeysSwitch == 2 ) { @@ -1475,9 +1486,11 @@ void sdlPollEvents() } break; case SDLK_KP_DIVIDE: + case SDLK_MINUS: sdlChangeVolume(-0.1); break; case SDLK_KP_MULTIPLY: + case SDLK_EQUALS: sdlChangeVolume(0.1); break; case SDLK_KP_MINUS: @@ -1543,15 +1556,19 @@ void sdlPollEvents() case SDLK_g: if(!(event.key.keysym.mod & MOD_NOCTRL) && (event.key.keysym.mod & KMOD_CTRL)) { - filterFunction = 0; - while (!filterFunction) - { - filter = (Filter)((filter + 1) % kInvalidFilter); - filterFunction = initFilter(filter, systemColorDepth, srcWidth); - } - if (getFilterEnlargeFactor(filter) != filter_enlarge) - sdlInitVideo(); - systemScreenMessage(getFilterName(filter)); + filterFunction = 0; + while (!filterFunction) { + filter = (Filter)((filter + 1) % kInvalidFilter); +#ifdef __native_client__ + if (getFilterEnlargeFactor(filter) != filter_enlarge) + continue; +#endif + filterFunction = initFilter(filter, systemColorDepth, srcWidth); + } + if (getFilterEnlargeFactor(filter) != filter_enlarge) { + sdlInitVideo(); + } + systemScreenMessage(getFilterName(filter)); } break; case SDLK_F11: @@ -2486,6 +2503,7 @@ void systemDrawScreen() drawSpeed(screen, destPitch, 10, 20); if (openGL) { +#ifdef USE_OPENGL glClear( GL_COLOR_BUFFER_BIT ); glPixelStorei(GL_UNPACK_ROW_LENGTH, destWidth); if (systemColorDepth == 16) @@ -2508,6 +2526,7 @@ void systemDrawScreen() glEnd(); SDL_GL_SwapBuffers(); +#endif } else { SDL_UnlockSurface(surface); SDL_Flip(surface); diff --git a/src/sdl/inputSDL.cpp b/src/sdl/inputSDL.cpp index e0ba0ca5..1360e91b 100644 --- a/src/sdl/inputSDL.cpp +++ b/src/sdl/inputSDL.cpp @@ -85,7 +85,7 @@ static int sensorY = 2047; static uint32_t sdlGetHatCode(const SDL_Event &event) { if (!event.jhat.value) return 0; - + return ( ((event.jhat.which + 1) << 16) | 32 | @@ -249,7 +249,6 @@ static void sdlUpdateJoyButton(int which, int b = joypad[j][i] & 0xffff; if(dev) { dev--; - if((dev == which) && (b >= 128) && (b == (button+128))) { sdlButtons[j][i] = pressed; } @@ -399,7 +398,11 @@ void inputInitJoysticks() joypad[PAD_MAIN][i] = joypad[PAD_DEFAULT][i]; } +#if defined (__native_client__) + sdlNumDevices = 1; +#else sdlNumDevices = SDL_NumJoysticks(); +#endif if(sdlNumDevices) sdlDevices = (SDL_Joystick **)calloc(1,sdlNumDevices * @@ -418,8 +421,11 @@ void inputInitJoysticks() if(sdlDevices[dev] == NULL) { sdlDevices[dev] = SDL_JoystickOpen(dev); } - +#if defined (__native_client__) + ok = true; // Force gamepad to be useable. +#else ok = sdlCheckJoyKey(joypad[j][i]); +#endif } else ok = false; }