diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b605d45b..91d3b8f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,15 +20,25 @@ header_directories(${PROJECT}_INCLUDES) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/extras/GitSHA1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp" @ONLY) list(APPEND ${PROJECT}_SOURCES "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp") -add_executable(${EXECUTABLE} WIN32 - ${${PROJECT}_SOURCES} -) +if(NOT ANDROID) + add_executable(${EXECUTABLE} WIN32 ${${PROJECT}_SOURCES}) +elseif(ANDROID) + add_library(${EXECUTABLE} SHARED ${${PROJECT}_SOURCES}) +endif() target_link_libraries(${EXECUTABLE} PRIVATE librw::librw Threads::Threads ) +if(ANDROID) + target_link_libraries(${EXECUTABLE} PRIVATE + log + GLESv3 + OpenSLES + ) +endif() + target_include_directories(${EXECUTABLE} PRIVATE $ @@ -49,8 +59,29 @@ if(LIBRW_PLATFORM_D3D9) ) endif() -target_compile_definitions(${EXECUTABLE} PRIVATE CMAKE_BUILD) -target_compile_definitions(${EXECUTABLE} PRIVATE USE_OUR_VERSIONING) +if(ANDROID) + target_compile_definitions(${EXECUTABLE} PRIVATE + ANDROID + _ANDROID + LINUX + _LINUX + POSIX + _POSIX + NO_HOOK_MALLOC + AL_LIBTYPE_STATIC + GNUC + RW_GL3 + LIBRW_SDL2 + LIBRW + AUDIO_OAL + USE_OUR_VERSIONING + HAVE_EFFICIENT_UNALIGNED_ACCESS + VECTORIZE_SINCOS + ) +else() + target_compile_definitions(${EXECUTABLE} PRIVATE CMAKE_BUILD) + target_compile_definitions(${EXECUTABLE} PRIVATE USE_OUR_VERSIONING) +endif() if(${PROJECT}_AUDIO STREQUAL "OAL") find_package(OpenAL REQUIRED) @@ -91,6 +122,12 @@ target_compile_definitions(${EXECUTABLE} PRIVATE ) option(${PROJECT}_WITH_SANITIZERS "Use UB sanitizers (better crash log)" OFF) option(${PROJECT}_WITH_ASAN "Use Address sanitizer (better crash log)" OFF) +option(${PROJECT}_DISABLE_WARNINGS "Disable build warnings" ON) + +if(${PROJECT}_DISABLE_WARNINGS) + target_compile_options(${EXECUTABLE} PRIVATE + -w) +endif() if(${PROJECT}_WITH_SANITIZERS) target_compile_options(${EXECUTABLE} PUBLIC @@ -105,6 +142,26 @@ if(${PROJECT}_WITH_ASAN) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + if(ANDROID) + target_compile_options(${EXECUTABLE} + PRIVATE + -pthread + -std=c++11 + -fno-strict-aliasing + -fexceptions + -Wcast-align + -Wuninitialized + -Winit-self + -Wstrict-aliasing + -Wno-reorder + -Wno-unknown-pragmas + -Wno-unused-function + -Wno-unused-but-set-variable + -Wno-unused-value + -Wno-unused-command-line-argument + -faligned-new + ) + endif() target_compile_options(${EXECUTABLE} PRIVATE "-Wall" diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp index bea527e7..9903c6ce 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -499,11 +499,15 @@ _FindMP3s(void) printf("MP3 folder path is too long, no place left for file names. MP3 finding aborted.\n"); return; } - +#if defined ANDROID OutputDebugString("Finding MP3s..."); + strcpy(path, _mp3DirectoryPath); + strcat(path, "/MP3/"); +#else + OutputDebugString("Finding MP3s..."); strcpy(path, _mp3DirectoryPath); strcat(path, "\\MP3\\"); - +#endif #if !defined(_WIN32) char *actualPath = casepath(path); if (actualPath) { diff --git a/src/core/CdStream_posix.cpp b/src/core/CdStream_posix.cpp index d4fcf676..0287adc5 100644 --- a/src/core/CdStream_posix.cpp +++ b/src/core/CdStream_posix.cpp @@ -1,14 +1,22 @@ #ifndef _WIN32 #include "common.h" -#ifdef GTA_PC +#ifdef GTA_PC || GTA_MOBILE #include "crossplatform.h" #include #include +#ifndef ANDROID #include +#endif #include #include #include +#ifndef ANDROID #include +#else +#include "AndroidMain.h" +#include +#define statvfs statfs +#endif #include #include #include @@ -24,7 +32,7 @@ #include "MemoryMgr.h" #define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) -#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) debug ("%s: " f "\n", "cdvd_stream trace", ## __VA_ARGS__) #ifdef FLUSHABLE_STREAMING bool flushStream[MAX_CDCHANNELS]; @@ -52,32 +60,32 @@ re3_sem_close(sem_t* sem) free(sem); } -#else +#elifndef ANDROID #define RE3_SEM_OPEN re3_sem_open sem_t* re3_sem_open(const char* format, ...) { - char semName[21]; - va_list va; - va_start(va, format); - vsprintf(semName, format, va); + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); - return sem_open(semName, O_CREAT, 0644, 1); + return sem_open(semName, O_CREAT, 0644, 1); } #define RE3_SEM_CLOSE re3_sem_close void re3_sem_close(sem_t* sem, const char* format, ...) { - sem_close(sem); + sem_close(sem); - char semName[21]; - va_list va; - va_start(va, format); - vsprintf(semName, format, va); + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); - sem_unlink(semName); + sem_unlink(semName); } #endif @@ -95,7 +103,11 @@ struct CdReadInfo pthread_t pChannelThread; sem_t *pStartSemaphore; #endif +#ifndef ANDROID sem_t *pDoneSemaphore; // used for CdStreamSync +#elif ANDROID + pthread_mutex_t pDoneSemaphore; // used for CdStreamSync +#endif int32 hFile; }; @@ -108,7 +120,11 @@ char *gImgNames[MAX_CDIMAGES]; #ifndef ONE_THREAD_PER_CHANNEL pthread_t _gCdStreamThread; +#ifndef ANDROID sem_t *gCdStreamSema; // released when we have new thing to read(so channel is set) +#elif ANDROID +pthread_mutex_t gCdStreamSema; // released when we have new thing to read(so channel is set) +#endif int8 gCdStreamThreadStatus; // 0: created 1:priority set up 2:abort now Queue gChannelRequestQ; bool _gbCdStreamOverlapped; @@ -132,21 +148,26 @@ CdStreamInitThread(void) gChannelRequestQ.tail = 0; gChannelRequestQ.size = gNumChannels + 1; ASSERT(gChannelRequestQ.items != nil ); - gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream"); +#ifndef ANDROID + gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream"); - if (gCdStreamSema == SEM_FAILED) { - CDTRACE("failed to create stream semaphore"); - ASSERT(0); - return; - } + if (gCdStreamSema == SEM_FAILED) { + CDTRACE("failed to create stream semaphore"); + ASSERT(0); + return; + } +#elif ANDROID + //pthread_mutex_unlock(&gCdStreamSema); +#endif #endif if ( gNumChannels > 0 ) { for ( int32 i = 0; i < gNumChannels; i++ ) { - gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i); +#ifndef ANDROID + gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i); if (gpReadInfo[i].pDoneSemaphore == SEM_FAILED) { @@ -154,6 +175,9 @@ CdStreamInitThread(void) ASSERT(0); return; } +#elif ANDROID + //pthread_mutex_unlock(&gpReadInfo[i].doneMutex); +#endif #ifdef ONE_THREAD_PER_CHANNEL gpReadInfo[i].pStartSemaphore = RE3_SEM_OPEN("/semaphore_start%d", i); @@ -199,14 +223,32 @@ void CdStreamInit(int32 numChannels) { struct statvfs fsInfo; +#if defined ANDROID + char imgPath[MAX_PATH]; + if(StorageRootBuffer == NULL) { + char pwd[128]; + getcwd(pwd, 128); + setenv("STORAGE_ROOT", pwd, 1); + debug("%s\n", pwd); + } + + debug("FILES %s\n", StorageRootBuffer); + strcpy(imgPath, StorageRootBuffer); + strcat(imgPath, "/models/gta3.img"); + debug("%s\n", imgPath); - if((statvfs("models/gta3.img", &fsInfo)) < 0) + if((statvfs(imgPath, &fsInfo)) < 0) +#elifndef ANDROID + if((statvfs("models/gta3.img", &fsInfo)) < 0) +#endif { CDTRACE("can't get filesystem info"); ASSERT(0); return; } -#ifdef __linux__ +#if defined ANDROID + _gdwCdStreamFlags = O_RDONLY; +#elif defined __linux__ _gdwCdStreamFlags = O_RDONLY | O_NOATIME; #else _gdwCdStreamFlags = O_RDONLY; @@ -271,7 +313,11 @@ CdStreamShutdown(void) // Destroying semaphores and free(gpReadInfo) will be done at threads #ifndef ONE_THREAD_PER_CHANNEL gCdStreamThreadStatus = 2; +#ifndef ANDROID sem_post(gCdStreamSema); +#elif ANDROID + pthread_mutex_unlock(&gCdStreamSema); +#endif pthread_join(_gCdStreamThread, nil); #else for ( int32 i = 0; i < gNumChannels; i++ ) { @@ -318,7 +364,11 @@ CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size) #ifndef ONE_THREAD_PER_CHANNEL AddToQueue(&gChannelRequestQ, channel); - if ( sem_post(gCdStreamSema) != 0 ) +#if defined ANDROID + if ( pthread_mutex_unlock(&gCdStreamSema) != 0 ) +#else + if ( sem_post(gCdStreamSema) != 0 ) +#endif printf("Signal Sema Error\n"); #else if ( sem_post(pChannel->pStartSemaphore) != 0 ) @@ -387,7 +437,11 @@ CdStreamSync(int32 channel) pthread_kill(_gCdStreamThread, SIGUSR1); #endif while (pChannel->bLocked) +#ifndef ANDROID sem_wait(pChannel->pDoneSemaphore); +#elif ANDROID + pthread_mutex_lock(&pChannel->pDoneSemaphore); +#endif } pChannel->bReading = false; flushStream[channel] = false; @@ -399,7 +453,11 @@ CdStreamSync(int32 channel) { pChannel->bLocked = true; while (pChannel->bLocked && pChannel->nSectorsToRead != 0){ +#ifndef ANDROID sem_wait(pChannel->pDoneSemaphore); +#elif ANDROID + pthread_mutex_lock(&pChannel->pDoneSemaphore); +#endif } pChannel->bLocked = false; } @@ -452,7 +510,11 @@ void *CdStreamThread(void *param) #ifndef ONE_THREAD_PER_CHANNEL while (gCdStreamThreadStatus != 2) { +#ifndef ANDROID sem_wait(gCdStreamSema); +#elif ANDROID + pthread_mutex_lock(&gCdStreamSema); +#endif int32 channel = GetFirstInQueue(&gChannelRequestQ); @@ -510,7 +572,11 @@ void *CdStreamThread(void *param) if ( pChannel->bLocked ) { pChannel->bLocked = 0; +#ifndef ANDROID sem_post(pChannel->pDoneSemaphore); +#else + pthread_mutex_unlock(&pChannel->pDoneSemaphore); +#endif } pChannel->bReading = false; } @@ -518,9 +584,17 @@ void *CdStreamThread(void *param) #ifndef ONE_THREAD_PER_CHANNEL for ( int32 i = 0; i < gNumChannels; i++ ) { +#ifndef ANDROID RE3_SEM_CLOSE(gpReadInfo[i].pDoneSemaphore, "/semaphore_done%d", i); +#elif ANDROID + pthread_mutex_destroy(&gpReadInfo[i].pDoneSemaphore); +#endif } +#ifndef ANDROID RE3_SEM_CLOSE(gCdStreamSema, "/semaphore_cd_stream"); +#elif ANDROID + pthread_mutex_destroy(&gCdStreamSema); +#endif free(gChannelRequestQ.items); #else RE3_SEM_CLOSE(gpReadInfo[channel].pStartSemaphore, "/semaphore_start%d", channel); diff --git a/src/core/ControllerConfig.cpp b/src/core/ControllerConfig.cpp index fdc21e45..372001d4 100644 --- a/src/core/ControllerConfig.cpp +++ b/src/core/ControllerConfig.cpp @@ -26,6 +26,13 @@ CControllerConfigManager::CControllerConfigManager() m_bFirstCapture = false; m_bMouseAssociated = false; + m_lStickSensX = 1.0f; + m_lStickSensY = 1.0f; + m_rStickSensX = 1.0f; + m_rStickSensY = 1.0f; + m_lStickDeadzone = 0.3f; + m_rStickDeadzone = 0.3f; + MakeControllerActionsBlank(); InitDefaultControlConfiguration(); InitialiseControllerActionNameArray(); @@ -47,6 +54,7 @@ void CControllerConfigManager::MakeControllerActionsBlank() #ifdef RW_GL3 int MapIdToButtonId(int mapId) { +#ifndef LIBRW_SDL2 switch (mapId) { case GLFW_GAMEPAD_BUTTON_A: // Cross return 2; @@ -84,6 +92,45 @@ int MapIdToButtonId(int mapId) { default: return 0; } +#else + switch (mapId) { + case SDL_CONTROLLER_BUTTON_A: // Cross + return 2; + case SDL_CONTROLLER_BUTTON_B: // Circle + return 1; + case SDL_CONTROLLER_BUTTON_X: // Square + return 3; + case SDL_CONTROLLER_BUTTON_Y: // Triangle + return 4; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return 7; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return 8; + case SDL_CONTROLLER_BUTTON_BACK: + return 9; + case SDL_CONTROLLER_BUTTON_START: + return 12; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return 10; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return 11; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return 13; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return 14; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return 15; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return 16; + // SDL2 sends those as axes, so I added them here manually. + case 15: // Left trigger + return 5; + case 16: // Right trigger + return 6; + default: + return 0; + } +#endif } #endif @@ -2807,7 +2854,11 @@ void CControllerConfigManager::UpdateJoyButtonState(int32 padnumber) #elif defined RW_GL3 if (m_NewState.isGamepad) { for (int32 i = 0; i < MAX_BUTTONS; i++) { +#ifndef LIBRW_SDL2 if (i == GLFW_GAMEPAD_BUTTON_GUIDE) +#else + if (i == SDL_CONTROLLER_BUTTON_GUIDE) +#endif continue; m_aButtonStates[MapIdToButtonId(i)-1] = m_NewState.mappedButtons[i]; diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h index 9f3d63f9..8c1c530c 100644 --- a/src/core/ControllerConfig.h +++ b/src/core/ControllerConfig.h @@ -104,18 +104,18 @@ class CMouseControllerState; class CControllerState; -#define JOY_BUTTONS 16 +#define JOY_BUTTONS 32 #define MAX_BUTTONS (JOY_BUTTONS+1) #define ACTIONNAME_LENGTH 40 #ifdef RW_GL3 -struct GlfwJoyState { - int8 id; - bool isGamepad; - uint8 numButtons; - uint8* buttons; - bool mappedButtons[17]; +struct JoyState { + int8 id; + bool isGamepad; + uint8 numButtons; + uint8 buttons[MAX_BUTTONS]; + bool mappedButtons[MAX_BUTTONS]; }; #endif @@ -136,8 +136,8 @@ public: bool m_bFirstCapture; #if defined RW_GL3 - GlfwJoyState m_OldState; - GlfwJoyState m_NewState; + JoyState m_OldState; + JoyState m_NewState; #else DIJOYSTATE2 m_OldState; DIJOYSTATE2 m_NewState; @@ -147,6 +147,8 @@ public: tControllerConfigBind m_aSettings[MAX_CONTROLLERACTIONS][MAX_CONTROLLERTYPES]; bool m_aSimCheckers[MAX_SIMS][MAX_CONTROLLERTYPES]; bool m_bMouseAssociated; + float m_lStickSensX, m_lStickSensY, m_rStickSensX, m_rStickSensY; + float m_lStickDeadzone, m_rStickDeadzone; #ifdef LOAD_INI_SETTINGS static uint32 ms_padButtonsInited; diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp index dd569d11..12442352 100644 --- a/src/core/FileMgr.cpp +++ b/src/core/FileMgr.cpp @@ -37,9 +37,21 @@ static myFILE myfiles[NUMFILES]; // Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) void mychdir(char const *path) { +#if defined(ANDROID) + if(!path) { + return; + } +#endif char* r = casepath(path, false); if (r) { +#if defined(ANDROID) + char path[MAX_PATH]; + strcpy(path, CFileMgr::GetRootDirName()); + strcat(path, r); + chdir(path); +#else chdir(r); +#endif free(r); } else { errno = ENOENT; @@ -200,8 +212,16 @@ char CFileMgr::ms_dirName[128]; void CFileMgr::Initialise(void) { - _getcwd(ms_rootDirName, 128); +#if defined(ANDROID) + if(getenv("STORAGE_ROOT") != NULL) { + strcpy(ms_rootDirName, getenv("STORAGE_ROOT")); + strcat(ms_rootDirName, "/"); + debug("Android: Root Dir: %s\n", ms_rootDirName); + } +#else + _getcwd(ms_rootDirName, 128); strcat(ms_rootDirName, "\\"); +#endif } void @@ -213,10 +233,13 @@ CFileMgr::ChangeDir(const char *dir) } if(*dir != '\0'){ strcat(ms_dirName, dir); - // BUG in the game it seems, it's off by one +#ifndef ANDROID + // BUG in the game it seems, it's off by one if(dir[strlen(dir)-1] != '\\') strcat(ms_dirName, "\\"); +#endif } + debug("CFileMgr::ChangeDir: %s", ms_dirName); mychdir(ms_dirName); } @@ -226,10 +249,13 @@ CFileMgr::SetDir(const char *dir) strcpy(ms_dirName, ms_rootDirName); if(*dir != '\0'){ strcat(ms_dirName, dir); - // BUG in the game it seems, it's off by one +#ifndef ANDROID + // BUG in the game it seems, it's off by one if(dir[strlen(dir)-1] != '\\') strcat(ms_dirName, "\\"); +#endif } + debug("CFileMgr::SetDir: %s", ms_dirName); mychdir(ms_dirName); } @@ -267,12 +293,14 @@ CFileMgr::LoadFile(const char *file, uint8 *buf, int maxlen, const char *mode) int CFileMgr::OpenFile(const char *file, const char *mode) { + debug("CFileMgr::OpenFile: %s", file); return myfopen(file, mode); } int CFileMgr::OpenFileForWriting(const char *file) { + debug("CFileMgr::OpenFileForWriting: %s", file); return OpenFile(file, "wb"); } diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp index 875bb76b..f2e26e50 100644 --- a/src/core/Frontend.cpp +++ b/src/core/Frontend.cpp @@ -633,10 +633,11 @@ CMenuManager::CentreMousePointer() Point.y = SCREEN_HEIGHT / 2; ClientToScreen(PSGLOBAL(window), &Point); SetCursorPos(Point.x, Point.y); -#elif defined RW_GL3 +#elif defined RW_GL3 && !defined(LIBRW_SDL2) glfwSetCursorPos(PSGLOBAL(window), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); +#elif defined(RW_GL3) && defined(LIBRW_SDL2) + SDL_WarpMouseInWindow(PSGLOBAL(window), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); #endif - PSGLOBAL(lastMousePos.x) = SCREEN_WIDTH / 2; PSGLOBAL(lastMousePos.y) = SCREEN_HEIGHT / 2; } @@ -4946,6 +4947,13 @@ CMenuManager::ProcessUserInput(uint8 goDown, uint8 goUp, uint8 optionSelected, u PSGLOBAL(joy1)->GetCapabilities(&devCaps); ControlsManager.InitDefaultControlConfigJoyPad(devCaps.dwButtons); } +#elif defined(LIBRW_SDL2) // TODO SDL2 the part below seems unnecessary SDL2 (at least on Linux), remove in the future + /*if (PSGLOBAL(joy1id) != -1 && SDL_IsGameController(PSGLOBAL(joy1id))) { + SDL_Joystick* joy1 = SDL_JoystickOpen(PSGLOBAL(joy1id)); + int count = SDL_JoystickNumButtons(joy1); + SDL_JoystickClose(joy1); + ControlsManager.InitDefaultControlConfigJoyPad(count); + }*/ #else if (PSGLOBAL(joy1id) != -1 && glfwJoystickPresent(PSGLOBAL(joy1id))) { int count; diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp index 86684d60..da0ba2d7 100644 --- a/src/core/Pad.cpp +++ b/src/core/Pad.cpp @@ -871,9 +871,13 @@ CMouseControllerState CMousePointerStateHelper::GetMouseSetUp() } #else // It seems there is no way to get number of buttons on mouse, so assign all buttons if we have mouse. - double xpos = 1.0f, ypos; +#if !defined(LIBRW_SDL2) + double xpos = 1.0f, ypos; glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); - +#else + int xpos = 1, ypos; + SDL_GetMouseState(&xpos, &ypos); +#endif if (xpos != 0.f) { state.MMB = true; state.RMB = true; @@ -931,8 +935,13 @@ void CPad::UpdateMouse() #else if ( IsForegroundApp() && PSGLOBAL(cursorIsInWindow) ) { - double xpos = 1.0f, ypos; +#ifdef LIBRW_SDL2 + int xpos = 1, ypos; + int mouseState = SDL_GetMouseState(&xpos, &ypos); +#else + double xpos = 1.0f, ypos; glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); +#endif if (xpos == 0.f) return; @@ -951,12 +960,19 @@ void CPad::UpdateMouse() PCTempMouseControllerState.x = (float)(signX * (xpos - PSGLOBAL(lastMousePos.x))); PCTempMouseControllerState.y = (float)(signy * (ypos - PSGLOBAL(lastMousePos.y))); +#ifdef LIBRW_SDL2 + PCTempMouseControllerState.LMB = !!(mouseState & SDL_BUTTON(1)); + PCTempMouseControllerState.RMB = !!(mouseState & SDL_BUTTON(3)); + PCTempMouseControllerState.MMB = !!(mouseState & SDL_BUTTON(2)); + PCTempMouseControllerState.MXB1 = !!(mouseState & SDL_BUTTON(4)); + PCTempMouseControllerState.MXB2 = !!(mouseState & SDL_BUTTON(5)); +#else PCTempMouseControllerState.LMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_LEFT); PCTempMouseControllerState.RMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_RIGHT); PCTempMouseControllerState.MMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_MIDDLE); PCTempMouseControllerState.MXB1 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_4); PCTempMouseControllerState.MXB2 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_5); - +#endif if (PSGLOBAL(mouseWheel) > 0) PCTempMouseControllerState.WHEELUP = 1; else if (PSGLOBAL(mouseWheel) < 0) @@ -1631,14 +1647,14 @@ void CPad::AffectFromXinput(uint32 pad) float rx = (float)xstate.Gamepad.sThumbRX / (float)0x7FFF; float ry = (float)xstate.Gamepad.sThumbRY / (float)0x7FFF; - if (Abs(lx) > 0.3f || Abs(ly) > 0.3f) { - PCTempJoyState.LeftStickX = (int32)(lx * 128.0f); - PCTempJoyState.LeftStickY = (int32)(-ly * 128.0f); + if (Abs(lx) > ControlsManager.m_lStickDeadzone || Abs(ly) > ControlsManager.m_lStickDeadzone) { + PCTempJoyState.LeftStickX = (int32)(lx * 128.0f * ControlsManager.m_lStickSensX); + PCTempJoyState.LeftStickY = (int32)(-ly * 128.0f * ControlsManager.m_lStickSensY); } - if (Abs(rx) > 0.3f || Abs(ry) > 0.3f) { - PCTempJoyState.RightStickX = (int32)(rx * 128.0f); - PCTempJoyState.RightStickY = (int32)(-ry * 128.0f); + if (Abs(rx) > ControlsManager.m_rStickDeadzone || Abs(ry) > ControlsManager.m_rStickDeadzone) { + PCTempJoyState.RightStickX = (int32)(rx * 128.0f * ControlsManager.m_rStickSensX); + PCTempJoyState.RightStickY = (int32)(-ry * 128.0f * ControlsManager.m_rStickSensY); } XINPUT_VIBRATION VibrationState; diff --git a/src/core/common.h b/src/core/common.h index 92158c08..e0ddb033 100644 --- a/src/core/common.h +++ b/src/core/common.h @@ -21,6 +21,10 @@ #include #include +#if defined ANDROID +#include +#endif + #ifdef __MWERKS__ #define AUDIO_MSS #define RWLIBS // codewarrior doesn't support project level defines - so not even this is enough, but still catches most ifdefs @@ -341,7 +345,7 @@ void re3_usererror(const char *format, ...); #define DEBUGBREAK() __debugbreak(); // Switch to enable development messages. -#if 1 +#if 0 #define DEV(f, ...) #else #define DEV(f, ...) re3_debug("[DEV]: " f, ## __VA_ARGS__) diff --git a/src/core/config.h b/src/core/config.h index f6f005a7..73295443 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -26,9 +26,11 @@ enum Config { PEDMODELSIZE = 130, VEHICLEMODELSIZE = 110, TWODFXSIZE = 1210, - - MAXVEHICLESLOADED = 50, // 70 on mobile - +#if defined ANDROID + MAXVEHICLESLOADED = 70, +#else + MAXVEHICLESLOADED = 50, // 70 on mobile +#endif NUMOBJECTINFO = 210, // Pool sizes @@ -154,12 +156,14 @@ enum Config { #define GTA_PC //#define GTA_PS2 //#define GTA_XBOX +#define GTA_MOBILE // Version defines #define GTAVC_PS2 400 #define GTAVC_PC_10 410 #define GTAVC_PC_11 411 #define GTAVC_PC_JAP 412 +#define GTAVC_ANDROID 413 // ANDROID 413? // TODO? maybe something for xbox or android? #define GTA_VERSION GTAVC_PC_11 @@ -290,7 +294,7 @@ enum Config { #define EXTENDED_OFFSCREEN_DESPAWN_RANGE // Use onscreen despawn range for offscreen peds and vehicles to avoid them despawning in the distance when you look // away -#if defined(__LP64__) || defined(_WIN64) +#if defined(__LP64__) || defined(_WIN64) || defined(__aarch64__) #define FIX_BUGS_64 // Must have fixes to be able to run 64 bit build #endif @@ -306,6 +310,10 @@ enum Config { #define MISSION_SWITCHER // from debug menu #endif +#ifdef ANDROID +#define USE_FILE_LOG +#endif + // Rendering/display #define ASPECT_RATIO_SCALE // Not just makes everything scale with aspect ratio, also adds support for all aspect ratios #define PROPER_SCALING // use original DEFAULT_SCREEN_WIDTH/DEFAULT_SCREEN_HEIGHT from PS2 instead of PC(R* changed HEIGHT here to make radar look better, but broke other hud elements aspect ratio). diff --git a/src/core/main.cpp b/src/core/main.cpp index 5dfb2772..7cfb29cf 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1084,8 +1084,10 @@ DisplayGameDebugText() sprintf(verA, #if defined _WIN32 "Win " -#elif defined __linux__ +#elif defined __linux__ && !defined ANDROID "Linux " +#elif defined(ANDROID) + "Android " #elif defined __APPLE__ "Mac OS X " #elif defined __FreeBSD__ @@ -1093,8 +1095,14 @@ DisplayGameDebugText() #else "Posix-compliant " #endif -#if defined __LP64__ || defined _WIN64 +#if defined __LP64__ || defined _WIN64 || defined __aarch64__ +#if defined ANDROID + "arm64-v8a " +#else "64-bit " +#endif +#elif defined ANDROID + "armeabi-v7a " #else "32-bit " #endif diff --git a/src/core/re3.cpp b/src/core/re3.cpp index b9cfc34d..901d36af 100644 --- a/src/core/re3.cpp +++ b/src/core/re3.cpp @@ -136,6 +136,9 @@ CustomFrontendOptionsPopulate(void) #ifdef EXTENDED_PIPELINES const char *vehPipelineNames[] = { "FED_MFX", "FED_NEO" }; const char *off_on[] = { "FEM_OFF", "FEM_ON" }; +#if defined ANDROID + CFileMgr::ChangeDir("\\"); +#endif fd = CFileMgr::OpenFile("neo/neo.txd","r"); if (fd) { #ifdef GRAPHICS_MENU_OPTIONS @@ -1212,7 +1215,7 @@ void re3_assert(const char *expr, const char *filename, unsigned int lineno, con strcat_s(re3_buff, re3_buffsize, "(Press Retry to debug the application)"); - nCode = ::MessageBoxA(nil, re3_buff, "REVC Assertion Failed!", + nCode = ::MessageBoxA(nil, re3_buff, "reVC Assertion Failed!", MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); if (nCode == IDABORT) @@ -1233,7 +1236,7 @@ void re3_assert(const char *expr, const char *filename, unsigned int lineno, con abort(); #else // TODO - printf("\nREVC ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + printf("\nreVC ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); assert(false); #endif } @@ -1250,7 +1253,9 @@ void re3_debug(const char *format, ...) vsprintf(re3_buff, format, va); #endif va_end(va); - +#if defined ANDROID + __android_log_print(ANDROID_LOG_DEBUG, "reVC-DEBUG", re3_buff); +#endif printf("%s", re3_buff); CDebug::DebugAddText(re3_buff); #endif @@ -1273,7 +1278,9 @@ void re3_trace(const char *filename, unsigned int lineno, const char *func, cons sprintf(buff, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff); #endif - +#if defined ANDROID + __android_log_print(ANDROID_LOG_DEBUG, "reVC-TRACE", buff); +#endif OutputDebugString(buff); } #endif @@ -1287,14 +1294,17 @@ void re3_usererror(const char *format, ...) vsprintf_s(re3_buff, re3_buffsize, format, va); va_end(va); - ::MessageBoxA(nil, re3_buff, "REVC Error!", + ::MessageBoxA(nil, re3_buff, "reVC Error!", MB_OK|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); raise(SIGABRT); _exit(3); #else vsprintf(re3_buff, format, va); - printf("\nREVC Error!\n\t%s\n",re3_buff); +#if defined ANDROID + __android_log_print(ANDROID_LOG_ERROR, "reVC-ERROR", re3_buff); +#endif + printf("\nreVC Error!\n\t%s\n",re3_buff); assert(false); #endif } diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp index b2fcfbc7..428f19ab 100644 --- a/src/entities/Entity.cpp +++ b/src/entities/Entity.cpp @@ -29,6 +29,9 @@ #include "SaveBuf.h" int gBuildings; +#if defined ANDROID +int g_iLastRenderedObject; +#endif CEntity::CEntity(void) { @@ -415,6 +418,9 @@ CEntity::PreRender(void) void CEntity::Render(void) { +#if defined ANDROID + g_iLastRenderedObject = this->m_modelIndex; +#endif if(m_rwObject){ bImBeingRendered = true; if(RwObjectGetType(m_rwObject) == rpATOMIC) diff --git a/src/save/PCSave.cpp b/src/save/PCSave.cpp index a33e9d90..9fc0b2d8 100644 --- a/src/save/PCSave.cpp +++ b/src/save/PCSave.cpp @@ -19,7 +19,12 @@ C_PcSave PcSaveHelper; void C_PcSave::SetSaveDirectory(const char *path) { - sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTAVCsf"); +#if defined ANDROID + sprintf(DefaultPCSaveFileName, "%s/%s", path, "GTAVCsf"); + debug("SetSaveDirectory: %s", DefaultPCSaveFileName); +#else + sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTAVCsf"); +#endif } bool diff --git a/src/skel/android/AndroidMain.cpp b/src/skel/android/AndroidMain.cpp new file mode 100644 index 00000000..ba2da023 --- /dev/null +++ b/src/skel/android/AndroidMain.cpp @@ -0,0 +1,432 @@ +// +// Created by mrxenginner on 13/07/2025. +// + +#if defined ANDROID + +#include "AndroidMain.h" +#include "JavaWrapper.h" +#include "logger/log.h" +#include +#include +#include +#include + +JavaVM* javaVM = NULL; +char* StorageRootBuffer = NULL; +uintptr_t g_libREVC = NULL; + +bool AndWrapper::AppInitialized = false; +bool AndWrapper::AppStarted = false; + +JAVA_WRAPPER Java_org_libsdl_app_SDLActivity_nativeSetupJNI(JNIEnv* env, jobject initGame) +{ + if(!javaVM) { + env->GetJavaVM(&javaVM); + } + + g_pJavaWrapper = new CJavaWrapper(env, initGame); +} + +JAVA_WRAPPER Java_com_revc_game_core_REVC_setGamePath(JNIEnv *env, jobject obj, jstring value) +{ + const char* root = env->GetStringUTFChars(value, NULL); + setenv("STORAGE_ROOT", root, 1); + + StorageRootBuffer = getenv("STORAGE_ROOT"); + debug("Storage Root: %s", StorageRootBuffer); + + env->ReleaseStringUTFChars(value, root); +} + +bool AndWrapper::InitLibraries() { + g_libREVC = Patch::FindLib("libreVC.so"); + + if (!g_libREVC) { + Logger::Log("[ERROR]: Required libraries not found!"); + return false; + } + + Logger::Log("[INFO]: libreVC base: 0x%X", g_libREVC); + return true; +} + +void AndWrapper::TimeInitialize() { + struct timeval v0; + + gettimeofday(&v0, NULL); + // base_time = (double)v0.tv_usec / 1000000.0 + (double)v0.tv_sec; +} + +void* AndWrapper::GetJNI() { + return CJavaWrapper::GetEnv(); +} + +void* AndWrapper::GetJNIFunc() { + return CJavaWrapper::GetEnv(); +} + +void* AndWrapper::GetObj() { + //return s_event_globalThiz; +} + +const char* AndWrapper::GetAppId() +{ + /*JNIEnv* CurrentJNIEnv; // x19 + jobject v1; // x20 + const char *v2; // x21 + jboolean v4[4]; // [xsp+Ch] [xbp-14h] BYREF + + if (!staticAppId[0]) { + CurrentJNIEnv = CJavaWrapper::GetEnv(); + + v1 = _JNIEnv::CallObjectMethod(CurrentJNIEnv, s_event_globalThiz, s_GetAppId); + v2 = CurrentJNIEnv->functions->GetStringUTFChars(CurrentJNIEnv, (jstring)v1, v4); + strcpy(staticAppId, v2); + CurrentJNIEnv->functions->ReleaseStringUTFChars(CurrentJNIEnv, (jstring)v1, v2); + CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, v1); + } + return staticAppId;*/ +} + +const char* AndWrapper::GetDeviceID() +{ + /*JNIEnv *CurrentJNIEnv; // x19 + jobject v1; // x20 + const char *v2; // x21 + jboolean v4[4]; // [xsp+Ch] [xbp-24h] BYREF + + CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + v1 = JNIEnv::CallObjectMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceID); + v2 = CurrentJNIEnv->functions->GetStringUTFChars(CurrentJNIEnv, (jstring)v1, v4); + strncpy(staticDeviceID, v2, 0x80uLL); + CurrentJNIEnv->functions->ReleaseStringUTFChars(CurrentJNIEnv, (jstring)v1, v2); + CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, v1); + return staticDeviceID;*/ +} + +int AndWrapper::GetDeviceInfo(int index) +{ + //JNIEnv *CurrentJNIEnv; // x0 + + //CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + //return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceInfo, (unsigned int)index); +} + +bool AndWrapper::IsAppInstalled(const char* app) +{ + /*_JNIEnv *CurrentJNIEnv; // x20 + __int64 v3; // x19 + bool v4; // w21 + + CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + v3 = (__int64)CurrentJNIEnv->functions->NewStringUTF(CurrentJNIEnv, app); + v4 = _JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_IsAppInstalled, v3) != 0; + CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, (jobject)v3); + return v4;*/ +} + +void AndWrapper::OpenLink(const char* link) +{ + /*_JNIEnv *CurrentJNIEnv; // x20 + __int64 v3; // x19 + + CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + v3 = (__int64)CurrentJNIEnv->functions->NewStringUTF(CurrentJNIEnv, link); + _JNIEnv::CallVoidMethod(CurrentJNIEnv, s_event_globalThiz, s_OpenLink, v3); + CurrentJNIEnv->functions->DeleteLocalRef(CurrentJNIEnv, (jobject)v3);*/ +} + +bool AndWrapper::DeviceIsTV() +{ + //_JNIEnv *CurrentJNIEnv; // x0 + + //CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + //return _JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_IsTV) != 0; +} + +int AndWrapper::DeviceLocale() +{ + // _JNIEnv *CurrentJNIEnv; // x0 + + // CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + // return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceLocale); +} + +int AndWrapper::DeviceType() +{ + //_JNIEnv *CurrentJNIEnv; // x0 + + //CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + //return _JNIEnv::CallIntMethod(CurrentJNIEnv, s_event_globalThiz, s_GetDeviceType); +} + +void AndWrapper::SystemInitialize() +{ + +} +/* +int32_t __fastcall NVEventAppMain(int32_t argc, char **argv) +{ + _QWORD *v4; // x8 + pthread_key_t v5; // w0 + __int64 v6; // x21 + _BOOL4 v7; // w21 + _JNIEnv *CurrentJNIEnv; // x0 + _JNIEnv *v9; // x0 + jint v10; // w0 + __int64 v11; // x22 + __int64 v12; // x22 + _JNIEnv *v13; // x0 + int v14; // w8 + _JNIEnv *v15; // x0 + _JNIEnv *v16; // x0 + double v17; // d8 + bool v18; // w19 + double v19; // d1 + double v20; // d0 + double v21; // d12 + double v22; // d8 + float v23; // s0 + char v24; // w20 + pthread_mutexattr_t *v25; // x19 + pthread_mutexattr_t *v26; // x19 + pthread_mutexattr_t *v27; // x19 + struct timeval v29; // [xsp+0h] [xbp-90h] BYREF + int data; // [xsp+1Ch] [xbp-74h] BYREF + + OS_ApplicationPreinit(); + AND_KeyboardInitialize(); + memset(lastGamepadAxis, 0, sizeof(lastGamepadAxis)); + gettimeofday(&v29, 0LL); + base_time = (double)v29.tv_usec / 1000000.0 + (double)v29.tv_sec; + if ( !ANDThread_Initted ) + { + pthread_key_create(&ANDThreadStorageKey, ANDThreadData::Destroy); + v4 = malloc(0x18uLL); + v5 = ANDThreadStorageKey; + v4[1] = 0LL; + v4[2] = 0LL; + *v4 = 0LL; + pthread_setspecific(v5, v4); + ANDThread_Initted = 1; + } + v6 = operator new(0x30uLL); + pthread_mutexattr_init((pthread_mutexattr_t *)(v6 + 40)); + pthread_mutexattr_settype((pthread_mutexattr_t *)(v6 + 40), 1); + pthread_mutex_init((pthread_mutex_t *)v6, (const pthread_mutexattr_t *)(v6 + 40)); + fileMutex = (OSMutex)v6; + if ( DoInitGraphics ) + initGraphics(); + v7 = 0; + if ( IsInitGraphics ) + goto LABEL_8; + while ( IsAndroidPaused || !v7 ) + { + while ( 1 ) + { + ++NVEventAppMain(int,char **)::iter; + v7 = ProcessEvents(0); + if ( !IsInitGraphics ) + break; + LABEL_8: + if ( !IsAndroidPaused ) + goto LABEL_12; + } + } + LABEL_12: + pthread_mutex_lock((pthread_mutex_t *)AndroidEGLContext); + CurrentJNIEnv = NVThreadGetCurrentJNIEnv(); + if ( CurrentJNIEnv && s_event_globalThiz ) + { + if ( !_JNIEnv::CallBooleanMethod(CurrentJNIEnv, s_event_globalThiz, s_makeCurrent) ) + __android_log_print(3, "NVEvent", "Error: MakeCurrent failed"); + } + else + { + __android_log_print(3, "NVEvent", "Error: No valid JNI env in MakeCurrent"); + } + AND_WRAPPER::SystemInitialize(); + v9 = NVThreadGetCurrentJNIEnv(); + v10 = _JNIEnv::CallIntMethod(v9, s_event_globalThiz, s_GetDeviceType); + isLowMemoryDevice = (((unsigned int)v10 >> 1) & 1) == 0 || v10 >> 6 < 250; + v11 = operator new(0x30uLL); + pthread_mutexattr_init((pthread_mutexattr_t *)(v11 + 40)); + pthread_mutexattr_settype((pthread_mutexattr_t *)(v11 + 40), 1); + pthread_mutex_init((pthread_mutex_t *)v11, (const pthread_mutexattr_t *)(v11 + 40)); + billingMutex = (OSMutex)v11; + v12 = operator new(0x30uLL); + pthread_mutexattr_init((pthread_mutexattr_t *)(v12 + 40)); + pthread_mutexattr_settype((pthread_mutexattr_t *)(v12 + 40), 1); + pthread_mutex_init((pthread_mutex_t *)v12, (const pthread_mutexattr_t *)(v12 + 40)); + gameServiceMutex = (OSMutex)v12; + s_conflictHandler = 0LL; + if ( OS_ApplicationInitialize(argc, (const char **)argv) ) + { + data = 0; + AND_AppInitialized = 1; + v13 = NVThreadGetCurrentJNIEnv(); + if ( _JNIEnv::CallBooleanMethod(v13, s_event_globalThiz, s_IsWifiAvailable) ) + { + v14 = 2; + } + else + { + v15 = NVThreadGetCurrentJNIEnv(); + if ( !_JNIEnv::CallBooleanMethod(v15, s_event_globalThiz, s_IsNetworkAvailable) ) + goto LABEL_23; + v14 = 1; + } + data = v14; + LABEL_23: + OS_ApplicationEvent(OSET_NetworkChanged, &data); + v16 = NVThreadGetCurrentJNIEnv(); + if ( v16 && s_event_globalThiz ) + { + if ( !_JNIEnv::CallBooleanMethod(v16, s_event_globalThiz, s_unMakeCurrent) ) + __android_log_print(3, "NVEvent", "Error: UnMakeCurrent failed"); + } + else + { + __android_log_print(3, "NVEvent", "Error: No valid JNI env in UnMakeCurrent"); + } + pthread_mutex_unlock((pthread_mutex_t *)AndroidEGLContext); + if ( OS_ApplicationStartup(windowSize[0], windowSize[1], argc, (const char **)argv) ) + { + AND_AppStarted = 1; + gettimeofday(&v29, 0LL); + if ( !v7 ) + { + v17 = (double)v29.tv_usec / 1000000.0 + (double)v29.tv_sec; + do + { + v18 = ProcessEvents(0); + while ( !v18 ) + { + if ( !IsAndroidPaused ) + break; + if ( IsAndroidInMultiplayer ) + break; + v18 = ProcessEvents(0); + usleep(0x61A8u); + } + gettimeofday(&v29, 0LL); + if ( v29.tv_usec > 1000000 || v29.tv_usec < 0 ) + v19 = OS_TimeAccurate(void)::last_current_time + - (double)(unsigned int)OS_TimeAccurate(void)::last_current_time + + 0.00033; + else + v19 = (double)v29.tv_usec / 1000000.0; + v20 = v19 + (double)v29.tv_sec; + OS_TimeAccurate(void)::last_current_time = v20; + if ( v20 - OS_TimeAccurate(void)::lastPrint > 5.0 ) + OS_TimeAccurate(void)::lastPrint = v19 + (double)v29.tv_sec; + v21 = v20 - base_time; + v22 = v20 - base_time - v17; + v23 = v22; + v24 = OS_ApplicationTick(v23); + AND_GamepadUpdate(); + AND_FileUpdate(v22); + AND_BillingUpdate(0); + v17 = v21; + } + while ( !v18 && (v24 & 1) != 0 ); + } + OS_ApplicationEvent(OSET_RequestExit, 0LL); + pthread_key_delete(ANDThreadStorageKey); + if ( items ) + { + free(items); + items = 0LL; + numItems = 0; + } + v25 = (pthread_mutexattr_t *)billingMutex; + if ( billingMutex ) + { + pthread_mutex_destroy((pthread_mutex_t *)billingMutex); + pthread_mutexattr_destroy(v25 + 5); + operator delete(v25); + } + billingMutex = 0LL; + AND_ClearAchievementData(1); + v26 = (pthread_mutexattr_t *)billingMutex; + if ( billingMutex ) + { + pthread_mutex_destroy((pthread_mutex_t *)billingMutex); + pthread_mutexattr_destroy(v26 + 5); + operator delete(v26); + } + v27 = (pthread_mutexattr_t *)fileMutex; + if ( fileMutex ) + { + pthread_mutex_destroy((pthread_mutex_t *)fileMutex); + pthread_mutexattr_destroy(v27 + 5); + operator delete(v27); + } + fileMutex = 0LL; + } + else + { + OS_ApplicationEvent(OSET_RequestExit, 0LL); + } + } + return 0; +}*/ +/* +void __fastcall OS_ApplicationEvent(OSEventType type, void *data) +{ + switch ( type ) + { + case OSET_RequestExit: + RsGlobal.quit = TRUE; + //OS_ThreadWait(mainThread); + break; + + case OSET_Pause: + SaveGameForPause(eSaveTypes::eExitSave, 0LL); + CTimer::StartUserPause(); + if ( !CPad::GetPad(0)->DisablePlayerControls + && !gMobileMenu.screenStack.numEntries + && !CCutsceneMgr::IntroTextIsActiveHack + && !CCutsceneMgr::ms_running + && !gMobileMenu.pendingScreen + && !CTouchInterface::AnyWidgetsUsingAltBack() ) + { + if (FindPlayerPed()) + bPendingPause = 1; + } + CAEAudioHardware::PauseOpenAL((CAEAudioHardware *)&AEAudioHardware, 1); + break; + case OSET_Resume: + OS_ThreadUnmakeCurrent(); + CTimer::Update(); + if ( !gMobileMenu.screenStack.numEntries && !gMobileMenu.pendingScreen ) + CTimer::EndUserPause(); + CAEAudioHardware::PauseOpenAL((CAEAudioHardware *)&AEAudioHardware, 0); + break; + case OSET_LowMemory: + //DoLowMemoryCleanup = 1; + break; + default: + return; + } +}*/ + +JNI_WRAPPER int InitializeGame() { + debug("Initialize Game"); + + if (!AndWrapper::InitLibraries()) return NULL; + + int argc = 0; + char* argv[1] = { nullptr }; + + SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); + // SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + + CrashHandler::SetupSignalHandlers(); + + int result = SDL_main(argc, argv); + return result; +} + +#endif \ No newline at end of file diff --git a/src/skel/android/AndroidMain.h b/src/skel/android/AndroidMain.h new file mode 100644 index 00000000..dc851f3f --- /dev/null +++ b/src/skel/android/AndroidMain.h @@ -0,0 +1,39 @@ +// +// Created by mrxenginner on 13/07/2025. +// + +#ifndef REVC_ANDROIDMAIN_H +#define REVC_ANDROIDMAIN_H + +#if defined ANDROID + +#include "common.h" + +#define JNI_WRAPPER extern "C" __attribute__ ((visibility("default"))) +#define JAVA_WRAPPER extern "C" JNIEXPORT void JNICALL + +extern uintptr_t g_libREVC; +extern char* StorageRootBuffer; + +namespace AndWrapper { + extern bool AppInitialized; + extern bool AppStarted; + + bool InitLibraries(); + void SystemInitialize(); + void TimeInitialize(); + void* GetJNI(); + void* GetJNIFunc(); + void* GetObj(); + const char* GetAppId(); + const char* GetDeviceID(); + int GetDeviceInfo(int index); + bool IsAppInstalled(const char* app); + void OpenLink(const char* link); + bool DeviceIsTV(); + int DeviceLocale(); + int DeviceType(); +} +#endif + +#endif //REVC_ANDROIDMAIN_H diff --git a/src/skel/android/JavaWrapper.cpp b/src/skel/android/JavaWrapper.cpp new file mode 100644 index 00000000..d5b3511f --- /dev/null +++ b/src/skel/android/JavaWrapper.cpp @@ -0,0 +1,73 @@ +// +// Created by mrxenginner on 14/07/2025. +// + +#include "common.h" +#include "JavaWrapper.h" + +#if defined ANDROID + +extern "C" JavaVM *javaVM; + +JNIEnv* CJavaWrapper::GetEnv() { + + JNIEnv* env = NULL; + + int getEnvStat = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6); + + if (getEnvStat == JNI_EDETACHED) { + debug("GetEnv: not attached"); + if (javaVM->AttachCurrentThread(&env, NULL) != 0) { + debug("Failed to attach"); + return NULL; + } + } + + if (getEnvStat == JNI_EVERSION) { + debug("GetEnv: version not supported"); + return NULL; + } + + if (getEnvStat == JNI_ERR) { + debug("GetEnv: JNI_ERR"); + return NULL; + } + return env; +} + +void CJavaWrapper::ExitGame() { + + JNIEnv* env = GetEnv(); + + if (!env) { + debug("No env"); + return; + } + + env->CallVoidMethod(this->activity, this->s_ExitGame); +} + +CJavaWrapper::CJavaWrapper(JNIEnv *env, jobject activity) { + this->activity = env->NewGlobalRef(activity); + + jclass nvEventClass = env->GetObjectClass(activity); + + if (!nvEventClass) { + debug("nvEventClass null"); + return; + } + + s_ExitGame = env->GetMethodID(nvEventClass, "exitGame", "()V"); + env->DeleteLocalRef(nvEventClass); + env->DeleteLocalRef(activity); +} + +CJavaWrapper::~CJavaWrapper() { + JNIEnv* pEnv = GetEnv(); + if (pEnv) { + pEnv->DeleteGlobalRef(this->activity); + } +} + +CJavaWrapper* g_pJavaWrapper = nullptr; +#endif \ No newline at end of file diff --git a/src/skel/android/JavaWrapper.h b/src/skel/android/JavaWrapper.h new file mode 100644 index 00000000..0303d0a9 --- /dev/null +++ b/src/skel/android/JavaWrapper.h @@ -0,0 +1,39 @@ +// +// Created by mrxenginner on 14/07/2025. +// + +#ifndef REVC_CJAVAWRAPPER_H +#define REVC_CJAVAWRAPPER_H + +#if defined ANDROID + +#include +#include + +#define EXCEPTION_CHECK(env) \ + if ((env)->ExceptionCheck()) \ + { \ + (env)->ExceptionDescribe(); \ + (env)->ExceptionClear(); \ + return; \ + } + +class CJavaWrapper +{ + jmethodID s_ExitGame; +public: + jobject activity; + + CJavaWrapper(JNIEnv* env, jobject activity); + ~CJavaWrapper(); + + void ExitGame(); + + // static methods + static JNIEnv* GetEnv(); +}; + +extern CJavaWrapper* g_pJavaWrapper; +#endif + +#endif //REVC_CJAVAWRAPPER_H diff --git a/src/skel/android/logger/log.cpp b/src/skel/android/logger/log.cpp new file mode 100644 index 00000000..7d10086f --- /dev/null +++ b/src/skel/android/logger/log.cpp @@ -0,0 +1,65 @@ +// +// Created by mrxenginner on 11/05/2025. +// + +#if defined ANDROID + +#include "common.h" +#include "log.h" +#include "AndroidMain.h" + +extern char* StorageRootBuffer; + +void Logger::Log(const char *fmt, ...) +{ + static char buffer[512]{}; + + memset(buffer, 0, sizeof(buffer)); + + va_list arg; + va_start(arg, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, arg); + va_end(arg); +#if defined ANDROID + __android_log_write(ANDROID_LOG_INFO, "LOG", buffer); +#endif +#ifdef USE_FILE_LOG + static FILE* flLog = nullptr; + + if(flLog == nullptr && StorageRootBuffer != nullptr) { + sprintf(buffer, "%slog.txt", StorageRootBuffer); + flLog = fopen(buffer, "ab"); + } + + if(flLog == nullptr) return; + fprintf(flLog, "%s\n", buffer); + fflush(flLog); +#endif +} + +void Logger::CrashLog(const char* fmt, ...) +{ + static char buffer[512]{}; + memset(buffer, 0, sizeof(buffer)); + + va_list arg; + va_start(arg, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, arg); + va_end(arg); +#if defined ANDROID + __android_log_write(ANDROID_LOG_FATAL, "CRASH LOG", buffer); +#endif +#ifdef USE_FILE_LOG + static FILE* flLog = nullptr; + + if (flLog == nullptr && StorageRootBuffer != nullptr) { + sprintf(buffer, "%scrash_log.txt", StorageRootBuffer); + flLog = fopen(buffer, "ab"); + } + + if (flLog == nullptr) return; + fprintf(flLog, "%s\n", buffer); + fflush(flLog); +#endif +} +#endif \ No newline at end of file diff --git a/src/skel/android/logger/log.h b/src/skel/android/logger/log.h new file mode 100644 index 00000000..1f8e0d86 --- /dev/null +++ b/src/skel/android/logger/log.h @@ -0,0 +1,16 @@ +// +// Created by mrxenginner on 11/05/2025. +// + +#ifndef REVC_LOGGER_H +#define REVC_LOGGER_H + +#if defined ANDROID + +namespace Logger { + void Log(const char* fmt, ...); + void CrashLog(const char* fmt, ...); +} +#endif + +#endif diff --git a/src/skel/android/patch/patch.h b/src/skel/android/patch/patch.h new file mode 100644 index 00000000..76ba37d2 --- /dev/null +++ b/src/skel/android/patch/patch.h @@ -0,0 +1,36 @@ +// +// Created by mrxenginner on 14/07/2025. +// + +#ifndef REVC_PATCH_H +#define REVC_PATCH_H + +#if defined ANDROID + +#include "common.h" +#include + +class Patch { +public: + static uintptr_t FindLib(const char* libName) + { +#if defined ANDROID + void* handle = dlopen(libName, RTLD_LAZY); + + if (handle) { + void* symbol = dlsym(handle, "InitializeGame"); + if (symbol) { + Dl_info info; + if (dladdr(symbol, &info) != 0) { + return reinterpret_cast(info.dli_fbase); + } + } + dlclose(handle); + } +#endif + return 0; + } +}; +#endif + +#endif // REVC_PATCH_H diff --git a/src/skel/android/signalhandler/SignalHandler.cpp b/src/skel/android/signalhandler/SignalHandler.cpp new file mode 100644 index 00000000..fcc23abc --- /dev/null +++ b/src/skel/android/signalhandler/SignalHandler.cpp @@ -0,0 +1,102 @@ +// +// Created by mrxenginner on 11/05/2025. +// + +#if defined ANDROID + +#include "SignalHandler.h" +#include "../logger/log.h" +#include "StackTrace.h" + +#include +#include +#include +#include + +extern int16 g_usLastProcessedModelIndexAutomobile; +extern int g_iLastProcessedModelIndexAutoEnt; + +extern int g_iLastProcessedSkinCollision; +extern int g_iLastProcessedEntityCollision; +extern char lastFile[123]; +extern int g_iLastRenderedObject; +extern int lastNvEvent; +extern CVector lastPos; +char g_iLastBlock[123]; +char streamimgState[255]; + +namespace CrashHandler { + static struct sigaction oldHandlers[4]; // 0: SEGV, 1: ABRT, 2: FPE, 3: BUS + + void PrintBuildCrashInfo() + { + time_t currentTime = time(nullptr); + tm* timeInfo = localtime(¤tTime); + + Logger::CrashLog("Crash time: %d:%d:%d %d:%d:%d", timeInfo->tm_mday, timeInfo->tm_mon, timeInfo->tm_year, timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); + Logger::CrashLog("Build times: %s %s. ABI: %s", __TIME__, __DATE__, (ANDROID_x32 ? "armeabi-v7a" : "arm64-v8a")); + Logger::CrashLog("Last processed auto and entity: %d %d", g_usLastProcessedModelIndexAutomobile, g_iLastProcessedModelIndexAutoEnt); + Logger::CrashLog("Last rendered object: %d", g_iLastRenderedObject); + } + + void SignalHandler(int signum, siginfo_t* info, void* contextPtr) { + auto* context = static_cast(contextPtr); + + struct sigaction* oldHandler = nullptr; + const char* signalName = nullptr; + + switch (signum) { + case SIGSEGV: + oldHandler = &oldHandlers[0]; + signalName = "SIGSEGV"; + Logger::CrashLog(" "); + break; + case SIGABRT: + oldHandler = &oldHandlers[1]; + signalName = "SIGABRT"; + Logger::CrashLog(" "); + break; + case SIGFPE: + oldHandler = &oldHandlers[2]; + signalName = "SIGFPE"; + break; + case SIGBUS: + oldHandler = &oldHandlers[3]; + signalName = "SIGBUS"; + break; + default: + Logger::CrashLog("Unhandled signal: %d", signum); + return; + } + + if (oldHandler && oldHandler->sa_sigaction) { + oldHandler->sa_sigaction(signum, info, contextPtr); + } + + PrintBuildCrashInfo(); + Logger::CrashLog("%s | Fault address: 0x%X", signalName, info->si_addr); + PRINT_CRASH_STATES(context); + CStackTrace::printBacktrace(); + } + + void SetupSignalHandlers() { + struct { + int signal; + int index; + } signals[] = { + { SIGSEGV, 0 }, + { SIGABRT, 1 }, + { SIGFPE, 2 }, + { SIGBUS, 3 }, + }; + + for (const auto& s : signals) { + struct sigaction act {}; + act.sa_sigaction = SignalHandler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + sigaction(s.signal, &act, &oldHandlers[s.index]); + } + } +} +#endif \ No newline at end of file diff --git a/src/skel/android/signalhandler/SignalHandler.h b/src/skel/android/signalhandler/SignalHandler.h new file mode 100644 index 00000000..16381062 --- /dev/null +++ b/src/skel/android/signalhandler/SignalHandler.h @@ -0,0 +1,17 @@ +// +// Created by mrxenginner on 11/05/2025. +// + +#ifndef REVC_SIGNALHANDLER_H +#define REVC_SIGNALHANDLER_H + +#if defined ANDROID + +#include "common.h" + +namespace CrashHandler { + void SetupSignalHandlers(); +} +#endif + +#endif //REVC_SIGNALHANDLER_H \ No newline at end of file diff --git a/src/skel/android/signalhandler/StackTrace.h b/src/skel/android/signalhandler/StackTrace.h new file mode 100644 index 00000000..47d6c897 --- /dev/null +++ b/src/skel/android/signalhandler/StackTrace.h @@ -0,0 +1,67 @@ +// +// Created by mrxenginner on 13/07/2025. +// + +#ifndef REVC_STACKTRACE_H +#define REVC_STACKTRACE_H + +#if defined ANDROID + +#include +#include +#include + +extern uintptr_t g_libREVC; + +#if ANDROID_x32 +#define PRINT_CRASH_STATES(context) \ + Logger::CrashLog("register states:"); \ + Logger::CrashLog("r0: 0x%X, r1: 0x%X, r2: 0x%X, r3: 0x%X", (context)->uc_mcontext.arm_r0, (context)->uc_mcontext.arm_r1, (context)->uc_mcontext.arm_r2, (context)->uc_mcontext.arm_r3); \ + Logger::CrashLog("r4: 0x%x, r5: 0x%x, r6: 0x%x, r7: 0x%x", (context)->uc_mcontext.arm_r4, (context)->uc_mcontext.arm_r5, (context)->uc_mcontext.arm_r6, (context)->uc_mcontext.arm_r7); \ + Logger::CrashLog("r8: 0x%x, r9: 0x%x, sl: 0x%x, fp: 0x%x", (context)->uc_mcontext.arm_r8, (context)->uc_mcontext.arm_r9, (context)->uc_mcontext.arm_r10, (context)->uc_mcontext.arm_fp); \ + Logger::CrashLog("ip: 0x%x, sp: 0x%x, lr: 0x%x, pc: 0x%x", (context)->uc_mcontext.arm_ip, (context)->uc_mcontext.arm_sp, (context)->uc_mcontext.arm_lr, (context)->uc_mcontext.arm_pc); \ + Logger::CrashLog("1: libreVC.so + 0x%X", context->uc_mcontext.arm_pc - g_libREVC); \ + Logger::CrashLog("2: libreVC.so + 0x%X", context->uc_mcontext.arm_lr - g_libREVC); +#else +#define PRINT_CRASH_STATES(context) \ + Logger::CrashLog("1: libreVC.so + 0x%llx", context->uc_mcontext.pc - g_libREVC); \ + Logger::CrashLog("2: libreVC.so + 0x%llx", context->uc_mcontext.regs[30] - g_libREVC); +#endif + +class CStackTrace +{ +public: + static void printBacktrace() + { + Logger::CrashLog("------------ START BACKTRACE ------------"); + Logger::CrashLog(" "); + PrintStackTrace(); + } + +private: + static _Unwind_Reason_Code TraceFunction(_Unwind_Context* context, void* arg) { + uintptr_t pc = _Unwind_GetIP(context); + + Dl_info info; + if (dladdr(reinterpret_cast(pc), &info) && info.dli_sname != nullptr) { + Logger::CrashLog("[adr: %p reVC: %p] %s\n", + reinterpret_cast(pc), + reinterpret_cast(pc - g_libREVC), + info.dli_sname); + } else { + Logger::CrashLog("[adr: %p reVC: %p] name not found\n", + reinterpret_cast(pc), + reinterpret_cast(pc - g_libREVC)); + } + + return _URC_NO_REASON; + } + + static void PrintStackTrace() { + _Unwind_Backtrace(TraceFunction, nullptr); + } + +}; +#endif + +#endif \ No newline at end of file diff --git a/src/skel/crossplatform.cpp b/src/skel/crossplatform.cpp index 58ab7920..99ec599d 100644 --- a/src/skel/crossplatform.cpp +++ b/src/skel/crossplatform.cpp @@ -1,5 +1,6 @@ #include "common.h" #include "crossplatform.h" +#include "FileMgr.h" // Codes compatible with Windows and Linux #ifndef _WIN32 @@ -91,7 +92,11 @@ void GetDateFormat(int unused1, int unused2, SYSTEMTIME* in, int unused3, char* linuxTime.tm_hour = in->wHour; linuxTime.tm_min = in->wMinute; linuxTime.tm_sec = in->wSecond; - strftime(out, size, nl_langinfo(D_FMT), &linuxTime); +#if defined ANDROID + strftime(out, size, "%Y-%m-%d", &linuxTime); +#else + strftime(out, size, nl_langinfo(D_FMT), &linuxTime); +#endif } void FileTimeToSystemTime(time_t* writeTime, SYSTEMTIME* out) { @@ -212,6 +217,20 @@ char* casepath(char const* path, bool checkPathFirst) } else #endif +#if defined(ANDROID) // TODO: Android is fuck!!! + char cwd[MAX_PATH]; + getcwd(cwd, sizeof(cwd)); + + char* pos = strstr(p, CFileMgr::GetRootDirName()); + if (pos != NULL) { + pos += strlen(CFileMgr::GetRootDirName()); + p = pos; + } + d = opendir(cwd); + out[0] = '.'; + out[1] = 0; + rl = 1; +#else if (p[0] == '/' || p[0] == '\\') { d = opendir("/"); @@ -223,6 +242,7 @@ char* casepath(char const* path, bool checkPathFirst) out[1] = 0; rl = 1; } +#endif bool cantProceed = false; // just convert slashes in what's left in string, don't correct case of letters(because we can't) bool mayBeTrailingSlash = false; @@ -273,7 +293,7 @@ char* casepath(char const* path, bool checkPathFirst) if (!e) { - printf("casepath couldn't find dir/file \"%s\", full path was %s\n", c, path); + debug("casepath couldn't find dir/file \"%s\", full path was %s\n", c, path); // No match, add original name and continue converting further slashes. strcpy(out + rl, c); rl += strlen(c); @@ -288,7 +308,7 @@ char* casepath(char const* path, bool checkPathFirst) } if (rl > l + 2) { - printf("\n\ncasepath: Corrected path length is longer then original+2:\n\tOriginal: %s (%zu chars)\n\tCorrected: %s (%zu chars)\n\n", path, l, out, rl); + debug("\n\ncasepath: Corrected path length is longer then original+2:\n\tOriginal: %s (%zu chars)\n\tCorrected: %s (%zu chars)\n\n", path, l, out, rl); } return out; } diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h index 522e1be1..e48d3b2c 100644 --- a/src/skel/crossplatform.h +++ b/src/skel/crossplatform.h @@ -3,6 +3,10 @@ #include #include +#ifdef LIBRW_SDL2 +#include +#endif + // This is the common include for platform/renderer specific skeletons(glfw.cpp, win.cpp etc.) and using cross platform things (like Windows directories wrapper, platform specific global arrays etc.) // Functions that's different on glfw and win but have same signature, should be located on platform.h. @@ -62,16 +66,20 @@ int _caserename(const char *old_filename, const char *new_filename); #define caserename _caserename #endif -#ifdef RW_GL3 +#if defined(RW_GL3) typedef struct { +#if defined(LIBRW_SDL2) + SDL_Window* window; +#else GLFWwindow* window; +#endif RwBool fullScreen; RwV2d lastMousePos; double mouseWheel; // glfw doesn't cache it bool cursorIsInWindow; - RwInt8 joy1id; - RwInt8 joy2id; + RwInt8 joy1id; + RwInt8 joy2id; } psGlobalType; @@ -166,6 +174,7 @@ void GetDateFormat(int, int, SYSTEMTIME*, int, char*, int); #ifdef __SWITCH__ +#ifndef LIBRW_SDL2 // tweak glfw values for switch to match expected pc bindings #ifdef GLFW_GAMEPAD_BUTTON_A #undef GLFW_GAMEPAD_BUTTON_A @@ -186,6 +195,7 @@ void GetDateFormat(int, int, SYSTEMTIME*, int, char*, int); #undef GLFW_GAMEPAD_BUTTON_Y #endif #define GLFW_GAMEPAD_BUTTON_Y 2 +#endif #endif #endif diff --git a/src/skel/events.cpp b/src/skel/events.cpp index 87447819..f77f6e27 100644 --- a/src/skel/events.cpp +++ b/src/skel/events.cpp @@ -774,7 +774,7 @@ HandlePadButtonUp(RsPadButtonStatus *padButtonStatus) ControlsManager.UpdateJoyButtonState(padNumber); - for ( int32 i = 1; i < _TODOCONST(16); i++ ) + for ( int32 i = 0; i < _TODOCONST(16); i++ ) // 1 > 0 TODO: Fixed a button handling for-loop? { RsPadButtons btn = rsPADNULL; if ( ControlsManager.m_aButtonStates[i] == FALSE ) diff --git a/src/skel/sdl2/sdl2.cpp b/src/skel/sdl2/sdl2.cpp new file mode 100644 index 00000000..47b943f3 --- /dev/null +++ b/src/skel/sdl2/sdl2.cpp @@ -0,0 +1,1981 @@ +#if defined RW_GL3 && defined LIBRW_SDL2 + +long _dwOperatingSystemVersion; + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "rwcore.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "MemoryCard.h" +#include "Sprite2d.h" +#include "AnimViewer.h" +#include "Font.h" +#include "MemoryMgr.h" + +#if defined ANDROID +#include "JavaWrapper.h" +extern char* StorageRootBuffer; +#endif + +#define MAX_SUBSYSTEMS (16) + +rw::EngineOpenParams openParams; + +static RwBool ForegroundApp = TRUE; +static RwBool WindowIconified = FALSE; +static RwBool WindowFocused = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool useDefault; + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +static psGlobalType PsGlobal; + +static SDL_GameController* gamepad1 = nullptr; +static SDL_GameController* gamepad2 = nullptr; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +size_t _dwMemAvailPhys; +RwUInt32 gGameState; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +/* + ***************************************************************************** + */ +void _psCreateFolder(const char *path) +{ +#if defined(ANDROID) + const char* pathroot = StorageRootBuffer; + char dbPath[1024]; + snprintf(dbPath, sizeof(dbPath), "%s%s", pathroot, path); + mkdir(dbPath, 0755); + debug("Creating Folder Path: %s", dbPath); +#else + debug("Creating Folder Path: %s", path); + struct stat info; + char fullpath[PATH_MAX]; + realpath(path, fullpath); + + if (lstat(fullpath, &info) != 0) { + if (errno == ENOENT || (errno != EACCES && !S_ISDIR(info.st_mode))) { + debug("Creating Folder FullPath: %s", fullpath); + mkdir(fullpath, 0755); + } + } +#endif +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ + static char szUserFiles[256]; + strcpy(szUserFiles, "userfiles"); + _psCreateFolder(szUserFiles); + return szUserFiles; +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ +#ifdef LEGACY_MENU_OPTIONS + if (FrontEndMenuManager.m_PrefsVsync || FrontEndMenuManager.m_bMenuActive) +#else + if (FrontEndMenuManager.m_PrefsFrameLimiter || FrontEndMenuManager.m_bMenuActive) +#endif + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT); + + return; +} + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +double +psTimer(void) +{ + struct timespec start; +#if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &start); +#elif defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &start); +#else + clock_gettime(CLOCK_MONOTONIC, &start); +#endif + return start.tv_sec * 1000.0 + start.tv_nsec/1000000.0; +} + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + SDL_WarpMouseInWindow(PSGLOBAL(window), pos->x, pos->y); + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return true; +} + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ + +static void _psInitializeVibration() {} +static void _psHandleVibration() {} + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + PsGlobal.cursorIsInWindow = FALSE; + WindowFocused = TRUE; + WindowIconified = FALSE; + + PsGlobal.joy1id = -1; + PsGlobal.joy2id = -1; + + CFileMgr::Initialise(); + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short lang; + lang = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; + + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); + +#endif + + _psInitializeVibration(); + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes + +#ifndef PS2_MENU + FrontEndMenuManager.LoadSettings(); +#endif + +#ifndef __APPLE__ + struct sysinfo systemInfo; + sysinfo(&systemInfo); + _dwMemAvailPhys = systemInfo.freeram; + debug("Physical memory size %u\n", systemInfo.totalram); + debug("Available physical memory %u\n", systemInfo.freeram); +#else + uint64_t size = 0; + uint64_t page_size = 0; + size_t uint64_len = sizeof(uint64_t); + size_t ull_len = sizeof(unsigned long long); + sysctl((int[]){CTL_HW, HW_PAGESIZE}, 2, &page_size, &ull_len, NULL, 0); + sysctl((int[]){CTL_HW, HW_MEMSIZE}, 2, &size, &uint64_len, NULL, 0); + vm_statistics_data_t vm_stat; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stat, &count); + _dwMemAvailPhys = (uint64_t)(vm_stat.free_count * page_size); + debug("Physical memory size %llu\n", _dwMemAvailPhys); + debug("Available physical memory %llu\n", size); +#endif + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%d X %d X %d", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if (!_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode)) + { + RsGlobal.quit = TRUE; + printf("ERROR: Failed to select new screen resolution\n"); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + RwBool modeFound = FALSE; + + if (!useDefault) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if (!GnumSubSystems) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if (!useDefault) + { + if (_psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + printf("WARNING: Cannot find 640x480 video mode, selecting device cancelled\n"); + return FALSE; +#endif + } + } + } +#else + if (!useDefault) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + SDL_DisplayMode mode; + // TODO how to get displayIndex for the current display? + SDL_GetCurrentDisplayMode(0, &mode); + FrontEndMenuManager.m_nPrefsWidth = mode.w; + FrontEndMenuManager.m_nPrefsHeight = mode.h; + FrontEndMenuManager.m_nPrefsDepth = 32; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for(GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++){ + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)){ + bestWndMode = GcurSelVM; + } else { + // try the largest one that isn't larger than what we wanted + if(vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth && + vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight && + vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){ + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + } + } + } + + if(bestFsMode < 0){ + printf("WARNING: Cannot find desired video mode, selecting device cancelled\n"); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + /* + TODO + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + */ +#ifndef IMPROVED_VIDEOMODE + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + } +#else + RsGlobal.maximumWidth = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.maximumHeight = FrontEndMenuManager.m_nPrefsHeight; + RsGlobal.width = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.height = FrontEndMenuManager.m_nPrefsHeight; + + PSGLOBAL(fullScreen) = !FrontEndMenuManager.m_nPrefsWindowed; +#endif + +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +bool IsThisJoystickBlacklisted(int i) +{ +#ifndef DETECT_JOYSTICK_MENU + return false; +#else + if (SDL_IsGameController(i)) + return false; + + const char* joyname = SDL_JoystickNameForIndex(i); + + if (gSelectedJoystickName[0] != '\0' + && strncmp(joyname, gSelectedJoystickName, strlen(gSelectedJoystickName)) == 0) { + return false; + } + + return true; +#endif +} + +void _InputInitialiseJoys() +{ + PSGLOBAL(joy1id) = -1; + PSGLOBAL(joy2id) = -1; + + // Load our gamepad mappings + const char* EnvControlConfig = getenv("SDL_GAMECONTROLLERCONFIG_FILE"); + + if (EnvControlConfig != nil) { + if (SDL_GameControllerAddMappingsFromFile(EnvControlConfig) <= 0) { + Error("Could not load custom controller mapping (SDL_GAMECONTROLLERCONFIG_FILE env variable\n)"); + } + } else { +#if defined ANDROID + const char* pathRoot = getenv("STORAGE_ROOT"); + char SDL_GAMEPAD_DB_PATH[MAX_PATH]; + snprintf(SDL_GAMEPAD_DB_PATH, sizeof(SDL_GAMEPAD_DB_PATH), "%s%s", pathRoot, "gamecontrollerdb.txt"); +#else + const char* SDL_GAMEPAD_DB_PATH = "gamecontrollerdb.txt"; +#endif + if (SDL_GameControllerAddMappingsFromFile(SDL_GAMEPAD_DB_PATH) <= 0) { + debug ("You don't seem to have copied %s file from reVC/gamefiles " + "to GTA: Vice City directory. Some gamepads may not be recognized.\n", + SDL_GAMEPAD_DB_PATH); + } + } + + // TODO SDL2 the part below seems unnecessary SDL2 (at least on Linux), remove in the future + /*for (int i = 0; i <= SDL_NumJoysticks(); i++) { + if (!IsThisJoystickBlacklisted(i)) { + if (PSGLOBAL(joy1id) == -1) + PSGLOBAL(joy1id) = i; + else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = i; + else + break; + } + } + + if (PSGLOBAL(joy1id) != -1) { + SDL_Joystick* joy1 = SDL_JoystickOpen(PSGLOBAL(joy1id)); + int count = SDL_JoystickNumButtons(joy1); + SDL_JoystickClose(joy1); +#ifdef DETECT_JOYSTICK_MENU + strncpy(gSelectedJoystickName, SDL_JoystickNameForIndex(PSGLOBAL(joy1id)), sizeof(gSelectedJoystickName)); +#endif + ControlsManager.InitDefaultControlConfigJoyPad(count); + }*/ +} + +#if 0 +TODO SDL2 +-long _InputInitialiseMouse() ++int lastCursorMode = GLFW_CURSOR_HIDDEN; ++long _InputInitialiseMouse(bool exclusive) + { +- glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); ++ // Disabled = keep cursor centered and hide ++ lastCursorMode = exclusive ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_HIDDEN; ++ glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, lastCursorMode); + return 0; + } +#endif + +long _InputInitialiseMouse(bool exclusive) +{ + // TODO SDL2 what to do about exclusive? + SDL_ShowCursor(SDL_DISABLE); + return 0; +} + +void _InputShutdownMouse() +{ + // Not needed +} + +bool _InputMouseNeedsExclusive() +{ +#if 0 + TODO SDL2 + // That was the cause of infamous mouse bug on Win. + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + // If windowed, free the cursor on menu(where this func. is called and DISABLED-HIDDEN transition is done accordingly) + // If it's fullscreen, be sure that it didn't stuck on HIDDEN. + return !(vm.flags & rwVIDEOMODEEXCLUSIVE) || lastCursorMode == GLFW_CURSOR_HIDDEN; +#endif + return false; +} + +void psPostRWinit(void) +{ + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + _InputInitialiseJoys(); + _InputInitialiseMouse(false); + + if(!(vm.flags & rwVIDEOMODEEXCLUSIVE)) + SDL_SetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); + + // Make sure all keys are released + CPad::GetPad(0)->Clear(true); + CPad::GetPad(1)->Clear(true); +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if (RsEventHandler(rsRWINITIALIZE, &openParams) == rsEVENTERROR) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + psPostRWinit(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwChar ** +CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +{ + RwInt32 numArgs = 0; + RwBool inArg, inString; + RwInt32 i, len; + RwChar *res, *str, **aptr; + + len = strlen(cmdLine); + + /* + * Count the number of arguments... + */ + inString = FALSE; + inArg = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if (inArg) + { + inArg = FALSE; + + numArgs++; + } + } + else if( !inArg ) + { + inArg = TRUE; + } + } + + /* + * Allocate memory for result... + */ + res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); + str = res + sizeof(RwChar *) * numArgs; + aptr = (RwChar **)res; + + strcpy(str, cmdLine); + + /* + * Walk through cmdLine again this time setting pointer to each arg... + */ + inArg = FALSE; + inString = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if (inArg) + { + if( str[i-1] == '"' ) + { + str[i-1] = '\0'; + } + else + { + str[i] = '\0'; + } + + inArg = FALSE; + } + } + else if (!inArg && cmdLine[i] != '"') + { + inArg = TRUE; + + *aptr++ = &str[i]; + } + } + + *argCount = numArgs; + + return (RwChar **)res; +} + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short primUserLCID, primSystemLCID; + primUserLCID = primSystemLCID = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; + + short primLayout = !strncmp(keyboardLang, "fr_",3) ? LANG_FRENCH : (!strncmp(keyboardLang, "de_",3) ? LANG_GERMAN : LANG_ENGLISH); + + short subUserLCID, subSystemLCID; + subUserLCID = subSystemLCID = !strncmp(systemLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + short subLayout = !strncmp(keyboardLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + FrontEndMenuManager.m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + FrontEndMenuManager.OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + + // TODO this is needed for strcasecmp to work correctly across all languages, but can these cause other problems?? + setlocale(LC_CTYPE, "C"); + setlocale(LC_COLLATE, "C"); + setlocale(LC_NUMERIC, "C"); + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ + +void HandleExit() +{ + // We now handle terminate message always, why handle on some cases? + return; +} + +void terminateHandler(int sig, siginfo_t *info, void *ucontext) { +#if defined(ANDROID) + if(g_pJavaWrapper) + g_pJavaWrapper->ExitGame(); +#endif + RsGlobal.quit = TRUE; +} + +#ifdef FLUSHABLE_STREAMING +void dummyHandler(int sig){ + // Don't kill the app pls +} +#endif + +void resizeCB(int width, int height) { + /* + * Handle event to ensure window contents are displayed during re-size + * as this can be disabled by the user, then if there is not enough + * memory things don't work. + */ + /* redraw window */ + + if (RwInitialised && gGameState == GS_PLAYING_GAME) + { + RsEventHandler(rsIDLE, (void *)TRUE); + } + + if (RwInitialised && height > 0 && width > 0) { + RwRect r; + + // TODO fix artifacts of resizing with mouse + RsGlobal.maximumHeight = height; + RsGlobal.maximumWidth = width; + + r.x = 0; + r.y = 0; + r.w = width; + r.h = height; + + RsEventHandler(rsCAMERASIZE, &r); + } +} + +void scrollCB(double xoffset, double yoffset) { + PSGLOBAL(mouseWheel) = yoffset; +} + +bool lshiftStatus = false; +bool rshiftStatus = false; + +static const std::unordered_map keymap = { + {SDLK_SPACE, ' '}, + {SDLK_QUOTE, '\''}, + {SDLK_COMMA, ','}, + {SDLK_MINUS, '-'}, + {SDLK_PERIOD, '.'}, + {SDLK_SLASH, '/'}, + {SDLK_0, '0'}, + {SDLK_1, '1'}, + {SDLK_2, '2'}, + {SDLK_3, '3'}, + {SDLK_4, '4'}, + {SDLK_5, '5'}, + {SDLK_6, '6'}, + {SDLK_7, '7'}, + {SDLK_8, '8'}, + {SDLK_9, '9'}, + {SDLK_SEMICOLON, ';'}, + {SDLK_EQUALS, '='}, + {SDLK_LEFTBRACKET, '['}, + {SDLK_BACKSLASH, '\\'}, + {SDLK_RIGHTBRACKET, ']'}, + {SDLK_BACKQUOTE, '`'}, + {SDLK_ESCAPE, rsESC}, + {SDLK_RETURN, rsENTER}, + {SDLK_TAB, rsTAB}, + {SDLK_BACKSPACE, rsBACKSP}, + {SDLK_INSERT, rsINS}, + {SDLK_DELETE, rsDEL}, + {SDLK_RIGHT, rsRIGHT}, + {SDLK_LEFT, rsLEFT}, + {SDLK_DOWN, rsDOWN}, + {SDLK_UP, rsUP}, + {SDLK_PAGEUP, rsPGUP}, + {SDLK_PAGEDOWN, rsPGDN}, + {SDLK_HOME, rsHOME}, + {SDLK_END, rsEND}, + {SDLK_CAPSLOCK, rsCAPSLK}, + {SDLK_SCROLLLOCK, rsSCROLL}, + //{SDLK_PRINTSCREEN, rsNULL}, + {SDLK_PAUSE, rsPAUSE}, + {SDLK_F1, rsF1}, + {SDLK_F2, rsF2}, + {SDLK_F3, rsF3}, + {SDLK_F4, rsF4}, + {SDLK_F5, rsF5}, + {SDLK_F6, rsF6}, + {SDLK_F7, rsF7}, + {SDLK_F8, rsF8}, + {SDLK_F9, rsF9}, + {SDLK_F10, rsF10}, + {SDLK_F11, rsF11}, + {SDLK_F12, rsF12}, + //{SDLK_F13, rsNULL}, + //{SDLK_F14, rsNULL}, + //{SDLK_F15, rsNULL}, + //{SDLK_F16, rsNULL}, + //{SDLK_F17, rsNULL}, + //{SDLK_F18, rsNULL}, + //{SDLK_F19, rsNULL}, + //{SDLK_F20, rsNULL}, + //{SDLK_F21, rsNULL}, + //{SDLK_F22, rsNULL}, + //{SDLK_F23, rsNULL}, + //{SDLK_F24, rsNULL}, + //{SDLK_F25, rsNULL}, + {SDLK_KP_0, rsPADINS}, + {SDLK_KP_1, rsPADEND}, + {SDLK_KP_2, rsPADDOWN}, + {SDLK_KP_3, rsPADPGDN}, + {SDLK_KP_4, rsPADLEFT}, + {SDLK_KP_5, rsPAD5}, + {SDLK_KP_6, rsPADRIGHT}, + {SDLK_KP_7, rsPADHOME}, + {SDLK_KP_8, rsPADUP}, + {SDLK_KP_9, rsPADPGUP}, + {SDLK_KP_DECIMAL, rsPADDEL}, + {SDLK_KP_DIVIDE, rsDIVIDE}, + {SDLK_KP_MULTIPLY, rsTIMES}, + {SDLK_KP_MINUS, rsMINUS}, + {SDLK_KP_PLUS, rsPLUS}, + {SDLK_KP_ENTER, rsPADENTER}, + //{SDLK_KP_EQUAL, rsNULL}, + {SDLK_LSHIFT, rsLSHIFT}, + {SDLK_LCTRL, rsLCTRL}, + {SDLK_LALT, rsLALT}, + {SDLK_LGUI, rsLWIN}, + {SDLK_RSHIFT, rsRSHIFT}, + {SDLK_RCTRL, rsRCTRL}, + {SDLK_RALT, rsRALT}, + {SDLK_RGUI, rsRWIN}, + //{SDLK_MENU, rsNULL} +}; + +void +keypressCB(int key, int action, int mods) +{ + RsKeyCodes ks = rsNULL; + + if (key <= 0) + return; + + // for a mysterious reason, isalpha() crashes for e.g. SDLK_VOLUMEUP/DOWN + if (key >= 'A' && key <= 'Z') { + ks = (RsKeyCodes) key; + } else if (key >= 'a' && key <= 'z') { + ks = (RsKeyCodes) toupper(key); + } else { + auto it = keymap.find(key); + + if (it != keymap.end()) { + ks = (RsKeyCodes) it->second; + } + } + + if (key == SDLK_LSHIFT) + lshiftStatus = (action != SDL_KEYUP); + + if (key == SDLK_RSHIFT) + rshiftStatus = (action != SDL_KEYUP); + + if (ks == rsNULL) + return; + + switch (action) { + case SDL_KEYDOWN: RsKeyboardEventHandler(rsKEYDOWN, &ks); break; + case SDL_KEYUP: RsKeyboardEventHandler(rsKEYUP, &ks); break; + } +} + +// R* calls that in ControllerConfig, idk why +void +_InputTranslateShiftKeyUpDown(RsKeyCodes *rs) { + RsKeyboardEventHandler(lshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsLSHIFT)); + RsKeyboardEventHandler(rshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsRSHIFT)); +} + +void +cursorCB(double xpos, double ypos) { + if (!FrontEndMenuManager.m_bMenuActive) + return; + + // TODO remove? + //int winw, winh; + //SDL_GetWindowSize(PSGLOBAL(window), &winw, &winh); + FrontEndMenuManager.m_nMouseTempPosX = xpos; // * (RsGlobal.maximumWidth / winw); + FrontEndMenuManager.m_nMouseTempPosY = ypos; // * (RsGlobal.maximumHeight / winh); +} + +void +cursorEnterCB(int entered) { + PSGLOBAL(cursorIsInWindow) = !!entered; +} + +void +windowFocusCB(int focused) { + WindowFocused = !!focused; +} + +void +windowIconifyCB(int iconified) { + WindowIconified = !!iconified; +} + +void inputEventHandler() { + SDL_Event event; + + if (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_KEYDOWN: /* fall-through */ + case SDL_KEYUP: + keypressCB(event.key.keysym.sym, event.type, 0); + break; + + case SDL_MOUSEMOTION: cursorCB(event.motion.x, event.motion.y); break; + case SDL_MOUSEWHEEL: scrollCB(event.wheel.x, event.wheel.y); break; + + // note that SDL_CONTROLLERDEVICEADDED/REMOVED exists, but it did not work for me + case SDL_JOYDEVICEADDED: /* fall-through */ + case SDL_JOYDEVICEREMOVED: + joysChangeCB(event.jdevice.which, event.type); + break; + + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_ENTER: cursorEnterCB(true); break; + case SDL_WINDOWEVENT_LEAVE: cursorEnterCB(false); break; + case SDL_WINDOWEVENT_FOCUS_GAINED: windowFocusCB(true); break; + case SDL_WINDOWEVENT_FOCUS_LOST: windowFocusCB(false); break; + // TODO should it be minimized/maximized/restored instead of shown/hidden? + case SDL_WINDOWEVENT_SHOWN: windowIconifyCB(false); break; + case SDL_WINDOWEVENT_HIDDEN: windowIconifyCB(true); break; + } + break; + + default: + break; + } + } +} + +/* + ***************************************************************************** + */ +int +main(int argc, char *argv[]) +{ + RwV2d pos; + RwInt32 i; + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + + struct sigaction act; + act.sa_sigaction = terminateHandler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGTERM, &act, NULL); +#ifdef FLUSHABLE_STREAMING + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = dummyHandler; + sa.sa_flags = 0; + sigaction(SIGUSR1, &sa, NULL); +#endif + + for(i = 1; i < argc; i++) + { + if(strcmp(argv[i], "--dir") == 0 && i + 1 < argc) { + const char *gamePath = argv[i+1]; + setenv("STORAGE_ROOT", gamePath, 1); + } + } + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + + for(i=1; iGetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +//#ifndef NO_MOVIES +// CloseClip(); +// CoUninitialize(); +//#endif +// +// if (CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN) +// PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); +// else +// PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { +// CPad::UpdatePads(); +// +// if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0) + ++gGameState; +// else if (CPad::GetPad(0)->GetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { + //CoUninitialize(); + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + FrontEndMenuManager.m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + if(!WindowIconified) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!FrontEndMenuManager.m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + + +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + + return 0; +} + +/* + ***************************************************************************** + */ + +RwV2d leftStickPos; +RwV2d rightStickPos; + +void CapturePad(RwInt32 padID) +{ + static SDL_GameController* gamepad = nullptr; + + if (padID == 0) + gamepad = gamepad1; + else if(padID == 1) + gamepad = gamepad2; + else + assert("invalid padID"); + + if (gamepad == nullptr) + return; + + SDL_Joystick* joy = SDL_GameControllerGetJoystick(gamepad); + int joyId = SDL_JoystickInstanceID(joy); + int numAxes = SDL_JoystickNumAxes(joy); + + if (ControlsManager.m_bFirstCapture == false) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + } else { + // In case connected gamepad doesn't have L-R trigger axes. + ControlsManager.m_NewState.mappedButtons[15] = 0; // left trigger + ControlsManager.m_NewState.mappedButtons[16] = 0; // right trigger + } + + // Update buttons state + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) { + int state = SDL_GameControllerGetButton(gamepad, (SDL_GameControllerButton) i); + assert(state == 0 || state == 1); + ControlsManager.m_NewState.buttons[i] = state; + ControlsManager.m_NewState.mappedButtons[i] = !!state; + } + + ControlsManager.m_NewState.numButtons = SDL_CONTROLLER_BUTTON_MAX - 1; + ControlsManager.m_NewState.id = joyId; + ControlsManager.m_NewState.isGamepad = true; // SDL_IsGameController(joyId); + + if (ControlsManager.m_NewState.isGamepad) { + // TRIGGERLEFT/RIGHT are in range 0..32767, which needs to be converted to -1 (released) .. 1 (pressed) + float lt = (SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT) - 16384.0) / 16384.0; + float rt = (SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) - 16384.0) / 16384.0; + + // glfw returns 0.0 for non-existent axises(which is bullocks) so we treat it as deadzone, and keep value of previous frame. + // otherwise if this axis is present, -1 = released, 1 = pressed + if (lt != 0.0f) + ControlsManager.m_NewState.mappedButtons[15] = lt > -0.8f; + + if (rt != 0.0f) + ControlsManager.m_NewState.mappedButtons[16] = rt > -0.8f; + } + // TODO? L2-R2 axes(not buttons-that's fine) on joysticks that don't have SDL gamepad mapping AREN'T handled, and I think it's impossible to do without mapping. + + if (ControlsManager.m_bFirstCapture == true) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + ControlsManager.m_bFirstCapture = false; + } + + RsPadButtonStatus bs; + bs.padID = padID; + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + // Gamepad axes are guaranteed to return 0.0f if that particular gamepad doesn't have that axis. + // And that's really good for sticks, because gamepads return 0.0 for them when sticks are in released state. + // Stick position is converted from range [-32768; 32767] to [-1; +1] + leftStickPos.x = (ControlsManager.m_NewState.isGamepad && numAxes >= 1) ? SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_LEFTX) / 32768.0f : 0.0f; + leftStickPos.y = (ControlsManager.m_NewState.isGamepad && numAxes >= 2) ? SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_LEFTY) / 32768.0f : 0.0f; + + rightStickPos.x = (ControlsManager.m_NewState.isGamepad && numAxes >= 3) ? SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_RIGHTX) / 32768.0f : 0.0f; + rightStickPos.y = (ControlsManager.m_NewState.isGamepad && numAxes >= 4) ? SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_RIGHTY) / 32768.0f : 0.0f; + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + CPad *pad = CPad::GetPad(bs.padID); + + if (Abs(leftStickPos.x) > ControlsManager.m_lStickDeadzone) + pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f * ControlsManager.m_lStickSensX); + + if (Abs(leftStickPos.y) > ControlsManager.m_lStickDeadzone) + pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f * ControlsManager.m_lStickSensY); + + if (Abs(rightStickPos.x) > ControlsManager.m_rStickDeadzone) + pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f * ControlsManager.m_rStickSensX); + + if (Abs(rightStickPos.y) > ControlsManager.m_rStickDeadzone) + pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f * ControlsManager.m_rStickSensY); + } + + _psHandleVibration(); +} + +void joysChangeCB(int jid, int event) +{ + if (event == SDL_JOYDEVICEADDED && !IsThisJoystickBlacklisted(jid)) { + if (PSGLOBAL(joy1id) == -1) { + assert(gamepad1 == nullptr); + gamepad1 = SDL_GameControllerOpen(jid); + assert(gamepad1 != nullptr); +#ifdef DETECT_JOYSTICK_MENU + strncpy(gSelectedJoystickName, SDL_JoystickNameForIndex(jid), sizeof(gSelectedJoystickName)); +#endif + // This is behind LOAD_INI_SETTINGS, because otherwise the Init call below will destroy/overwrite your bindings. +#ifdef LOAD_INI_SETTINGS + SDL_Joystick* joy = SDL_GameControllerGetJoystick(gamepad1); + int count = SDL_JoystickNumButtons(joy); + ControlsManager.InitDefaultControlConfigJoyPad(count); + PSGLOBAL(joy1id) = SDL_JoystickInstanceID(joy); // needed to get right ID for remove event + // Do not close the controller handle, it is done in the device remove handler (below) +#endif + } else if (PSGLOBAL(joy2id) == -1) { + assert(gamepad2 == nullptr); + gamepad2 = SDL_GameControllerOpen(jid); + assert(gamepad2 != nullptr); + SDL_Joystick* joy = SDL_GameControllerGetJoystick(gamepad2); + PSGLOBAL(joy2id) = SDL_JoystickInstanceID(joy); // needed to get right ID for remove event + } + + } else if (event == SDL_JOYDEVICEREMOVED) { + if (PSGLOBAL(joy1id) == jid) { + assert(gamepad1 != nullptr); + SDL_GameControllerClose(gamepad1); + PSGLOBAL(joy1id) = -1; + gamepad1 = nullptr; + } else if (PSGLOBAL(joy2id) == jid) { + assert(gamepad2 != nullptr); + SDL_GameControllerClose(gamepad2); + PSGLOBAL(joy2id) = -1; + gamepad2 = nullptr; + } + } +} + +#endif diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp index 08647187..281b71e0 100644 --- a/src/vehicles/Automobile.cpp +++ b/src/vehicles/Automobile.cpp @@ -54,6 +54,11 @@ bool bAllCarCheat; +#if defined ANDROID +int16 g_usLastProcessedModelIndexAutomobile = 0; +int g_iLastProcessedModelIndexAutoEnt = 0; +#endif + RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data); bool CAutomobile::m_sAllTaxiLights; @@ -3024,7 +3029,10 @@ CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) { int i; CColModel *colModel; - +#if defined ANDROID + g_usLastProcessedModelIndexAutomobile = ent->GetModelIndex(); + g_iLastProcessedModelIndexAutoEnt = ent->GetModelIndex(); +#endif if(GetStatus() != STATUS_SIMPLE) bVehicleColProcessed = true;