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