mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Refactor android native code
This commit is contained in:
@@ -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 {};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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
Reference in New Issue
Block a user