// SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #include "jvm.h" namespace skyline { std::string JniString::GetJString(JNIEnv *env, jstring jString) { auto utf{env->GetStringUTFChars(jString, nullptr)}; std::string string{utf}; env->ReleaseStringUTFChars(jString, utf); return string; } /* * @brief A thread-local wrapper over JNIEnv and JavaVM which automatically handles attaching and detaching threads */ struct JniEnvironment { JNIEnv *env{}; static inline JavaVM *vm{}; bool attached{}; void Initialize(JNIEnv *environment) { env = environment; if (env->GetJavaVM(&vm) < 0) throw exception("Cannot get JavaVM from environment"); attached = true; } JniEnvironment() { if (vm && !attached) { vm->AttachCurrentThread(&env, nullptr); attached = true; } } ~JniEnvironment() { if (vm && attached) vm->DetachCurrentThread(); } operator JNIEnv *() { if (!attached) throw exception("Not attached"); return env; } JNIEnv *operator->() { if (!attached) throw exception("Not attached"); return env; } }; thread_local inline JniEnvironment env; JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance{environ->NewGlobalRef(instance)}, instanceClass{reinterpret_cast(environ->NewGlobalRef(environ->GetObjectClass(instance)))}, initializeControllersId{environ->GetMethodID(instanceClass, "initializeControllers", "()V")}, vibrateDeviceId{environ->GetMethodID(instanceClass, "vibrateDevice", "(I[J[I)V")}, clearVibrationDeviceId{environ->GetMethodID(instanceClass, "clearVibrationDevice", "(I)V")}, showKeyboardId{environ->GetMethodID(instanceClass, "showKeyboard", "(Ljava/nio/ByteBuffer;Ljava/lang/String;)Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;")}, waitForSubmitOrCancelId{environ->GetMethodID(instanceClass, "waitForSubmitOrCancel", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)[Ljava/lang/Object;")}, closeKeyboardId{environ->GetMethodID(instanceClass, "closeKeyboard", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)V")}, showValidationResultId{environ->GetMethodID(instanceClass, "showValidationResult", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;ILjava/lang/String;)I")}, getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")}, reportCrashId{environ->GetMethodID(instanceClass, "reportCrash", "()V")}, showPipelineLoadingScreenId{environ->GetMethodID(instanceClass, "showPipelineLoadingScreen", "(I)V")}, updatePipelineLoadingProgressId{environ->GetMethodID(instanceClass, "updatePipelineLoadingProgress", "(I)V")}, hidePipelineLoadingScreenId{environ->GetMethodID(instanceClass, "hidePipelineLoadingScreen", "()V")}, getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")}, getDhcpInfoId{environ->GetMethodID(instanceClass, "getDhcpInfo", "()Landroid/net/DhcpInfo;")} { env.Initialize(environ); } JvmManager::~JvmManager() { env->DeleteGlobalRef(instanceClass); env->DeleteGlobalRef(instance); } JNIEnv *JvmManager::GetEnv() { return env; } jobject JvmManager::GetField(const char *key, const char *signature) { return env->GetObjectField(instance, env->GetFieldID(instanceClass, key, signature)); } bool JvmManager::CheckNull(const char *key, const char *signature) { return env->IsSameObject(env->GetObjectField(instance, env->GetFieldID(instanceClass, key, signature)), nullptr); } bool JvmManager::CheckNull(jobject &object) { return env->IsSameObject(object, nullptr); } void JvmManager::InitializeControllers() { env->CallVoidMethod(instance, initializeControllersId); } void JvmManager::VibrateDevice(jint index, const span &timings, const span &litudes) { auto jTimings{env->NewLongArray(static_cast(timings.size()))}; env->SetLongArrayRegion(jTimings, 0, static_cast(timings.size()), timings.data()); auto jAmplitudes{env->NewIntArray(static_cast(amplitudes.size()))}; env->SetIntArrayRegion(jAmplitudes, 0, static_cast(amplitudes.size()), amplitudes.data()); env->CallVoidMethod(instance, vibrateDeviceId, index, jTimings, jAmplitudes); env->DeleteLocalRef(jTimings); env->DeleteLocalRef(jAmplitudes); } void JvmManager::ClearVibrationDevice(jint index) { env->CallVoidMethod(instance, clearVibrationDeviceId, index); } jobject JvmManager::ShowKeyboard(KeyboardConfig &config, std::u16string initialText) { auto buffer{env->NewDirectByteBuffer(&config, sizeof(KeyboardConfig))}; auto str{env->NewString(reinterpret_cast(initialText.data()), static_cast(initialText.length()))}; jobject localKeyboardDialog{env->CallObjectMethod(instance, showKeyboardId, buffer, str)}; env->DeleteLocalRef(buffer); env->DeleteLocalRef(str); auto keyboardDialog{env->NewGlobalRef(localKeyboardDialog)}; env->DeleteLocalRef(localKeyboardDialog); return keyboardDialog; } std::pair JvmManager::WaitForSubmitOrCancel(jobject keyboardDialog) { auto returnArray{reinterpret_cast(env->CallObjectMethod(instance, waitForSubmitOrCancelId, keyboardDialog))}; auto buttonInteger{env->GetObjectArrayElement(returnArray, 0)}; auto inputJString{reinterpret_cast(env->GetObjectArrayElement(returnArray, 1))}; auto stringChars{env->GetStringChars(inputJString, nullptr)}; std::u16string input{stringChars, stringChars + env->GetStringLength(inputJString)}; env->ReleaseStringChars(inputJString, stringChars); return {static_cast(env->CallIntMethod(buttonInteger, getIntegerValueId)), input}; } DhcpInfo JvmManager::GetDhcpInfo() { jobject dhcpInfo{env->CallObjectMethod(instance, getDhcpInfoId)}; jclass dhcpInfoClass{env->GetObjectClass(dhcpInfo)}; jfieldID ipAddressFieldId{env->GetFieldID(dhcpInfoClass, "ipAddress", "I")}; jfieldID subnetFieldId{env->GetFieldID(dhcpInfoClass, "netmask", "I")}; jfieldID gatewayFieldId{env->GetFieldID(dhcpInfoClass, "gateway", "I")}; jfieldID dns1FieldId{env->GetFieldID(dhcpInfoClass, "dns1", "I")}; jfieldID dns2FieldId{env->GetFieldID(dhcpInfoClass, "dns2", "I")}; jint ipAddress{env->GetIntField(dhcpInfo, ipAddressFieldId)}; jint subnet{env->GetIntField(dhcpInfo, subnetFieldId)}; jint gateway{env->GetIntField(dhcpInfo, gatewayFieldId)}; jint dns1{env->GetIntField(dhcpInfo, dns1FieldId)}; jint dns2{env->GetIntField(dhcpInfo, dns2FieldId)}; return DhcpInfo{ipAddress, subnet, gateway, dns1, dns2}; } void JvmManager::CloseKeyboard(jobject dialog) { env->CallVoidMethod(instance, closeKeyboardId, dialog); env->DeleteGlobalRef(dialog); } JvmManager::KeyboardCloseResult JvmManager::ShowValidationResult(jobject dialog, KeyboardTextCheckResult checkResult, std::u16string message) { auto str{env->NewString(reinterpret_cast(message.data()), static_cast(message.length()))}; auto result{static_cast(env->CallIntMethod(instance, showValidationResultId, dialog, checkResult, str))}; env->DeleteLocalRef(str); return result; } void JvmManager::reportCrash() { env->CallVoidMethod(instance, reportCrashId); } void JvmManager::ShowPipelineLoadingScreen(u32 totalPipelineCount) { env->CallVoidMethod(instance, showPipelineLoadingScreenId, static_cast(totalPipelineCount)); } void JvmManager::UpdatePipelineLoadingProgress(u32 progress) { env->CallVoidMethod(instance, updatePipelineLoadingProgressId, static_cast(progress)); } void JvmManager::HidePipelineLoadingScreen() { env->CallVoidMethod(instance, hidePipelineLoadingScreenId); } i32 JvmManager::GetVersionCode() { return env->CallIntMethod(instance, getVersionCodeId); } }