From 54041ceba15aadc47dc7ac3f798d09ce2922bdc2 Mon Sep 17 00:00:00 2001 From: Dima Date: Mon, 24 Apr 2023 14:46:42 +0300 Subject: [PATCH] Implement GetCurrentIpConfigInfo Needed for almost every LAN game --- app/src/main/AndroidManifest.xml | 1 + app/src/main/cpp/skyline/jvm.cpp | 20 +++- app/src/main/cpp/skyline/jvm.h | 14 +++ .../skyline/services/nifm/IGeneralService.cpp | 96 ++++++++++++++++++- .../skyline/services/nifm/IGeneralService.h | 91 ++++++++++++++++++ .../java/emu/skyline/EmulationActivity.kt | 8 ++ 6 files changed, 226 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d68f7da1..ee547a3e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + GetMethodID(instanceClass, "closeKeyboard", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)V")}, showValidationResultId{environ->GetMethodID(instanceClass, "showValidationResult", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;ILjava/lang/String;)I")}, getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")}, - getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")} { + getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")}, + getDhcpInfoId{environ->GetMethodID(instanceClass, "getDhcpInfo", "()Landroid/net/DhcpInfo;")} { env.Initialize(environ); } @@ -132,6 +133,23 @@ namespace skyline { 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); diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index 535a307a..6f593a2b 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -7,6 +7,14 @@ #include namespace skyline { + struct DhcpInfo { + i32 ipAddress; + i32 subnet; + i32 gateway; + i32 dns1; + i32 dns2; + }; + /** * @brief A wrapper over std::string that supports construction using a JNI jstring */ @@ -182,6 +190,11 @@ namespace skyline { */ i32 GetVersionCode(); + /** + * @brief A call to EmulationActivity.getDhcpInfo in Kotlin + */ + DhcpInfo GetDhcpInfo(); + private: jmethodID initializeControllersId; jmethodID vibrateDeviceId; @@ -191,6 +204,7 @@ namespace skyline { jmethodID closeKeyboardId; jmethodID showValidationResultId; jmethodID getVersionCodeId; + jmethodID getDhcpInfoId; jmethodID getIntegerValueId; }; diff --git a/app/src/main/cpp/skyline/services/nifm/IGeneralService.cpp b/app/src/main/cpp/skyline/services/nifm/IGeneralService.cpp index 636c6c3a..4e6e11ac 100644 --- a/app/src/main/cpp/skyline/services/nifm/IGeneralService.cpp +++ b/app/src/main/cpp/skyline/services/nifm/IGeneralService.cpp @@ -4,8 +4,22 @@ #include "IScanRequest.h" #include "IRequest.h" #include "IGeneralService.h" +#include +#include namespace skyline::service::nifm { + /** + * @brief Converts integer value to an array of bytes ordered in little-endian format + */ + static std::array ConvertIntToByteArray(i32 value) { + std::array result{}; + result[0] = value & 0xFF; + result[1] = (value >> 8) & 0xFF; + result[2] = (value >> 16) & 0xFF; + result[3] = (value >> 24) & 0xFF; + return result; + } + IGeneralService::IGeneralService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} Result IGeneralService::CreateScanRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -18,13 +32,89 @@ namespace skyline::service::nifm { return {}; } + Result IGeneralService::GetCurrentNetworkProfile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!(*state.settings->isInternetEnabled)) + return result::NoInternetConnection; + + const UUID uuid{static_cast(0xdeadbeef) << 64}; + auto dhcpInfo{state.jvm->GetDhcpInfo()}; + + SfNetworkProfileData networkProfileData{ + .ipSettingData{ + .ipAddressSetting{ + true, + .currentAddress{ConvertIntToByteArray(dhcpInfo.ipAddress)}, + .subnetMask{ConvertIntToByteArray(dhcpInfo.subnet)}, + .gateway{ConvertIntToByteArray(dhcpInfo.gateway)}, + }, + .dnsSetting{ + true, + .primaryDns{ConvertIntToByteArray(dhcpInfo.dns1)}, + .secondaryDns{ConvertIntToByteArray(dhcpInfo.dns2)}, + }, + .proxySetting{ + false, + .port{}, + .proxyServer{}, + .automaticAuthEnabled{}, + .user{}, + .password{}, + }, + 1500, + }, + .uuid{uuid}, + .networkName{"Skyline Network"}, + .wirelessSettingData{ + 12, + .ssid{"Skyline Network"}, + .passphrase{"skylinepassword"}, + }, + }; + + request.outputBuf.at(0).as() = networkProfileData; + return {}; + } + Result IGeneralService::GetCurrentIpAddress(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - return result::NoInternetConnection; + if (!(*state.settings->isInternetEnabled)) + return result::NoInternetConnection; + + auto dhcpInfo{state.jvm->GetDhcpInfo()}; + response.Push(ConvertIntToByteArray(dhcpInfo.ipAddress)); + return {}; + } + + Result IGeneralService::GetCurrentIpConfigInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!(*state.settings->isInternetEnabled)) + return result::NoInternetConnection; + + auto dhcpInfo{state.jvm->GetDhcpInfo()}; + + struct IpConfigInfo { + IpAddressSetting ipAddressSetting; + DnsSetting dnsSetting; + }; + + IpConfigInfo ipConfigInfo{ + .ipAddressSetting{ + true, + .currentAddress{ConvertIntToByteArray(dhcpInfo.ipAddress)}, + .subnetMask{ConvertIntToByteArray(dhcpInfo.subnet)}, + .gateway{ConvertIntToByteArray(dhcpInfo.gateway)}, + }, + .dnsSetting{ + true, + .primaryDns{ConvertIntToByteArray(dhcpInfo.dns1)}, + .secondaryDns{ConvertIntToByteArray(dhcpInfo.dns2)}, + }, + }; + + response.Push(ipConfigInfo); + return {}; } Result IGeneralService::IsAnyInternetRequestAccepted(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - // We don't emulate networking so always return false - response.Push(false); + response.Push(*state.settings->isInternetEnabled); return {}; } } diff --git a/app/src/main/cpp/skyline/services/nifm/IGeneralService.h b/app/src/main/cpp/skyline/services/nifm/IGeneralService.h index efdce9d0..e5f94e85 100644 --- a/app/src/main/cpp/skyline/services/nifm/IGeneralService.h +++ b/app/src/main/cpp/skyline/services/nifm/IGeneralService.h @@ -4,11 +4,90 @@ #pragma once #include +#include "common/uuid.h" namespace skyline::service::nifm { namespace result { constexpr Result NoInternetConnection{110, 300}; } + + struct IpAddressSetting { + bool isAutomatic{}; + std::array currentAddress{}; + std::array subnetMask{}; + std::array gateway{}; + }; + static_assert(sizeof(IpAddressSetting) == 0xD); + + struct DnsSetting { + bool isAutomatic{}; + std::array primaryDns{}; + std::array secondaryDns{}; + }; + static_assert(sizeof(DnsSetting) == 0x9); + + struct ProxySetting { + bool enabled{}; + u8 _pad0_[0x1]; + u16 port{}; + std::array proxyServer{}; + bool automaticAuthEnabled{}; + std::array user{}; + std::array password{}; + u8 _pad1_[0x1]; + }; + static_assert(sizeof(ProxySetting) == 0xAA); + + struct IpSettingData { + IpAddressSetting ipAddressSetting{}; + DnsSetting dnsSetting{}; + ProxySetting proxySetting{}; + u16 mtu{}; + }; + static_assert(sizeof(IpSettingData) == 0xC2); + + struct SfWirelessSettingData { + u8 ssidLength{}; + std::array ssid{}; + u8 _unk0_[0x3]; + std::array passphrase{}; + }; + static_assert(sizeof(SfWirelessSettingData) == 0x65); + + struct NifmWirelessSettingData { + u8 ssidLength{}; + std::array ssid{}; + u8 _unk0_[0x1]; + u8 _pad0_[0x1]; + u32 _unk1_[0x2]; + std::array passphrase{}; + u8 _pad1_[0x3]; + }; + static_assert(sizeof(NifmWirelessSettingData) == 0x70); + + #pragma pack(push, 1) + struct SfNetworkProfileData { + IpSettingData ipSettingData{}; + UUID uuid{}; + std::array networkName{}; + u8 _unk0_[0x4]; + SfWirelessSettingData wirelessSettingData{}; + u8 _pad0_[0x1]; + }; + static_assert(sizeof(SfNetworkProfileData) == 0x17C); + + struct NifmNetworkProfileData { + UUID uuid{}; + std::array networkName{}; + u32 _unk0_[0x2]; + u8 _unk1_[0x2]; + u8 _pad0_[0x2]; + NifmWirelessSettingData wirelessSettingData{}; + IpSettingData ipSettingData{}; + }; + static_assert(sizeof(NifmNetworkProfileData) == 0x18E); + #pragma pack(pop) + /** * @brief IGeneralService is used by applications to control the network connection * @url https://switchbrew.org/wiki/Network_Interface_services#IGeneralService @@ -27,11 +106,21 @@ namespace skyline::service::nifm { */ Result CreateRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** + * @url https://switchbrew.org/wiki/Network_Interface_services#GetCurrentNetworkProfile + */ + Result GetCurrentNetworkProfile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** * @url https://switchbrew.org/wiki/Network_Interface_services#GetCurrentIpAddress */ Result GetCurrentIpAddress(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** + * @url https://switchbrew.org/wiki/Network_Interface_services#GetCurrentIpConfigInfo + */ + Result GetCurrentIpConfigInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** * @url https://switchbrew.org/wiki/Network_Interface_services#IsAnyInternetRequestAccepted */ @@ -40,7 +129,9 @@ namespace skyline::service::nifm { SERVICE_DECL( SFUNC(0x1, IGeneralService, CreateScanRequest), SFUNC(0x4, IGeneralService, CreateRequest), + SFUNC(0x5, IGeneralService, GetCurrentNetworkProfile), SFUNC(0xC, IGeneralService, GetCurrentIpAddress), + SFUNC(0xF, IGeneralService, GetCurrentIpConfigInfo), SFUNC(0x15, IGeneralService, IsAnyInternetRequestAccepted) ) }; diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index 768df54c..83a5da04 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -18,6 +18,8 @@ import android.content.res.Configuration import android.graphics.PointF import android.graphics.drawable.Icon import android.hardware.display.DisplayManager +import android.net.DhcpInfo +import android.net.wifi.WifiManager import android.os.* import android.util.Log import android.util.Rational @@ -626,6 +628,12 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo return dialog.waitForSubmitOrCancel().let { arrayOf(if (it.cancelled) 1 else 0, it.text) } } + @Suppress("unused") + fun getDhcpInfo() : DhcpInfo { + val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + return wifiManager.dhcpInfo + } + @Suppress("unused") fun closeKeyboard(dialog : SoftwareKeyboardDialog) { runOnUiThread { dialog.dismiss() }