first commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures <see cref="Application.runInBackground"/> is set to true at all times.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Nitrox needs to be running updates and processing packets at all times to work properly.
|
||||
/// </remarks>
|
||||
public sealed partial class Application_runInBackground_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Property(() => Application.runInBackground).GetSetMethod();
|
||||
|
||||
public static bool Prefix(bool value)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
Log.WarnOnce($"An attempt to set {nameof(Application.runInBackground)} to \"false\" was issued but it was ignored.\n{new StackTrace()}");
|
||||
Application.runInBackground = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
internal partial class CellManager_GetPrefabForSlot_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CellManager t) => t.GetPrefabForSlot(default(EntitySlot)));
|
||||
|
||||
public static bool Prefix(IEntitySlot slot, out EntitySlot.Filler __result)
|
||||
{
|
||||
__result = default;
|
||||
return !Multiplayer.Active;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class CellManager_TryLoadCacheBatchCells_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CellManager t) => t.TryLoadCacheBatchCells(default(BatchCells)));
|
||||
private static readonly MethodInfo PATH_PREFIX_GETTER = Reflect.Property((LargeWorldStreamer t) => t.pathPrefix).GetMethod;
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
|
||||
{
|
||||
List<CodeInstruction> instrList = instructions.ToList();
|
||||
|
||||
Label pathPrefixJmp = generator.DefineLabel();
|
||||
|
||||
Label labeledPathInstructionJmp = generator.DefineLabel();
|
||||
|
||||
Label fallbackPrefixJmp = generator.DefineLabel();
|
||||
|
||||
Label labeledFallbackInstructionJmp = generator.DefineLabel();
|
||||
|
||||
for (int i = 0; i < instrList.Count; i++)
|
||||
{
|
||||
CodeInstruction instruction = instrList[i];
|
||||
if (instrList.Count > i + 2 && instrList[i + 2].opcode == OpCodes.Callvirt && ReferenceEquals(instrList[i + 2].operand, PATH_PREFIX_GETTER))
|
||||
{
|
||||
foreach (CodeInstruction instr in TranspilerHelper.IsMultiplayer(pathPrefixJmp, generator))
|
||||
{
|
||||
yield return instr;
|
||||
}
|
||||
|
||||
yield return new CodeInstruction(OpCodes.Ldstr, ""); // Replace pathPrefix with an empty string
|
||||
yield return new CodeInstruction(OpCodes.Br, labeledPathInstructionJmp);
|
||||
instrList[i].labels.Add(pathPrefixJmp);
|
||||
yield return instrList[i];
|
||||
yield return instrList[i + 1];
|
||||
yield return instrList[i + 2];
|
||||
|
||||
CodeInstruction labeledCodeInstruction = new(instrList[i + 3].opcode, instrList[i + 3].operand);
|
||||
labeledCodeInstruction.labels.Add(labeledPathInstructionJmp);
|
||||
|
||||
yield return labeledCodeInstruction;
|
||||
i += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// Patch to disable the EscapePodFirstUseCinematicsController.OnSceneObjectsLoaded method when in multiplayer.
|
||||
/// Initialize will be called OnSceneLoad to setup cinematics or first use cinematics
|
||||
/// </summary>
|
||||
public sealed partial class EscapePodFirstUseCinematicsController_OnSceneObjectsLoaded_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePodFirstUseCinematicsController t) => t.OnSceneObjectsLoaded());
|
||||
|
||||
public static bool Prefix(EscapePodFirstUseCinematicsController __instance) => !Multiplayer.Active;
|
||||
}
|
83
NitroxPatcher/Patches/Persistent/GameAnalytics_Patch.cs
Normal file
83
NitroxPatcher/Patches/Persistent/GameAnalytics_Patch.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// Patch to disable any analytics uploads to UWE servers. Since game is modded it's not useful for UWE and it's less
|
||||
/// code to run for us
|
||||
/// </summary>
|
||||
public sealed class GameAnalytics_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Disables analytics uploads
|
||||
/// </summary>
|
||||
private static readonly MethodInfo TARGET_METHOD_SDK = Reflect.Method((SentrySdk t) => t.Start());
|
||||
|
||||
/// <summary>
|
||||
/// Skip manager create as well to remove NRE in Player.log on exit (OnDestroy will try access null SentrySdk instance)
|
||||
/// </summary>
|
||||
private static readonly MethodInfo TARGET_METHOD_MANAGER = Reflect.Method((SentrySdkManager t) => t.Awake());
|
||||
private static readonly MethodInfo TARGET_METHOD_MANAGER_ONENABLE = Reflect.Method((SentrySdkManager t) => t.OnEnable());
|
||||
|
||||
/// <summary>
|
||||
/// Skip controller create as well to remove NRE in Player.log
|
||||
/// </summary>
|
||||
private static readonly MethodInfo TARGET_METHOD_CONTROLLER = Reflect.Method((AnalyticsController t) => t.Awake());
|
||||
private static readonly MethodInfo TARGET_METHOD_CONTROLLER_ONENABLE = Reflect.Method((AnalyticsController t) => t.OnEnable());
|
||||
|
||||
private static readonly MethodInfo TARGET_METHOD_TELEMETRY = Reflect.Method((Telemetry t) => t.Awake());
|
||||
private static readonly MethodInfo TARGET_METHOD_TELEMETRY_ONENABLE = Reflect.Method((Telemetry t) => t.OnEnable());
|
||||
private static readonly MethodInfo TARGET_METHOD_TELEMETRY_QUIT = Reflect.Method(() => Telemetry.SendGameQuit(default));
|
||||
private static readonly MethodInfo TARGET_METHOD_GAMEANALYTICS_SEND_EVENT = Reflect.Method(() => GameAnalytics.Send(default(GameAnalytics.Event), default(bool), default(string)));
|
||||
private static readonly MethodInfo TARGET_METHOD_GAMEANALYTICS_SEND_EVENTINFO = Reflect.Method(() => GameAnalytics.Send(default(GameAnalytics.EventInfo), default(bool), default(string)));
|
||||
private static readonly MethodInfo TARGET_METHOD_GAMEANALYTICS_SEND_LEGACYEVENT = Reflect.Method(() => GameAnalytics.LegacyEvent(default(GameAnalytics.Event), default(string)));
|
||||
private static readonly MethodInfo TARGET_METHOD_SPAWNER_ANALYTICS = Reflect.Method((SystemsSpawner t) => t.Awake());
|
||||
|
||||
public static bool Prefix(SentrySdk __instance)
|
||||
{
|
||||
FieldInfo initializedField = __instance.GetType().GetField("_initialized", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (initializedField == null)
|
||||
{
|
||||
Log.WarnOnce($"{nameof(SentrySdk)} could not be properly disabled because the field '_initialized' is missing. Expect NRE in logs where Telemetry events are processed.");
|
||||
return false;
|
||||
}
|
||||
initializedField.SetValue(__instance, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Prefix(AnalyticsController __instance)
|
||||
{
|
||||
UnityEngine.Object.Destroy(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Prefix(Telemetry __instance)
|
||||
{
|
||||
UnityEngine.Object.Destroy(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Patch(Harmony harmony)
|
||||
{
|
||||
PatchPrefix(harmony, TARGET_METHOD_SDK, ((Func<SentrySdk, bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_CONTROLLER, ((Func<AnalyticsController, bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_CONTROLLER_ONENABLE, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_MANAGER, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_MANAGER_ONENABLE, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_TELEMETRY, ((Func<Telemetry, bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_TELEMETRY_ONENABLE, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_TELEMETRY_QUIT, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_GAMEANALYTICS_SEND_EVENT, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_GAMEANALYTICS_SEND_EVENTINFO, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_GAMEANALYTICS_SEND_LEGACYEVENT, ((Func<bool>)Prefix).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_SPAWNER_ANALYTICS, ((Func<bool>)Prefix).Method);
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class GameInput_Initialize_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((GameInput t) => t.Initialize());
|
||||
|
||||
private static readonly OpCode INJECTION_OPCODE = OpCodes.Stsfld;
|
||||
private static readonly object INJECTION_OPERAND = Reflect.Field(() => GameInput.numButtons);
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
Validate.NotNull(INJECTION_OPERAND);
|
||||
|
||||
foreach (CodeInstruction instruction in instructions)
|
||||
{
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
||||
{
|
||||
/*
|
||||
* int prev = GameInput.GetMaximumEnumValue(typeof(GameInput.Button)) + 1;
|
||||
* // ^ This value is already calculated by the original code, it's stored on top of the stack.
|
||||
* KeyBindingManager keyBindingManager = new KeyBindingManager();
|
||||
* GameButton.numButtons = Math.Max(keyBindingManager.GetHighestKeyBindingValue() + 1, prev);
|
||||
*/
|
||||
yield return new CodeInstruction(OpCodes.Newobj, Reflect.Constructor(() => new KeyBindingManager()));
|
||||
yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Method((KeyBindingManager t) => t.GetHighestKeyBindingValue()));
|
||||
yield return new CodeInstruction(OpCodes.Ldc_I4_1);
|
||||
yield return new CodeInstruction(OpCodes.Add);
|
||||
yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Math.Max(default(int), default(int))));
|
||||
}
|
||||
|
||||
yield return instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public partial class GameInput_SetupDefaultKeyboardBindings_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => GameInput.SetupDefaultKeyboardBindings());
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
KeyBindingManager keyBindingManager = new();
|
||||
foreach (KeyBinding keyBinding in keyBindingManager.KeyboardKeyBindings)
|
||||
{
|
||||
GameInput.SetBindingInternal(keyBinding.Device, keyBinding.Button, GameInput.BindingSet.Primary, keyBinding.PrimaryKey);
|
||||
|
||||
// if the options.bin gets deleted (when loading SP) and the client.cfg has secondary keybinds, repopulate them.
|
||||
if (!string.IsNullOrEmpty(keyBinding.SecondaryKey))
|
||||
{
|
||||
GameInput.SetBindingInternal(keyBinding.Device, keyBinding.Button, GameInput.BindingSet.Secondary, keyBinding.SecondaryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
|
||||
using NitroxClient.Serialization;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class GameSettings_SerializeInputSettings_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => GameSettings.SerializeInputSettings(default(GameSettings.ISerializer)));
|
||||
|
||||
public static void Postfix(GameSettings.ISerializer serializer)
|
||||
{
|
||||
ClientConfig cfg = ClientConfig.Load(NitroxUser.AppDataPath);
|
||||
KeyBindingManager keyBindingManager = new();
|
||||
|
||||
foreach (GameInput.BindingSet bindingSet in Enum.GetValues(typeof(GameInput.BindingSet)))
|
||||
{
|
||||
foreach (KeyBinding keyBinding in keyBindingManager.KeyboardKeyBindings)
|
||||
{
|
||||
Log.Debug($"Getting keybinding: {keyBinding.Device}, {keyBinding.Label} ({keyBinding.Button}), {bindingSet}");
|
||||
string binding = GameInput.GetBinding(keyBinding.Device, keyBinding.Button, bindingSet);
|
||||
|
||||
// We need to assign the correct binding for primary and secondary binding sets to the relevant area of the config.
|
||||
switch ((KeyBindingValues)keyBinding.Button)
|
||||
{
|
||||
case KeyBindingValues.CHAT when bindingSet == GameInput.BindingSet.Primary:
|
||||
cfg.OpenChatKeybindPrimary = binding;
|
||||
break;
|
||||
case KeyBindingValues.FOCUS_DISCORD when bindingSet == GameInput.BindingSet.Primary:
|
||||
cfg.FocusDiscordKeybindPrimary = binding;
|
||||
break;
|
||||
case KeyBindingValues.CHAT when bindingSet == GameInput.BindingSet.Secondary:
|
||||
cfg.OpenChatKeybindSecondary = binding;
|
||||
break;
|
||||
case KeyBindingValues.FOCUS_DISCORD when bindingSet == GameInput.BindingSet.Secondary:
|
||||
cfg.FocusDiscordKeybindSecondary = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Serialize(NitroxUser.AppDataPath);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using LitJson;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public class Language_LoadLanguageFile_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Language t) => t.LoadLanguageFile(default));
|
||||
|
||||
private static readonly Dictionary<string, Tuple<string, string>> languageToIsoCode = new(); // First Tuple item is region specific (en-US), second isn't (en)
|
||||
|
||||
public static void Postfix(string language, Dictionary<string, string> ___strings)
|
||||
{
|
||||
if (!TryLoadLanguageFile("en", ___strings)) //Loading english as fallback for missing files or keys
|
||||
{
|
||||
Log.Error($"The English language file could not be loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryLoadLanguageFile(languageToIsoCode[language].Item1, ___strings))
|
||||
{
|
||||
if (!TryLoadLanguageFile(languageToIsoCode[language].Item2, ___strings))
|
||||
{
|
||||
Log.Warn($"No language file was found for {language}. Using English as fallback");
|
||||
}
|
||||
}
|
||||
|
||||
Language.main.ParseMetaData();
|
||||
}
|
||||
|
||||
private static bool TryLoadLanguageFile(string fileName, IDictionary<string, string> strings)
|
||||
{
|
||||
string filePath = Path.Combine(NitroxUser.LanguageFilesPath, $"{fileName}.json");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using StreamReader streamReader = new(filePath);
|
||||
try
|
||||
{
|
||||
JsonData json = JsonMapper.ToObject(streamReader);
|
||||
|
||||
foreach (string key in json.Keys)
|
||||
{
|
||||
JsonData entry = json[key];
|
||||
if (entry.IsString)
|
||||
{
|
||||
strings[key] = (string)entry;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error while reading language file {fileName}.json");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Patch(Harmony harmony)
|
||||
{
|
||||
List<string> existingLanguageNames = Directory.EnumerateFiles(SNUtils.InsideUnmanaged("LanguageFiles"), "*.json").Select(Path.GetFileNameWithoutExtension).ToList();
|
||||
|
||||
foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
||||
{
|
||||
if (!languageToIsoCode.ContainsKey(culture.EnglishName) && !string.IsNullOrEmpty(culture.Name) && existingLanguageNames.Contains(culture.EnglishName))
|
||||
{
|
||||
languageToIsoCode.Add(culture.EnglishName, new Tuple<string, string>(culture.Name, culture.TwoLetterISOLanguageName));
|
||||
}
|
||||
}
|
||||
|
||||
// This language isn't registered in CultureInfo
|
||||
if (existingLanguageNames.Contains("Spanish (Latin America)"))
|
||||
{
|
||||
languageToIsoCode.Add("Spanish (Latin America)", new Tuple<string, string>("es-419", "es"));
|
||||
}
|
||||
|
||||
PatchPostfix(harmony, TARGET_METHOD, ((Action<string, Dictionary<string, string>>)Postfix).Method);
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public partial class MainGameController_StartGame_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((MainGameController t) => t.StartGame()));
|
||||
|
||||
public static readonly OpCode INJECTION_OPCODE = OpCodes.Call;
|
||||
public static readonly object INJECTION_OPERAND = Reflect.Method(() => WaitScreen.Remove(default));
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
Validate.NotNull(INJECTION_OPERAND);
|
||||
|
||||
int injectSeenCounter = 0;
|
||||
|
||||
foreach (CodeInstruction instruction in instructions)
|
||||
{
|
||||
yield return instruction;
|
||||
|
||||
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
||||
{
|
||||
injectSeenCounter++;
|
||||
|
||||
if (injectSeenCounter == 3)
|
||||
{
|
||||
yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Multiplayer.SubnauticaLoadingCompleted()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// MainMenuLoadButton.RestoreParentsSettings() is throwing null refs because we copy it for our MainMenu and then destroy it.
|
||||
/// Unfortunately OnDestroy can't be prevented when destroying MBs so we fix the NRE here.
|
||||
/// </summary>
|
||||
public sealed partial class MainMenuLoadButton_RestoreParentsSettings_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((MainMenuLoadButton lb) => lb.RestoreParentsSettings());
|
||||
|
||||
public static bool Prefix(GridLayoutGroup ___gridLayoutGroup, ScrollRect ___scrollRect)
|
||||
{
|
||||
return ___gridLayoutGroup && ___scrollRect;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours.Gui.MainMenu;
|
||||
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public partial class MainMenuRightSide_OpenGroup_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((MainMenuRightSide t) => t.OpenGroup(default(string)));
|
||||
|
||||
public static void Prefix(string target)
|
||||
{
|
||||
// Stopping the client if we leave the joining process
|
||||
if (target is not (MainMenuJoinServerPanel.NAME or MainMenuEnterPasswordPanel.NAME or MainMenuNotificationPanel.NAME))
|
||||
{
|
||||
JoinServerBackend.StopMultiplayerClient();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NitroxClient.Helpers;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class ProtobufSerializer_Deserialize_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((ProtobufSerializer t) => t.Deserialize(default(Stream), default(object), default(Type), default(bool)));
|
||||
private static readonly NitroxProtobufSerializer serializer = Resolve<NitroxProtobufSerializer>(true);
|
||||
|
||||
public static bool Prefix(Stream stream, object target, Type type, bool verbose)
|
||||
{
|
||||
if (Multiplayer.Active && serializer.NitroxTypes.ContainsKey(type))
|
||||
{
|
||||
serializer.Deserialize(stream, target, type);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NitroxClient.Helpers;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class ProtobufSerializer_Serialize_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((ProtobufSerializer t) => t.Serialize(default(Stream), default(object), default(Type)));
|
||||
|
||||
/// <summary>
|
||||
/// This patch is in a hot path so it needs this optimization.
|
||||
/// </summary>
|
||||
private static readonly NitroxProtobufSerializer serializer = Resolve<NitroxProtobufSerializer>(true);
|
||||
|
||||
public static bool Prefix(Stream stream, object source, Type type)
|
||||
{
|
||||
if (Multiplayer.Active && serializer.NitroxTypes.ContainsKey(type))
|
||||
{
|
||||
serializer.Serialize(stream, source);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
// Set "false" to "true" then start any game and look into game.log to find the code to paste in ReefbackSpawnData.cs
|
||||
#if false
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public sealed partial class ReefbackLife_OnEnable_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((ReefbackLife t) => t.OnEnable());
|
||||
|
||||
private static bool printed;
|
||||
|
||||
public static void Postfix(ReefbackLife __instance)
|
||||
{
|
||||
if (printed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
printed = true;
|
||||
|
||||
// Make sure to switch culture so that formatted string use "." instead of "," for numbers
|
||||
CultureInfo previousCulture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
|
||||
string text = $"\npublic const int CREATURE_SLOTS_COUNT = {__instance.creatureSlots.Length};";
|
||||
text += $"\npublic const int PLANT_SLOTS_COUNT = {__instance.plantSlots.Length};";
|
||||
text += $"\npublic const int GRASS_VARIANTS_COUNT = {__instance.grassVariants.Length};";
|
||||
text += $"\n\npublic static readonly NitroxTransform CreatureSlotsRootTransform = {TransformToString(__instance.creatureSlots[0].parent)};";
|
||||
text += $"\npublic static readonly NitroxTransform PlantSlotsRootTransform = {TransformToString(__instance.plantSlotsRoot)};";
|
||||
|
||||
text += "\n\npublic static List<ReefbackSlotCreature> SpawnableCreatures { get; } =\n[\n";
|
||||
foreach (ReefbackSlotsData.ReefbackSlotCreature reefbackSlotCreature in __instance.reefbackSlotsData.creatures)
|
||||
{
|
||||
string classId = reefbackSlotCreature.prefab.GetComponent<UniqueIdentifier>().ClassId;
|
||||
text += $"new() {{ Probability = {reefbackSlotCreature.probability}f, MinNumber = {reefbackSlotCreature.minNumber}, MaxNumber = {reefbackSlotCreature.maxNumber}, ClassId = \"{classId}\" }},\n";
|
||||
}
|
||||
|
||||
text += "];\n\npublic static List<ReefbackSlotPlant> SpawnablePlants { get; } =\n[\n";
|
||||
foreach (ReefbackSlotsData.ReefbackSlotPlant reefbackSlotPlant in __instance.reefbackSlotsData.plants)
|
||||
{
|
||||
List<string> list = [];
|
||||
foreach (GameObject gameObject in reefbackSlotPlant.prefabVariants)
|
||||
{
|
||||
list.Add(gameObject.GetComponent<UniqueIdentifier>().ClassId);
|
||||
}
|
||||
text += $"new() {{ ClassIds = [\"{string.Join("\", \"", list)}\"], Probability = {reefbackSlotPlant.probability}f, StartRotation = {Vector3ToString(reefbackSlotPlant.startRotation)} }},\n";
|
||||
}
|
||||
|
||||
text += "];\n\npublic static List<NitroxTransform> CreatureSlotsCoordinates { get; } =\n[\n";
|
||||
foreach (Transform transform in __instance.creatureSlots)
|
||||
{
|
||||
text += $"{TransformToString(transform)},\n";
|
||||
}
|
||||
|
||||
text += "];\n\npublic static List<NitroxTransform> PlantSlotsCoordinates { get; } =\n[\n";
|
||||
foreach (Transform transform in __instance.plantSlots)
|
||||
{
|
||||
text += $"{TransformToString(transform)},\n";
|
||||
}
|
||||
text += "];\n";
|
||||
|
||||
Log.Info(text);
|
||||
|
||||
CultureInfo.CurrentCulture = previousCulture;
|
||||
}
|
||||
|
||||
private static string TransformToString(Transform transform)
|
||||
{
|
||||
return $"new({Vector3ToString(transform.localPosition)}, {QuaternionToString(transform.localRotation)}, {Vector3ToString(transform.localScale)})";
|
||||
}
|
||||
|
||||
private static string Vector3ToString(Vector3 vector3)
|
||||
{
|
||||
return $"new({vector3.x}f, {vector3.y}f, {vector3.z}f)";
|
||||
}
|
||||
|
||||
private static string QuaternionToString(Quaternion quaternion)
|
||||
{
|
||||
return $"new({quaternion.x}f, {quaternion.y}f, {quaternion.z}f, {quaternion.w}f)";
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,16 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class ScreenshotManager_Initialise : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => ScreenshotManager.Initialize(default(string)));
|
||||
|
||||
public static void Prefix(ScreenshotManager __instance, ref string _savePath)
|
||||
{
|
||||
_savePath = Path.GetFullPath(NitroxUser.LauncherPath ?? ".");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
#if DEBUG
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
internal sealed partial class StartScreen_TryToShowDisclaimer_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StartScreen t) => t.TryToShowDisclaimer());
|
||||
|
||||
/// <summary>
|
||||
/// Speed up startup in development by skipping disclaimer screen.
|
||||
/// </summary>
|
||||
public static bool Prefix() => false;
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure that Coroutine aren't fully blocked once they encounter an Exception
|
||||
/// </summary>
|
||||
public sealed partial class UnityEngine_SetupCoroutine_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
// UnityEngine DLLs aren't publicized so we can't access this class as done in other patches
|
||||
public static readonly MethodInfo TARGET_METHOD = Assembly.GetAssembly(typeof(GameObject))
|
||||
.GetType("UnityEngine.SetupCoroutine")
|
||||
.GetMethod("InvokeMoveNext", BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
Log.Error(__exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class uGUI_FeedbackCollector_IsEnabled_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((uGUI_FeedbackCollector t) => t.IsEnabled());
|
||||
|
||||
public static bool Prefix(ref bool __result)
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
#if DEBUG
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.MultiplayerSession;
|
||||
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
|
||||
using NitroxModel;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
// TODO: Rework this to be less ad hoc and more robust with command line arguments
|
||||
public sealed partial class uGUI_MainMenu_Start_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((uGUI_MainMenu t) => t.Start()));
|
||||
|
||||
private static bool applied;
|
||||
private static string playerName;
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
if (applied)
|
||||
{
|
||||
return;
|
||||
}
|
||||
applied = true;
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
Log.Info($"CommandLineArgs: {string.Join(" ", args)}");
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i].Equals("--instantlaunch", StringComparison.OrdinalIgnoreCase) && args.Length > i + 1)
|
||||
{
|
||||
playerName = args[i + 1];
|
||||
Log.Info($"Detected instant launch, connecting to 127.0.0.1:11000 as {playerName}");
|
||||
_ = JoinServerBackend.StartDetachedMultiplayerClientAsync(IPAddress.Loopback, 11000, SessionConnectionStateChangedHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SessionConnectionStateChangedHandler(IMultiplayerSessionConnectionState state)
|
||||
{
|
||||
switch (state.CurrentStage)
|
||||
{
|
||||
case MultiplayerSessionConnectionStage.AWAITING_RESERVATION_CREDENTIALS:
|
||||
|
||||
if (Resolve<IMultiplayerSession>().SessionPolicy.RequiresServerPassword)
|
||||
{
|
||||
Log.Error("Local server requires a password which is not supported with instant launch.");
|
||||
Log.InGame("Local server requires a password which is not supported with instant launch.");
|
||||
break;
|
||||
}
|
||||
|
||||
NitroxColor playerColor = new(1,1,1);
|
||||
byte[] nameHash = playerName.AsMd5Hash();
|
||||
if (nameHash.Length >= 8)
|
||||
{
|
||||
float hue = BitConverter.ToUInt64([nameHash[0], nameHash[1], nameHash[2], nameHash[3], nameHash[4], nameHash[5], nameHash[6], nameHash[7]], 0) / (float)ulong.MaxValue;
|
||||
playerColor = NitroxColor.FromHsb(hue);
|
||||
}
|
||||
PlayerSettings playerSettings = new(playerColor);
|
||||
AuthenticationContext authenticationContext = new(playerName, Optional.Empty);
|
||||
Resolve<IMultiplayerSession>().RequestSessionReservation(playerSettings, authenticationContext);
|
||||
break;
|
||||
|
||||
case MultiplayerSessionConnectionStage.SESSION_RESERVED:
|
||||
Resolve<IMultiplayerSession>().ConnectionStateChanged -= SessionConnectionStateChangedHandler;
|
||||
JoinServerBackend.StartGame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
/// <summary>
|
||||
/// Remove the option for PDA pause
|
||||
/// </summary>
|
||||
public sealed partial class uGUI_OptionsPanel_AddAccessibilityTab_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((uGUI_OptionsPanel t) => t.AddAccessibilityTab());
|
||||
|
||||
/// <summary>
|
||||
/// Simply removes following line
|
||||
/// AddToggleOption(num, "PDAPause" ...
|
||||
/// </summary>
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchStartForward([
|
||||
new(OpCodes.Ldarg_0),
|
||||
new(OpCodes.Ldloc_0),
|
||||
new(OpCodes.Ldstr, "PDAPause")
|
||||
])
|
||||
.RemoveInstructions(10)
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class uGUI_OptionsPanel_AddBindings_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((uGUI_OptionsPanel t) => t.AddBindings(default(int), default(GameInput.Device)));
|
||||
|
||||
public static void Postfix(uGUI_OptionsPanel __instance, int tabIndex, GameInput.Device device)
|
||||
{
|
||||
KeyBindingManager keyBindingManager = new();
|
||||
if (device == GameInput.Device.Keyboard)
|
||||
{
|
||||
foreach (KeyBinding keyBinding in keyBindingManager.KeyboardKeyBindings)
|
||||
{
|
||||
__instance.AddBindingOption(tabIndex, keyBinding.Label, keyBinding.Device, keyBinding.Button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent
|
||||
{
|
||||
public partial class uGUI_OptionsPanel_AddTabs_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((uGUI_OptionsPanel t) => t.AddTabs());
|
||||
private static NitroxSettingsManager nitroxSettingsManager;
|
||||
|
||||
public static void Postfix(uGUI_OptionsPanel __instance)
|
||||
{
|
||||
nitroxSettingsManager ??= Resolve<NitroxSettingsManager>(true);
|
||||
int tabIndex = __instance.AddTab("Nitrox");
|
||||
foreach (KeyValuePair<string, List<NitroxSettingsManager.Setting>> settingEntries in nitroxSettingsManager.NitroxSettings)
|
||||
{
|
||||
__instance.AddHeading(tabIndex, settingEntries.Key);
|
||||
foreach (NitroxSettingsManager.Setting setting in settingEntries.Value)
|
||||
{
|
||||
switch (setting.SettingType)
|
||||
{
|
||||
case NitroxSettingsManager.SettingType.TOGGLE:
|
||||
__instance.AddToggleOption(tabIndex, setting.Label, setting.GetValue<bool>(), (UnityAction<bool>)setting.Callback);
|
||||
break;
|
||||
case NitroxSettingsManager.SettingType.SLIDER:
|
||||
__instance.AddSliderOption(tabIndex, setting.Label, setting.GetValue<float>(), setting.SliderMinValue, setting.SliderMaxValue, setting.SliderDefaultValue, setting.SliderStep, (UnityAction<float>)setting.Callback, setting.LabelMode, setting.FloatFormat, setting.Tooltip);
|
||||
break;
|
||||
case NitroxSettingsManager.SettingType.LIST:
|
||||
__instance.AddChoiceOption(tabIndex, setting.Label, setting.ListItems, setting.GetValue<int>(), (UnityAction<int>)setting.Callback);
|
||||
break;
|
||||
case NitroxSettingsManager.SettingType.BUTTON:
|
||||
__instance.AddButton(tabIndex, setting.Label, (UnityAction)setting.Callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user