Merge pull request #11908 from t895/log-spam

android: Fix URI parsing in native code
This commit is contained in:
liamwhite 2023-10-30 13:28:11 -04:00 committed by GitHub
commit 07276cf62a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 714 additions and 568 deletions

View file

@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@ -68,7 +69,7 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun openContentUri(path: String?, openmode: String?): Int { fun openContentUri(path: String?, openmode: String?): Int {
return if (isNativePath(path!!)) { return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode) YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else { } else {
FileUtil.openContentUri(path, openmode) FileUtil.openContentUri(path, openmode)
@ -78,7 +79,7 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getSize(path: String?): Long { fun getSize(path: String?): Long {
return if (isNativePath(path!!)) { return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path) YuzuApplication.documentsTree!!.getFileSize(path)
} else { } else {
FileUtil.getFileSize(path) FileUtil.getFileSize(path)
@ -88,23 +89,41 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun exists(path: String?): Boolean { fun exists(path: String?): Boolean {
return if (isNativePath(path!!)) { return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path) YuzuApplication.documentsTree!!.exists(path)
} else { } else {
FileUtil.exists(path) FileUtil.exists(path, suppressLog = true)
} }
} }
@Keep @Keep
@JvmStatic @JvmStatic
fun isDirectory(path: String?): Boolean { fun isDirectory(path: String?): Boolean {
return if (isNativePath(path!!)) { return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path) YuzuApplication.documentsTree!!.isDirectory(path)
} else { } else {
FileUtil.isDirectory(path) FileUtil.isDirectory(path)
} }
} }
@Keep
@JvmStatic
fun getParentDirectory(path: String): String =
if (DocumentsTree.isNativePath(path)) {
YuzuApplication.documentsTree!!.getParentDirectory(path)
} else {
path
}
@Keep
@JvmStatic
fun getFilename(path: String): String =
if (DocumentsTree.isNativePath(path)) {
YuzuApplication.documentsTree!!.getFilename(path)
} else {
FileUtil.getFilename(Uri.parse(path))
}
/** /**
* Returns true if pro controller isn't available and handheld is * Returns true if pro controller isn't available and handheld is
*/ */
@ -215,32 +234,6 @@ object NativeLibrary {
external fun initGameIni(gameID: String?) external fun initGameIni(gameID: String?)
/**
* Gets the embedded icon within the given ROM.
*
* @param filename the file path to the ROM.
* @return a byte array containing the JPEG data for the icon.
*/
external fun getIcon(filename: String): ByteArray
/**
* Gets the embedded title of the given ISO/ROM.
*
* @param filename The file path to the ISO/ROM.
* @return the embedded title of the ISO/ROM.
*/
external fun getTitle(filename: String): String
external fun getDescription(filename: String): String
external fun getGameId(filename: String): String
external fun getRegions(filename: String): String
external fun getCompany(filename: String): String
external fun isHomebrew(filename: String): Boolean
external fun setAppDirectory(directory: String) external fun setAppDirectory(directory: String)
/** /**
@ -293,11 +286,6 @@ object NativeLibrary {
*/ */
external fun stopEmulation() external fun stopEmulation()
/**
* Resets the in-memory ROM metadata cache.
*/
external fun resetRomMetadata()
/** /**
* Returns true if emulation is running (or is paused). * Returns true if emulation is running (or is paused).
*/ */

View file

@ -147,7 +147,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
private class DiffCallback : DiffUtil.ItemCallback<Game>() { private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.gameId == newItem.gameId return oldItem.programId == newItem.programId
} }
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {

View file

@ -12,15 +12,14 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
class Game( class Game(
val title: String, val title: String,
val description: String,
val regions: String,
val path: String, val path: String,
val gameId: String, val programId: String,
val company: String, val developer: String,
val version: String,
val isHomebrew: Boolean val isHomebrew: Boolean
) : Parcelable { ) : Parcelable {
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed" val keyLastPlayedTime get() = "${programId}_LastPlayed"
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is Game) { if (other !is Game) {
@ -32,11 +31,9 @@ class Game(
override fun hashCode(): Int { override fun hashCode(): Int {
var result = title.hashCode() var result = title.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode() result = 31 * result + path.hashCode()
result = 31 * result + gameId.hashCode() result = 31 * result + programId.hashCode()
result = 31 * result + company.hashCode() result = 31 * result + developer.hashCode()
result = 31 * result + isHomebrew.hashCode() result = 31 * result + isHomebrew.hashCode()
return result return result
} }

View file

@ -14,15 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.GameMetadata
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games val games: StateFlow<List<Game>> get() = _games
private val _games = MutableStateFlow(emptyList<Game>()) private val _games = MutableStateFlow(emptyList<Game>())
@ -58,7 +56,8 @@ class GamesViewModel : ViewModel() {
val game: Game val game: Game
try { try {
game = Json.decodeFromString(it) game = Json.decodeFromString(it)
} catch (e: MissingFieldException) { } catch (e: Exception) {
// We don't care about any errors related to parsing the game cache
return@forEach return@forEach
} }
@ -113,7 +112,7 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata() GameMetadata.resetMetadata()
setGames(GameHelper.getGames()) setGames(GameHelper.getGames())
_isReloading.value = false _isReloading.value = false

View file

@ -42,6 +42,23 @@ class DocumentsTree {
return node != null && node.isDirectory return node != null && node.isDirectory
} }
fun getParentDirectory(filepath: String): String {
val node = resolvePath(filepath)!!
val parentNode = node.parent
if (parentNode != null && parentNode.isDirectory) {
return parentNode.uri!!.toString()
}
return node.uri!!.toString()
}
fun getFilename(filepath: String): String {
val node = resolvePath(filepath)
if (node != null) {
return node.name!!
}
return filepath
}
private fun resolvePath(filepath: String): DocumentsNode? { private fun resolvePath(filepath: String): DocumentsNode? {
val tokens = StringTokenizer(filepath, File.separator, false) val tokens = StringTokenizer(filepath, File.separator, false)
var iterator = root var iterator = root

View file

@ -144,7 +144,7 @@ object FileUtil {
* @param path Native content uri path * @param path Native content uri path
* @return bool * @return bool
*/ */
fun exists(path: String?): Boolean { fun exists(path: String?, suppressLog: Boolean = false): Boolean {
var c: Cursor? = null var c: Cursor? = null
try { try {
val mUri = Uri.parse(path) val mUri = Uri.parse(path)
@ -152,7 +152,9 @@ object FileUtil {
c = context.contentResolver.query(mUri, columns, null, null, null) c = context.contentResolver.query(mUri, columns, null, null, null)
return c!!.count > 0 return c!!.count > 0
} catch (e: Exception) { } catch (e: Exception) {
if (!suppressLog) {
Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
}
} finally { } finally {
closeQuietly(c) closeQuietly(c)
} }

View file

@ -71,27 +71,26 @@ object GameHelper {
fun getGame(uri: Uri, addedToLibrary: Boolean): Game { fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
val filePath = uri.toString() val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath) var name = GameMetadata.getTitle(filePath)
// If the game's title field is empty, use the filename. // If the game's title field is empty, use the filename.
if (name.isEmpty()) { if (name.isEmpty()) {
name = FileUtil.getFilename(uri) name = FileUtil.getFilename(uri)
} }
var gameId = NativeLibrary.getGameId(filePath) var programId = GameMetadata.getProgramId(filePath)
// If the game's ID field is empty, use the filename without extension. // If the game's ID field is empty, use the filename without extension.
if (gameId.isEmpty()) { if (programId.isEmpty()) {
gameId = name.substring(0, name.lastIndexOf(".")) programId = name.substring(0, name.lastIndexOf("."))
} }
val newGame = Game( val newGame = Game(
name, name,
NativeLibrary.getDescription(filePath).replace("\n", " "),
NativeLibrary.getRegions(filePath),
filePath, filePath,
gameId, programId,
NativeLibrary.getCompany(filePath), GameMetadata.getDeveloper(filePath),
NativeLibrary.isHomebrew(filePath) GameMetadata.getVersion(filePath),
GameMetadata.getIsHomebrew(filePath)
) )
if (addedToLibrary) { if (addedToLibrary) {

View file

@ -18,7 +18,6 @@ import coil.key.Keyer
import coil.memory.MemoryCache import coil.memory.MemoryCache
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.request.Options import coil.request.Options
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
@ -36,7 +35,7 @@ class GameIconFetcher(
} }
private fun decodeGameIcon(uri: String): Bitmap? { private fun decodeGameIcon(uri: String): Bitmap? {
val data = NativeLibrary.getIcon(uri) val data = GameMetadata.getIcon(uri)
return BitmapFactory.decodeByteArray( return BitmapFactory.decodeByteArray(
data, data,
0, 0,

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
object GameMetadata {
external fun getTitle(path: String): String
external fun getProgramId(path: String): String
external fun getDeveloper(path: String): String
external fun getVersion(path: String): String
external fun getIcon(path: String): ByteArray
external fun getIsHomebrew(path: String): Boolean
external fun resetMetadata()
}

View file

@ -14,8 +14,10 @@ add_library(yuzu-android SHARED
id_cache.cpp id_cache.cpp
id_cache.h id_cache.h
native.cpp native.cpp
native.h
native_config.cpp native_config.cpp
uisettings.cpp uisettings.cpp
game_metadata.cpp
) )
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})

View file

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <core/core.h>
#include <core/file_sys/patch_manager.h>
#include <core/loader/nro.h>
#include <jni.h>
#include "core/loader/loader.h"
#include "jni/android_common/android_common.h"
#include "native.h"
struct RomMetadata {
std::string title;
u64 programId;
std::string developer;
std::string version;
std::vector<u8> icon;
bool isHomebrew;
};
std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file =
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadProgramId(entry.programId);
loader->ReadIcon(entry.icon);
const FileSys::PatchManager pm{
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
EmulationSession::GetInstance().System().GetContentProvider()};
const auto control = pm.GetControlMetadata();
if (control.first != nullptr) {
entry.developer = control.first->GetDeveloperName();
entry.version = control.first->GetVersionString();
} else {
FileSys::NACP nacp;
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
entry.developer = nacp.GetDeveloperName();
} else {
entry.developer = "";
}
entry.version = "1.0.0";
}
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
return entry;
}
RomMetadata GetRomMetadata(const std::string& path) {
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
return search->second;
}
return CacheRomMetadata(path);
}
extern "C" {
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
jstring jpath) {
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title);
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
jstring jpath) {
return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId));
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
jstring jpath) {
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer);
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
jstring jpath) {
return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version);
}
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
jstring jpath) {
auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon;
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
reinterpret_cast<jbyte*>(icon_data.data()));
return icon;
}
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
jstring jpath) {
return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew);
}
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
return m_rom_metadata_cache.clear();
}
} // extern "C"

View file

@ -33,7 +33,6 @@
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h" #include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h" #include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h" #include "core/file_sys/vfs_real.h"
@ -48,63 +47,55 @@
#include "core/hid/emulated_controller.h" #include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h" #include "core/hid/hid_core.h"
#include "core/hid/hid_types.h" #include "core/hid/hid_types.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h" #include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "jni/android_common/android_common.h" #include "jni/android_common/android_common.h"
#include "jni/applets/software_keyboard.h"
#include "jni/config.h" #include "jni/config.h"
#include "jni/emu_window/emu_window.h"
#include "jni/id_cache.h" #include "jni/id_cache.h"
#include "video_core/rasterizer_interface.h" #include "jni/native.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#define jconst [[maybe_unused]] const auto #define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto #define jauto [[maybe_unused]] auto
namespace { static EmulationSession s_instance;
class EmulationSession final { EmulationSession::EmulationSession() {
public:
EmulationSession() {
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
} }
~EmulationSession() = default; EmulationSession& EmulationSession::GetInstance() {
static EmulationSession& GetInstance() {
return s_instance; return s_instance;
} }
const Core::System& System() const { const Core::System& EmulationSession::System() const {
return m_system; return m_system;
} }
Core::System& System() { Core::System& EmulationSession::System() {
return m_system; return m_system;
} }
const EmuWindow_Android& Window() const { const EmuWindow_Android& EmulationSession::Window() const {
return *m_window; return *m_window;
} }
EmuWindow_Android& Window() { EmuWindow_Android& EmulationSession::Window() {
return *m_window; return *m_window;
} }
ANativeWindow* NativeWindow() const { ANativeWindow* EmulationSession::NativeWindow() const {
return m_native_window; return m_native_window;
} }
void SetNativeWindow(ANativeWindow* native_window) { void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
m_native_window = native_window; m_native_window = native_window;
} }
int InstallFileToNand(std::string filename, std::string file_extension) { int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
std::size_t block_size) { std::size_t block_size) {
if (src == nullptr || dest == nullptr) { if (src == nullptr || dest == nullptr) {
@ -153,8 +144,8 @@ public:
return InstallError; return InstallError;
} }
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
*nsp, true, copy_func); copy_func);
switch (res) { switch (res) {
case FileSys::InstallResult::Success: case FileSys::InstallResult::Success:
@ -168,7 +159,8 @@ public:
} }
} }
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
const std::string& custom_driver_dir,
const std::string& custom_driver_name, const std::string& custom_driver_name,
const std::string& file_redirect_dir) { const std::string& file_redirect_dir) {
#ifdef ARCHITECTURE_arm64 #ifdef ARCHITECTURE_arm64
@ -191,8 +183,7 @@ public:
// Try to load the system driver. // Try to load the system driver.
if (!handle) { if (!handle) {
handle = handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
nullptr, nullptr, file_redirect_dir_, nullptr); nullptr, nullptr, file_redirect_dir_, nullptr);
} }
@ -200,27 +191,27 @@ public:
#endif #endif
} }
bool IsRunning() const { bool EmulationSession::IsRunning() const {
return m_is_running; return m_is_running;
} }
bool IsPaused() const { bool EmulationSession::IsPaused() const {
return m_is_running && m_is_paused; return m_is_running && m_is_paused;
} }
const Core::PerfStatsResults& PerfStats() const { const Core::PerfStatsResults& EmulationSession::PerfStats() const {
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
return m_perf_stats; return m_perf_stats;
} }
void SurfaceChanged() { void EmulationSession::SurfaceChanged() {
if (!IsRunning()) { if (!IsRunning()) {
return; return;
} }
m_window->OnSurfaceChanged(m_native_window); m_window->OnSurfaceChanged(m_native_window);
} }
void ConfigureFilesystemProvider(const std::string& filepath) { void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
if (!file) { if (!file) {
return; return;
@ -256,12 +247,12 @@ public:
} }
} }
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
// Create the render window. // Create the render window.
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_window =
m_vulkan_library); std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
m_system.SetFilesystem(m_vfs); m_system.SetFilesystem(m_vfs);
m_system.GetUserChannel().clear(); m_system.GetUserChannel().clear();
@ -310,7 +301,7 @@ public:
return Core::SystemResultStatus::Success; return Core::SystemResultStatus::Success;
} }
void ShutdownEmulation() { void EmulationSession::ShutdownEmulation() {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
m_is_running = false; m_is_running = false;
@ -333,25 +324,25 @@ public:
m_window.reset(); m_window.reset();
} }
void PauseEmulation() { void EmulationSession::PauseEmulation() {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
m_system.Pause(); m_system.Pause();
m_is_paused = true; m_is_paused = true;
} }
void UnPauseEmulation() { void EmulationSession::UnPauseEmulation() {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
m_system.Run(); m_system.Run();
m_is_paused = false; m_is_paused = false;
} }
void HaltEmulation() { void EmulationSession::HaltEmulation() {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
m_is_running = false; m_is_running = false;
m_cv.notify_one(); m_cv.notify_one();
} }
void RunEmulation() { void EmulationSession::RunEmulation() {
{ {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
m_is_running = true; m_is_running = true;
@ -361,8 +352,7 @@ public:
if (Settings::values.use_disk_shader_cache.GetValue()) { if (Settings::values.use_disk_shader_cache.GetValue()) {
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
m_system.Renderer().ReadRasterizer()->LoadDiskResources( m_system.Renderer().ReadRasterizer()->LoadDiskResources(
m_system.GetApplicationProcessProgramID(), std::stop_token{}, m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
} }
@ -391,23 +381,7 @@ public:
} }
} }
std::string GetRomTitle(const std::string& path) { bool EmulationSession::IsHandheldOnly() {
return GetRomMetadata(path).title;
}
std::vector<u8> GetRomIcon(const std::string& path) {
return GetRomMetadata(path).icon;
}
bool GetIsHomebrew(const std::string& path) {
return GetRomMetadata(path).isHomebrew;
}
void ResetRomMetadata() {
m_rom_metadata_cache.clear();
}
bool IsHandheldOnly() {
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
if (npad_style_set.fullkey == 1) { if (npad_style_set.fullkey == 1) {
@ -421,18 +395,17 @@ public:
return !Settings::IsDockedMode(); return !Settings::IsDockedMode();
} }
void SetDeviceType([[maybe_unused]] int index, int type) { void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
} }
void OnGamepadConnectEvent([[maybe_unused]] int index) { void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
// Ensure that player1 is configured correctly and handheld disconnected // Ensure that player1 is configured correctly and handheld disconnected
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
jauto handheld = jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
@ -443,8 +416,7 @@ public:
// Ensure that handheld is configured correctly and player 1 disconnected // Ensure that handheld is configured correctly and player 1 disconnected
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
jauto player1 = jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
@ -458,105 +430,34 @@ public:
} }
} }
void OnGamepadDisconnectEvent([[maybe_unused]] int index) { void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
controller->Disconnect(); controller->Disconnect();
} }
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
return m_software_keyboard; return m_software_keyboard;
} }
private: void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
struct RomMetadata { int max) {
std::string title;
std::vector<u8> icon;
bool isHomebrew;
};
RomMetadata GetRomMetadata(const std::string& path) {
if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
return search->second;
}
return CacheRomMetadata(path);
}
RomMetadata CacheRomMetadata(const std::string& path) {
jconst file = Core::GetGameFileFromPath(m_vfs, path);
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon);
if (loader->GetFileType() == Loader::FileType::NRO) {
jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
return entry;
}
private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
JNIEnv* env = IDCache::GetEnvForThread(); JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
static_cast<jint>(progress), static_cast<jint>(max)); static_cast<jint>(progress), static_cast<jint>(max));
} }
static void OnEmulationStarted() { void EmulationSession::OnEmulationStarted() {
JNIEnv* env = IDCache::GetEnvForThread(); JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted());
IDCache::GetOnEmulationStarted());
} }
static void OnEmulationStopped(Core::SystemResultStatus result) { void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
JNIEnv* env = IDCache::GetEnvForThread(); JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(),
IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); static_cast<jint>(result));
} }
private:
static EmulationSession s_instance;
// Frontend management
std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
// Window management
std::unique_ptr<EmuWindow_Android> m_window;
ANativeWindow* m_native_window{};
// Core emulation
Core::System m_system;
InputCommon::InputSubsystem m_input_subsystem;
Common::DetachedTasks m_detached_tasks;
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
std::atomic<bool> m_is_running = false;
std::atomic<bool> m_is_paused = false;
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
// GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
// Synchronization
std::condition_variable_any m_cv;
mutable std::mutex m_perf_stats_mutex;
mutable std::mutex m_mutex;
};
/*static*/ EmulationSession EmulationSession::s_instance;
} // Anonymous namespace
static Core::SystemResultStatus RunEmulation(const std::string& filepath) { static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
Common::Log::Initialize(); Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true); Common::Log::SetColorConsoleBackendEnabled(true);
@ -657,10 +558,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
EmulationSession::GetInstance().HaltEmulation(); EmulationSession::GetInstance().HaltEmulation();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
EmulationSession::GetInstance().ResetRomMetadata();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
} }
@ -766,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
} }
} }
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
jstring j_filename) {
jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
reinterpret_cast<jbyte*>(icon_data.data()));
return icon;
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
jstring j_filename) {
jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
return env->NewStringUTF(title.c_str());
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
jstring j_filename) {
return j_filename;
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
jstring j_filename) {
return j_filename;
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
jstring j_filename) {
return env->NewStringUTF("");
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
jstring j_filename) {
return env->NewStringUTF("");
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
jstring j_filename) {
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
// Create the default config.ini. // Create the default config.ini.
Config{}; Config{};

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <android/native_window_jni.h>
#include "common/detached_tasks.h"
#include "core/core.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/perf_stats.h"
#include "jni/applets/software_keyboard.h"
#include "jni/emu_window/emu_window.h"
#include "video_core/rasterizer_interface.h"
#pragma once
class EmulationSession final {
public:
explicit EmulationSession();
~EmulationSession() = default;
static EmulationSession& GetInstance();
const Core::System& System() const;
Core::System& System();
const EmuWindow_Android& Window() const;
EmuWindow_Android& Window();
ANativeWindow* NativeWindow() const;
void SetNativeWindow(ANativeWindow* native_window);
void SurfaceChanged();
int InstallFileToNand(std::string filename, std::string file_extension);
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir);
bool IsRunning() const;
bool IsPaused() const;
void PauseEmulation();
void UnPauseEmulation();
void HaltEmulation();
void RunEmulation();
void ShutdownEmulation();
const Core::PerfStatsResults& PerfStats() const;
void ConfigureFilesystemProvider(const std::string& filepath);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
bool IsHandheldOnly();
void SetDeviceType([[maybe_unused]] int index, int type);
void OnGamepadConnectEvent([[maybe_unused]] int index);
void OnGamepadDisconnectEvent([[maybe_unused]] int index);
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
static void OnEmulationStarted();
static void OnEmulationStopped(Core::SystemResultStatus result);
private:
// Window management
std::unique_ptr<EmuWindow_Android> m_window;
ANativeWindow* m_native_window{};
// Core emulation
Core::System m_system;
InputCommon::InputSubsystem m_input_subsystem;
Common::DetachedTasks m_detached_tasks;
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
std::atomic<bool> m_is_running = false;
std::atomic<bool> m_is_paused = false;
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
// GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
// Synchronization
std::condition_variable_any m_cv;
mutable std::mutex m_perf_stats_mutex;
mutable std::mutex m_mutex;
};

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/fs_android.h" #include "common/fs/fs_android.h"
#include "common/string_util.h"
namespace Common::FS::Android { namespace Common::FS::Android {
@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) {
env->GetJavaVM(&g_jvm); env->GetJavaVM(&g_jvm);
native_library = clazz; native_library = clazz;
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature) F(JMethodID, JMethodName, Signature)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature) F(JMethodID, JMethodName, Signature)
#define F(JMethodID, JMethodName, Signature) \ #define F(JMethodID, JMethodName, Signature) \
JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS) ANDROID_STORAGE_FUNCTIONS(FS)
#undef F #undef F
#undef FS #undef FS
#undef FR #undef FR
#undef FH
} }
void UnRegisterCallbacks() { void UnRegisterCallbacks() {
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) JMethodID = nullptr; #define F(JMethodID) JMethodID = nullptr;
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS) ANDROID_STORAGE_FUNCTIONS(FS)
#undef F #undef F
#undef FS #undef FS
#undef FR #undef FR
#undef FH
} }
bool IsContentUri(const std::string& path) { bool IsContentUri(const std::string& path) {
@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F #undef F
#undef FR #undef FR
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
F(FunctionName, JMethodID, Caller)
#define F(FunctionName, JMethodID, Caller) \
std::string FunctionName(const std::string& filepath) { \
if (JMethodID == nullptr) { \
return 0; \
} \
auto env = GetEnvForThread(); \
jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
jstring j_return = \
static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \
if (!j_return) { \
return {}; \
} \
const jchar* jchars = env->GetStringChars(j_return, nullptr); \
const jsize length = env->GetStringLength(j_return); \
const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \
const std::string converted_string = Common::UTF16ToUTF8(string_view); \
env->ReleaseStringChars(j_return, jchars); \
return converted_string; \
}
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
#undef F
#undef FH
} // namespace Common::FS::Android } // namespace Common::FS::Android

View file

@ -17,19 +17,28 @@
"(Ljava/lang/String;)Z") \ "(Ljava/lang/String;)Z") \
V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \
V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \
"(Ljava/lang/String;)Ljava/lang/String;") \
V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \
"(Ljava/lang/String;)Ljava/lang/String;")
namespace Common::FS::Android { namespace Common::FS::Android {
static JavaVM* g_jvm = nullptr; static JavaVM* g_jvm = nullptr;
static jclass native_library = nullptr; static jclass native_library = nullptr;
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) static jmethodID JMethodID = nullptr; #define F(JMethodID) static jmethodID JMethodID = nullptr;
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS) ANDROID_STORAGE_FUNCTIONS(FS)
#undef F #undef F
#undef FS #undef FS
#undef FR #undef FR
#undef FH
enum class OpenMode { enum class OpenMode {
Read, Read,
@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F #undef F
#undef FR #undef FR
#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName)
#define F(FunctionName) std::string FunctionName(const std::string& filepath);
ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
#undef F
#undef FH
} // namespace Common::FS::Android } // namespace Common::FS::Android

View file

@ -401,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
} }
std::string_view GetParentPath(std::string_view path) { std::string_view GetParentPath(std::string_view path) {
if (path.empty()) {
return path;
}
#ifdef ANDROID
if (path[0] != '/') {
std::string path_string{path};
return FS::Android::GetParentDirectory(path_string);
}
#endif
const auto name_bck_index = path.rfind('\\'); const auto name_bck_index = path.rfind('\\');
const auto name_fwd_index = path.rfind('/'); const auto name_fwd_index = path.rfind('/');
std::size_t name_index; std::size_t name_index;

View file

@ -14,6 +14,10 @@
#include <windows.h> #include <windows.h>
#endif #endif
#ifdef ANDROID
#include <common/fs/fs_android.h>
#endif
namespace Common { namespace Common {
/// Make a string lowercase /// Make a string lowercase
@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
if (full_path.empty()) if (full_path.empty())
return false; return false;
#ifdef ANDROID
if (full_path[0] != '/') {
*_pPath = Common::FS::Android::GetParentDirectory(full_path);
*_pFilename = Common::FS::Android::GetFilename(full_path);
return true;
}
#endif
std::size_t dir_end = full_path.find_last_of("/" std::size_t dir_end = full_path.find_last_of("/"
// windows needs the : included for something like just "C:" to be considered a directory // windows needs the : included for something like just "C:" to be considered a directory
#ifdef _WIN32 #ifdef _WIN32