├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── BepInEx.Hacknet ├── BepInEx.Hacknet.csproj ├── ConsoleLogger.cs ├── Entrypoint.cs ├── HacknetChainloader.cs └── HacknetPlugin.cs ├── Configurations.props ├── ExampleMod ├── ExampleMod.csproj └── ExampleModPlugin.cs ├── Hacknet-Pathfinder.sln ├── LICENSE ├── Linux ├── StartPathfinder.sh ├── intercept.c └── intercept.so ├── PathfinderAPI ├── Action │ ├── ActionDelayDecorator.cs │ ├── ActionManager.cs │ ├── ConditionManager.cs │ ├── DelayablePathfinderAction.cs │ ├── PathfinderAction.cs │ └── PathfinderCondition.cs ├── Administrator │ ├── AdministratorManager.cs │ └── BaseAdministrator.cs ├── BaseGameFixes │ ├── AutoClearMissionsOnSingleComplete.cs │ ├── ClearPostLoadActions.cs │ ├── DontLosePlayerCompAdmin.cs │ ├── FixExtensionTests.cs │ ├── FixTutorialStartup.cs │ ├── FlickeringTextReportNull.cs │ ├── HHBS.cs │ ├── KeepBranchesOnMailMissionCompletion.cs │ ├── KillExeCheckIdentifierName.cs │ ├── ListingServerFixes.cs │ ├── LoadBuiltinThemes.cs │ ├── MissionListingServerLoadTime.cs │ ├── NeedsMissionComplete.cs │ ├── Performance │ │ ├── CatModuleRendering.cs │ │ ├── NodeLookup.cs │ │ └── ThemeCaching.cs │ ├── PreventSkippingETAS.cs │ ├── RandomIPNoRepeats.cs │ ├── SelfAuthenticatingHostWhitelistDisplay.cs │ ├── SendEmailMission.cs │ └── StartingActionsAfterNodes.cs ├── Command │ ├── CommandManager.cs │ └── DebugCommands.cs ├── Daemon │ ├── BaseDaemon.cs │ └── DaemonManager.cs ├── Event │ ├── BepInEx │ │ ├── LoadEvent.cs │ │ ├── PostLoadEvent.cs │ │ └── UnloadEvent.cs │ ├── EventManager.cs │ ├── Gameplay │ │ ├── CommandExecuteEvent.cs │ │ ├── ExecutableExecuteEvent.cs │ │ ├── ExecutableListEvent.cs │ │ └── OSUpdateEvent.cs │ ├── Loading │ │ ├── ExtensionLoadEvent.cs │ │ ├── OSLoadedEvent.cs │ │ ├── SaveComputerLoadedEvent.cs │ │ └── TextReplaceEvent.cs │ ├── Menu │ │ ├── DrawMainMenuEvent.cs │ │ ├── DrawMainMenuTitlesEvent.cs │ │ └── MainMenuEvent.cs │ ├── Options │ │ └── CustomOptionsSaveEvent.cs │ ├── Pathfinder │ │ └── BuildAutocompletesEvent.cs │ ├── PathfinderEvent.cs │ └── Saving │ │ ├── SaveComputerEvent.cs │ │ └── SaveEvent.cs ├── Executable │ ├── BaseExecutable.cs │ ├── ExeModuleExtensions.cs │ ├── ExecutableManager.cs │ └── GameExecutable.cs ├── GUI │ ├── ArbitraryCodeWarning.cs │ ├── ExtensionListScroll.cs │ └── PFButton.cs ├── Logger.cs ├── Meta │ ├── Load │ │ ├── ActionAttribute.cs │ │ ├── AdministratorAttribute.cs │ │ ├── AttributeManager.cs │ │ ├── BaseAttribute.cs │ │ ├── CommandAttribute.cs │ │ ├── ComputerExecutorAttribute.cs │ │ ├── ConditionAttribute.cs │ │ ├── DaemonAttribute.cs │ │ ├── EventAttribute.cs │ │ ├── ExecutableAttribute.cs │ │ ├── ExtensionInfoExecutorAttribute.cs │ │ ├── GoalAttribute.cs │ │ ├── HacknetPluginExtensions.cs │ │ ├── IgnoreEventAttribute.cs │ │ ├── IgnorePluginAttribute.cs │ │ ├── MissionExecutorAttribute.cs │ │ ├── OptionAttribute.cs │ │ ├── OptionsTabAttribute.cs │ │ ├── PortAttribute.cs │ │ └── SaveExecutorAttribute.cs │ └── UpdaterAttribute.cs ├── MiscPatches.cs ├── Mission │ ├── GoalManager.cs │ └── PathfinderGoal.cs ├── Options │ ├── Options.cs │ ├── OptionsManager.cs │ ├── PathfinderOptions.cs │ └── PathfinderOptionsMenu.cs ├── PathfinderAPI.csproj ├── PathfinderAPIPlugin.cs ├── Port │ ├── ComputerExtensions.cs │ ├── PortManager.cs │ ├── PortRecord.cs │ └── PortState.cs ├── Replacements │ ├── ActionsLoader.cs │ ├── ContentLoader.cs │ ├── ExtensionInfoLoader.cs │ ├── FileEncrypterReplacement.cs │ ├── MissionLoader.cs │ ├── ObjectSerializerReplacement.cs │ ├── ReplacementsCommon.cs │ ├── SaveLoader.cs │ └── SaveWriter.cs ├── SteamPatches.cs └── Util │ ├── AssemblyAssociatedList.cs │ ├── CachedCustomTheme.cs │ ├── ComputerLookup.cs │ ├── DictionaryExtensions.cs │ ├── EnumerableExtensions.cs │ ├── ErrorHelper.cs │ ├── FixedSizeCacheDict.cs │ ├── InitializeAttribute.cs │ ├── StringExtensions.cs │ ├── XML │ ├── ElementInfo.cs │ ├── EventExecutor.cs │ └── EventReader.cs │ ├── XMLStorageAttribute.cs │ └── XMLTypeConverter.cs ├── PathfinderInstaller ├── .gitignore ├── PathfinderInstaller.py └── PathfinderInstaller.spec ├── PathfinderPatcher ├── PathfinderPatcher.csproj └── Program.cs ├── PathfinderUpdater ├── MainMenuOverride.cs ├── PathfinderUpdater.csproj ├── PathfinderUpdaterPlugin.cs ├── RestartPopupScreen.cs └── Updater.cs ├── README.md ├── hacknet_modding_docs_logo.png ├── hacknet_modding_logo.png └── libs ├── 0Harmony.dll ├── 0Harmony.xml ├── BepInEx.Core.dll ├── BepInEx.Core.xml ├── BepInEx.cfg ├── LICENSE-BepInEx.md ├── LICENSE-Harmony.md ├── LICENSE-HarmonyX.md ├── LICENSE-Mono.Cecil.md ├── LICENSE-MonoMod.md ├── LICENSE-Newtonsoft.Json.txt ├── LICENSE-SemVer.md ├── Mono.Cecil.Mdb.dll ├── Mono.Cecil.Pdb.dll ├── Mono.Cecil.Rocks.dll ├── Mono.Cecil.dll ├── MonoMod.RuntimeDetour.dll ├── MonoMod.RuntimeDetour.xml ├── MonoMod.Utils.dll ├── MonoMod.Utils.xml ├── Newtonsoft.Json.dll ├── Newtonsoft.Json.xml └── SemanticVersioning.dll /.gitattributes: -------------------------------------------------------------------------------- 1 | # normalize bash files to use lf 2 | *.sh eol=lf 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Checkout Extra Libraries 17 | uses: actions/checkout@v2 18 | with: 19 | repository: Windows10CE/HacknetPluginTemplate 20 | path: 'template' 21 | 22 | - name: Copy Libraries 23 | run: | 24 | copy template/libs/HacknetPathfinder.exe libs/HacknetPathfinder.exe 25 | copy template/libs/FNA.dll libs/FNA.dll 26 | 27 | - name: Setup .NET 28 | uses: actions/setup-dotnet@v2 29 | with: 30 | dotnet-version: '6.0.x' 31 | 32 | - name: Build Projects 33 | run: dotnet build -c release /p:DisableBepInExHacknetPrepareForBuild=true 34 | 35 | - name: Create Release Directory 36 | run: | 37 | mkdir Release 38 | mkdir Release/BepInEx 39 | mkdir Release/BepInEx/core 40 | mkdir Release/BepInEx/plugins 41 | mkdir Release/BepInEx/config 42 | copy PathfinderAPI/bin/Release/PathfinderAPI.dll Release/BepInEx/plugins/ 43 | copy PathfinderUpdater/bin/Release/PathfinderUpdater.dll Release/BepInEx/plugins/ 44 | copy BepInEx.Hacknet/bin/Release/* Release/BepInEx/core/ 45 | copy libs/BepInEx.cfg Release/BepInEx/config/BepInEx.cfg 46 | copy PathfinderPatcher/bin/Release/PathfinderPatcher.exe Release/PathfinderPatcher.exe 47 | copy libs/Mono.Cecil.dll Release/Mono.Cecil.dll 48 | copy Linux/intercept.so Release/intercept.so 49 | copy Linux/StartPathfinder.sh Release/StartPathfinder.sh 50 | 51 | - name: Create Release ZIP 52 | uses: TheDoctor0/zip-release@0.6.0 53 | with: 54 | type: 'zip' 55 | filename: 'Pathfinder.Release.zip' 56 | directory: 'Release' 57 | 58 | - name: Setup Python 59 | uses: actions/setup-python@v2.2.2 60 | with: 61 | python-version: '3.9' 62 | 63 | - name: Install PyInstaller 64 | run: python -m pip install pyinstaller requests 65 | 66 | - name: Build Installer EXE 67 | run: | 68 | cd PathfinderInstaller 69 | pyinstaller --onefile --noconsole PathfinderInstaller.py 70 | cd .. 71 | 72 | - name: Publish Release 73 | uses: softprops/action-gh-release@v1 74 | with: 75 | files: | 76 | Release/Pathfinder.Release.zip 77 | PathfinderInstaller/dist/PathfinderInstaller.exe 78 | PathfinderInstaller/PathfinderInstaller.py 79 | 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | # Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | # ReSharper is a .NET coding add-in 36 | _ReSharper*/ 37 | *.[Rr]e[Ss]harper 38 | *.DotSettings.user 39 | 40 | # dotCover 41 | *.dotCover 42 | 43 | # VSCode options directory 44 | .vscode/ 45 | 46 | # Visual Studio 2015/2017 cache/options directory 47 | .vs/ 48 | 49 | # User-specific files 50 | *.suo 51 | *.user 52 | *.userosscache 53 | *.sln.docstates 54 | 55 | # User-specific files (MonoDevelop/Xamarin Studio) 56 | *.userprefs 57 | 58 | # Visual Studio 2017 auto generated files 59 | Generated\ Files/ 60 | 61 | # MSTest test Results 62 | [Tt]est[Rr]esult*/ 63 | [Bb]uild[Ll]og.* 64 | 65 | # NUNIT 66 | *.VisualState.xml 67 | TestResult.xml 68 | 69 | # Build Results of an ATL Project 70 | [Dd]ebugPS/ 71 | [Rr]eleasePS/ 72 | dlldata.c 73 | 74 | # Benchmark Results 75 | BenchmarkDotNet.Artifacts/ 76 | 77 | # .NET Core 78 | project.lock.json 79 | project.fragment.lock.json 80 | artifacts/ 81 | 82 | # StyleCop 83 | StyleCopReport.xml 84 | 85 | # Files built by Visual Studio 86 | *_i.c 87 | *_p.c 88 | *_i.h 89 | *.ilk 90 | *.meta 91 | *.obj 92 | *.iobj 93 | *.pch 94 | *.pdb 95 | *.ipdb 96 | *.pgc 97 | *.pgd 98 | *.rsp 99 | *.sbr 100 | *.tlb 101 | *.tli 102 | *.tlh 103 | *.tmp 104 | *.tmp_proj 105 | *.log 106 | *.vspscc 107 | *.vssscc 108 | .builds 109 | *.pidb 110 | *.svclog 111 | *.scc 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # Visual Studio Trace Files 120 | *.e2e 121 | 122 | *.arch64 123 | *.osx 124 | *.ubuntu64 125 | *.ubuntu86 126 | 127 | # Cake Automation 128 | tools/* 129 | !tools/packages.config 130 | 131 | #Hacknet assembly 132 | libs/Hacknet.exe 133 | libs/HacknetPathfinder.exe 134 | libs/Hacknet_publicized.exe 135 | libs/FNA.dll 136 | 137 | #Rider files 138 | .idea/ 139 | 140 | #Custom properties 141 | Custom.props 142 | -------------------------------------------------------------------------------- /BepInEx.Hacknet/BepInEx.Hacknet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | $(LibsDir)HacknetPathfinder.exe 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 35 | 39 | 41 | 43 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /BepInEx.Hacknet/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Logging; 2 | using BepInEx.Configuration; 3 | 4 | namespace BepInEx.Hacknet; 5 | 6 | internal class ConsoleLogger : ILogListener 7 | { 8 | protected static readonly ConfigEntry ConfigConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind( 9 | "Logging.Console", "LogLevels", 10 | LogLevel.Fatal | LogLevel.Error | LogLevel.Warning | LogLevel.Message | LogLevel.Info, 11 | "Only displays the specified log levels in the console output."); 12 | 13 | protected static readonly ConfigEntry BackupConsoleEnabled = ConfigFile.CoreConfig.Bind( 14 | "Logging.Console", "BackupConsole", 15 | false, 16 | "Enables backup console output in case the normal one fails."); 17 | 18 | public void Dispose() { } 19 | 20 | public void LogEvent(object sender, LogEventArgs eventArgs) 21 | { 22 | if (!BackupConsoleEnabled.Value || (eventArgs.Level & ConfigConsoleDisplayedLevel.Value) == 0) 23 | return; 24 | 25 | Console.ForegroundColor = eventArgs.Level.GetConsoleColor(); 26 | Console.Out.Write(eventArgs.ToStringLine()); 27 | Console.ForegroundColor = ConsoleColor.Gray; 28 | } 29 | } -------------------------------------------------------------------------------- /BepInEx.Hacknet/Entrypoint.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using BepInEx.Logging; 4 | using HarmonyLib; 5 | using HN = global::Hacknet; 6 | 7 | namespace BepInEx.Hacknet; 8 | 9 | public static class Entrypoint 10 | { 11 | public static void Bootstrap() 12 | { 13 | WriteDiagnosticHeader(); 14 | 15 | AppDomain.CurrentDomain.AssemblyResolve += ResolveBepAssembly; 16 | if (Type.GetType("Mono.Runtime") != null) 17 | AppDomain.CurrentDomain.AssemblyResolve += ResolveGACAssembly; 18 | AppDomain.CurrentDomain.AssemblyResolve += ResolveRenamedAssembly; 19 | 20 | Environment.SetEnvironmentVariable("MONOMOD_DMD_TYPE", "dynamicmethod"); 21 | 22 | LoadBepInEx.Load(); 23 | } 24 | 25 | private static void WriteDiagnosticHeader() 26 | { 27 | string Center(string s, int r) 28 | { 29 | int x = r - s.Length; 30 | int l = x/2 + s.Length; 31 | return s.PadLeft(l).PadRight(r); 32 | } 33 | void WriteInBox(params string[] lines) 34 | { 35 | int l = lines.Max(s => s.Length); 36 | 37 | Console.WriteLine("#" + new string('=', l+2) + "#"); 38 | foreach (string line in lines) 39 | Console.WriteLine("| " + Center(line,l) + " |"); 40 | Console.WriteLine("#" + new string('=', l+2) + "#"); 41 | } 42 | bool hasDLC = File.Exists("Content/DLC/DLCFaction.xml"); 43 | bool isSteam = typeof(HN.PlatformAPI.Storage.SteamCloudStorageMethod).GetField("deserialized") != null; 44 | WriteInBox 45 | ( 46 | $"Hacknet {(hasDLC ? "+Labyrinths " : "")}{(isSteam ? "Steam" : "Non-Steam")} {HN.MainMenu.OSVersion}", 47 | $"{Environment.OSVersion.Platform} ({SDL2.SDL.SDL_GetPlatform()})", 48 | $"Chainloader {HacknetChainloader.VERSION}" 49 | ); 50 | } 51 | 52 | public static Assembly ResolveBepAssembly(object sender, ResolveEventArgs args) 53 | { 54 | var asmName = new AssemblyName(args.Name); 55 | 56 | foreach (var path in Directory 57 | .GetFiles("./BepInEx", $"{asmName.Name}.dll", SearchOption.AllDirectories) 58 | .Select(Path.GetFullPath)) 59 | { 60 | try 61 | { 62 | return Assembly.LoadFile(path); 63 | } 64 | catch {} 65 | } 66 | 67 | return null; 68 | } 69 | 70 | public static Assembly ResolveGACAssembly(object sender, ResolveEventArgs args) 71 | { 72 | var asmName = new AssemblyName(args.Name); 73 | 74 | try 75 | { 76 | foreach (var path in Directory 77 | .GetFiles($"/usr/lib/mono/gac/{asmName.Name}", $"{asmName.Name}.dll", SearchOption.AllDirectories) 78 | .Select(Path.GetFullPath)) 79 | { 80 | try 81 | { 82 | return Assembly.LoadFile(path); 83 | } 84 | catch { } 85 | } 86 | } 87 | catch { } 88 | 89 | return null; 90 | } 91 | 92 | public static Assembly ResolveRenamedAssembly(object sender, ResolveEventArgs args) 93 | { 94 | var asmName = new AssemblyName(args.Name); 95 | 96 | if (ChainloaderFix.Remaps.TryGetValue(asmName.Name, out Assembly ret)) 97 | return ret; 98 | 99 | return null; 100 | } 101 | } 102 | 103 | internal static class LoadBepInEx 104 | { 105 | [MethodImpl(MethodImplOptions.NoInlining)] 106 | internal static void Load() 107 | { 108 | try 109 | { 110 | Paths.SetExecutablePath(typeof(HN.Program).Assembly.GetName().Name); 111 | 112 | Logger.Listeners.Add(new ConsoleLogger()); 113 | AccessTools.PropertySetter(typeof(TraceLogSource), nameof(TraceLogSource.IsListening)).Invoke(null, new object[] { true }); 114 | ConsoleManager.Initialize(true); 115 | 116 | // Start chainloader for plugins 117 | var chainloader = new HacknetChainloader(); 118 | chainloader.Initialize(); 119 | chainloader.Execute(); 120 | } 121 | catch (Exception ex) 122 | { 123 | Console.WriteLine("Fatal loading exception:"); 124 | Console.WriteLine(ex); 125 | Console.ReadLine(); 126 | Environment.Exit(1); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /BepInEx.Hacknet/HacknetPlugin.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Logging; 2 | using BepInEx.Configuration; 3 | using HarmonyLib; 4 | using Hacknet.Extensions; 5 | using HN = global::Hacknet; 6 | 7 | namespace BepInEx.Hacknet; 8 | 9 | public abstract class HacknetPlugin 10 | { 11 | protected HacknetPlugin() 12 | { 13 | var metadata = MetadataHelper.GetMetadata(this); 14 | 15 | HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID); 16 | 17 | Log = Logger.CreateLogSource(metadata.Name); 18 | 19 | InstalledGlobally = !HN.Settings.IsInExtensionMode; 20 | 21 | Config = new ConfigFile(Path.Combine(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata); 22 | 23 | if (!InstalledGlobally) 24 | UserConfig = new ConfigFile( 25 | Path.Combine("BepInEx/config/", ExtensionLoader.ActiveExtensionInfo.GetFoldersafeName(), metadata.GUID + ".cfg"), 26 | false, 27 | metadata 28 | ); 29 | } 30 | 31 | public ManualLogSource Log { get; } 32 | 33 | public bool InstalledGlobally { get; } 34 | 35 | /// 36 | /// If this plugin is installed in an extension, holds a to be edited by the extension developer. 37 | ///
38 | /// Otherwise, if its installed globally, holds a to be edited by the user. 39 | ///

40 | /// For the extension player's , see . 41 | ///
42 | public ConfigFile Config { get; } 43 | /// 44 | /// If this plugin is installed in an extension, holds a to be edited by the extension player. 45 | ///
46 | /// Otherwise, if its installed globally, holds the value null. 47 | ///

48 | /// For the extension developer's , see . 49 | ///
50 | public ConfigFile UserConfig { get; } 51 | 52 | public Harmony HarmonyInstance { get; set; } 53 | 54 | public abstract bool Load(); 55 | 56 | /// 57 | /// Runs after all plugins have executed their Load method 58 | /// 59 | public virtual void PostLoad() {} 60 | 61 | public virtual bool Unload() 62 | { 63 | HarmonyInstance.UnpatchSelf(); 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Configurations.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | C:\Program Files (x86)\Steam\steamapps\common\ 8 | 9 | 10 | 11 | 12 | 13 | $(Home)/.local/share/Steam/steamapps/common/ 14 | 15 | $(SteamLibraryDir.Trim()) 16 | 17 | 18 | 19 | 20 | $(SteamLibraryDir)Hacknet\ 21 | 22 | 23 | $(SteamLibraryDir)Hacknet/ 24 | 25 | 26 | $(SteamLibraryDir)Hacknet/ 27 | 28 | $(HacknetDir.Trim()) 29 | 30 | 31 | 32 | net472 33 | 34 | 10 35 | enable 36 | true 37 | false 38 | $(MSBuildThisFileDirectory) 39 | $(PathfinderSolutionDir)libs/ 40 | $(PathfinderSolutionDir)PathfinderPatcher/ 41 | $(PatcherDir)bin/$(Configuration)/ 42 | 43 | $(AssemblySearchPaths); 44 | $(LibsDir); 45 | $(HacknetDir); 46 | 47 | 48 | 49 | 50 | full 51 | 52 | 53 | 54 | pdbonly 55 | 56 | 57 | 58 | false 59 | false 60 | false 61 | 5.0.0.0 62 | Copyright © 2021 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /ExampleMod/ExampleMod.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C:\Program Files (x86)\Steam\steamapps\common\ 6 | 7 | 8 | 9 | 10 | 11 | $(Home)/.local/share/Steam/steamapps/common/ 12 | 13 | $(SteamLibraryDir.Trim()) 14 | 15 | 16 | 17 | 18 | $(SteamLibraryDir)Hacknet\ 19 | 20 | 21 | $(SteamLibraryDir)Hacknet/ 22 | 23 | 24 | $(SteamLibraryDir)Hacknet/ 25 | 26 | $(HacknetDir.Trim()) 27 | 28 | 29 | 30 | net472 31 | 32 | 10 33 | enable 34 | 35 | $(AssemblySearchPaths); 36 | ../libs/; 37 | $(HacknetDir); 38 | 39 | 40 | 41 | 42 | full 43 | 44 | 45 | 46 | pdbonly 47 | 48 | 49 | 50 | false 51 | false 52 | false 53 | 5.0.0.0 54 | Copyright © 2021 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ../libs/HacknetPathfinder.exe 63 | 64 | 65 | 66 | 67 | {64faeda5-e87c-47ed-8200-e1de1f263040} 68 | BepInEx.Hacknet 69 | False 70 | 71 | 72 | {4de0a4cf-ec60-46e1-ad96-be3a0f5be406} 73 | PathfinderAPI 74 | False 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Hacknet-Pathfinder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31025.194 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathfinderPatcher", "PathfinderPatcher\PathfinderPatcher.csproj", "{71E84BE2-DC97-41A1-B96C-090005A851F9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Hacknet", "BepInEx.Hacknet\BepInEx.Hacknet.csproj", "{64FAEDA5-E87C-47ED-8200-E1DE1F263040}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleMod", "ExampleMod\ExampleMod.csproj", "{D9FD21F5-A4EA-4B32-915F-31FD1EB7F2BD}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathfinderAPI", "PathfinderAPI\PathfinderAPI.csproj", "{4DE0A4CF-EC60-46E1-AD96-BE3A0F5BE406}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathfinderUpdater", "PathfinderUpdater\PathfinderUpdater.csproj", "{A21A9ADF-50A9-4F73-AA14-59CF85E4CA9B}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {71E84BE2-DC97-41A1-B96C-090005A851F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {71E84BE2-DC97-41A1-B96C-090005A851F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {71E84BE2-DC97-41A1-B96C-090005A851F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {71E84BE2-DC97-41A1-B96C-090005A851F9}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {64FAEDA5-E87C-47ED-8200-E1DE1F263040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {64FAEDA5-E87C-47ED-8200-E1DE1F263040}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {64FAEDA5-E87C-47ED-8200-E1DE1F263040}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {64FAEDA5-E87C-47ED-8200-E1DE1F263040}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {D9FD21F5-A4EA-4B32-915F-31FD1EB7F2BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D9FD21F5-A4EA-4B32-915F-31FD1EB7F2BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D9FD21F5-A4EA-4B32-915F-31FD1EB7F2BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D9FD21F5-A4EA-4B32-915F-31FD1EB7F2BD}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {4DE0A4CF-EC60-46E1-AD96-BE3A0F5BE406}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {4DE0A4CF-EC60-46E1-AD96-BE3A0F5BE406}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {4DE0A4CF-EC60-46E1-AD96-BE3A0F5BE406}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4DE0A4CF-EC60-46E1-AD96-BE3A0F5BE406}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {A21A9ADF-50A9-4F73-AA14-59CF85E4CA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {A21A9ADF-50A9-4F73-AA14-59CF85E4CA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {A21A9ADF-50A9-4F73-AA14-59CF85E4CA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {A21A9ADF-50A9-4F73-AA14-59CF85E4CA9B}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {3EA218DB-98A5-42B2-BCDD-37F5C4994F64} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Benjamin Peyrille, George L. Albany, Aaron Robinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Linux/StartPathfinder.sh: -------------------------------------------------------------------------------- 1 | cd "$(dirname "$0")" 2 | 3 | if [ "$(uname -m)" = "x86_64" ]; then 4 | LD_PRELOAD="$(pwd)/intercept.so /usr/lib/libmono-2.0.so" MONO_DEBUG=explicit-null-checks ./HacknetPathfinder.bin.x86_64 "$@" 5 | else 6 | LD_PRELOAD="$(pwd)/intercept.so /usr/lib/libmono-2.0.so" MONO_DEBUG=explicit-null-checks ./HacknetPathfinder.bin.x86 "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /Linux/intercept.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | 6 | void mono_set_dirs(char *assembly_dir,char *config_dir) { 7 | setenv("MONO_PATH", "/usr/lib/mono/4.5", 1); 8 | 9 | void (*set_dir_ptr)(char*,char*) = dlsym(RTLD_NEXT, "mono_set_dirs"); 10 | (*set_dir_ptr)("/usr/lib/mono/4.5", config_dir); 11 | } 12 | -------------------------------------------------------------------------------- /Linux/intercept.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/Linux/intercept.so -------------------------------------------------------------------------------- /PathfinderAPI/Action/ActionDelayDecorator.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | using Pathfinder.Replacements; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Action; 7 | 8 | public sealed class ActionDelayDecorator : DelayablePathfinderAction 9 | { 10 | public static SerializableAction Create(ElementInfo info, SerializableAction action) 11 | { 12 | if (info.Attributes.ContainsKey("Delay") && info.Attributes.ContainsKey("DelayHost")) 13 | return new ActionDelayDecorator(info, action); 14 | 15 | return action; 16 | } 17 | 18 | SerializableAction targetAction; 19 | 20 | public ActionDelayDecorator(ElementInfo info, SerializableAction action) 21 | { 22 | LoadFromXml(info); 23 | targetAction = action; 24 | } 25 | 26 | public override void Trigger(OS os) 27 | { 28 | targetAction.Trigger(os); 29 | } 30 | 31 | public override XElement GetSaveElement() 32 | { 33 | XElement element = SaveWriter.GetActionSaveElement(targetAction); 34 | element.SetAttributeValue("Delay", Delay); 35 | element.SetAttributeValue("DelayHost", DelayHost); 36 | return element; 37 | } 38 | } -------------------------------------------------------------------------------- /PathfinderAPI/Action/ActionManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text; 3 | using System.Xml; 4 | using Hacknet; 5 | using HarmonyLib; 6 | using Pathfinder.Event; 7 | using Pathfinder.Util.XML; 8 | using Pathfinder.Util; 9 | 10 | namespace Pathfinder.Action; 11 | 12 | [HarmonyPatch] 13 | public static class ActionManager 14 | { 15 | private static readonly Dictionary CustomActions = new Dictionary(); 16 | private static readonly Dictionary XmlNames = new Dictionary(); 17 | 18 | static ActionManager() 19 | { 20 | EventManager.onPluginUnload += OnPluginUnload; 21 | } 22 | 23 | internal static bool TryLoadCustomAction(ElementInfo info, out PathfinderAction action) 24 | { 25 | if (CustomActions.TryGetValue(info.Name, out var actionType)) 26 | { 27 | action = (PathfinderAction) Activator.CreateInstance(actionType); 28 | action.LoadFromXml(info); 29 | return true; 30 | } 31 | 32 | action = null; 33 | return false; 34 | } 35 | 36 | private static void OnPluginUnload(Assembly pluginAsm) 37 | { 38 | foreach (var entry in CustomActions.Where(x => x.Value.Assembly == pluginAsm).ToList()) 39 | { 40 | CustomActions.Remove(entry.Key); 41 | XmlNames.Remove(entry.Value); 42 | } 43 | } 44 | 45 | public static void RegisterAction(string xmlName) where T : PathfinderAction => RegisterAction(typeof(T), xmlName); 46 | public static void RegisterAction(Type actionType, string xmlName) 47 | { 48 | actionType.ThrowNotInherit(nameof(actionType)); 49 | CustomActions.Add(xmlName, actionType); 50 | if (!XmlNames.ContainsKey(actionType)) 51 | XmlNames.Add(actionType, xmlName); 52 | } 53 | 54 | public static void UnregisterAction() where T : PathfinderAction => UnregisterAction(typeof(T)); 55 | public static void UnregisterAction(Type actionType) 56 | { 57 | foreach(var xmlName in CustomActions.Where(x => x.Value == actionType).Select(x => x.Key).ToList()) 58 | CustomActions.Remove(xmlName); 59 | if(XmlNames.ContainsKey(actionType)) 60 | XmlNames.Remove(actionType); 61 | } 62 | public static void UnregisterAction(string xmlName) 63 | { 64 | if (!CustomActions.ContainsKey(xmlName)) 65 | return; 66 | var actionType = CustomActions[xmlName]; 67 | CustomActions.Remove(xmlName); 68 | if (XmlNames[actionType] != xmlName) 69 | return; 70 | /* find the next applicable name */ 71 | string nextName = CustomActions.FirstOrDefault(x => x.Value == actionType).Key; 72 | if (nextName == null) 73 | XmlNames.Remove(actionType); 74 | else 75 | XmlNames[actionType] = nextName; 76 | } 77 | public static string GetXmlNameFor(Type type) 78 | { 79 | XmlNames.TryGetValue(type, out string retVal); 80 | return retVal; 81 | } 82 | 83 | [HarmonyPrefix] 84 | [HarmonyPatch(typeof(SerializableAction), nameof(SerializableAction.GetSaveString))] 85 | internal static bool GetSaveStringOverridePrefix(SerializableAction __instance, ref string __result) 86 | { 87 | if (__instance is PathfinderAction pfAction) 88 | { 89 | var builder = new StringBuilder(); 90 | using (var writer = XmlWriter.Create(builder)) 91 | pfAction.GetSaveElement().WriteTo(writer); 92 | __result = builder.ToString(); 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | } -------------------------------------------------------------------------------- /PathfinderAPI/Action/ConditionManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text; 3 | using System.Xml; 4 | using Hacknet; 5 | using HarmonyLib; 6 | using Pathfinder.Event; 7 | using Pathfinder.Util.XML; 8 | using Pathfinder.Util; 9 | 10 | namespace Pathfinder.Action; 11 | 12 | [HarmonyPatch] 13 | public static class ConditionManager 14 | { 15 | private static readonly Dictionary CustomConditions = new Dictionary(); 16 | private static readonly Dictionary XmlNames = new Dictionary(); 17 | 18 | static ConditionManager() 19 | { 20 | EventManager.onPluginUnload += OnPluginUnload; 21 | } 22 | 23 | internal static bool TryLoadCustomCondition(ElementInfo info, out PathfinderCondition action) 24 | { 25 | if (CustomConditions.TryGetValue(info.Name, out var actionType)) 26 | { 27 | action = (PathfinderCondition)Activator.CreateInstance(actionType); 28 | action.LoadFromXml(info); 29 | return true; 30 | } 31 | 32 | action = null; 33 | return false; 34 | } 35 | 36 | private static void OnPluginUnload(Assembly pluginAsm) 37 | { 38 | foreach (var entry in CustomConditions.Where(x => x.Value.Assembly == pluginAsm).ToList()) 39 | { 40 | CustomConditions.Remove(entry.Key); 41 | XmlNames.Remove(entry.Value); 42 | } 43 | } 44 | 45 | public static void RegisterCondition(string xmlName) where T : PathfinderCondition => RegisterCondition(typeof(T), xmlName); 46 | public static void RegisterCondition(Type conditionType, string xmlName) 47 | { 48 | conditionType.ThrowNotInherit(nameof(conditionType)); 49 | CustomConditions.Add(xmlName, conditionType); 50 | if (!XmlNames.ContainsKey(conditionType)) 51 | XmlNames.Add(conditionType, xmlName); 52 | } 53 | 54 | public static void UnregisterCondition() where T : PathfinderCondition => UnregisterCondition(typeof(T)); 55 | public static void UnregisterCondition(Type conditionType) 56 | { 57 | foreach(var xmlName in CustomConditions.Where(x => x.Value == conditionType).Select(x => x.Key).ToList()) 58 | CustomConditions.Remove(xmlName); 59 | if(XmlNames.ContainsKey(conditionType)) 60 | XmlNames.Remove(conditionType); 61 | } 62 | public static void UnregisterCondition(string xmlName) 63 | { 64 | if (!CustomConditions.ContainsKey(xmlName)) 65 | return; 66 | var conditionType = CustomConditions[xmlName]; 67 | CustomConditions.Remove(xmlName); 68 | if (XmlNames[conditionType] != xmlName) 69 | return; 70 | /* find the next applicable name */ 71 | string nextName = CustomConditions.FirstOrDefault(x => x.Value == conditionType).Key; 72 | if (nextName == null) 73 | XmlNames.Remove(conditionType); 74 | else 75 | XmlNames[conditionType] = nextName; 76 | } 77 | public static string GetXmlNameFor(Type type) 78 | { 79 | XmlNames.TryGetValue(type, out string retVal); 80 | return retVal; 81 | } 82 | 83 | [HarmonyPrefix] 84 | [HarmonyPatch(typeof(SerializableCondition), nameof(SerializableCondition.GetSaveString))] 85 | internal static bool GetSaveStringOverridePrefix(SerializableCondition __instance, ref string __result) 86 | { 87 | if (__instance is PathfinderCondition pfCondition) 88 | { 89 | var builder = new StringBuilder(); 90 | using (var writer = XmlWriter.Create(builder)) 91 | pfCondition.GetSaveElement().WriteTo(writer); 92 | __result = builder.ToString(); 93 | return false; 94 | } 95 | 96 | return true; 97 | } 98 | } -------------------------------------------------------------------------------- /PathfinderAPI/Action/DelayablePathfinderAction.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Pathfinder.Util; 3 | using Pathfinder.Util.XML; 4 | 5 | namespace Pathfinder.Action; 6 | 7 | public abstract class DelayablePathfinderAction : PathfinderAction 8 | { 9 | [XMLStorage] 10 | public string DelayHost; 11 | [XMLStorage] 12 | public string Delay; 13 | 14 | private DelayableActionSystem delayHost; 15 | private float delay = 0f; 16 | 17 | public sealed override void Trigger(object os_obj) 18 | { 19 | if (delayHost == null && DelayHost != null) 20 | { 21 | var delayComp = Programs.getComputer(OS.currentInstance, DelayHost); 22 | if (delayComp == null) 23 | throw new FormatException($"{this.GetType().Name}: DelayHost could not be found"); 24 | delayHost = DelayableActionSystem.FindDelayableActionSystemOnComputer(delayComp); 25 | } 26 | 27 | if (delay <= 0f || delayHost == null) 28 | { 29 | Trigger((OS)os_obj); 30 | return; 31 | } 32 | 33 | DelayHost = null; 34 | Delay = null; 35 | delayHost.AddAction(this, delay); 36 | delay = 0f; 37 | } 38 | 39 | public abstract void Trigger(OS os); 40 | 41 | public override void LoadFromXml(ElementInfo info) 42 | { 43 | base.LoadFromXml(info); 44 | 45 | if (Delay != null && !float.TryParse(Delay, out delay)) 46 | throw new FormatException($"{this.GetType().Name}: Couldn't parse delay time!"); 47 | } 48 | } -------------------------------------------------------------------------------- /PathfinderAPI/Action/PathfinderAction.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | using Pathfinder.Util; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Action; 7 | 8 | public abstract class PathfinderAction : SerializableAction, IXmlName 9 | { 10 | 11 | public string XmlName => ActionManager.GetXmlNameFor(this.GetType()) ?? this.GetType().Name; 12 | 13 | public virtual XElement GetSaveElement() 14 | { 15 | return XMLStorageAttribute.WriteToElement(this); 16 | } 17 | 18 | public virtual void LoadFromXml(ElementInfo info) 19 | { 20 | XMLStorageAttribute.ReadFromElement(info, this); 21 | } 22 | } -------------------------------------------------------------------------------- /PathfinderAPI/Action/PathfinderCondition.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | using Pathfinder.Util; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Action; 7 | 8 | public abstract class PathfinderCondition : SerializableCondition, IXmlName 9 | { 10 | public string XmlName => ConditionManager.GetXmlNameFor(this.GetType()) ?? this.GetType().Name; 11 | 12 | public virtual XElement GetSaveElement() 13 | { 14 | return XMLStorageAttribute.WriteToElement(this); 15 | } 16 | 17 | public virtual void LoadFromXml(ElementInfo info) 18 | { 19 | XMLStorageAttribute.ReadFromElement(info, this); 20 | } 21 | } -------------------------------------------------------------------------------- /PathfinderAPI/Administrator/AdministratorManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Hacknet; 3 | using Pathfinder.Event; 4 | using Pathfinder.Util; 5 | using Pathfinder.Util.XML; 6 | 7 | namespace Pathfinder.Administrator; 8 | 9 | public static class AdministratorManager 10 | { 11 | private static readonly Dictionary CustomAdministrators = new Dictionary(); 12 | 13 | static AdministratorManager() 14 | { 15 | EventManager.onPluginUnload += onPluginUnload; 16 | } 17 | 18 | internal static void LoadAdministrator(ElementInfo info, Computer comp, OS os) 19 | { 20 | if (!info.Attributes.TryGetValue("type", out var adminTypeName)) 21 | return; 22 | 23 | if (CustomAdministrators.TryGetValue(adminTypeName, out Type adminType)) 24 | { 25 | BaseAdministrator admin = (BaseAdministrator)Activator.CreateInstance(adminType, new object[] { comp, os }); 26 | admin.LoadFromXml(info); 27 | comp.admin = admin; 28 | } 29 | else 30 | { 31 | switch (adminTypeName) 32 | { 33 | case "fast": 34 | comp.admin = new FastBasicAdministrator(); 35 | break; 36 | case "basic": 37 | comp.admin = new BasicAdministrator(); 38 | break; 39 | case "progress": 40 | comp.admin = new FastProgressOnlyAdministrator(); 41 | break; 42 | case "none": 43 | comp.admin = null; 44 | break; 45 | } 46 | 47 | if (comp.admin != null) 48 | { 49 | comp.admin.ResetsPassword = info.Attributes.GetBool("resetPass", info.Attributes.GetBool("resetPassword", true)); 50 | comp.admin.IsSuper = info.Attributes.GetBool("isSuper"); 51 | } 52 | } 53 | } 54 | 55 | private static void onPluginUnload(Assembly pluginAsm) 56 | { 57 | foreach (var name in CustomAdministrators.Where(x => x.Value.Assembly == pluginAsm).Select(x => x.Key).ToList()) 58 | CustomAdministrators.Remove(name); 59 | } 60 | 61 | public static void RegisterAdministrator() where T : BaseAdministrator => RegisterAdministrator(typeof(T)); 62 | public static void RegisterAdministrator(Type adminType) => RegisterAdministrator(adminType, adminType.Name); 63 | public static void RegisterAdministrator(string xmlName) where T : BaseAdministrator => RegisterAdministrator(typeof(T), xmlName); 64 | public static void RegisterAdministrator(Type adminType, string xmlName) 65 | { 66 | adminType.ThrowNotInherit(nameof(adminType)); 67 | CustomAdministrators.Add(xmlName, adminType); 68 | } 69 | 70 | public static void UnregisterAdministrator() where T : BaseAdministrator => UnregisterAdministrator(typeof(T)); 71 | public static void UnregisterAdministrator(Type adminType) 72 | { 73 | var xmlName = CustomAdministrators.FirstOrDefault(x => x.Value == adminType).Key; 74 | if (xmlName != null) 75 | CustomAdministrators.Remove(xmlName); 76 | } 77 | public static void UnregisterAdministrator(string xmlName) 78 | { 79 | CustomAdministrators.Remove(xmlName); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /PathfinderAPI/Administrator/BaseAdministrator.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | using Pathfinder.Util; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Administrator; 7 | 8 | public abstract class BaseAdministrator : Hacknet.Administrator, IXmlName 9 | { 10 | protected Computer computer; 11 | protected OS opSystem; 12 | 13 | public BaseAdministrator(Computer computer, OS opSystem) : base() 14 | { 15 | this.computer = computer; 16 | this.opSystem = opSystem; 17 | } 18 | 19 | public string XmlName => "admin"; 20 | 21 | public virtual void LoadFromXml(ElementInfo info) 22 | { 23 | base.ResetsPassword = info.Attributes.GetBool("resetPass"); 24 | base.IsSuper = info.Attributes.GetBool("isSuper"); 25 | 26 | XMLStorageAttribute.ReadFromElement(info, this); 27 | } 28 | 29 | public virtual XElement GetSaveElement() 30 | { 31 | return XMLStorageAttribute.WriteToElement(this); 32 | } 33 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/AutoClearMissionsOnSingleComplete.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using Mono.Cecil.Cil; 3 | using MonoMod.Cil; 4 | using Hacknet; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | internal static class AutoClearMissionsOnSingleComplete 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(DLCHubServer), nameof(DLCHubServer.PlayerAttemptCompleteMission))] 13 | internal static void DontClearMissionsOnCompleteIL(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | c.GotoNext(MoveType.AfterLabel, 18 | x => x.MatchLdarg(0), 19 | x => x.MatchLdfld(AccessTools.Field(typeof(Hacknet.Daemon), nameof(Hacknet.Daemon.os))), 20 | x => x.MatchCallvirt(AccessTools.Method(typeof(OS), nameof(OS.saveGame))) 21 | ); 22 | 23 | c.Emit(OpCodes.Ldarg_0); 24 | c.Emit(OpCodes.Ldfld, AccessTools.Field(typeof(DLCHubServer), nameof(DLCHubServer.AutoClearMissionsOnSingleComplete))); 25 | var branch = c.Emit(OpCodes.Brtrue, c.DefineLabel()).Prev; 26 | c.Emit(OpCodes.Ldarg_0); 27 | c.Emit(OpCodes.Ldloc_0); 28 | c.Emit(OpCodes.Stfld, AccessTools.Field(typeof(DLCHubServer), nameof(DLCHubServer.ActiveMissions))); 29 | 30 | ((ILLabel) branch.Operand).Target = c.Next; 31 | } 32 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/ClearPostLoadActions.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.BaseGameFixes; 5 | 6 | [HarmonyPatch] 7 | internal class ClearPostLoadActions 8 | { 9 | [HarmonyPostfix] 10 | [HarmonyPatch(typeof(OS), nameof(OS.LoadContent))] 11 | internal static void ClearComputerPostLoadActionsPostfix() 12 | { 13 | ComputerLoader.postAllLoadedActions = null; 14 | } 15 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/DontLosePlayerCompAdmin.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | internal static class DontLosePlayerCompAdmin 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(SAChangeIP), nameof(SAChangeIP.Trigger))] 13 | internal static void NotifyOSOfPlayerCompIPChange(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | c.GotoNext(MoveType.After, 18 | x => x.MatchLdloc(1), 19 | x => x.MatchLdarg(0), 20 | x => x.MatchLdfld(AccessTools.Field(typeof(SAChangeIP), nameof(SAChangeIP.NewIP))), 21 | x => x.MatchStfld(AccessTools.Field(typeof(Computer), nameof(Computer.ip))) 22 | ); 23 | 24 | c.Emit(OpCodes.Ldarg_1); 25 | c.Emit(OpCodes.Castclass, typeof(OS)); 26 | c.Emit(OpCodes.Ldloc_1); 27 | c.EmitDelegate>((os, comp) => 28 | { 29 | if (comp.idName == "playerComp") 30 | { 31 | os.thisComputerIPReset(); 32 | } 33 | }); 34 | } 35 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/FixExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Extensions; 3 | using Hacknet.Misc; 4 | using HarmonyLib; 5 | using MonoMod.Cil; 6 | using Mono.Cecil.Cil; 7 | 8 | namespace Pathfinder.BaseGameFixes; 9 | 10 | [HarmonyPatch] 11 | public static class FixExtensionTests 12 | { 13 | [HarmonyILManipulator] 14 | [HarmonyPatch(typeof(ExtensionTests), nameof(ExtensionTests.TestExtensionForRuntime))] 15 | [HarmonyPatch(typeof(ExtensionTests), nameof(ExtensionTests.TestExtensionMission))] 16 | internal static void TestMissionStartingMissionNONEFix(ILContext il) 17 | { 18 | ILCursor c = new ILCursor(il); 19 | 20 | // string _text = TestSuite.TestMission(ExtensionLoader.ActiveExtensionInfo.FolderPath + "/" + ExtensionLoader.ActiveExtensionInfo.StartingMissionPath, _os); 21 | c.GotoNext(MoveType.After, 22 | x => x.MatchLdsfld(AccessTools.Field(typeof(ExtensionLoader), nameof(ExtensionLoader.ActiveExtensionInfo))), 23 | x => x.MatchLdfld(AccessTools.Field(typeof(ExtensionInfo), nameof(ExtensionInfo.FolderPath))), 24 | x => x.MatchLdstr("/"), 25 | x => x.MatchLdsfld(AccessTools.Field(typeof(ExtensionLoader), nameof(ExtensionLoader.ActiveExtensionInfo))), 26 | x => x.MatchLdfld(AccessTools.Field(typeof(ExtensionInfo), nameof(ExtensionInfo.StartingMissionPath))), 27 | x => x.MatchCall(AccessTools.Method(typeof(string), nameof(string.Concat), new Type[] { typeof(string), typeof(string), typeof(string) })), 28 | _ => true, 29 | x => x.MatchCall(AccessTools.Method(typeof(TestSuite), nameof(TestSuite.TestMission))), 30 | x => x.MatchStloc(out int _) 31 | ); 32 | c.Index--; 33 | ILLabel labelDontTestStartingMission = c.MarkLabel(); 34 | c.Index -= 7; 35 | ILLabel labelTestStartingMission = c.MarkLabel(); 36 | c.MoveBeforeLabels(); 37 | 38 | c.Emit(OpCodes.Dup); 39 | c.EmitDelegate>(info => 40 | info.StartingMissionPath == null 41 | ); 42 | c.Emit(OpCodes.Brfalse, labelTestStartingMission); 43 | c.Emit(OpCodes.Pop); 44 | c.Emit(OpCodes.Ldstr, ""); 45 | c.Emit(OpCodes.Br, labelDontTestStartingMission); 46 | } 47 | [HarmonyILManipulator] 48 | [HarmonyPatch(typeof(ExtensionInfo), nameof(ExtensionInfo.VerifyExtensionInfo))] 49 | internal static void ExtensionInfoStartingMissionNONEFix(ILContext il) 50 | { 51 | ILCursor c = new ILCursor(il); 52 | // if (!File.Exists(info.FolderPath + "/" + info.StartingMissionPath)) 53 | c.GotoNext(MoveType.After, 54 | x => x.MatchLdarg(0), 55 | x => x.MatchLdfld(AccessTools.Field(typeof(ExtensionInfo), nameof(ExtensionInfo.FolderPath))), 56 | x => x.MatchLdstr("/"), 57 | x => x.MatchLdarg(0), 58 | x => x.MatchLdfld(AccessTools.Field(typeof(ExtensionInfo), nameof(ExtensionInfo.StartingMissionPath))), 59 | x => x.MatchCall(AccessTools.Method(typeof(string), nameof(string.Concat), new Type[] { typeof(string), typeof(string), typeof(string) })), 60 | x => x.MatchCall(AccessTools.Method(typeof(File), nameof(File.Exists))), 61 | x => x.MatchStloc(3), 62 | x => x.MatchLdloc(3), 63 | x => x.MatchBrtrue(out ILLabel _) 64 | ); 65 | c.Index--; 66 | c.Emit(OpCodes.Ldarg_0); 67 | c.EmitDelegate>((b, info) => 68 | b || info.StartingMissionPath == null 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/FixTutorialStartup.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Extensions; 3 | using HarmonyLib; 4 | using MonoMod.Cil; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | public static class FixTutorialStartup 11 | { 12 | [HarmonyILManipulator] 13 | [HarmonyPatch(typeof(Programs), nameof(Programs.firstTimeInit))] 14 | internal static void FirstTimeInitSetStartingMissionForTutorial(ILContext il) 15 | { 16 | ILCursor c = new ILCursor(il); 17 | 18 | c.GotoNext(MoveType.After, 19 | x => x.MatchLdstr("Launching Tutorial...") 20 | ); 21 | 22 | c.Emit(OpCodes.Ldarg_1); 23 | c.EmitDelegate>(os => 24 | { 25 | if (Settings.IsInExtensionMode && ExtensionLoader.ActiveExtensionInfo?.StartingMissionPath != null) 26 | os.currentMission = (ActiveMission)ComputerLoader.readMission(ExtensionLoader.ActiveExtensionInfo.FolderPath + "/" + ExtensionLoader.ActiveExtensionInfo.StartingMissionPath); 27 | }); 28 | } 29 | [HarmonyILManipulator] 30 | [HarmonyPatch(typeof(AdvancedTutorial), nameof(AdvancedTutorial.Killed))] 31 | internal static void AdvancedTutorialKilledNullMissionFix(ILContext il) 32 | { 33 | ILCursor c = new ILCursor(il); 34 | 35 | // os.currentMission.sendEmail(os); 36 | c.GotoNext(MoveType.After, 37 | x => x.MatchNop(), 38 | x => x.MatchLdarg(0), 39 | x => x.MatchLdfld(AccessTools.Field(typeof(Module), nameof(Module.os))), 40 | x => x.MatchLdfld(AccessTools.Field(typeof(OS), nameof(OS.currentMission))), 41 | x => x.MatchLdarg(0), 42 | x => x.MatchLdfld(AccessTools.Field(typeof(Module), nameof(Module.os))), 43 | x => x.MatchCallvirt(AccessTools.Method(typeof(ActiveMission), nameof(ActiveMission.sendEmail))) 44 | ); 45 | // os.Flags.AddFlag("TutorialComplete"); 46 | ILLabel nextLine = c.MarkLabel(); 47 | c.Index -= 3; 48 | ILLabel continueThisLine = c.MarkLabel(); 49 | c.MoveBeforeLabels(); 50 | 51 | c.Emit(OpCodes.Dup); 52 | c.Emit(OpCodes.Ldnull); 53 | c.Emit(OpCodes.Ceq); 54 | c.Emit(OpCodes.Brfalse, continueThisLine); 55 | c.Emit(OpCodes.Pop); 56 | c.Emit(OpCodes.Br, nextLine); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/FlickeringTextReportNull.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Hacknet.Effects; 3 | using HarmonyLib; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | public static class FlickeringTextReportNull { 11 | [HarmonyPatch(typeof(FlickeringTextEffect), nameof(FlickeringTextEffect.GetReportString))] 12 | [HarmonyILManipulator] 13 | private static void GetReportStringManipulator(ILContext context, ILLabel retLabel) { 14 | ILCursor cursor = new(context); 15 | 16 | ILLabel unskipLabel = context.DefineLabel(); 17 | cursor.Emit(OpCodes.Ldsfld, typeof(FlickeringTextEffect).GetField(nameof(FlickeringTextEffect.LinedItemTarget), BindingFlags.Static | BindingFlags.Public)); 18 | cursor.Emit(OpCodes.Brtrue, unskipLabel); 19 | cursor.Emit(OpCodes.Ldstr, "FlickeringTextEffect was not used in this execution."); 20 | cursor.Emit(OpCodes.Br, retLabel); 21 | unskipLabel.Target = cursor.Next; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/HHBS.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Hacknet; 3 | using HarmonyLib; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | internal static class HHBS 11 | { 12 | [Util.Initialize] 13 | internal static void Initialize() 14 | { 15 | var a = HostileHackerBreakinSequence.BaseDirectory; 16 | } 17 | 18 | private static readonly ConstructorInfo HHBSCctor = typeof(HostileHackerBreakinSequence).TypeInitializer; 19 | 20 | [HarmonyPrefix] 21 | [HarmonyPatch(typeof(OS), MethodType.Constructor, new Type[0])] 22 | internal static void ReloadHHBSOnOSCtorPrefix() => HHBSCctor.Invoke(null, null); 23 | 24 | [HarmonyILManipulator] 25 | [HarmonyPatch(typeof(HostileHackerBreakinSequence), nameof(HostileHackerBreakinSequence.GetBaseDirectory))] 26 | internal static void FixPathCombineOrderIL(ILContext il) 27 | { 28 | ILCursor c = new ILCursor(il); 29 | 30 | c.GotoNext(MoveType.AfterLabel, 31 | x => x.MatchLdsfld(AccessTools.Field(typeof(Settings), nameof(Settings.IsInExtensionMode))) 32 | ); 33 | 34 | var combine = AccessTools.Method(typeof(Path), nameof(Path.Combine), new Type[] { typeof(string), typeof(string) }); 35 | 36 | c.Emit(OpCodes.Ldloc_0); 37 | c.Emit(OpCodes.Ldstr, "HacknetPathfinder"); 38 | c.Emit(OpCodes.Call, combine); 39 | c.Emit(OpCodes.Ldstr, "Accounts"); 40 | c.Emit(OpCodes.Call, combine); 41 | c.Emit(OpCodes.Stloc_0); 42 | 43 | c.GotoNext(MoveType.Before, 44 | x => x.MatchLdstr("Hacknet") 45 | ); 46 | 47 | c.Next.Operand = "HHBS"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/KeepBranchesOnMailMissionCompletion.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | using MonoMod.Utils; 7 | 8 | namespace Pathfinder.BaseGameFixes; 9 | 10 | [HarmonyPatch] 11 | public static class KeepBranchesOnMailMissionCompletion { 12 | [HarmonyPatch(typeof(MailServer), nameof(MailServer.doRespondDisplay))] 13 | [HarmonyILManipulator] 14 | public static void DoRespondDisplayManipulator(ILContext context) { 15 | ILCursor cursor = new(context); 16 | 17 | cursor.GotoNext(MoveType.After, 18 | x => x.MatchLdarg(0), 19 | x => x.MatchLdarg(0), 20 | x => x.MatchLdfld("os"), 21 | x => x.MatchLdfld("branchMissions"), 22 | x => x.MatchLdloc(4), 23 | x => x.MatchCallvirt(out MethodReference method) && 24 | method.DeclaringType.Is(typeof(List)) && 25 | method.Name == "get_Item" 26 | , 27 | x => x.MatchCall("attemptCompleteMission"), 28 | x => x.MatchStloc(11) 29 | ); 30 | Instruction startOfRange = cursor.Next; 31 | 32 | cursor.GotoNext(MoveType.After, 33 | x => x.MatchLdarg(0), 34 | x => x.MatchLdfld("os"), 35 | x => x.MatchLdfld("branchMissions"), 36 | x => x.MatchCallvirt(out MethodReference method) && 37 | method.DeclaringType.Is(typeof(List)) && 38 | method.Name == "Clear" 39 | ); 40 | Instruction endOfRange = cursor.Prev; 41 | 42 | cursor.Goto(startOfRange); 43 | int count = cursor.Instrs.IndexOf(endOfRange) - cursor.Instrs.IndexOf(startOfRange) + 1; 44 | cursor.RemoveRange(count); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/KillExeCheckIdentifierName.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | public static class KillExeCheckIdentifierName 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(SAKillExe), nameof(SAKillExe.Trigger))] 13 | internal static void SAKillExeTriggerIL(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | // if (oS.exes[i].name.ToLower().Contains(ExeName.ToLower())) 18 | c.GotoNext(MoveType.After, 19 | // (...) 20 | x => x.MatchLdfld(AccessTools.Field(typeof(SAKillExe), nameof(SAKillExe.ExeName))), 21 | x => x.MatchCallvirt(AccessTools.Method(typeof(string), nameof(string.ToLower))), 22 | x => x.MatchCallvirt(AccessTools.Method(typeof(string), nameof(string.Contains), new Type[]{ typeof(string) })), 23 | x => x.MatchLdcI4(0), 24 | x => x.MatchCeq() 25 | ); 26 | c.Emit(OpCodes.Ldarg_0); 27 | c.Emit(OpCodes.Ldloc_0); 28 | c.Emit(OpCodes.Ldloc_1); 29 | c.EmitDelegate>((__instance, oS, i) => 30 | oS.exes[i].IdentifierName.ToLower().Contains(__instance.ExeName.ToLower()) 31 | ); 32 | c.Emit(OpCodes.Ldc_I4_0); 33 | c.Emit(OpCodes.Ceq); 34 | c.Emit(OpCodes.And); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/ListingServerFixes.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | internal static class ListingServerFixes 10 | { 11 | [HarmonyPrefix] 12 | [HarmonyPatch(typeof(MissionListingServer), nameof(MissionListingServer.addMisison))] 13 | internal static bool SkipNullMissions(ActiveMission m) => m != null; 14 | 15 | [HarmonyILManipulator] 16 | [HarmonyPatch(typeof(SAAddMissionToHubServer), nameof(SAAddMissionToHubServer.Trigger))] 17 | internal static void NullCheckOnAssignmentTag(ILContext il) 18 | { 19 | var c = new ILCursor(il); 20 | 21 | c.GotoNext(x => x.MatchCallvirt(AccessTools.Method(typeof(string), nameof(string.ToLower)))); 22 | 23 | var start = c.DefineLabel(); // IL_0123 24 | var end = c.DefineLabel(); // IL_0132 25 | 26 | c.Emit(OpCodes.Dup); 27 | c.Emit(OpCodes.Brtrue, start); 28 | c.Emit(OpCodes.Pop); 29 | c.Emit(OpCodes.Ldc_I4_0); 30 | c.Emit(OpCodes.Br, end); 31 | 32 | start.Target = c.Next; 33 | 34 | c.GotoNext(x => x.MatchCallvirt(AccessTools.Method(typeof(MissionListingServer), nameof(MissionListingServer.addMisison)))); 35 | 36 | end.Target = c.Next; 37 | } 38 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/LoadBuiltinThemes.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | internal class LoadBuiltinThemes { 11 | /** 12 | * Replaces dumb manual enumeration name lookup with Enum.TryParse 13 | * 14 | * The original code does a .ToLower() on the enum code to check against 15 | * the theme, as the original code .ToLower()'s the theme name. 16 | * We don't, so we just ignore case completely instead. Does the same thing. 17 | * 18 | */ 19 | [HarmonyILManipulator] 20 | [HarmonyPatch(typeof (Hacknet.Extensions.ExtensionLoader), "LoadNewExtensionSession")] 21 | internal static void PatchEnumFind(ILContext il) { 22 | ILCursor cursor = new ILCursor(il); 23 | 24 | cursor.GotoNext(MoveType.Before, 25 | x => x.MatchLdcI4(0), 26 | x => x.MatchStloc(9), 27 | x => x.MatchNop(), 28 | x => { 29 | if(!x.MatchLdtoken(out IMetadataTokenProvider metadata)) 30 | return false; 31 | if(!(metadata is TypeReference typeRef)) 32 | return false; 33 | return typeRef.FullName == "Hacknet.OSTheme"; 34 | }, 35 | x => x.MatchCall(typeof(Type), "GetTypeFromHandle"), 36 | x => x.MatchCall(typeof(Enum), "GetValues") 37 | ); 38 | cursor.RemoveRange(57); // whew 39 | /* arg1: `info.Theme` */ 40 | cursor.Emit(OpCodes.Ldarg_0); /* info */ 41 | cursor.Emit(OpCodes.Ldfld, AccessTools.Field(typeof(Hacknet.Extensions.ExtensionInfo), "Theme")); 42 | /* arg2: `true` */ 43 | cursor.Emit(OpCodes.Ldc_I4_1); 44 | /* arg3: `out theme` */ 45 | cursor.Emit(OpCodes.Ldloca, 8); 46 | /* call: `Enum.TryParse(string: info.Theme, bool: true, out OSTheme: theme)` */ 47 | cursor.Emit(OpCodes.Call, AccessTools.FirstMethod(typeof(Enum), x => x.Name == "TryParse" && x.GetParameters().Length == 3 && x.GetGenericArguments().Length == 1).MakeGenericMethod(typeof(OSTheme))); 48 | cursor.Emit(OpCodes.Dup); 49 | cursor.Emit(OpCodes.Stloc, 9); 50 | } 51 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/MissionListingServerLoadTime.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | using Pathfinder.Replacements; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | internal static class MissionListingServerLoadTime 11 | { 12 | [HarmonyILManipulator] 13 | [HarmonyPatch(typeof(MissionListingServer), nameof(MissionListingServer.addListingsForGroup))] 14 | internal static void FixMissionLoadTimes(ILContext il) 15 | { 16 | ILCursor c = new ILCursor(il); 17 | 18 | c.GotoNext(MoveType.Before, 19 | x => x.MatchLdarg(0), 20 | x => x.MatchLdfld(AccessTools.Field(typeof(MissionListingServer), nameof(MissionListingServer.CustomFolderLoadPath))) 21 | ); 22 | 23 | c.Index += 1; 24 | var start = c.MarkLabel(); 25 | 26 | c.GotoNext(MoveType.Before, 27 | x => x.MatchNop(), 28 | x => x.MatchNop(), 29 | x => x.MatchBr(out _), 30 | x => x.MatchLdarg(0), 31 | x => x.MatchLdfld(AccessTools.Field(typeof(MissionListingServer), nameof(MissionListingServer.groupName))) 32 | ); 33 | 34 | var end = c.Index; 35 | 36 | c.GotoLabel(start); 37 | 38 | c.RemoveRange(end - c.Index); 39 | 40 | c.Emit(OpCodes.Ldloc_1); 41 | c.EmitDelegate>((listingDaemon, shouldGen) => 42 | { 43 | ComputerLoader.postAllLoadedActions += () => 44 | { 45 | foreach (var file in Directory.GetFiles(listingDaemon.CustomFolderLoadPath, "*.xml")) 46 | { 47 | OS.currentInstance.branchMissions = new List(); 48 | listingDaemon.addMisison(MissionLoader.LoadContentMission(file)); 49 | } 50 | 51 | if (shouldGen) 52 | { 53 | for (int i = 0; i < 2; i++) 54 | { 55 | OS.currentInstance.branchMissions = new List(); 56 | listingDaemon.addMisison((ActiveMission)MissionGenerator.generate(2)); 57 | } 58 | } 59 | }; 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/NeedsMissionComplete.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MonoMod.Cil; 3 | using Mono.Cecil.Cil; 4 | using Hacknet; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | internal static class NeedsMissionComplete 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(SCInstantly), nameof(SCInstantly.Check))] 13 | internal static void ActiveMissionNullCheckIL(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | c.GotoNext(MoveType.After, 18 | x => x.MatchLdfld(AccessTools.Field(typeof(OS), nameof(OS.currentMission))) 19 | ); 20 | 21 | c.RemoveRange(2); 22 | c.EmitDelegate>(mission => mission?.isComplete() ?? false); 23 | } 24 | 25 | [HarmonyILManipulator] 26 | [HarmonyPatch(typeof(SCOnConnect), nameof(SCOnConnect.Check))] 27 | internal static void ActiveMissionLogicFixIL(ILContext il, ILLabel retLabel) 28 | { 29 | ILCursor c = new ILCursor(il); 30 | 31 | c.GotoNext(MoveType.Before, 32 | x => x.MatchLdarg(0), 33 | x => x.MatchLdfld(AccessTools.Field(typeof(SCOnConnect), nameof(SCOnConnect.needsMissionComplete))) 34 | ); 35 | 36 | // retain last 3 instructions (ldloc_4, br, ret) for earlier `return false` 37 | c.RemoveRange((c.Instrs.Count - 3) - c.Index); 38 | c.Emit(OpCodes.Ldarg_0); 39 | var startInst = c.Prev; 40 | c.Emit(OpCodes.Ldloc_0); 41 | c.Emit(OpCodes.Ldloc_1); 42 | c.EmitDelegate>((self, os, comp) => 43 | (!self.needsMissionComplete || os.currentMission == null || os.currentMission.isComplete()) 44 | && (os.connectedComp != null && os.connectedComp.ip == comp.ip) 45 | ); 46 | c.Emit(OpCodes.Br, retLabel); 47 | 48 | foreach (var label in il.Labels) 49 | { 50 | if (!il.Instrs.Any(x => label.Target.Equals(x))) 51 | { 52 | label.Target = startInst; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/Performance/CatModuleRendering.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Effects; 3 | using HarmonyLib; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.BaseGameFixes.Performance; 7 | 8 | [HarmonyPatch] 9 | internal static class CatModuleRendering 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(DisplayModule), nameof(DisplayModule.doCatDisplay))] 13 | internal static void DoCatDisplayNoWrapIL(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | c.GotoNext(MoveType.Before, 18 | x => x.MatchLdloc(5), 19 | x => x.MatchCallOrCallvirt(AccessTools.Method(typeof(LocalizedFileLoader), nameof(LocalizedFileLoader.SafeFilterString))) 20 | ); 21 | 22 | c.Index += 1; 23 | c.RemoveRange(10); 24 | } 25 | 26 | [HarmonyILManipulator] 27 | [HarmonyPatch(typeof(Programs), nameof(Programs.cat))] 28 | [HarmonyPatch(typeof(Programs), nameof(Programs.replace))] 29 | [HarmonyPatch(typeof(Programs), nameof(Programs.replace2))] 30 | internal static void WrapOnceInCommandIL(ILContext il) 31 | { 32 | ILCursor c = new ILCursor(il); 33 | 34 | c.GotoNext(MoveType.Before, 35 | x => x.MatchLdfld(AccessTools.Field(typeof(FileEntry), nameof(FileEntry.data))), 36 | x => x.MatchStfld(AccessTools.Field(typeof(OS), nameof(OS.displayCache))) 37 | ); 38 | 39 | c.Index += 1; 40 | 41 | c.EmitDelegate>(fileData => 42 | { 43 | lastFileData = fileData; 44 | return Utils.SuperSmartTwimForWidth(LocalizedFileLoader.SafeFilterString(fileData), OS.currentInstance.display.bounds.Width - 40, GuiData.tinyfont); 45 | }); 46 | } 47 | 48 | private static bool isFlickering = false; 49 | 50 | [HarmonyPrefix] 51 | [HarmonyPatch(typeof(ActiveEffectsUpdater), nameof(ActiveEffectsUpdater.Update))] 52 | internal static void CheckThemeSwapFlicker(ActiveEffectsUpdater __instance) => isFlickering = __instance.themeSwapTimeRemaining > 0f; 53 | 54 | private static string displayCache2 = null; 55 | private static string lastFileData = null; 56 | 57 | [HarmonyPostfix] 58 | [HarmonyPatch(typeof(ThemeManager), nameof(ThemeManager.switchTheme), new Type[] { typeof(object), typeof(OSTheme) })] 59 | internal static void CacheDisplayStringForThemeSwitch(object osObject) 60 | { 61 | var os = (OS) osObject; 62 | 63 | if (os.displayCache == null || lastFileData == null || (os.display.command != "cat" && os.display.command != "less")) 64 | return; 65 | 66 | if (isFlickering) 67 | { 68 | if (displayCache2 == null) 69 | { 70 | displayCache2 = os.displayCache; 71 | 72 | os.displayCache = Utils.SuperSmartTwimForWidth(LocalizedFileLoader.SafeFilterString(lastFileData), os.display.bounds.Width - 40, GuiData.tinyfont); 73 | } 74 | else 75 | { 76 | var temp = displayCache2; 77 | displayCache2 = os.displayCache; 78 | os.displayCache = temp; 79 | } 80 | } 81 | else 82 | { 83 | os.displayCache = Utils.SuperSmartTwimForWidth(LocalizedFileLoader.SafeFilterString(lastFileData), os.display.bounds.Width - 40, GuiData.tinyfont); 84 | } 85 | 86 | isFlickering = false; 87 | } 88 | 89 | [HarmonyPostfix] 90 | [HarmonyPatch(typeof(ActiveEffectsUpdater), nameof(ActiveEffectsUpdater.CompleteThemeSwap))] 91 | internal static void ClearCache2OnFlickerFinish() => displayCache2 = null; 92 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/PreventSkippingETAS.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Effects; 3 | using HarmonyLib; 4 | 5 | namespace Pathfinder.BaseGameFixes; 6 | 7 | [HarmonyPatch] 8 | internal static class PreventSkippingETAS 9 | { 10 | [HarmonyPrefix] 11 | [HarmonyPatch(typeof(Programs), nameof(Programs.reboot))] 12 | internal static bool RebootPrefix(string[] args, OS os) 13 | { 14 | if (os.TraceDangerSequence.IsActive && (os.connectedComp == null || os.connectedComp == os.thisComputer)) 15 | { 16 | os.write("REBOOT ERROR: OS reports critical action already in progress."); 17 | return false; 18 | } 19 | return true; 20 | } 21 | 22 | [HarmonyPostfix] 23 | [HarmonyPatch(typeof(OS), nameof(OS.thisComputerCrashed))] 24 | [HarmonyPatch(typeof(OS), nameof(OS.rebootThisComputer))] 25 | internal static void ETASGameover(OS __instance) 26 | { 27 | if (__instance.TraceDangerSequence.IsActive) 28 | { 29 | __instance.TraceDangerSequence.timeThisState = 0f; 30 | __instance.TraceDangerSequence.state = TraceDangerSequence.TraceDangerState.Gameover; 31 | __instance.TraceDangerSequence.CancelTraceDangerSequence(); 32 | Game1.getSingleton().Exit(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/RandomIPNoRepeats.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Pathfinder.Util; 4 | 5 | namespace Pathfinder.BaseGameFixes; 6 | 7 | [HarmonyPatch] 8 | internal static class RandomIPNoRepeats 9 | { 10 | [HarmonyPrefix] 11 | [HarmonyPatch(typeof(NetworkMap), nameof(NetworkMap.generateRandomIP))] 12 | internal static bool GenerateRandomIPReplacement(out string __result) 13 | { 14 | while (true) 15 | { 16 | var ip = Utils.random.Next(254) + 1 + "." + (Utils.random.Next(254) + 1) + "." + (Utils.random.Next(254) + 1) + "." + (Utils.random.Next(254) + 1); 17 | if (ComputerLookup.FindByIp(ip, false) == null) 18 | { 19 | __result = ip; 20 | return false; 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/SelfAuthenticatingHostWhitelistDisplay.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.BaseGameFixes; 7 | 8 | [HarmonyPatch] 9 | internal static class SelfAuthenticatingHostWhitelistDisplay 10 | { 11 | [HarmonyILManipulator] 12 | [HarmonyPatch(typeof(WhitelistConnectionDaemon), nameof(WhitelistConnectionDaemon.draw))] 13 | internal static void WhitelistDrawFix(ILContext il) 14 | { 15 | ILCursor c = new ILCursor(il); 16 | 17 | // bool flag = RemoteCompCanBeAccessed(); 18 | c.GotoNext(MoveType.After, 19 | x => x.MatchCallOrCallvirt(AccessTools.Method(typeof(WhitelistConnectionDaemon), nameof(WhitelistConnectionDaemon.RemoteCompCanBeAccessed))) 20 | ); 21 | 22 | // or RemoteCompCanBeAccessed() with RemoteSourceIP == null 23 | // Could be more efficient by checking RemoteSourceIP first and branching 24 | c.Emit(OpCodes.Ldarg_0); 25 | c.Emit(OpCodes.Ldfld, AccessTools.Field(typeof(WhitelistConnectionDaemon), nameof(WhitelistConnectionDaemon.RemoteSourceIP))); 26 | c.Emit(OpCodes.Ldnull); 27 | c.Emit(OpCodes.Ceq); 28 | c.Emit(OpCodes.Or); 29 | } 30 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/SendEmailMission.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using MonoMod.Cil; 3 | using Hacknet; 4 | 5 | namespace Pathfinder.BaseGameFixes; 6 | 7 | [HarmonyPatch] 8 | internal static class SendEmailMission 9 | { 10 | [HarmonyILManipulator] 11 | [HarmonyPatch(typeof(MailServer), nameof(MailServer.MailWithSubjectExists))] 12 | public static void IndexIntoInboxFolderIL(ILContext il) 13 | { 14 | ILCursor c = new ILCursor(il); 15 | 16 | c.GotoNext(MoveType.Before, 17 | x => x.MatchStloc(1) 18 | ); 19 | 20 | c.EmitDelegate>(folder => folder.folders[0]); 21 | } 22 | } -------------------------------------------------------------------------------- /PathfinderAPI/BaseGameFixes/StartingActionsAfterNodes.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Extensions; 3 | using HarmonyLib; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | 7 | namespace Pathfinder.BaseGameFixes; 8 | 9 | [HarmonyPatch] 10 | internal static class StartingActionsAfterNodes 11 | { 12 | [HarmonyILManipulator] 13 | [HarmonyPatch(typeof(ExtensionLoader), nameof(ExtensionLoader.LoadNewExtensionSession))] 14 | internal static void FixStartingActionsIL(ILContext il) 15 | { 16 | ILCursor c = new ILCursor(il); 17 | 18 | c.GotoNext(MoveType.Before, 19 | x => x.MatchCallOrCallvirt(AccessTools.Method(typeof(RunnableConditionalActions), nameof(RunnableConditionalActions.LoadIntoOS))) 20 | ); 21 | 22 | c.Remove(); 23 | c.Emit(OpCodes.Pop); 24 | c.Emit(OpCodes.Pop); 25 | } 26 | 27 | [HarmonyPostfix] 28 | [HarmonyPatch(typeof(OS), nameof(OS.LoadContent))] 29 | internal static void RunStartingActions(ref OS __instance) 30 | { 31 | if (!OS.WillLoadSave && Settings.IsInExtensionMode && ExtensionLoader.ActiveExtensionInfo.StartingActionsPath != null) 32 | RunnableConditionalActions.LoadIntoOS(ExtensionLoader.ActiveExtensionInfo.StartingActionsPath, __instance); 33 | } 34 | } -------------------------------------------------------------------------------- /PathfinderAPI/Command/CommandManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Hacknet; 4 | using HarmonyLib; 5 | using Pathfinder.Event; 6 | using Pathfinder.Event.Gameplay; 7 | using Pathfinder.Event.Pathfinder; 8 | using Pathfinder.Util; 9 | 10 | namespace Pathfinder.Command; 11 | 12 | [HarmonyPatch] 13 | public static class CommandManager 14 | { 15 | private struct CustomCommand 16 | { 17 | public string Name; 18 | public Action CommandAction; 19 | public bool Autocomplete; 20 | public bool CaseSensitive; 21 | } 22 | 23 | private static readonly AssemblyAssociatedList CustomCommands = new AssemblyAssociatedList(); 24 | 25 | static CommandManager() 26 | { 27 | EventManager.AddHandler(OnCommandExecute); 28 | EventManager.onPluginUnload += OnPluginUnload; 29 | } 30 | 31 | private static void OnCommandExecute(CommandExecuteEvent args) 32 | { 33 | Action custom = null; 34 | foreach (var command in CustomCommands.AllItems) 35 | { 36 | if (string.Equals(command.Name, args.Args[0], command.CaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase)) 37 | { 38 | custom = command.CommandAction; 39 | break; 40 | } 41 | } 42 | 43 | if (custom != null) 44 | { 45 | args.Found = true; 46 | args.Cancelled = true; 47 | 48 | custom(args.Os, args.Args); 49 | } 50 | } 51 | 52 | [HarmonyReversePatch(HarmonyReversePatchType.Original)] 53 | [HarmonyPatch(typeof(ProgramList), nameof(ProgramList.init))] 54 | [MethodImpl(MethodImplOptions.NoInlining)] 55 | private static void OrigProgramListInit() { throw new NotImplementedException(); } 56 | 57 | [HarmonyPrefix] 58 | [HarmonyPatch(typeof(ProgramList), nameof(ProgramList.init))] 59 | private static bool ProgramListInitPrefix() 60 | { 61 | RebuildAutoComplete(); 62 | return false; 63 | } 64 | 65 | private static void RebuildAutoComplete() 66 | { 67 | OrigProgramListInit(); 68 | foreach (var command in CustomCommands.AllItems) 69 | { 70 | if (command.Autocomplete && !ProgramList.programs.Contains(command.Name)) 71 | ProgramList.programs.Add(command.Name); 72 | } 73 | ProgramList.programs = EventManager.InvokeAll(new BuildAutocompletesEvent(ProgramList.programs)).Autocompletes; 74 | } 75 | 76 | private static void OnPluginUnload(Assembly pluginAsm) 77 | { 78 | if (CustomCommands.RemoveAssembly(pluginAsm, out _)) 79 | RebuildAutoComplete(); 80 | } 81 | 82 | [MethodImpl(MethodImplOptions.NoInlining)] 83 | public static void RegisterCommand(string commandName, Action handler, bool addAutocomplete = true, bool caseSensitive = false) 84 | { 85 | var pluginAsm = Assembly.GetCallingAssembly(); 86 | 87 | if (CustomCommands.AllItems.Any(x => x.Name == commandName)) 88 | throw new ArgumentException($"Command {commandName} has already been registered!", nameof(commandName)); 89 | 90 | CustomCommands.Add(new CustomCommand 91 | { 92 | Name = commandName, 93 | CommandAction = handler, 94 | Autocomplete = addAutocomplete, 95 | CaseSensitive = caseSensitive 96 | }, pluginAsm); 97 | 98 | if (addAutocomplete) 99 | RebuildAutoComplete(); 100 | } 101 | 102 | [MethodImpl(MethodImplOptions.NoInlining)] 103 | public static void UnregisterCommand(string commandName, Assembly pluginAsm = null) 104 | { 105 | CustomCommands.RemoveAll(x => x.Name == commandName, pluginAsm ?? Assembly.GetCallingAssembly()); 106 | } 107 | } -------------------------------------------------------------------------------- /PathfinderAPI/Command/DebugCommands.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Pathfinder.Util; 3 | 4 | namespace Pathfinder.Command; 5 | 6 | internal static class DebugCommands 7 | { 8 | internal static void AddCommands() 9 | { 10 | CommandManager.RegisterCommand("loadmission", LoadMission); 11 | CommandManager.RegisterCommand("loadactions", LoadActions); 12 | CommandManager.RegisterCommand("dscan", DScanReplacement); 13 | } 14 | 15 | private static void LoadMission(OS os, string[] args) 16 | { 17 | os.currentMission = Replacements.MissionLoader.LoadContentMission(string.Join(" ", args.Skip(1)).ContentFilePath()); 18 | os.currentMission.sendEmail(os); 19 | } 20 | 21 | private static void LoadActions(OS os, string[] args) 22 | { 23 | RunnableConditionalActions.LoadIntoOS(string.Join(" ", args.Skip(1)), os); 24 | } 25 | 26 | private static void DScanReplacement(OS os, string[] args) 27 | { 28 | if (args.Length < 2) 29 | { 30 | os.write("No Node ID Given"); 31 | return; 32 | } 33 | var comp = ComputerLookup.FindById(args[1]); 34 | if (comp != null) 35 | { 36 | os.netMap.discoverNode(comp); 37 | comp.highlightFlashTime = 1f; 38 | } 39 | else 40 | { 41 | os.write("Node ID Not found"); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /PathfinderAPI/Daemon/BaseDaemon.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | using Pathfinder.Util; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Daemon; 7 | 8 | public abstract class BaseDaemon : Hacknet.Daemon 9 | { 10 | public BaseDaemon(Computer computer, string serviceName, OS opSystem) : base(computer, serviceName, opSystem) 11 | { 12 | this.name = Identifier; 13 | } 14 | 15 | public virtual string Identifier => this.GetType().Name; 16 | 17 | /// 18 | /// DO NOT USE! This is a stubbed version of the base game method and is never saved by Pathfinder 19 | /// 20 | /// Returns null always 21 | public sealed override string getSaveString() => null; 22 | 23 | public virtual XElement GetSaveElement() 24 | { 25 | return XMLStorageAttribute.WriteToElement(this); 26 | } 27 | 28 | public virtual void LoadFromXml(ElementInfo info) 29 | { 30 | XMLStorageAttribute.ReadFromElement(info, this); 31 | } 32 | } -------------------------------------------------------------------------------- /PathfinderAPI/Daemon/DaemonManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Hacknet; 3 | using Pathfinder.Event; 4 | using Pathfinder.Util.XML; 5 | using Pathfinder.Util; 6 | 7 | namespace Pathfinder.Daemon; 8 | 9 | public static class DaemonManager 10 | { 11 | internal static readonly List CustomDaemons = new List(); 12 | 13 | static DaemonManager() 14 | { 15 | EventManager.onPluginUnload += onPluginUnload; 16 | } 17 | 18 | internal static bool TryLoadCustomDaemon(ElementInfo info, Computer comp, OS os) 19 | { 20 | var daemonType = CustomDaemons.FirstOrDefault(x => x.Name == info.Name); 21 | if (daemonType != null) 22 | { 23 | BaseDaemon daemon = (BaseDaemon)Activator.CreateInstance(daemonType, new object[] { comp, info.Name, os }); 24 | daemon.LoadFromXml(info); 25 | comp.daemons.Add(daemon); 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | private static void onPluginUnload(Assembly pluginAsm) 33 | { 34 | CustomDaemons.RemoveAll(x => x.Assembly == pluginAsm); 35 | } 36 | 37 | public static void RegisterDaemon() where T : BaseDaemon => RegisterDaemon(typeof(T)); 38 | public static void RegisterDaemon(Type daemonType) 39 | { 40 | daemonType.ThrowNotInherit(nameof(daemonType)); 41 | CustomDaemons.Add(daemonType); 42 | } 43 | 44 | public static void UnregisterDaemon() where T : BaseDaemon => UnregisterDaemon(typeof(T)); 45 | public static void UnregisterDaemon(Type daemonType) 46 | { 47 | CustomDaemons.Remove(daemonType); 48 | } 49 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/BepInEx/LoadEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using HarmonyLib; 4 | 5 | namespace Pathfinder.Event.BepInEx; 6 | 7 | [HarmonyPatch] 8 | public class LoadEvent : PathfinderEvent 9 | { 10 | [HarmonyPostfix] 11 | [HarmonyPatch(typeof(HacknetChainloader), nameof(HacknetChainloader.LoadPlugin))] 12 | private static void OnPluginLoad(ref HacknetChainloader __instance, ref HacknetPlugin __result, Assembly pluginAssembly) 13 | { 14 | var evt = new LoadEvent(); 15 | EventManager.InvokeAssembly(pluginAssembly, evt); 16 | } 17 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/BepInEx/PostLoadEvent.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.Event.BepInEx; 5 | 6 | [HarmonyPatch] 7 | public class PostLoadEvent : PathfinderEvent 8 | { 9 | [HarmonyPostfix] 10 | [HarmonyPatch(typeof(HacknetPlugin), nameof(HacknetPlugin.PostLoad))] 11 | private static void OnPluginPostLoad(ref HacknetPlugin __instance) 12 | { 13 | var evt = new PostLoadEvent(); 14 | EventManager.InvokeAssembly(__instance.GetType().Assembly, evt); 15 | } 16 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/BepInEx/UnloadEvent.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.Event.BepInEx; 5 | 6 | [HarmonyPatch] 7 | public class UnloadEvent : PathfinderEvent 8 | { 9 | [HarmonyPrefix] 10 | [HarmonyPatch(typeof(HacknetPlugin), nameof(HacknetPlugin.Unload))] 11 | private static void OnPluginUnload(ref HacknetPlugin __instance) 12 | { 13 | var evt = new UnloadEvent(); 14 | EventManager.InvokeAssembly(__instance.GetType().Assembly, evt); 15 | } 16 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Gameplay/CommandExecuteEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.Event.Gameplay; 5 | 6 | [HarmonyPatch] 7 | public class CommandExecuteEvent : PathfinderEvent 8 | { 9 | public OS Os { get; } 10 | public string[] Args { get; set; } 11 | private bool found = false; 12 | public bool Found 13 | { 14 | get => found; 15 | set => found |= value; 16 | } 17 | 18 | public CommandExecuteEvent(OS os, string[] args) 19 | { 20 | Os = os; 21 | Args = args; 22 | } 23 | 24 | [HarmonyPrefix] 25 | [HarmonyPatch(typeof(ProgramRunner), nameof(ProgramRunner.ExecuteProgram))] 26 | private static bool OnCommandExecutePrefix(ref object os_object, ref string[] arguments, ref bool __result) 27 | { 28 | var commandExecuteEvent = new CommandExecuteEvent((OS)os_object, arguments); 29 | EventManager.InvokeAll(commandExecuteEvent); 30 | 31 | arguments = commandExecuteEvent.Args; 32 | __result = commandExecuteEvent.Found; 33 | return !commandExecuteEvent.Cancelled; 34 | } 35 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Gameplay/ExecutableExecuteEvent.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using HarmonyLib; 3 | using MonoMod.Cil; 4 | using Mono.Cecil.Cil; 5 | using Hacknet; 6 | 7 | namespace Pathfinder.Event.Gameplay; 8 | 9 | [HarmonyPatch] 10 | public class ExecutableExecuteEvent : PathfinderEvent 11 | { 12 | public Computer Computer { get; private set; } 13 | public OS OS { get; private set; } 14 | public string ExecutableName 15 | { 16 | get { return ExeFile?.name; } 17 | set 18 | { 19 | if (ExeFile == null || value == null) 20 | return; 21 | ExeFile.name = value; 22 | } 23 | } 24 | public string ExecutableData 25 | { 26 | get { return ExeFile?.data; } 27 | set 28 | { 29 | if (ExeFile == null || value == null) 30 | return; 31 | ExeFile.data = value; 32 | } 33 | } 34 | public List Arguments { get; private set; } 35 | public Folder ExeFolder { get; private set; } 36 | public int FileIndex { get; private set; } 37 | public FileEntry ExeFile { get; private set; } 38 | private ExecutionResult _result = ExecutionResult.NotFound; 39 | public ExecutionResult Result 40 | { 41 | get => _result; 42 | set 43 | { 44 | _result = value; 45 | if (value == ExecutionResult.Cancelled) 46 | Cancelled = true; 47 | } 48 | } 49 | 50 | public ExecutableExecuteEvent(Computer com, OS os, Folder fol, int finde, FileEntry file, string[] args) 51 | { 52 | Computer = com; 53 | OS = os; 54 | ExeFolder = fol; 55 | FileIndex = finde; 56 | ExeFile = file; 57 | Arguments = new List(args ?? new string[0]); 58 | } 59 | 60 | public string this[int index] 61 | { 62 | get 63 | { 64 | if (Arguments.Count <= index) 65 | return ""; 66 | return Arguments[index]; 67 | } 68 | } 69 | 70 | delegate int InjectDelegate(ref Computer com, ref Folder fol, ref int founde, ref string exeData, ref OS os, ref string[] args); 71 | 72 | [HarmonyILManipulator] 73 | [HarmonyPatch(typeof(ProgramRunner), nameof(ProgramRunner.AttemptExeProgramExecution))] 74 | private static void onExecutableExecuteIL(ILContext il, ILLabel retLabel) 75 | { 76 | ILCursor c = new ILCursor(il); 77 | 78 | c.GotoNext(MoveType.Before, 79 | x => x.MatchNop(), 80 | x => x.MatchLdloc(1), 81 | x => x.MatchLdfld(AccessTools.Field(typeof(Folder), nameof(Folder.files))) 82 | ); 83 | 84 | c.Emit(OpCodes.Ldloca, 0); 85 | c.Emit(OpCodes.Ldloca, 1); 86 | c.Emit(OpCodes.Ldloca, 2); 87 | c.Emit(OpCodes.Ldloca, 6); 88 | c.Emit(OpCodes.Ldarga, 0); 89 | c.Emit(OpCodes.Ldarga, 1); 90 | 91 | c.EmitDelegate((ref Computer com, ref Folder fol, ref int founde, ref string exeData, ref OS os, ref string[] args) => 92 | { 93 | FileEntry f = fol.files[founde]; 94 | 95 | var executableExecuteEvent = new ExecutableExecuteEvent(com, os, fol, founde, f, args); 96 | EventManager.InvokeAll(executableExecuteEvent); 97 | 98 | return (int)(executableExecuteEvent.Cancelled ? ExecutionResult.Cancelled : executableExecuteEvent.Result); 99 | }); 100 | 101 | c.Emit(OpCodes.Dup); 102 | c.Emit(OpCodes.Ldc_I4_0); 103 | var label = c.DefineLabel(); 104 | c.Emit(OpCodes.Blt, label); 105 | c.Emit(OpCodes.Br, retLabel); 106 | c.Emit(OpCodes.Pop); 107 | label.Target = c.Prev; 108 | } 109 | } 110 | 111 | [DefaultValue(NotFound)] 112 | public enum ExecutionResult 113 | { 114 | NotFound = -1, 115 | Error = 0, 116 | StartupSuccess = 1, 117 | Cancelled, 118 | } 119 | -------------------------------------------------------------------------------- /PathfinderAPI/Event/Gameplay/ExecutableListEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.Event.Gameplay; 5 | 6 | [HarmonyPatch] 7 | public class ExecutableListEvent : PathfinderEvent 8 | { 9 | public OS OS { get; } 10 | 11 | public List EmbeddedExes { get; } = new List 12 | { 13 | "PortHack", "ForkBomb", "Shell", "Tutorial" 14 | }; 15 | public Dictionary BinExes { get; } 16 | 17 | public ExecutableListEvent(OS os, Dictionary binExes) 18 | { 19 | OS = os; 20 | BinExes = binExes; 21 | } 22 | 23 | [HarmonyPrefix] 24 | [HarmonyPatch(typeof(Programs), nameof(Programs.execute))] 25 | private static bool ProgramsExecuteReplacement(string[] args, OS os){ 26 | var binFiles = os.thisComputer.files.root.searchForFolder("bin").files; // folders[2].files; 27 | 28 | var binExes = new Dictionary(); 29 | foreach (FileEntry exeFile in binFiles) 30 | binExes[exeFile] = 31 | PortExploits.crackExeData .Any(x => x.Value == exeFile.data) || 32 | PortExploits.crackExeDataLocalRNG.Any(x => x.Value == exeFile.data); 33 | 34 | var programsExecute = new ExecutableListEvent(os, binExes); 35 | EventManager.InvokeAll(programsExecute); 36 | 37 | os.write("Available Executables:\n"); 38 | 39 | foreach (string embedded in programsExecute.EmbeddedExes) 40 | os.write(embedded); 41 | foreach (FileEntry file in binFiles.Where(x => binExes[x])) 42 | os.write(file.name.Replace(".exe", "")); 43 | 44 | os.write(" "); 45 | return false; 46 | } 47 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Gameplay/OSUpdateEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Microsoft.Xna.Framework; 4 | 5 | namespace Pathfinder.Event.Gameplay; 6 | 7 | [HarmonyPatch] 8 | public class OSUpdateEvent : PathfinderEvent 9 | { 10 | public OS OS { get; } 11 | public GameTime GameTime { get; } 12 | 13 | public OSUpdateEvent(OS os, GameTime gameTime) 14 | { 15 | OS = os; 16 | GameTime = gameTime; 17 | } 18 | 19 | [HarmonyPostfix] 20 | [HarmonyPatch(typeof(OS), nameof(OS.Update))] 21 | private static void OSUpdatePostfix(OS __instance, GameTime gameTime) 22 | { 23 | var osUpdate = new OSUpdateEvent(__instance, gameTime); 24 | EventManager.InvokeAll(osUpdate); 25 | } 26 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Loading/ExtensionLoadEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Extensions; 3 | using Hacknet.Gui; 4 | using Hacknet.Screens; 5 | using HarmonyLib; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace Pathfinder.Event.Loading; 9 | 10 | [HarmonyPatch] 11 | public class ExtensionLoadEvent : PathfinderEvent 12 | { 13 | public ExtensionInfo Info { get; } 14 | public bool Unload { get; } 15 | 16 | public ExtensionLoadEvent(ExtensionInfo info, bool unload) 17 | { 18 | Info = info; 19 | Unload = unload; 20 | } 21 | 22 | [HarmonyPostfix] 23 | [HarmonyPatch(typeof(ExtensionsMenuScreen), nameof(ExtensionsMenuScreen.ActivateExtensionPage))] 24 | internal static void ExtensionLoadPostfix(ExtensionInfo info) 25 | { 26 | var loadEvent = new ExtensionLoadEvent(info, false); 27 | EventManager.InvokeAll(loadEvent); 28 | } 29 | 30 | [HarmonyPrefix] 31 | [HarmonyPatch(typeof(OS), nameof(OS.quitGame))] 32 | private static void OSQuitPrefix() 33 | { 34 | if (Settings.IsInExtensionMode) 35 | { 36 | var unloadEvent = new ExtensionLoadEvent(ExtensionLoader.ActiveExtensionInfo, true); 37 | EventManager.InvokeAll(unloadEvent); 38 | } 39 | } 40 | 41 | // I would hook Hacknet.Screens.DrawExtensionInfoDetail instead, but for some reason that method is cursed, so I look here instead 42 | [HarmonyPostfix] 43 | [HarmonyPatch(typeof(Button), nameof(Button.doButton), new Type[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(string), typeof(Color?) })] 44 | [HarmonyBefore("BepInEx.Hacknet.Chainloader")] 45 | private static void OnBackButtonPressPostfix(int myID, bool __result) 46 | { 47 | if (myID == 7900040 && __result) 48 | { 49 | var unloadEvent = new ExtensionLoadEvent(ExtensionLoader.ActiveExtensionInfo, true); 50 | EventManager.InvokeAll(unloadEvent); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Loading/OSLoadedEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | 4 | namespace Pathfinder.Event.Loading; 5 | 6 | [HarmonyPatch] 7 | public class OSLoadedEvent : PathfinderEvent 8 | { 9 | public OS Os { get; } 10 | 11 | public OSLoadedEvent(OS os) 12 | { 13 | Os = os; 14 | } 15 | 16 | [HarmonyPostfix] 17 | [HarmonyPatch(typeof(OS), nameof(OS.LoadContent))] 18 | private static void OSLoadPostfix(OS __instance) => EventManager.InvokeAll(new OSLoadedEvent(__instance)); 19 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Loading/SaveComputerLoadedEvent.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using Hacknet; 3 | using Pathfinder.Util.XML; 4 | 5 | namespace Pathfinder.Event.Loading; 6 | 7 | [HarmonyPatch] 8 | public class SaveComputerLoadedEvent : PathfinderEvent 9 | { 10 | public OS Os { get; } 11 | public Computer Comp { get; } 12 | public ElementInfo Info { get; } 13 | 14 | public SaveComputerLoadedEvent(OS os, Computer comp, ElementInfo info) 15 | { 16 | Os = os; 17 | Comp = comp; 18 | Info = info; 19 | } 20 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Loading/TextReplaceEvent.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using Hacknet; 3 | 4 | namespace Pathfinder.Event.Loading; 5 | 6 | [HarmonyPatch] 7 | public class TextReplaceEvent : PathfinderEvent 8 | { 9 | public string Original { get; } 10 | public string Replacement { get; set; } 11 | 12 | public TextReplaceEvent(string original, string replacement) 13 | { 14 | Original = original; 15 | Replacement = replacement; 16 | } 17 | 18 | [HarmonyPostfix] 19 | [HarmonyPatch(typeof(ComputerLoader), nameof(ComputerLoader.filter))] 20 | private static void TextFilterPostfix(ref string s, ref string __result) 21 | { 22 | var textReplaceEvent = new TextReplaceEvent(s, __result); 23 | EventManager.InvokeAll(textReplaceEvent); 24 | __result = textReplaceEvent.Replacement; 25 | } 26 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Menu/DrawMainMenuEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using HarmonyLib; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Cil; 5 | 6 | namespace Pathfinder.Event.Menu; 7 | 8 | [HarmonyPatch] 9 | public class DrawMainMenuEvent : MainMenuEvent 10 | { 11 | public DrawMainMenuEvent(MainMenu mainMenu) : base(mainMenu) 12 | { 13 | } 14 | 15 | [HarmonyILManipulator] 16 | [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.Draw))] 17 | private static void AfterMainMenuDraw(ILContext il) 18 | { 19 | ILCursor c = new ILCursor(il); 20 | 21 | c.GotoNext(MoveType.AfterLabel, 22 | x => x.MatchCallOrCallvirt(typeof(GuiData), nameof(GuiData.endDraw)) 23 | ); 24 | 25 | c.Emit(OpCodes.Ldarg_0); 26 | c.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(typeof(DrawMainMenuEvent), new Type[] { typeof(MainMenu) })); 27 | c.Emit(OpCodes.Call, AccessTools.DeclaredMethod(typeof(EventManager), nameof(EventManager.InvokeAll))); 28 | } 29 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Hacknet; 2 | using HarmonyLib; 3 | using MonoMod.Cil; 4 | using Mono.Cecil.Cil; 5 | using Hacknet; 6 | using Hacknet.Gui; 7 | using Hacknet.Effects; 8 | using Microsoft.Xna.Framework; 9 | using Microsoft.Xna.Framework.Graphics; 10 | 11 | namespace Pathfinder.Event.Menu; 12 | 13 | [HarmonyPatch] 14 | public class DrawMainMenuTitlesEvent : MainMenuEvent 15 | { 16 | public DrawMainMenuTitlesEvent(MainMenu menu, TitleData main, TitleData sub) : base(menu) { Main = main; Sub = sub; } 17 | 18 | public TitleData Main { get; private set; } 19 | public TitleData Sub { get; private set; } 20 | 21 | static Color defaultTitleColor = new Color(190, 190, 190, 0); 22 | static SpriteFont defaultTitleFont; 23 | 24 | delegate void EmitDelegate(MainMenu menu, ref Rectangle rect); 25 | 26 | [HarmonyILManipulator] 27 | [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.DrawBackgroundAndTitle))] 28 | private static void onDrawMainMenuTitlesIL(ILContext il) 29 | { 30 | ILCursor c = new ILCursor(il); 31 | 32 | c.GotoNext(MoveType.After, 33 | x => x.MatchCall(AccessTools.Constructor(typeof(Rectangle), new Type[] { typeof(int), typeof(int), typeof(int), typeof(int) })) 34 | ); 35 | 36 | c.Emit(OpCodes.Ldarg_0); 37 | c.Emit(OpCodes.Ldloca, 0); 38 | c.EmitDelegate((MainMenu self, ref Rectangle dest) => 39 | { 40 | if (defaultTitleFont == null) defaultTitleFont = self.ScreenManager.Game.Content.Load("Kremlin"); 41 | 42 | var version = HacknetChainloader.VERSION; 43 | var mainTitle = "HACKNET"; 44 | var subtitle = "OS" 45 | + (DLC1SessionUpgrader.HasDLC1Installed ? "+Labyrinths " : " ") 46 | + MainMenu.OSVersion + " Pathfinder " + version; 47 | 48 | var main = new TitleData(mainTitle, 49 | defaultTitleColor, 50 | defaultTitleFont, 51 | dest 52 | ); 53 | var sub = new TitleData(subtitle, 54 | main.Color * 0.5f, 55 | GuiData.smallfont, 56 | new Rectangle(520, 178, 0, 0) 57 | ); 58 | 59 | var drawMainMenuTitles = new DrawMainMenuTitlesEvent(self, main, sub); 60 | EventManager.InvokeAll(drawMainMenuTitles); 61 | 62 | main = drawMainMenuTitles.Main; 63 | sub = drawMainMenuTitles.Sub; 64 | FlickeringTextEffect.DrawLinedFlickeringText( 65 | dest = main.Destination, 66 | main.Title, 67 | 7f, 68 | 0.55f, 69 | main.Font, 70 | null, 71 | main.Color 72 | ); 73 | TextItem.doFontLabel(new Vector2(sub.Destination.Location.X, sub.Destination.Location.Y), sub.Title, sub.Font, sub.Color, 600f, 26f); 74 | }); 75 | 76 | var firstLabel = c.MarkLabel(); 77 | c.GotoNext(MoveType.Before, 78 | x => x.MatchCall(AccessTools.Method( 79 | typeof(TextItem), 80 | nameof(TextItem.doFontLabel), 81 | new Type[] { typeof(Vector2), typeof(string), typeof(SpriteFont), typeof(Color?), typeof(float), typeof(float), typeof(bool) } 82 | ) 83 | ) 84 | ); 85 | var endInst = c.Index; 86 | 87 | c.GotoLabel(firstLabel, MoveType.Before); 88 | c.RemoveRange((endInst - c.Index) + 1); 89 | } 90 | 91 | public class TitleData 92 | { 93 | public TitleData(string title, Color color, SpriteFont font, Rectangle dest) 94 | { 95 | Title = title; 96 | Color = color; 97 | Font = font; 98 | Destination = dest; 99 | } 100 | public string Title { get; set; } 101 | public Color Color { get; set; } 102 | public SpriteFont Font { get; set; } 103 | public Rectangle Destination { get; set; } 104 | } 105 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Menu/MainMenuEvent.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | 3 | namespace Pathfinder.Event.Menu; 4 | 5 | public abstract class MainMenuEvent : PathfinderEvent 6 | { 7 | public MainMenu MainMenu { get; private set; } 8 | public MainMenuEvent(MainMenu mainMenu) { MainMenu = mainMenu; } 9 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Options/CustomOptionsSaveEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Event.Options; 2 | 3 | public class CustomOptionsSaveEvent : PathfinderEvent 4 | { 5 | public CustomOptionsSaveEvent() { } 6 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Pathfinder/BuildAutocompletesEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Event.Pathfinder; 2 | 3 | public class BuildAutocompletesEvent : PathfinderEvent 4 | { 5 | public List Autocompletes { get; set; } 6 | 7 | public BuildAutocompletesEvent(List autocompletes) 8 | { 9 | Autocompletes = autocompletes; 10 | } 11 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/PathfinderEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Event; 2 | 3 | public abstract class PathfinderEvent 4 | { 5 | internal bool cancelled = false; 6 | public bool Cancelled { 7 | get 8 | { 9 | return cancelled; 10 | } 11 | set 12 | { 13 | cancelled |= value; 14 | } 15 | } 16 | public bool Thrown { get; internal set; } = false; 17 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Saving/SaveComputerEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | 4 | namespace Pathfinder.Event.Saving; 5 | 6 | public class SaveComputerEvent : PathfinderEvent 7 | { 8 | public OS Os { get; } 9 | public Computer Comp { get; } 10 | public XElement Element { get; set; } 11 | 12 | public SaveComputerEvent(OS os, Computer comp, XElement element) 13 | { 14 | Os = os; 15 | Comp = comp; 16 | Element = element; 17 | } 18 | } -------------------------------------------------------------------------------- /PathfinderAPI/Event/Saving/SaveEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Hacknet; 3 | 4 | namespace Pathfinder.Event.Saving; 5 | 6 | public class SaveEvent : PathfinderEvent 7 | { 8 | public OS Os { get; } 9 | public XElement Save { get; } 10 | public string Filename { get; } 11 | 12 | public SaveEvent(OS os, XElement save, string filename) 13 | { 14 | Os = os; 15 | Save = save; 16 | Filename = filename; 17 | } 18 | } -------------------------------------------------------------------------------- /PathfinderAPI/Executable/BaseExecutable.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Microsoft.Xna.Framework; 3 | 4 | namespace Pathfinder.Executable; 5 | 6 | public abstract class BaseExecutable : ExeModule 7 | { 8 | [Obsolete("To be removed in 6.0.0")] 9 | public virtual string GetIdentifier() => null; 10 | 11 | public string[] Args; 12 | 13 | public BaseExecutable(Rectangle location, OS operatingSystem, string[] args) : base(location, operatingSystem) 14 | { 15 | Args = args; 16 | } 17 | } -------------------------------------------------------------------------------- /PathfinderAPI/Executable/ExeModuleExtensions.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | 3 | namespace Pathfinder.Executable; 4 | 5 | public static class ExeModuleExtensions 6 | { 7 | public static bool CanKill(this ExeModule module) => 8 | !( 9 | !module.os.exes.Contains(module) || 10 | ((module is GameExecutable gameExe) && !gameExe.CanBeKilled) || 11 | ((module is DLCIntroExe introExe) && !((introExe.State == DLCIntroExe.IntroState.NotStarted) || (introExe.State == DLCIntroExe.IntroState.Exiting))) || 12 | ((module is ExtensionSequencerExe seqExe) && (seqExe.state == ExtensionSequencerExe.SequencerExeState.Active)) 13 | ); 14 | 15 | public static bool Kill(this ExeModule module) 16 | { 17 | if(!module.CanKill()) 18 | return false; 19 | module.Killed(); 20 | return module.os.exes.Remove(module); 21 | } 22 | } -------------------------------------------------------------------------------- /PathfinderAPI/GUI/ArbitraryCodeWarning.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Extensions; 3 | using Hacknet.Gui; 4 | using Hacknet.Screens; 5 | using HarmonyLib; 6 | using Microsoft.Xna.Framework; 7 | using Mono.Cecil; 8 | using Pathfinder.Event; 9 | using Pathfinder.Event.Menu; 10 | 11 | namespace Pathfinder.GUI; 12 | 13 | [HarmonyPatch] 14 | internal static class ArbitraryCodeWarning 15 | { 16 | private static ExtensionInfo needsApproval = null; 17 | private static ExtensionInfo approvedInfo = null; 18 | private static string messages = null; 19 | private static ExtensionsMenuScreen screen = null; 20 | 21 | [Util.Initialize] 22 | internal static void Initialize() 23 | { 24 | EventManager.AddHandler(OnDrawMainMenu); 25 | } 26 | 27 | private static PFButton Continue = new PFButton(650, 0, 130, 20, "Continue", new Color(255, 110, 110)); 28 | private static PFButton Cancel = new PFButton(800, 0, 130, 20, "Cancel", new Color(50, 50, 50)); 29 | 30 | private static void OnDrawMainMenu(DrawMainMenuEvent args) 31 | { 32 | if (needsApproval == null) 33 | return; 34 | 35 | GuiData.spriteBatch.Draw(Utils.white, 36 | new Rectangle(0, 0, GuiData.spriteBatch.GraphicsDevice.Viewport.Width, 37 | GuiData.spriteBatch.GraphicsDevice.Viewport.Height), new Color(0, 0, 0, 0.75f)); 38 | 39 | TextItem.doLabel(new Vector2(650, 230), "Arbitary Code Warning", new Color(255, 130, 130)); 40 | var endPfMessage = (int)TextItem.doMeasuredSmallLabel(new Vector2(650, 270), 41 | Utils.SuperSmartTwimForWidth( 42 | $"The extension {needsApproval.Name} contains DLLs inside the plugin folder that Pathfinder will attempt to load.\nThis will allow whatever code is in that DLL to be ran on your machine.\nPlease confirm that you acknowledge this and are comfortable with loading these plugins.\n\nLoading from {needsApproval.GetFullFolderPath()}", 43 | GuiData.spriteBatch.GraphicsDevice.Viewport.Width - 660, GuiData.smallfont), Color.White).Y + 280; 44 | var messageTextEnd = (int)TextItem.doMeasuredTinyLabel(new Vector2(650, endPfMessage), messages, Color.White).Y + 45 | endPfMessage + 10; 46 | Continue.Y = messageTextEnd; 47 | Cancel.Y = messageTextEnd; 48 | if (Continue.Do()) 49 | { 50 | approvedInfo = needsApproval; 51 | needsApproval = null; 52 | screen.ActivateExtensionPage(approvedInfo); 53 | } 54 | else if (Cancel.Do()) 55 | { 56 | needsApproval = null; 57 | } 58 | } 59 | 60 | [HarmonyPrefix] 61 | [HarmonyBefore("BepInEx.Hacknet.Chainloader")] 62 | [HarmonyPatch(typeof(ExtensionsMenuScreen), nameof(ExtensionsMenuScreen.ActivateExtensionPage))] 63 | private static bool ShowArbitraryWarning(ExtensionsMenuScreen __instance, ExtensionInfo info) 64 | { 65 | screen = __instance; 66 | if (approvedInfo == info) 67 | { 68 | approvedInfo = null; 69 | return true; 70 | } 71 | 72 | approvedInfo = null; 73 | 74 | string[] dlls = null; 75 | try 76 | { 77 | dlls = Directory.GetFiles(Path.Combine(info.GetFullFolderPath(), "Plugins"), "*.dll", 78 | SearchOption.AllDirectories); 79 | } 80 | catch (DirectoryNotFoundException) 81 | { 82 | return true; 83 | } 84 | 85 | List warnings = new List(); 86 | foreach (var dll in dlls) 87 | { 88 | try 89 | { 90 | using (var asm = AssemblyDefinition.ReadAssembly(dll)) 91 | { 92 | foreach (var plugin in asm.MainModule.Types) 93 | { 94 | if (!plugin.HasCustomAttributes) 95 | continue; 96 | var pluginInfo = 97 | plugin.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "BepInPlugin"); 98 | if (pluginInfo != null) 99 | warnings.Add( 100 | $"Plugin with the name \"{pluginInfo.ConstructorArguments[1].Value}\" from {Path.GetFileName(dll)}"); 101 | } 102 | } 103 | } 104 | catch 105 | { 106 | warnings.Add($"Could not process DLL {dll}, it's possibly dangerous!"); 107 | } 108 | } 109 | 110 | if (warnings.Count == 0) 111 | return true; 112 | 113 | needsApproval = info; 114 | messages = Utils.SuperSmartTwimForWidth( 115 | string.Join("\n", warnings), 116 | GuiData.spriteBatch.GraphicsDevice.Viewport.Width - 660, GuiData.tinyfont 117 | ); 118 | return false; 119 | } 120 | } -------------------------------------------------------------------------------- /PathfinderAPI/GUI/ExtensionListScroll.cs: -------------------------------------------------------------------------------- 1 | using Hacknet; 2 | using Hacknet.Screens; 3 | using HarmonyLib; 4 | using Microsoft.Xna.Framework; 5 | using Mono.Cecil.Cil; 6 | using MonoMod.Cil; 7 | 8 | namespace Pathfinder.GUI; 9 | 10 | [HarmonyPatch] 11 | internal static class ExtensionListScroll { 12 | 13 | [HarmonyPatch(typeof(ExtensionsMenuScreen), nameof(ExtensionsMenuScreen.DrawExtensionList))] 14 | [HarmonyILManipulator] 15 | private static void AddExtensionScrollWheelListenerManipulator(ILContext context) { 16 | ILCursor cursor = new(context); 17 | 18 | /* Matches the loop header `for(int loc1 = this.ScrollStartIndex; … this.Extensions…)` */ 19 | cursor.GotoNext(MoveType.Before, 20 | i => i.MatchLdarg(0), 21 | i => i.MatchLdfld("ScrollStartIndex"), 22 | i => i.MatchStloc(1), 23 | i => 24 | i.MatchBr(out ILLabel label) && 25 | label.Target.MatchLdloc(1) && 26 | label.Target.Next.MatchLdarg(0) && 27 | label.Target.Next.Next.MatchLdfld("Extensions") 28 | ); 29 | 30 | cursor.Emit(OpCodes.Ldarg_0); 31 | cursor.Emit(OpCodes.Ldarg_1); 32 | cursor.Emit(OpCodes.Ldloc_0); 33 | cursor.EmitDelegate(ScrollUpdate); 34 | } 35 | 36 | private static void ScrollUpdate(ExtensionsMenuScreen self, Vector2 drawPos, Rectangle fullScreen) { 37 | const double bottomMargin = 140; 38 | const int itemHeight = 55; 39 | int extensionsOnScreen = (int) ((fullScreen.Height - drawPos.Y - bottomMargin) / itemHeight) + 1; 40 | int bottomMostPosition = Math.Max(self.Extensions.Count - extensionsOnScreen, 0); 41 | 42 | int newPosition = self.ScrollStartIndex + (int) GuiData.getMouseWheelScroll(); 43 | if(newPosition < 0) 44 | newPosition = 0; 45 | else if(newPosition > bottomMostPosition) 46 | newPosition = bottomMostPosition; 47 | self.ScrollStartIndex = newPosition; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /PathfinderAPI/GUI/PFButton.cs: -------------------------------------------------------------------------------- 1 | using Hacknet.Gui; 2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | 5 | namespace Pathfinder.GUI; 6 | 7 | public class PFButton : IDisposable 8 | { 9 | private static int _idCounter = int.MinValue; 10 | private static readonly List returnedIds = new List(); 11 | public static int GetNextID() 12 | { 13 | if (returnedIds.Count > 0) 14 | { 15 | var ret = returnedIds[0]; 16 | returnedIds.RemoveAt(0); 17 | return ret; 18 | } 19 | 20 | return _idCounter++; 21 | } 22 | public static void ReturnID(int id) 23 | { 24 | returnedIds.Add(id); 25 | } 26 | 27 | public readonly int ID = GetNextID(); 28 | 29 | public int X; 30 | public int Y; 31 | public int Height; 32 | public int Width; 33 | public string Text; 34 | public Color? Color; 35 | public Texture2D Texture; 36 | 37 | private bool invalid = false; 38 | 39 | public PFButton(int x, int y, int width, int height, string text, Color? color = null, Texture2D texture = null) 40 | { 41 | X = x; 42 | Y = y; 43 | Width = width; 44 | Height = height; 45 | Text = text ?? throw new ArgumentNullException(nameof(text), "Button text cannot be null!"); 46 | Color = color; 47 | Texture = texture; 48 | } 49 | 50 | public bool Do() => 51 | DoAt(X, Y); 52 | public bool Do(Point offset) => 53 | Do(offset.X, offset.Y); 54 | public bool Do(Rectangle offset) => 55 | Do(offset.X, offset.Y); 56 | public bool Do(Vector2 offset) => 57 | Do((int) offset.X, (int) offset.Y); 58 | public bool Do(int offsetX, int offsetY) => 59 | DoAt(X + offsetX, Y + offsetY); 60 | private bool DoAt(int x, int y) 61 | { 62 | if (invalid) 63 | throw new ObjectDisposedException(nameof(PFButton), "This Button has been disposed, and is no longer valid"); 64 | if (Texture == null) 65 | return Button.doButton(ID, x, y, Width, Height, Text, Color); 66 | return Button.doButton(ID, x, y, Width, Height, Text, Color, Texture); 67 | } 68 | 69 | public void Dispose() 70 | { 71 | if (invalid) 72 | return; 73 | returnedIds.Add(ID); 74 | invalid = true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /PathfinderAPI/Logger.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Logging; 2 | 3 | namespace Pathfinder; 4 | 5 | internal static class Logger 6 | { 7 | internal static ManualLogSource LogSource; 8 | 9 | internal static void Log(LogLevel severity, object msg) => LogSource.Log(severity, msg); 10 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/ActionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Action; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 8 | public class ActionAttribute : BaseAttribute 9 | { 10 | public string XmlName { get; } 11 | 12 | public ActionAttribute(string xmlName) 13 | { 14 | this.XmlName = xmlName; 15 | } 16 | 17 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 18 | { 19 | ActionManager.RegisterAction((Type)targettedInfo, XmlName); 20 | } 21 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/AdministratorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Administrator; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 8 | public class AdministratorAttribute : BaseAttribute 9 | { 10 | public string XmlName { get; } 11 | 12 | public AdministratorAttribute() 13 | { 14 | } 15 | public AdministratorAttribute(string xmlName) 16 | { 17 | this.XmlName = xmlName; 18 | } 19 | 20 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 21 | { 22 | if (XmlName == null) 23 | AdministratorManager.RegisterAdministrator((Type)targettedInfo); 24 | else 25 | AdministratorManager.RegisterAdministrator((Type)targettedInfo, XmlName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/AttributeManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using HarmonyLib; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | 7 | namespace Pathfinder.Meta.Load; 8 | 9 | [HarmonyPatch] 10 | internal static class AttributeManager 11 | { 12 | [HarmonyILManipulator] 13 | [HarmonyPatch(typeof(HacknetChainloader), nameof(HacknetChainloader.LoadPlugin))] 14 | private static void OnPluginLoadIL(ILContext il) 15 | { 16 | var c = new ILCursor(il); 17 | 18 | c.GotoNext( 19 | x => x.MatchCallvirt(AccessTools.Method(typeof(HacknetPlugin), nameof(HacknetPlugin.Load))) 20 | ); 21 | 22 | c.Emit(OpCodes.Dup); 23 | c.Emit(OpCodes.Call, AccessTools.Method(typeof(AttributeManager), nameof(ReadAttributesFor))); 24 | } 25 | 26 | public static void ReadAttributesFor(HacknetPlugin plugin) 27 | { 28 | var pluginType = plugin.GetType(); 29 | if(pluginType.GetCustomAttribute() != null) 30 | return; 31 | ReadAttributesOnType(plugin, pluginType); 32 | foreach(var type in pluginType.Assembly.GetTypes()) 33 | { 34 | if(type == pluginType) continue; 35 | ReadAttributesOnType(plugin, type); 36 | } 37 | } 38 | 39 | private static void ReadAttributesOnType(HacknetPlugin plugin, Type type) 40 | { 41 | foreach (var attribute in type.GetCustomAttributes()) 42 | { 43 | attribute.CallOn(plugin, type); 44 | } 45 | foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) 46 | { 47 | if (member.MemberType == MemberTypes.NestedType) 48 | { 49 | ReadAttributesOnType(plugin, (Type)member); 50 | } 51 | else foreach (var attribute in member.GetCustomAttributes()) 52 | { 53 | attribute.CallOn(plugin, member); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/BaseAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | 4 | namespace Pathfinder.Meta.Load; 5 | 6 | public abstract class BaseAttribute : Attribute 7 | { 8 | internal protected abstract void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo); 9 | 10 | internal void ThrowOnInvalidOperation(bool evaluation, string message) 11 | { 12 | if(evaluation) 13 | throw new InvalidOperationException(message); 14 | } 15 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Hacknet; 4 | using Pathfinder.Command; 5 | 6 | namespace Pathfinder.Meta.Load; 7 | 8 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 9 | public class CommandAttribute : BaseAttribute 10 | { 11 | public string CommandName { get; } 12 | public bool AddAutocomplete { get; set; } 13 | public bool CaseSensitive { get; set; } 14 | 15 | public CommandAttribute(string commandName, bool addAutocomplete = true, bool caseSensitive = false) 16 | { 17 | CommandName = commandName; 18 | AddAutocomplete = addAutocomplete; 19 | CaseSensitive = caseSensitive; 20 | } 21 | 22 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 23 | { 24 | var methodInfo = (MethodInfo)targettedInfo; 25 | var commandAction = (Action)methodInfo.CreateDelegate(typeof(Action)); 26 | CommandManager.RegisterCommand(CommandName, commandAction, AddAutocomplete, CaseSensitive); 27 | } 28 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/ComputerExecutorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Replacements; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Meta.Load; 7 | 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public class ComputerExecutorAttribute : BaseAttribute 10 | { 11 | public string Element { get; } 12 | public ParseOption ParseOptions { get; set; } 13 | 14 | public ComputerExecutorAttribute(string element, ParseOption parseOptions = ParseOption.None) 15 | { 16 | Element = element; 17 | ParseOptions = parseOptions; 18 | } 19 | 20 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 21 | { 22 | ContentLoader.RegisterExecutor((Type)targettedInfo, Element, ParseOptions); 23 | } 24 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/ConditionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Action; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 8 | public class ConditionAttribute : BaseAttribute 9 | { 10 | public string XmlName { get; } 11 | 12 | public ConditionAttribute(string xmlName) 13 | { 14 | this.XmlName = xmlName; 15 | } 16 | 17 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 18 | { 19 | ConditionManager.RegisterCondition((Type)targettedInfo, XmlName); 20 | } 21 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/DaemonAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Daemon; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class)] 8 | public class DaemonAttribute : BaseAttribute 9 | { 10 | public DaemonAttribute() 11 | { 12 | } 13 | 14 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 15 | { 16 | DaemonManager.RegisterDaemon((Type)targettedInfo); 17 | } 18 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/EventAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Pathfinder.Event; 3 | using BepInEx.Hacknet; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)] 8 | public class EventAttribute : BaseAttribute 9 | { 10 | private EventHandlerOptions eventHandlerOptions; 11 | public int Priority 12 | { 13 | set => eventHandlerOptions.Priority = value; 14 | } 15 | public bool ContinueOnCancel 16 | { 17 | set => eventHandlerOptions.ContinueOnCancel = value; 18 | } 19 | public bool ContinueOnThrow 20 | { 21 | set => eventHandlerOptions.ContinueOnThrow = value; 22 | } 23 | 24 | public EventAttribute() 25 | { 26 | } 27 | 28 | public EventAttribute(int priority, bool continueOnCancel = false, bool continueOnThrow = false) 29 | { 30 | eventHandlerOptions = new EventHandlerOptions{ 31 | Priority = priority, 32 | ContinueOnCancel = continueOnCancel, 33 | ContinueOnThrow = continueOnThrow 34 | }; 35 | } 36 | 37 | public EventAttribute(bool continueOnCancel = false, bool continueOnThrow = false) 38 | { 39 | eventHandlerOptions = new EventHandlerOptions{ 40 | ContinueOnCancel = continueOnCancel, 41 | ContinueOnThrow = continueOnThrow 42 | }; 43 | } 44 | 45 | private void CallOn(HacknetPlugin plugin, MethodInfo info) 46 | { 47 | if(!info.IsStatic) 48 | throw new InvalidOperationException("EventAttribute can not register event handlers to instance methods"); 49 | var eventType = info.GetParameters().FirstOrDefault()?.ParameterType; 50 | EventManager.AddHandler(eventType, info); 51 | } 52 | 53 | private void CallOn(HacknetPlugin plugin, Type type) 54 | { 55 | foreach(var method in type.GetMethods( 56 | BindingFlags.Public 57 | | BindingFlags.NonPublic 58 | | BindingFlags.Static 59 | )) 60 | { 61 | if(method.GetCustomAttribute() != null) 62 | continue; 63 | var parameter = method.GetParameters().FirstOrDefault(); 64 | if(method.ReturnType == typeof(void) 65 | && (parameter?.ParameterType?.IsSubclassOf(typeof(PathfinderEvent)) ?? false)) 66 | CallOn(plugin, method); 67 | } 68 | } 69 | 70 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 71 | { 72 | if(targettedInfo is MethodInfo method) 73 | CallOn(plugin, method); 74 | else 75 | CallOn(plugin, (Type) targettedInfo); 76 | } 77 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/ExecutableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Executable; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 8 | public class ExecutableAttribute : BaseAttribute 9 | { 10 | public string XmlName { get; } 11 | 12 | public ExecutableAttribute(string xmlName) 13 | { 14 | XmlName = xmlName; 15 | } 16 | 17 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 18 | { 19 | ExecutableManager.RegisterExecutable((Type)targettedInfo, XmlName); 20 | } 21 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/ExtensionInfoExecutorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Replacements; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Meta.Load; 7 | 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public class ExtensionInfoExecutorAttribute : BaseAttribute 10 | { 11 | public string Element { get; } 12 | public ParseOption ParseOptions { get; set; } 13 | 14 | public ExtensionInfoExecutorAttribute(string element, ParseOption parseOptions = ParseOption.None) 15 | { 16 | Element = element; 17 | ParseOptions = parseOptions; 18 | } 19 | 20 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 21 | { 22 | ExtensionInfoLoader.RegisterExecutor((Type)targettedInfo, Element, ParseOptions); 23 | } 24 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/GoalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Mission; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 8 | public class GoalAttribute : BaseAttribute 9 | { 10 | public string XmlName { get; } 11 | 12 | public GoalAttribute(string xmlName) 13 | { 14 | this.XmlName = xmlName; 15 | } 16 | 17 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 18 | { 19 | GoalManager.RegisterGoal((Type)targettedInfo, XmlName); 20 | } 21 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/HacknetPluginExtensions.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Hacknet; 2 | 3 | namespace Pathfinder.Meta.Load; 4 | 5 | public static class HacknetPluginExtensions 6 | { 7 | public static string GetOptionsTag(this HacknetPlugin plugin) 8 | { 9 | if(!OptionsTabAttribute.pluginToOptionsTag.TryGetValue(plugin, out var tag)) 10 | return null; 11 | return tag; 12 | } 13 | 14 | public static bool HasOptionsTag(this HacknetPlugin plugin) 15 | { 16 | return OptionsTabAttribute.pluginToOptionsTag.ContainsKey(plugin); 17 | } 18 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/IgnoreEventAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Meta.Load; 2 | 3 | [AttributeUsage(AttributeTargets.Method)] 4 | public class IgnoreEventAttribute : System.Attribute 5 | { 6 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/IgnorePluginAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Meta.Load; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | public class IgnorePluginAttribute : Attribute 5 | { 6 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/MissionExecutorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Replacements; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Meta.Load; 7 | 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public class MissionExecutorAttribute : BaseAttribute 10 | { 11 | public string Element { get; } 12 | public ParseOption ParseOptions { get; set; } 13 | 14 | public MissionExecutorAttribute(string element, ParseOption parseOptions = ParseOption.None) 15 | { 16 | Element = element; 17 | ParseOptions = parseOptions; 18 | } 19 | 20 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 21 | { 22 | MissionLoader.RegisterExecutor((Type)targettedInfo, Element, ParseOptions); 23 | } 24 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/OptionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Options; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] 8 | public class OptionAttribute : BaseAttribute 9 | { 10 | public string Tag { get; set; } 11 | 12 | public OptionAttribute(string tag = null) 13 | { 14 | this.Tag = tag; 15 | } 16 | 17 | public OptionAttribute(Type pluginType) 18 | { 19 | this.Tag = pluginType.GetCustomAttribute()?.Tag; 20 | } 21 | 22 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 23 | { 24 | if(Tag == null) 25 | { 26 | Tag = plugin.GetOptionsTag(); 27 | if(Tag == null) 28 | throw new InvalidOperationException($"Could not find Pathfinder.Meta.Load.OptionsTabAttribute for {targettedInfo.DeclaringType.FullName}"); 29 | } 30 | 31 | if(targettedInfo.DeclaringType != plugin.GetType()) 32 | throw new InvalidOperationException($"Pathfinder.Meta.Load.OptionAttribute is only valid in a class derived from BepInEx.Hacknet.HacknetPlugin"); 33 | 34 | Option option = null; 35 | switch(targettedInfo) 36 | { 37 | case PropertyInfo propertyInfo: 38 | if(!propertyInfo.PropertyType.IsSubclassOf(typeof(Option))) 39 | throw new InvalidOperationException($"Property {propertyInfo.Name}'s type does not derive from Pathfinder.Options.Option"); 40 | option = (Option)(propertyInfo.GetGetMethod()?.Invoke(plugin, null)); 41 | break; 42 | case FieldInfo fieldInfo: 43 | if(!fieldInfo.FieldType.IsSubclassOf(typeof(Option))) 44 | throw new InvalidOperationException($"Field {fieldInfo.Name}'s type does not derive from Pathfinder.Options.Option"); 45 | option = (Option)fieldInfo.GetValue(plugin); 46 | break; 47 | } 48 | 49 | if(option == null) 50 | throw new InvalidOperationException($"Option not set to a default value, Option members should be set before HacknetPlugin.Load() is called"); 51 | 52 | OptionsManager.AddOption(Tag, option); 53 | } 54 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/OptionsTabAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | 4 | namespace Pathfinder.Meta.Load; 5 | 6 | [AttributeUsage(AttributeTargets.Class)] 7 | public class OptionsTabAttribute : BaseAttribute 8 | { 9 | internal static readonly Dictionary pluginToOptionsTag = new Dictionary(); 10 | 11 | public string Tag { get; } 12 | 13 | public OptionsTabAttribute(string tag) 14 | { 15 | this.Tag = tag; 16 | } 17 | 18 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 19 | { 20 | pluginToOptionsTag.Add(plugin, Tag); 21 | } 22 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/PortAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Port; 4 | 5 | namespace Pathfinder.Meta.Load; 6 | 7 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] 8 | public class PortAttribute : BaseAttribute 9 | { 10 | public PortAttribute() 11 | { 12 | } 13 | 14 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 15 | { 16 | if(targettedInfo.DeclaringType != plugin.GetType()) 17 | throw new InvalidOperationException($"Pathfinder.Meta.Load.PortAttribute is only valid in a class derived from BepInEx.Hacknet.HacknetPlugin"); 18 | 19 | object portRecord = null; 20 | switch(targettedInfo) 21 | { 22 | case PropertyInfo propertyInfo: 23 | if(propertyInfo.PropertyType != typeof(PortRecord)) 24 | throw new InvalidOperationException($"Property {propertyInfo.Name}'s type does not derive from Pathfinder.Port.PortRecord"); 25 | portRecord = propertyInfo.GetGetMethod()?.Invoke(plugin, null); 26 | break; 27 | case FieldInfo fieldInfo: 28 | if(fieldInfo.FieldType != typeof(PortRecord)) 29 | throw new InvalidOperationException($"Field {fieldInfo.Name}'s type does not derive from Pathfinder.Port.PortRecord"); 30 | portRecord = fieldInfo.GetValue(plugin); 31 | break; 32 | } 33 | 34 | if(portRecord == null) 35 | throw new InvalidOperationException($"PortRecord not set to a default value, PortRecord should be set before HacknetPlugin.Load() is called"); 36 | 37 | PortManager.RegisterPortInternal((PortRecord)portRecord, targettedInfo.Module.Assembly); 38 | } 39 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/Load/SaveExecutorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BepInEx.Hacknet; 3 | using Pathfinder.Replacements; 4 | using Pathfinder.Util.XML; 5 | 6 | namespace Pathfinder.Meta.Load; 7 | 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public class SaveExecutorAttribute : BaseAttribute 10 | { 11 | public string Element { get; } 12 | public ParseOption ParseOptions { get; set; } 13 | 14 | public SaveExecutorAttribute(string element, ParseOption parseOptions = ParseOption.None) 15 | { 16 | Element = element; 17 | ParseOptions = parseOptions; 18 | } 19 | 20 | protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) 21 | { 22 | SaveLoader.RegisterExecutor((Type)targettedInfo, Element, ParseOptions); 23 | } 24 | } -------------------------------------------------------------------------------- /PathfinderAPI/Meta/UpdaterAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Pathfinder.Meta; 2 | 3 | public class UpdaterAttribute : Attribute 4 | { 5 | public string GithubApiUrl { get; set; } 6 | public string AssetFileName { get; set; } 7 | public string ZipEntryPath { get; set; } 8 | public bool IncludePrerelease { get; set; } 9 | 10 | public UpdaterAttribute(string apiUrl, string assetName, string zipPath = null, bool includePrerlease = false) 11 | { 12 | GithubApiUrl = apiUrl; 13 | AssetFileName = assetName; 14 | ZipEntryPath = zipPath; 15 | IncludePrerelease = includePrerlease; 16 | } 17 | } -------------------------------------------------------------------------------- /PathfinderAPI/Mission/GoalManager.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Hacknet.Mission; 3 | using Pathfinder.Event; 4 | using Pathfinder.Util; 5 | 6 | namespace Pathfinder.Mission; 7 | 8 | public static class GoalManager 9 | { 10 | private static readonly Dictionary CustomGoals = new Dictionary(); 11 | 12 | static GoalManager() 13 | { 14 | EventManager.onPluginUnload += OnPluginUnload; 15 | } 16 | 17 | internal static bool TryLoadCustomGoal(string type, out MisisonGoal customGoal) 18 | { 19 | if (CustomGoals.TryGetValue(type, out var goalType)) 20 | { 21 | customGoal = (MisisonGoal)Activator.CreateInstance(goalType); 22 | return true; 23 | } 24 | 25 | customGoal = null; 26 | return false; 27 | } 28 | 29 | private static void OnPluginUnload(Assembly pluginAsm) 30 | { 31 | foreach (var name in CustomGoals.Where(x => x.Value.Assembly == pluginAsm).Select(x => x.Key).ToList()) 32 | CustomGoals.Remove(name); 33 | } 34 | 35 | public static void RegisterGoal(string xmlName) where T : MisisonGoal => RegisterGoal(typeof(T), xmlName); 36 | public static void RegisterGoal(Type goalType, string xmlName) 37 | { 38 | goalType.ThrowNotInherit(nameof(goalType), " (yes, that is how it's spelled)"); 39 | CustomGoals.Add(xmlName.ToLower(), goalType); 40 | } 41 | 42 | public static void UnregisterGoal() => UnregisterGoal(typeof(T)); 43 | public static void UnregisterGoal(Type goalType) 44 | { 45 | var xmlName = CustomGoals.FirstOrDefault(x => x.Value == goalType).Key; 46 | if (xmlName != null) 47 | CustomGoals.Remove(xmlName); 48 | } 49 | public static void UnregisterGoal(string xmlName) 50 | { 51 | CustomGoals.Remove(xmlName.ToLower()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PathfinderAPI/Mission/PathfinderGoal.cs: -------------------------------------------------------------------------------- 1 | using Hacknet.Mission; 2 | using Pathfinder.Util; 3 | using Pathfinder.Util.XML; 4 | 5 | namespace Pathfinder.Mission; 6 | 7 | public abstract class PathfinderGoal : MisisonGoal 8 | { 9 | public virtual void LoadFromXML(ElementInfo info) 10 | { 11 | XMLStorageAttribute.ReadFromElement(info, this); 12 | } 13 | } -------------------------------------------------------------------------------- /PathfinderAPI/Options/Options.cs: -------------------------------------------------------------------------------- 1 | using Hacknet.Gui; 2 | using Microsoft.Xna.Framework; 3 | using Pathfinder.GUI; 4 | 5 | namespace Pathfinder.Options; 6 | 7 | public abstract class Option 8 | { 9 | public string Name; 10 | public string Description = ""; 11 | public bool Enabled = true; 12 | 13 | public virtual int SizeX => 0; 14 | public virtual int SizeY => 0; 15 | 16 | public Option(string name, string description="") 17 | { 18 | this.Name = name; 19 | this.Description = description; 20 | } 21 | 22 | public abstract void Draw(int x, int y); 23 | } 24 | 25 | public class OptionCheckbox : Option 26 | { 27 | public bool Value; 28 | 29 | public override int SizeX => 100; 30 | public override int SizeY => 75; 31 | 32 | private int ButtonID = PFButton.GetNextID(); 33 | 34 | public OptionCheckbox(string name, string description="", bool defVal=false) : base(name, description) 35 | { 36 | this.Value = defVal; 37 | } 38 | 39 | public override void Draw(int x, int y) 40 | { 41 | TextItem.doLabel(new Vector2(x, y), Name, null, 200); 42 | Value = CheckBox.doCheckBox(ButtonID, x, y + 34, Value, null); 43 | 44 | TextItem.doSmallLabel(new Vector2(x+32, y+30), Description, null); 45 | } 46 | } -------------------------------------------------------------------------------- /PathfinderAPI/Options/OptionsManager.cs: -------------------------------------------------------------------------------- 1 | using Pathfinder.GUI; 2 | 3 | namespace Pathfinder.Options; 4 | 5 | public static class OptionsManager 6 | { 7 | public readonly static Dictionary Tabs = new Dictionary(); 8 | 9 | static OptionsManager() { } 10 | 11 | public static void AddOption(string tag, Option opt) 12 | { 13 | if (!Tabs.TryGetValue(tag, out var tab)) { 14 | tab = new OptionsTab(tag); 15 | Tabs.Add(tag, tab); 16 | } 17 | tab.Options.Add(opt); 18 | } 19 | } 20 | 21 | public class OptionsTab 22 | { 23 | public string Name; 24 | 25 | public List