feat: Implement admin account deletion with confirmation and update documentation

This commit is contained in:
Alexa
2026-06-09 00:16:02 +03:00
parent 5b13e24880
commit 2b87f3d7c5
6 changed files with 415 additions and 17 deletions
+19 -11
View File
@@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition="'$(TargetFramework)' == '' and '$(TargetFrameworks)' == ''">net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>DorkNet.ClientMod</RootNamespace>
<AssemblyName>DorkNet.ClientMod</AssemblyName>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendTargetFrameworkToOutputPath Condition="'$(TargetFrameworks)' == ''">false</AppendTargetFrameworkToOutputPath>
<AppendTargetFrameworkToOutputPath Condition="'$(TargetFrameworks)' != ''">true</AppendTargetFrameworkToOutputPath>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NoWarn>$(NoWarn);CS8602;CS8604;CS0436</NoWarn>
</PropertyGroup>
@@ -19,21 +20,28 @@
they would be derivative works of the game anyway, so we never
reference them. Only MelonLoader's own DLLs are needed.
By default the project downloads the pinned MelonLoader release
into DorkNet.ClientMod/build/ and references those DLLs. Pass
-p:MelonLoaderDir=<path-to-extracted-MelonLoader> only when you
deliberately want to use a local override.
By default the project downloads the pinned desktop MelonLoader release
into DorkNet.ClientMod/build/ and references its net6 DLLs. For Quest /
LemonLoader builds, pass:
-p:TargetFrameworks=net8.0
-p:MelonLoaderDir=<path-to-LemonLoader-melon_data/MelonLoader>
The reference folder follows the target framework unless explicitly
overridden with -p:MelonLoaderReferenceTfm=...
-->
<PropertyGroup>
<MelonLoaderVersion Condition="'$(MelonLoaderVersion)' == ''">v0.6.6</MelonLoaderVersion>
<MelonLoaderCacheRoot>$(MSBuildThisFileDirectory)build\melonloader</MelonLoaderCacheRoot>
<MelonLoaderZip>$(MelonLoaderCacheRoot)\MelonLoader.x64.$(MelonLoaderVersion).zip</MelonLoaderZip>
<MelonLoaderDir Condition="'$(MelonLoaderDir)' == ''">$(MelonLoaderCacheRoot)\MelonLoader</MelonLoaderDir>
<MelonLoaderReferenceTfm Condition="'$(MelonLoaderReferenceTfm)' == '' and '$(TargetFramework)' == 'net8.0'">net8</MelonLoaderReferenceTfm>
<MelonLoaderReferenceTfm Condition="'$(MelonLoaderReferenceTfm)' == ''">net6</MelonLoaderReferenceTfm>
</PropertyGroup>
<Target Name="EnsureMelonLoaderReferences"
BeforeTargets="ResolveAssemblyReferences"
Condition="!Exists('$(MelonLoaderDir)\net6\MelonLoader.dll')">
Condition="'$(MelonLoaderReferenceTfm)' == 'net6' and !Exists('$(MelonLoaderDir)\$(MelonLoaderReferenceTfm)\MelonLoader.dll')">
<MakeDir Directories="$(MelonLoaderCacheRoot)" />
<DownloadFile
SourceUrl="https://github.com/LavaGang/MelonLoader/releases/download/$(MelonLoaderVersion)/MelonLoader.x64.zip"
@@ -47,19 +55,19 @@
<ItemGroup>
<Reference Include="MelonLoader">
<HintPath>$(MelonLoaderDir)\net6\MelonLoader.dll</HintPath>
<HintPath>$(MelonLoaderDir)\$(MelonLoaderReferenceTfm)\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(MelonLoaderDir)\net6\0Harmony.dll</HintPath>
<HintPath>$(MelonLoaderDir)\$(MelonLoaderReferenceTfm)\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppInterop.Runtime">
<HintPath>$(MelonLoaderDir)\net6\Il2CppInterop.Runtime.dll</HintPath>
<HintPath>$(MelonLoaderDir)\$(MelonLoaderReferenceTfm)\Il2CppInterop.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Il2CppInterop.Common">
<HintPath>$(MelonLoaderDir)\net6\Il2CppInterop.Common.dll</HintPath>
<HintPath>$(MelonLoaderDir)\$(MelonLoaderReferenceTfm)\Il2CppInterop.Common.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
+71 -4
View File
@@ -179,10 +179,16 @@ public class Mod : MelonMod
// EMGGKBALALH returns Photon.Realtime.AuthenticationValues —
// verified at PUNNetworkManager.txt:7560. Builds AuthValues with
// environment + accessToken AuthGetParameters.
TryPatchByName("PUNNetworkManager", "FJOLIPKKIBE",
postfix: nameof(PhotonPatches.LogAppSettings_Postfix));
TryPatchByName("PUNNetworkManager", "EMGGKBALALH",
postfix: nameof(PhotonPatches.LogAuthValues_Postfix));
//
// Quest-Dec uses the same obfuscation seed as PC-Dec, but discover by
// return type first so a future platform/reseed does not make these
// two critical hooks depend on the private method names.
TryPatchDeclaredMethodByReturnType("PUNNetworkManager", "AppSettings",
fallbackMethodName: "FJOLIPKKIBE",
postfix: nameof(PhotonPatches.LogAppSettings_Postfix));
TryPatchDeclaredMethodByReturnType("PUNNetworkManager", "AuthenticationValues",
fallbackMethodName: "EMGGKBALALH",
postfix: nameof(PhotonPatches.LogAuthValues_Postfix));
// PUNNetworkManager.OnCustomAuthenticationFailed — accepts whatever
// string-like arg the IL2CPP method takes (declared `string` in
// dump.cs but Il2CppInterop wraps it in a way that breaks
@@ -723,6 +729,58 @@ public class Mod : MelonMod
return TryPatch(label, type, methodName, args, prefix, postfix, logMiss);
}
private bool TryPatchDeclaredMethodByReturnType(string typeName, string returnTypeName,
string fallbackMethodName,
string? prefix = null, string? postfix = null,
bool logMiss = true)
{
var type = ResolveType(typeName);
var label = $"{typeName}.<returns {returnTypeName}>";
if (type is null)
{
if (logMiss) Log.Warning($"[patch-miss] {label}: type not found");
return TryPatchByName(typeName, fallbackMethodName, args: null, prefix, postfix, logMiss);
}
var matches = new List<MethodInfo>();
foreach (var method in AccessTools.GetDeclaredMethods(type))
{
if (method.IsAbstract || method.ContainsGenericParameters || method.IsSpecialName) continue;
if (ReturnTypeMatches(method.ReturnType, returnTypeName)) matches.Add(method);
}
if (matches.Count == 1)
{
var method = matches[0];
try
{
HarmonyInstance.Patch(method,
prefix: prefix is null ? null : new HarmonyMethod(GetPatchMethod(prefix)),
postfix: postfix is null ? null : new HarmonyMethod(GetPatchMethod(postfix)));
Log.Msg($"[patch-ok] {typeName}.{method.Name} (returns {returnTypeName})");
return true;
}
catch (Exception ex)
{
Log.Error($"[patch-fail] {typeName}.{method.Name} (returns {returnTypeName}): {ex.Message}");
return false;
}
}
if (matches.Count > 1)
{
var names = new List<string>();
foreach (var method in matches) names.Add(method.Name);
Log.Warning($"[patch-ambiguous] {label}: {string.Join(", ", names)}; falling back to {fallbackMethodName}");
}
else if (logMiss)
{
Log.Warning($"[patch-miss] {label}; falling back to {fallbackMethodName}");
}
return TryPatchByName(typeName, fallbackMethodName, args: null, prefix, postfix, logMiss);
}
internal static Type? ResolveType(string name)
{
if (ResolvedTypeCache.TryGetValue(name, out var cached))
@@ -795,6 +853,15 @@ public class Mod : MelonMod
type.FullName?.EndsWith(".Il2Cpp" + requested, StringComparison.Ordinal) == true;
}
private static bool ReturnTypeMatches(Type type, string requested)
{
var prefixed = "Il2Cpp" + requested;
if (type.FullName == requested || type.FullName == prefixed) return true;
if (type.Name == requested || type.Name == prefixed) return true;
return type.FullName?.EndsWith("." + requested, StringComparison.Ordinal) == true ||
type.FullName?.EndsWith(".Il2Cpp" + requested, StringComparison.Ordinal) == true;
}
private static string FormatPatchLabel(string typeName, string methodName, Type[]? args)
{
if (args is null) return $"{typeName}.{methodName}";