(Android) Updated the game launching logic

This commit is contained in:
Leon Styhre 2023-12-07 18:51:09 +01:00
parent d87bb4e3a4
commit 3ae7bc6b5b
4 changed files with 230 additions and 115 deletions

2
.gitignore vendored
View file

@ -34,7 +34,7 @@ emulationstation.worker.js
/ROMs
# Android build
android
/android
# AppImage
AppDir

View file

@ -882,6 +882,7 @@ void FileData::launchGame()
std::string commandRaw {command};
std::string romPath {Utils::FileSystem::getEscapedPath(mPath)};
std::string baseName {Utils::FileSystem::getStem(mPath)};
std::string romRaw {Utils::FileSystem::getPreferredPath(mPath)};
// For the special case where a directory has a supported file extension and is therefore
// interpreted as a file, check if there is a matching filename inside the directory.
@ -890,7 +891,11 @@ void FileData::launchGame()
for (std::string& file : Utils::FileSystem::getDirContent(mPath)) {
if (Utils::FileSystem::getFileName(file) == Utils::FileSystem::getFileName(mPath) &&
(Utils::FileSystem::isRegularFile(file) || Utils::FileSystem::isSymlink(file))) {
#if defined(__ANDROID__)
romRaw = file;
#else
romPath = Utils::FileSystem::getEscapedPath(file);
#endif
baseName = baseName.substr(0, baseName.find("."));
break;
}
@ -898,7 +903,6 @@ void FileData::launchGame()
}
const std::string fileName {baseName + Utils::FileSystem::getExtension(romPath)};
const std::string romRaw {Utils::FileSystem::getPreferredPath(mPath)};
const std::string esPath {Utils::FileSystem::getExePath()};
bool runInBackground {false};
@ -951,8 +955,12 @@ void FileData::launchGame()
std::string androidPackage;
std::string androidActivity;
std::string androidAction;
std::string androidFileAsURI;
std::vector<std::pair<std::string, std::string>> androidExtras;
std::string androidCategory;
std::string androidMimeType;
std::string androidData;
std::map<std::string, std::string> androidExtrasString;
std::map<std::string, std::string> androidExtrasBool;
std::vector<std::string> androidActivityFlags;
#endif
#if defined(_WIN64)
@ -1119,18 +1127,6 @@ void FileData::launchGame()
androidPackage = androidPackage.substr(0, separatorPos);
}
separatorPos = androidPackage.find('|');
if (separatorPos != std::string::npos) {
androidAction = androidPackage.substr(separatorPos + 1);
androidPackage = androidPackage.substr(0, separatorPos);
}
separatorPos = androidActivity.find('|');
if (separatorPos != std::string::npos) {
androidAction = androidActivity.substr(separatorPos + 1);
androidActivity = androidActivity.substr(0, separatorPos);
}
LOG(LogDebug) << "FileData::launchGame(): Found emulator package \"" << androidPackage
<< "\"";
}
@ -1609,6 +1605,7 @@ void FileData::launchGame()
#endif
#endif
#if !defined(__ANDROID__)
// Replace the remaining variables with their actual values.
command = Utils::String::replace(command, "%ROM%", romPath);
command = Utils::String::replace(command, "%BASENAME%", baseName);
@ -1616,12 +1613,64 @@ void FileData::launchGame()
command = Utils::String::replace(command, "%ROMRAW%", romRaw);
command = Utils::String::replace(command, "%ROMPATH%",
Utils::FileSystem::getEscapedPath(getROMDirectory()));
#if defined(__ANDROID__)
if (command.find("%ROMURI%") != std::string::npos)
androidFileAsURI = romRaw;
#else
command = Utils::String::replace(command, "%ANDROIDPACKAGE%", androidPackage);
size_t extraPos {command.find("%EXTRA_")};
const std::vector<std::string> androidVariabels {
"%ACTION%=", "%CATEGORY%=", "%MIMETYPE%=", "%DATA%="};
for (std::string variable : androidVariabels) {
size_t dataPos {command.find(variable)};
if (dataPos != std::string::npos) {
bool invalidEntry {false};
bool isQuoted {(command.length() > dataPos + variable.length() &&
command[dataPos + variable.length()] == '\"')};
std::string value;
if (isQuoted) {
const size_t closeQuotePos {command.find("\"", dataPos + variable.length() + 1)};
if (closeQuotePos != std::string::npos)
value = command.substr(dataPos + variable.length() + 1,
closeQuotePos - (dataPos + variable.length() + 1));
else
invalidEntry = true;
}
else {
const size_t spacePos {command.find(" ", dataPos)};
if (spacePos != std::string::npos)
value = command.substr(dataPos + variable.length(),
spacePos - (dataPos + variable.length()));
else
value = command.substr(dataPos + variable.length(),
command.size() - dataPos + variable.length());
}
if (invalidEntry) {
LOG(LogError) << "Invalid entry in systems configuration file es_systems.xml";
LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE", 6000);
window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true);
return;
}
if (variable == "%ACTION%=")
androidAction = value;
else if (variable == "%DATA%=")
androidData = value;
else if (variable == "%CATEGORY%=")
androidCategory = value;
else if (variable == "%MIMETYPE%=")
androidMimeType = value;
}
}
std::vector<std::string> extraVariabels {"%EXTRA_", "%EXTRABOOL_"};
for (std::string variable : extraVariabels) {
size_t extraPos {command.find(variable)};
while (extraPos != std::string::npos) {
if (extraPos != std::string::npos) {
bool invalidEntry {false};
@ -1633,19 +1682,21 @@ void FileData::launchGame()
if (equalPos == std::string::npos)
invalidEntry = true;
if (!invalidEntry && extraPos + 8 >= command.size())
if (!invalidEntry && extraPos + variable.length() + 1 >= command.size())
invalidEntry = true;
if (!invalidEntry) {
if (command.length() > equalPos && command[equalPos + 1] == '\"')
isQuoted = true;
extraName = command.substr(extraPos + 7, equalPos - (extraPos + 8));
extraName = command.substr(extraPos + variable.length(),
equalPos - (extraPos + variable.length() + 1));
if (isQuoted) {
const size_t closeQuotePos {command.find("\"", equalPos + 2)};
if (closeQuotePos != std::string::npos)
extraValue = command.substr(equalPos + 2, closeQuotePos - (equalPos + 2));
extraValue =
command.substr(equalPos + 2, closeQuotePos - (equalPos + 2));
else
invalidEntry = true;
}
@ -1658,7 +1709,8 @@ void FileData::launchGame()
}
if (invalidEntry) {
LOG(LogError) << "Invalid entry in systems configuration file es_systems.xml";
LOG(LogError)
<< "Invalid entry in systems configuration file es_systems.xml";
LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw;
@ -1669,12 +1721,24 @@ void FileData::launchGame()
return;
}
if (extraName != "" && extraValue != "")
androidExtras.emplace_back(make_pair(extraName, extraValue));
if (extraName != "" && extraValue != "") {
if (variable == "%EXTRA_")
androidExtrasString[extraName] = extraValue;
else if (variable == "%EXTRABOOL_")
androidExtrasBool[extraName] = extraValue;
}
}
extraPos = command.find("%EXTRA_", extraPos + 1);
}
extraPos = command.find(variable, extraPos + 1);
}
}
if (command.find("%ACTIVITY_CLEAR_TASK%") != std::string::npos)
androidActivityFlags.emplace_back("%ACTIVITY_CLEAR_TASK%");
if (command.find("%ACTIVITY_CLEAR_TOP%") != std::string::npos)
androidActivityFlags.emplace_back("%ACTIVITY_CLEAR_TOP%");
if (command.find("%ACTIVITY_NO_HISTORY%") != std::string::npos)
androidActivityFlags.emplace_back("%ACTIVITY_NO_HISTORY%");
#endif
#if defined(_WIN64)
@ -1722,15 +1786,29 @@ void FileData::launchGame()
#if defined(__ANDROID__)
LOG(LogInfo) << "Expanded emulator launch arguments:";
LOG(LogInfo) << "Package: " << androidPackage;
LOG(LogInfo) << "Activity: " << (androidActivity == "" ? "<package default>" : androidActivity);
LOG(LogInfo) << "Action: " << (androidAction == "" ? "<package default>" : androidAction);
if (androidFileAsURI != "") {
LOG(LogInfo) << "File (URI): " << androidFileAsURI;
if (androidActivity != "") {
LOG(LogInfo) << "Activity: " << androidActivity;
}
for (auto& extra : androidExtras) {
if (androidAction != "") {
LOG(LogInfo) << "Action: " << androidAction;
}
if (androidCategory != "") {
LOG(LogInfo) << "Category: " << androidCategory;
}
if (androidMimeType != "") {
LOG(LogInfo) << "MIME type: " << androidMimeType;
}
if (androidData != "") {
LOG(LogInfo) << "Data: " << androidData;
}
for (auto& extra : androidExtrasString) {
LOG(LogInfo) << "Extra name: " << extra.first;
LOG(LogInfo) << "Extra value: " << extra.second;
}
for (auto& extra : androidExtrasBool) {
LOG(LogInfo) << "Extra bool name: " << extra.first;
LOG(LogInfo) << "Extra bool value: " << extra.second;
}
#else
LOG(LogInfo) << "Expanded emulator launch command:";
LOG(LogInfo) << command;
@ -1755,13 +1833,14 @@ void FileData::launchGame()
Utils::String::stringToWideString(startDirectory), runInBackground, hideWindow);
#elif defined(__ANDROID__)
returnValue = Utils::Platform::Android::launchGame(
androidPackage, androidActivity, androidAction, androidFileAsURI, androidExtras);
androidPackage, androidActivity, androidAction, androidCategory, androidMimeType,
androidData, romRaw, androidExtrasString, androidExtrasBool, androidActivityFlags);
#else
returnValue = Utils::Platform::launchGameUnix(command, startDirectory, runInBackground);
#endif
// Notify the user in case of a failed game launch using a popup window.
if (returnValue != 0) {
LOG(LogWarning) << "...launch terminated with nonzero return value " << returnValue;
LOG(LogWarning) << "Launch terminated with nonzero return value " << returnValue;
window->queueInfoPopup("ERROR LAUNCHING GAME '" +
Utils::String::toUpper(metadata.get("name")) + "' (ERROR CODE " +
@ -2051,13 +2130,10 @@ const std::pair<std::string, FileData::findEmulatorResult> FileData::findEmulato
for (std::string& androidpackage : emulatorAndroidPackages) {
// If a forward slash character is present in the androidpackage entry it means an explicit
// Intent activity should be used rather than the default one. The checkEmulatorInstalled()
// Java function will check for the activity as well and if it's not found it flags
// the overall emulator entry as not found. It's also possible to define an explicit
// Intent action using the pipe character but this is not checked for in the Java
// function as invalid actions will not lead to crashes.
// Java function will check for the activity as well and if it's not found it flags the
// overall emulator entry as not found.
std::string packageName {androidpackage};
std::string activity;
std::string action;
size_t separatorPos {packageName.find('/')};
if (separatorPos != std::string::npos) {
@ -2065,18 +2141,6 @@ const std::pair<std::string, FileData::findEmulatorResult> FileData::findEmulato
packageName = packageName.substr(0, separatorPos);
}
separatorPos = packageName.find('|');
if (separatorPos != std::string::npos) {
action = packageName.substr(separatorPos + 1);
packageName = packageName.substr(0, separatorPos);
}
separatorPos = activity.find('|');
if (separatorPos != std::string::npos) {
action = activity.substr(separatorPos + 1);
activity = activity.substr(0, separatorPos);
}
if (Utils::Platform::Android::checkEmulatorInstalled(packageName, activity)) {
return std::make_pair(androidpackage,
FileData::findEmulatorResult::FOUND_ANDROID_PACKAGE);

View file

@ -31,6 +31,18 @@
#include <array>
#include <fcntl.h>
#if defined(__ANDROID__)
JNIEXPORT void JNICALL Java_org_es_1de_frontend_MainActivity_nativeLogOutput(JNIEnv* jniEnv,
jclass jniClass,
jstring output,
jint logLevel)
{
const char* outputUtf {jniEnv->GetStringUTFChars(output, nullptr)};
LOG(static_cast<LogLevel>(logLevel)) << outputUtf;
jniEnv->ReleaseStringUTFChars(output, outputUtf);
}
#endif
namespace Utils
{
namespace Platform
@ -380,10 +392,9 @@ namespace Utils
JNIEnv* jniEnv {reinterpret_cast<JNIEnv*>(SDL_AndroidGetJNIEnv())};
jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")};
jmethodID methodID {
jniEnv->GetStaticMethodID(jniClass, "requestStoragePermissions", "()Z")};
const bool result {
static_cast<bool>(jniEnv->CallStaticBooleanMethod(jniClass, methodID))};
// jniEnv->DeleteLocalRef(jniClass);
jniEnv->GetStaticMethodID(jniClass, "requestStoragePermissions", "()V")};
jniEnv->CallStaticVoidMethod(jniClass, methodID);
bool result {false};
return result;
}
@ -461,33 +472,61 @@ namespace Utils
int launchGame(const std::string& packageName,
const std::string& activity,
const std::string& action,
const std::string& fileAsURI,
std::vector<std::pair<std::string, std::string>>& extras)
const std::string& category,
const std::string& mimeType,
const std::string& data,
const std::string& romRaw,
const std::map<std::string, std::string>& extrasString,
const std::map<std::string, std::string>& extrasBool,
const std::vector<std::string>& activityFlags)
{
JNIEnv* jniEnv {reinterpret_cast<JNIEnv*>(SDL_AndroidGetJNIEnv())};
jclass jniClass {jniEnv->FindClass("org/es_de/frontend/MainActivity")};
jmethodID methodID {jniEnv->GetStaticMethodID(
jniClass, "launchGame",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;Ljava/util/Vector;Ljava/util/Vector;)Z")};
jclass vectorClass {jniEnv->FindClass("java/util/Vector")};
jmethodID vectorMID {jniEnv->GetMethodID(vectorClass, "<init>", "()V")};
jmethodID addMethodID {
jniEnv->GetMethodID(vectorClass, "add", "(Ljava/lang/Object;)Z")};
jobject extrasNames {jniEnv->NewObject(vectorClass, vectorMID)};
jobject extrasValues {jniEnv->NewObject(vectorClass, vectorMID)};
for (auto& extra : extras) {
jniEnv->CallBooleanMethod(extrasNames, addMethodID,
jniEnv->NewStringUTF(extra.first.c_str()));
jniEnv->CallBooleanMethod(extrasValues, addMethodID,
jniEnv->NewStringUTF(extra.second.c_str()));
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/"
"HashMap;Ljava/util/HashMap;Ljava/util/List;)Z")};
jclass hashMapClass {jniEnv->FindClass("java/util/HashMap")};
jmethodID hashMapInit {jniEnv->GetMethodID(hashMapClass, "<init>", "(I)V")};
jobject hashMapString {
jniEnv->NewObject(hashMapClass, hashMapInit, extrasString.size())};
jobject hashMapBool {
jniEnv->NewObject(hashMapClass, hashMapInit, extrasBool.size())};
jmethodID hashMapPutMethodString {jniEnv->GetMethodID(
hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")};
for (auto it : extrasString) {
jniEnv->CallObjectMethod(hashMapString, hashMapPutMethodString,
jniEnv->NewStringUTF(it.first.c_str()),
jniEnv->NewStringUTF(it.second.c_str()));
}
for (auto it : extrasBool) {
jniEnv->CallObjectMethod(hashMapBool, hashMapPutMethodString,
jniEnv->NewStringUTF(it.first.c_str()),
jniEnv->NewStringUTF(it.second.c_str()));
}
jclass arrayListClass {jniEnv->FindClass("java/util/ArrayList")};
jmethodID arrayListInit {jniEnv->GetMethodID(arrayListClass, "<init>", "()V")};
jmethodID addMethod {
jniEnv->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z")};
jobject flags {jniEnv->NewObject(arrayListClass, arrayListInit)};
for (auto& flag : activityFlags)
jniEnv->CallBooleanMethod(flags, addMethod, jniEnv->NewStringUTF(flag.c_str()));
const bool returnValue {static_cast<bool>(jniEnv->CallStaticBooleanMethod(
jniClass, methodID, jniEnv->NewStringUTF(packageName.c_str()),
jniEnv->NewStringUTF(activity.c_str()), jniEnv->NewStringUTF(action.c_str()),
jniEnv->NewStringUTF(fileAsURI.c_str()), extrasNames, extrasValues))};
// jniEnv->DeleteLocalRef(vectorClass);
// jniEnv->DeleteLocalRef(jniClass);
jniEnv->NewStringUTF(category.c_str()), jniEnv->NewStringUTF(mimeType.c_str()),
jniEnv->NewStringUTF(data.c_str()), jniEnv->NewStringUTF(romRaw.c_str()),
hashMapString, hashMapBool, flags))};
if (returnValue)
return -1;
else

View file

@ -17,6 +17,12 @@
#if defined(__ANDROID__)
#include <jni.h>
#include <map>
extern "C" void Java_org_es_1de_frontend_MainActivity_nativeLogOutput(JNIEnv* jniEnv,
jclass jniClass,
jstring output,
jint logLevel);
#endif
namespace Utils
@ -68,8 +74,14 @@ namespace Utils
int launchGame(const std::string& packageName,
const std::string& activity,
const std::string& action,
const std::string& fileAsURI,
std::vector<std::pair<std::string, std::string>>& extras);
const std::string& category,
const std::string& mimeType,
const std::string& data,
const std::string& romRaw,
const std::map<std::string, std::string>& extrasString,
const std::map<std::string, std::string>& extrasBool,
const std::vector<std::string>& activityFlags);
} // namespace Android
#endif