Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Inkorporated v1.1.0
Inkorporated.dll
Decompiled 20 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.UI.CharacterCustomization; using Il2CppTMPro; using Inkorporated; using Inkorporated.Config; using Inkorporated.Content; using Inkorporated.Model; using Inkorporated.Registration; using Inkorporated.Shop; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using S1API.Rendering; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Inkorporated", "1.1.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Inkorporated")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "ModManager&PhoneApp" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Inkorporated")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+4b11ddb24c640398473cc451a6cb1843bf120d9a")] [assembly: AssemblyProduct("Inkorporated")] [assembly: AssemblyTitle("Inkorporated")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Inkorporated { public static class API { public static bool RegisterTattoo(string id, string displayName, TattooPlacement placement, Texture2D texture, float price = 0f, string source = "API") { return TattooRegistry.Add(new TattooDef { Id = id, DisplayName = (string.IsNullOrWhiteSpace(displayName) ? id : displayName), Placement = placement, Price = ((price < 0f) ? 0f : price), Texture = texture, Source = (string.IsNullOrWhiteSpace(source) ? "API" : source) }); } public static bool RegisterTattooFromFile(string id, string displayName, TattooPlacement placement, string pngPath, float price = 0f, string source = "API") { return TattooRegistry.Add(new TattooDef { Id = id, DisplayName = (string.IsNullOrWhiteSpace(displayName) ? id : displayName), Placement = placement, Price = ((price < 0f) ? 0f : price), PngPath = pngPath, Source = (string.IsNullOrWhiteSpace(source) ? "API" : source) }); } [MethodImpl(MethodImplOptions.NoInlining)] public static bool RegisterTattooFromResource(string id, string displayName, TattooPlacement placement, string resourceName, float price = 0f, string source = "API") { return RegisterTattooFromResource(id, displayName, placement, Assembly.GetCallingAssembly(), resourceName, price, source); } public static bool RegisterTattooFromResource(string id, string displayName, TattooPlacement placement, Assembly assembly, string resourceName, float price = 0f, string source = "API") { return TattooRegistry.Add(new TattooDef { Id = id, DisplayName = (string.IsNullOrWhiteSpace(displayName) ? id : displayName), Placement = placement, Price = ((price < 0f) ? 0f : price), ResourceAssembly = assembly, ResourceName = resourceName, Source = (string.IsNullOrWhiteSpace(source) ? "API" : source) }); } } public sealed class Core : MelonMod { public static Core Instance { get; private set; } public static Instance Log { get; private set; } public override void OnInitializeMelon() { Instance = this; Log = ((MelonBase)this).LoggerInstance; Preferences.Initialize(); ExamplePack.ExtractIfEnabled(); int value = TattooRegistry.AddRange(PackLoader.LoadAll()); try { ((MelonBase)this).HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); } catch (Exception ex) { Log.Warning("Harmony patch failed: " + ex.Message); } Log.Msg($"Inkorporated 1.1.0 - {value} pack tattoo(s) loaded ({TattooRegistry.AllDefs.Count} total). Shop injection armed."); Log.Msg("Drop packs in: " + PackLoader.PacksRoot); } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { ShopInjector.Reset(); } } } namespace Inkorporated.Shop { [HarmonyPatch(typeof(CharacterCustomizationCategory), "Awake")] internal static class CategoryAwakePatch { private static void Prefix(CharacterCustomizationCategory __instance) { ShopInjector.TryInject(__instance); } } internal static class ShopInjector { private static readonly HashSet<IntPtr> _processed = new HashSet<IntPtr>(); public static void Reset() { _processed.Clear(); } public static void TryInject(CharacterCustomizationCategory category) { try { if ((Object)(object)category == (Object)null || !_processed.Add(((Il2CppObjectBase)category).Pointer) || (Object)(object)((Component)category).GetComponentInParent<TattooShopUI>() == (Object)null) { return; } Il2CppArrayBase<CharacterCustomizationOption> componentsInChildren = ((Component)category).GetComponentsInChildren<CharacterCustomizationOption>(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return; } CharacterCustomizationOption val = PickTemplate(componentsInChildren); if ((Object)(object)val == (Object)null) { return; } List<string> list = new List<string>(); for (int i = 0; i < componentsInChildren.Length; i++) { string text = (((Object)(object)componentsInChildren[i] != (Object)null) ? componentsInChildren[i].Label : null); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } int num = 0; foreach (TattooDef allDef in TattooRegistry.AllDefs) { if (LabelsContain(list, TattooRegistry.CategoryToken(allDef.Placement)) && TattooRegistry.EnsureRegistered(allDef) && !HasLabel(componentsInChildren, allDef.ResourcePath) && CloneOption(category, val, allDef)) { num++; } } if (num <= 0) { return; } Transform parent = ((Component)val).transform.parent; if ((Object)(object)parent != (Object)null) { List<Transform> list2 = new List<Transform>(); for (int j = 0; j < parent.childCount; j++) { Transform child = parent.GetChild(j); if ((Object)(object)((Component)child).GetComponent<CharacterCustomizationOption>() == (Object)null) { list2.Add(child); } } foreach (Transform item in list2) { item.SetAsLastSibling(); } } Instance log = Core.Log; if (log != null) { log.Msg($"Injected {num} custom tattoo(s) into category '{category.CategoryName}'."); } } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Shop injection error: " + ex.Message); } } } private static CharacterCustomizationOption PickTemplate(Il2CppArrayBase<CharacterCustomizationOption> opts) { for (int i = 0; i < opts.Length; i++) { if ((Object)(object)opts[i] != (Object)null && ((Component)opts[i]).gameObject.activeSelf) { return opts[i]; } } if (opts.Length <= 0) { return null; } return opts[0]; } private static bool LabelsContain(List<string> labels, string token) { foreach (string label in labels) { if (label.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static bool HasLabel(Il2CppArrayBase<CharacterCustomizationOption> opts, string label) { if (label == null) { return false; } for (int i = 0; i < opts.Length; i++) { if ((Object)(object)opts[i] != (Object)null && string.Equals(opts[i].Label, label, StringComparison.Ordinal)) { return true; } } return false; } private static bool CloneOption(CharacterCustomizationCategory category, CharacterCustomizationOption template, TattooDef def) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) try { Transform parent = ((Component)template).transform.parent; GameObject val = ((Il2CppObjectBase)Object.Instantiate<GameObject>(((Component)template).gameObject, parent, false)).Cast<GameObject>(); val.transform.localScale = Vector3.one; ((Object)val).name = "Inkorporated_" + def.Source + "_" + def.Id; CharacterCustomizationOption component = val.GetComponent<CharacterCustomizationOption>(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return false; } component.Name = def.DisplayName; component.Label = def.ResourcePath; component.Price = def.Price; component.RequireLevel = false; if ((Object)(object)component.NameLabel != (Object)null) { ((TMP_Text)component.NameLabel).text = def.DisplayName; } if ((Object)(object)component.PriceLabel != (Object)null) { ((TMP_Text)component.PriceLabel).text = ((def.Price > 0f) ? Mathf.RoundToInt(def.Price).ToString() : "Free"); } if (!val.activeSelf) { val.SetActive(true); } return true; } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("Tattoo '" + def.Key + "': failed to create shop button - " + ex.Message); } return false; } } } } namespace Inkorporated.Registration { internal static class TattooRegistry { private static readonly List<TattooDef> _all = new List<TattooDef>(); private static readonly HashSet<string> _keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static IReadOnlyList<TattooDef> AllDefs => _all; public static bool Add(TattooDef def) { if (def == null || string.IsNullOrWhiteSpace(def.Id)) { return false; } if (!_keys.Add(def.Key)) { return false; } _all.Add(def); return true; } public static int AddRange(IEnumerable<TattooDef> defs) { int num = 0; if (defs == null) { return 0; } foreach (TattooDef def in defs) { if (Add(def)) { num++; } } return num; } public static string CategoryToken(TattooPlacement p) { return p switch { TattooPlacement.Chest => "/chest/", TattooPlacement.LeftArm => "/leftarm/", TattooPlacement.RightArm => "/rightarm/", TattooPlacement.Face => "/face/", _ => "/chest/", }; } private static string SourceLayer(TattooPlacement p) { return p switch { TattooPlacement.Chest => "Avatar/Layers/Tattoos/chest/Chest_Bird", TattooPlacement.LeftArm => "Avatar/Layers/Tattoos/leftarm/LeftArm_Web", TattooPlacement.RightArm => "Avatar/Layers/Tattoos/rightarm/RightArm_Web", TattooPlacement.Face => "Avatar/Layers/Tattoos/face/Face_Teardrop", _ => "Avatar/Layers/Tattoos/chest/Chest_Bird", }; } private static string TargetPath(TattooDef def) { string text = ((def.Placement == TattooPlacement.Face) ? "Face" : def.Placement.ToString().ToLowerInvariant()); return "Avatar/Layers/Tattoos/custom/" + text + "/" + Sanitize(def.Source) + "_" + Sanitize(def.Id); } public static bool EnsureRegistered(TattooDef def) { if (def == null) { return false; } if (def.ResourcePath != null) { return true; } try { Texture2D val = def.Texture; if ((Object)(object)val == (Object)null) { if (!string.IsNullOrEmpty(def.PngPath)) { val = TextureUtils.LoadTextureFromFile(def.PngPath, (FilterMode)1, (TextureWrapMode)1); } else { if (!(def.ResourceAssembly != null) || string.IsNullOrEmpty(def.ResourceName)) { Instance log = Core.Log; if (log != null) { log.Warning("Tattoo '" + def.Key + "': no texture, PNG path or embedded resource."); } return false; } byte[] array = ReadResource(def.ResourceAssembly, def.ResourceName); if (array != null) { val = TextureUtils.LoadTextureFromBytes(array, (FilterMode)1, (TextureWrapMode)1); } } if ((Object)(object)val == (Object)null) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Tattoo '" + def.Key + "': failed to load image."); } return false; } } string text = TargetPath(def); string text2 = SourceLayer(def.Placement); if (!AvatarLayerFactory.CreateAndRegisterAvatarLayer(text2, text, def.DisplayName ?? def.Id, val)) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning($"Tattoo '{def.Key}': CreateAndRegisterAvatarLayer failed (source '{text2}')."); } return false; } def.ResourcePath = text; Instance log4 = Core.Log; if (log4 != null) { log4.Msg("Registered tattoo '" + def.Key + "' -> " + text); } return true; } catch (Exception ex) { Instance log5 = Core.Log; if (log5 != null) { log5.Warning("Tattoo '" + def.Key + "': registration error - " + ex.Message); } return false; } } private static byte[] ReadResource(Assembly asm, string name) { try { string name2 = name; if (asm.GetManifestResourceStream(name2) == null) { string[] manifestResourceNames = asm.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (text == name || text.EndsWith("." + name, StringComparison.OrdinalIgnoreCase)) { name2 = text; break; } } } using Stream stream = asm.GetManifestResourceStream(name2); if (stream == null) { Instance log = Core.Log; if (log != null) { log.Warning("Embedded resource not found: '" + name + "' in " + asm.GetName().Name); } return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Embedded resource read failed '" + name + "': " + ex.Message); } return null; } } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return "x"; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_'); } return stringBuilder.ToString(); } } } namespace Inkorporated.Model { public enum TattooPlacement { Chest, LeftArm, RightArm, Face } public sealed class TattooDef { public string Id; public string DisplayName; public TattooPlacement Placement; public float Price; public string PngPath; public Texture2D Texture; public Assembly ResourceAssembly; public string ResourceName; public string Source; public string ResourcePath; public string Key => (Source ?? "?") + "/" + (Id ?? "?"); } } namespace Inkorporated.Content { internal static class ExamplePack { private const string ResourcePrefix = "Inkorporated.Assets.ExamplePack."; public static void ExtractIfEnabled() { if (!Preferences.LoadExamplePack) { return; } string text = Path.Combine(PackLoader.PacksRoot, "Examples"); try { if (Directory.Exists(text) && File.Exists(Path.Combine(text, "manifest.json"))) { Instance log = Core.Log; if (log != null) { log.Msg("Example pack already present - leaving it untouched."); } return; } Directory.CreateDirectory(text); Assembly executingAssembly = Assembly.GetExecutingAssembly(); int num = 0; string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); foreach (string text2 in manifestResourceNames) { if (!text2.StartsWith("Inkorporated.Assets.ExamplePack.", StringComparison.Ordinal)) { continue; } string path = text2.Substring("Inkorporated.Assets.ExamplePack.".Length); using Stream stream = executingAssembly.GetManifestResourceStream(text2); if (stream != null) { using FileStream destination = File.Create(Path.Combine(text, path)); stream.CopyTo(destination); num++; } } Instance log2 = Core.Log; if (log2 != null) { log2.Msg($"Extracted bundled example pack ({num} file(s)) -> {text}"); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning("Example pack extraction failed: " + ex.Message); } } } } internal static class PackLoader { public static string PacksRoot => Path.Combine(MelonEnvironment.UserDataDirectory, "Inkorporated", "Packs"); public static List<TattooDef> LoadAll() { List<TattooDef> list = new List<TattooDef>(); string packsRoot = PacksRoot; try { Directory.CreateDirectory(packsRoot); WriteReadmeIfMissing(packsRoot); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("Could not prepare packs folder '" + packsRoot + "': " + ex.Message); } return list; } string[] directories = Directory.GetDirectories(packsRoot); foreach (string text in directories) { string path = Path.Combine(text, "manifest.json"); if (!File.Exists(path)) { continue; } string name = new DirectoryInfo(text).Name; try { PackManifest packManifest = JsonConvert.DeserializeObject<PackManifest>(File.ReadAllText(path)); if (packManifest?.tattoos == null) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Pack '" + name + "': manifest has no 'tattoos' array - skipped."); } continue; } int num = 0; foreach (ManifestEntry tattoo in packManifest.tattoos) { TattooDef tattooDef = ToDef(name, text, tattoo); if (tattooDef != null) { list.Add(tattooDef); num++; } } Instance log3 = Core.Log; if (log3 != null) { log3.Msg($"Pack '{name}' ({packManifest.name ?? "unnamed"}): {num} tattoo(s)."); } } catch (Exception ex2) { Instance log4 = Core.Log; if (log4 != null) { log4.Warning("Pack '" + name + "': failed to read manifest.json - " + ex2.Message); } } } return list; } private static TattooDef ToDef(string packName, string packDir, ManifestEntry e) { if (e == null || string.IsNullOrWhiteSpace(e.id)) { Instance log = Core.Log; if (log != null) { log.Warning("Pack '" + packName + "': an entry is missing 'id' - skipped."); } return null; } if (!TryParsePlacement(e.placement, out var placement)) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"Pack '{packName}' tattoo '{e.id}': unknown placement '{e.placement}' (expected chest|leftarm|rightarm|face) - skipped."); } return null; } string path = (string.IsNullOrWhiteSpace(e.file) ? (e.id + ".png") : e.file); string text = Path.Combine(packDir, path); if (!File.Exists(text)) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning($"Pack '{packName}' tattoo '{e.id}': PNG not found at '{text}' - skipped."); } return null; } return new TattooDef { Id = e.id, DisplayName = (string.IsNullOrWhiteSpace(e.name) ? e.id : e.name), Placement = placement, Price = ((e.price < 0f) ? 0f : e.price), PngPath = text, Source = packName }; } private static bool TryParsePlacement(string s, out TattooPlacement placement) { placement = TattooPlacement.Chest; if (string.IsNullOrWhiteSpace(s)) { return false; } switch (s.Trim().ToLowerInvariant()) { case "chest": placement = TattooPlacement.Chest; return true; case "left_arm": case "left": case "leftarm": placement = TattooPlacement.LeftArm; return true; case "right": case "rightarm": case "right_arm": placement = TattooPlacement.RightArm; return true; case "face": placement = TattooPlacement.Face; return true; default: return false; } } private static void WriteReadmeIfMissing(string root) { string path = Path.Combine(root, "README.txt"); if (!File.Exists(path)) { File.WriteAllText(path, "Inkorporated - custom tattoo packs\n==================================\n\nDrop one folder per pack in this directory. Each pack needs a manifest.json and the PNG files it lists.\n\nFolder layout:\n Packs/\n MyPack/\n manifest.json\n my_chest_tattoo.png\n my_face_tattoo.png\n\nmanifest.json:\n{\n \"name\": \"My Pack\",\n \"author\": \"you\",\n \"tattoos\": [\n { \"id\": \"skull\", \"name\": \"Skull\", \"placement\": \"chest\", \"file\": \"my_chest_tattoo.png\" },\n { \"id\": \"teardrop\",\"name\": \"Teardrop X\", \"placement\": \"face\", \"file\": \"my_face_tattoo.png\", \"price\": 250 }\n ]\n}\n\nplacement: chest | leftarm | rightarm | face\nprice: optional, omit or 0 for \"Free\"\n\nTip: PNGs must match the game's body/face UV layout to sit correctly on the skin. Use an existing\nin-game tattoo texture as a template. Tattoos appear in the in-game tattoo shop.\n"); } } } public sealed class PackManifest { public string name; public string author; public List<ManifestEntry> tattoos; } public sealed class ManifestEntry { public string id; public string name; public string placement; public string file; public float price; } } namespace Inkorporated.Config { internal static class Preferences { private const string CategoryId = "Inkorporated_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _loadExamplePack; internal static bool LoadExamplePack => _loadExamplePack?.Value ?? false; internal static void Initialize() { if (_category == null) { _category = MelonPreferences.CreateCategory("Inkorporated_01_Main", "Inkorporated (Custom Tattoos)"); _loadExamplePack = _category.CreateEntry<bool>("LoadExamplePack", false, "Load example tattoo pack", "OFF by default. When ON, Inkorporated drops a small bundled example pack into UserData/Inkorporated/Packs/Examples on startup (if not already there) so you get a few ready-made tattoos plus a working folder/manifest template to copy for your own pack. Requires a game restart.", false, false, (ValueValidator)null, (string)null); } } } }