From a83763c2c3ba38f1668cc2abe1e1306cf34b54be Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 1 Dec 2023 23:00:41 +0100 Subject: [PATCH] (Android) Added preliminary support for copying assets to the internal data directory --- CMakeLists.txt | 2 +- es-app/src/main.cpp | 4 +++ es-core/src/ThemeData.cpp | 2 ++ es-core/src/resources/ResourceManager.cpp | 43 ++++++++++++++++++++--- es-core/src/resources/ResourceManager.h | 13 +++++-- es-core/src/utils/PlatformUtil.cpp | 42 +++++++++++++++++++++- es-core/src/utils/PlatformUtil.h | 1 + 7 files changed, 97 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5775c8c9d..6552d1eb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -413,7 +413,7 @@ add_compile_definitions(GLM_FORCE_XYZW_ONLY) # For Unix systems, assign the installation prefix. If it's not explicitly set, # we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD. -if(NOT WIN32 AND NOT APPLE) +if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(CMAKE_SYSTEM_NAME MATCHES Linux) set(CMAKE_INSTALL_PREFIX /usr CACHE INTERNAL CMAKE_INSTALL_PREFIX) diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 8f13f39c9..fe0c191af 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -719,6 +719,10 @@ int main(int argc, char* argv[]) renderer = Renderer::getInstance(); window = Window::getInstance(); +#if defined(__ANDROID__) + Utils::Platform::Android::setupResources(); +#endif + ViewController::getInstance()->setMenuColors(); CollectionSystemsManager::getInstance(); Screensaver screensaver; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 4cfdfa06c..fed58c4cd 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -709,6 +709,8 @@ void ThemeData::populateThemes() Utils::FileSystem::getExePath() + "/themes", #if defined(__APPLE__) Utils::FileSystem::getExePath() + "/../Resources/themes", +#elif defined(__ANDROID__) + ResourceManager::getInstance().getDataDirectory() + "/themes", #elif defined(__unix__) && !defined(APPIMAGE_BUILD) Utils::FileSystem::getProgramDataPath() + "/themes", #endif diff --git a/es-core/src/resources/ResourceManager.cpp b/es-core/src/resources/ResourceManager.cpp index b14e11a4f..d4f5e3201 100644 --- a/es-core/src/resources/ResourceManager.cpp +++ b/es-core/src/resources/ResourceManager.cpp @@ -42,14 +42,17 @@ std::string ResourceManager::getResourcePath(const std::string& path, bool termi return applePackagePath; } #elif defined(__unix__) && !defined(APPIMAGE_BUILD) +#if defined(__ANDROID__) + std::string testDataPath {mDataDirectory + "/resources/" + &path[2]}; +#else // Check under the data installation directory (Unix only). std::string testDataPath {Utils::FileSystem::getProgramDataPath() + "/resources/" + &path[2]}; - - if (Utils::FileSystem::exists(testDataPath)) { - return testDataPath; - } #endif + if (Utils::FileSystem::exists(testDataPath)) + return testDataPath; +#endif +#if !defined(__ANDROID__) // Check under the ES executable directory. std::string testExePath {Utils::FileSystem::getExePath() + "/resources/" + &path[2]}; @@ -60,6 +63,14 @@ std::string ResourceManager::getResourcePath(const std::string& path, bool termi // indicate that we have a broken EmulationStation installation. If the argument // terminateOnFailure is set to false though, then skip this step. else { +#else + SDL_RWops* resFile {SDL_RWFromFile(path.substr(2).c_str(), "rb")}; + if (resFile != nullptr) { + SDL_RWclose(resFile); + return path.substr(2); + } + else { +#endif if (terminateOnFailure) { LOG(LogError) << "Program resource missing: " << path; LOG(LogError) << "Tried to find the resource in the following locations:"; @@ -69,7 +80,9 @@ std::string ResourceManager::getResourcePath(const std::string& path, bool termi #elif defined(__unix__) && !defined(APPIMAGE_BUILD) LOG(LogError) << testDataPath; #endif +#if !defined(__ANDROID__) LOG(LogError) << testExePath; +#endif LOG(LogError) << "Has EmulationStation been properly installed?"; Utils::Platform::emergencyShutdown(); } @@ -88,10 +101,19 @@ const ResourceData ResourceManager::getFileData(const std::string& path) const // Check if its a resource. const std::string respath {getResourcePath(path)}; +#if defined(__ANDROID__) + SDL_RWops* resFile {SDL_RWFromFile(respath.c_str(), "rb")}; + if (resFile != nullptr) { + ResourceData data {loadFile(resFile)}; + SDL_RWclose(resFile); + return data; + } +#else if (Utils::FileSystem::exists(respath)) { ResourceData data {loadFile(respath)}; return data; } +#endif // If the file doesn't exist, return an "empty" ResourceData. ResourceData data {nullptr, 0}; @@ -107,7 +129,7 @@ ResourceData ResourceManager::loadFile(const std::string& path) const #endif stream.seekg(0, stream.end); - size_t size {static_cast(stream.tellg())}; + const size_t size {static_cast(stream.tellg())}; stream.seekg(0, stream.beg); // Supply custom deleter to properly free array. @@ -120,6 +142,17 @@ ResourceData ResourceManager::loadFile(const std::string& path) const return ret; } +ResourceData ResourceManager::loadFile(SDL_RWops* resFile) const +{ + const size_t size {static_cast(SDL_RWsize(resFile))}; + std::shared_ptr data {new unsigned char[size], + [](unsigned char* p) { delete[] p; }}; + SDL_RWread(resFile, reinterpret_cast(data.get()), 1, size); + + ResourceData ret {data, size}; + return ret; +} + bool ResourceManager::fileExists(const std::string& path) const { // If it exists as a resource file, return true. diff --git a/es-core/src/resources/ResourceManager.h b/es-core/src/resources/ResourceManager.h index 1f41e864a..526926de6 100644 --- a/es-core/src/resources/ResourceManager.h +++ b/es-core/src/resources/ResourceManager.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// EmulationStation Desktop Edition +// ES-DE // ResourceManager.h // // Handles the application resources (fonts, graphics, sounds etc.). @@ -14,6 +14,8 @@ #include #include +#include + // The ResourceManager exists to: // Allow loading resources embedded into the executable like an actual file. // Allow embedded resources to be optionally remapped to actual files for further customization. @@ -42,6 +44,9 @@ public: void unloadAll(); void reloadAll(); + void setDataDirectory(const std::string& dataDirectory) { mDataDirectory = dataDirectory; } + const std::string& getDataDirectory() const { return mDataDirectory; } + std::string getResourcePath(const std::string& path, bool terminateOnFailure = true) const; const ResourceData getFileData(const std::string& path) const; bool fileExists(const std::string& path) const; @@ -49,9 +54,11 @@ public: private: ResourceManager() noexcept {} - std::list> mReloadables; - ResourceData loadFile(const std::string& path) const; + ResourceData loadFile(SDL_RWops* resFile) const; + + std::list> mReloadables; + std::string mDataDirectory; }; #endif // ES_CORE_RESOURCES_RESOURCE_MANAGER_H diff --git a/es-core/src/utils/PlatformUtil.cpp b/es-core/src/utils/PlatformUtil.cpp index 3ad639707..d9de622e5 100644 --- a/es-core/src/utils/PlatformUtil.cpp +++ b/es-core/src/utils/PlatformUtil.cpp @@ -387,13 +387,53 @@ namespace Utils return result; } + bool setupResources() + { + JNIEnv* jniEnv {reinterpret_cast(SDL_AndroidGetJNIEnv())}; + { + jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")}; + jmethodID methodID {jniEnv->GetStaticMethodID(jniClass, "getDataDirectory", + "()Ljava/lang/String;")}; + jstring dataDirectory { + static_cast(jniEnv->CallStaticObjectMethod(jniClass, methodID))}; + const char* dataDirUtf {jniEnv->GetStringUTFChars(dataDirectory, nullptr)}; + ResourceManager::getInstance().setDataDirectory(std::string(dataDirUtf)); + jniEnv->ReleaseStringUTFChars(dataDirectory, dataDirUtf); + jniEnv->DeleteLocalRef(jniClass); + } + { + jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")}; + jmethodID methodID { + jniEnv->GetStaticMethodID(jniClass, "setupResources", "()Z")}; + const bool returnValue { + static_cast(jniEnv->CallStaticBooleanMethod(jniClass, methodID))}; + jniEnv->DeleteLocalRef(jniClass); + if (returnValue) { + LOG(LogError) << "Couldn't setup application resources on internal storage"; + return true; + } + } + { + jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")}; + jmethodID methodID {jniEnv->GetStaticMethodID(jniClass, "setupThemes", "()Z")}; + const bool returnValue { + static_cast(jniEnv->CallStaticBooleanMethod(jniClass, methodID))}; + jniEnv->DeleteLocalRef(jniClass); + if (returnValue) { + LOG(LogError) << "Couldn't setup application themes on internal storage"; + return true; + } + } + return false; + } + bool checkEmulatorInstalled(const std::string& packageName, const std::string& activity) { JNIEnv* jniEnv {reinterpret_cast(SDL_AndroidGetJNIEnv())}; jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")}; jmethodID methodID {jniEnv->GetStaticMethodID( jniClass, "checkEmulatorInstalled", "(Ljava/lang/String;Ljava/lang/String;)Z")}; - bool returnValue {static_cast(jniEnv->CallStaticBooleanMethod( + const bool returnValue {static_cast(jniEnv->CallStaticBooleanMethod( jniClass, methodID, jniEnv->NewStringUTF(packageName.c_str()), jniEnv->NewStringUTF(activity.c_str())))}; // jniEnv->DeleteLocalRef(jniClass); diff --git a/es-core/src/utils/PlatformUtil.h b/es-core/src/utils/PlatformUtil.h index ff64bfbd3..7c041f0bf 100644 --- a/es-core/src/utils/PlatformUtil.h +++ b/es-core/src/utils/PlatformUtil.h @@ -60,6 +60,7 @@ namespace Utils namespace Android { bool requestStoragePermission(); + bool setupResources(); bool checkEmulatorInstalled(const std::string& packageName, const std::string& activity); int launchGame(const std::string& packageName,