using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Il2CppInterop.Runtime.Injection; using MelonLoader; using MelonLoader.Preferences; using SledPixelate; using SledPixelate.Util; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SledPixelate")] [assembly: AssemblyFileVersion("0.1.0")] [assembly: MelonInfo(typeof(Mod), "SledPixelate", "0.1.0", "Natebag", null)] [assembly: MelonGame("The Sledding Corporation", "Sledding Game")] [assembly: MelonColor(255, 0, 255, 102)] [assembly: MelonAuthorColor(255, 255, 140, 60)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyVersion("0.1.0.0")] namespace SledPixelate { public sealed class Mod : MelonMod { public const string Name = "SledPixelate"; public const string Version = "0.1.0"; public const string Author = "Natebag"; private static int _divisor = 4; private static bool _enabled; private static MelonPreferences_Category _cat; private static MelonPreferences_Entry _divisorPref; private static MelonPreferences_Entry _enabledPref; private static MelonPreferences_Entry _hotkeyPref; private static Camera _camBound; private static RenderTexture _rt; public override void OnInitializeMelon() { SafeLog.Bind(((MelonBase)this).LoggerInstance); try { ClassInjector.RegisterTypeInIl2Cpp(); _cat = MelonPreferences.CreateCategory("SledPixelate"); _divisorPref = _cat.CreateEntry("Divisor", 4, "Divisor", "Render at 1 / divisor resolution. 1 = native. 4 = nice retro. 8 = chunky.", false, false, (ValueValidator)null, (string)null); _enabledPref = _cat.CreateEntry("EnabledOnStart", false, "EnabledOnStart", "Whether the pixelate filter starts ON. Default false — UI menus may render incorrectly while active. Toggle in-game with the hotkey.", false, false, (ValueValidator)null, (string)null); _hotkeyPref = _cat.CreateEntry("HotkeyKeyCode", 291, "HotkeyKeyCode", "UnityEngine.KeyCode integer for the toggle hotkey. F10 = 291.", false, false, (ValueValidator)null, (string)null); _divisor = Math.Max(1, _divisorPref.Value); _enabled = _enabledPref.Value; SafeLog.Info($"SledPixelate v{"0.1.0"} ready — divisor={_divisor} enabledOnStart={_enabled} hotkey={_hotkeyPref.Value}"); } catch (Exception value) { ((MelonBase)this).LoggerInstance.Error($"SledPixelate init failed: {value}"); } } public override void OnUpdate() { try { if (_hotkeyPref != null && Input.GetKeyDown((KeyCode)_hotkeyPref.Value)) { _enabled = !_enabled; SafeLog.Info("SledPixelate: toggled " + (_enabled ? "ON" : "OFF") + " via hotkey"); if (!_enabled) { Detach(); } } if (_divisorPref != null && _divisor != _divisorPref.Value) { _divisor = Math.Max(1, _divisorPref.Value); if (_enabled) { RebuildRT(); } } if (!_enabled) { return; } Camera main = Camera.main; if (!((Object)(object)main == (Object)null)) { int num = Math.Max(1, Screen.width / _divisor); if ((Object)(object)_camBound != (Object)(object)main || (Object)(object)_rt == (Object)null || ((Texture)_rt).width != num) { BindCamera(main); } } } catch (Exception ex) { SafeLog.Error("SledPixelate.OnUpdate", ex); } } private static void BindCamera(Camera cam) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Detach(); _camBound = cam; RebuildRT(); cam.targetTexture = _rt; GameObject val = new GameObject("SledPixelateBlitter"); Object.DontDestroyOnLoad((Object)val); val.AddComponent(); SafeLog.Info($"SledPixelate: bound to '{((Object)cam).name}' at {((Texture)_rt).width}x{((Texture)_rt).height}"); } private static void RebuildRT() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown if ((Object)(object)_rt != (Object)null) { _rt.Release(); Object.Destroy((Object)(object)_rt); _rt = null; } int num = Math.Max(1, Screen.width / _divisor); int num2 = Math.Max(1, Screen.height / _divisor); _rt = new RenderTexture(num, num2, 24); ((Texture)_rt).filterMode = (FilterMode)0; _rt.Create(); if ((Object)(object)_camBound != (Object)null) { _camBound.targetTexture = _rt; } } private static void Detach() { if ((Object)(object)_camBound != (Object)null) { _camBound.targetTexture = null; _camBound = null; } GameObject val = GameObject.Find("SledPixelateBlitter"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if ((Object)(object)_rt != (Object)null) { _rt.Release(); Object.Destroy((Object)(object)_rt); _rt = null; } } internal static RenderTexture GetRT() { return _rt; } } internal sealed class PixelateBlitter : MonoBehaviour { public PixelateBlitter(IntPtr ptr) : base(ptr) { } private void OnGUI() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) RenderTexture rT = Mod.GetRT(); if (!((Object)(object)rT == (Object)null) && (int)Event.current.type == 7) { GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)rT, (ScaleMode)0, false); } } } } namespace SledPixelate.Util { internal static class Refl { private static readonly Dictionary _typeCache = new Dictionary(); private static bool _loggedAssemblies = false; private static MethodInfo _il2cppCastGeneric; public static Type FindType(string assemblyHint, string fullName) { if (_typeCache.TryGetValue(fullName, out var value)) { return value; } try { string[] array = new string[2] { fullName, "Il2Cpp." + fullName }; string[] array2 = new string[3] { assemblyHint, "Il2Cpp" + assemblyHint, assemblyHint + "-firstpass" }; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name = assembly.GetName().Name; string[] array3 = array2; foreach (string text in array3) { if (name != text) { continue; } string[] array4 = array; foreach (string name2 in array4) { Type type = assembly.GetType(name2); if (type != null) { _typeCache[fullName] = type; return type; } } } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { string[] array3 = array; foreach (string name3 in array3) { Type type2; try { type2 = assembly2.GetType(name3); } catch { continue; } if (type2 != null) { _typeCache[fullName] = type2; return type2; } } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly3 in assemblies) { Type[] types; try { types = assembly3.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } catch { continue; } string text2 = fullName; int num = fullName.LastIndexOf('.'); if (num >= 0 && num < fullName.Length - 1) { text2 = fullName.Substring(num + 1); } Type[] array5 = types; foreach (Type type3 in array5) { if (!(type3 == null) && (type3.FullName == fullName || type3.FullName == "Il2Cpp." + fullName || (type3.FullName != null && type3.FullName.EndsWith("." + fullName)) || type3.Name == fullName || type3.Name == text2)) { _typeCache[fullName] = type3; SafeLog.Info($"Refl.FindType: '{fullName}' resolved via full scan as '{type3.FullName}' in {assembly3.GetName().Name}"); return type3; } } } if (!_loggedAssemblies) { SafeLog.Warn("Refl.FindType: '" + fullName + "' not found. Loaded assemblies:"); assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly4 in assemblies) { SafeLog.Warn(" - " + assembly4.GetName().Name); } _loggedAssemblies = true; } return null; } catch (Exception ex2) { SafeLog.Error($"Refl.FindType({assemblyHint},{fullName})", ex2); return null; } } public static MethodInfo Method(Type t, string name, params Type[] argTypes) { if (t == null) { return null; } try { BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; if (argTypes.Length == 0) { MethodInfo[] methods = t.GetMethods(bindingAttr); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == name) { return methodInfo; } } return null; } return t.GetMethod(name, bindingAttr, null, argTypes, null); } catch (Exception ex) { SafeLog.Error($"Refl.Method({t.Name}.{name})", ex); return null; } } public static FieldInfo Field(Type t, string name) { if (t == null) { return null; } try { return t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch (Exception ex) { SafeLog.Error($"Refl.Field({t.Name}.{name})", ex); return null; } } public static object CastToIl2Cpp(object obj, Type targetType) { if (obj == null || targetType == null) { return null; } if (targetType.IsInstanceOfType(obj)) { return obj; } try { if (_il2cppCastGeneric == null) { Type type = FindType("Il2CppInterop.Runtime", "Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"); if (type == null) { type = obj.GetType(); } while (type != null) { MethodInfo method = type.GetMethod("Cast", BindingFlags.Instance | BindingFlags.Public); if (method != null && method.IsGenericMethodDefinition) { _il2cppCastGeneric = method; break; } type = type.BaseType; } } if (_il2cppCastGeneric == null) { return null; } return _il2cppCastGeneric.MakeGenericMethod(targetType).Invoke(obj, null); } catch (Exception ex) { SafeLog.Error("Refl.CastToIl2Cpp(" + targetType.FullName + ")", ex); return null; } } } internal static class SafeLog { private static Instance _logger; private static readonly LinkedList _ring = new LinkedList(); private const int RingMax = 200; public static IEnumerable Recent => _ring; public static void Bind(Instance logger) { _logger = logger; } public static void Info(string msg) { Instance logger = _logger; if (logger != null) { logger.Msg(msg); } Push("INFO " + msg); } public static void Warn(string msg) { Instance logger = _logger; if (logger != null) { logger.Warning(msg); } Push("WARN " + msg); } public static void Error(string msg, Exception ex = null) { if (_logger != null) { if (ex != null) { _logger.Error($"{msg}: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}"); } else { _logger.Error(msg); } } Push("ERROR " + ((ex != null) ? (msg + ": " + ex.Message) : msg)); } private static void Push(string line) { _ring.AddLast(line); while (_ring.Count > 200) { _ring.RemoveFirst(); } } } }