diff --git a/app/build.gradle b/app/build.gradle index a8968dfe..c699b886 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,7 +39,7 @@ android { } externalNativeBuild { cmake { - version "3.8.0+" + version "3.10.2+" path "CMakeLists.txt" } } diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 2b279cc4..c4579be9 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -1,115 +1,51 @@ #include "skyline/common.h" #include "skyline/os.h" #include -#include #include -#include #include bool Halt{}; -uint faultCount{}; -ANativeActivity *Activity{}; -ANativeWindow *Window{}; -AInputQueue *Queue{}; -std::thread *uiThread{}; - -void GameThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) { - while (!Window) - sched_yield(); - setpriority(PRIO_PROCESS, static_cast(getpid()), skyline::constant::PriorityAn.second); - auto settings = std::make_shared(prefPath); - auto logger = std::make_shared(logPath, static_cast(std::stoi(settings->GetString("log_level")))); - //settings->List(logger); // (Uncomment when you want to print out all settings strings) - auto start = std::chrono::steady_clock::now(); - try { - skyline::kernel::OS os(logger, settings, Window); - logger->Info("Launching ROM {}", romPath); - os.Execute(romPath); - logger->Info("Emulation has ended"); - } catch (std::exception &e) { - logger->Error(e.what()); - } catch (...) { - logger->Error("An unknown exception has occurred"); - } - auto end = std::chrono::steady_clock::now(); - logger->Info("Done in: {} ms", (std::chrono::duration_cast(end - start).count())); - Window = nullptr; - Halt = true; -} - -void UIThread(const std::string &prefPath, const std::string &logPath, const std::string &romPath) { - while (!Queue) - sched_yield(); - std::thread gameThread(GameThread, std::string(prefPath), std::string(logPath), std::string(romPath)); - AInputEvent *event{}; - while (!Halt) { - if (AInputQueue_getEvent(Queue, &event) >= -1) { - if (AKeyEvent_getKeyCode(event) == AKEYCODE_BACK) - Halt = true; - AInputQueue_finishEvent(Queue, event, true); - } - } - Queue = nullptr; - gameThread.join(); - Halt = false; - ANativeActivity_finish(Activity); -} - -void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window) { - Window = window; -} - -void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) { - Queue = queue; -} - -void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window) { - Halt = true; - while (Window) - sched_yield(); -} - -void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) { - Halt = true; - while (Queue) - sched_yield(); -} +uint FaultCount{}; void signalHandler(int signal) { syslog(LOG_ERR, "Halting program due to signal: %s", strsignal(signal)); - if (faultCount > 2) - pthread_kill(uiThread->native_handle(), SIGKILL); + if (FaultCount > 2) + exit(SIGKILL); else - ANativeActivity_finish(Activity); - faultCount++; + Halt = true; + FaultCount++; } -JNIEXPORT void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize) { - Activity = activity; - Halt = false; - faultCount = 0; - JNIEnv *env = activity->env; - jobject intent = env->CallObjectMethod(activity->clazz, env->GetMethodID(env->GetObjectClass(activity->clazz), "getIntent", "()Landroid/content/Intent;")); - jclass icl = env->GetObjectClass(intent); - jmethodID gse = env->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"); - auto jsRom = reinterpret_cast(env->CallObjectMethod(intent, gse, env->NewStringUTF("rom"))); - auto jsPrefs = reinterpret_cast(env->CallObjectMethod(intent, gse, env->NewStringUTF("prefs"))); - auto jsLog = reinterpret_cast(env->CallObjectMethod(intent, gse, env->NewStringUTF("log"))); - const char *romPath = env->GetStringUTFChars(jsRom, nullptr); - const char *prefPath = env->GetStringUTFChars(jsPrefs, nullptr); - const char *logPath = env->GetStringUTFChars(jsLog, nullptr); +extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env, jobject instance, jstring romJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) { std::signal(SIGTERM, signalHandler); std::signal(SIGSEGV, signalHandler); std::signal(SIGINT, signalHandler); std::signal(SIGILL, signalHandler); std::signal(SIGABRT, signalHandler); std::signal(SIGFPE, signalHandler); - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onInputQueueCreated = onInputQueueCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; - uiThread = new std::thread(UIThread, std::string(prefPath), std::string(logPath), std::string(romPath)); - env->ReleaseStringUTFChars(jsRom, romPath); - env->ReleaseStringUTFChars(jsPrefs, prefPath); - env->ReleaseStringUTFChars(jsLog, logPath); + + setpriority(PRIO_PROCESS, static_cast(getpid()), skyline::constant::PriorityAn.second); + + auto jvmManager = std::make_shared(env, instance); + auto settings = std::make_shared(preferenceFd); + auto logger = std::make_shared(logFd, static_cast(std::stoi(settings->GetString("log_level")))); + //settings->List(logger); // (Uncomment when you want to print out all settings strings) + + auto start = std::chrono::steady_clock::now(); + + try { + skyline::kernel::OS os(jvmManager, logger, settings); + const char *romString = env->GetStringUTFChars(romJstring, nullptr); + logger->Info("Launching ROM {}", romString); + env->ReleaseStringUTFChars(romJstring, romString); + os.Execute(romFd, static_cast(romType)); + logger->Info("Emulation has ended"); + } catch (std::exception &e) { + logger->Error(e.what()); + } catch (...) { + logger->Error("An unknown exception has occurred"); + } + + auto end = std::chrono::steady_clock::now(); + logger->Info("Done in: {} ms", (std::chrono::duration_cast(end - start).count())); } diff --git a/app/src/main/cpp/skyline/common.cpp b/app/src/main/cpp/skyline/common.cpp index 96b64a1a..fca451e6 100644 --- a/app/src/main/cpp/skyline/common.cpp +++ b/app/src/main/cpp/skyline/common.cpp @@ -4,9 +4,9 @@ #include namespace skyline { - Settings::Settings(const std::string &prefXml) { + Settings::Settings(const int preferenceFd) { tinyxml2::XMLDocument pref; - if (pref.LoadFile(prefXml.c_str())) + if (pref.LoadFile(fdopen(preferenceFd, "r"))) throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr())); tinyxml2::XMLElement *elem = pref.LastChild()->FirstChild()->ToElement(); while (elem) { @@ -51,8 +51,8 @@ namespace skyline { logger->Info("Key: {}, Value: {}, Type: Bool", iter.first, GetBool(iter.first)); } - Logger::Logger(const std::string &logPath, LogLevel configLevel) : configLevel(configLevel) { - logFile.open(logPath, std::ios::app); + Logger::Logger(const int logFd, LogLevel configLevel) : configLevel(configLevel) { + logFile.__open(logFd, std::ios::app); WriteHeader("Logging started"); } @@ -75,9 +75,20 @@ namespace skyline { logFile.flush(); } - DeviceState::DeviceState(kernel::OS *os, std::shared_ptr &thisProcess, std::shared_ptr &thisThread, ANativeWindow *window, std::shared_ptr settings, std::shared_ptr logger) : os(os), settings(std::move(settings)), logger(std::move(logger)), thisProcess(thisProcess), thisThread(thisThread) { - // We assign these later as they may use the state in their constructor and we don't want null pointers + JvmManager::JvmManager(JNIEnv *env, jobject instance) : env(env), instance(instance), instanceClass(env->GetObjectClass(instance)) {} + + jobject JvmManager::GetField(const char *key, const char *signature) { + return env->GetObjectField(instance, env->GetFieldID(instanceClass, key, signature)); + } + + bool JvmManager::CheckNull(const char *key, const char *signature) { + return env->IsSameObject(env->GetObjectField(instance, env->GetFieldID(instanceClass, key, signature)), nullptr); + } + + DeviceState::DeviceState(kernel::OS *os, std::shared_ptr &thisProcess, std::shared_ptr &thisThread, std::shared_ptr jvmManager, std::shared_ptr settings, std::shared_ptr logger) + : os(os), jvmManager(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)), thisProcess(thisProcess), thisThread(thisThread) { + // We assign these later as they use the state in their constructor and we don't want null pointers nce = std::move(std::make_shared(*this)); - gpu = std::move(std::make_shared(*this, window)); + gpu = std::move(std::make_shared(*this)); } } diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index c8796105..4b23a7cf 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace skyline { using u128 = __uint128_t; //!< Unsigned 128-bit integer @@ -94,6 +95,16 @@ namespace skyline { } }; + /** + * @brief This enumerates the types of the ROM + * @note This needs to be synchronized with emu.skyline.loader.BaseLoader.TitleFormat + */ + enum class TitleFormat { + NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO + XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI + NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws + }; + namespace instr { /** * @brief A bit-field struct that encapsulates a BRK instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction. @@ -182,7 +193,7 @@ namespace skyline { enum class Sreg { Sp, Pc, PState }; /** - * @brief The Logger class is to generate a log of the program + * @brief The Logger class is to write log output */ class Logger { private: @@ -195,13 +206,13 @@ namespace skyline { LogLevel configLevel; //!< The level of logs to write /** - * @param logPath The path to the log file + * @param logFd A FD to the log file * @param configLevel The minimum level of logs to write */ - Logger(const std::string &logPath, LogLevel configLevel); + Logger(const int logFd, LogLevel configLevel); /** - * Writes "Logging ended" as a header + * @brief Writes the termination message to the log file */ ~Logger(); @@ -278,9 +289,9 @@ namespace skyline { public: /** - * @param prefXml The path to the preference XML file + * @param preferenceFd An FD to the preference XML file */ - Settings(const std::string &prefXml); + Settings(const int preferenceFd); /** * @brief Retrieves a particular setting as a string @@ -322,6 +333,64 @@ namespace skyline { inline exception(const S &formatStr, Args &&... args) : runtime_error(fmt::format(formatStr, args...)) {} }; + /** + * @brief The JvmManager class is used to simplify transactions with the Java component + */ + class JvmManager { + public: + JNIEnv *env; //!< A pointer to the JNI environment + jobject instance; //!< A reference to the activity + jclass instanceClass; //!< The class of the activity + + /** + * @param env A pointer to the JNI environment + * @param instance A reference to the activity + */ + JvmManager(JNIEnv *env, jobject instance); + + /** + * @brief Retrieves a specific field of the given type from the activity + * @tparam objectType The type of the object in the field + * @param key The name of the field in the activity class + * @return The contents of the field as objectType + */ + template + objectType GetField(const char *key) { + if constexpr(std::is_same()) + return env->GetBooleanField(instance, env->GetFieldID(instanceClass, key, "Z")); + else if constexpr(std::is_same()) + return env->GetByteField(instance, env->GetFieldID(instanceClass, key, "B")); + else if constexpr(std::is_same()) + return env->GetCharField(instance, env->GetFieldID(instanceClass, key, "C")); + else if constexpr(std::is_same()) + return env->GetShortField(instance, env->GetFieldID(instanceClass, key, "S")); + else if constexpr(std::is_same()) + return env->GetIntField(instance, env->GetFieldID(instanceClass, key, "I")); + else if constexpr(std::is_same()) + return env->GetLongField(instance, env->GetFieldID(instanceClass, key, "J")); + else if constexpr(std::is_same()) + return env->GetFloatField(instance, env->GetFieldID(instanceClass, key, "F")); + else if constexpr(std::is_same()) + return env->GetDoubleField(instance, env->GetFieldID(instanceClass, key, "D")); + } + + /** + * @brief Retrieves a specific field from the activity as a jobject + * @param key The name of the field in the activity class + * @param signature The signature of the field + * @return A jobject of the contents of the field + */ + jobject GetField(const char *key, const char *signature); + + /** + * @brief Checks if a specific field from the activity is null or not + * @param key The name of the field in the activity class + * @param signature The signature of the field + * @return If the field is null or not + */ + bool CheckNull(const char *key, const char *signature); + }; + /** * @brief Returns the current time in nanoseconds * @return The current time in nanoseconds @@ -346,13 +415,14 @@ namespace skyline { * @brief This struct is used to hold the state of a device */ struct DeviceState { - DeviceState(kernel::OS *os, std::shared_ptr &thisProcess, std::shared_ptr &thisThread, ANativeWindow *window, std::shared_ptr settings, std::shared_ptr logger); + DeviceState(kernel::OS *os, std::shared_ptr &thisProcess, std::shared_ptr &thisThread, std::shared_ptr jvmManager, std::shared_ptr settings, std::shared_ptr logger); kernel::OS *os; //!< This holds a reference to the OS class std::shared_ptr &thisProcess; //!< This holds a reference to the current process object std::shared_ptr &thisThread; //!< This holds a reference to the current thread object std::shared_ptr nce; //!< This holds a reference to the NCE class std::shared_ptr gpu; //!< This holds a reference to the GPU class + std::shared_ptr jvmManager; //!< This holds a reference to the JvmManager class std::shared_ptr settings; //!< This holds a reference to the Settings class std::shared_ptr logger; //!< This holds a reference to the Logger class }; diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 69a2e04d..48501918 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -4,11 +4,12 @@ #include "gpu/devices/nvhost_channel.h" #include "gpu/devices/nvhost_as_gpu.h" #include +#include extern bool Halt; namespace skyline::gpu { - GPU::GPU(const DeviceState &state, ANativeWindow *window) : state(state), window(window), bufferQueue(state), vsyncEvent(std::make_shared(state)), bufferEvent(std::make_shared(state)) { + GPU::GPU(const DeviceState &state) : state(state), window(ANativeWindow_fromSurface(state.jvmManager->env, state.jvmManager->GetField("surface", "Landroid/view/Surface;"))), bufferQueue(state), vsyncEvent(std::make_shared(state)), bufferEvent(std::make_shared(state)) { ANativeWindow_acquire(window); resolution.width = static_cast(ANativeWindow_getWidth(window)); resolution.height = static_cast(ANativeWindow_getHeight(window)); @@ -20,6 +21,18 @@ namespace skyline::gpu { } void GPU::Loop() { + if(state.jvmManager->CheckNull("surface", "Landroid/view/Surface;")) { + while (state.jvmManager->CheckNull("surface", "Landroid/view/Surface;")) { + if(state.jvmManager->GetField("halt")) + return; + sched_yield(); + } + window = ANativeWindow_fromSurface(state.jvmManager->env, state.jvmManager->GetField("surface", "Landroid/view/Surface;")); + ANativeWindow_acquire(window); + resolution.width = static_cast(ANativeWindow_getWidth(window)); + resolution.height = static_cast(ANativeWindow_getHeight(window)); + format = ANativeWindow_getFormat(window); + } if (!bufferQueue.displayQueue.empty()) { auto &buffer = bufferQueue.displayQueue.front(); bufferQueue.displayQueue.pop(); diff --git a/app/src/main/cpp/skyline/gpu.h b/app/src/main/cpp/skyline/gpu.h index 318442f9..aa7b9ada 100644 --- a/app/src/main/cpp/skyline/gpu.h +++ b/app/src/main/cpp/skyline/gpu.h @@ -31,7 +31,7 @@ namespace skyline::gpu { /** * @param window The ANativeWindow to render to */ - GPU(const DeviceState &state, ANativeWindow *window); + GPU(const DeviceState &state); /** * @brief The destructor for the GPU class diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index 20d6500b..41f91463 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -2,12 +2,12 @@ #include #include +#include namespace skyline::loader { class Loader { protected: - std::string filePath; //!< The path to the ROM file - std::ifstream file; //!< An input stream from the file + const int romFd; //!< An FD to the ROM file /** * @brief Read the file at a particular offset @@ -17,9 +17,8 @@ namespace skyline::loader { * @param size The amount to read in bytes */ template - void ReadOffset(T *output, u32 offset, size_t size) { - file.seekg(offset, std::ios_base::beg); - file.read(reinterpret_cast(output), size); + void ReadOffset(T *output, u64 offset, size_t size) { + pread64(romFd, output, size, offset); } /** @@ -52,7 +51,7 @@ namespace skyline::loader { /** * @param filePath The path to the ROM file */ - Loader(std::string &filePath) : filePath(filePath), file(filePath, std::ios::binary | std::ios::beg) {} + Loader(const int romFd) : romFd(romFd) {} /** * This loads in the data of the main process diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index 8d58e511..4d645dde 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -2,7 +2,7 @@ #include "nro.h" namespace skyline::loader { - NroLoader::NroLoader(std::string filePath) : Loader(filePath) { + NroLoader::NroLoader(const int romFd) : Loader(romFd) { ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader)); if (header.magic != constant::NroMagic) throw exception("Invalid NRO magic! 0x{0:X}", header.magic); diff --git a/app/src/main/cpp/skyline/loader/nro.h b/app/src/main/cpp/skyline/loader/nro.h index c49037a2..60f57115 100644 --- a/app/src/main/cpp/skyline/loader/nro.h +++ b/app/src/main/cpp/skyline/loader/nro.h @@ -45,7 +45,7 @@ namespace skyline::loader { /** * @param filePath The path to the ROM file */ - NroLoader(std::string filePath); + NroLoader(const int romFd); /** * This loads in the data of the main process diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 0d5d3069..d2a97691 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -32,7 +32,7 @@ namespace skyline { void NCE::Execute() { int status = 0; while (!Halt && !state.os->processMap.empty()) { - for (const auto &process : state.os->processMap) { // NOLINT(performance-for-range-copy) + for (const auto &process : state.os->processMap) { state.os->thisProcess = process.second; state.os->thisThread = process.second->threadMap.at(process.first); currPid = process.first; @@ -87,6 +87,7 @@ namespace skyline { } state.os->serviceManager.Loop(); state.gpu->Loop(); + Halt = state.jvmManager->GetField("halt"); } for (const auto &process : state.os->processMap) { state.os->KillThread(process.first); diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 98918899..c1c46dde 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -3,14 +3,12 @@ #include "loader/nro.h" namespace skyline::kernel { - OS::OS(std::shared_ptr &logger, std::shared_ptr &settings, ANativeWindow *window) : state(this, thisProcess, thisThread, window, settings, logger), serviceManager(state) {} + OS::OS(std::shared_ptr& jvmManager, std::shared_ptr &logger, std::shared_ptr &settings) : state(this, thisProcess, thisThread, jvmManager, settings, logger), serviceManager(state) {} - void OS::Execute(const std::string &romFile) { - std::string romExt = romFile.substr(romFile.find_last_of('.') + 1); - std::transform(romExt.begin(), romExt.end(), romExt.begin(), [](unsigned char c) { return std::tolower(c); }); + void OS::Execute(const int romFd, const TitleFormat romType) { auto process = CreateProcess(constant::BaseAddr, constant::DefStackSize); - if (romExt == "nro") { - loader::NroLoader loader(romFile); + if (romType == TitleFormat::NRO) { + loader::NroLoader loader(romFd); loader.LoadProcessData(process, state); } else throw exception("Unsupported ROM extension."); diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 9add81ce..fe3f8780 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -30,13 +30,14 @@ namespace skyline::kernel { * @param settings An instance of the Settings class * @param window The ANativeWindow object to draw the screen to */ - OS(std::shared_ptr &logger, std::shared_ptr &settings, ANativeWindow *window); + OS(std::shared_ptr& jvmManager, std::shared_ptr &logger, std::shared_ptr &settings); /** * @brief Execute a particular ROM file. This launches the main process and calls the NCE class to handle execution. - * @param romFile The path to the ROM file to execute + * @param romFd A FD to the ROM file to execute + * @param romType The type of the ROM file */ - void Execute(const std::string &romFile); + void Execute(const int romFd, const TitleFormat romType); /** * @brief Creates a new process diff --git a/app/src/main/java/emu/skyline/GameActivity.kt b/app/src/main/java/emu/skyline/GameActivity.kt index 717cb784..478fa0b2 100644 --- a/app/src/main/java/emu/skyline/GameActivity.kt +++ b/app/src/main/java/emu/skyline/GameActivity.kt @@ -38,7 +38,7 @@ class GameActivity : AppCompatActivity(), SurfaceHolder.Callback, InputQueue.Cal val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml") preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE) val log = File("${applicationInfo.dataDir}/skyline.log") - logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_READ_WRITE) + logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE) window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) game_view.holder.addCallback(this) //window.takeInputQueue(this) diff --git a/app/src/main/java/emu/skyline/adapter/LogAdapter.kt b/app/src/main/java/emu/skyline/adapter/LogAdapter.kt index 32791cde..43762c3a 100644 --- a/app/src/main/java/emu/skyline/adapter/LogAdapter.kt +++ b/app/src/main/java/emu/skyline/adapter/LogAdapter.kt @@ -73,7 +73,7 @@ internal class LogAdapter internal constructor(val context: Context, val compact viewHolder.txtTitle!!.text = item.title } viewHolder.position = position - return view!! + return view } private class ViewHolder { diff --git a/app/src/main/java/emu/skyline/loader/BaseLoader.kt b/app/src/main/java/emu/skyline/loader/BaseLoader.kt index 890888e8..3fa08d5f 100644 --- a/app/src/main/java/emu/skyline/loader/BaseLoader.kt +++ b/app/src/main/java/emu/skyline/loader/BaseLoader.kt @@ -19,7 +19,7 @@ enum class TitleFormat { internal class TitleEntry(var name: String, var author: String, var romType: TitleFormat, var valid: Boolean, @Transient var uri: Uri, @Transient var icon: Bitmap) : Serializable { constructor(context: Context, author: String, romType: TitleFormat, valid: Boolean, uri: Uri) : this("", author, romType, valid, uri, context.resources.getDrawable(R.drawable.ic_missing_icon, context.theme).toBitmap(256, 256)) { - context.contentResolver.query(uri, null, null, null, null)!!.use { cursor -> + context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() name = cursor.getString(nameIndex) diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index ba489324..4449f71b 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -12,12 +12,4 @@ 2 3 - - System Language - English - - - sys - en - - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cdbc8ed5..6837e093 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,8 +27,6 @@ Compact Logs Logs will be displayed in a compact form factor The logs will be displayed in a verbose form factor - Localization - Language System Use Docked Mode The system will emulate being in handheld mode