Add icon & title loader classes and JNI project

This commit is contained in:
SSimco
2023-06-11 07:50:44 +03:00
parent a03ce9a746
commit 12e7f9956c
16 changed files with 8316 additions and 33 deletions
+21 -12
View File
@@ -43,6 +43,9 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_subdirectory(Common)
add_subdirectory(gui)
if(ANDROID)
add_subdirectory(android/app/src/main/cpp)
endif()
add_subdirectory(Cafe)
add_subdirectory(Cemu)
add_subdirectory(config)
@@ -53,21 +56,27 @@ add_subdirectory(imgui)
add_subdirectory(resource)
add_subdirectory(asm)
add_executable(CemuBin
main.cpp
mainLLE.cpp
)
if(ANDROID)
add_library(CemuBin
main.cpp
mainLLE.cpp
)
else()
add_executable(CemuBin
main.cpp
mainLLE.cpp
)
if(WIN32)
target_sources(CemuBin PRIVATE
resource/cemu.rc
)
if(WIN32)
target_sources(CemuBin PRIVATE
resource/cemu.rc
)
endif()
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET CemuBin PROPERTY WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>)
set(OUTPUT_NAME "Cemu_$<LOWER_CASE:$<CONFIG>>")
endif()
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET CemuBin PROPERTY WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>)
set(OUTPUT_NAME "Cemu_$<LOWER_CASE:$<CONFIG>>")
if (MACOS_BUNDLE)
set_property(TARGET CemuBin PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resource/MacOSXBundleInfo.plist.in")
+20 -18
View File
@@ -4,12 +4,12 @@ plugins {
android {
namespace 'info.cemu.Cemu'
compileSdk 33
compileSdk 34
ndkVersion '25.2.9519653'
defaultConfig {
applicationId "info.cemu.Cemu"
minSdk 30
targetSdk 33
targetSdk 34
versionCode 1
versionName "1.0"
ndk {
@@ -17,20 +17,6 @@ android {
abiFilters("x86_64")
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
arguments(
'-DENABLE_VCPKG=ON',
'-DVCPKG_TARGET_ANDROID=ON',
'-DENABLE_SDL=OFF',
'-DENABLE_OPENGL=OFF',
'-DENABLE_WXWIDGETS=OFF',
'-DBUNDLE_SPEEX=ON',
'-DENABLE_DISCORD_RPC=OFF',
'-DENABLE_WAYLAND=OFF'
)
}
}
}
buildTypes {
@@ -45,8 +31,25 @@ android {
}
externalNativeBuild {
cmake {
path file('../../../CMakeLists.txt')
version '3.22.1'
path file('../../../CMakeLists.txt')
}
}
defaultConfig {
externalNativeBuild {
cmake {
arguments(
'-DENABLE_VCPKG=ON',
'-DVCPKG_TARGET_ANDROID=ON',
'-DENABLE_SDL=OFF',
'-DENABLE_WXWIDGETS=OFF',
'-DENABLE_OPENGL=OFF',
'-DBUNDLE_SPEEX=ON',
'-DENABLE_DISCORD_RPC=OFF',
'-DENABLE_WAYLAND=OFF'
)
abiFilters("x86_64")
}
}
}
buildFeatures {
@@ -55,7 +58,6 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
@@ -0,0 +1,12 @@
add_library(
CemuAndroid
SHARED
native-lib.cpp
)
target_link_libraries(
CemuAndroid
PRIVATE
CemuCommon
CemuAndroidGui
)
@@ -0,0 +1,10 @@
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
@@ -9,9 +9,8 @@ import info.cemu.Cemu.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// Used to load the 'Cemu' library on application startup.
static {
System.loadLibrary("Cemu");
System.loadLibrary("CemuAndroid");
}
private ActivityMainBinding binding;
+29
View File
@@ -0,0 +1,29 @@
add_library(CemuAndroidGui
AndroidGuiWrapper.cpp
GameIconLoader.cpp
GameIconLoader.h
GameTitleLoader.cpp
GameTitleLoader.h
OpenGLCanvas.cpp
Image.cpp
)
target_include_directories(CemuAndroidGui PUBLIC "../")
target_link_libraries(CemuAndroidGui PRIVATE
CemuAudio
CemuCafe
CemuCommon
CemuComponents
CemuConfig
CemuInput
CemuResource
CemuUtil
Boost::headers
CURL::libcurl
libzip::zip
OpenSSL::Crypto
pugixml::pugixml
ZArchive::zarchive
)
+70
View File
@@ -0,0 +1,70 @@
#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();
}
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()) {
if (m_onIconLoaded) m_onIconLoaded(titleId, iconIt->second);
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) {
int width, height, channels;
Image image(tgaData.value());
if (image.isOk()) {
if (m_onIconLoaded) m_onIconLoaded(titleId, image);
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::function<void(TitleId, const Image &)> &onIconLoaded) {
{
std::lock_guard lock(m_threadMutex);
m_onIconLoaded = onIconLoaded;
}
m_condVar.notify_one();
};
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Cafe/TitleList/TitleId.h"
#include "Image.h"
class GameIconLoader {
std::atomic_bool m_continueLoading = true;
std::condition_variable m_condVar;
std::thread m_loaderThread;
std::mutex m_threadMutex;
std::deque <TitleId> m_iconsToLoad;
std::map <TitleId, Image> m_iconCache;
std::function<void(TitleId, const Image &)> m_onIconLoaded = nullptr;
void loadGameIcons();
public:
GameIconLoader();
~GameIconLoader();
void setOnIconLoaded(const std::function<void(TitleId, const Image &)> &onIconLoaded);
void requestIcon(TitleId titleId);
};
+114
View File
@@ -0,0 +1,114 @@
#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::function<void(const Game &)> &onTitleLoaded) {
{
std::lock_guard lock(m_threadMutex);
m_onTitleLoaded = onTitleLoaded;
}
m_condVar.notify_one();
}
void GameTitleLoader::reloadGameTitles() {
if (m_callbackIdTitleList.has_value()) {
CafeTitleList::Refresh();
CafeTitleList::UnregisterCallback(m_callbackIdTitleList.value());
}
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 gameIndex = m_gameInfos.find(baseTitleId); gameIndex == m_gameInfos.end()) {
isNewEntry = true;
m_titleIds.push_back(baseTitleId);
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_onTitleLoaded) m_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());
}
}
+48
View File
@@ -0,0 +1,48 @@
#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 GameTitleLoader {
std::atomic_bool m_continueLoading = true;
std::mutex m_threadMutex;
std::condition_variable m_condVar;
std::thread m_loaderThread;
std::deque<TitleId> m_titlesToLoad;
std::optional<uint64> m_callbackIdTitleList;
std::vector<TitleId> m_titleIds;
std::map<TitleId, Game> m_gameInfos;
std::map<TitleId, std::string> m_name_cache;
std::function<void(const Game &)> m_onTitleLoaded = nullptr;
public:
GameTitleLoader();
void queueTitle(TitleId titleId);
void setOnTitleLoaded(const std::function<void(const Game &)> &onTitleLoaded);
void reloadGameTitles();
~GameTitleLoader();
void titleRefresh(TitleId titleId);
private:
void loadGameTitles();
std::string GetNameByTitleId(uint64 titleId);
void registerCallback();
void HandleTitleListCallback(CafeTitleListCallbackEvent *evt);
};
+41
View File
@@ -0,0 +1,41 @@
#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);
}
+18
View File
@@ -0,0 +1,18 @@
#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();
};
+3
View File
@@ -0,0 +1,3 @@
bool GLCanvas_MakeCurrent(bool padView) {}
void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC) {}
bool GLCanvas_HasPadViewOpen() {}
File diff suppressed because it is too large Load Diff
+5
View File
@@ -2,6 +2,11 @@ add_library(CemuGui
guiWrapper.h
)
if(ANDROID)
add_subdirectory(AndroidGui)
target_link_libraries(CemuGui PRIVATE CemuAndroidGui)
endif()
if(ENABLE_WXWIDGETS)
add_subdirectory(wxGui)
target_link_libraries(CemuGui PRIVATE CemuwxGui)
+2 -1
View File
@@ -327,11 +327,12 @@ int main(int argc, char* argv[])
return 0;
}
#elif __ANDROID__
#else
int main(int argc, char *argv[])
{
#if BOOST_OS_LINUX && !__ANDROID__
#if BOOST_OS_LINUX
XInitThreads();
#endif
if (!LaunchSettings::HandleCommandline(argc, argv))