using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using FishNet.Connection;
using FishNet.Managing.Scened;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Scripting;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("fred_plugin_2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("fred_plugin_2")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("a0650051-5c69-4a23-b28a-c815791fa0dc")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace FredLevelPlugin;
[Serializable]
[Preserve]
public class ScenesJson
{
public BundleSpec[] bundles;
}
[Serializable]
[Preserve]
public class BundleSpec
{
public string name;
public string[] scenes;
public string displayPrefix;
}
[BepInPlugin("com.fred.fredlevel", "Fred Level Plugin", "2.0.0")]
public class Plugin : BaseUnityPlugin
{
internal class SceneEntry
{
public string SceneName;
public string BundleDiskPath;
public string SourcePack;
public string DisplayPrefix;
public string ScenePathInBundle;
public bool TriedResolvePath;
}
public const string PluginGuid = "com.fred.fredlevel";
public const string PluginName = "Fred Level Plugin";
public const string PluginVersion = "2.0.0";
internal static ManualLogSource LogSrc;
internal static Harmony Harmony;
internal static readonly Dictionary<string, SceneEntry> SceneRegistry = new Dictionary<string, SceneEntry>(StringComparer.OrdinalIgnoreCase);
internal static readonly Dictionary<string, AssetBundle> LoadedBundles = new Dictionary<string, AssetBundle>(StringComparer.OrdinalIgnoreCase);
internal static int NetSceneLoadDepth = 0;
internal static string AssetBundlesRoot;
private void Awake()
{
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Expected O, but got Unknown
LogSrc = ((BaseUnityPlugin)this).Logger;
try
{
string path = Directory.GetParent(Paths.PluginPath)?.FullName ?? Paths.PluginPath;
AssetBundlesRoot = Path.Combine(path, "Assets", "AssetBundles");
}
catch
{
AssetBundlesRoot = Path.Combine(Application.dataPath, "Assets", "AssetBundles");
}
ScanScenePacks();
DebugDumpRegistry(tryResolve: true);
Harmony = new Harmony("com.fred.fredlevel");
Harmony.PatchAll(typeof(Plugin).Assembly);
Log($"Initialized. Registered {SceneRegistry.Count} scene(s).");
}
private static void DebugDumpRegistry(bool tryResolve)
{
Log($"[Debug] SceneRegistry has {SceneRegistry.Count} item(s).");
foreach (KeyValuePair<string, SceneEntry> item in SceneRegistry)
{
SceneEntry value = item.Value;
bool flag = (tryResolve ? EnsureScenePathResolved(value) : (!string.IsNullOrEmpty(value.ScenePathInBundle)));
string text = (string.IsNullOrEmpty(value.BundleDiskPath) ? "(none)" : Path.GetFileName(value.BundleDiskPath));
Log("[Debug] '" + value.SceneName + "' from pack '" + value.SourcePack + "' -> bundle '" + text + "' -> " + (flag ? value.ScenePathInBundle : "UNRESOLVED"));
}
}
internal static void Log(string msg)
{
if (LogSrc != null)
{
LogSrc.LogInfo((object)("[FredLevel] " + msg));
}
else
{
Debug.Log((object)("[FredLevel] " + msg));
}
}
internal static void Warn(string msg)
{
if (LogSrc != null)
{
LogSrc.LogWarning((object)("[FredLevel] " + msg));
}
else
{
Debug.LogWarning((object)("[FredLevel] " + msg));
}
}
internal static void Error(string msg, Exception e = null)
{
if (LogSrc != null)
{
LogSrc.LogError((object)("[FredLevel] " + msg + ((e != null) ? (" :: " + e) : "")));
}
else
{
Debug.LogError((object)("[FredLevel] " + msg + ((e != null) ? (" :: " + e) : "")));
}
}
private static void ScanScenePacks()
{
try
{
if (!Directory.Exists(Paths.PluginPath))
{
Warn("Plugins path not found: " + Paths.PluginPath);
return;
}
int num = 0;
string text = Path.Combine(Paths.PluginPath, "scenes.json");
if (File.Exists(text))
{
num++;
ParseScenesJson(text, "(plugins root)");
}
string[] directories = Directory.GetDirectories(Paths.PluginPath, "*", SearchOption.TopDirectoryOnly);
string[] array = directories;
foreach (string text2 in array)
{
string text3 = FindScenesJson(text2);
if (text3 != null)
{
num++;
ParseScenesJson(text3, new DirectoryInfo(text2).Name);
}
}
Log($"Scan complete. Found {num} scenes.json file(s).");
}
catch (Exception e)
{
Error("ScanScenePacks failed.", e);
}
}
private static void ParseScenesJson(string scenesJsonPath, string sourcePackName)
{
try
{
string text = File.ReadAllText(scenesJsonPath, Encoding.UTF8);
ScenesJson scenesJson = JsonUtility.FromJson<ScenesJson>(text);
if (scenesJson == null || scenesJson.bundles == null || scenesJson.bundles.Length == 0)
{
if (scenesJson == null)
{
Warn($"FromJson returned null for {scenesJsonPath} (length={text.Length}). Trying fallback parser.");
}
else
{
Warn("Parsed but no bundles array in " + scenesJsonPath + ". Trying fallback parser.");
}
scenesJson = FallbackParseScenes(text);
if (scenesJson == null || scenesJson.bundles == null || scenesJson.bundles.Length == 0)
{
Warn("Fallback JSON parse also found no bundles in " + scenesJsonPath + ".");
return;
}
Log($"[Fallback JSON] Parsed {scenesJson.bundles.Length} bundle(s) from {scenesJsonPath}.");
}
BundleSpec[] bundles = scenesJson.bundles;
foreach (BundleSpec bundleSpec in bundles)
{
if (bundleSpec == null)
{
continue;
}
if (string.IsNullOrWhiteSpace(bundleSpec.name))
{
Warn("A bundle entry in " + scenesJsonPath + " has no 'name'. Skipping.");
continue;
}
if (bundleSpec.scenes == null || bundleSpec.scenes.Length == 0)
{
Warn("Bundle '" + bundleSpec.name + "' in " + scenesJsonPath + " has no 'scenes'. Skipping.");
continue;
}
string text2 = ResolveBundleOnDisk(bundleSpec.name);
if (string.IsNullOrEmpty(text2) || !File.Exists(text2))
{
Warn("Bundle file not found for '" + bundleSpec.name + "'. Looking under: " + AssetBundlesRoot);
continue;
}
string[] scenes = bundleSpec.scenes;
foreach (string text3 in scenes)
{
if (!string.IsNullOrWhiteSpace(text3))
{
SceneEntry sceneEntry = new SceneEntry
{
SceneName = text3.Trim(),
BundleDiskPath = text2,
SourcePack = sourcePackName,
DisplayPrefix = (bundleSpec.displayPrefix ?? "")
};
if (!SceneRegistry.ContainsKey(sceneEntry.SceneName))
{
SceneRegistry[sceneEntry.SceneName] = sceneEntry;
continue;
}
Warn("Duplicate scene name '" + sceneEntry.SceneName + "' encountered (pack '" + sceneEntry.SourcePack + "'). Keeping the first registration.");
}
}
}
}
catch (Exception e)
{
Error("Failed parsing " + scenesJsonPath, e);
}
}
private static string FindScenesJson(string subdir)
{
try
{
foreach (string item in Directory.EnumerateFiles(subdir, "*", SearchOption.TopDirectoryOnly))
{
string fileName = Path.GetFileName(item);
if (string.Equals(fileName, "scenes.json", StringComparison.OrdinalIgnoreCase))
{
return item;
}
}
}
catch
{
}
return null;
}
private static string ResolveBundleOnDisk(string nameFromJson)
{
string text = nameFromJson.Trim();
if (Path.IsPathRooted(text))
{
return text;
}
string text2 = Path.Combine(AssetBundlesRoot, text);
if (File.Exists(text2))
{
return text2;
}
string[] array = new string[2] { ".bundle", ".unity3d" };
string[] array2 = array;
foreach (string text3 in array2)
{
text2 = Path.Combine(AssetBundlesRoot, text + text3);
if (File.Exists(text2))
{
return text2;
}
}
try
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
text2 = Path.Combine(AssetBundlesRoot, fileNameWithoutExtension);
if (File.Exists(text2))
{
return text2;
}
string[] array3 = array;
foreach (string text4 in array3)
{
text2 = Path.Combine(AssetBundlesRoot, fileNameWithoutExtension + text4);
if (File.Exists(text2))
{
return text2;
}
}
}
catch
{
}
return null;
}
internal static bool EnsureScenePathResolved(SceneEntry entry)
{
if (entry == null)
{
return false;
}
if (entry.TriedResolvePath && !string.IsNullOrEmpty(entry.ScenePathInBundle))
{
return true;
}
entry.TriedResolvePath = true;
try
{
if (string.IsNullOrEmpty(entry.BundleDiskPath) || !File.Exists(entry.BundleDiskPath))
{
Warn("Bundle missing for scene '" + entry.SceneName + "'. Path: " + entry.BundleDiskPath);
return false;
}
if (!LoadedBundles.TryGetValue(entry.BundleDiskPath, out var value) || (Object)(object)value == (Object)null)
{
value = AssetBundle.LoadFromFile(entry.BundleDiskPath);
if ((Object)(object)value == (Object)null)
{
Warn("Failed to load AssetBundle: " + entry.BundleDiskPath);
return false;
}
LoadedBundles[entry.BundleDiskPath] = value;
}
string[] allScenePaths = value.GetAllScenePaths();
if (allScenePaths == null || allScenePaths.Length == 0)
{
Warn("No scenes in bundle: " + entry.BundleDiskPath);
return false;
}
foreach (string text in allScenePaths)
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
if (string.Equals(fileNameWithoutExtension, entry.SceneName, StringComparison.OrdinalIgnoreCase))
{
entry.ScenePathInBundle = text;
Log("Resolved '" + entry.SceneName + "' -> '" + entry.ScenePathInBundle + "' (bundle: " + Path.GetFileName(entry.BundleDiskPath) + ")");
return true;
}
}
Warn("Bundle '" + Path.GetFileName(entry.BundleDiskPath) + "' does not contain a scene named '" + entry.SceneName + "'. Available: " + string.Join(", ", allScenePaths.Select((string s) => Path.GetFileNameWithoutExtension(s))));
}
catch (Exception e)
{
Error("EnsureScenePathResolved failed for '" + entry.SceneName + "'.", e);
}
return false;
}
internal static void InjectAllMaps(MapsManager mm)
{
//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
//IL_0104: Unknown result type (might be due to invalid IL or missing references)
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_0112: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Expected O, but got Unknown
//IL_015c: Unknown result type (might be due to invalid IL or missing references)
//IL_0161: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)mm == (Object)null)
{
return;
}
try
{
if (SceneRegistry.Count == 0)
{
return;
}
List<Map> list = new List<Map>();
if (mm.allMaps != null)
{
list.AddRange(mm.allMaps);
}
if (mm.allMapsDict == null)
{
mm.allMapsDict = new Dictionary<string, Map>();
}
object? value = AccessTools.Field(typeof(MapsManager), "mapInstance").GetValue(mm);
GameObject val = (GameObject)((value is GameObject) ? value : null);
object? value2 = AccessTools.Field(typeof(MapsManager), "standardMapParent").GetValue(mm);
Transform val2 = (Transform)((value2 is Transform) ? value2 : null);
foreach (KeyValuePair<string, SceneEntry> item in SceneRegistry)
{
string key = item.Key;
if (mm.allMapsDict.ContainsKey(key))
{
continue;
}
Map val3 = new Map
{
mapName = key,
index = list.Count,
isDlcExclusive = false,
isAltMap = false,
isUnlocked = true,
isSelected = false,
mapInstance = null
};
list.Add(val3);
mm.allMapsDict[val3.mapName] = val3;
if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null)
{
GameObject val4 = Object.Instantiate<GameObject>(val, val2.position, Quaternion.identity, val2);
MapInstance component = val4.GetComponent<MapInstance>();
if ((Object)(object)component != (Object)null)
{
val3.mapInstance = component;
component.name = val3.mapName;
component.selected = false;
val4.SetActive(true);
component.UpdateUI();
}
}
else
{
Warn("Could not access 'mapInstance' prefab or 'standardMapParent' via reflection. UI entry may not appear.");
}
}
mm.allMaps = list.ToArray();
List<int> list2 = new List<int>();
if (mm.unlockedMaps != null)
{
list2.AddRange(mm.unlockedMaps);
}
for (int i = 0; i < list.Count; i++)
{
if (!list2.Contains(i))
{
list2.Add(i);
}
}
mm.unlockedMaps = list2.ToArray();
mm.SortMapsFromMapInstanceName();
Log($"Injected {SceneRegistry.Count} custom map(s) into MapsManager.");
}
catch (Exception e)
{
Error("InjectAllMaps failed.", e);
}
}
private static ScenesJson FallbackParseScenes(string json)
{
try
{
if (!(SimpleJson.Parse(json) is Dictionary<string, object> dictionary))
{
return null;
}
if (!dictionary.TryGetValue("bundles", out var value))
{
ScenesJson scenesJson = new ScenesJson();
scenesJson.bundles = new BundleSpec[0];
return scenesJson;
}
if (!(value is List<object> list))
{
ScenesJson scenesJson = new ScenesJson();
scenesJson.bundles = new BundleSpec[0];
return scenesJson;
}
List<BundleSpec> list2 = new List<BundleSpec>();
foreach (object item in list)
{
if (!(item is Dictionary<string, object> dictionary2))
{
continue;
}
object value2;
string text = (dictionary2.TryGetValue("name", out value2) ? (value2 as string) : null);
object value3;
List<object> list3 = (dictionary2.TryGetValue("scenes", out value3) ? (value3 as List<object>) : null);
object value4;
string displayPrefix = (dictionary2.TryGetValue("displayPrefix", out value4) ? (value4 as string) : null);
if (string.IsNullOrWhiteSpace(text) || list3 == null || list3.Count == 0)
{
continue;
}
List<string> list4 = new List<string>();
foreach (object item2 in list3)
{
string text2 = item2 as string;
if (!string.IsNullOrWhiteSpace(text2))
{
list4.Add(text2);
}
}
if (list4.Count != 0)
{
list2.Add(new BundleSpec
{
name = text,
scenes = list4.ToArray(),
displayPrefix = displayPrefix
});
}
}
return new ScenesJson
{
bundles = list2.ToArray()
};
}
catch (Exception e)
{
Error("FallbackParseScenes failed.", e);
return null;
}
}
}
[HarmonyPatch(typeof(MapsManager), "InitMaps")]
internal static class MapsManager_InitMaps_Postfix
{
public static void Postfix(MapsManager __instance)
{
try
{
Plugin.InjectAllMaps(__instance);
}
catch (Exception e)
{
Plugin.Error("InitMaps postfix failed.", e);
}
}
}
[HarmonyPatch(typeof(SceneLoader), "LoadScene", new Type[] { typeof(string) })]
internal static class SceneLoader_LoadScene_Prefix
{
public static bool Prefix(ref string sceneName)
{
try
{
if (string.IsNullOrEmpty(sceneName))
{
return true;
}
if (Plugin.SceneRegistry.TryGetValue(sceneName, out var value))
{
if (Plugin.NetSceneLoadDepth > 0)
{
Plugin.Log("Skipping duplicate local LoadScene('" + sceneName + "') because FishNet is already loading it.");
return false;
}
if (Plugin.EnsureScenePathResolved(value))
{
Plugin.Log("Redirecting SceneLoader.LoadScene from '" + sceneName + "' to bundle path '" + value.ScenePathInBundle + "'.");
sceneName = value.ScenePathInBundle;
}
else
{
Plugin.Warn("Bundle scene path not found for '" + sceneName + "'. Falling back; load may fail.");
}
}
}
catch (Exception e)
{
Plugin.Error("SceneLoader_LoadScene_Prefix failed.", e);
}
return true;
}
}
internal static class FishNetSceneRedirect
{
private static bool _reentry;
private static List<string> GetRequestedSceneNames(SceneLoadData data)
{
List<string> list = new List<string>();
if (data == null)
{
return list;
}
try
{
PropertyInfo property = ((object)data).GetType().GetProperty("Scenes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
object value = property.GetValue(data, null);
if (value is string[] array && array.Length != 0)
{
list.AddRange(array);
}
}
}
catch
{
}
try
{
PropertyInfo property2 = ((object)data).GetType().GetProperty("SceneNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property2 != null)
{
object value2 = property2.GetValue(data, null);
if (value2 is IEnumerable<string> collection)
{
list.AddRange(collection);
}
}
}
catch
{
}
try
{
FieldInfo field = ((object)data).GetType().GetField("SceneLookupDatas", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null && field.GetValue(data) is IEnumerable enumerable)
{
foreach (object item in enumerable)
{
PropertyInfo property3 = item.GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property3 != null)
{
string text = property3.GetValue(item, null) as string;
if (!string.IsNullOrEmpty(text))
{
list.Add(text);
}
}
}
}
}
catch
{
}
return list;
}
private static SceneLoadData CloneWithReplacedScenes(SceneLoadData original, List<string> newScenePaths)
{
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Expected O, but got Unknown
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Expected O, but got Unknown
//IL_0130: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Expected O, but got Unknown
//IL_0180: Unknown result type (might be due to invalid IL or missing references)
//IL_0186: Expected O, but got Unknown
SceneLoadData val = null;
try
{
ConstructorInfo constructor = typeof(SceneLoadData).GetConstructor(new Type[1] { typeof(string[]) });
if (constructor != null)
{
val = (SceneLoadData)constructor.Invoke(new object[1] { newScenePaths.ToArray() });
}
else
{
ConstructorInfo constructor2 = typeof(SceneLoadData).GetConstructor(new Type[1] { typeof(string) });
if (constructor2 != null)
{
val = (SceneLoadData)constructor2.Invoke(new object[1] { newScenePaths.FirstOrDefault() ?? string.Empty });
}
}
if (val == null)
{
throw new MissingMethodException("SceneLoadData ctor(string[]) and ctor(string) not found.");
}
}
catch
{
ConstructorInfo[] constructors = ((object)original).GetType().GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
ConstructorInfo[] array = constructors;
foreach (ConstructorInfo constructorInfo in array)
{
ParameterInfo[] parameters = constructorInfo.GetParameters();
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string[]))
{
val = (SceneLoadData)constructorInfo.Invoke(new object[1] { newScenePaths.ToArray() });
break;
}
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
{
val = (SceneLoadData)constructorInfo.Invoke(new object[1] { newScenePaths.FirstOrDefault() ?? string.Empty });
break;
}
}
if (val == null)
{
throw;
}
}
try
{
PropertyInfo property = ((object)original).GetType().GetProperty("ReplaceScenes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
object value = property.GetValue(original, null);
PropertyInfo property2 = ((object)val).GetType().GetProperty("ReplaceScenes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property2 != null)
{
property2.SetValue(val, value, null);
}
}
PropertyInfo property3 = ((object)original).GetType().GetProperty("Options", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property3 != null)
{
object value2 = property3.GetValue(original, null);
PropertyInfo property4 = ((object)val).GetType().GetProperty("Options", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property4 != null)
{
property4.SetValue(val, value2, null);
}
}
PropertyInfo property5 = ((object)original).GetType().GetProperty("PreferredActiveScene", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property5 != null)
{
object value3 = property5.GetValue(original, null);
PropertyInfo property6 = ((object)val).GetType().GetProperty("PreferredActiveScene", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property6 != null)
{
property6.SetValue(val, value3, null);
}
}
PropertyInfo property7 = ((object)original).GetType().GetProperty("MovedNetworkObjects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property7 != null)
{
object value4 = property7.GetValue(original, null);
PropertyInfo property8 = ((object)val).GetType().GetProperty("MovedNetworkObjects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property8 != null)
{
property8.SetValue(val, value4, null);
}
}
}
catch
{
}
return val;
}
private static bool TryBuildReplacement(SceneLoadData sceneLoadData, out SceneLoadData replacement)
{
replacement = null;
List<string> requestedSceneNames = GetRequestedSceneNames(sceneLoadData);
if (requestedSceneNames.Count == 0)
{
return false;
}
bool flag = false;
List<string> list = new List<string>(requestedSceneNames.Count);
foreach (string item in requestedSceneNames)
{
if (Plugin.SceneRegistry.TryGetValue(item, out var value) && Plugin.EnsureScenePathResolved(value))
{
list.Add(value.ScenePathInBundle);
flag = true;
}
else
{
list.Add(item);
}
}
if (!flag)
{
return false;
}
replacement = CloneWithReplacedScenes(sceneLoadData, list);
return replacement != null;
}
internal static bool TryRedirect(SceneManager manager, SceneLoadData data, NetworkConnection ownerOrNull)
{
if (_reentry)
{
return false;
}
if (!TryBuildReplacement(data, out var replacement))
{
return false;
}
_reentry = true;
Plugin.NetSceneLoadDepth++;
try
{
if (ownerOrNull == (NetworkConnection)null)
{
Plugin.Log("Redirecting FishNet LoadGlobalScenes to paths from bundles.");
manager.LoadGlobalScenes(replacement);
}
else
{
Plugin.Log("Redirecting FishNet LoadConnectionScenes to paths from bundles.");
manager.LoadConnectionScenes(ownerOrNull, replacement);
}
}
finally
{
Plugin.NetSceneLoadDepth--;
_reentry = false;
}
return true;
}
}
[HarmonyPatch]
internal static class FishNetSceneManagerPatches
{
[HarmonyPatch(typeof(SceneManager), "LoadGlobalScenes", new Type[] { typeof(SceneLoadData) })]
[HarmonyPrefix]
private static bool LoadGlobalScenes_Prefix(SceneManager __instance, [HarmonyArgument(0)] SceneLoadData sceneLoadData)
{
try
{
if (FishNetSceneRedirect.TryRedirect(__instance, sceneLoadData, null))
{
return false;
}
}
catch (Exception e)
{
Plugin.Error("FishNet LoadGlobalScenes prefix failed.", e);
}
return true;
}
[HarmonyPatch(typeof(SceneManager), "LoadConnectionScenes", new Type[]
{
typeof(NetworkConnection),
typeof(SceneLoadData)
})]
[HarmonyPrefix]
private static bool LoadConnectionScenes_Prefix(SceneManager __instance, [HarmonyArgument(0)] NetworkConnection connection, [HarmonyArgument(1)] SceneLoadData sceneLoadData)
{
try
{
if (FishNetSceneRedirect.TryRedirect(__instance, sceneLoadData, connection))
{
return false;
}
}
catch (Exception e)
{
Plugin.Error("FishNet LoadConnectionScenes prefix failed.", e);
}
return true;
}
}
internal static class SimpleJson
{
private class Parser
{
private readonly string _s;
private int _i;
private char Peek => _s[_i];
private bool End => _i >= _s.Length;
public Parser(string s)
{
_s = s ?? "";
_i = 0;
}
public object ParseValue()
{
SkipWs();
if (End)
{
return null;
}
switch (Peek)
{
case '{':
return ParseObject();
case '[':
return ParseArray();
case '"':
return ParseString();
default:
if (Match("true"))
{
return true;
}
if (Match("false"))
{
return false;
}
if (Match("null"))
{
return null;
}
return ParseNumberOrBare();
}
}
private Dictionary<string, object> ParseObject()
{
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Expect('{');
SkipWs();
if (Try('}'))
{
return dictionary;
}
while (true)
{
SkipWs();
string key = ParseString();
SkipWs();
Expect(':');
object value = ParseValue();
dictionary[key] = value;
SkipWs();
if (Try('}'))
{
break;
}
Expect(',');
}
return dictionary;
}
private List<object> ParseArray()
{
List<object> list = new List<object>();
Expect('[');
SkipWs();
if (Try(']'))
{
return list;
}
while (true)
{
object item = ParseValue();
list.Add(item);
SkipWs();
if (Try(']'))
{
break;
}
Expect(',');
}
return list;
}
private string ParseString()
{
Expect('"');
StringBuilder stringBuilder = new StringBuilder();
while (!End)
{
char c = Next();
switch (c)
{
case '\\':
if (End)
{
break;
}
c = Next();
switch (c)
{
case '"':
stringBuilder.Append('"');
break;
case '\\':
stringBuilder.Append('\\');
break;
case '/':
stringBuilder.Append('/');
break;
case 'b':
stringBuilder.Append('\b');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'u':
if (_i + 4 <= _s.Length)
{
string s = _s.Substring(_i, 4);
if (ushort.TryParse(s, NumberStyles.HexNumber, null, out var result))
{
stringBuilder.Append((char)result);
_i += 4;
}
}
break;
default:
stringBuilder.Append(c);
break;
}
continue;
default:
stringBuilder.Append(c);
continue;
case '"':
break;
}
break;
}
return stringBuilder.ToString();
}
private object ParseNumberOrBare()
{
int i = _i;
while (!End)
{
char peek = Peek;
if (char.IsWhiteSpace(peek) || peek == ',' || peek == ']' || peek == '}')
{
break;
}
_i++;
}
string text = _s.Substring(i, _i - i);
if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return text;
}
private void SkipWs()
{
while (!End && char.IsWhiteSpace(Peek))
{
_i++;
}
}
private bool Match(string lit)
{
SkipWs();
if (_i + lit.Length > _s.Length)
{
return false;
}
for (int i = 0; i < lit.Length; i++)
{
if (_s[_i + i] != lit[i])
{
return false;
}
}
_i += lit.Length;
return true;
}
private void Expect(char ch)
{
SkipWs();
if (End || _s[_i] != ch)
{
throw new Exception($"Expected '{ch}' at {_i}");
}
_i++;
}
private bool Try(char ch)
{
SkipWs();
if (!End && _s[_i] == ch)
{
_i++;
return true;
}
return false;
}
private char Next()
{
return _s[_i++];
}
}
public static object Parse(string json)
{
return new Parser(json).ParseValue();
}
}