Added title manager

This commit is contained in:
SSimco
2025-03-10 17:15:53 +02:00
parent 9db3f87549
commit f73b921349
41 changed files with 2437 additions and 93 deletions
@@ -80,6 +80,10 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext
void gqr_generateScaleCode(VReg& dataReg, bool isLoad, bool scalePS1, const IMLReg& registerGQR);
void prepareJump(IMLSegment* target)
{
}
bool conditional_r_s32([[maybe_unused]] IMLInstruction* imlInstruction)
{
cemu_assert_unimplemented();
@@ -601,6 +605,12 @@ void AArch64GenContext_t::compare_s32(IMLInstruction* imlInstruction)
cset(regR, cond);
}
struct ConditionalJumpInfo
{
WReg regBool;
bool mustBeTrue;
};
void AArch64GenContext_t::cjump(const std::unordered_map<IMLSegment*, Label>& labels, IMLInstruction* imlInstruction, IMLSegment* imlSegment)
{
const Label& label = labels.at(imlSegment->nextSegmentBranchTaken);
+2 -4
View File
@@ -5,13 +5,11 @@
#include "Common/unix/FilesystemAndroid.h"
using fd_streambuf = boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source>;
class ContentUriStream : public fd_streambuf, public std::istream
class ContentUriStream : public boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source>, public std::istream
{
public:
explicit ContentUriStream(const std::filesystem::path& path)
: fd_streambuf(FilesystemAndroid::openContentUri(path), boost::iostreams::close_handle), std::istream(this) {}
: boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source>(FilesystemAndroid::openContentUri(path), boost::iostreams::close_handle), std::istream(this) {}
bool is_open()
{
@@ -43,6 +43,10 @@
android:name=".settings.SettingsActivity"
android:exported="false"
android:parentActivityName=".MainActivity" />
<activity
android:name=".titlemanager.TitleManagerActivity"
android:exported="false"
android:parentActivityName=".MainActivity" />
<activity
android:name=".emulation.EmulationActivity"
android:configChanges="orientation|screenSize"
@@ -16,7 +16,7 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
bool result = false;
std::thread([&, this]() {
JNIUtils::ScopedJNIENV env;
jstring uriString = env->NewStringUTF(uri.c_str());
jstring uriString = JNIUtils::toJString(env, uri);
result = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
env->DeleteLocalRef(uriString);
}).join();
@@ -40,7 +40,7 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
int fd = -1;
std::thread([&, this]() {
JNIUtils::ScopedJNIENV env;
jstring uriString = env->NewStringUTF(uri.c_str());
jstring uriString = JNIUtils::toJString(env, uri);
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
env->DeleteLocalRef(uriString);
}).join();
@@ -52,7 +52,7 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
std::vector<std::filesystem::path> paths;
std::thread([&, this]() {
JNIUtils::ScopedJNIENV env;
jstring uriString = env->NewStringUTF(uri.c_str());
jstring uriString = JNIUtils::toJString(env, uri);
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallStaticObjectMethod(*m_fileUtilClass, m_listFilesMid, uriString));
env->DeleteLocalRef(uriString);
jsize arrayLength = env->GetArrayLength(pathsObjArray);
@@ -60,7 +60,7 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks
for (jsize i = 0; i < arrayLength; i++)
{
jstring pathStr = static_cast<jstring>(env->GetObjectArrayElement(pathsObjArray, i));
paths.push_back(JNIUtils::JStringToString(*env, pathStr));
paths.push_back(JNIUtils::toString(env, pathStr));
env->DeleteLocalRef(pathStr);
}
env->DeleteLocalRef(pathsObjArray);
@@ -20,7 +20,7 @@ class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj)
{
JNIUtils::ScopedJNIENV env;
m_bitmapFormat = JNIUtils::getEnumValue(*env, "android/graphics/Bitmap$Config", "ARGB_8888");
m_bitmapFormat = JNIUtils::getEnumValue(env, "android/graphics/Bitmap$Config", "ARGB_8888");
m_gameConstructorMID = env->GetMethodID(*m_gamejclass, "<init>", "(JLjava/lang/String;Ljava/lang/String;SSISSSIZLandroid/graphics/Bitmap;)V");
m_createBitmapMID = env->GetStaticMethodID(*m_bitmapClass, "createBitmap", "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
}
@@ -28,8 +28,8 @@ class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
void onTitleLoaded(const Game& game, const std::shared_ptr<Image>& icon) override
{
static JNIUtils::ScopedJNIENV env;
jstring name = env->NewStringUTF(game.name.c_str());
jstring path = game.path.has_value() ? env->NewStringUTF(game.path->c_str()) : nullptr;
jstring name = JNIUtils::toJString(env, game.name);
jstring path = game.path.has_value() ? JNIUtils::toJString(env, game.path.value()) : nullptr;
jobject bitmap = nullptr;
sint32 lastPlayedYear = 0, lastPlayedMonth = 0, lastPlayedDay = 0;
if (game.lastPlayed.has_value())
@@ -13,7 +13,7 @@ void AndroidSwkbdCallbacks::showSoftwareKeyboard(const std::string& initialText,
{
std::thread([&, this]() {
JNIUtils::ScopedJNIENV env;
jstring j_initialText = env->NewStringUTF(initialText.c_str());
jstring j_initialText = JNIUtils::toJString(env, initialText);
JNIUtils::ScopedJNIENV()->CallStaticVoidMethod(*m_emulationActivityClass, m_showSoftwareKeyboardMethodID, j_initialText, maxLength);
env->DeleteLocalRef(j_initialText);
}).join();
+3 -1
View File
@@ -2,7 +2,7 @@ add_library(CemuAndroid SHARED
AndroidAudio.cpp
AndroidEmulatedController.cpp
AndroidSwkbdCallbacks.cpp
CMakeLists.txt
CompressTitleCallbacks.cpp
GameTitleLoader.cpp
Image.cpp
JNIUtils.cpp
@@ -15,6 +15,7 @@ add_library(CemuAndroid SHARED
NativeLogging.cpp
NativeSettings.cpp
NativeSwkbd.cpp
WuaConverter.cpp
)
target_link_libraries(CemuAndroid PRIVATE
@@ -24,4 +25,5 @@ target_link_libraries(CemuAndroid PRIVATE
CemuComponents
CemuCafe
CemuBin
ZArchive::zarchive
)
@@ -0,0 +1,20 @@
#include "CompressTitleCallbacks.h"
CompressTitleCallbacks::CompressTitleCallbacks(jobject compressTitleCallbacks)
: m_compressTitleCallbacks{compressTitleCallbacks}
{
JNIUtils::ScopedJNIENV env;
JNIUtils::Scopedjclass compressTitleCallbacksClass("info/cemu/cemu/nativeinterface/NativeGameTitles$TitleCompressCallbacks");
m_onFinishedMID = env->GetMethodID(*compressTitleCallbacksClass, "onFinished", "()V");
m_onErrorMID = env->GetMethodID(*compressTitleCallbacksClass, "onError", "()V");
}
void CompressTitleCallbacks::onFinished()
{
JNIUtils::ScopedJNIENV()->CallVoidMethod(*m_compressTitleCallbacks, m_onFinishedMID);
}
void CompressTitleCallbacks::onError()
{
JNIUtils::ScopedJNIENV()->CallVoidMethod(*m_compressTitleCallbacks, m_onErrorMID);
}
@@ -0,0 +1,17 @@
#pragma once
#include "JNIUtils.h"
class CompressTitleCallbacks
{
jmethodID m_onFinishedMID;
jmethodID m_onErrorMID;
JNIUtils::Scopedjobject m_compressTitleCallbacks;
public:
explicit CompressTitleCallbacks(jobject compressTitleCallbacks);
void onFinished();
void onError();
};
+2 -11
View File
@@ -5,16 +5,6 @@ namespace JNIUtils
JavaVM* g_jvm = nullptr;
};
std::string JNIUtils::JStringToString(JNIEnv* env, jstring jstr)
{
if (jstr == nullptr)
return {};
const char* c_str = env->GetStringUTFChars(jstr, nullptr);
std::string str(c_str);
env->ReleaseStringUTFChars(jstr, c_str);
return str;
}
jobject JNIUtils::createJavaStringArrayList(JNIEnv* env, const std::vector<std::string>& strings)
{
jclass clsArrayList = env->FindClass("java/util/ArrayList");
@@ -60,8 +50,9 @@ jobject JNIUtils::createJavaLongArrayList(JNIEnv* env, const std::vector<uint64_
jclass longClass = env->FindClass("java/lang/Long");
jmethodID valueOf = env->GetStaticMethodID(longClass, "valueOf", "(J)Ljava/lang/Long;");
std::vector<jobject> valuesJava;
valuesJava.reserve(values.size());
for (auto&& value : values)
valuesJava.push_back(env->CallStaticObjectMethod(longClass, valueOf, static_cast<jlong>(value)));
valuesJava.push_back(env->CallStaticObjectMethod(longClass, valueOf, value));
env->DeleteLocalRef(longClass);
return JNIUtils::createArrayList(env, valuesJava);
}
+42 -13
View File
@@ -7,7 +7,20 @@ namespace JNIUtils
{
extern JavaVM* g_jvm;
std::string JStringToString(JNIEnv* env, jstring jstr);
inline std::string toString(JNIEnv* env, jstring jstr)
{
if (jstr == nullptr)
return {};
const char* c_str = env->GetStringUTFChars(jstr, nullptr);
std::string str(c_str);
env->ReleaseStringUTFChars(jstr, c_str);
return str;
}
inline jstring toJString(JNIEnv* env, const std::string& str)
{
return env->NewStringUTF(str.c_str());
}
jobject createJavaStringArrayList(JNIEnv* env, const std::vector<std::string>& stringList);
@@ -21,16 +34,17 @@ namespace JNIUtils
ScopedJNIENV()
{
jint result = g_jvm->GetEnv((void**)&m_env, JNI_VERSION_1_6);
if (result == JNI_EDETACHED)
{
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = nullptr;
args.group = nullptr;
result = g_jvm->AttachCurrentThread(&m_env, &args);
if (result == JNI_OK)
m_threadWasAttached = true;
}
if (result != JNI_EDETACHED)
return;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = nullptr;
args.group = nullptr;
result = g_jvm->AttachCurrentThread(&m_env, &args);
if (result == JNI_OK)
m_threadWasAttached = true;
}
JNIEnv*& operator*()
@@ -43,6 +57,11 @@ namespace JNIUtils
return m_env;
}
operator JNIEnv*() const
{
return m_env;
}
~ScopedJNIENV()
{
if (m_threadWasAttached)
@@ -133,7 +152,7 @@ namespace JNIUtils
explicit Scopedjclass(const std::string& className)
{
ScopedJNIENV scopedEnv;
jobject tempObj = scopedEnv->FindClass(className.c_str());
jclass tempObj = scopedEnv->FindClass(className.c_str());
m_jclass = static_cast<jclass>(scopedEnv->NewGlobalRef(tempObj));
scopedEnv->DeleteLocalRef(tempObj);
}
@@ -146,7 +165,7 @@ namespace JNIUtils
bool isValid() const
{
return m_jclass;
return m_jclass != nullptr;
}
jclass& operator*()
@@ -161,4 +180,14 @@ namespace JNIUtils
Scopedjobject getEnumValue(JNIEnv* env, const std::string& enumClassName, const std::string& enumName);
jobject createArrayList(JNIEnv* env, const std::vector<jobject>& objects);
jobject createJavaLongArrayList(JNIEnv* env, const std::vector<uint64_t>& values);
template<typename... TArgs>
jobject newObject(JNIEnv* env, const std::string& className, const std::string& ctrSig = "()V", TArgs... args)
{
jclass javaClass = env->FindClass(className.c_str());
jmethodID ctrId = env->GetMethodID(javaClass, "<init>", ctrSig.c_str());
jobject obj = env->NewObject(javaClass, ctrId, std::forward<TArgs>(args)...);
env->DeleteLocalRef(javaClass);
return obj;
}
} // namespace JNIUtils
@@ -4,5 +4,11 @@
extern "C" [[maybe_unused]] JNIEXPORT jstring JNICALL
Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_getMLCPath(JNIEnv* env, [[maybe_unused]] jclass clazz)
{
return env->NewStringUTF(ActiveSettings::GetMlcPath().c_str());
return JNIUtils::toJString(env, ActiveSettings::GetMlcPath());
}
extern "C" [[maybe_unused]] JNIEXPORT jstring JNICALL
Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_getUserDataPath(JNIEnv* env, [[maybe_unused]] jclass clazz)
{
return JNIUtils::toJString(env, ActiveSettings::GetUserDataPath());
}
@@ -161,8 +161,8 @@ Java_info_cemu_cemu_nativeinterface_NativeEmulation_setReplaceTVWithPadView([[ma
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeEmulation_initializeActiveSettings(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring data_path, jstring cache_path)
{
std::string dataPath = JNIUtils::JStringToString(env, data_path);
std::string cachePath = JNIUtils::JStringToString(env, cache_path);
std::string dataPath = JNIUtils::toString(env, data_path);
std::string cachePath = JNIUtils::toString(env, cache_path);
std::set<fs::path> failedWriteAccess;
ActiveSettings::SetPaths(false, {}, dataPath, dataPath, cachePath, dataPath, failedWriteAccess);
}
@@ -258,5 +258,5 @@ Java_info_cemu_cemu_nativeinterface_NativeEmulation_startGame([[maybe_unused]] J
{
GuiSystem::getWindowInfo().set_keystates_up();
NativeEmulation::initializeAudioDevices();
return NativeEmulation::startGame(JNIUtils::JStringToString(env, launchPath));
return NativeEmulation::startGame(JNIUtils::toString(env, launchPath));
}
+228 -11
View File
@@ -1,8 +1,10 @@
#include <Cafe/TitleList/SaveList.h>
#include "JNIUtils.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "GameTitleLoader.h"
#include "AndroidGameTitleLoadedCallback.h"
#include "Cafe/TitleList/SaveList.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "JNIUtils.h"
#include "GameTitleLoader.h"
#include "WuaConverter.h"
#include "CompressTitleCallbacks.h"
namespace NativeGameTitles
{
@@ -35,6 +37,8 @@ namespace NativeGameTitles
s_currentGameProfile.Reset();
s_currentGameProfile.Load(titleId);
}
std::unique_ptr<WuaConverter> s_wuaConverter;
} // namespace NativeGameTitles
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
@@ -169,8 +173,8 @@ class SaveListCallback
name.replace(nl, 1, " - ");
JNIUtils::ScopedJNIENV env;
jstring nameJava = env->NewStringUTF(name.c_str());
jobject pathJava = env->NewStringUTF(saveInfo.GetPath().c_str());
jstring nameJava = JNIUtils::toJString(env, name);
jstring pathJava = JNIUtils::toJString(env, saveInfo.GetPath());
jobject saveData = env->NewObject(
*m_saveDataClass,
m_saveDataConstructorMID,
@@ -224,16 +228,15 @@ class TitleListCallbacks
if (titleInfo.IsSystemDataTitle())
return; // don't show system data titles for now
JNIUtils::ScopedJNIENV env;
ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo();
std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue());
const auto nl = name.find(L'\n');
if (nl != std::string::npos)
name.replace(nl, 1, " - ");
jobject nameJava = env->NewStringUTF(name.c_str());
jobject pathJava = env->NewStringUTF(titleInfo.GetPath().c_str());
JNIUtils::ScopedJNIENV env;
jobject nameJava = JNIUtils::toJString(env, name);
jobject pathJava = JNIUtils::toJString(env, titleInfo.GetPath());
jobject titleData = env->NewObject(
*m_titleDataClass,
m_titleDataConstructorMID,
@@ -324,4 +327,218 @@ Java_info_cemu_cemu_nativeinterface_NativeGameTitles_setSaveListCallback([[maybe
s_saveListCallback = nullptr;
else
s_saveListCallback = std::make_unique<SaveListCallback>(save_list_callback);
}
}
extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_checkIfTitleExists(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring meta_path)
{
TitleInfo titleInfo(fs::path(JNIUtils::toString(env, meta_path)));
if (!titleInfo.IsValid())
return nullptr;
fs::path target_location = ActiveSettings::GetMlcPath(titleInfo.GetInstallPath());
auto createTitleExistsStatus = [&](jobject existsError = nullptr) {
if (existsError == nullptr)
existsError = JNIUtils::newObject(env, "info/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsError$None");
return JNIUtils::newObject(
env,
"info/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsStatus",
"(Linfo/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsError;Ljava/lang/String;)V",
existsError,
JNIUtils::toJString(env, target_location));
};
std::error_code ec;
if (!fs::exists(target_location, ec))
{
return createTitleExistsStatus();
}
try
{
const TitleInfo tmp(target_location);
if (!tmp.IsValid())
{
// does not exist / is not valid. We allow to overwrite it
return createTitleExistsStatus();
}
TitleIdParser tip(titleInfo.GetAppTitleId());
TitleIdParser tipOther(tmp.GetAppTitleId());
jint oldType = static_cast<jint>(tip.GetType());
jint toInstallType = static_cast<jint>(tipOther.GetType());
if (oldType != toInstallType)
{
jobject err = JNIUtils::newObject(
env,
"info/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsError$DifferentType",
"(II)V",
oldType,
toInstallType);
return createTitleExistsStatus(err);
}
else if (tmp.GetAppTitleVersion() == titleInfo.GetAppTitleVersion())
{
jobject err = JNIUtils::newObject(env, "info/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsError$SameVersion");
return createTitleExistsStatus(err);
}
else if (tmp.GetAppTitleVersion() > titleInfo.GetAppTitleVersion())
{
jobject err = JNIUtils::newObject(env, "info/cemu/cemu/nativeinterface/NativeGameTitles$TitleExistsError$NewVersion");
return createTitleExistsStatus(err);
}
} catch (const std::exception& ex)
{
cemuLog_log(LogType::Force, "exist-error: {} at {}", ex.what(), _pathToUtf8(target_location));
}
return createTitleExistsStatus();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_addTitleFromPath(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring path)
{
CafeTitleList::AddTitleFromPath(fs::path(JNIUtils::toString(env, path)));
}
struct Title
{
uint64 uid;
uint16 version;
};
extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_queueTitleToCompress(JNIEnv* env, [[maybe_unused]] jclass clazz, jlong titleId, jlong selectedUID, jobject titlesCallback)
{
jclass titlesCallbackClass = env->GetObjectClass(titlesCallback);
jmethodID getTitlesMID = env->GetMethodID(titlesCallbackClass, "getTitlesByTitleId", "(J)[Linfo/cemu/cemu/nativeinterface/NativeGameTitles$TitleIdToTitlesCallback$Title;");
jclass titleClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeGameTitles$TitleIdToTitlesCallback$Title");
jfieldID versionFieldId = env->GetFieldID(titleClass, "version", "S");
jfieldID titleUIDFieldId = env->GetFieldID(titleClass, "titleUID", "J");
auto getTitlePrintPath = [&](const TitleInfo& titleInfo) -> jstring {
if (!titleInfo.IsValid())
return nullptr;
return JNIUtils::toJString(env, titleInfo.GetPrintPath());
};
auto getTitlesByTitleId = [&](uint64 titleId) -> std::vector<Title> {
auto titlesJava = static_cast<jobjectArray>(env->CallObjectMethod(titlesCallback, getTitlesMID, titleId));
jsize arraySize = env->GetArrayLength(titlesJava);
std::vector<Title> titles;
titles.reserve(arraySize);
for (jsize i = 0; i < arraySize; i++)
{
jobject title = env->GetObjectArrayElement(titlesJava, i);
uint64 uid = env->GetLongField(title, titleUIDFieldId);
uint16 version = env->GetShortField(title, versionFieldId);
titles.push_back(Title{.uid = uid, .version = version});
}
return titles;
};
TitleInfo titleInfo_base;
TitleInfo titleInfo_update;
TitleInfo titleInfo_aoc;
titleId = TitleIdParser::MakeBaseTitleId(titleId); // if the titleId of a separate update is selected, this converts it back to the base titleId
TitleIdParser titleIdParser(titleId);
bool hasBaseTitleId = titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::AOC;
bool hasUpdateTitleId = titleIdParser.CanHaveSeparateUpdateTitleId();
TitleId updateTitleId = hasUpdateTitleId ? titleIdParser.GetSeparateUpdateTitleId() : 0;
// todo - AOC titleIds might differ from the base/update game in other bits than the type. We have to use the meta data from the base/update to match aoc to the base title id
// for now we just assume they match
TitleId aocTitleId;
if (hasBaseTitleId)
aocTitleId = (titleId & (uint64)~0xFF00000000) | (uint64)0xC00000000;
else
aocTitleId = titleId;
// find base and update
if (hasBaseTitleId)
{
for (const auto& title : getTitlesByTitleId(titleId))
{
if (!titleInfo_base.IsValid())
{
titleInfo_base = CafeTitleList::GetTitleInfoByUID(title.uid);
if (title.uid == selectedUID)
break; // prefer the users selection
}
}
}
if (hasUpdateTitleId)
{
for (const auto& title : getTitlesByTitleId(updateTitleId))
{
if (!titleInfo_update.IsValid())
{
titleInfo_update = CafeTitleList::GetTitleInfoByUID(title.uid);
if (title.uid == selectedUID)
break;
}
else
{
// if multiple updates are present use the newest one
if (titleInfo_update.GetAppTitleVersion() < title.version)
titleInfo_update = CafeTitleList::GetTitleInfoByUID(title.uid);
if (title.uid == selectedUID)
break;
}
}
}
// find AOC
for (const auto& title : getTitlesByTitleId(aocTitleId))
{
titleInfo_aoc = CafeTitleList::GetTitleInfoByUID(title.uid);
if (title.uid == selectedUID)
break;
}
NativeGameTitles::s_wuaConverter = std::make_unique<WuaConverter>(titleInfo_base, titleInfo_update, titleInfo_aoc);
jobject compressTitleInfo = JNIUtils::newObject(
env,
"info/cemu/cemu/nativeinterface/NativeGameTitles$CompressTitleInfo",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
getTitlePrintPath(titleInfo_base),
getTitlePrintPath(titleInfo_update),
getTitlePrintPath(titleInfo_aoc));
return compressTitleInfo;
}
extern "C" [[maybe_unused]] JNIEXPORT jstring JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_getCompressedFileNameForQueuedTitle(JNIEnv* env, [[maybe_unused]] jclass clazz)
{
if (NativeGameTitles::s_wuaConverter == nullptr)
return nullptr;
return JNIUtils::toJString(env, NativeGameTitles::s_wuaConverter->getCompressedFileName());
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_compressQueuedTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint fd, jobject compressTitleCallbacks)
{
if (NativeGameTitles::s_wuaConverter == nullptr)
return;
NativeGameTitles::s_wuaConverter->startConversion(fd, std::make_unique<CompressTitleCallbacks>(compressTitleCallbacks));
}
extern "C" [[maybe_unused]] JNIEXPORT jlong JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_getCurrentProgressForCompression([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
{
if (NativeGameTitles::s_wuaConverter == nullptr)
return 0L;
return NativeGameTitles::s_wuaConverter->getTransferredInputBytes();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGameTitles_cancelTitleCompression([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
{
if (NativeGameTitles::s_wuaConverter == nullptr)
return;
NativeGameTitles::s_wuaConverter.reset();
}
@@ -59,7 +59,7 @@ namespace NativeGraphicPacks
continue;
}
jstring categoryJStr = category.empty() ? nullptr : env->NewStringUTF(category.c_str());
jstring categoryJStr = category.empty() ? nullptr : JNIUtils::toJString(env, category);
std::vector<std::string> presetSelections;
std::optional<std::string> activePreset;
for (auto& pentry : entry)
@@ -74,10 +74,10 @@ namespace NativeGraphicPacks
}
jstring activePresetJstr = nullptr;
if (activePreset)
activePresetJstr = env->NewStringUTF(activePreset->c_str());
if (activePreset.has_value())
activePresetJstr = JNIUtils::toJString(env, activePreset.value());
else if (!presetSelections.empty())
activePresetJstr = env->NewStringUTF(presetSelections.front().c_str());
activePresetJstr = JNIUtils::toJString(env, presetSelections.front());
auto presetJObject = env->NewObject(graphicPackPresetClass, graphicPackPresetCtorId, id, categoryJStr, JNIUtils::createJavaStringArrayList(env, presetSelections), activePresetJstr);
presetsJobjects.push_back(presetJObject);
}
@@ -103,9 +103,10 @@ Java_info_cemu_cemu_nativeinterface_NativeGraphicPacks_getGraphicPackBasicInfos(
auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "<init>", "(JLjava/lang/String;ZLjava/util/ArrayList;)V");
std::vector<jobject> graphicPackInfoJObjects;
graphicPackInfoJObjects.reserve(NativeGraphicPacks::s_graphicPacks.size());
for (auto&& graphicPack : NativeGraphicPacks::s_graphicPacks)
{
jstring virtualPath = env->NewStringUTF(graphicPack.second->GetVirtualPath().c_str());
jstring virtualPath = JNIUtils::toJString(env, graphicPack.second->GetVirtualPath());
jlong id = graphicPack.first;
jobject titleIds = JNIUtils::createJavaLongArrayList(env, graphicPack.second->GetTitleIds());
jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath, graphicPack.second->IsEnabled(), titleIds);
@@ -121,8 +122,8 @@ Java_info_cemu_cemu_nativeinterface_NativeGraphicPacks_getGraphicPack(JNIEnv* en
auto graphicPackCtorId = env->GetMethodID(graphicPackClass, "<init>", "(JZLjava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;)V");
auto graphicPack = NativeGraphicPacks::s_graphicPacks.at(id);
jstring graphicPackName = env->NewStringUTF(graphicPack->GetName().c_str());
jstring graphicPackDescription = env->NewStringUTF(graphicPack->GetDescription().c_str());
jstring graphicPackName = JNIUtils::toJString(env, graphicPack->GetName());
jstring graphicPackDescription = JNIUtils::toJString(env, graphicPack->GetDescription());
return env->NewObject(
graphicPackClass,
graphicPackCtorId,
@@ -144,9 +145,9 @@ Java_info_cemu_cemu_nativeinterface_NativeGraphicPacks_setGraphicPackActive([[ma
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeGraphicPacks_setGraphicPackActivePreset([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong id, jstring category, jstring preset)
{
std::string presetCategory = category == nullptr ? "" : JNIUtils::JStringToString(env, category);
std::string presetCategory = category == nullptr ? "" : JNIUtils::toString(env, category);
auto graphicPack = NativeGraphicPacks::s_graphicPacks.at(id);
graphicPack->SetActivePreset(presetCategory, JNIUtils::JStringToString(env, preset));
graphicPack->SetActivePreset(presetCategory, JNIUtils::toString(env, preset));
NativeGraphicPacks::saveGraphicPackStateToConfig(graphicPack);
}
+8 -8
View File
@@ -24,8 +24,8 @@ namespace NativeInput
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeInput_onNativeKey(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint key, jboolean is_pressed)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
auto deviceDescriptor = JNIUtils::toString(env, device_descriptor);
auto deviceName = JNIUtils::toString(env, device_name);
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider*>(apiProvider.get());
androidControllerProvider->on_key_event(deviceDescriptor, deviceName, key, is_pressed);
@@ -34,8 +34,8 @@ Java_info_cemu_cemu_nativeinterface_NativeInput_onNativeKey(JNIEnv* env, [[maybe
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeInput_onNativeAxis(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint axis, jfloat value)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
auto deviceDescriptor = JNIUtils::toString(env, device_descriptor);
auto deviceName = JNIUtils::toString(env, device_name);
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider*>(apiProvider.get());
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axis, value);
@@ -118,8 +118,8 @@ Java_info_cemu_cemu_nativeinterface_NativeInput_isControllerDisabled([[maybe_unu
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeInput_setControllerMapping(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint index, jint mapping_id, jint button_id)
{
auto deviceName = JNIUtils::JStringToString(env, device_name);
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::toString(env, device_name);
auto deviceDescriptor = JNIUtils::toString(env, device_descriptor);
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto controller = ControllerFactory::CreateController(InputAPI::Android, deviceDescriptor, deviceName);
AndroidEmulatedController::getAndroidEmulatedController(index).setMapping(mapping_id, controller, button_id);
@@ -129,7 +129,7 @@ extern "C" [[maybe_unused]] JNIEXPORT jstring JNICALL
Java_info_cemu_cemu_nativeinterface_NativeInput_getControllerMapping(JNIEnv* env, [[maybe_unused]] jclass clazz, jint index, jint mapping_id)
{
auto mapping = AndroidEmulatedController::getAndroidEmulatedController(index).getMapping(mapping_id);
return env->NewStringUTF(mapping.value_or("").c_str());
return JNIUtils::toJString(env, mapping.value_or(""));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
@@ -151,7 +151,7 @@ Java_info_cemu_cemu_nativeinterface_NativeInput_getControllerMappings(JNIEnv* en
for (const auto& pair : mappings)
{
jint key = pair.first;
jstring buttonName = env->NewStringUTF(pair.second.c_str());
jstring buttonName = JNIUtils::toJString(env, pair.second);
jobject mappingId = env->NewObject(integerClass, integerConstructor, key);
env->CallObjectMethod(hashMapObj, hashMapPut, mappingId, buttonName);
}
@@ -4,7 +4,7 @@
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeLogging_log(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring message)
{
cemuLog_log(LogType::Force, JNIUtils::JStringToString(env, message));
cemuLog_log(LogType::Force, JNIUtils::toString(env, message));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
@@ -13,5 +13,5 @@ Java_info_cemu_cemu_nativeinterface_NativeLogging_crashLog(JNIEnv* env, [[maybe_
if (!CrashLog_Create())
return; // give up if crashlog was already created
CrashLog_WriteLine("Unhandled exception from java code");
CrashLog_WriteLine(JNIUtils::JStringToString(env, stacktrace));
CrashLog_WriteLine(JNIUtils::toString(env, stacktrace));
}
@@ -188,7 +188,7 @@ extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeSettings_addGamesPath(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring uri)
{
auto& gamePaths = g_config.data().game_paths;
auto gamePath = JNIUtils::JStringToString(env, uri);
auto gamePath = JNIUtils::toString(env, uri);
if (std::any_of(gamePaths.begin(), gamePaths.end(), [&](auto path) { return path == gamePath; }))
return;
gamePaths.push_back(gamePath);
@@ -198,7 +198,7 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_addGamesPath(JNIEnv* env, [[m
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeSettings_removeGamesPath(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring uri)
{
auto gamePath = JNIUtils::JStringToString(env, uri);
auto gamePath = JNIUtils::toString(env, uri);
auto& gamePaths = g_config.data().game_paths;
std::erase_if(gamePaths, [&](auto path) { return path == gamePath; });
g_config.Save();
+2 -2
View File
@@ -34,13 +34,13 @@ Java_info_cemu_cemu_nativeinterface_NativeSwkbd_initializeSwkbd([[maybe_unused]]
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeSwkbd_setCurrentInputText([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring initial_text)
{
NativeSwkbd::s_currentInputText = JNIUtils::JStringToString(env, initial_text);
NativeSwkbd::s_currentInputText = JNIUtils::toString(env, initial_text);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_cemu_nativeinterface_NativeSwkbd_onTextChanged([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_text)
{
std::string text = JNIUtils::JStringToString(env, j_text);
std::string text = JNIUtils::toString(env, j_text);
auto stringDiff = NativeSwkbd::getStringDiffs(text, NativeSwkbd::s_currentInputText);
for (size_t i = 0; i < stringDiff.numberOfCharacterToDelete; i++)
swkbd::keyInput(swkbd::BACKSPACE_KEYCODE);
@@ -0,0 +1,216 @@
#include "WuaConverter.h"
WuaConverter::WuaConverter(const TitleInfo& titleInfo_base, const TitleInfo& titleInfo_update, const TitleInfo& titleInfo_aoc)
: m_titleInfo_base{titleInfo_base},
m_titleInfo_update{titleInfo_update},
m_titleInfo_aoc{titleInfo_aoc}
{
}
WuaConverter::~WuaConverter()
{
m_writerContext.cancelled = true;
if (m_workerThread.joinable())
m_workerThread.join();
}
void WuaConverter::startConversion(int fd, std::unique_ptr<CompressTitleCallbacks>&& callbacks)
{
m_workerThread = std::thread([callbacks(std::move(callbacks)), fd, this]() {
if (fd == -1)
{
callbacks->onError();
return;
}
struct ScopedFd
{
int fd;
~ScopedFd()
{
close(fd);
}
} scopedFd{.fd = fd};
if (m_started)
return;
m_started = true;
std::vector<TitleInfo*> titlesToConvert;
if (m_titleInfo_base.IsValid())
titlesToConvert.emplace_back(&m_titleInfo_base);
if (m_titleInfo_update.IsValid())
titlesToConvert.emplace_back(&m_titleInfo_update);
if (m_titleInfo_aoc.IsValid())
titlesToConvert.emplace_back(&m_titleInfo_aoc);
if (titlesToConvert.empty())
{
callbacks->onError();
return;
}
// mount and store
m_writerContext.isValid = true;
m_writerContext.fd = fd;
m_writerContext.zaWriter = std::make_unique<ZArchiveWriter>(&ZArchiveWriterContext::NewOutputFile, &ZArchiveWriterContext::WriteOutputData, &m_writerContext);
if (!m_writerContext.isValid)
{
callbacks->onError();
return;
}
bool result = m_writerContext.AddTitles(titlesToConvert.data(), titlesToConvert.size());
if (m_writerContext.cancelled)
return;
if (!result)
{
callbacks->onError();
return;
}
m_writerContext.zaWriter->Finalize();
// verify the created WUA file
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> stream(fd, boost::iostreams::never_close_handle);
ZArchiveReader* zreader = ZArchiveReader::OpenFromStream(std::make_unique<std::istream>(&stream));
if (!zreader)
{
callbacks->onError();
return;
}
// todo - do a quick verification here
delete zreader;
CafeTitleList::Refresh();
callbacks->onFinished();
});
}
uint64 WuaConverter::getTransferredInputBytes() const
{
return m_writerContext.transferredInputBytes.load(std::memory_order_relaxed);
}
std::string WuaConverter::getCompressedFileName()
{
CafeConsoleLanguage languageId = CafeConsoleLanguage::EN; // todo - use user's locale
std::string shortName;
if (m_titleInfo_base.IsValid())
shortName = m_titleInfo_base.GetMetaInfo()->GetShortName(languageId);
else if (m_titleInfo_update.IsValid())
shortName = m_titleInfo_update.GetMetaInfo()->GetShortName(languageId);
else if (m_titleInfo_aoc.IsValid())
shortName = m_titleInfo_aoc.GetMetaInfo()->GetShortName(languageId);
if (!shortName.empty())
{
boost::replace_all(shortName, ":", "");
}
// get the short name, which we will use as a suggested default file name
std::string defaultFileName = std::move(shortName);
boost::replace_all(defaultFileName, "/", "");
boost::replace_all(defaultFileName, "\\", "");
CafeConsoleRegion region = CafeConsoleRegion::Auto;
if (m_titleInfo_base.IsValid() && m_titleInfo_base.HasValidXmlInfo())
region = m_titleInfo_base.GetMetaInfo()->GetRegion();
else if (m_titleInfo_update.IsValid() && m_titleInfo_update.HasValidXmlInfo())
region = m_titleInfo_update.GetMetaInfo()->GetRegion();
if (region == CafeConsoleRegion::JPN)
defaultFileName.append(" (JP)");
else if (region == CafeConsoleRegion::EUR)
defaultFileName.append(" (EU)");
else if (region == CafeConsoleRegion::USA)
defaultFileName.append(" (US)");
if (m_titleInfo_update.IsValid())
{
defaultFileName.append(fmt::format(" (v{})", m_titleInfo_update.GetAppTitleVersion()));
}
defaultFileName.append(".wua");
return defaultFileName;
}
bool WuaConverter::ZArchiveWriterContext::RecursivelyAddFiles(std::string archivePath, std::string fscPath)
{
sint32 fscStatus;
std::unique_ptr<FSCVirtualFile> vfDir(fsc_openDirIterator(fscPath.c_str(), &fscStatus));
if (!vfDir)
return false;
if (cancelled)
return false;
zaWriter->MakeDir(archivePath.c_str(), false);
FSCDirEntry dirEntry;
while (fsc_nextDir(vfDir.get(), &dirEntry))
{
if (dirEntry.isFile)
{
zaWriter->StartNewFile((archivePath + dirEntry.path).c_str());
std::unique_ptr<FSCVirtualFile> vFile(fsc_open((fscPath + dirEntry.path).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus));
if (!vFile)
return false;
transferBuffer.resize(32 * 1024); // 32KB
uint32 readBytes;
while (true)
{
readBytes = vFile->fscReadData(transferBuffer.data(), transferBuffer.size());
if (readBytes == 0)
break;
zaWriter->AppendData(transferBuffer.data(), readBytes);
if (cancelled)
return false;
transferredInputBytes += readBytes;
}
}
else if (dirEntry.isDirectory)
{
if (!RecursivelyAddFiles(fmt::format("{}{}/", archivePath, dirEntry.path), fmt::format("{}{}/", fscPath, dirEntry.path)))
return false;
}
else
{
cemu_assert_unimplemented();
}
}
return true;
}
void WuaConverter::ZArchiveWriterContext::NewOutputFile(const int32_t partIndex, void* _ctx)
{
auto ctx = (ZArchiveWriterContext*)_ctx;
ctx->sink = std::make_unique<boost::iostreams::file_descriptor_sink>(ctx->fd, boost::iostreams::never_close_handle);
ctx->isValid = ctx->sink->is_open();
}
void WuaConverter::ZArchiveWriterContext::WriteOutputData(const void* data, size_t length, void* _ctx)
{
auto* ctx = (ZArchiveWriterContext*)_ctx;
if (ctx->isValid)
ctx->sink->write(reinterpret_cast<const char*>(data), length);
}
bool WuaConverter::ZArchiveWriterContext::StoreTitle(TitleInfo* titleInfo)
{
std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath();
titleInfo->Mount(temporaryMountPath, "", FSC_PRIORITY_BASE);
bool r = RecursivelyAddFiles(fmt::format("{:016x}_v{}/", titleInfo->GetAppTitleId(), titleInfo->GetAppTitleVersion()), temporaryMountPath);
titleInfo->Unmount(temporaryMountPath);
return r;
}
bool WuaConverter::ZArchiveWriterContext::AddTitles(TitleInfo** titles, size_t count)
{
// store files
for (size_t i = 0; i < count; i++)
{
if (!StoreTitle(titles[i]))
return false;
}
return true;
}

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