Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental
This commit is contained in:
@@ -24,7 +24,8 @@ extern "C" int main(int argc, char** argv) {
|
||||
|
||||
// Initialize Android globals, including logging. Needs parsed cvars.
|
||||
// TODO(Triang3l): Obtain the actual API level.
|
||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr);
|
||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr,
|
||||
nullptr);
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "cvar.h"
|
||||
|
||||
#include "utf8.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
|
||||
#define UTF_CPP_CPLUSPLUS 201703L
|
||||
#include "third_party/utfcpp/source/utf8.h"
|
||||
@@ -17,6 +15,7 @@
|
||||
#include "xenia/base/console.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/system.h"
|
||||
#include "xenia/base/utf8.h"
|
||||
|
||||
namespace utfcpp = utf8;
|
||||
|
||||
|
||||
@@ -20,8 +20,13 @@
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
#include <jni.h>
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
|
||||
namespace cvar {
|
||||
|
||||
namespace toml {
|
||||
@@ -56,6 +61,7 @@ class CommandVar : virtual public ICommandVar {
|
||||
const std::string& description() const override;
|
||||
void AddToLaunchOptions(cxxopts::Options* options) override;
|
||||
void LoadFromLaunchOptions(cxxopts::ParseResult* result) override;
|
||||
void SetCommandLineValue(T val);
|
||||
T* current_value() { return current_value_; }
|
||||
|
||||
protected:
|
||||
@@ -67,7 +73,6 @@ class CommandVar : virtual public ICommandVar {
|
||||
T Convert(std::string val);
|
||||
static std::string ToString(T val);
|
||||
void SetValue(T val);
|
||||
void SetCommandLineValue(T val);
|
||||
void UpdateValue() override;
|
||||
};
|
||||
#pragma warning(push)
|
||||
@@ -297,6 +302,9 @@ inline void AddCommandVar(ICommandVar* cv) {
|
||||
void ParseLaunchArguments(int& argc, char**& argv,
|
||||
const std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options);
|
||||
#if XE_PLATFORM_ANDROID
|
||||
void ParseLaunchArgumentsFromAndroidBundle(jobject bundle);
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
|
||||
template <typename T>
|
||||
IConfigVar* define_configvar(const char* name, T* default_value,
|
||||
|
||||
212
src/xenia/base/cvar_android.cc
Normal file
212
src/xenia/base/cvar_android.cc
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/main_android.h"
|
||||
|
||||
namespace cvar {
|
||||
|
||||
void ParseLaunchArgumentsFromAndroidBundle(jobject bundle) {
|
||||
if (!ConfigVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv* jni_env = xe::GetAndroidThreadJniEnv();
|
||||
if (!jni_env) {
|
||||
return;
|
||||
}
|
||||
|
||||
jclass bundle_class = jni_env->GetObjectClass(bundle);
|
||||
if (!bundle_class) {
|
||||
return;
|
||||
}
|
||||
bool bundle_methods_obtained = true;
|
||||
jmethodID bundle_get_boolean =
|
||||
jni_env->GetMethodID(bundle_class, "getBoolean", "(Ljava/lang/String;)Z");
|
||||
bundle_methods_obtained &= (bundle_get_boolean != nullptr);
|
||||
jmethodID bundle_get_double =
|
||||
jni_env->GetMethodID(bundle_class, "getDouble", "(Ljava/lang/String;)D");
|
||||
bundle_methods_obtained &= (bundle_get_double != nullptr);
|
||||
jmethodID bundle_get_int =
|
||||
jni_env->GetMethodID(bundle_class, "getInt", "(Ljava/lang/String;)I");
|
||||
bundle_methods_obtained &= (bundle_get_int != nullptr);
|
||||
jmethodID bundle_get_long =
|
||||
jni_env->GetMethodID(bundle_class, "getLong", "(Ljava/lang/String;)J");
|
||||
bundle_methods_obtained &= (bundle_get_long != nullptr);
|
||||
jmethodID bundle_get_string = jni_env->GetMethodID(
|
||||
bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
bundle_methods_obtained &= (bundle_get_string != nullptr);
|
||||
jmethodID bundle_key_set_method_id =
|
||||
jni_env->GetMethodID(bundle_class, "keySet", "()Ljava/util/Set;");
|
||||
bundle_methods_obtained &= (bundle_key_set_method_id != nullptr);
|
||||
if (!bundle_methods_obtained) {
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
|
||||
jobject key_set = jni_env->CallObjectMethod(bundle, bundle_key_set_method_id);
|
||||
if (!key_set) {
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
|
||||
jclass set_class = jni_env->GetObjectClass(key_set);
|
||||
if (!set_class) {
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
bool set_methods_obtained = true;
|
||||
jmethodID set_iterator_method_id =
|
||||
jni_env->GetMethodID(set_class, "iterator", "()Ljava/util/Iterator;");
|
||||
set_methods_obtained &= (set_iterator_method_id != nullptr);
|
||||
if (!set_methods_obtained) {
|
||||
jni_env->DeleteLocalRef(set_class);
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
|
||||
jobject key_set_iterator =
|
||||
jni_env->CallObjectMethod(key_set, set_iterator_method_id);
|
||||
if (!key_set_iterator) {
|
||||
jni_env->DeleteLocalRef(set_class);
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
|
||||
jclass iterator_class = jni_env->GetObjectClass(key_set_iterator);
|
||||
if (!iterator_class) {
|
||||
jni_env->DeleteLocalRef(key_set_iterator);
|
||||
jni_env->DeleteLocalRef(set_class);
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
bool iterator_methods_obtained = true;
|
||||
jmethodID iterator_has_next =
|
||||
jni_env->GetMethodID(iterator_class, "hasNext", "()Z");
|
||||
iterator_methods_obtained &= (iterator_has_next != nullptr);
|
||||
jmethodID iterator_next =
|
||||
jni_env->GetMethodID(iterator_class, "next", "()Ljava/lang/Object;");
|
||||
iterator_methods_obtained &= (iterator_next != nullptr);
|
||||
if (!iterator_methods_obtained) {
|
||||
jni_env->DeleteLocalRef(iterator_class);
|
||||
jni_env->DeleteLocalRef(key_set_iterator);
|
||||
jni_env->DeleteLocalRef(set_class);
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
return;
|
||||
}
|
||||
|
||||
while (jni_env->CallBooleanMethod(key_set_iterator, iterator_has_next)) {
|
||||
jstring key = reinterpret_cast<jstring>(
|
||||
jni_env->CallObjectMethod(key_set_iterator, iterator_next));
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const char* key_utf = jni_env->GetStringUTFChars(key, nullptr);
|
||||
if (!key_utf) {
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_it = ConfigVars->find(key_utf);
|
||||
jni_env->ReleaseStringUTFChars(key, key_utf);
|
||||
// key_utf can't be used from now on.
|
||||
if (cvar_it == ConfigVars->end()) {
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
IConfigVar* cvar = cvar_it->second;
|
||||
auto cvar_bool = dynamic_cast<CommandVar<bool>*>(cvar);
|
||||
if (cvar_bool) {
|
||||
cvar_bool->SetCommandLineValue(
|
||||
bool(jni_env->CallBooleanMethod(bundle, bundle_get_boolean, key)));
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_int32 = dynamic_cast<CommandVar<int32_t>*>(cvar);
|
||||
if (cvar_int32) {
|
||||
cvar_int32->SetCommandLineValue(
|
||||
jni_env->CallIntMethod(bundle, bundle_get_int, key));
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_uint32 = dynamic_cast<CommandVar<uint32_t>*>(cvar);
|
||||
if (cvar_uint32) {
|
||||
cvar_uint32->SetCommandLineValue(
|
||||
uint32_t(jni_env->CallIntMethod(bundle, bundle_get_int, key)));
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_uint64 = dynamic_cast<CommandVar<uint64_t>*>(cvar);
|
||||
if (cvar_uint64) {
|
||||
cvar_uint64->SetCommandLineValue(
|
||||
uint64_t(jni_env->CallLongMethod(bundle, bundle_get_long, key)));
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_double = dynamic_cast<CommandVar<double>*>(cvar);
|
||||
if (cvar_double) {
|
||||
cvar_double->SetCommandLineValue(
|
||||
jni_env->CallDoubleMethod(bundle, bundle_get_double, key));
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_string = dynamic_cast<CommandVar<std::string>*>(cvar);
|
||||
if (cvar_string) {
|
||||
jstring cvar_string_value = reinterpret_cast<jstring>(
|
||||
jni_env->CallObjectMethod(bundle, bundle_get_string, key));
|
||||
if (cvar_string_value) {
|
||||
const char* cvar_string_value_utf =
|
||||
jni_env->GetStringUTFChars(cvar_string_value, nullptr);
|
||||
if (cvar_string_value_utf) {
|
||||
cvar_string->SetCommandLineValue(cvar_string_value_utf);
|
||||
jni_env->ReleaseStringUTFChars(cvar_string_value,
|
||||
cvar_string_value_utf);
|
||||
}
|
||||
jni_env->DeleteLocalRef(cvar_string_value);
|
||||
}
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
auto cvar_path = dynamic_cast<CommandVar<std::filesystem::path>*>(cvar);
|
||||
if (cvar_path) {
|
||||
jstring cvar_string_value = reinterpret_cast<jstring>(
|
||||
jni_env->CallObjectMethod(bundle, bundle_get_string, key));
|
||||
if (cvar_string_value) {
|
||||
const char* cvar_string_value_utf =
|
||||
jni_env->GetStringUTFChars(cvar_string_value, nullptr);
|
||||
if (cvar_string_value_utf) {
|
||||
cvar_path->SetCommandLineValue(xe::to_path(cvar_string_value_utf));
|
||||
jni_env->ReleaseStringUTFChars(cvar_string_value,
|
||||
cvar_string_value_utf);
|
||||
}
|
||||
jni_env->DeleteLocalRef(cvar_string_value);
|
||||
}
|
||||
jni_env->DeleteLocalRef(key);
|
||||
continue;
|
||||
}
|
||||
assert_always("Unsupported type of cvar {}", cvar->name().c_str());
|
||||
jni_env->DeleteLocalRef(key);
|
||||
}
|
||||
|
||||
jni_env->DeleteLocalRef(iterator_class);
|
||||
jni_env->DeleteLocalRef(key_set_iterator);
|
||||
jni_env->DeleteLocalRef(set_class);
|
||||
jni_env->DeleteLocalRef(key_set);
|
||||
jni_env->DeleteLocalRef(bundle_class);
|
||||
}
|
||||
|
||||
} // namespace cvar
|
||||
@@ -14,8 +14,10 @@
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -122,6 +124,14 @@ struct FileInfo {
|
||||
bool GetInfo(const std::filesystem::path& path, FileInfo* out_info);
|
||||
std::vector<FileInfo> ListFiles(const std::filesystem::path& path);
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
void AndroidInitialize();
|
||||
void AndroidShutdown();
|
||||
bool IsAndroidContentUri(const std::string_view source);
|
||||
int OpenAndroidContentFileDescriptor(const std::string_view uri,
|
||||
const char* mode);
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
|
||||
} // namespace filesystem
|
||||
} // namespace xe
|
||||
|
||||
|
||||
278
src/xenia/base/filesystem_android.cc
Normal file
278
src/xenia/base/filesystem_android.cc
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/main_android.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
namespace filesystem {
|
||||
|
||||
// Using Android logging because the file system may need to be initialized
|
||||
// before logging.
|
||||
|
||||
static jobject android_content_resolver_;
|
||||
static jclass android_content_resolver_class_;
|
||||
static jmethodID android_content_resolver_open_file_descriptor_;
|
||||
|
||||
static jclass android_parcel_file_descriptor_class_;
|
||||
static jmethodID android_parcel_file_descriptor_detach_fd_;
|
||||
|
||||
static jclass android_uri_class_;
|
||||
static jmethodID android_uri_parse_;
|
||||
|
||||
static bool android_content_resolver_initialized_;
|
||||
|
||||
static void AndroidShutdownContentResolver() {
|
||||
android_content_resolver_initialized_ = false;
|
||||
android_uri_parse_ = nullptr;
|
||||
android_parcel_file_descriptor_detach_fd_ = nullptr;
|
||||
android_content_resolver_open_file_descriptor_ = nullptr;
|
||||
JNIEnv* jni_env = GetAndroidThreadJniEnv();
|
||||
if (jni_env) {
|
||||
if (android_uri_class_) {
|
||||
jni_env->DeleteGlobalRef(android_uri_class_);
|
||||
}
|
||||
if (android_parcel_file_descriptor_class_) {
|
||||
jni_env->DeleteGlobalRef(
|
||||
reinterpret_cast<jobject>(android_parcel_file_descriptor_class_));
|
||||
}
|
||||
if (android_content_resolver_class_) {
|
||||
jni_env->DeleteGlobalRef(
|
||||
reinterpret_cast<jobject>(android_content_resolver_class_));
|
||||
}
|
||||
if (android_content_resolver_) {
|
||||
jni_env->DeleteGlobalRef(
|
||||
reinterpret_cast<jobject>(android_content_resolver_));
|
||||
}
|
||||
}
|
||||
android_uri_class_ = nullptr;
|
||||
android_parcel_file_descriptor_class_ = nullptr;
|
||||
android_content_resolver_class_ = nullptr;
|
||||
android_content_resolver_ = nullptr;
|
||||
}
|
||||
|
||||
static void AndroidInitializeContentResolver() {
|
||||
JNIEnv* jni_env = GetAndroidThreadJniEnv();
|
||||
if (!jni_env) {
|
||||
return;
|
||||
}
|
||||
jobject application_context = GetAndroidApplicationContext();
|
||||
if (!application_context) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
jclass context_class = jni_env->GetObjectClass(application_context);
|
||||
if (!context_class) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the context class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
jmethodID context_get_content_resolver =
|
||||
jni_env->GetMethodID(context_class, "getContentResolver",
|
||||
"()Landroid/content/ContentResolver;");
|
||||
if (!context_get_content_resolver) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the getContentResolver method of the context");
|
||||
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(context_class));
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
jobject content_resolver = jni_env->CallObjectMethod(
|
||||
application_context, context_get_content_resolver);
|
||||
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(context_class));
|
||||
if (!content_resolver) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the content resolver");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_content_resolver_ = jni_env->NewGlobalRef(content_resolver);
|
||||
jni_env->DeleteLocalRef(content_resolver);
|
||||
}
|
||||
if (!android_content_resolver_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to create a global reference to the content resolver");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
{
|
||||
jobject content_resolver_class =
|
||||
jni_env->GetObjectClass(android_content_resolver_);
|
||||
if (!content_resolver_class) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the content resolver class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_content_resolver_class_ =
|
||||
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
|
||||
reinterpret_cast<jobject>(content_resolver_class)));
|
||||
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(content_resolver_class));
|
||||
}
|
||||
if (!android_content_resolver_class_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to create a global reference to the content resolver class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_content_resolver_open_file_descriptor_ = jni_env->GetMethodID(
|
||||
android_content_resolver_class_, "openFileDescriptor",
|
||||
"(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;");
|
||||
if (!android_content_resolver_open_file_descriptor_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the openFileDescriptor method of the content resolver");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
jclass parcel_file_descriptor_class =
|
||||
jni_env->FindClass("android/os/ParcelFileDescriptor");
|
||||
if (!parcel_file_descriptor_class) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the parcel file descriptor class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_parcel_file_descriptor_class_ =
|
||||
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
|
||||
reinterpret_cast<jobject>(parcel_file_descriptor_class)));
|
||||
jni_env->DeleteLocalRef(
|
||||
reinterpret_cast<jobject>(parcel_file_descriptor_class));
|
||||
}
|
||||
if (!android_parcel_file_descriptor_class_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to create a global reference to the parcel file descriptor "
|
||||
"class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_parcel_file_descriptor_detach_fd_ = jni_env->GetMethodID(
|
||||
android_parcel_file_descriptor_class_, "detachFd", "()I");
|
||||
if (!android_parcel_file_descriptor_detach_fd_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the detachFd method of the parcel file descriptor");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
jclass uri_class = jni_env->FindClass("android/net/Uri");
|
||||
if (!uri_class) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the URI class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_uri_class_ = reinterpret_cast<jclass>(
|
||||
jni_env->NewGlobalRef(reinterpret_cast<jobject>(uri_class)));
|
||||
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(uri_class));
|
||||
}
|
||||
if (!android_uri_class_) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to create a global reference to the URI class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
android_uri_parse_ = jni_env->GetStaticMethodID(
|
||||
android_uri_class_, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
|
||||
if (!android_uri_parse_) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
|
||||
"Failed to get the parse method of the URI class");
|
||||
AndroidShutdownContentResolver();
|
||||
return;
|
||||
}
|
||||
|
||||
android_content_resolver_initialized_ = true;
|
||||
}
|
||||
|
||||
void AndroidInitialize() { AndroidInitializeContentResolver(); }
|
||||
|
||||
void AndroidShutdown() { AndroidShutdownContentResolver(); }
|
||||
|
||||
bool IsAndroidContentUri(const std::string_view source) {
|
||||
// A URI schema is case-insensitive. Though just content: defines the schema,
|
||||
// still including // in the comparison to distinguish from a file with a name
|
||||
// starting from content: (as this is the main purpose of this code -
|
||||
// separating URIs from file paths) more clearly.
|
||||
static const char kContentSchema[] = "content://";
|
||||
constexpr size_t kContentSchemaLength = xe::countof(kContentSchema) - 1;
|
||||
return source.size() >= kContentSchemaLength &&
|
||||
!xe_strncasecmp(source.data(), kContentSchema, kContentSchemaLength);
|
||||
}
|
||||
|
||||
int OpenAndroidContentFileDescriptor(const std::string_view uri,
|
||||
const char* mode) {
|
||||
if (!android_content_resolver_initialized_) {
|
||||
return -1;
|
||||
}
|
||||
JNIEnv* jni_env = GetAndroidThreadJniEnv();
|
||||
if (!jni_env) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
jobject uri_object;
|
||||
{
|
||||
jstring uri_string;
|
||||
{
|
||||
std::u16string uri_u16 = xe::to_utf16(uri);
|
||||
uri_string = jni_env->NewString(
|
||||
reinterpret_cast<const jchar*>(uri_u16.data()), uri_u16.size());
|
||||
}
|
||||
if (!uri_string) {
|
||||
return -1;
|
||||
}
|
||||
uri_object = jni_env->CallStaticObjectMethod(
|
||||
android_uri_class_, android_uri_parse_, uri_string);
|
||||
jni_env->DeleteLocalRef(uri_string);
|
||||
}
|
||||
if (!uri_object) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
jstring mode_string = jni_env->NewStringUTF(mode);
|
||||
if (!mode_string) {
|
||||
jni_env->DeleteLocalRef(uri_object);
|
||||
return -1;
|
||||
}
|
||||
|
||||
jobject parcel_file_descriptor = jni_env->CallObjectMethod(
|
||||
android_content_resolver_, android_content_resolver_open_file_descriptor_,
|
||||
uri_object, mode_string);
|
||||
jni_env->DeleteLocalRef(mode_string);
|
||||
jni_env->DeleteLocalRef(uri_object);
|
||||
if (jni_env->ExceptionCheck()) {
|
||||
jni_env->ExceptionClear();
|
||||
return -1;
|
||||
}
|
||||
if (!parcel_file_descriptor) {
|
||||
return -1;
|
||||
}
|
||||
int file_descriptor = jni_env->CallIntMethod(
|
||||
parcel_file_descriptor, android_parcel_file_descriptor_detach_fd_);
|
||||
jni_env->DeleteLocalRef(parcel_file_descriptor);
|
||||
|
||||
return file_descriptor;
|
||||
}
|
||||
|
||||
} // namespace filesystem
|
||||
} // namespace xe
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/system.h"
|
||||
@@ -44,7 +46,8 @@ static void AndroidThreadJNIEnvDestructor(void* jni_env_pointer) {
|
||||
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||
JNIEnv* main_thread_jni_env,
|
||||
jobject application_context) {
|
||||
jobject application_context,
|
||||
jobject launch_arguments_bundle) {
|
||||
if (android_initializations_++) {
|
||||
// Already initialized for another component in the process.
|
||||
return;
|
||||
@@ -96,6 +99,13 @@ void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||
// Logging uses threading.
|
||||
xe::threading::AndroidInitialize();
|
||||
|
||||
xe::filesystem::AndroidInitialize();
|
||||
|
||||
// Initialize the cvars before logging.
|
||||
if (launch_arguments_bundle) {
|
||||
cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle);
|
||||
}
|
||||
|
||||
// Multiple apps can be launched within one process - don't pass the actual
|
||||
// app name.
|
||||
xe::InitializeLogging("xenia");
|
||||
@@ -128,6 +138,8 @@ void ShutdownAndroidAppFromMainThread() {
|
||||
|
||||
xe::ShutdownLogging();
|
||||
|
||||
xe::filesystem::AndroidShutdown();
|
||||
|
||||
xe::threading::AndroidShutdown();
|
||||
|
||||
if (android_application_context_) {
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace xe {
|
||||
// must be called in `main`, with a null main thread JNI environment.
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||
JNIEnv* main_thread_jni_env,
|
||||
jobject application_context);
|
||||
jobject application_context,
|
||||
jobject launch_arguments_bundle);
|
||||
void ShutdownAndroidAppFromMainThread();
|
||||
|
||||
// May be the minimum supported level if the initialization was done without a
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
@@ -26,17 +28,25 @@ class MappedMemory {
|
||||
static std::unique_ptr<MappedMemory> Open(const std::filesystem::path& path,
|
||||
Mode mode, size_t offset = 0,
|
||||
size_t length = 0);
|
||||
#if XE_PLATFORM_ANDROID
|
||||
static std::unique_ptr<MappedMemory> OpenForAndroidContentUri(
|
||||
const std::string_view uri, Mode mode, size_t offset = 0,
|
||||
size_t length = 0);
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
|
||||
MappedMemory(const std::filesystem::path& path, Mode mode)
|
||||
: path_(path), mode_(mode), data_(nullptr), size_(0) {}
|
||||
MappedMemory(const std::filesystem::path& path, Mode mode, void* data,
|
||||
size_t size)
|
||||
: path_(path), mode_(mode), data_(data), size_(size) {}
|
||||
MappedMemory() : data_(nullptr), size_(0) {}
|
||||
MappedMemory(void* data, size_t size) : data_(data), size_(size) {}
|
||||
MappedMemory(const MappedMemory& mapped_memory) = delete;
|
||||
MappedMemory& operator=(const MappedMemory& mapped_memory) = delete;
|
||||
MappedMemory(MappedMemory&& mapped_memory) = delete;
|
||||
MappedMemory& operator=(MappedMemory&& mapped_memory) = delete;
|
||||
virtual ~MappedMemory() = default;
|
||||
|
||||
std::unique_ptr<MappedMemory> Slice(Mode mode, size_t offset, size_t length) {
|
||||
// The mapping is still backed by the object the slice was created from, a
|
||||
// slice is not owning.
|
||||
std::unique_ptr<MappedMemory> Slice(size_t offset, size_t length) {
|
||||
return std::unique_ptr<MappedMemory>(
|
||||
new MappedMemory(path_, mode, data() + offset, length));
|
||||
new MappedMemory(data() + offset, length));
|
||||
}
|
||||
|
||||
uint8_t* data() const { return reinterpret_cast<uint8_t*>(data_); }
|
||||
@@ -50,8 +60,6 @@ class MappedMemory {
|
||||
virtual bool Remap(size_t offset, size_t length) { return false; }
|
||||
|
||||
protected:
|
||||
std::filesystem::path path_;
|
||||
Mode mode_;
|
||||
void* data_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
@@ -9,73 +9,126 @@
|
||||
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <cstdio>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
class PosixMappedMemory : public MappedMemory {
|
||||
public:
|
||||
PosixMappedMemory(const std::filesystem::path& path, Mode mode)
|
||||
: MappedMemory(path, mode), file_handle(nullptr) {}
|
||||
PosixMappedMemory(void* data, size_t size, int file_descriptor)
|
||||
: MappedMemory(data, size), file_descriptor_(file_descriptor) {}
|
||||
|
||||
~PosixMappedMemory() override {
|
||||
if (data_) {
|
||||
munmap(data_, size_);
|
||||
munmap(data_, size());
|
||||
}
|
||||
if (file_handle) {
|
||||
fclose(file_handle);
|
||||
if (file_descriptor_ >= 0) {
|
||||
close(file_descriptor_);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file_handle;
|
||||
static std::unique_ptr<PosixMappedMemory> WrapFileDescriptor(
|
||||
int file_descriptor, Mode mode, size_t offset = 0, size_t length = 0) {
|
||||
int protection = 0;
|
||||
switch (mode) {
|
||||
case Mode::kRead:
|
||||
protection |= PROT_READ;
|
||||
break;
|
||||
case Mode::kReadWrite:
|
||||
protection |= PROT_READ | PROT_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t map_length = length;
|
||||
if (!length) {
|
||||
struct stat64 file_stat;
|
||||
if (fstat64(file_descriptor, &file_stat)) {
|
||||
close(file_descriptor);
|
||||
return nullptr;
|
||||
}
|
||||
map_length = size_t(file_stat.st_size);
|
||||
}
|
||||
|
||||
void* data =
|
||||
mmap(0, map_length, protection, MAP_SHARED, file_descriptor, offset);
|
||||
if (!data) {
|
||||
close(file_descriptor);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<PosixMappedMemory>(data, map_length,
|
||||
file_descriptor);
|
||||
}
|
||||
|
||||
void Close(uint64_t truncate_size) override {
|
||||
if (data_) {
|
||||
munmap(data_, size());
|
||||
data_ = nullptr;
|
||||
}
|
||||
if (file_descriptor_ >= 0) {
|
||||
if (truncate_size) {
|
||||
ftruncate64(file_descriptor_, off64_t(truncate_size));
|
||||
}
|
||||
close(file_descriptor_);
|
||||
file_descriptor_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() override { msync(data(), size(), MS_ASYNC); }
|
||||
|
||||
private:
|
||||
int file_descriptor_;
|
||||
};
|
||||
|
||||
std::unique_ptr<MappedMemory> MappedMemory::Open(
|
||||
const std::filesystem::path& path, Mode mode, size_t offset,
|
||||
size_t length) {
|
||||
const char* mode_str;
|
||||
int prot;
|
||||
int open_flags = 0;
|
||||
switch (mode) {
|
||||
case Mode::kRead:
|
||||
mode_str = "rb";
|
||||
prot = PROT_READ;
|
||||
open_flags |= O_RDONLY;
|
||||
break;
|
||||
case Mode::kReadWrite:
|
||||
mode_str = "r+b";
|
||||
prot = PROT_READ | PROT_WRITE;
|
||||
open_flags |= O_RDWR;
|
||||
break;
|
||||
}
|
||||
|
||||
auto mm =
|
||||
std::unique_ptr<PosixMappedMemory>(new PosixMappedMemory(path, mode));
|
||||
|
||||
mm->file_handle = fopen(path.c_str(), mode_str);
|
||||
if (!mm->file_handle) {
|
||||
int file_descriptor = open(path.c_str(), open_flags);
|
||||
if (file_descriptor < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t map_length;
|
||||
map_length = length;
|
||||
if (!length) {
|
||||
fseeko(mm->file_handle, 0, SEEK_END);
|
||||
map_length = ftello(mm->file_handle);
|
||||
fseeko(mm->file_handle, 0, SEEK_SET);
|
||||
}
|
||||
mm->size_ = map_length;
|
||||
|
||||
mm->data_ =
|
||||
mmap(0, map_length, prot, MAP_SHARED, fileno(mm->file_handle), offset);
|
||||
if (!mm->data_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::move(mm);
|
||||
return PosixMappedMemory::WrapFileDescriptor(file_descriptor, mode, offset,
|
||||
length);
|
||||
}
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
std::unique_ptr<MappedMemory> MappedMemory::OpenForAndroidContentUri(
|
||||
const std::string_view uri, Mode mode, size_t offset, size_t length) {
|
||||
const char* open_mode = nullptr;
|
||||
switch (mode) {
|
||||
case Mode::kRead:
|
||||
open_mode = "r";
|
||||
break;
|
||||
case Mode::kReadWrite:
|
||||
open_mode = "rw";
|
||||
break;
|
||||
}
|
||||
int file_descriptor =
|
||||
xe::filesystem::OpenAndroidContentFileDescriptor(uri, open_mode);
|
||||
if (file_descriptor < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return PosixMappedMemory::WrapFileDescriptor(file_descriptor, mode, offset,
|
||||
length);
|
||||
}
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
|
||||
std::unique_ptr<ChunkedMappedMemoryWriter> ChunkedMappedMemoryWriter::Open(
|
||||
const std::filesystem::path& path, size_t chunk_size,
|
||||
bool low_address_space) {
|
||||
|
||||
@@ -32,9 +32,6 @@ class Win32MappedMemory : public MappedMemory {
|
||||
// CreateFileMapping returns nullptr in case of failure.
|
||||
static constexpr HANDLE kMappingHandleInvalid = nullptr;
|
||||
|
||||
Win32MappedMemory(const std::filesystem::path& path, Mode mode)
|
||||
: MappedMemory(path, mode) {}
|
||||
|
||||
~Win32MappedMemory() override {
|
||||
if (data_) {
|
||||
UnmapViewOfFile(data_);
|
||||
@@ -135,7 +132,7 @@ std::unique_ptr<MappedMemory> MappedMemory::Open(
|
||||
offset & ~static_cast<size_t>(system_info.dwAllocationGranularity - 1);
|
||||
const size_t aligned_length = length + (offset - aligned_offset);
|
||||
|
||||
auto mm = std::make_unique<Win32MappedMemory>(path, mode);
|
||||
auto mm = std::make_unique<Win32MappedMemory>();
|
||||
mm->view_access_ = view_access;
|
||||
|
||||
mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr,
|
||||
|
||||
@@ -67,9 +67,6 @@
|
||||
#endif
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#define strdup _strdup
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX // Don't want windows.h including min/max macros.
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
@@ -9,9 +9,16 @@
|
||||
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if !XE_PLATFORM_WIN32
|
||||
#include <strings.h>
|
||||
#endif // !XE_PLATFORM_WIN32
|
||||
|
||||
#define UTF_CPP_CPLUSPLUS 201703L
|
||||
#include "third_party/utfcpp/source/utf8.h"
|
||||
|
||||
@@ -19,6 +26,30 @@ namespace utfcpp = utf8;
|
||||
|
||||
namespace xe {
|
||||
|
||||
int xe_strcasecmp(const char* string1, const char* string2) {
|
||||
#if XE_PLATFORM_WIN32
|
||||
return _stricmp(string1, string2);
|
||||
#else
|
||||
return strcasecmp(string1, string2);
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
}
|
||||
|
||||
int xe_strncasecmp(const char* string1, const char* string2, size_t count) {
|
||||
#if XE_PLATFORM_WIN32
|
||||
return _strnicmp(string1, string2, count);
|
||||
#else
|
||||
return strncasecmp(string1, string2, count);
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
}
|
||||
|
||||
char* xe_strdup(const char* source) {
|
||||
#if XE_PLATFORM_WIN32
|
||||
return _strdup(source);
|
||||
#else
|
||||
return strdup(source);
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
}
|
||||
|
||||
std::string to_utf8(const std::u16string_view source) {
|
||||
return utfcpp::utf16to8(source);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utf8.h"
|
||||
#include "xenia/base/utf8.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
int xe_strcasecmp(const char* string1, const char* string2);
|
||||
int xe_strncasecmp(const char* string1, const char* string2, size_t count);
|
||||
char* xe_strdup(const char* source);
|
||||
|
||||
std::string to_utf8(const std::u16string_view source);
|
||||
std::u16string to_utf16(const std::string_view source);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user