Refactor android native code

This commit is contained in:
SSimco
2023-07-30 14:29:33 +03:00
parent e83359b9d8
commit a6f09536c6
25 changed files with 9289 additions and 578 deletions
@@ -116,7 +116,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
if(backend == GuiSystem::WindowHandleInfo::Backend::X11)
requiredExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
#ifdef HAS_WAYLAND
else if (backend == GuiSystem::WindowHandleInfo::Backend::WAYLAND)
else if (backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
requiredExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif // HAS_WAYLAND
#endif // __ANDROID__
@@ -1191,7 +1191,7 @@ std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo
if(backend == GuiSystem::WindowHandleInfo::Backend::X11)
requiredInstanceExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
#if HAS_WAYLAND
else if (backend == GuiSystem::WindowHandleInfo::Backend::WAYLAND)
else if (backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
requiredInstanceExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif // HAS_WAYLAND
#endif // __ANDROID__
@@ -1365,7 +1365,7 @@ VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, struc
if(windowInfo.backend == GuiSystem::WindowHandleInfo::Backend::X11)
return CreateXlibSurface(instance, static_cast<Display*>(windowInfo.display), reinterpret_cast<Window>(windowInfo.surface));
#ifdef HAS_WAYLAND
if(windowInfo.backend == GuiSystem::WindowHandleInfo::Backend::WAYLAND)
if(windowInfo.backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
return CreateWaylandSurface(instance, static_cast<wl_display*>(windowInfo.display), static_cast<wl_surface*>(windowInfo.surface));
#endif
return {};
+4 -4
View File
@@ -8,10 +8,10 @@ struct WindowHandleInfo
enum class Backend
{
X11,
WAYLAND,
ANDROID,
COCOA,
WINDOWS
Wayland,
Android,
Cocoa,
Windows
} backend;
void* display = nullptr;
void* surface = nullptr;
@@ -0,0 +1,56 @@
#include "AndroidAudio.h"
#include "Cafe/OS/libs/snd_core/ax.h"
#include "audio/IAudioAPI.h"
#if HAS_CUBEB
#include "audio/CubebAPI.h"
#endif // HAS_CUBEB
namespace AndroidAudio
{
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV)
{
static constexpr int AX_FRAMES_PER_GROUP = 4;
std::unique_lock lock(g_audioMutex);
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
switch (channels)
{
case 0:
channels = 1;
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
switch (audioApi)
{
#if HAS_CUBEB
case IAudioAPI::AudioAPI::Cubeb:
{
audioDevice.reset();
std::shared_ptr<CubebAPI::CubebDeviceDescription> deviceDescriptionPtr = std::make_shared<CubebAPI::CubebDeviceDescription>(nullptr, std::string(), std::wstring());
audioDevice = IAudioAPI::CreateDevice(IAudioAPI::AudioAPI::Cubeb, deviceDescriptionPtr, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
audioDevice->SetVolume(volume);
break;
}
#endif // HAS_CUBEB
default:
cemuLog_log(LogType::Force, fmt::format("Invalid audio api: {}", audioApi));
break;
}
}
void setAudioVolume(sint32 volume, bool isTV)
{
std::shared_lock lock(g_audioMutex);
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
if (audioDevice)
audioDevice->SetVolume(volume);
}
}; // namespace AndroidAudio
@@ -0,0 +1,9 @@
#pragma once
#include "audio/IAudioAPI.h"
namespace AndroidAudio
{
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV = true);
void setAudioVolume(sint32 volume, bool isTV = true);
}; // namespace AndroidAudio
@@ -0,0 +1,3 @@
#include "AndroidEmulatedController.h"
std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> AndroidEmulatedController::s_emulatedControllers;
@@ -0,0 +1,112 @@
#pragma once
#include "input/InputManager.h"
#include "input/api/Controller.h"
#include "input/emulated/ClassicController.h"
#include "input/emulated/ProController.h"
#include "input/emulated/WiimoteController.h"
class AndroidEmulatedController
{
private:
size_t m_index;
static std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> s_emulatedControllers;
EmulatedControllerPtr m_emulatedController;
AndroidEmulatedController(size_t index) : m_index(index)
{
m_emulatedController = InputManager::instance().get_controller(m_index);
}
public:
static AndroidEmulatedController &getAndroidEmulatedController(size_t index)
{
auto &controller = s_emulatedControllers.at(index);
if (!controller)
controller = std::unique_ptr<AndroidEmulatedController>(new AndroidEmulatedController(index));
return *controller;
}
void setType(EmulatedController::Type type)
{
if (m_emulatedController && m_emulatedController->type() == type)
return;
m_emulatedController = InputManager::instance().set_controller(m_index, type);
InputManager::instance().save(m_index);
}
void setMapping(uint64 mappingId, ControllerPtr controller, uint64 buttonId)
{
if (m_emulatedController && controller)
{
const auto &controllers = m_emulatedController->get_controllers();
auto controllerIt = std::find_if(controllers.begin(), controllers.end(), [&](const ControllerPtr &c)
{ return c->api() == controller->api() && c->uuid() == controller->uuid(); });
if (controllerIt == controllers.end())
m_emulatedController->add_controller(controller);
else
controller = *controllerIt;
m_emulatedController->set_mapping(mappingId, controller, buttonId);
InputManager::instance().save(m_index);
}
}
std::optional<std::string> getMapping(uint64 mapping) const
{
if (!m_emulatedController)
return {};
auto controller = m_emulatedController->get_mapping_controller(mapping);
if (!controller)
return {};
auto mappingName = m_emulatedController->get_mapping_name(mapping);
return fmt::format("{}: {}", controller->display_name(), mappingName);
}
std::map<uint64, std::string> getMappings() const
{
if (!m_emulatedController)
return {};
std::map<uint64, std::string> mappings;
auto type = m_emulatedController->type();
uint64 mapping = 0;
uint64 maxMapping = 0;
if (type == EmulatedController::Type::VPAD)
{
mapping = VPADController::ButtonId::kButtonId_A;
maxMapping = VPADController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Pro)
{
mapping = ProController::ButtonId::kButtonId_A;
maxMapping = ProController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Classic)
{
mapping = ClassicController::ButtonId::kButtonId_A;
maxMapping = ClassicController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Wiimote)
{
mapping = WiimoteController::ButtonId::kButtonId_A;
maxMapping = WiimoteController::ButtonId::kButtonId_Max;
}
for (; mapping < maxMapping; mapping++)
{
auto mappingName = getMapping(mapping);
if (mappingName.has_value())
mappings[mapping] = mappingName.value();
}
return mappings;
}
void setDisabled()
{
InputManager::instance().delete_controller(m_index, true);
m_emulatedController.reset();
}
EmulatedControllerPtr getEmulatedController()
{
return m_emulatedController;
}
void clearMapping(uint64 mapping)
{
if (!m_emulatedController)
return;
m_emulatedController->delete_mapping(mapping);
}
};
@@ -0,0 +1,144 @@
#pragma once
#include "JNIUtils.h"
#include "Common/unix/FilesystemAndroid.h"
class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
{
jmethodID m_openContentUriMid;
jmethodID m_listFilesMid;
jmethodID m_isDirectoryMid;
jmethodID m_isFileMid;
jmethodID m_existsMid;
JNIUtils::Scopedjclass m_fileUtilClass;
std::function<void(JNIEnv *)> m_function = nullptr;
std::mutex m_functionMutex;
std::condition_variable m_functionCV;
std::thread m_thread;
std::mutex m_threadMutex;
std::condition_variable m_threadCV;
std::atomic_bool m_continue = true;
bool callBooleanFunction(const std::filesystem::path &uri, jmethodID methodId)
{
std::lock_guard lock(m_functionMutex);
bool functionFinished = false;
bool result = false;
m_function = [&, this](JNIEnv *env)
{
jstring uriString = env->NewStringUTF(uri.c_str());
result = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
env->DeleteLocalRef(uriString);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]() -> bool
{ return functionFinished; });
}
return result;
}
void runFilesystemCallbacks()
{
JNIUtils::ScopedJNIENV env;
while (m_continue)
{
std::unique_lock threadLock(m_threadMutex);
m_threadCV.wait(threadLock, [&]
{ return m_function || !m_continue; });
if (!m_continue)
return;
m_function(*env);
m_function = nullptr;
m_functionCV.notify_one();
}
}
public:
AndroidFilesystemCallbacks()
{
JNIUtils::ScopedJNIENV env;
m_fileUtilClass = JNIUtils::Scopedjclass("info/cemu/Cemu/FileUtil");
m_openContentUriMid = env->GetStaticMethodID(*m_fileUtilClass, "openContentUri", "(Ljava/lang/String;)I");
m_listFilesMid = env->GetStaticMethodID(*m_fileUtilClass, "listFiles", "(Ljava/lang/String;)[Ljava/lang/String;");
m_isDirectoryMid = env->GetStaticMethodID(*m_fileUtilClass, "isDirectory", "(Ljava/lang/String;)Z");
m_isFileMid = env->GetStaticMethodID(*m_fileUtilClass, "isFile", "(Ljava/lang/String;)Z");
m_existsMid = env->GetStaticMethodID(*m_fileUtilClass, "exists", "(Ljava/lang/String;)Z");
m_thread = std::thread(&AndroidFilesystemCallbacks::runFilesystemCallbacks, this);
}
~AndroidFilesystemCallbacks()
{
m_continue = false;
m_threadCV.notify_one();
m_thread.join();
}
int openContentUri(const std::filesystem::path &uri) override
{
std::lock_guard lock(m_functionMutex);
bool functionFinished = false;
int fd = -1;
m_function = [&, this](JNIEnv *env)
{
jstring uriString = env->NewStringUTF(uri.c_str());
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
env->DeleteLocalRef(uriString);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]()
{ return functionFinished; });
}
return fd;
}
std::vector<std::filesystem::path> listFiles(const std::filesystem::path &uri) override
{
std::lock_guard lock(m_functionMutex);
bool functionFinished = false;
std::vector<std::filesystem::path> paths;
m_function = [&, this](JNIEnv *env)
{
jstring uriString = env->NewStringUTF(uri.c_str());
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallStaticObjectMethod(*m_fileUtilClass, m_listFilesMid, uriString));
env->DeleteLocalRef(uriString);
jsize arrayLength = env->GetArrayLength(pathsObjArray);
paths.reserve(arrayLength);
for (jsize i = 0; i < arrayLength; i++)
{
jstring pathStr = static_cast<jstring>(env->GetObjectArrayElement(pathsObjArray, i));
paths.push_back(JNIUtils::JStringToString(env, pathStr));
env->DeleteLocalRef(pathStr);
}
env->DeleteLocalRef(pathsObjArray);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]()
{ return functionFinished; });
}
return paths;
}
bool isDirectory(const std::filesystem::path &uri) override
{
return callBooleanFunction(uri, m_isDirectoryMid);
}
bool isFile(const std::filesystem::path &uri) override
{
return callBooleanFunction(uri, m_isFileMid);
}
bool exists(const std::filesystem::path &uri) override
{
return callBooleanFunction(uri, m_existsMid);
}
};
@@ -0,0 +1,25 @@
#pragma once
#include "GameIconLoader.h"
#include "JNIUtils.h"
class AndroidGameIconLoadedCallback : public GameIconLoadedCallback
{
jmethodID m_onGameIconLoadedMID;
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
public:
AndroidGameIconLoadedCallback(jmethodID onGameIconLoadedMID,
jobject gameTitleLoadedCallbackObj)
: m_onGameIconLoadedMID(onGameIconLoadedMID),
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
void onIconLoaded(TitleId titleId, int* colors, int width, int height) override
{
JNIUtils::ScopedJNIENV env;
jintArray jIconData = env->NewIntArray(width * height);
env->SetIntArrayRegion(jIconData, 0, width * height, reinterpret_cast<const jint*>(colors));
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, *reinterpret_cast<const jlong*>(&titleId), jIconData, width, height);
env->DeleteLocalRef(jIconData);
}
};
@@ -0,0 +1,23 @@
#pragma once
#include "GameTitleLoader.h"
class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
{
jmethodID m_onGameTitleLoadedMID;
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
public:
AndroidGameTitleLoadedCallback(jmethodID onGameTitleLoadedMID, jobject gameTitleLoadedCallbackObj)
: m_onGameTitleLoadedMID(onGameTitleLoadedMID),
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
void onTitleLoaded(const Game &game) override
{
JNIUtils::ScopedJNIENV env;
jstring name = env->NewStringUTF(game.name.c_str());
jlong titleId = *reinterpret_cast<const jlong *>(&game.titleId);
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, titleId, name);
env->DeleteLocalRef(name);
}
};
+26 -8
View File
@@ -1,16 +1,34 @@
add_library(
CemuAndroid
SHARED
native-lib.cpp
add_library(CemuAndroid SHARED
AndroidAudio.cpp
AndroidAudio.h
AndroidEmulatedController.cpp
AndroidEmulatedController.h
AndroidFilesystemCallbacks.h
AndroidGameIconLoadedCallback.h
AndroidGameTitleLoadedCallback.h
CMakeLists.txt
CafeSystemUtils.cpp
CafeSystemUtils.h
EmulationState.h
GameIconLoader.cpp
GameIconLoader.h
GameTitleLoader.cpp
GameTitleLoader.h
Image.cpp
Image.h
JNIUtils.cpp
JNIUtils.h
Utils.cpp
Utils.h
native-lib.cpp
stb_image.h
)
target_link_libraries(
CemuAndroid
PRIVATE
target_link_libraries(CemuAndroid PRIVATE
-landroid
CemuCommon
CemuAudio
CemuComponents
CemuCafe
CemuBin
CemuAndroidGui
)
@@ -0,0 +1,52 @@
#include "CafeSystemUtils.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/TitleList/TitleList.h"
namespace CafeSystemUtils
{
void startGame(TitleId titleId)
{
TitleInfo launchTitle;
if (!CafeTitleList::GetFirstByTitleId(titleId, launchTitle))
return;
if (launchTitle.IsValid())
{
// the title might not be in the TitleList, so we add it as a temporary entry
CafeTitleList::AddTitleFromPath(launchTitle.GetPath());
// title is valid, launch from TitleId
TitleId baseTitleId;
if (!CafeTitleList::FindBaseTitleId(launchTitle.GetAppTitleId(), baseTitleId))
{
}
CafeSystem::STATUS_CODE statusCode = CafeSystem::PrepareForegroundTitle(baseTitleId);
if (statusCode == CafeSystem::STATUS_CODE::INVALID_RPX)
{
}
else if (statusCode == CafeSystem::STATUS_CODE::UNABLE_TO_MOUNT)
{
}
else if (statusCode != CafeSystem::STATUS_CODE::SUCCESS)
{
}
}
else // if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE )
{
// title is invalid, if its an RPX/ELF we can launch it directly
// otherwise its an error
CafeTitleFileType fileType = DetermineCafeSystemFileType(launchTitle.GetPath());
if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF)
{
CafeSystem::STATUS_CODE statusCode = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(
launchTitle.GetPath());
if (statusCode != CafeSystem::STATUS_CODE::SUCCESS)
{
return;
}
}
}
CafeSystem::LaunchForegroundTitle();
}
}; // namespace CafeSystemUtils
@@ -0,0 +1,8 @@
#pragma once
#include "Cafe/TitleList/TitleId.h"
namespace CafeSystemUtils
{
void startGame(TitleId titleId);
};
@@ -0,0 +1,234 @@
#pragma once
#include <jni.h>
#include "AndroidAudio.h"
#include "AndroidEmulatedController.h"
#include "AndroidFilesystemCallbacks.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "CafeSystemUtils.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "GameIconLoader.h"
#include "GameTitleLoader.h"
#include "Utils.h"
#include "input/ControllerFactory.h"
#include "input/InputManager.h"
#include "input/api/Android/AndroidController.h"
#include "input/api/Android/AndroidControllerProvider.h"
int mainEmulatorHLE();
class EmulationState
{
GameIconLoader m_gameIconLoader;
GameTitleLoader m_gameTitleLoader;
public:
void initializeEmulation()
{
FilesystemAndroid::setFilesystemCallbacks(std::make_shared<AndroidFilesystemCallbacks>());
NetworkConfig::LoadOnce();
mainEmulatorHLE();
InputManager::instance().load();
InitializeGlobalVulkan();
createCemuDirectories();
g_config.Load();
}
void initializeActiveSettings(const fs::path &dataPath, const fs::path &cachePath)
{
ActiveSettings::LoadOnce({}, dataPath, dataPath, cachePath, dataPath);
}
void clearSurface(bool isMainCanvas)
{
VulkanRenderer::GetInstance()->ClearSurface(isMainCanvas);
}
void notifySurfaceChanged(bool isMainCanvas)
{
VulkanRenderer::GetInstance()->NotifySurfaceChanged(isMainCanvas);
}
void setSurface(JNIEnv *env, jobject surface, bool isMainCanvas)
{
auto &windowHandleInfo = isMainCanvas ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
if (windowHandleInfo.surface)
{
ANativeWindow_release(static_cast<ANativeWindow *>(windowHandleInfo.surface));
windowHandleInfo.surface = nullptr;
}
if (surface)
windowHandleInfo.surface = ANativeWindow_fromSurface(env, surface);
if (isMainCanvas)
GuiSystem::getWindowInfo().window_main.surface = windowHandleInfo.surface;
}
void setSurfaceSize(int width, int height, bool isMainCanvas)
{
auto &windowInfo = GuiSystem::getWindowInfo();
if (isMainCanvas)
{
windowInfo.width = windowInfo.phys_width = width;
windowInfo.height = windowInfo.phys_height = height;
}
else
{
windowInfo.pad_width = windowInfo.phys_pad_width = width;
windowInfo.pad_height = windowInfo.phys_pad_height = height;
}
}
void onKeyEvent(const std::string &deviceDescriptor, const std::string &deviceName, int keyCode, bool isPressed)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
androidControllerProvider->on_key_event(deviceDescriptor, deviceName, keyCode, isPressed);
}
void onAxisEvent(const std::string &deviceDescriptor, const std::string &deviceName, int axisCode, float value)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axisCode, value);
}
std::optional<std::string> getEmulatedControllerMapping(size_t index, uint64 mappingId)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getMapping(mappingId);
}
int getVPADControllersCount()
{
int vpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(i).getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() == EmulatedController::Type::VPAD)
++vpadCount;
}
return vpadCount;
}
int getWPADControllersCount()
{
int wpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
i)
.getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() != EmulatedController::Type::VPAD)
++wpadCount;
}
return wpadCount;
}
EmulatedController::Type getEmulatedControllerType(size_t index)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(index).getEmulatedController();
if (emulatedController)
return emulatedController->type();
throw std::runtime_error(fmt::format("can't get type for emulated controller {}", index));
}
void clearEmulatedControllerMapping(size_t index, uint64 mapping)
{
AndroidEmulatedController::getAndroidEmulatedController(index).clearMapping(mapping);
}
void setEmulatedControllerType(size_t index, EmulatedController::Type type)
{
auto &androidEmulatedController = AndroidEmulatedController::getAndroidEmulatedController(index);
if (EmulatedController::Type::VPAD <= type && type < EmulatedController::Type::MAX)
androidEmulatedController.setType(type);
else
androidEmulatedController.setDisabled();
}
void initializeRenderer()
{
g_renderer = std::make_unique<VulkanRenderer>();
}
void initializeRenderSurface(bool isMainCanvas)
{
int width, height;
if (isMainCanvas)
GuiSystem::getWindowPhysSize(width, height);
else
GuiSystem::getPadWindowPhysSize(width, height);
VulkanRenderer::GetInstance()->InitializeSurface({width, height}, isMainCanvas);
}
void setDPI(float dpi)
{
auto &windowInfo = GuiSystem::getWindowInfo();
windowInfo.dpi_scale = windowInfo.pad_dpi_scale = dpi;
}
bool isEmulatedControllerDisabled(size_t index)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getEmulatedController() == nullptr;
}
std::map<uint64, std::string> getEmulatedControllerMappings(size_t index)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getMappings();
}
void setControllerMapping(const std::string &deviceDescriptor, const std::string &deviceName, size_t index, uint64 mappingId, uint64 buttonId)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto controller = ControllerFactory::CreateController(InputAPI::Android, deviceDescriptor, deviceName);
AndroidEmulatedController::getAndroidEmulatedController(index).setMapping(mappingId, controller, buttonId);
}
void initializeAudioDevices()
{
auto &config = g_config.data();
if (!config.tv_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.tv_channels, config.tv_volume, true);
if (!config.pad_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.pad_channels, config.pad_volume, false);
}
void setOnGameTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &onGameTitleLoaded)
{
m_gameTitleLoader.setOnTitleLoaded(onGameTitleLoaded);
}
const Image &getGameIcon(TitleId titleId)
{
return m_gameIconLoader.getGameIcon(titleId);
}
void setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback> &onGameIconLoaded)
{
m_gameIconLoader.setOnIconLoaded(onGameIconLoaded);
}
void addGamePath(const fs::path &gamePath)
{
m_gameTitleLoader.addGamePath(gamePath);
}
void requestGameIcon(TitleId titleId)
{
m_gameIconLoader.requestIcon(titleId);
}
void reloadGameTitles()
{
m_gameTitleLoader.reloadGameTitles();
}
void startGame(TitleId titleId)
{
initializeAudioDevices();
CafeSystemUtils::startGame(titleId);
}
};
@@ -0,0 +1,87 @@
#include "GameIconLoader.h"
#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/TitleList/TitleList.h"
GameIconLoader::GameIconLoader()
{
m_loaderThread = std::thread(&GameIconLoader::loadGameIcons, this);
}
GameIconLoader::~GameIconLoader()
{
m_continueLoading = false;
m_condVar.notify_one();
m_loaderThread.join();
}
const Image &GameIconLoader::getGameIcon(TitleId titleId)
{
return m_iconCache.at(titleId);
}
void GameIconLoader::requestIcon(TitleId titleId)
{
{
std::lock_guard lock(m_threadMutex);
m_iconsToLoad.emplace_front(titleId);
}
m_condVar.notify_one();
}
void GameIconLoader::loadGameIcons()
{
while (m_continueLoading)
{
TitleId titleId = 0;
{
std::unique_lock lock(m_threadMutex);
m_condVar.wait(lock, [this]
{ return !m_iconsToLoad.empty() || !m_continueLoading; });
if (!m_continueLoading)
return;
titleId = m_iconsToLoad.front();
m_iconsToLoad.pop_front();
}
TitleInfo titleInfo;
if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo))
continue;
if (auto iconIt = m_iconCache.find(titleId); iconIt != m_iconCache.end())
{
auto &icon = iconIt->second;
if (m_onIconLoaded)
m_onIconLoaded->onIconLoaded(titleId, icon.intColors(), icon.m_width, icon.m_height);
continue;
}
std::string tempMountPath = TitleInfo::GetUniqueTempMountingPath();
if (!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE))
continue;
auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str());
if (tgaData && tgaData->size() > 16)
{
Image image(tgaData.value());
if (image.isOk())
{
if (m_onIconLoaded)
m_onIconLoaded->onIconLoaded(titleId, image.intColors(), image.m_width, image.m_height);
m_iconCache.emplace(titleId, std::move(image));
}
}
else
{
cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId);
}
titleInfo.Unmount(tempMountPath);
}
}
void GameIconLoader::setOnIconLoaded(const std::shared_ptr<GameIconLoadedCallback> &onIconLoaded)
{
{
std::lock_guard lock(m_threadMutex);
m_onIconLoaded = onIconLoaded;
}
m_condVar.notify_one();
};
@@ -0,0 +1,35 @@
#pragma once
#include "Cafe/TitleList/TitleId.h"
#include "Image.h"
class GameIconLoadedCallback
{
public:
virtual void onIconLoaded(TitleId titleId, int *colors, int width, int height) = 0;
};
class GameIconLoader
{
std::condition_variable m_condVar;
std::atomic_bool m_continueLoading = true;
std::thread m_loaderThread;
std::mutex m_threadMutex;
std::deque<TitleId> m_iconsToLoad;
std::map<TitleId, Image> m_iconCache;
std::shared_ptr<GameIconLoadedCallback> m_onIconLoaded = nullptr;
public:
GameIconLoader();
~GameIconLoader();
const Image &getGameIcon(TitleId titleId);
void setOnIconLoaded(const std::shared_ptr<GameIconLoadedCallback> &onIconLoaded);
void requestIcon(TitleId titleId);
private:
void loadGameIcons();
};
@@ -0,0 +1,140 @@
#include "GameTitleLoader.h"
GameTitleLoader::GameTitleLoader()
{
m_loaderThread = std::thread(&GameTitleLoader::loadGameTitles, this);
}
void GameTitleLoader::queueTitle(TitleId titleId)
{
{
std::lock_guard lock(m_threadMutex);
m_titlesToLoad.emplace_front(titleId);
}
m_condVar.notify_one();
}
void GameTitleLoader::setOnTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &gameTitleLoadedCallback)
{
{
std::lock_guard lock(m_threadMutex);
m_gameTitleLoadedCallback = gameTitleLoadedCallback;
}
m_condVar.notify_one();
}
void GameTitleLoader::reloadGameTitles()
{
if (m_callbackIdTitleList.has_value())
{
CafeTitleList::Refresh();
CafeTitleList::UnregisterCallback(m_callbackIdTitleList.value());
}
m_gameInfos.clear();
registerCallback();
}
GameTitleLoader::~GameTitleLoader()
{
m_continueLoading = false;
m_condVar.notify_one();
m_loaderThread.join();
if (m_callbackIdTitleList.has_value())
CafeTitleList::UnregisterCallback(m_callbackIdTitleList.value());
}
void GameTitleLoader::titleRefresh(TitleId titleId)
{
GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId);
if (!gameInfo.IsValid())
{
return;
}
TitleId baseTitleId = gameInfo.GetBaseTitleId();
bool isNewEntry = false;
if (auto gameInfoIt = m_gameInfos.find(baseTitleId); gameInfoIt == m_gameInfos.end())
{
isNewEntry = true;
m_gameInfos[baseTitleId] = Game();
}
Game &game = m_gameInfos[baseTitleId];
game.titleId = baseTitleId;
game.name = GetNameByTitleId(baseTitleId);
game.version = gameInfo.GetVersion();
game.region = gameInfo.GetRegion();
if (gameInfo.HasAOC())
{
game.dlc = gameInfo.GetAOCVersion();
}
if (isNewEntry)
{
iosu::pdm::GameListStat playTimeStat{};
if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat))
game.secondsPlayed = playTimeStat.numMinutesPlayed * 60;
if (m_gameTitleLoadedCallback)
m_gameTitleLoadedCallback->onTitleLoaded(game);
}
else
{
// TODO: Update
}
}
void GameTitleLoader::loadGameTitles()
{
while (m_continueLoading)
{
TitleId titleId = 0;
{
std::unique_lock lock(m_threadMutex);
m_condVar.wait(lock, [this]
{ return (!m_titlesToLoad.empty()) ||
!m_continueLoading; });
if (!m_continueLoading)
return;
titleId = m_titlesToLoad.front();
m_titlesToLoad.pop_front();
}
titleRefresh(titleId);
}
}
std::string GameTitleLoader::GetNameByTitleId(uint64 titleId)
{
auto it = m_name_cache.find(titleId);
if (it != m_name_cache.end())
return it->second;
TitleInfo titleInfo;
if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo))
return "Unknown title";
std::string name;
if (!GetConfig().GetGameListCustomName(titleId, name))
name = titleInfo.GetTitleName();
m_name_cache.emplace(titleId, name);
return name;
}
void GameTitleLoader::registerCallback()
{
m_callbackIdTitleList = CafeTitleList::RegisterCallback(
[](CafeTitleListCallbackEvent *evt, void *ctx)
{
static_cast<GameTitleLoader *>(ctx)->HandleTitleListCallback(evt);
},
this);
}
void GameTitleLoader::HandleTitleListCallback(CafeTitleListCallbackEvent *evt)
{
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED || evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED)
{
queueTitle(evt->titleInfo->GetAppTitleId());
}
}
void GameTitleLoader::addGamePath(const fs::path &path)
{
CafeTitleList::ClearScanPaths();
CafeTitleList::AddScanPath(path);
CafeTitleList::Refresh();
}
@@ -0,0 +1,58 @@
#pragma once
#include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/TitleList/TitleId.h"
#include "Cafe/TitleList/TitleList.h"
struct Game
{
std::string name;
uint32 secondsPlayed;
uint16 dlc;
uint16 version;
TitleId titleId;
CafeConsoleRegion region;
};
class GameTitleLoadedCallback
{
public:
virtual void onTitleLoaded(const Game &game) = 0;
};
class GameTitleLoader
{
std::mutex m_threadMutex;
std::condition_variable m_condVar;
std::thread m_loaderThread;
std::atomic_bool m_continueLoading = true;
std::deque<TitleId> m_titlesToLoad;
std::optional<uint64> m_callbackIdTitleList;
std::map<TitleId, Game> m_gameInfos;
std::map<TitleId, std::string> m_name_cache;
std::shared_ptr<GameTitleLoadedCallback> m_gameTitleLoadedCallback = nullptr;
public:
GameTitleLoader();
void queueTitle(TitleId titleId);
void setOnTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &gameTitleLoadedCallback);
void reloadGameTitles();
~GameTitleLoader();
void titleRefresh(TitleId titleId);
void addGamePath(const fs::path &path);
private:
void loadGameTitles();
std::string GetNameByTitleId(uint64 titleId);
void registerCallback();
void HandleTitleListCallback(CafeTitleListCallbackEvent *evt);
};
+44
View File
@@ -0,0 +1,44 @@
#include "Image.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_TGA
#include "stb_image.h"
Image::Image(Image &&image)
{
this->m_image = image.m_image;
this->m_width = image.m_width;
this->m_height = image.m_height;
this->m_channels = image.m_channels;
image.m_image = nullptr;
}
Image::Image(const std::vector<uint8> &imageBytes)
{
m_image = stbi_load_from_memory(imageBytes.data(), imageBytes.size(), &m_width, &m_height, &m_channels, STBI_rgb_alpha);
if (m_image)
{
for (int i = 0; i < m_width * m_height * 4; i += 4)
{
uint8 r = m_image[i];
uint8 g = m_image[i + 1];
uint8 b = m_image[i + 2];
uint8 a = m_image[i + 3];
m_image[i] = b;
m_image[i + 1] = g;
m_image[i + 2] = r;
m_image[i + 3] = a;
}
}
}
bool Image::isOk() const { return m_image != nullptr; }
int *Image::intColors() const { return reinterpret_cast<int *>(m_image); }
Image::~Image()
{
if (m_image)
stbi_image_free(m_image);
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
struct Image
{
uint8_t *m_image = nullptr;
int m_width = 0;
int m_height = 0;
int m_channels = 0;
Image(Image &&image);
Image(const std::vector<uint8> &imageBytes);
bool isOk() const;
int *intColors() const;
~Image();
};
+82
View File
@@ -0,0 +1,82 @@
#include "Utils.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "config/ActiveSettings.h"
void createCemuDirectories()
{
std::wstring mlc = ActiveSettings::GetMlcPath().generic_wstring();
// create sys/usr folder in mlc01
try
{
const auto sysFolder = fs::path(mlc).append(L"sys");
fs::create_directories(sysFolder);
const auto usrFolder = fs::path(mlc).append(L"usr");
fs::create_directories(usrFolder);
fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base
fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc
fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update
// Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200},
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a000/user/common/db"));
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a100/user/common/db"));
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a200/user/common/db"));
// lang files
auto langDir = fs::path(mlc).append(L"sys/title/0005001b/1005c000/content");
fs::create_directories(langDir);
auto langFile = fs::path(langDir).append("language.txt");
if (!fs::exists(langFile))
{
std::ofstream file(langFile);
if (file.is_open())
{
const char *langStrings[] = {"ja", "en", "fr", "de", "it", "es", "zh", "ko", "nl", "pt", "ru", "zh"};
for (const char *lang : langStrings)
file << fmt::format(R"("{}",)", lang) << std::endl;
file.flush();
file.close();
}
}
auto countryFile = fs::path(langDir).append("country.txt");
if (!fs::exists(countryFile))
{
std::ofstream file(countryFile);
for (sint32 i = 0; i < 201; i++)
{
const char *countryCode = NCrypto::GetCountryAsString(i);
if (boost::iequals(countryCode, "NN"))
file << "NULL," << std::endl;
else
file << fmt::format(R"("{}",)", countryCode) << std::endl;
}
file.flush();
file.close();
}
}
catch (const std::exception &ex)
{
exit(0);
}
// cemu directories
try
{
const auto controllerProfileFolder = ActiveSettings::GetConfigPath(L"controllerProfiles").generic_wstring();
if (!fs::exists(controllerProfileFolder))
fs::create_directories(controllerProfileFolder);
const auto memorySearcherFolder = ActiveSettings::GetUserDataPath(L"memorySearcher").generic_wstring();
if (!fs::exists(memorySearcherFolder))
fs::create_directories(memorySearcherFolder);
}
catch (const std::exception &ex)
{
exit(0);
}
}

Some files were not shown because too many files have changed in this diff Show More