├── .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 Options = new List ();
26 |
27 | internal int ButtonID = PFButton.GetNextID();
28 |
29 | public OptionsTab(string name) {
30 | Name = name;
31 | }
32 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Options/PathfinderOptions.cs:
--------------------------------------------------------------------------------
1 | using Pathfinder.Event;
2 | using Pathfinder.Event.Options;
3 |
4 | namespace Pathfinder.Options;
5 |
6 | internal static class PathfinderOptions
7 | {
8 | private const string OPTION_TAG = "Pathfinder";
9 |
10 | internal static OptionCheckbox PreloadAllThemes = new OptionCheckbox("Preload All Themes",
11 | "Preload all themes at extension start\nimproves performance at the cost of memory");
12 |
13 | internal static OptionCheckbox DisableSteamCloudError = new OptionCheckbox("Disable Steam Cloud Message",
14 | "Disables the Steam Cloud disabled message on the main menu");
15 |
16 | [Util.Initialize]
17 | static void Initialize()
18 | {
19 | OptionsManager.AddOption(OPTION_TAG, PreloadAllThemes);
20 | OptionsManager.AddOption(OPTION_TAG, DisableSteamCloudError);
21 | EventManager.AddHandler(onOptionsSave);
22 | initConfig();
23 | }
24 |
25 | private static void initConfig()
26 | {
27 | var preloadAllThemesDef = PathfinderAPIPlugin.Config.Bind("PathfinderAPI", "PreloadAllThemes", false);
28 | var disableSteamCloudDef = PathfinderAPIPlugin.Config.Bind("PathfinderAPI", "DisableSteamCloudMessage", false);
29 | PreloadAllThemes.Value = preloadAllThemesDef.Value;
30 | DisableSteamCloudError.Value = disableSteamCloudDef.Value;
31 | }
32 |
33 | private static void onOptionsSave(CustomOptionsSaveEvent _)
34 | {
35 | PathfinderAPIPlugin.Config.TryGetEntry("PathfinderAPI", "PreloadAllThemes", out var preloadAllThemesVal);
36 | PathfinderAPIPlugin.Config.TryGetEntry("PathfinderAPI", "DisableSteamCloudMessage", out var disableCloudMessage);
37 |
38 | preloadAllThemesVal.Value = PreloadAllThemes.Value;
39 | disableCloudMessage.Value = DisableSteamCloudError.Value;
40 | PathfinderAPIPlugin.Config.Save();
41 | }
42 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Options/PathfinderOptionsMenu.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using MonoMod.Cil;
3 | using Hacknet;
4 | using Hacknet.Gui;
5 | using Microsoft.Xna.Framework;
6 | using Pathfinder.Event;
7 | using Pathfinder.Event.Options;
8 | using Pathfinder.GUI;
9 |
10 | namespace Pathfinder.Options;
11 |
12 | [HarmonyPatch]
13 | internal static class PathfinderOptionsMenu
14 | {
15 | private static bool isInPathfinderMenu = false;
16 | private static string currentTabName = null;
17 |
18 | private static PFButton ReturnButton = new PFButton(10, 10, 220, 54, "Back to Options", Color.Yellow);
19 |
20 | [HarmonyPrefix]
21 | [HarmonyPatch(typeof(OptionsMenu), nameof(OptionsMenu.Draw))]
22 | internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime)
23 | {
24 | if (!isInPathfinderMenu)
25 | return true;
26 |
27 | PostProcessor.begin();
28 | GuiData.startDraw();
29 | PatternDrawer.draw(new Rectangle(0, 0, __instance.ScreenManager.GraphicsDevice.Viewport.Width, __instance.ScreenManager.GraphicsDevice.Viewport.Height), 0.5f, Color.Black, new Color(2, 2, 2), GuiData.spriteBatch);
30 |
31 | if (ReturnButton.Do())
32 | {
33 | currentTabName = null;
34 | isInPathfinderMenu = false;
35 | GuiData.endDraw();
36 | PostProcessor.end();
37 | var saveEvent = new CustomOptionsSaveEvent();
38 | EventManager.InvokeAll(saveEvent);
39 | return false;
40 | }
41 |
42 | var tabs = OptionsManager.Tabs;
43 |
44 | int tabX = 10;
45 |
46 | foreach (var tab in tabs.Values)
47 | {
48 | if (currentTabName == null)
49 | currentTabName = tab.Name;
50 | var active = currentTabName == tab.Name;
51 | // Display tab button
52 | if (Button.doButton(tab.ButtonID, tabX, 70, 128, 20, tab.Name, active ? Color.Green : Color.Gray))
53 | {
54 | currentTabName = tab.Name;
55 | break;
56 | }
57 | tabX += 128 + 10;
58 |
59 | if (currentTabName != tab.Name)
60 | continue;
61 |
62 | // Display options
63 | int optX = 80, optY = 110;
64 | foreach (var option in tab.Options)
65 | {
66 | option.Draw(optX, optY);
67 | optY += 10 + option.SizeY;
68 | }
69 | }
70 |
71 | GuiData.endDraw();
72 | PostProcessor.end();
73 | return false;
74 | }
75 |
76 | private static PFButton EnterButton = new PFButton(240, 10, 220, 54, "Pathfinder Options", Color.Yellow);
77 |
78 | [HarmonyILManipulator]
79 | [HarmonyPatch(typeof(OptionsMenu), nameof(OptionsMenu.Draw))]
80 | internal static void BeforeEndDrawOptions(ILContext il)
81 | {
82 | ILCursor c = new ILCursor(il);
83 |
84 | c.GotoNext(MoveType.AfterLabel, x => x.MatchCallOrCallvirt(AccessTools.Method(typeof(GuiData), nameof(GuiData.endDraw))));
85 |
86 | c.EmitDelegate(() =>
87 | {
88 | if (EnterButton.Do())
89 | {
90 | isInPathfinderMenu = true;
91 | }
92 | });
93 | }
94 | }
--------------------------------------------------------------------------------
/PathfinderAPI/PathfinderAPI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pathfinder
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | $(LibsDir)HacknetPathfinder.exe
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {64faeda5-e87c-47ed-8200-e1de1f263040}
23 | BepInEx.Hacknet
24 | False
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/PathfinderAPI/PathfinderAPIPlugin.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Configuration;
3 | using BepInEx.Hacknet;
4 | using HarmonyLib;
5 | using Pathfinder.Meta;
6 | using Pathfinder.Util;
7 | using System.Globalization;
8 | using SDL2;
9 |
10 | namespace Pathfinder;
11 |
12 | [BepInPlugin(ModGUID, ModName, HacknetChainloader.VERSION)]
13 | [BepInDependency("com.Pathfinder.Updater", BepInDependency.DependencyFlags.SoftDependency)]
14 | [Updater(
15 | "https://api.github.com/repos/Arkhist/Hacknet-Pathfinder/releases",
16 | "Pathfinder.Release.zip",
17 | "BepInEx/plugins/PathfinderAPI.dll"
18 | )]
19 | public class PathfinderAPIPlugin : HacknetPlugin
20 | {
21 | public const string ModGUID = "com.Pathfinder.API";
22 | public const string ModName = "PathfinderAPI";
23 |
24 | new internal static Harmony HarmonyInstance;
25 | new internal static ConfigFile Config;
26 |
27 | public static readonly bool GameIsSteamVersion = typeof(Hacknet.PlatformAPI.Storage.SteamCloudStorageMethod).GetField("deserialized") != null;
28 |
29 | public override bool Load()
30 | {
31 | PathfinderAPIPlugin.HarmonyInstance = base.HarmonyInstance;
32 | Logger.LogSource = base.Log;
33 | PathfinderAPIPlugin.Config = base.Config;
34 |
35 | Environment.SetEnvironmentVariable("LD_PRELOAD", $"./lib{(Environment.Is64BitProcess ? "64" : "")}/libcef.so");
36 |
37 | foreach (var initMethod in typeof(PathfinderAPIPlugin).Assembly.GetTypes().SelectMany(AccessTools.GetDeclaredMethods))
38 | {
39 | if (initMethod.GetCustomAttributes(false).Any(x => x is InitializeAttribute) && initMethod.IsStatic && initMethod.GetParameters().Length == 0)
40 | initMethod.Invoke(null, null);
41 | }
42 |
43 | HarmonyInstance.PatchAll(typeof(PathfinderAPIPlugin).Assembly);
44 |
45 | CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
46 | CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
47 |
48 | // Expose textures and renderbuffers to other OpenGL contexts for funky mods =D
49 | SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
50 |
51 | return true;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/PathfinderAPI/Port/PortRecord.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using Pathfinder.Util;
3 |
4 | namespace Pathfinder.Port;
5 |
6 | public class PortRecord : IEquatable
7 | {
8 | public string Protocol { get; }
9 | public int OriginalPortNumber { get; }
10 | public string DefaultDisplayName { get; }
11 | public int DefaultPortNumber { get; private set; }
12 | public PortRecord(string protocol, string defDisplayName, int defPortNumber) : this(protocol, defDisplayName, defPortNumber, defPortNumber)
13 | {
14 | }
15 |
16 | internal PortRecord(string protocol, string defDisplayName, int defPortNumber, int originalPortNumber)
17 | {
18 | protocol.ThrowNull(nameof(protocol));
19 | defDisplayName.ThrowNull(nameof(defDisplayName));
20 | defPortNumber.ThrowOutOfRange(nameof(defPortNumber), 0);
21 | Protocol = protocol;
22 | DefaultDisplayName = defDisplayName;
23 | DefaultPortNumber = defPortNumber;
24 | OriginalPortNumber = originalPortNumber;
25 | }
26 |
27 | public PortState CreateState(Computer computer, string displayName = null, int portNumber = -1, bool cracked = false)
28 | {
29 | return new PortState(computer, this, displayName, portNumber, cracked);
30 | }
31 |
32 | public PortState CreateState(Computer computer, bool cracked)
33 | {
34 | return CreateState(computer, null, cracked: cracked);
35 | }
36 |
37 | public bool Equals(PortRecord other)
38 | {
39 | return (!(other is null)) && Protocol == other.Protocol && OriginalPortNumber == other.OriginalPortNumber;
40 | }
41 | public static bool operator ==(PortRecord first, PortRecord second)
42 | {
43 | if(first is null && second is null) return true;
44 | if(first is null || second is null) return false;
45 | return first.Equals(second);
46 | }
47 | public static bool operator !=(PortRecord first, PortRecord second)
48 | {
49 | return !(first == second);
50 | }
51 | public override bool Equals(object obj)
52 | {
53 | if (obj is null) return false;
54 | if (obj.GetType() != this.GetType())
55 | return false;
56 | return Equals((PortRecord)obj);
57 | }
58 | public override int GetHashCode()
59 | {
60 | unchecked
61 | {
62 | var hashCode = (Protocol != null ? Protocol.GetHashCode() : 0);
63 | hashCode = (hashCode * 397) ^ OriginalPortNumber;
64 | return hashCode;
65 | }
66 | }
67 |
68 | #pragma warning disable 618
69 | [Obsolete("Avoid PortData")]
70 | public static explicit operator PortRecord(PortData data)
71 | => new PortRecord(data.Protocol, data.DisplayName, data.Port, data.OriginalPort);
72 |
73 | [Obsolete("Avoid PortData")]
74 | public static explicit operator PortData(PortRecord record)
75 | => new PortData(record.Protocol, record.OriginalPortNumber, record.DefaultPortNumber, record.DefaultDisplayName);
76 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Port/PortState.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 |
3 | namespace Pathfinder.Port;
4 |
5 | public class PortState
6 | {
7 | public Computer Computer { get; internal set; }
8 | public PortRecord Record { get; }
9 | private string _DisplayName;
10 | public string DisplayName
11 | {
12 | get => _DisplayName;
13 | set => _DisplayName = value ?? Record.DefaultDisplayName;
14 | }
15 | private int _PortNumber;
16 | public int PortNumber
17 | {
18 | get => _PortNumber;
19 | set => _PortNumber = value > -1 ? value : Record.DefaultPortNumber;
20 | }
21 | public bool Cracked { get; set; }
22 | public void SetCracked(bool cracked, string ipFrom)
23 | {
24 | if(cracked && !Cracked)
25 | Computer.openPort(Record.Protocol, ipFrom);
26 | else if(!cracked && Cracked)
27 | Computer.closePort(Record.Protocol, ipFrom);
28 | }
29 |
30 | public PortState(Computer comp, PortRecord record, bool cracked) : this(comp, record, null, cracked: cracked)
31 | {
32 | }
33 | public PortState(Computer comp, PortRecord record, string displayName = null, int portNumber = -1, bool cracked = false)
34 | {
35 | Computer = comp;
36 | Record = record;
37 | Cracked = cracked;
38 | DisplayName = displayName;
39 | PortNumber = portNumber;
40 | }
41 | public PortState(Computer comp, string protocol, bool cracked) : this(comp, protocol, null, cracked: cracked)
42 | {
43 | }
44 | public PortState(Computer comp, string protocol, string displayName = null, int portNumber = -1, bool cracked = false)
45 | {
46 | Computer = comp;
47 | Record = PortManager.GetPortRecordFromProtocol(protocol);
48 | Cracked = cracked;
49 | DisplayName = displayName;
50 | PortNumber = portNumber;
51 | }
52 |
53 | public PortState Clone(Computer comp = null)
54 | {
55 | return Record.CreateState(comp, _DisplayName, _PortNumber, Cracked);
56 | }
57 |
58 | public bool Remove()
59 | {
60 | return Computer.RemovePort(Record);
61 | }
62 |
63 | #pragma warning disable 618
64 | [Obsolete("Avoid PortData")]
65 | public static explicit operator PortData(PortState state)
66 | => new PortData(state.Record.Protocol, state.Record.OriginalPortNumber, state.PortNumber, state.DisplayName)
67 | {
68 | Cracked = state.Cracked
69 | };
70 |
71 | [Obsolete("Avoid PortData")]
72 | public static explicit operator PortState(PortData data)
73 | => new PortState(null, (PortRecord)data, data.Cracked);
74 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Replacements/ReplacementsCommon.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using Hacknet.Factions;
3 | using HarmonyLib;
4 | using Microsoft.Xna.Framework;
5 | using Pathfinder.Port;
6 | using Pathfinder.Util;
7 | using Pathfinder.Util.XML;
8 |
9 | namespace Pathfinder.Replacements;
10 |
11 | [HarmonyPatch]
12 | public static class ReplacementsCommon
13 | {
14 | public static MemoryContents LoadMemoryContents(ElementInfo info)
15 | {
16 | if (info.Children.TryGetElement("Memory", out var internalInfo)) info = internalInfo;
17 | var memory = new MemoryContents();
18 |
19 | if (info.Children.TryGetElement("Commands", out var commands))
20 | {
21 | foreach (var command in commands.Children.Select(x => x.Content).Where(StringExtensions.HasContent))
22 | {
23 | if (command.Contains("\n"))
24 | {
25 | foreach (var trueCommand in command.Split(Utils.robustNewlineDelim, StringSplitOptions.RemoveEmptyEntries))
26 | {
27 | memory.CommandsRun.Add(trueCommand.HasContent() ? trueCommand.Filter() : " ");
28 | }
29 | }
30 | else
31 | memory.CommandsRun.Add(command.Filter());
32 | }
33 | }
34 |
35 | if (info.Children.TryGetElement("Data", out var data))
36 | {
37 | foreach (var block in data.Children)
38 | {
39 | memory.DataBlocks.Add(block.Content.Filter());
40 | }
41 | }
42 |
43 | if (info.Children.TryGetElement("FileFragments", out var files))
44 | {
45 | foreach (var file in files.Children)
46 | {
47 | memory.FileFragments.Add(new KeyValuePair(
48 | file.Attributes.GetString("name", "UNKNOWN"),
49 | file.Content
50 | ));
51 | }
52 | }
53 |
54 | if (info.Children.TryGetElement("Images", out var images))
55 | {
56 | foreach (var image in images.Children)
57 | {
58 | memory.Images.Add(image.Content);
59 | }
60 | }
61 |
62 | return memory;
63 | }
64 |
65 | public static Faction LoadFaction(ElementInfo info)
66 | {
67 | Faction ret;
68 |
69 | var name = info.Attributes.GetString("name", "UNKNOWN");
70 | var needed = info.Attributes.GetInt("neededVal");
71 | switch (info.Name)
72 | {
73 | case "HubFaction":
74 | ret = new HubFaction(name, needed);
75 | break;
76 | case "EntropyFaction":
77 | ret = new EntropyFaction(name, needed);
78 | break;
79 | case "CustomFaction":
80 | var actions = new List();
81 | foreach (var actionSetInfo in info.Children)
82 | {
83 | actions.Add(new CustomFactionAction()
84 | {
85 | ValueRequiredForTrigger = actionSetInfo.Attributes.GetInt("ValueRequired"),
86 | FlagsRequiredForTrigger = actionSetInfo.Attributes.GetString("Flags", null),
87 | TriggerActions = actionSetInfo.Children.Select(ActionsLoader.ReadAction).ToList()
88 | });
89 | }
90 |
91 | ret = new CustomFaction(name, 100)
92 | {
93 | CustomActions = actions
94 | };
95 | break;
96 | default:
97 | ret = new Faction(name, needed);
98 | break;
99 | }
100 |
101 | ret.playerValue = info.Attributes.GetInt("playerVal");
102 | ret.idName = info.Attributes.GetString("id");
103 | ret.playerHasPassedValue = info.Attributes.GetBool("playerHasPassed");
104 |
105 | return ret;
106 | }
107 |
108 | internal static bool isPathfinderComputer = false;
109 |
110 | internal static readonly List defaultPorts = new List
111 | {
112 | new ElementInfo() { Name = "web" },
113 | new ElementInfo() { Name = "smtp" },
114 | new ElementInfo() { Name = "ftp" },
115 | new ElementInfo() { Name = "ssh" }
116 | };
117 |
118 | [HarmonyPostfix]
119 | [HarmonyPatch(typeof(Computer), MethodType.Constructor, new Type[] { typeof(string), typeof(string), typeof(Vector2), typeof(int), typeof(byte), typeof(OS) })]
120 | private static void LoadDefaultPortsIfReplacement(Computer __instance)
121 | {
122 | if (!isPathfinderComputer)
123 | {
124 | PortManager.LoadPortsFromChildren(__instance, defaultPorts, false);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/PathfinderAPI/SteamPatches.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Hacknet;
3 | using HarmonyLib;
4 | using Mono.Cecil.Cil;
5 | using MonoMod.Cil;
6 | using Pathfinder.Options;
7 |
8 | namespace Pathfinder;
9 |
10 | [HarmonyPatch]
11 | public class SteamPatches
12 | {
13 | [HarmonyPrepare]
14 | internal static bool Prepare(MethodBase original) =>
15 | PathfinderAPIPlugin.GameIsSteamVersion;
16 |
17 | [HarmonyILManipulator]
18 | [HarmonyPatch(typeof(PlatformAPISettings), nameof(PlatformAPISettings.InitPlatformAPI))]
19 | private static void NoSteamCloudSavesIL(ILContext il)
20 | {
21 | ILCursor c = new ILCursor(il);
22 |
23 | c.GotoNext(MoveType.Before, x => x.MatchStsfld(AccessTools.Field(typeof(PlatformAPISettings), nameof(PlatformAPISettings.RemoteStorageRunning))));
24 |
25 | c.Emit(OpCodes.Pop);
26 | c.Emit(OpCodes.Ldc_I4_0);
27 | }
28 |
29 | [HarmonyILManipulator]
30 | [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.drawMainMenuButtons))]
31 | private static void NoSteamErrorMessageIL(ILContext il)
32 | {
33 | ILCursor c = new ILCursor(il);
34 |
35 | c.GotoNext(MoveType.Before, x => x.MatchLdsfld(AccessTools.Field(typeof(PlatformAPISettings), nameof(PlatformAPISettings.RemoteStorageRunning))));
36 |
37 | c.RemoveRange(3);
38 |
39 | c.Emit(OpCodes.Ldsfld, AccessTools.Field(typeof(PathfinderOptions), nameof(PathfinderOptions.DisableSteamCloudError)));
40 | c.Emit(OpCodes.Ldfld, AccessTools.Field(typeof(OptionCheckbox), nameof(OptionCheckbox.Value)));
41 |
42 | c.GotoNext(MoveType.Before, x => x.MatchLdstr(out _));
43 | c.Next.Operand = "Steam Cloud saving disabled by Pathfinder";
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/PathfinderAPI/Util/AssemblyAssociatedList.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Reflection;
3 |
4 | namespace Pathfinder.Util;
5 |
6 | public class AssemblyAssociatedList
7 | {
8 | private Dictionary> asmDictionary = new Dictionary>();
9 | private ReadOnlyCollection _allItems = null;
10 | public ReadOnlyCollection AllItems {
11 | get {
12 | if(_allItems == null)
13 | RefreshCache();
14 | return _allItems;
15 | }
16 | }
17 |
18 | public ReadOnlyCollection this[Assembly assembly]
19 | {
20 | get
21 | {
22 | if(!asmDictionary.TryGetValue(assembly, out var list))
23 | return new ReadOnlyCollection(new List());
24 | return new ReadOnlyCollection(list);
25 | }
26 | }
27 |
28 | public void Add(T val, Assembly owner)
29 | {
30 | if (!asmDictionary.ContainsKey(owner))
31 | asmDictionary[owner] = new List();
32 | asmDictionary[owner].Add(val);
33 |
34 | InvalidateCache();
35 | }
36 |
37 | public void Remove(T val, Assembly owner)
38 | {
39 | if (asmDictionary.TryGetValue(owner, out var list))
40 | {
41 | list.Remove(val);
42 | InvalidateCache();
43 | }
44 | }
45 |
46 | public void RemoveAll(Predicate predicate, Assembly owner)
47 | {
48 | if (asmDictionary.TryGetValue(owner, out var list))
49 | {
50 | list.RemoveAll(predicate);
51 | InvalidateCache();
52 | }
53 | }
54 |
55 | public bool RemoveAssembly(Assembly asm, out List removed)
56 | {
57 | asmDictionary.TryGetValue(asm, out removed);
58 | if (asmDictionary.Remove(asm))
59 | {
60 | InvalidateCache();
61 | return true;
62 | }
63 | return false;
64 | }
65 |
66 | private void InvalidateCache()
67 | {
68 | _allItems = null;
69 | }
70 |
71 | private void RefreshCache()
72 | {
73 | var allItems = new List();
74 | foreach (var list in asmDictionary.Values)
75 | {
76 | allItems.AddRange(list);
77 | }
78 | _allItems = new ReadOnlyCollection(allItems);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/PathfinderAPI/Util/ComputerLookup.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 |
3 | namespace Pathfinder.Util;
4 |
5 | [Flags]
6 | public enum SearchType
7 | {
8 | Id = 0b001,
9 | Ip = 0b010,
10 | Name = 0b100,
11 | Any = 0b111
12 | }
13 |
14 | public static class ComputerLookup
15 | {
16 | #region Lookup Tables
17 | private static readonly Dictionary idLookup = new Dictionary();
18 |
19 | private static readonly Dictionary ipLookup = new Dictionary();
20 |
21 | private static readonly Dictionary nameLookup = new Dictionary();
22 | #endregion
23 |
24 | public static void RebuildLookups(List nodes = null)
25 | {
26 | nodes ??= OS.currentInstance?.netMap?.nodes;
27 | nodes.ThrowNull(nameof(nodes), "No nodes passed in and OS hasn't finished loading netmap");
28 | ClearLookups();
29 | foreach (var node in nodes)
30 | Add(node);
31 | }
32 |
33 | public static void Add(Computer node)
34 | {
35 | if (!idLookup.ContainsKey(node.idName))
36 | idLookup[node.idName] = node;
37 | if (!ipLookup.ContainsKey(node.ip))
38 | ipLookup[node.ip] = node;
39 | if (!nameLookup.ContainsKey(node.name))
40 | nameLookup[node.name] = node;
41 | }
42 |
43 | internal static void ClearLookups()
44 | {
45 | idLookup.Clear();
46 | ipLookup.Clear();
47 | nameLookup.Clear();
48 | }
49 |
50 | public static Computer Find(string target, SearchType type = SearchType.Any)
51 | {
52 | Computer node = null;
53 | if (target == null) return null;
54 | if ((type & SearchType.Id) != 0)
55 | node = FindById(target);
56 | if (node == null && (type & SearchType.Ip) != 0)
57 | node = FindByIp(target);
58 | if (node == null && (type & SearchType.Name) != 0)
59 | node = FindByName(target);
60 | return node;
61 | }
62 |
63 | public static Computer FindById(string id) => idLookup.GetOrDefault(id);
64 |
65 | public static Computer FindByIp(string ip, bool filter = true) => ipLookup.GetOrDefault(filter ? ip.Filter() : ip);
66 |
67 | public static Computer FindByName(string name, bool filter = true) => nameLookup.GetOrDefault(filter ? name.Filter() : name);
68 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | using BepInEx.Logging;
2 | using Hacknet;
3 | using Microsoft.Xna.Framework;
4 |
5 | namespace Pathfinder.Util;
6 |
7 | public static class DictionaryExtensions
8 | {
9 | public static string GetString(this Dictionary dict, Key key, string defaultVal = "")
10 | {
11 | return dict.TryGetValue(key, out var ret) ? ret : defaultVal;
12 | }
13 |
14 | public static string GetOrThrow(this Dictionary dict, Key key, string exMessage, Predicate validator = null)
15 | {
16 | if (dict.TryGetValue(key, out var ret))
17 | {
18 | if (validator != null && !validator(ret))
19 | throw new ArgumentException(exMessage);
20 | return ret;
21 | }
22 |
23 | throw new KeyNotFoundException(exMessage);
24 | }
25 |
26 | public static string GetAnyOrThrow(this Dictionary dict, Key[] keys, string exMessage, Predicate validator = null)
27 | {
28 | foreach (var key in keys)
29 | {
30 | if (dict.TryGetValue(key, out var ret))
31 | {
32 | if (validator != null && !validator(ret))
33 | continue;
34 | return ret;
35 | }
36 | }
37 |
38 | throw new KeyNotFoundException(exMessage);
39 | }
40 |
41 | public static string GetOrWarn(this Dictionary dict, Key key, string warnMessage,
42 | Predicate validator = null)
43 | {
44 | if (dict.TryGetValue(key, out var ret))
45 | {
46 | if (validator != null && !validator(ret))
47 | {
48 | Logger.Log(LogLevel.Warning, warnMessage);
49 | return "";
50 | }
51 | return ret;
52 | }
53 |
54 | Logger.Log(LogLevel.Warning, $"Key not found: {warnMessage}");
55 | return "";
56 | }
57 |
58 | public static TValue GetOrDefault(this Dictionary dict, TKey key) =>
59 | dict.TryGetValue(key, out var value) ? value : default;
60 |
61 | public static int GetInt(this Dictionary dict, Key key, int defaultVal = 0)
62 | {
63 | if (dict.TryGetValue(key, out var val) && int.TryParse(val, out var ret))
64 | return ret;
65 | return defaultVal;
66 | }
67 |
68 | public static bool GetBool(this Dictionary dict, Key key, bool defaultVal = false)
69 | {
70 | if (dict.TryGetValue(key, out var val) && bool.TryParse(val, out var ret))
71 | return ret;
72 | return defaultVal;
73 | }
74 |
75 | public static float GetFloat(this Dictionary dict, Key key, float defaultVal = 0f)
76 | {
77 | if (dict.TryGetValue(key, out var val) && float.TryParse(val, out var ret))
78 | return ret;
79 | return defaultVal;
80 | }
81 |
82 | public static byte GetByte(this Dictionary dict, Key key, byte defaultVal = 0)
83 | {
84 | if (dict.TryGetValue(key, out var val) && byte.TryParse(val, out var ret))
85 | return ret;
86 | return defaultVal;
87 | }
88 |
89 | public static Vector2? GetVector(this Dictionary dict, Key x, Key y, Vector2? defaultVal = null)
90 | {
91 | if (dict.TryGetValue(x, out var x_string) && dict.TryGetValue(y, out var y_string) &&
92 | float.TryParse(x_string, out var x_float) && float.TryParse(y_string, out var y_float))
93 | return new Vector2(x_float, y_float);
94 | return defaultVal;
95 | }
96 |
97 | public static Color? GetColor(this Dictionary dict, Key key, Color? defaultVal = null)
98 | {
99 | if (dict.TryGetValue(key, out var color))
100 | return Utils.convertStringToColor(color);
101 | return defaultVal;
102 | }
103 |
104 | public static Computer GetComp(this Dictionary dict, Key key, SearchType searchType = SearchType.Any, string exMessage = null)
105 | {
106 | if (dict.TryGetValue(key, out var compString))
107 | return ComputerLookup.Find(compString.Filter(), searchType) ?? throw new NullReferenceException(exMessage ?? $"Could not find computer {compString} for attribute {key.ToString()}");
108 | throw new KeyNotFoundException(exMessage ?? $"Could not find attribute {key.ToString()}");
109 | }
110 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pathfinder.Util;
3 |
4 | public static class EnumerableExtensions
5 | {
6 | public static T? FirstOrNull(this IEnumerable source) where T : struct {
7 | using IEnumerator iter = source.GetEnumerator();
8 | if(iter.MoveNext())
9 | return iter.Current;
10 | return null;
11 | }
12 |
13 | public static T? FirstOrNull(this IEnumerable source, Func predicate) where T : struct {
14 | foreach(T item in source) {
15 | if(predicate(item))
16 | return item;
17 | }
18 | return null;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/PathfinderAPI/Util/ErrorHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 |
3 | namespace Pathfinder.Util;
4 |
5 | public static class ErrorHelper
6 | {
7 | public static void ThrowNotInherit(this Type type, string typeRefName, Type parentType, string extra = null)
8 | {
9 | if(!parentType.IsAssignableFrom(type))
10 | throw new ArgumentException($"{typeRefName} type must inherit from {parentType.FullName}!{extra}", typeRefName);
11 | }
12 |
13 | public static void ThrowNotInherit(this Type type, string typeRefName, string extra = null)
14 | => type.ThrowNotInherit(typeRefName, typeof(InheritT), extra);
15 |
16 | public static void ThrowNoDefaultCtor(this Type type, string nameOf)
17 | {
18 | if(type.GetConstructors().All(m => m.GetParameters().Length != 0))
19 | throw new ArgumentException($"{nameOf} must have a default constructor", nameOf);
20 | }
21 |
22 | public static void ThrowNull(this T check, string nameOf) where T : class
23 | {
24 | if(check == null)
25 | throw new ArgumentNullException(nameOf);
26 | }
27 |
28 | public static void ThrowNull(this T check, string nameOf, string msg) where T : class
29 | {
30 | if(check == null)
31 | throw new ArgumentNullException(nameOf, msg);
32 | }
33 |
34 | public static void ThrowOutOfRange(this int check, string nameOf, int lowerLimit = int.MinValue, int upperLimit = int.MaxValue)
35 | {
36 | if(check < lowerLimit || check > upperLimit)
37 | throw new ArgumentOutOfRangeException(nameOf);
38 | }
39 |
40 | public static void ThrowNotSameSizeAs(this ICollection left, string leftNameOf, ICollection right, string rightNameOf)
41 | {
42 | if(left.Count != right.Count)
43 | throw new ArgumentException($"{leftNameOf} is not same size as {rightNameOf}");
44 | }
45 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/FixedSizeCacheDict.cs:
--------------------------------------------------------------------------------
1 | namespace Pathfinder.Util;
2 |
3 | public class LRUCacheLinkedListNode where K : class where V : class
4 | {
5 | public K Key {get; set;} = default;
6 | public V Value {get; set;} = default;
7 | public LRUCacheLinkedListNode Next {get; set;} = null;
8 | public LRUCacheLinkedListNode Previous {get; set;} = null;
9 | }
10 |
11 | public class FixedSizeCacheDict where K : class where V : class
12 | {
13 | public readonly int Max;
14 | public LRUCacheLinkedListNode BackingListHead { get; private set; } = null;
15 | public LRUCacheLinkedListNode BackingListTail { get; private set; } = null;
16 | private readonly Dictionary> FastAccessDict = new Dictionary>();
17 |
18 | public FixedSizeCacheDict(int maxSize)
19 | {
20 | Max = maxSize;
21 |
22 | // List initialization
23 | if (maxSize != 0)
24 | {
25 | BackingListHead = new LRUCacheLinkedListNode();
26 | var previousNode = BackingListHead;
27 | var currentNode = BackingListHead;
28 | for (int i = 1; i < maxSize; i++) {
29 | currentNode = new LRUCacheLinkedListNode();
30 | previousNode.Next = currentNode;
31 | currentNode.Previous = previousNode;
32 | previousNode = currentNode;
33 | }
34 | BackingListTail = previousNode;
35 | }
36 | }
37 |
38 | private void touch(LRUCacheLinkedListNode node)
39 | {
40 | // Stitch the end
41 | if (node.Next == null)
42 | BackingListTail = node.Previous;
43 | // Stitch the middle
44 | else
45 | node.Next.Previous = node.Previous;
46 | if (node.Previous != null)
47 | node.Previous.Next = node.Next;
48 | // Put in front
49 | node.Previous = null;
50 | node.Next = BackingListHead;
51 | BackingListHead.Previous = node;
52 | BackingListHead = node;
53 | }
54 |
55 | public void Register(K key, V item)
56 | {
57 | LRUCacheLinkedListNode node;
58 | if (Max != 0)
59 | {
60 | // Eject last line of LRU Cache
61 | {
62 | var oldKey = BackingListTail.Key;
63 | if (oldKey != default)
64 | FastAccessDict.Remove(oldKey);
65 | if (BackingListTail.Value is IDisposable disposable)
66 | disposable.Dispose();
67 | BackingListTail.Value = default;
68 | }
69 | // Add new line
70 | {
71 | var current = BackingListTail;
72 | current.Key = key;
73 | current.Value = item;
74 | touch(current);
75 | node = current;
76 | }
77 | }
78 | else
79 | {
80 | node = new LRUCacheLinkedListNode();
81 | node.Key = key;
82 | node.Value = item;
83 | }
84 | FastAccessDict.Add(key, node);
85 | }
86 |
87 | public bool TryGetCached(K key, out V val)
88 | {
89 | if (FastAccessDict.TryGetValue(key, out LRUCacheLinkedListNode node))
90 | {
91 | if (Max != 0)
92 | touch(node);
93 | val = node.Value;
94 | return true;
95 | }
96 |
97 | val = default;
98 | return false;
99 | }
100 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/InitializeAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Pathfinder.Util;
2 |
3 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
4 | internal class InitializeAttribute : Attribute { }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using Hacknet.Extensions;
3 |
4 | namespace Pathfinder.Util;
5 |
6 | public static class StringExtensions
7 | {
8 | public static bool HasContent(this string s) => !string.IsNullOrWhiteSpace(s);
9 |
10 | public static bool ContentFileExists(this string filename) => File.Exists(filename.ContentFilePath());
11 |
12 | public static string ContentFilePath(this string filename)
13 | {
14 | filename = filename.Replace("\\", "/");
15 | if (Settings.IsInExtensionMode)
16 | {
17 | var extFolder = ExtensionLoader.ActiveExtensionInfo.FolderPath.Replace("\\", "/");
18 | return filename.StartsWith(extFolder) ? filename : Path.Combine(extFolder, filename);
19 | }
20 | return filename.StartsWith("Content/") ? filename : Path.Combine("Content", filename);
21 | }
22 |
23 | public static string Filter(this string s) => s == null ? null : ComputerLoader.filter(s);
24 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/XML/ElementInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using System.Xml;
4 | using System.Xml.Linq;
5 |
6 | namespace Pathfinder.Util.XML;
7 |
8 | public class ElementInfo
9 | {
10 | private static ulong freeId = 0;
11 |
12 | public string Name { get; set; }
13 | public string Content {
14 | get;
15 | set;
16 | } = null;
17 | public ElementInfo Parent { get; set; }
18 | public Dictionary Attributes { get; set; } = new Dictionary();
19 | public List Children { get; set; } = new List();
20 | public ulong NodeID { get; } = freeId++;
21 |
22 | public override string ToString()
23 | {
24 | var builder = new StringBuilder();
25 | var settings = new XmlWriterSettings
26 | {
27 | Indent = true
28 | };
29 | using (var writer = XmlWriter.Create(builder, settings))
30 | {
31 | WriteToXML(writer);
32 | }
33 |
34 | return builder.Replace("\t", " ").ToString();
35 | }
36 |
37 | public bool ContentAsBoolean()
38 | => bool.TryParse(Content, out var value)
39 | ? value
40 | : throw new FormatException($"Value of '{Name}' is not true or false");
41 |
42 | public int ContentAsInt()
43 | => int.TryParse(Content, out var value)
44 | ? value
45 | : throw new FormatException($"Value of '{Name}' is not an integer, e.g.: 0, 1, 2");
46 |
47 | public float ContentAsFloat()
48 | => float.TryParse(Content, out var value)
49 | ? value
50 | : throw new FormatException($"Value of '{Name}' is not a float, e.g.: 1.0");
51 |
52 | public void WriteToXML(XmlWriter writer)
53 | {
54 | writer.WriteStartElement(Name, "");
55 | foreach (var attr in Attributes)
56 | writer.WriteAttributeString(attr.Key, attr.Value);
57 | if (Content == null)
58 | {
59 | foreach (var child in Children)
60 | child.WriteToXML(writer);
61 | }
62 | else
63 | {
64 | writer.WriteValue(Content);
65 | }
66 | writer.WriteEndElement();
67 | }
68 |
69 | public XElement ConvertToXElement()
70 | {
71 | XElement Xel = new(Name, Content);
72 | foreach (KeyValuePair elAt in Attributes)
73 | {
74 | Xel.SetAttributeValue(elAt.Key, elAt.Value);
75 | }
76 | foreach (ElementInfo elx in Children)
77 | {
78 | Xel.Add(elx.ConvertToXElement());
79 | }
80 | return Xel;
81 | }
82 | }
83 |
84 | public static class ListExtensions
85 | {
86 | public static ElementInfo GetElement(this List list, string elementName)
87 | {
88 | foreach (var possibleInfo in list)
89 | {
90 | if (possibleInfo.Name == elementName)
91 | {
92 | return possibleInfo;
93 | }
94 | }
95 |
96 | return null;
97 | }
98 | public static bool TryGetElement(this List list, string elementName, out ElementInfo info)
99 | {
100 | info = GetElement(list, elementName);
101 | return info != null;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/PathfinderAPI/Util/XML/EventReader.cs:
--------------------------------------------------------------------------------
1 | using System.Xml;
2 |
3 | namespace Pathfinder.Util.XML;
4 |
5 | public class EventReader
6 | {
7 | protected virtual bool Read() => true;
8 | protected virtual bool ReadDocument() => true;
9 | protected virtual void ReadElement(Dictionary attributes) {}
10 | protected virtual void ReadEndElement() {}
11 | protected virtual void ReadText() {}
12 | protected virtual void EndRead() {}
13 |
14 | public XmlReader Reader { get; protected set; }
15 | public List ParentNames = new List();
16 | public string CurrentNamespace => string.Join(".", ParentNames);
17 |
18 | protected string Text;
19 |
20 | public EventReader() {}
21 |
22 | public EventReader(string text, bool isPath)
23 | {
24 | Text = isPath ? File.ReadAllText(text) : text;
25 | }
26 |
27 | public EventReader(XmlReader rdr)
28 | {
29 | Reader = rdr;
30 | }
31 |
32 | public void SetText(string text, bool isPath)
33 | {
34 | Text = isPath ? File.ReadAllText(text) : text;
35 | }
36 |
37 | public void Parse()
38 | {
39 | ParentNames.Clear();
40 |
41 | // verify xml is formatted legally
42 | if (Reader == null)
43 | using (XmlReader reader = XmlReader.Create(new StringReader(Text))) while (reader.Read());
44 |
45 | using (Reader ??= XmlReader.Create(new StringReader(Text)))
46 | {
47 | while (Reader.Read())
48 | {
49 | if (Read())
50 | {
51 | switch (Reader.NodeType)
52 | {
53 | case XmlNodeType.Document:
54 | if (!ReadDocument()) return;
55 | break;
56 | case XmlNodeType.Element:
57 | var attributes = new Dictionary();
58 |
59 | if (Reader.HasAttributes)
60 | {
61 | while (Reader.MoveToNextAttribute())
62 | {
63 | attributes.Add(Reader.Name, Reader.Value);
64 | }
65 |
66 | Reader.MoveToElement();
67 | }
68 |
69 | ParentNames.Add(Reader.Name);
70 | ReadElement(attributes);
71 |
72 | if (Reader.IsEmptyElement)
73 | {
74 | ReadEndElement();
75 | ParentNames.RemoveAt(ParentNames.Count - 1);
76 | }
77 |
78 | break;
79 | case XmlNodeType.EndElement:
80 | ReadEndElement();
81 | ParentNames.RemoveAt(ParentNames.Count - 1);
82 | break;
83 | case XmlNodeType.SignificantWhitespace:
84 | case XmlNodeType.CDATA:
85 | case XmlNodeType.Text:
86 | ReadText();
87 | break;
88 | }
89 | }
90 | }
91 |
92 | EndRead();
93 | }
94 |
95 | Reader = null;
96 | }
97 |
98 | public bool TryParse(out Exception exception)
99 | {
100 | try
101 | {
102 | Parse();
103 | exception = null;
104 | return true;
105 | }
106 | catch (Exception e)
107 | {
108 | exception = e;
109 | return false;
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/PathfinderAPI/Util/XMLTypeConverter.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using System.Numerics;
3 | using Microsoft.Xna.Framework;
4 |
5 | namespace Pathfinder.Util;
6 |
7 | public delegate object ToTypeConverter(string s);
8 | public delegate string FromTypeConverter(object o);
9 |
10 | public static class XMLTypeConverter
11 | {
12 | private class ConversionFailureException : Exception
13 | {
14 | public ConversionFailureException(string val, Type t, Exception inner) : base($"The value \"{(val is null ? "is null and" : $"\"{val}\"")}\" could not be converted to {t.Name}", inner) { }
15 | public ConversionFailureException(object o, Exception inner) : base($"The object {(o is null ? "is null and" : $"\"{o.ToString()}\"")} could not be converted to a string", inner) { }
16 | }
17 |
18 | private class TypeConverter
19 | {
20 | public ToTypeConverter ToType;
21 | public FromTypeConverter FromType;
22 |
23 | public TypeConverter(ToTypeConverter toType, FromTypeConverter fromType)
24 | {
25 | ToType = toType;
26 | FromType = fromType;
27 | }
28 | }
29 |
30 | private static readonly Dictionary TypeConverters = new Dictionary()
31 | {
32 | { typeof(string), new TypeConverter(s => s, s => (string)s) },
33 | { typeof(char), new TypeConverter(s => char.Parse(s), c => c.ToString()) },
34 | { typeof(sbyte), new TypeConverter(s => sbyte.Parse(s), b => b.ToString()) },
35 | { typeof(byte), new TypeConverter(s => byte.Parse(s), b => b.ToString()) },
36 | { typeof(int), new TypeConverter(s => int.Parse(s), i => i.ToString()) },
37 | { typeof(uint), new TypeConverter(s => uint.Parse(s), i => i.ToString()) },
38 | { typeof(long), new TypeConverter(s => long.Parse(s), i => i.ToString()) },
39 | { typeof(ulong), new TypeConverter(s => ulong.Parse(s), i => i.ToString()) },
40 | { typeof(short), new TypeConverter(s => short.Parse(s), i => i.ToString()) },
41 | { typeof(ushort), new TypeConverter(s => ushort.Parse(s), i => i.ToString()) },
42 | { typeof(BigInteger), new TypeConverter(s => BigInteger.Parse(s), i => i.ToString()) },
43 | { typeof(float), new TypeConverter(s => float.Parse(s), f => f.ToString()) },
44 | { typeof(double), new TypeConverter(s => double.Parse(s), f => f.ToString()) },
45 | { typeof(decimal), new TypeConverter(s => decimal.Parse(s), f => f.ToString()) },
46 | { typeof(bool), new TypeConverter(s => bool.Parse(s), b => b.ToString()) },
47 | { typeof(Computer), new TypeConverter(s => ComputerLookup.Find(s), c => ((Computer)c).idName) },
48 | { typeof(Color), new TypeConverter(s => Utils.convertStringToColor(s), c => Utils.convertColorToParseableString((Color)c)) }
49 | };
50 |
51 | public static object ConvertToType(Type t, string s)
52 | {
53 | if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
54 | {
55 | if (string.IsNullOrWhiteSpace(s))
56 | return null;
57 |
58 | t = t.GenericTypeArguments[0];
59 | }
60 |
61 | try
62 | {
63 | if (t.IsEnum)
64 | return Enum.Parse(t, s);
65 |
66 | return TypeConverters[t].ToType(s);
67 | }
68 | catch (KeyNotFoundException e)
69 | {
70 | throw new KeyNotFoundException($"No type converter exists for Type {t.Name}", e);
71 | }
72 | catch (Exception e)
73 | {
74 | throw new ConversionFailureException(s, t, e);
75 | }
76 | }
77 |
78 | public static string ConvertToString(object o, Type t = null)
79 | {
80 | if (t == null)
81 | {
82 | if (o == null)
83 | return null; //Should this error instead?
84 |
85 | t = o.GetType();
86 | }
87 |
88 | if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
89 | {
90 | if (o == null)
91 | return null;
92 |
93 | t = t.GenericTypeArguments[0];
94 | }
95 |
96 | try
97 | {
98 | if (t.IsEnum)
99 | return o.ToString();
100 |
101 | return TypeConverters[t].FromType(o);
102 | }
103 | catch (KeyNotFoundException e)
104 | {
105 | throw new KeyNotFoundException($"No type converter exists for Type {t.Name}", e);
106 | }
107 | catch (Exception e)
108 | {
109 | throw new ConversionFailureException(o, e);
110 | }
111 | }
112 |
113 | public static void AddTypeConverter(Type t, ToTypeConverter to, FromTypeConverter from)
114 | {
115 | TypeConverters.Add(t, new TypeConverter(to, from));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/PathfinderInstaller/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | venv/
3 | __pycache__/
4 | build/
5 | dist/
6 |
--------------------------------------------------------------------------------
/PathfinderInstaller/PathfinderInstaller.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python ; coding: utf-8 -*-
2 |
3 |
4 | block_cipher = None
5 |
6 |
7 | a = Analysis(['PathfinderInstaller.py'],
8 | pathex=['C:\\Users\\Aaron Robinson\\Projects\\Hacknet-Pathfinder\\PathfinderInstaller'],
9 | binaries=[],
10 | datas=[],
11 | hiddenimports=[],
12 | hookspath=[],
13 | runtime_hooks=[],
14 | excludes=[],
15 | win_no_prefer_redirects=False,
16 | win_private_assemblies=False,
17 | cipher=block_cipher,
18 | noarchive=False)
19 | pyz = PYZ(a.pure, a.zipped_data,
20 | cipher=block_cipher)
21 | exe = EXE(pyz,
22 | a.scripts,
23 | a.binaries,
24 | a.zipfiles,
25 | a.datas,
26 | [],
27 | name='PathfinderInstaller',
28 | debug=False,
29 | bootloader_ignore_signals=False,
30 | strip=False,
31 | upx=True,
32 | upx_exclude=[],
33 | runtime_tmpdir=None,
34 | console=False )
35 |
--------------------------------------------------------------------------------
/PathfinderPatcher/PathfinderPatcher.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/PathfinderUpdater/MainMenuOverride.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using Microsoft.Xna.Framework;
3 | using Pathfinder.Event;
4 | using Pathfinder.Event.Menu;
5 | using Pathfinder.Event.Options;
6 | using Pathfinder.GUI;
7 | using Pathfinder.Options;
8 | using HarmonyLib;
9 |
10 | namespace PathfinderUpdater;
11 |
12 | [HarmonyPatch]
13 | internal static class MainMenuOverride
14 | {
15 | private static OptionCheckbox IsEnabledBox = new OptionCheckbox("Enabled", "Enables the auto updater", PathfinderUpdaterPlugin.IsEnabled.Value);
16 | private static OptionCheckbox IncludePrerelease = new OptionCheckbox("IncludePreReleases", "Autoupdate to pre-releases", PathfinderUpdaterPlugin.EnablePreReleases.Value);
17 | internal static OptionCheckbox NoRestartPrompt = new OptionCheckbox("NoRestartPrompt", "Prevents a restart prompt appearing");
18 | private static PFButton CheckForUpdate = new PFButton(10, 10, 160, 30, "Check For Update", new Color(255, 255, 87));
19 | private const string UPDATE_STRING = "Update";
20 | private static PFButton PerformUpdate = new PFButton(180, 10, 160, 30, UPDATE_STRING);
21 | private static RestartPopupScreen popupScreen;
22 |
23 |
24 | [HarmonyPrefix]
25 | [HarmonyPatch(typeof(Program), nameof(Program.Main))]
26 | internal static void PFAPILoaded()
27 | {
28 | OptionsManager.AddOption("Updater", IsEnabledBox);
29 | OptionsManager.AddOption("Updater", IncludePrerelease);
30 | OptionsManager.AddOption("Updater", NoRestartPrompt);
31 | EventManager.AddHandler(OptionsSaved);
32 | EventManager.AddHandler(OnDrawMainMenu);
33 | CanPerformUpdate = PathfinderUpdaterPlugin.NeedsUpdate;
34 | }
35 |
36 | internal static void OptionsSaved(CustomOptionsSaveEvent args)
37 | {
38 | PathfinderUpdaterPlugin.IsEnabled.Value = IsEnabledBox.Value;
39 | PathfinderUpdaterPlugin.EnablePreReleases.Value = IncludePrerelease.Value;
40 | PathfinderUpdaterPlugin.NoRestartPrompt.Value = NoRestartPrompt.Value;
41 | if(popupScreen != null)
42 | popupScreen.NoRestartPrompt.Value = PathfinderUpdaterPlugin.NoRestartPrompt.Value;
43 | }
44 |
45 | internal static async Task PerformCheckAndUpdateButtonAsync()
46 | {
47 | PerformUpdate.Text = UPDATE_STRING;
48 | CanCheckForUpdate = false;
49 | var oldText = CheckForUpdate.Text;
50 | CheckForUpdate.Text = "Finding Latest Update...";
51 | CanPerformUpdate = PathfinderUpdaterPlugin.NeedsUpdate
52 | = (await PathfinderUpdaterPlugin.PerformCheckAsync(true)).Length > 0;
53 | CheckForUpdate.Text = oldText;
54 | CanCheckForUpdate = true;
55 | PerformUpdate.Text += $" (v{PathfinderUpdaterPlugin.PathfinderUpdater.LatestVersion})";
56 | }
57 |
58 | private static async Task PerformUpdateAndUpdateButtonAsync(MainMenu menu)
59 | {
60 | var couldCheckForUpdate = CanCheckForUpdate;
61 | CanCheckForUpdate = false;
62 | CanPerformUpdate = false;
63 | var oldText = PerformUpdate.Text;
64 | PerformUpdate.Text = "Currently Updating...";
65 | await PathfinderUpdaterPlugin.PerformUpdateAsync();
66 | PerformUpdate.Text = oldText;
67 | if(!menu.ScreenManager.screens.Contains(popupScreen) && !PathfinderUpdaterPlugin.NoRestartPrompt.Value)
68 | menu.ScreenManager.AddScreen(popupScreen ??= new RestartPopupScreen());
69 | CanPerformUpdate = !menu.ScreenManager.screens.Contains(popupScreen);
70 | CanCheckForUpdate = couldCheckForUpdate;
71 | }
72 |
73 | private static bool CanCheckForUpdate = true;
74 | private static bool CanPerformUpdate;
75 | internal static void OnDrawMainMenu(MainMenuEvent args)
76 | {
77 | if(CheckForUpdate.Do() && CanCheckForUpdate)
78 | Task.Run(async () => await PerformCheckAndUpdateButtonAsync());
79 |
80 | if (!PathfinderUpdaterPlugin.NeedsUpdate)
81 | return;
82 |
83 | if(PerformUpdate.Do() && CanPerformUpdate)
84 | Task.Run(async () => await PerformUpdateAndUpdateButtonAsync(args.MainMenu));
85 | }
86 | }
--------------------------------------------------------------------------------
/PathfinderUpdater/PathfinderUpdater.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | $(LibsDir)HacknetPathfinder.exe
11 | False
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {64faeda5-e87c-47ed-8200-e1de1f263040}
23 | BepInEx.Hacknet
24 | False
25 |
26 |
27 | {4de0a4cf-ec60-46e1-ad96-be3a0f5be406}
28 | PathfinderAPI
29 | False
30 |
31 |
32 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/PathfinderUpdater/RestartPopupScreen.cs:
--------------------------------------------------------------------------------
1 | using Hacknet;
2 | using Hacknet.Gui;
3 | using Microsoft.Xna.Framework;
4 | using Pathfinder.GUI;
5 | using Pathfinder.Options;
6 |
7 | namespace PathfinderUpdater;
8 |
9 | internal class RestartPopupScreen : GameScreen
10 | {
11 | private PFButton AcceptVersion = new PFButton(500, 330, 120, 30, "Yes", new Color(102,255,127));
12 | private PFButton DenyVersion = new PFButton(980, 330, 120, 30, "No", new Color(255,92,87));
13 | internal OptionCheckbox NoRestartPrompt = new OptionCheckbox("", "Do not prompt for restart");
14 |
15 | public RestartPopupScreen()
16 | {
17 | IsPopup = true;
18 | }
19 |
20 | public override void HandleInput(InputState input)
21 | {
22 | base.HandleInput(input);
23 | GuiData.doInput(input);
24 | }
25 |
26 | public override void Draw(GameTime gameTime)
27 | {
28 | ScreenManager.SpriteBatch.Begin();
29 | ScreenManager.SpriteBatch.Draw(Utils.white, new Rectangle(0, 0, ScreenManager.SpriteBatch.GraphicsDevice.Viewport.Width, GuiData.spriteBatch.GraphicsDevice.Viewport.Height), new Color(0, 0, 0, 0.65f));
30 | ScreenManager.SpriteBatch.Draw(Utils.white, new Rectangle(400, 250, 800, 150), Color.Black);
31 | TextItem.doLabel(new Vector2(550, 260), $"Do you want to restart the game?", Color.White);
32 | NoRestartPrompt.Draw(675, 300);
33 | if(AcceptVersion.Do())
34 | {
35 | PathfinderUpdaterPlugin.RestartForUpdate();
36 | ExitScreen();
37 | }
38 | else if(DenyVersion.Do())
39 | ExitScreen();
40 | ScreenManager.SpriteBatch.End();
41 | }
42 |
43 | public new void ExitScreen()
44 | {
45 | base.ExitScreen();
46 | PathfinderUpdaterPlugin.NoRestartPrompt.Value = NoRestartPrompt.Value;
47 | MainMenuOverride.NoRestartPrompt.Value = PathfinderUpdaterPlugin.NoRestartPrompt.Value;
48 | }
49 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pathfinder
2 |
3 | An extensive modding API and loader for Hacknet that enables practically limitless programable extensions to the game.
4 |
5 | Docs: https://arkhist.github.io/Hacknet-Pathfinder/
6 |
7 | ## Installation
8 |
9 | There are several options available to choose to install Pathfinder, the installer .exe, the installer .py, or the manually with the .zip.
10 |
11 | ### Installer
12 |
13 | If you're on Windows, it's recommended that you use the installer .exe from [here](https://github.com/Arkhist/Hacknet-Pathfinder/releases). Just run the installer and it should automatically find your Hacknet folder, then just hit install. Launching Hacknet from Steam will launch Pathfinder (on Windows)!
14 |
15 | If you decide to use the .py installer (or you're just on Linux and have to use it) keep in mind it requires python3 and tk to be installed before you run it.
16 |
17 | If you're on Linux, once the installer is complete, make sure to +x StartPathfinder.sh yourself.
18 |
19 | To uninstall, just reopen the installer and click uninstall. This will clear out all of the changes the installer made, and will also delete all of your mods with it.
20 |
21 | ### Manually (Windows)
22 |
23 | Get the latest ZIP from the releases page [here](https://github.com/Arkhist/Hacknet-Pathfinder/releases) and extract it to your Hacknet folder.
24 |
25 | Run PathfinderPatcher.exe and it will create HacknetPathfinder.exe, if you want this to be launched when you launch from Steam rename Hacknet.exe and replace it with HacknetPathfinder.exe.
26 |
27 | To uninstall, just delete HacknetPathfinder.exe (or whatever you renamed it to) and move back the original Hacknet.exe if you renamed it. If you also want to remove all your mods and configs, delete the BepInEx directory.
28 |
29 | ### Manually (Linux)
30 |
31 | Get the latest ZIP from the releases page [here](https://github.com/Arkhist/Hacknet-Pathfinder/releases) and extract it to your Hacknet folder.
32 |
33 | Run PathfinderPatcher.exe and it will create HacknetPathfinder.exe.
34 |
35 | Copy `Hacknet.bin.x86_64` to `HacknetPathfinder.bin.x86_64`
36 |
37 | Make StartPathfinder.sh executable and run it.
38 |
39 | ## Troubleshooting
40 |
41 | ### The game crashes before it even loads! (Windows only)
42 |
43 | If the game crashes before loading, or you see an error in the console that says `An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework.` on Windows, head into the BepInEx/core and BepInEx/plugins folders, open the properties for each DLL, and hit the unblock checkbox at the bottom.
44 |
45 | As an alternative, run this command in PowerShell in the Hacknet directory `Get-ChildItem -Recurse "./BepInEx" | Unblock-File`
46 |
47 | ## Creating Mods
48 |
49 | Use [this template](https://github.com/Windows10CE/HacknetPluginTemplate) or follow the steps below.
50 |
51 | 1. Start a new .NET library project with .NET Framework 4.0
52 | 2. Link it against the HacknetPathfinder.exe, PathfinderAPI.dll, FNA.dll, BepInEx.Core.dll, and BepInEx.Hacknet.dll. You may need more than this, but that's the reccomended set to begin on a simple mod.
53 | * You may need to go into the project options and set the project to target the x86 platform
54 | 3. Create a class that inherits from BepInEx.Hacknet.HacknetPlugin, and add the BepInEx.BepInPlugin attribute to it with a name, guid, and version.
55 | 4. And now you have a basic functioning mod, the rest is up to you.
56 |
57 | Install the mod by placing it in Hacknet/BepInEx/plugins or a folder called Plugins in your extension's folder if you want to make it extension-specific.
58 |
59 | ## Contributing to Pathfinder
60 |
61 | 1. Clone the project with `git clone https://github.com/Arkhist/Hacknet-Pathfinder` or preferably fork it and clone that repo so you can pull request
62 | 2. Compile PathfinderAPI or BepInEx.Hacknet
63 |
64 | ### Testing contiburitons
65 |
66 | 1. Copy libs/HacknetPathfinder.exe over to the Hacknet directory
67 | 2. Everything in BepInEx.Hacknet/bin/Debug goes into Hacknet/BepInEx/core.
68 | 3. Copy/symlink the .dll (or their containing folder) for PathfinderAPI and optionally ExampleMod to Hacknet/BepInEx/plugins
69 |
70 | ## Links
71 |
72 | [Discord](https://discord.gg/65SaxGg)
73 |
74 | [Github](https://github.com/Arkhist/Hacknet-Pathfinder)
75 |
76 | [Bug Reports](https://github.com/Arkhist/Hacknet-Pathfinder/issues)
77 |
--------------------------------------------------------------------------------
/hacknet_modding_docs_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/hacknet_modding_docs_logo.png
--------------------------------------------------------------------------------
/hacknet_modding_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/hacknet_modding_logo.png
--------------------------------------------------------------------------------
/libs/0Harmony.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/0Harmony.dll
--------------------------------------------------------------------------------
/libs/BepInEx.Core.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/BepInEx.Core.dll
--------------------------------------------------------------------------------
/libs/BepInEx.cfg:
--------------------------------------------------------------------------------
1 | [Caching]
2 |
3 | ## Enable/disable assembly metadata cache
4 | ## Enabling this will speed up discovery of plugins and patchers by caching the metadata of all types BepInEx discovers.
5 | # Setting type: Boolean
6 | # Default value: true
7 | EnableAssemblyCache = true
8 |
9 | [Harmony.Logger]
10 |
11 | ## Specifies which Harmony log channels to listen to.
12 | ## NOTE: IL channel dumps the whole patch methods, use only when needed!
13 | # Setting type: LogChannel
14 | # Default value: Warn, Error
15 | # Acceptable values: None, Info, IL, Warn, Error, Debug, All
16 | # Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)
17 | LogChannels = Warn, Error
18 |
19 | [Logging.Console]
20 |
21 | ## Only displays the specified log levels in the console output.
22 | # Setting type: LogLevel
23 | # Default value: Fatal, Error, Warning, Message, Info
24 | # Acceptable values: None, Fatal, Error, Warning, Message, Info, Debug, All
25 | # Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)
26 | LogLevels = All
27 |
28 | ## Enables backup console output in case the normal one fails.
29 | # Setting type: Boolean
30 | # Default value: false
31 | BackupConsole = false
32 |
33 | ## Enables showing a console for log output.
34 | # Setting type: Boolean
35 | # Default value: false
36 | Enabled = true
37 |
38 | ## If enabled, will prevent closing the console (either by deleting the close button or in other platform-specific way).
39 | # Setting type: Boolean
40 | # Default value: false
41 | PreventClose = false
42 |
43 | ## If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.
44 | # Setting type: Boolean
45 | # Default value: false
46 | ShiftJisEncoding = false
47 |
48 | ## Hints console manager on what handle to assign as StandardOut. Possible values:
49 | ## Auto - lets BepInEx decide how to redirect console output
50 | ## ConsoleOut - prefer redirecting to console output; if possible, closes original standard output
51 | ## StandardOut - prefer redirecting to standard output; if possible, closes console out
52 | ##
53 | # Setting type: ConsoleOutRedirectType
54 | # Default value: Auto
55 | # Acceptable values: Auto, ConsoleOut, StandardOut
56 | StandardOutType = Auto
57 |
58 | [Logging.Disk]
59 |
60 | ## Appends to the log file instead of overwriting, on game startup.
61 | # Setting type: Boolean
62 | # Default value: false
63 | AppendLog = false
64 |
65 | ## Enables writing log messages to disk.
66 | # Setting type: Boolean
67 | # Default value: true
68 | Enabled = true
69 |
70 | ## Only displays the specified log levels in the disk log output.
71 | # Setting type: LogLevel
72 | # Default value: Fatal, Error, Warning, Message, Info
73 | # Acceptable values: None, Fatal, Error, Warning, Message, Info, Debug, All
74 | # Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)
75 | LogLevels = All
76 |
77 | ## If true, instantly writes any received log entries to disk.
78 | ## This incurs a major performance hit if a lot of log messages are being written, however it is really useful for debugging crashes.
79 | ##
80 | # Setting type: Boolean
81 | # Default value: false
82 | InstantFlushing = true
83 |
84 | ## The maximum amount of concurrent log files that will be written to disk.
85 | ## As one log file is used per open game instance, you may find it necessary to increase this limit when debugging multiple instances at the same time.
86 | ##
87 | # Setting type: Int32
88 | # Default value: 5
89 | ConcurrentFileLimit = 5
90 |
91 |
--------------------------------------------------------------------------------
/libs/LICENSE-Harmony.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Andreas Pardeike
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.
--------------------------------------------------------------------------------
/libs/LICENSE-HarmonyX.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 BepInEx
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.
--------------------------------------------------------------------------------
/libs/LICENSE-Mono.Cecil.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008 - 2015 Jb Evain
2 | Copyright (c) 2008 - 2011 Novell, Inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/libs/LICENSE-MonoMod.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 - 2020 0x0ade
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.
--------------------------------------------------------------------------------
/libs/LICENSE-Newtonsoft.Json.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2007 James Newton-King
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/libs/LICENSE-SemVer.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Adam Reeve
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/libs/Mono.Cecil.Mdb.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/Mono.Cecil.Mdb.dll
--------------------------------------------------------------------------------
/libs/Mono.Cecil.Pdb.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/Mono.Cecil.Pdb.dll
--------------------------------------------------------------------------------
/libs/Mono.Cecil.Rocks.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/Mono.Cecil.Rocks.dll
--------------------------------------------------------------------------------
/libs/Mono.Cecil.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/Mono.Cecil.dll
--------------------------------------------------------------------------------
/libs/MonoMod.RuntimeDetour.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/MonoMod.RuntimeDetour.dll
--------------------------------------------------------------------------------
/libs/MonoMod.Utils.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/MonoMod.Utils.dll
--------------------------------------------------------------------------------
/libs/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/libs/SemanticVersioning.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arkhist/Hacknet-Pathfinder/65b6dd923485d65a1a16d72e2bb73ae6dde8698d/libs/SemanticVersioning.dll
--------------------------------------------------------------------------------