├── .editorconfig
├── .github
└── workflows
│ └── dotnetcore.yml
├── .gitignore
├── D.ConsoleApp
├── D.ConsoleApp.csproj
├── GameStateBenchmark.cs
└── Program.cs
├── D.Core
├── BotConfiguration.cs
├── D.Core.csproj
├── GameState.cs
├── GetOutBot.cs
├── GetOutBotAnalytic.cs
├── IStateUiWriter.cs
├── InteropTcpHelper.cs
├── MemoryReader.cs
└── WindowsHelper.cs
├── D.Models
├── D.Models.csproj
├── StatEnum.cs
└── Structs.cs
├── D.WpfApp
├── App.xaml
├── App.xaml.cs
├── ApplicationHelper.cs
├── D.WpfApp.csproj
├── GitVersion.yml
├── MachineStamp.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── StateUiWriter.cs
├── TextPath.cs
├── app.manifest
├── config.yml
└── run.cmd
├── GitVersion.yml
├── README.md
├── d2rStructs.reclass
├── dependencies
└── Analytics.Lib.dll
└── diablobot.sln
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # See https://github.com/dotnet/format/blob/master/docs/Supported-.editorconfig-options.md for reference
4 | [*.cs]
5 | csharp_new_line_before_open_brace = none
6 | csharp_new_line_before_catch = false
7 | csharp_new_line_before_else = false
8 | csharp_new_line_before_finally = false
9 | csharp_new_line_before_members_in_anonymous_types = false
10 | csharp_new_line_before_members_in_object_initializers = false
11 | csharp_new_line_between_query_expression_clauses = true
--------------------------------------------------------------------------------
/.github/workflows/dotnetcore.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/heads
6 | branches:
7 | - main
8 |
9 | jobs:
10 | build:
11 | runs-on: windows-latest
12 | steps:
13 | - name: Install GitVersion
14 | uses: gittools/actions/gitversion/setup@v0.9.7
15 | with:
16 | versionSpec: '5.5.0'
17 | - name: Show variable
18 | run: |
19 | echo "token: ${{ secrets.GITHUB_TOKEN }}"
20 | - uses: actions/checkout@v1
21 | name: "Checkout"
22 | - name: Determine Version
23 | id: gitversion # step id used as reference for output values
24 | uses: gittools/actions/gitversion/execute@v0.9.7
25 | with:
26 | useConfigFile: true
27 | configFilePath: D.WpfApp/GitVersion.yml
28 | - name: Display GitVersion outputs
29 | run: |
30 | echo "Major: ${{ steps.gitversion.outputs.major }}"
31 | echo "Minor: ${{ steps.gitversion.outputs.minor }}"
32 | echo "Patch: ${{ steps.gitversion.outputs.patch }}"
33 | echo "PreReleaseTag: ${{ steps.gitversion.outputs.preReleaseTag }}"
34 | echo "PreReleaseTagWithDash: ${{ steps.gitversion.outputs.preReleaseTagWithDash }}"
35 | echo "PreReleaseLabel: ${{ steps.gitversion.outputs.preReleaseLabel }}"
36 | echo "PreReleaseNumber: ${{ steps.gitversion.outputs.preReleaseNumber }}"
37 | echo "WeightedPreReleaseNumber: ${{ steps.gitversion.outputs.weightedPreReleaseNumber }}"
38 | echo "BuildMetaData: ${{ steps.gitversion.outputs.buildMetaData }}"
39 | echo "BuildMetaDataPadded: ${{ steps.gitversion.outputs.buildMetaDataPadded }}"
40 | echo "FullBuildMetaData: ${{ steps.gitversion.outputs.fullBuildMetaData }}"
41 | echo "MajorMinorPatch: ${{ steps.gitversion.outputs.majorMinorPatch }}"
42 | echo "SemVer: ${{ steps.gitversion.outputs.semVer }}"
43 | echo "LegacySemVer: ${{ steps.gitversion.outputs.legacySemVer }}"
44 | echo "LegacySemVerPadded: ${{ steps.gitversion.outputs.legacySemVerPadded }}"
45 | echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}"
46 | echo "AssemblySemFileVer: ${{ steps.gitversion.outputs.assemblySemFileVer }}"
47 | echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
48 | echo "InformationalVersion: ${{ steps.gitversion.outputs.informationalVersion }}"
49 | echo "BranchName: ${{ steps.gitversion.outputs.branchName }}"
50 | echo "EscapedBranchName: ${{ steps.gitversion.outputs.escapedBranchName }}"
51 | echo "Sha: ${{ steps.gitversion.outputs.sha }}"
52 | echo "ShortSha: ${{ steps.gitversion.outputs.shortSha }}"
53 | echo "NuGetVersionV2: ${{ steps.gitversion.outputs.nuGetVersionV2 }}"
54 | echo "NuGetVersion: ${{ steps.gitversion.outputs.nuGetVersion }}"
55 | echo "NuGetPreReleaseTagV2: ${{ steps.gitversion.outputs.nuGetPreReleaseTagV2 }}"
56 | echo "NuGetPreReleaseTag: ${{ steps.gitversion.outputs.nuGetPreReleaseTag }}"
57 | echo "VersionSourceSha: ${{ steps.gitversion.outputs.versionSourceSha }}"
58 | echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
59 | echo "CommitsSinceVersionSourcePadded: ${{ steps.gitversion.outputs.commitsSinceVersionSourcePadded }}"
60 | echo "UncommittedChanges: ${{ steps.gitversion.outputs.uncommittedChanges }}"
61 | echo "CommitDate: ${{ steps.gitversion.outputs.commitDate }}"
62 | - uses: actions/setup-dotnet@v1
63 | with:
64 | dotnet-version: 6.0.100
65 | - name: Build with dotnet
66 | run: dotnet publish ./D.WpfApp/D.WpfApp.csproj -r win10-x64 -c release --self-contained /p:PublishSingleFile=true /p:DebugType=None /p:DebugSymbols=false
67 | - name: Rename file
68 | run: powershell Rename-Item D.WpfApp/bin/release/net6.0-windows/win10-x64/publish/D.WpfApp.exe -NewName "GetOutBot.exe"
69 | - name: Build project # This would actually build your project, using zip for an example artifact
70 | run: |
71 | Compress-Archive -Path D.WpfApp/bin/release/net6.0-windows/win10-x64/publish/ -DestinationPath package.zip
72 | - name: Create Release
73 | id: create_release
74 | uses: actions/create-release@v1
75 | env:
76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77 | with:
78 | tag_name: ${{ steps.gitversion.outputs.fullSemVer }}
79 | release_name: Release ${{ steps.gitversion.outputs.fullSemVer }}
80 | draft: false
81 | prerelease: false
82 | - name: Upload Release Asset
83 | id: upload-release-asset
84 | uses: actions/upload-release-asset@v1
85 | env:
86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87 | with:
88 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
89 | asset_path: ./package.zip
90 | asset_name: package.zip
91 | asset_content_type: application/zip
92 | # - name: 'Upload package'
93 | # uses: actions/upload-artifact@v1
94 | # with:
95 | # name: windows-operationroom
96 | # path: ./bin/release/netcoreapp3.1/win10-x64/publish/operation-rooms.exe
97 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 | .vs/
9 | .gitattributes
10 |
11 | # Build results
12 | [Dd]ebug/
13 | [Dd]ebugPublic/
14 | [Rr]elease/
15 | x64/
16 | build/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 | App_Data/
21 |
22 | # Git
23 | *.orig
24 |
25 | .idea/**
26 |
27 | **D.ConsoleApp\BenchmarkDotnet**
--------------------------------------------------------------------------------
/D.ConsoleApp/D.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/D.ConsoleApp/GameStateBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Order;
3 | using D.Core;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace D.ConsoleApp {
7 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
8 | public class GameStateBenchmark {
9 | public MemoryReader memoryReader;
10 |
11 | [GlobalSetup]
12 | public void Setup() {
13 | memoryReader = new MemoryReader(new Logger(new LoggerFactory()));
14 | }
15 |
16 | [Benchmark]
17 | public void GetState() {
18 | var state = memoryReader.GetState();
19 | }
20 |
21 | [Benchmark]
22 | public void GetTcpTable() {
23 | var state = InteropTcpHelper.GetAllTcpConnections();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/D.ConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 |
3 | using BenchmarkDotNet.Running;
4 | using D.ConsoleApp;
5 | using D.Core;
6 | using D.Models;
7 | using Microsoft.Extensions.Logging;
8 | using System.Text;
9 |
10 | public class Program {
11 |
12 | static string mainPlayer = "";
13 | static void Main(string[] args) {
14 | // BenchmarkRunner.Run();
15 |
16 | var memoryReader = new MemoryReader(new Logger(new LoggerFactory()));
17 | var p = memoryReader.GetProcessInfo();
18 | var hashTable = memoryReader.GetUnitHashtableAddress();
19 |
20 | var unitHashTable = WindowsHelper.Read(p.Handle, hashTable);
21 |
22 | var dic = new Dictionary>();
23 | foreach (var unitPtr in unitHashTable.UnitTable) {
24 | var unitAny = WindowsHelper.Read(p.Handle, unitPtr);
25 | ReadAllUnit(unitAny, unitPtr, dic, p.Handle);
26 | //do {
27 | // if (unitAny.UnityType != 0)
28 | // continue;
29 |
30 | // var playerName = Encoding.ASCII.GetString(WindowsHelper.Read(p.Handle, unitAny.UnitData, 16)).TrimEnd((char)0);
31 | // if (!string.IsNullOrEmpty(playerName)) {
32 | // if (dic.ContainsKey(playerName)) {
33 | // dic[playerName].Add(unitPtr);
34 | // mainPlayer = playerName;
35 | // } else {
36 | // dic.Add(playerName, new List { unitPtr });
37 | // }
38 | // }
39 |
40 | // unitAny = WindowsHelper.Read(p.Handle, unitAny.pListNext);
41 | //}while()
42 |
43 | }
44 |
45 | foreach (var entry in dic) {
46 | Console.WriteLine($"{entry.Key} count: {entry.Value.Count}");
47 | }
48 |
49 | Console.WriteLine("finish");
50 | }
51 |
52 | public static void ReadAllUnit(UnitAny unitAny, IntPtr curUnitPtr, Dictionary> dic, IntPtr hdl) {
53 | if (curUnitPtr == IntPtr.Zero || unitAny.UnityType != 0)
54 | return;
55 |
56 | var playerName = Encoding.ASCII.GetString(WindowsHelper.Read(hdl, unitAny.UnitData, 16)).TrimEnd((char)0);
57 | if (!string.IsNullOrEmpty(playerName)) {
58 | if (dic.ContainsKey(playerName)) {
59 | dic[playerName].Add(curUnitPtr);
60 | mainPlayer = playerName;
61 | } else {
62 | dic.Add(playerName, new List { curUnitPtr });
63 | }
64 | }
65 | ReadAllUnit(WindowsHelper.Read(hdl, unitAny.pListNext), unitAny.pListNext, dic, hdl);
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/D.Core/BotConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace D.Core {
4 | public enum QuitMethod {
5 | None,
6 | Both,
7 | Socket,
8 | Menu
9 | }
10 |
11 | public class BotConfiguration {
12 | public BotConfiguration(IConfiguration config) {
13 | QuitOnHealthPercentage = (double)config.GetValue("QuitOnHealthPercentage", 35) / 100;
14 | QuitMethod = config.GetValue("QuitMethod", QuitMethod.Both);
15 | QuitOnMajorHit = config.GetValue("QuitOnMajorHit", true);
16 | }
17 |
18 | public double QuitOnHealthPercentage { get; set; }
19 | public QuitMethod QuitMethod { get; set; }
20 | public bool QuitOnMajorHit { get; set; }
21 |
22 | public bool QuitWithSocket => QuitMethod is QuitMethod.Both or QuitMethod.Socket;
23 |
24 | public bool QuitWithMenu => QuitMethod is QuitMethod.Both or QuitMethod.Menu;
25 | }
26 | }
--------------------------------------------------------------------------------
/D.Core/D.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ..\dependencies\Analytics.Lib.dll
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/D.Core/GameState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using D.Models;
4 |
5 | namespace D.Core {
6 | public record GameState {
7 | public string PlayerName { get; set; }
8 | public Area CurrentArea { get; set; }
9 | public bool IsInTown { get; set; }
10 |
11 | public bool IsGameMenuOpen { get; set; }
12 |
13 | public int? PreviousHealth { get; set; }
14 |
15 | public int CurrentHealth { get; set; }
16 |
17 | public int MaxHealth { get; set; }
18 |
19 | [JsonIgnore]
20 | public IntPtr WindowHandle { get; set; }
21 |
22 |
23 | [JsonIgnore]
24 | public InteropTcpHelper.MIB_TCPROW_OWNER_PID? GameSocket { get; set; }
25 |
26 | public bool IsInGame() => CurrentArea != Area.None;
27 |
28 | public bool IsDead() => IsInGame() && CurrentHealth <= 0;
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/D.Core/GetOutBot.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using D.Models;
5 | using Microsoft.Extensions.Logging;
6 | using Stateless;
7 |
8 | namespace D.Core {
9 | public class GetOutBot {
10 | public enum State {
11 | LookingForPlayer,
12 | WatchingYou,
13 | InTown,
14 | GettingOut,
15 | IsDead
16 | }
17 |
18 | public enum Trigger {
19 | Watch,
20 | Quited,
21 | SearchPlayer,
22 | GetOut,
23 | IsSafeInTown,
24 | Death
25 | }
26 |
27 | private readonly CancellationTokenSource m_BotTaskSrc = new();
28 |
29 | private readonly BotConfiguration m_Config;
30 |
31 | private readonly ILogger m_Logger;
32 |
33 | private readonly MemoryReader m_MemoryReader;
34 |
35 | public readonly StateMachine m_stateMachine;
36 | private readonly IStateUiWriter m_StateUiWriter;
37 |
38 | private readonly CancellationToken m_token;
39 | private StateMachine.TriggerWithParameters _setDeathTrigger;
40 |
41 | private StateMachine.TriggerWithParameters _setGetOutTrigger;
42 |
43 | public GetOutBot(IStateUiWriter stateUiWriter, BotConfiguration config, MemoryReader memoryReader,
44 | ILogger mLogger, CancellationToken mToken = default) {
45 | m_Config = config;
46 | m_Logger = mLogger;
47 | m_MemoryReader = memoryReader;
48 | m_StateUiWriter = stateUiWriter;
49 | m_stateMachine = new StateMachine(State.LookingForPlayer);
50 | m_token = mToken;
51 | m_token = m_BotTaskSrc.Token;
52 |
53 | ConfigureStateMachine();
54 | }
55 |
56 | public void ConfigureStateMachine() {
57 | m_StateUiWriter.WriteState($"{State.LookingForPlayer}");
58 | _setGetOutTrigger = m_stateMachine.SetTriggerParameters(Trigger.GetOut);
59 | _setDeathTrigger = m_stateMachine.SetTriggerParameters(Trigger.Death);
60 | m_stateMachine.OnTransitioned(transition => {
61 | // if change in state
62 | if (transition.Source != transition.Destination) {
63 | m_StateUiWriter.WriteState($"{transition.Destination}");
64 | m_Logger.LogInformation($"State transition: {transition.Destination}");
65 | if (transition.Source == State.LookingForPlayer)
66 | m_StateUiWriter.WriteAdditionalData("");
67 | else if (transition.Destination == State.LookingForPlayer && transition.Source != State.GettingOut)
68 | m_StateUiWriter.WriteAdditionalData("");
69 | }
70 | });
71 |
72 | m_stateMachine.Configure(State.LookingForPlayer)
73 | .PermitReentry(Trigger.SearchPlayer)
74 | .OnEntryFromAsync(Trigger.SearchPlayer, async () => await Task.Delay(1000))
75 | .Permit(Trigger.Death, State.IsDead)
76 | .Permit(Trigger.IsSafeInTown, State.InTown)
77 | .Permit(Trigger.GetOut, State.GettingOut)
78 | .Permit(Trigger.Watch, State.WatchingYou)
79 | .OnEntryAsync(async () => await OnWatching());
80 |
81 | m_stateMachine.Configure(State.WatchingYou)
82 | .PermitReentry(Trigger.Watch)
83 | .Permit(Trigger.Death, State.IsDead)
84 | .Permit(Trigger.IsSafeInTown, State.InTown)
85 | .OnEntryFromAsync(Trigger.IsSafeInTown, async () => await Task.Delay(500))
86 | .Permit(Trigger.GetOut, State.GettingOut)
87 | .Permit(Trigger.SearchPlayer, State.LookingForPlayer)
88 | .OnEntryAsync(async () => await OnWatching());
89 |
90 | m_stateMachine.Configure(State.InTown)
91 | .SubstateOf(State.WatchingYou)
92 | .PermitReentry(Trigger.IsSafeInTown)
93 | .OnEntryFromAsync(Trigger.IsSafeInTown, async () => await Task.Delay(500))
94 | .Permit(Trigger.Watch, State.WatchingYou);
95 |
96 | m_stateMachine.Configure(State.GettingOut)
97 | .Permit(Trigger.Death, State.IsDead)
98 | .Permit(Trigger.Watch, State.WatchingYou)
99 | .Permit(Trigger.IsSafeInTown, State.InTown)
100 | .OnEntryFromAsync(_setGetOutTrigger, async state => await OnGetOut(state))
101 | .Permit(Trigger.Quited, State.LookingForPlayer);
102 |
103 | m_stateMachine.Configure(State.IsDead)
104 | .OnEntryFromAsync(_setDeathTrigger, async state => await OnDeath(state))
105 | .Permit(Trigger.Watch, State.WatchingYou);
106 |
107 | // just ignore trigger in bad state
108 | m_stateMachine.OnUnhandledTrigger((s, t) => { m_Logger.LogWarning($"Invalid transition: {s} {t}"); });
109 | }
110 |
111 | public async Task Run() {
112 | m_Logger.LogInformation("run bot");
113 | await Task.Run(async () => {
114 | do {
115 | await OnWatching();
116 | await Task.Delay(10);
117 | } while (true);
118 | });
119 | }
120 |
121 | public async Task OnWatching() {
122 | try {
123 | if (m_token.IsCancellationRequested)
124 | return;
125 |
126 | var state = GetState();
127 |
128 | if (state == null) {
129 | await m_stateMachine.FireAsync(Trigger.SearchPlayer);
130 | return;
131 | }
132 |
133 | m_StateUiWriter.WriteAdditionalData($"guarding: {state.PlayerName}");
134 | if (state.IsInTown && state.CurrentArea != Area.None) {
135 | await m_stateMachine.FireAsync(Trigger.IsSafeInTown);
136 | return;
137 | }
138 |
139 | if (state.IsDead()) {
140 | await m_stateMachine.FireAsync(_setDeathTrigger, state);
141 | return;
142 | }
143 |
144 | if (HasToGetOut(state)) {
145 | await m_stateMachine.FireAsync(_setGetOutTrigger, state);
146 | return;
147 | }
148 |
149 | await Task.Delay(1);
150 | await m_stateMachine.FireAsync(Trigger.Watch);
151 | } catch (Exception e) {
152 | GetOutBotAnalytic.SendError(e.Message);
153 | m_Logger.LogError("Exception in OnWatching {@e}", e);
154 | throw;
155 | }
156 | }
157 |
158 | private async Task OnGetOut(GameState receivedState) {
159 | try {
160 | m_StateUiWriter.WriteAdditionalData("Try to get out!");
161 | var currentHealth = receivedState.CurrentHealth;
162 |
163 | var successfulOut = await TryToGetOut(receivedState);
164 | var state = GetState();
165 | if (!successfulOut && state != null && state.IsDead()) {
166 | await m_stateMachine.FireAsync(_setDeathTrigger, state);
167 | } else if (!successfulOut) {
168 | m_Logger.LogCritical("Couldn't get out!");
169 | m_Logger.LogWarning("Couldn't get out :( {@state}", state);
170 | m_StateUiWriter.WriteAdditionalData("Couldn't get out :(");
171 | GetOutBotAnalytic.SendInfo("couldn't get out", receivedState);
172 | await Task.Delay(2000);
173 | await m_stateMachine.FireAsync(Trigger.Watch);
174 | } else // Success!
175 | {
176 | m_Logger.LogInformation("Got out! With {currentHealth} hp", currentHealth);
177 | m_StateUiWriter.WriteAdditionalData($"Got out! With {currentHealth} hp");
178 | GetOutBotAnalytic.SendGotOutEvent(receivedState);
179 | await Task.Delay(6000);
180 | await m_stateMachine.FireAsync(Trigger.Quited);
181 | }
182 | } catch (Exception e) {
183 | GetOutBotAnalytic.SendError(e.Message);
184 | m_Logger.LogError("Exception in OnGetOut {@e}", e);
185 | throw;
186 | }
187 | }
188 |
189 | private async Task OnDeath(GameState state) {
190 | try {
191 | m_Logger.LogWarning("Death {@state}", state);
192 | m_StateUiWriter.WriteAdditionalData("woups..");
193 | GetOutBotAnalytic.SendDeathEvent(state);
194 | // stay in that state until not death (so in menu or other char)
195 | SpinWait.SpinUntil(() => {
196 | Thread.Sleep(500);
197 | return !GetState()?.IsDead() ?? true;
198 | }, -1);
199 | m_StateUiWriter.Clear();
200 | await m_stateMachine.FireAsync(Trigger.Watch);
201 | } catch (Exception e) {
202 | GetOutBotAnalytic.SendError(e.Message);
203 | m_Logger.LogError("exception in OnDeath {@e}", e);
204 | throw;
205 | }
206 | }
207 |
208 | private bool HasToGetOut(GameState? state) {
209 | if (state == null || state.IsDead() || state.IsInTown)
210 | return false;
211 |
212 | if (m_Config.QuitOnMajorHit && state.PreviousHealth.HasValue
213 | && state.PreviousHealth.Value <= state.MaxHealth) { // to prevent quitting when buff end
214 | var previousHealth = state.PreviousHealth.Value;
215 | var previousHit = previousHealth - state.CurrentHealth;
216 | var wasMajorHit = previousHit >= state.CurrentHealth;
217 | if (wasMajorHit) {
218 | m_Logger.LogInformation(
219 | "previous hit was {previousHit} and life remaining {currentHealth} so get out", previousHit,
220 | state.CurrentHealth);
221 | return true;
222 | }
223 | }
224 |
225 | return (double)state.CurrentHealth / state.MaxHealth <= m_Config.QuitOnHealthPercentage;
226 | }
227 |
228 | private GameState? GetState() {
229 | var state = m_MemoryReader.GetState();
230 | return state;
231 | }
232 |
233 | private async Task TryToGetOut(GameState? state) {
234 | var tokenSrc = new CancellationTokenSource();
235 | var token = tokenSrc.Token;
236 | m_Logger.LogInformation("Quit method: {QuitMethod}", m_Config.QuitMethod);
237 |
238 | var outFromSocketTsk = m_Config.QuitWithSocket
239 | ? Task.Run(() => TryToGetOutSocket(state))
240 | : Task.FromResult(false);
241 | var outFromMenuTsk = m_Config.QuitWithMenu
242 | ? Task.Run(() => TryToGetOutWithGameMenu(state, token))
243 | : Task.FromResult(false);
244 |
245 | Task.WaitAny(outFromSocketTsk, outFromMenuTsk);
246 | // cancel only if successfully killed socket
247 | if (outFromSocketTsk.IsCompleted && outFromSocketTsk.Result)
248 | tokenSrc.Cancel();
249 |
250 | if (outFromMenuTsk.Result)
251 | m_Logger.LogInformation("out from menu");
252 | if (outFromSocketTsk.Result)
253 | m_Logger.LogInformation("out from socket");
254 |
255 | return outFromSocketTsk.Result || outFromMenuTsk.Result;
256 | }
257 |
258 | private async Task TryToGetOutWithGameMenu(GameState receivedState, CancellationToken token) {
259 | var state = receivedState;
260 | m_Logger.LogInformation("start get out from menu task");
261 | try {
262 | // try some time
263 | var delayInMilliseconds = 10;
264 | var tryCountMax = 2500 / delayInMilliseconds;
265 | var tryCount = 0;
266 | var hasToGetOut = true;
267 | do {
268 | // we at least try 4 time before cancelling. since maybe the socket kill wasn't the right one lol
269 | if (token.IsCancellationRequested && tryCount > 4) {
270 | m_Logger.LogInformation("get out from menu was cancelled");
271 | return false;
272 | }
273 |
274 | WindowsHelper.SetForegroundWindow(state.WindowHandle);
275 | if (!state.IsGameMenuOpen) WindowsHelper.SendEscapeKey(state.WindowHandle);
276 |
277 | var windowRect = WindowsHelper.GetWindowRect(state.WindowHandle);
278 |
279 | var width = windowRect.Right - windowRect.Left;
280 | var height = windowRect.Bottom - windowRect.Top;
281 | WindowsHelper.LeftMouseClick(windowRect.Left + width / 2, windowRect.Top + height / 20 * 9);
282 | m_Logger.LogInformation($"Tried to get out from menu. tryCount: {tryCount}");
283 | await Task.Delay(10);
284 | tryCount++;
285 | state = GetState();
286 | hasToGetOut = HasToGetOut(state);
287 | } while (hasToGetOut && tryCount <= tryCountMax);
288 |
289 | return !hasToGetOut;
290 | } catch (TaskCanceledException) {
291 | m_Logger.LogInformation("get out from menu was cancelled");
292 | return false;
293 | }
294 | }
295 |
296 | private async Task TryToGetOutSocket(GameState? state) {
297 | m_Logger.LogInformation("start get out from socket task");
298 | if (state?.GameSocket == null)
299 | return false;
300 |
301 | var result = InteropTcpHelper.DeleteSocket(state.GameSocket.Value);
302 | if (result != 0) {
303 | GetOutBotAnalytic.SendError($"couldn't kill socket {result}");
304 | m_Logger.LogError("couldn't kill socket {@result}", result);
305 | return false;
306 | }
307 |
308 | m_Logger.LogInformation("successful kill socket");
309 | return true;
310 | }
311 | }
312 | }
--------------------------------------------------------------------------------
/D.Core/GetOutBotAnalytic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using analytics_lib;
4 | using System.Text.Json;
5 |
6 | namespace D.Core {
7 | public static class EventTypes {
8 | public static string Session = "session";
9 |
10 | public static string Error = "error";
11 |
12 | public static string Info = "info";
13 |
14 | public static string Death = "death";
15 |
16 | public static string GotOut = "gotOut";
17 |
18 | public static string Start = "start";
19 | }
20 |
21 | public static class GetOutBotAnalytic {
22 |
23 | public static IDisposable StartSession() => new GetOutAnalytic();
24 |
25 | public static void SendError(string message) => new BotErrorEvent(message).Send();
26 |
27 | public static void SendInfo(string message, object data = null) => new InfoEvent(message, data).Send();
28 | public static void SendDeathEvent(object data) => new DeathEvent(data).Send();
29 |
30 | public static void SendStart() => new StartEvent().Send();
31 |
32 | public static void SendGotOutEvent(GameState data) => new GotOutEvent(data).Send();
33 | }
34 |
35 | public class BotSpanBase : AnalyticSpanBase {
36 | static readonly JsonSerializerOptions k_SerializeOptions = new() {
37 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
38 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
39 | DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
40 | };
41 |
42 | public BotSpanBase(string name) : base(name) {
43 | AddData("ts", DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK"));
44 | AddData("eventType", name);
45 | }
46 |
47 | public void SetMessage(string message) => AddData("message", message);
48 |
49 | public void SetAdditionalData(IDictionary data) => AddData("data", JsonSerializer.Serialize(data, k_SerializeOptions));
50 | }
51 |
52 | public class BotEventBase : AnalyticEventBase {
53 | static readonly JsonSerializerOptions k_SerializeOptions = new() {
54 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
55 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
56 | DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
57 | };
58 |
59 | public BotEventBase(string name) : base(name) {
60 | AddData("ts", DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK"));
61 | AddData("eventType", name);
62 | }
63 |
64 | public void SetMessage(string message) => AddData("message", message);
65 |
66 | public void SetAdditionalData(object data) => AddData("data", JsonSerializer.Serialize(data, k_SerializeOptions));
67 | }
68 |
69 | public class StartEvent : BotEventBase {
70 | public StartEvent() : base(EventTypes.Start) {
71 | }
72 | }
73 |
74 | public class InfoEvent : BotEventBase {
75 | public InfoEvent(string message, object data) : base(EventTypes.Info) {
76 | SetMessage(message);
77 | if (data != null)
78 | SetAdditionalData(data);
79 | }
80 | }
81 |
82 |
83 | public class GetOutAnalytic : BotSpanBase {
84 | public GetOutAnalytic() : base(EventTypes.Session) {
85 | }
86 | }
87 |
88 | public class BotErrorEvent : BotEventBase {
89 | public BotErrorEvent(string message) : base(EventTypes.Error) {
90 | SetMessage(message);
91 | }
92 | }
93 |
94 | public class DeathEvent : BotEventBase {
95 | public DeathEvent(object data) : base(EventTypes.Death) {
96 | SetAdditionalData(data);
97 | }
98 | }
99 |
100 | public class GotOutEvent : BotEventBase {
101 | public GotOutEvent(GameState state) : base(EventTypes.GotOut) {
102 | SetAdditionalData(new Dictionary {
103 | {"state", state },
104 | {"percentage", ((double)state.CurrentHealth/state.MaxHealth).ToString("0.##") }
105 | });
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/D.Core/IStateUiWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace D.Core {
8 | public interface IStateUiWriter {
9 | void Clear();
10 | void WriteState(string state);
11 | void WriteAdditionalData(string data);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/D.Core/InteropTcpHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace D.Core {
5 | public static class InteropTcpHelper {
6 | public static int DeleteSocket(MIB_TCPROW_OWNER_PID socket) {
7 | socket.state = (int)InteropTcpHelper.State.Delete_TCB;
8 | var ptr = GetPtrToNewObject(socket);
9 | // https://github.com/magisterquis/EDRSniper#error-317
10 | // 317 The function is unable to set the TCP entry since the application is running non-elevated.
11 | return InteropTcpHelper.SetTcpEntry(ptr);
12 | }
13 |
14 | private static IntPtr GetPtrToNewObject(object? obj) {
15 | if (obj == null) return IntPtr.Zero;
16 | IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(obj));
17 | Marshal.StructureToPtr(obj, ptr, false);
18 | return ptr;
19 | }
20 |
21 | [DllImport("iphlpapi.dll")]
22 | //private static extern int SetTcpEntry(MIB_TCPROW tcprow);
23 | public static extern int SetTcpEntry(IntPtr pTcprow);
24 |
25 | public enum TCP_TABLE_CLASS {
26 | TCP_TABLE_BASIC_LISTENER,
27 | TCP_TABLE_BASIC_CONNECTIONS,
28 | TCP_TABLE_BASIC_ALL,
29 | TCP_TABLE_OWNER_PID_LISTENER,
30 | TCP_TABLE_OWNER_PID_CONNECTIONS,
31 | TCP_TABLE_OWNER_PID_ALL,
32 | TCP_TABLE_OWNER_MODULE_LISTENER,
33 | TCP_TABLE_OWNER_MODULE_CONNECTIONS,
34 | TCP_TABLE_OWNER_MODULE_ALL
35 | }
36 |
37 | [DllImport("iphlpapi.dll", SetLastError = true)]
38 | private static extern uint GetExtendedTcpTable(IntPtr pTcpTable,
39 | ref int dwOutBufLen,
40 | bool sort,
41 | int ipVersion,
42 | TCP_TABLE_CLASS tblClass,
43 | int reserved);
44 |
45 | public static MIB_TCPROW_OWNER_PID[] GetAllTcpConnections() {
46 | var AF_INET = 2; // IP_v4
47 | var buffSize = 0;
48 |
49 | // how much memory do we need?
50 | var ret = GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, AF_INET,
51 | TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
52 | if (ret != 0 && ret != 122) // 122 insufficient buffer size
53 | throw new Exception("bad ret on check " + ret);
54 | var buffTable = Marshal.AllocHGlobal(buffSize);
55 |
56 | try {
57 | ret = GetExtendedTcpTable(buffTable,
58 | ref buffSize,
59 | true,
60 | AF_INET,
61 | TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL,
62 | 0);
63 | if (ret != 0)
64 | throw new Exception("bad ret " + ret);
65 |
66 | // get the number of entries in the table
67 | var tab = (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));
68 | var rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
69 | MIB_TCPROW_OWNER_PID[] tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];
70 |
71 | for (var i = 0; i < tab.dwNumEntries; i++) {
72 | var tcpRow = (MIB_TCPROW_OWNER_PID)Marshal
73 | .PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));
74 | tTable[i] = tcpRow;
75 | // next entry
76 | rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow));
77 | }
78 |
79 | return tTable;
80 | } finally {
81 | // Free the Memory
82 | Marshal.FreeHGlobal(buffTable);
83 | }
84 | }
85 |
86 | [StructLayout(LayoutKind.Sequential)]
87 | public struct MIB_TCPROW_OWNER_PID {
88 | public uint state;
89 | public uint localAddr;
90 | public byte localPort1;
91 | public byte localPort2;
92 | public byte localPort3;
93 | public byte localPort4;
94 | public uint remoteAddr;
95 | public byte remotePort1;
96 | public byte remotePort2;
97 | public byte remotePort3;
98 | public byte remotePort4;
99 | public int owningPid;
100 |
101 | public ushort LocalPort {
102 | get {
103 | return BitConverter.ToUInt16(
104 | new byte[2] { localPort2, localPort1 }, 0);
105 | }
106 | }
107 |
108 | public ushort RemotePort {
109 | get {
110 | return BitConverter.ToUInt16(
111 | new byte[2] { remotePort2, remotePort1 }, 0);
112 | }
113 | }
114 | }
115 |
116 | public enum State {
117 | /// All
118 | All = 0,
119 |
120 | /// Closed
121 | Closed = 1,
122 |
123 | /// Listen
124 | Listen = 2,
125 |
126 | /// Syn_Sent
127 | Syn_Sent = 3,
128 |
129 | /// Syn_Rcvd
130 | Syn_Rcvd = 4,
131 |
132 | /// Established
133 | Established = 5,
134 |
135 | /// Fin_Wait1
136 | Fin_Wait1 = 6,
137 |
138 | /// Fin_Wait2
139 | Fin_Wait2 = 7,
140 |
141 | /// Close_Wait
142 | Close_Wait = 8,
143 |
144 | /// Closing
145 | Closing = 9,
146 |
147 | /// Last_Ack
148 | Last_Ack = 10,
149 |
150 | /// Time_Wait
151 | Time_Wait = 11,
152 |
153 | /// Delete_TCB
154 | Delete_TCB = 12
155 | }
156 |
157 | [StructLayout(LayoutKind.Sequential)]
158 | public struct MIB_TCPTABLE_OWNER_PID {
159 | public uint dwNumEntries;
160 | private readonly MIB_TCPROW_OWNER_PID table;
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/D.Core/MemoryReader.cs:
--------------------------------------------------------------------------------
1 | using D.Models;
2 | using Reloaded.Memory.Sigscan;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Runtime.InteropServices;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace D.Core {
13 | public partial class MemoryReader {
14 | private static readonly string ProcessName = Encoding.UTF8.GetString(new byte[] { 68, 50, 82 });
15 | public static int UnitHashTable = 0x20AF660;
16 | public static int UiSettings = 0x20BF310;
17 | public static int ExpansionCheck = 0x20BF335;
18 |
19 | private ProcessInfo m_ProcessInfo;
20 | private MemoryState _mMemoryState;
21 |
22 | private Models.Path mPath = default;
23 | private Room mRoom = default;
24 | private RoomEx mRoomEx = default;
25 | private Level mLevel = default;
26 | private StatList mStatList = default;
27 | private UiGameMenus mGameMenus = default;
28 | private static ILogger Logger;
29 | private GameState? previousState = null;
30 |
31 | public MemoryReader(ILogger logger) {
32 | Logger = logger;
33 | _mMemoryState = new MemoryState();
34 | m_ProcessInfo = GetProcessInfo();
35 | }
36 |
37 | class MemoryState {
38 | public MemoryState() {
39 | GameSocket = null;
40 | }
41 |
42 | public IntPtr UiSettingPtr { get; set; }
43 | public IntPtr PlayerUnitPtr { get; set; }
44 | public IntPtr UnitHashtableAddressPtr { get; set; }
45 | public IntPtr UiSettingAddressPtr { get; set; }
46 | public InteropTcpHelper.MIB_TCPROW_OWNER_PID? GameSocket { get; set; } = null;
47 |
48 | public UnitAny PlayerUnit = default;
49 | }
50 |
51 | void Reset() {
52 | previousState = null;
53 | _mMemoryState = new MemoryState();
54 | }
55 | // search process
56 | // search player
57 | // check if we still have the right player
58 |
59 | public class ProcessInfo {
60 | public Process? Process;
61 | public IntPtr Handle;
62 | public IntPtr BaseAddress;
63 | public IntPtr ProcessWindowHandle;
64 | public Scanner Scanner;
65 |
66 | public static ProcessInfo Create() {
67 | try {
68 | var process = Process.GetProcessesByName(ProcessName).FirstOrDefault();
69 | if (process == null)
70 | return null;
71 |
72 |
73 | return new ProcessInfo() {
74 | Process = process,
75 | BaseAddress = process.MainModule.BaseAddress,
76 | Handle = WindowsHelper.OpenProcess((uint)WindowsHelper.ProcessAccessFlags.VirtualMemoryRead, false, process.Id),
77 | ProcessWindowHandle = process.MainWindowHandle,
78 | Scanner = new Scanner(process, process.MainModule)
79 | };
80 | } catch (Exception e) {
81 | Logger.LogError("Get process info {@e}", e);
82 | throw;
83 | }
84 | }
85 | }
86 |
87 | public ProcessInfo GetProcessInfo() {
88 | if (m_ProcessInfo == null || m_ProcessInfo!.Process!.Id != Process.GetProcessesByName(ProcessName).FirstOrDefault()?.Id
89 | || m_ProcessInfo.ProcessWindowHandle == IntPtr.Zero) {
90 | Reset();
91 | return ProcessInfo.Create();
92 | }
93 |
94 | return m_ProcessInfo;
95 | }
96 |
97 | public IntPtr GetUnitHashtableAddress() {
98 | if (_mMemoryState.UnitHashtableAddressPtr != IntPtr.Zero)
99 | return _mMemoryState.UnitHashtableAddressPtr;
100 |
101 | // shameless copy from https://github.com/OneXDeveloper/MapAssist/ Helpers/Pattern.cs
102 | var offsetResult = m_ProcessInfo.Scanner.CompiledFindPattern("48 03 C7 49 8B 8C C6");
103 | var resultRelativeAddress = IntPtr.Add(IntPtr.Add(m_ProcessInfo.BaseAddress, offsetResult.Offset), 7);
104 | var offSetAddress = WindowsHelper.Read(m_ProcessInfo.Handle, resultRelativeAddress);
105 |
106 | return IntPtr.Add(m_ProcessInfo.BaseAddress, offSetAddress);
107 | }
108 |
109 | //https://github.com/OneXDeveloper/MapAssist/blob/main/Helpers/ProcessContext.cs
110 | public IntPtr GetUiSettingAddress() {
111 | if (_mMemoryState.UiSettingAddressPtr != IntPtr.Zero)
112 | return _mMemoryState.UiSettingAddressPtr;
113 |
114 | var offsetResult = m_ProcessInfo.Scanner.CompiledFindPattern("40 84 ed 0f 94 05");
115 | var resultRelativeAddress = IntPtr.Add(IntPtr.Add(m_ProcessInfo.BaseAddress, offsetResult.Offset), 6);
116 | var offSetAddress = WindowsHelper.Read(m_ProcessInfo.Handle, resultRelativeAddress);
117 |
118 | return IntPtr.Add(m_ProcessInfo.BaseAddress, offsetResult.Offset + 9 + offSetAddress);
119 | }
120 |
121 | public bool CurrentPlayerValid() {
122 | var unitAny = WindowsHelper.Read(m_ProcessInfo.Handle, _mMemoryState.PlayerUnitPtr);
123 | return unitAny.Inventory != IntPtr.Zero;
124 | }
125 |
126 | // Logic took from https://github.com/RushTheOne/MapAssist/blob/main/Helpers/GameMemory.cs
127 | public unsafe GameState? GetState() {
128 | try {
129 | m_ProcessInfo = GetProcessInfo();
130 | if (m_ProcessInfo == null) {
131 | Reset();
132 | return null;
133 | }
134 |
135 | if (_mMemoryState.PlayerUnitPtr == IntPtr.Zero || !CurrentPlayerValid()) {
136 | var unitHashTablePtr = GetUnitHashtableAddress();
137 | var uiSettingPtr = GetUiSettingAddress();
138 |
139 | var result = FindPlayer(unitHashTablePtr, m_ProcessInfo);
140 | if (!result.HasValue)
141 | return null;
142 |
143 | _mMemoryState = new MemoryState() {
144 | PlayerUnit = result.Value.data,
145 | PlayerUnitPtr = result.Value.ptr,
146 | UnitHashtableAddressPtr = unitHashTablePtr,
147 | UiSettingAddressPtr = uiSettingPtr,
148 | GameSocket = GetGameSocket()
149 | };
150 | }
151 |
152 | mGameMenus = WindowsHelper.Read(m_ProcessInfo.Handle, _mMemoryState.UiSettingAddressPtr);
153 | mPath = ReadAndValidate(m_ProcessInfo, _mMemoryState.PlayerUnit.pPath);
154 | mRoom = ReadAndValidate(m_ProcessInfo, mPath.pRoom);
155 | mRoomEx = ReadAndValidate(m_ProcessInfo, mRoom.pRoomEx);
156 | mLevel = ReadAndValidate(m_ProcessInfo, mRoomEx.pLevel);
157 | mStatList = ReadAndValidate(m_ProcessInfo, _mMemoryState.PlayerUnit.StatsList);
158 |
159 | var playerName = Encoding.ASCII.GetString(WindowsHelper.Read(m_ProcessInfo.Handle, _mMemoryState.PlayerUnit.UnitData, 16)).TrimEnd((char)0);
160 | var stats = WindowsHelper.Read(m_ProcessInfo.Handle, mStatList.Stats.Array, (int)mStatList.Stats.Size);
161 | var levelId = mLevel.LevelId;
162 | if (levelId == Area.None)
163 | throw new Exception("Invalid area");
164 |
165 | var currentState = new GameState {
166 | PlayerName = playerName,
167 | CurrentArea = mLevel.LevelId,
168 | PreviousHealth = previousState?.CurrentHealth,
169 | CurrentHealth = GetCurrentHealth(stats),
170 | MaxHealth = GetMaxHp(stats),
171 | IsInTown =
172 | levelId is Area.RogueEncampment or Area.LutGholein or Area.KurastDocks or Area
173 | .ThePandemoniumFortress or Area.Harrogath,
174 | IsGameMenuOpen = mGameMenus.IsGameMenuOpen == 1,
175 | WindowHandle = m_ProcessInfo.ProcessWindowHandle,
176 | GameSocket = _mMemoryState.GameSocket
177 | };
178 | previousState = currentState;
179 | return currentState;
180 | } catch (Exception) {
181 | Reset();
182 | return null;
183 | }
184 | }
185 |
186 | static (IntPtr ptr, UnitAny data)? FindPlayer(IntPtr unitHashTablePtr, ProcessInfo pInfo) {
187 | var dic = new Dictionary>();
188 | var mainPlayerName = string.Empty;
189 | var mainPlayerPtr = IntPtr.Zero;
190 | var unitHashTable = WindowsHelper.Read(pInfo.Handle, unitHashTablePtr);
191 |
192 | foreach (var unitPtr in unitHashTable.UnitTable) {
193 | var unitAny = WindowsHelper.Read(pInfo.Handle, unitPtr);
194 | ReadAllUnits(unitAny, unitPtr, dic, pInfo.Handle, ref mainPlayerName);
195 | }
196 | foreach (var ptrAndData in dic[mainPlayerName]) {
197 | var path = WindowsHelper.Read(pInfo.Handle, ptrAndData.data.pPath);
198 | if (path.DynamicY != 0 && path.DynamicY != 0) {
199 | return ptrAndData;
200 | }
201 | }
202 |
203 | return null;
204 |
205 | }
206 |
207 | public static void ReadAllUnits(UnitAny unitAny, IntPtr curUnitPtr, Dictionary> dic, IntPtr hdl, ref string mainPlayer) {
208 | if (curUnitPtr == IntPtr.Zero)
209 | return;
210 |
211 | if(unitAny.UnityType == 0) {
212 | var playerName = Encoding.ASCII.GetString(WindowsHelper.Read(hdl, unitAny.UnitData, 16)).TrimEnd((char)0);
213 | if (!string.IsNullOrEmpty(playerName)) {
214 | if (dic.ContainsKey(playerName)) {
215 | dic[playerName].Add((curUnitPtr, unitAny));
216 | mainPlayer = playerName;
217 | } else {
218 | dic.Add(playerName, new List<(IntPtr, UnitAny)> { (curUnitPtr, unitAny) });
219 | }
220 | }
221 | }
222 | ReadAllUnits(WindowsHelper.Read(hdl, unitAny.pListNext), unitAny.pListNext, dic, hdl, ref mainPlayer);
223 | }
224 |
225 | static int GetCurrentHealth(StatValue[] stats) => stats.FirstOrDefault(x => x.Stat == Stat.STAT_HP).Value >> 8;
226 | static int GetMaxHp(StatValue[] stats) => stats.FirstOrDefault(x => x.Stat == Stat.STAT_MAXHP).Value >> 8;
227 |
228 | private T ReadAndValidate(ProcessInfo pInfo, IntPtr ptr) where T : struct {
229 | if (ptr == IntPtr.Zero)
230 | throw new Exception($"Pointer to {typeof(T).Name} is null");
231 | return WindowsHelper.Read(pInfo.Handle, ptr);
232 | }
233 |
234 | /*
235 | This is best effort to find the game socket. We take the most recent one.
236 | */
237 | public InteropTcpHelper.MIB_TCPROW_OWNER_PID GetGameSocket() {
238 | if (this._mMemoryState.GameSocket.HasValue)
239 | return this._mMemoryState.GameSocket.Value;
240 | var d2rSockets = InteropTcpHelper.GetAllTcpConnections().Where(x =>
241 | x.state == (uint)InteropTcpHelper.State.Established &&
242 | x.owningPid == m_ProcessInfo.Process.Id
243 | ).ToList();
244 |
245 | var seen = new HashSet();
246 | var duplicate = new HashSet();
247 | foreach (var s in d2rSockets) {
248 | if (seen.Contains(s.remoteAddr))
249 | duplicate.Add(s.remoteAddr);
250 | seen.Add(s.remoteAddr);
251 | }
252 | d2rSockets = d2rSockets.Where(x => !duplicate.Contains(x.remoteAddr)).ToList();
253 | var socketForThatIp = d2rSockets.Last();
254 | this._mMemoryState.GameSocket = socketForThatIp;
255 | Logger.LogInformation("found ip: {gameIp}", IPIntToString((int)socketForThatIp.remoteAddr));
256 |
257 | return socketForThatIp;
258 | }
259 |
260 | public static string IPIntToString(int IP) {
261 | byte[] addr = System.BitConverter.GetBytes(IP);
262 | return addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3];
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/D.Core/WindowsHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace D.Core {
5 | public static class WindowsHelper {
6 | [System.Runtime.InteropServices.DllImport("user32.dll")]
7 | public static extern int SetForegroundWindow(IntPtr hWnd);
8 |
9 | //This is a replacement for Cursor.Position in WinForms
10 | [System.Runtime.InteropServices.DllImport("user32.dll")]
11 | static extern bool SetCursorPos(int x, int y);
12 |
13 | [System.Runtime.InteropServices.DllImport("user32.dll")]
14 | public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
15 |
16 | public const int MOUSEEVENTF_LEFTDOWN = 0x02;
17 | public const int MOUSEEVENTF_LEFTUP = 0x04;
18 |
19 | //This simulates a left mouse click
20 | public static void LeftMouseClick(int xpos, int ypos) {
21 | SetCursorPos(xpos, ypos);
22 | mouse_event(MOUSEEVENTF_LEFTDOWN, xpos, ypos, 0, 0);
23 | mouse_event(MOUSEEVENTF_LEFTUP, xpos, ypos, 0, 0);
24 | }
25 |
26 | [System.Runtime.InteropServices.DllImport("user32.dll")]
27 | public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
28 |
29 | const UInt32 WM_KEYDOWN = 0x0100;
30 | const int VK_ESCAPE = 0x1B;
31 |
32 | [System.Runtime.InteropServices.DllImport("user32.dll")]
33 | public static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
34 |
35 | public static void SendEscapeKey(IntPtr hWnd) => PostMessage(hWnd, WM_KEYDOWN, VK_ESCAPE, 0);
36 |
37 | public static Rect GetWindowRect(IntPtr hWnd) {
38 | var windowRec = new Rect();
39 | GetWindowRect(hWnd, ref windowRec);
40 | return windowRec;
41 | }
42 |
43 | public enum AllocationProtect : uint {
44 | PAGE_EXECUTE = 0x00000010,
45 | PAGE_EXECUTE_READ = 0x00000020,
46 | PAGE_EXECUTE_READWRITE = 0x00000040,
47 | PAGE_EXECUTE_WRITECOPY = 0x00000080,
48 | PAGE_NOACCESS = 0x00000001,
49 | PAGE_READONLY = 0x00000002,
50 | PAGE_READWRITE = 0x00000004,
51 | PAGE_WRITECOPY = 0x00000008,
52 | PAGE_GUARD = 0x00000100,
53 | PAGE_NOCACHE = 0x00000200,
54 | PAGE_WRITECOMBINE = 0x00000400
55 | }
56 |
57 | // https://docs.microsoft.com/en-au/windows/win32/memory/memory-protection-constants
58 | [Flags]
59 | public enum MemoryInformationProtection {
60 | PAGE_EXECUTE = 0x10,
61 | PAGE_EXECUTE_READ = 0x20,
62 | PAGE_EXECUTE_READWRITE = 0x40,
63 | PAGE_EXECUTE_WRITECOPY = 0x80,
64 | PAGE_NOACCESS = 0x01,
65 | PAGE_READONLY = 0x02,
66 | PAGE_READWRITE = 0x04,
67 | PAGE_WRITECOPY = 0x08,
68 | PAGE_TARGETS_INVALID = 0x40000000,
69 | PAGE_TARGETS_NO_UPDATE = 0x40000000,
70 |
71 | PAGE_GUARD = 0x100,
72 | PAGE_NOCACHE = 0x200,
73 | PAGE_WRITECOMBINE = 0x400
74 | }
75 |
76 | // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
77 | public enum MemoryInformationState {
78 | MEM_COMMIT = 0x1000,
79 | MEM_FREE = 0x10000,
80 | MEM_RESERVE = 0x2000
81 | }
82 |
83 | // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
84 | public enum MemoryInformationType {
85 | MEM_IMAGE = 0x1000000,
86 | MEM_MAPPED = 0x40000,
87 | MEM_PRIVATE = 0x20000
88 | }
89 |
90 | [Flags]
91 | public enum ProcessAccessFlags : uint {
92 | All = 0x001F0FFF,
93 | Terminate = 0x00000001,
94 | CreateThread = 0x00000002,
95 | VirtualMemoryOperation = 0x00000008,
96 | VirtualMemoryRead = 0x00000010,
97 | VirtualMemoryWrite = 0x00000020,
98 | DuplicateHandle = 0x00000040,
99 | CreateProcess = 0x000000080,
100 | SetQuota = 0x00000100,
101 | SetInformation = 0x00000200,
102 | QueryInformation = 0x00000400,
103 | QueryLimitedInformation = 0x00001000,
104 | Synchronize = 0x00100000
105 | }
106 |
107 | public static T Read(IntPtr processHandle, IntPtr address) where T : struct {
108 | return Read(processHandle, address, 1)[0];
109 | }
110 |
111 | // copied from https://github.com/misterokaygo/MapAssist/
112 | public static T[] Read(IntPtr processHandle, IntPtr address, int count) where T : struct {
113 | var size = Marshal.SizeOf();
114 | var buffer = new byte[size * count];
115 |
116 | ReadProcessMemory(processHandle, address, buffer, buffer.Length, out var bytesRead);
117 |
118 | var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
119 | try {
120 | var result = new T[count];
121 | for (var i = 0; i < count; i++)
122 | result[i] = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject() + i * size, typeof(T));
123 |
124 | return result;
125 | } finally {
126 | handle.Free();
127 | }
128 | }
129 |
130 | [DllImport("kernel32.dll", SetLastError = true)]
131 | public static extern IntPtr OpenProcess(
132 | uint processAccess,
133 | bool bInheritHandle,
134 | int processId
135 | );
136 |
137 | [DllImport("kernel32.dll", SetLastError = true)]
138 | public static extern bool ReadProcessMemory(
139 | IntPtr hProcess,
140 | IntPtr lpBaseAddress,
141 | [Out] byte[] lpBuffer,
142 | int dwSize,
143 | out IntPtr lpNumberOfBytesRead);
144 |
145 | // This helper static method is required because the 32-bit version of user32.dll does not contain this API
146 | // (on any versions of Windows), so linking the method will fail at run-time. The bridge dispatches the request
147 | // to the correct function (GetWindowLong in 32-bit mode and GetWindowLongPtr in 64-bit mode)
148 |
149 | // If that doesn't work, the following signature can be used alternatively.
150 | [DllImport("user32.dll")]
151 | public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
152 |
153 | [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
154 | private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);
155 |
156 | [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
157 | private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);
158 |
159 | // This static method is required because Win32 does not support
160 | // GetWindowLongPtr directly
161 | public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex) {
162 | if (IntPtr.Size == 8)
163 | return GetWindowLongPtr64(hWnd, nIndex);
164 | return GetWindowLongPtr32(hWnd, nIndex);
165 | }
166 |
167 | [DllImport("user32.dll")]
168 | [return: MarshalAs(UnmanagedType.Bool)]
169 | public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy,
170 | uint uFlags);
171 |
172 | [DllImport("user32.dll")]
173 | public static extern IntPtr GetForegroundWindow();
174 |
175 | [DllImport("kernel32.dll")]
176 | public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
177 |
178 | [DllImport("kernel32.dll")]
179 | public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress,
180 | out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength);
181 |
182 | [DllImport("kernel32.dll")]
183 | public static extern bool ReadProcessMemory2(IntPtr hProcess, ulong lpBaseAddress, byte[] lpBuffer,
184 | UIntPtr nSize, ref UIntPtr lpNumberOfBytesRead);
185 |
186 | [DllImport("kernel32.dll", SetLastError = true)]
187 | public static extern bool CloseHandle(IntPtr hHandle);
188 |
189 | [DllImport("user32.dll", SetLastError = true)]
190 | public static extern bool SetProcessDPIAware();
191 |
192 | [DllImport("user32.dll")]
193 | public static extern IntPtr GetClientRect(IntPtr hWnd, ref Rect rect);
194 |
195 | [DllImport("user32.dll")]
196 | [return: MarshalAs(UnmanagedType.Bool)]
197 | public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
198 |
199 | [StructLayout(LayoutKind.Sequential)]
200 | public struct Rect {
201 | public int Left { get; set; }
202 | public int Top { get; set; }
203 | public int Right { get; set; }
204 | public int Bottom { get; set; }
205 | }
206 |
207 | [StructLayout(LayoutKind.Sequential)]
208 | public struct Point {
209 | public int x;
210 |
211 | public int y;
212 | }
213 |
214 | // http://www.pinvoke.net/default.aspx/kernel32.virtualqueryex
215 | // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
216 | [StructLayout(LayoutKind.Sequential)]
217 | public struct MEMORY_BASIC_INFORMATION64 {
218 | public ulong BaseAddress;
219 | public ulong AllocationBase;
220 | public int AllocationProtect;
221 | public int __alignment1;
222 | public ulong RegionSize;
223 | public int State;
224 | public int Protect;
225 | public int Type;
226 | public int __alignment2;
227 | }
228 | }
229 | }
--------------------------------------------------------------------------------
/D.Models/D.Models.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/D.Models/StatEnum.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | // https://github.com/noah-/d2bs/blob/master/Constants.h
4 | public enum Stat : short {
5 | STAT_STRENGTH = 0, // str
6 | STAT_ENERGY = 1, // energy
7 | STAT_DEXTERITY = 2, // dexterity
8 | STAT_VITALITY = 3, // vitality
9 | STAT_STATPOINTSLEFT = 4,
10 | STAT_SKILLPOINTSLEFT = 5,
11 | STAT_HP = 6, // life
12 | STAT_MAXHP = 7, // max life
13 | STAT_MANA = 8, // mana
14 | STAT_MAXMANA = 9, // max mana
15 | STAT_STAMINA = 10, // stamina
16 | STAT_MAXSTAMINA = 11, // max stamina
17 | STAT_LEVEL = 12, // level
18 | STAT_EXP = 13, // experience
19 | STAT_GOLD = 14, // gold
20 | STAT_GOLDBANK = 15, // stash gold
21 | STAT_ENHANCEDDEFENSE = 16,
22 | STAT_ENHANCEDDAMAGEMAX = 17, // njipStats["itemmaxdamagepercent"]=[17,0];
23 | STAT_ENHANCEDDAMAGEMIN = 18, // njipStats["itemmindamagepercent"]=[18,0]; njipStats["enhanceddamage"]=[18,0];
24 | STAT_TOBLOCK = 20, // to block
25 | STAT_LASTEXP = 29,
26 | STAT_NEXTEXP = 30,
27 | STAT_DAMAGEREDUCTION = 36, // damage reduction
28 | STAT_MAGICDAMAGEREDUCTION = 35, // magic damage reduction
29 | STAT_MAGICRESIST = 37, // magic resist
30 | STAT_MAXMAGICRESIST = 38, // max magic resist
31 | STAT_FIRERESIST = 39, // fire resist
32 | STAT_MAXFIRERESIST = 40, // max fire resist
33 | STAT_LIGHTNINGRESIST = 41, // lightning resist
34 | STAT_MAXLIGHTNINGRESIST = 42, // max lightning resist
35 | STAT_COLDRESIST = 43, // cold resist
36 | STAT_MAXCOLDRESIST = 44, // max cold resist
37 | STAT_POISONRESIST = 45, // poison resist
38 | STAT_MAXPOISONRESIST = 46, // max poison resist
39 | STAT_LIFELEECH = 60, // Life Leech
40 | STAT_MANALEECH = 62, // Mana Leech
41 | STAT_VELOCITYPERCENT = 67, // effective run/walk
42 | STAT_AMMOQUANTITY = 70, // ammo quantity(arrow/bolt/throwing)
43 | STAT_DURABILITY = 72, // item durability
44 | STAT_MAXDURABILITY = 73, // max item durability
45 | STAT_GOLDFIND = 79, // Gold find (GF)
46 | STAT_MAGICFIND = 80, // magic find (MF)
47 | STAT_ITEMLEVELREQ = 92,
48 | STAT_IAS = 93, // IAS
49 | STAT_FASTERRUNWALK = 96, // faster run/walk
50 | STAT_FASTERHITRECOVERY = 99, // faster hit recovery
51 | STAT_FASTERBLOCK = 102, // faster block rate
52 | STAT_FASTERCAST = 105, // faster cast rate
53 | STAT_POISONLENGTHREDUCTION = 110, // Poison length reduction
54 | STAT_OPENWOUNDS = 135, // Open Wounds
55 | STAT_CRUSHINGBLOW = 136, // crushing blow
56 | STAT_DEADLYSTRIKE = 141, // deadly strike
57 | STAT_FIREABSORBPERCENT = 142, // fire absorb %
58 | STAT_FIREABSORB = 143, // fire absorb
59 | STAT_LIGHTNINGABSORBPERCENT = 144, // lightning absorb %
60 | STAT_LIGHTNINGABSORB = 145, // lightning absorb
61 | STAT_COLDABSORBPERCENT = 148, // cold absorb %
62 | STAT_COLDABSORB = 149, // cold absorb
63 | STAT_SLOW = 150, // slow %
64 | }
--------------------------------------------------------------------------------
/D.Models/Structs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace D.Models {
5 |
6 | [StructLayout(LayoutKind.Explicit)]
7 | unsafe public struct UiGameMenus {
8 | [FieldOffset(0x00)] public byte IsGameMenuOpen;
9 | [FieldOffset(0x08)] public byte IsInGame;
10 | [FieldOffset(0x09)] public byte IsTrading; // not sure
11 | [FieldOffset(0x0A)] public byte StatsWindowOpen;
12 | [FieldOffset(0x0B)] public byte MouseAttackMenuOpen;
13 | [FieldOffset(0x0C)] public byte SkillTreeMenuOpen;
14 | [FieldOffset(0x10)] public byte IsTalkingToNpc;
15 | [FieldOffset(0x13)] public byte IsTrading2; // not sure
16 | [FieldOffset(0x1B)] public byte WaypointMenuOpen;
17 | [FieldOffset(0x20)] public byte ChestOpen;
18 | }
19 |
20 | [StructLayout(LayoutKind.Explicit)]
21 | unsafe public struct StatList {
22 | [FieldOffset(0x30)] public readonly StatArray Stats;
23 | [FieldOffset(0x80)] public readonly StatArray Stats2;
24 | }
25 |
26 | [StructLayout(LayoutKind.Explicit)]
27 | unsafe public struct StatArray {
28 | [FieldOffset(0x0)] public readonly IntPtr Array;
29 | [FieldOffset(0x8)] public readonly ulong Size;
30 | }
31 |
32 | [StructLayout(LayoutKind.Explicit)]
33 | public readonly struct StatValue {
34 | [FieldOffset(0x2)] public readonly Stat Stat;
35 | [FieldOffset(0x4)] public readonly int Value;
36 | }
37 |
38 | [StructLayout(LayoutKind.Explicit)]
39 | unsafe public struct UnitHashTable {
40 | [FieldOffset(0x00)]
41 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
42 | public IntPtr[] UnitTable;
43 | }
44 |
45 | [StructLayout(LayoutKind.Explicit)]
46 | unsafe public struct UnitAny {
47 | //https://github.com/noah-/d2bs#public-int-type-2
48 | [FieldOffset(0x00)] public uint UnityType;
49 | [FieldOffset(0x04)] public uint TxtFileNo;
50 | [FieldOffset(0x08)] public uint UnitId;
51 | [FieldOffset(0x10)] public IntPtr UnitData;
52 | [FieldOffset(0x20)] public IntPtr pAct;
53 | [FieldOffset(0x38)] public IntPtr pPath;
54 | [FieldOffset(0x88)] public IntPtr StatsList;
55 | [FieldOffset(0x90)] public IntPtr Inventory;
56 | [FieldOffset(0xB8)] public uint OwnerType; // ?
57 | [FieldOffset(0xC4)] public ushort X;
58 | [FieldOffset(0xC6)] public ushort Y;
59 | [FieldOffset(0x150)] public IntPtr pListNext;
60 | [FieldOffset(0x158)] public IntPtr pRoomNext;
61 | }
62 |
63 | [StructLayout(LayoutKind.Explicit)]
64 | unsafe public struct Path {
65 | [FieldOffset(0x02)] public ushort DynamicX;
66 | [FieldOffset(0x06)] public ushort DynamicY;
67 | [FieldOffset(0x10)] public ushort StaticX;
68 | [FieldOffset(0x14)] public ushort StaticY;
69 | [FieldOffset(0x20)] public IntPtr pRoom;
70 | }
71 |
72 | [StructLayout(LayoutKind.Explicit)]
73 | unsafe public struct Room {
74 | [FieldOffset(0x00)] public IntPtr pRoomsNear;
75 | [FieldOffset(0x18)] public IntPtr pRoomEx;
76 | [FieldOffset(0x40)] public uint numRoomsNear;
77 | [FieldOffset(0x48)] public IntPtr pAct;
78 | [FieldOffset(0xA8)] public IntPtr pUnitFirst;
79 | [FieldOffset(0xB0)] public IntPtr pRoomNext;
80 | }
81 |
82 | [StructLayout(LayoutKind.Explicit)]
83 | unsafe public struct RoomEx {
84 | [FieldOffset(0x90)] public IntPtr pLevel;
85 | }
86 |
87 | [StructLayout(LayoutKind.Explicit)]
88 | unsafe public struct Act {
89 | [FieldOffset(0x14)] public uint MapSeed;
90 | [FieldOffset(0x20)] public uint ActId;
91 | [FieldOffset(0x70)] public IntPtr ActMisc;
92 | }
93 |
94 | [StructLayout(LayoutKind.Explicit)]
95 | unsafe public struct ActMisc {
96 | [FieldOffset(0x830)] public Difficulty GameDifficulty;
97 | [FieldOffset(0x858)] public IntPtr pAct;
98 | [FieldOffset(0x868)] public IntPtr pLevelFirst;
99 | }
100 |
101 | public enum Difficulty : ushort {
102 | Normal = 0,
103 | Nightmare = 1,
104 | Hell = 2
105 | }
106 |
107 | [StructLayout(LayoutKind.Explicit)]
108 | unsafe public struct Level {
109 | [FieldOffset(0x1F8)] public Area LevelId;
110 | }
111 |
112 | public enum Area : uint {
113 | Abaddon = 125,
114 | AncientTunnels = 65,
115 | ArcaneSanctuary = 74,
116 | ArreatPlateau = 112,
117 | ArreatSummit = 120,
118 | Barracks = 28,
119 | BlackMarsh = 6,
120 | BloodMoor = 2,
121 | BloodyFoothills = 110,
122 | BurialGrounds = 17,
123 | CanyonOfTheMagi = 46,
124 | CatacombsLevel1 = 34,
125 | CatacombsLevel2 = 35,
126 | CatacombsLevel3 = 36,
127 | CatacombsLevel4 = 37,
128 | Cathedral = 33,
129 | CaveLevel1 = 9,
130 | CaveLevel2 = 13,
131 | ChaosSanctuary = 108,
132 | CityOfTheDamned = 106,
133 | ClawViperTempleLevel1 = 58,
134 | ClawViperTempleLevel2 = 61,
135 | ColdPlains = 3,
136 | Crypt = 18,
137 | CrystallinePassage = 113,
138 | DarkWood = 5,
139 | DenOfEvil = 8,
140 | DisusedFane = 95,
141 | DisusedReliquary = 99,
142 | DrifterCavern = 116,
143 | DryHills = 42,
144 | DuranceOfHateLevel1 = 100,
145 | DuranceOfHateLevel2 = 101,
146 | DuranceOfHateLevel3 = 102,
147 | DurielsLair = 73,
148 | FarOasis = 43,
149 | FlayerDungeonLevel1 = 88,
150 | FlayerDungeonLevel2 = 89,
151 | FlayerDungeonLevel3 = 91,
152 | FlayerJungle = 78,
153 | ForgottenReliquary = 96,
154 | ForgottenSands = 134,
155 | ForgottenTemple = 97,
156 | ForgottenTower = 20,
157 | FrigidHighlands = 111,
158 | FrozenRiver = 114,
159 | FrozenTundra = 117,
160 | FurnaceOfPain = 135,
161 | GlacialTrail = 115,
162 | GreatMarsh = 77,
163 | HallsOfAnguish = 122,
164 | HallsOfPain = 123,
165 | HallsOfTheDeadLevel1 = 56,
166 | HallsOfTheDeadLevel2 = 57,
167 | HallsOfTheDeadLevel3 = 60,
168 | HallsOfVaught = 124,
169 | HaremLevel1 = 50,
170 | HaremLevel2 = 51,
171 | Harrogath = 109,
172 | HoleLevel1 = 11,
173 | HoleLevel2 = 15,
174 | IcyCellar = 119,
175 | InfernalPit = 127,
176 | InnerCloister = 32,
177 | JailLevel1 = 29,
178 | JailLevel2 = 30,
179 | JailLevel3 = 31,
180 | KurastBazaar = 80,
181 | KurastCauseway = 82,
182 | KurastDocks = 75,
183 | LostCity = 44,
184 | LowerKurast = 79,
185 | LutGholein = 40,
186 | MaggotLairLevel1 = 62,
187 | MaggotLairLevel2 = 63,
188 | MaggotLairLevel3 = 64,
189 | MatronsDen = 133,
190 | Mausoleum = 19,
191 | MonasteryGate = 26,
192 | MooMooFarm = 39,
193 | NihlathaksTemple = 121,
194 | None = 0,
195 | OuterCloister = 27,
196 | OuterSteppes = 104,
197 | PalaceCellarLevel1 = 52,
198 | PalaceCellarLevel2 = 53,
199 | PalaceCellarLevel3 = 54,
200 | PitLevel1 = 12,
201 | PitLevel2 = 16,
202 | PitOfAcheron = 126,
203 | PlainsOfDespair = 105,
204 | RiverOfFlame = 107,
205 | RockyWaste = 41,
206 | RogueEncampment = 1,
207 | RuinedFane = 98,
208 | RuinedTemple = 94,
209 | SewersLevel1Act2 = 47,
210 | SewersLevel1Act3 = 92,
211 | SewersLevel2Act2 = 48,
212 | SewersLevel2Act3 = 93,
213 | SewersLevel3Act2 = 49,
214 | SpiderCave = 84,
215 | SpiderCavern = 85,
216 | SpiderForest = 76,
217 | StonyField = 4,
218 | StonyTombLevel1 = 55,
219 | StonyTombLevel2 = 59,
220 | SwampyPitLevel1 = 86,
221 | SwampyPitLevel2 = 87,
222 | SwampyPitLevel3 = 90,
223 | TalRashasTomb1 = 66,
224 | TalRashasTomb2 = 67,
225 | TalRashasTomb3 = 68,
226 | TalRashasTomb4 = 69,
227 | TalRashasTomb5 = 70,
228 | TalRashasTomb6 = 71,
229 | TalRashasTomb7 = 72,
230 | TamoeHighland = 7,
231 | TheAncientsWay = 118,
232 | ThePandemoniumFortress = 103,
233 | TheWorldstoneChamber = 132,
234 | TheWorldStoneKeepLevel1 = 128,
235 | TheWorldStoneKeepLevel2 = 129,
236 | TheWorldStoneKeepLevel3 = 130,
237 | ThroneOfDestruction = 131,
238 | TowerCellarLevel1 = 21,
239 | TowerCellarLevel2 = 22,
240 | TowerCellarLevel3 = 23,
241 | TowerCellarLevel4 = 24,
242 | TowerCellarLevel5 = 25,
243 | Travincal = 83,
244 | Tristram = 38,
245 | UberTristram = 136,
246 | UndergroundPassageLevel1 = 10,
247 | UndergroundPassageLevel2 = 14,
248 | UpperKurast = 81,
249 | ValleyOfSnakes = 45,
250 | MapsAncientTemple = 137,
251 | MapsDesecratedTemple = 138,
252 | MapsFrigidPlateau = 139,
253 | MapsInfernalTrial = 140,
254 | MapsRuinedCitadel = 141,
255 | }
256 | }
257 |
258 |
--------------------------------------------------------------------------------
/D.WpfApp/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 | Status:
8 |
9 |
10 |
--------------------------------------------------------------------------------
/D.WpfApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace D.WpfApp {
10 | ///
11 | /// Interaction logic for App.xaml
12 | ///
13 | public partial class App : Application {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/D.WpfApp/ApplicationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace D.WpfApp {
5 | public static class ApplicationHelper {
6 | private static readonly string MyApplicationKey = "a7a0d62e-187f-46a0-8773-b32e6052cd8d";
7 |
8 | private static Mutex _instanceMutex;
9 |
10 | // https://xcalibursystems.com/restricting-wpf-applications-run-mutex/
11 | public static bool ReserveMutex() {
12 | // Set mutex
13 | _instanceMutex = new Mutex(true, MyApplicationKey);
14 |
15 | // Check if already running
16 | var isAlreadyInUse = false;
17 | try {
18 | isAlreadyInUse = !_instanceMutex.WaitOne(TimeSpan.Zero, true);
19 | } catch (AbandonedMutexException) {
20 | FreeMutex();
21 | isAlreadyInUse = false;
22 | } catch (Exception) {
23 | _instanceMutex.Close();
24 | isAlreadyInUse = false;
25 | }
26 |
27 | return isAlreadyInUse;
28 | }
29 |
30 | ///
31 | /// Kills the instance.
32 | ///
33 | /// The code.
34 | public static void FreeMutex(int code = 0) {
35 | if (_instanceMutex == null) return;
36 |
37 | try {
38 | _instanceMutex.ReleaseMutex();
39 | } catch (Exception) {
40 | }
41 |
42 | _instanceMutex.Close();
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/D.WpfApp/D.WpfApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 | win10-x64
9 | False
10 | False
11 | app.manifest
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ..\dependencies\Analytics.Lib.dll
23 |
24 |
25 |
26 |
27 | Always
28 | Always
29 |
30 |
31 | Always
32 | Always
33 |
34 |
35 | runtime; build; native; contentfiles; analyzers; buildtransitive
36 | all
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/D.WpfApp/GitVersion.yml:
--------------------------------------------------------------------------------
1 | mode: Mainline
2 | assembly-versioning-format: '{Major}.{Minor}.{Patch}.{env:BUILDCOUNT ?? 0}'
3 | assembly-informational-format: '{NuGetVersionV2}+Build.{env:BUILDCOUNT ?? 0}.Date.{CommitDate}.Branch.{env:SAFEBRANCHNAME ?? unknown}.Sha.{Sha}'
4 |
5 | tag-prefix: '[vV]'
6 | continuous-delivery-fallback-tag: '' # ci
7 | major-version-bump-message: '\+semver:\s?(breaking|major)'
8 | minor-version-bump-message: '\+semver:\s?(feature|minor)'
9 | patch-version-bump-message: '\+semver:\s?(fix|patch)'
10 | no-bump-message: '\+semver:\s?(none|skip)'
11 | commit-message-incrementing: Enabled
12 | branches:
13 | main:
14 | regex: ^master$|^main$
15 | tag: ''
16 | increment: Patch
17 | prevent-increment-of-merged-branch-version: true
18 | source-branches:
19 | - develop
20 | - release
21 | tracks-release-branches: false
22 | is-release-branch: false
23 | is-mainline: true
24 | develop:
25 | regex: ^dev(elop)?(ment)?$
26 | tag: alpha
27 | increment: Patch
28 | prevent-increment-of-merged-branch-version: false
29 | tracks-release-branches: true
30 | is-release-branch: false
31 | is-mainline: true
32 | release:
33 | regex: ^release?[/-]
34 | tag: beta
35 | increment: None
36 | prevent-increment-of-merged-branch-version: true
37 | source-branches:
38 | - develop
39 | - main
40 | - release
41 | is-release-branch: true
42 | feature:
43 | regex: ^features?[/-]
44 | tag: useBranchName
45 | increment: Inherit
46 | prevent-increment-of-merged-branch-version: false
47 | source-branches:
48 | - develop
49 | - main
50 | - release
51 | - feature
52 | - hotfix
53 | pull-request:
54 | tag: '' # PullRequest
55 | increment: Inherit
56 | prevent-increment-of-merged-branch-version: false
57 | tag-number-pattern: '[/-](?\d+)'
58 | regex: ^(pull|pull\-requests|pr)[/-]
59 | source-branches:
60 | - develop
61 | - main
62 | - release
63 | - feature
64 | - hotfix
65 | tracks-release-branches: false
66 | is-release-branch: false
67 | is-mainline: false
68 | hotfix:
69 | regex: ^hotfix(es)?[/-]
70 | tag: beta
71 | increment: Patch
72 | prevent-increment-of-merged-branch-version: false
73 | source-branches:
74 | - develop
75 | - main
76 | tracks-release-branches: false
77 | is-release-branch: false
78 | is-mainline: false
79 | ignore:
80 | sha: []
81 | commit-date-format: yyyy-MM-dd-HHmmss
82 | merge-message-formats: {}
83 | update-build-number: true
84 |
--------------------------------------------------------------------------------
/D.WpfApp/MachineStamp.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Management;
3 | using System.Reflection;
4 | using System.Security.Cryptography;
5 | using System.Text;
6 |
7 | namespace D.WpfApp {
8 | public static class MachineStamp {
9 | public static string Version {
10 | get {
11 | var assembly = Assembly.GetAssembly(typeof(MainWindow));
12 |
13 | var gitVersionInformationType = assembly.GetType("GitVersionInformation");
14 | // GitVersionInformation
15 | //"MajorMinorPatch";
16 | var versionField = gitVersionInformationType.GetField("MajorMinorPatch");
17 | if (versionField != null) {
18 | return versionField.GetValue(null).ToString();
19 | }
20 |
21 | var versionProperty = gitVersionInformationType.GetProperty("MajorMinorPatch");
22 | if (versionProperty != null) return versionProperty.GetGetMethod(true).Invoke(null, null).ToString();
23 |
24 | return "";
25 | }
26 | }
27 |
28 | public static string CreateMachineId() {
29 | var sha1 = SHA256.Create();
30 | var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes($"{GetProcessorId()}{FecthMACAddressInternal()}"));
31 | var base64Hash = Convert.ToBase64String(hash);
32 | return base64Hash[^9..];
33 | }
34 |
35 | public static string GetProcessorId() {
36 | var cpu = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");
37 | foreach (var cpuObj in cpu.Get())
38 | return cpuObj["ProcessorId"]?.ToString() ?? "";
39 | return string.Empty;
40 | }
41 |
42 | public static string FecthMACAddressInternal() {
43 | try {
44 | var macAddress = string.Empty;
45 | var networkAdapterObjs = new ManagementClass("Win32_NetworkAdapterConfiguration").GetInstances();
46 | foreach (var networkAdapterObj in networkAdapterObjs)
47 | if (networkAdapterObj["MacAddress"] != null &&
48 | bool.TryParse(networkAdapterObj["IPEnabled"]?.ToString(), out var isIpEnabled) && isIpEnabled)
49 | return networkAdapterObj["MacAddress"]?.ToString() ?? string.Empty;
50 | return "";
51 | } catch (Exception) {
52 | return "";
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/D.WpfApp/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
27 |
28 |
45 |
46 |
47 |
51 |
52 |
55 |
56 |
57 |
73 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
101 |
114 |
127 |
128 |
141 |
142 |
147 |
148 |
149 |
150 |
151 |
152 |
160 |
161 |
165 |
166 |
170 |
171 |
--------------------------------------------------------------------------------
/D.WpfApp/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using analytics_lib;
8 | using D.Core;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.Configuration.Yaml;
11 | using Microsoft.Extensions.DependencyInjection;
12 | using Microsoft.Extensions.Logging;
13 | using Serilog;
14 | using Serilog.Events;
15 | using ILogger = Microsoft.Extensions.Logging.ILogger;
16 |
17 | namespace D.WpfApp {
18 | ///
19 | /// Interaction logic for MainWindow.xaml
20 | ///
21 | public partial class MainWindow {
22 | private static readonly string machineId = MachineStamp.CreateMachineId();
23 |
24 | private readonly TaskScheduler _uiScheduler;
25 | private readonly AnalyticService m_AnalyticService;
26 |
27 | private readonly IServiceProvider m_ServiceProvider;
28 | private Thread botThread;
29 | private CancellationTokenSource cancellationTokenSource;
30 | private readonly ILogger logger;
31 | private IDisposable sessionSpan;
32 |
33 | public MainWindow() {
34 | _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
35 | InitializeComponent();
36 | var v = MachineStamp.Version;
37 |
38 | version.Text = $"V{v}";
39 |
40 | var builder = new ConfigurationBuilder()
41 | #if !DEBUG
42 | .SetBasePath(System.IO.Path.GetDirectoryName(Environment.ProcessPath))
43 | #endif
44 | .AddYamlFile("config.yml", false, false);
45 | var conf = builder.Build();
46 |
47 | var services = new ServiceCollection()
48 | .AddSingleton(conf)
49 | .AddSingleton()
50 | .AddAnalytics(
51 | (x, p) =>
52 | x.WithSourceName("diablo get out bot")
53 | .WithCommonHeader(new Dictionary
54 | {
55 | {"version", v},
56 | {"machineId", machineId}
57 | }))
58 | .AddLogging(x => {
59 | x.ClearProviders();
60 | x.AddSerilog(new LoggerConfiguration()
61 | .MinimumLevel.Information()
62 | .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
63 | .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
64 | .MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
65 | .Enrich.FromLogContext()
66 | // "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
67 | .WriteTo.File("bot.log", rollOnFileSizeLimit: true,
68 | outputTemplate:
69 | "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}][{SourceContext}] {Message}{NewLine}{Exception}")
70 | .CreateLogger());
71 | x.AddFilter("Microsoft", LogLevel.Warning);
72 | });
73 |
74 | m_ServiceProvider = services.BuildServiceProvider();
75 | logger = m_ServiceProvider.GetService().CreateLogger();
76 | m_AnalyticService = m_ServiceProvider.InitAnalytics();
77 |
78 | logger.LogInformation("Initialize, machineId: {@machineId}", machineId);
79 |
80 | if (!ApplicationHelper.ReserveMutex()) return;
81 | logger.LogWarning("App already launched. Exiting!");
82 | Close();
83 | }
84 |
85 | private void OnWindowLoaded(object sender, RoutedEventArgs e) {
86 | sessionSpan = GetOutBotAnalytic.StartSession();
87 | GetOutBotAnalytic.SendStart();
88 | StartBot();
89 | }
90 |
91 | private void WindowClosing(object sender, CancelEventArgs e) {
92 | e.Cancel = true;
93 | sessionSpan?.Dispose();
94 | m_AnalyticService.ForceFlush().GetAwaiter().GetResult();
95 | ApplicationHelper.FreeMutex();
96 | e.Cancel = false;
97 | }
98 |
99 | public void StartBot() {
100 | cancellationTokenSource = new CancellationTokenSource();
101 |
102 | botThread = new Thread(() => Run(cancellationTokenSource.Token));
103 | botThread.Start();
104 | }
105 |
106 | public async Task Run(CancellationToken token = default) {
107 | await EnsureWindowVisible();
108 | var uiWriter = new StateUiWriter(statusBox, additionalData, _uiScheduler);
109 | try {
110 | var bot = new GetOutBot(uiWriter, m_ServiceProvider.GetService(),
111 | new MemoryReader(m_ServiceProvider.GetService().CreateLogger()),
112 | m_ServiceProvider.GetService().CreateLogger(),
113 | cancellationTokenSource.Token);
114 | await bot.Run();
115 | } catch (ThreadInterruptedException) {
116 | } catch (OperationCanceledException) {
117 | Console.WriteLine("Bot stopped");
118 | } catch (Exception exception) {
119 | Console.WriteLine(exception);
120 | logger.LogError("Error {@exception}", exception);
121 | uiWriter.WriteState("Error:");
122 | uiWriter.WriteAdditionalData(exception.Message);
123 | GetOutBotAnalytic.SendError(exception.Message);
124 | throw;
125 | }
126 | }
127 |
128 | private async Task EnsureWindowVisible() {
129 | await Task.Factory.StartNew(async () => {
130 | await Task.Delay(8000);
131 | WindowState = WindowState.Normal;
132 | Activate();
133 | }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/D.WpfApp/StateUiWriter.cs:
--------------------------------------------------------------------------------
1 | using D.Core;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using System.Threading.Tasks.Dataflow;
9 | using System.Windows.Controls;
10 |
11 | namespace D.WpfApp {
12 | internal class StateUiWriter : IStateUiWriter {
13 |
14 | private readonly TaskScheduler _uiScheduler;
15 | public readonly TextPath m_Status;
16 | public readonly TextPath m_AdditionalData;
17 |
18 |
19 | public StateUiWriter(TextPath statusBox, TextPath additionalData, TaskScheduler uiScheduler) {
20 | _uiScheduler = uiScheduler;
21 | m_Status = statusBox;
22 | m_AdditionalData = additionalData;
23 | }
24 |
25 | public void WriteState(string state) {
26 | Task.Factory.StartNew(
27 | () => {
28 | m_Status.Text = state;
29 | },
30 | CancellationToken.None,
31 | TaskCreationOptions.None,
32 | this._uiScheduler);
33 | }
34 |
35 | public void WriteAdditionalData(string data) {
36 | Task.Factory.StartNew(
37 | () => {
38 | m_AdditionalData.Text = data;
39 | },
40 | CancellationToken.None,
41 | TaskCreationOptions.None,
42 | this._uiScheduler);
43 | }
44 |
45 | public void Clear() {
46 | WriteState("");
47 | WriteAdditionalData("");
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/D.WpfApp/TextPath.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Threading;
4 | using System.Windows;
5 | using System.Windows.Documents;
6 | using System.Windows.Media;
7 | using System.Windows.Shapes;
8 |
9 | namespace D.WpfApp {
10 | // https://www.codeproject.com/Articles/1106695/WPF-Text-Outline-Font
11 | public class TextPath : Shape {
12 | private Geometry _textGeometry;
13 |
14 | #region Dependency Properties
15 | public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(typeof(TextPath),
16 | new FrameworkPropertyMetadata(SystemFonts.MessageFontFamily,
17 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits,
18 | OnPropertyChanged));
19 | [Bindable(true), Category("Appearance")]
20 | [Localizability(LocalizationCategory.Font)]
21 | [TypeConverter(typeof(FontFamilyConverter))]
22 | public FontFamily FontFamily { get { return (FontFamily)GetValue(FontFamilyProperty); } set { SetValue(FontFamilyProperty, value); } }
23 |
24 | public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(TextPath),
25 | new FrameworkPropertyMetadata(SystemFonts.MessageFontSize,
26 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure,
27 | OnPropertyChanged));
28 | [Bindable(true), Category("Appearance")]
29 | [TypeConverter(typeof(FontSizeConverter))]
30 | [Localizability(LocalizationCategory.None)]
31 | public double FontSize { get { return (double)GetValue(FontSizeProperty); } set { SetValue(FontSizeProperty, value); } }
32 |
33 | public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(typeof(TextPath),
34 | new FrameworkPropertyMetadata(TextElement.FontStretchProperty.DefaultMetadata.DefaultValue,
35 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits,
36 | OnPropertyChanged));
37 | [Bindable(true), Category("Appearance")]
38 | [TypeConverter(typeof(FontStretchConverter))]
39 | public FontStretch FontStretch { get { return (FontStretch)GetValue(FontStretchProperty); } set { SetValue(FontStretchProperty, value); } }
40 |
41 | public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(typeof(TextPath),
42 | new FrameworkPropertyMetadata(SystemFonts.MessageFontStyle,
43 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits,
44 | OnPropertyChanged));
45 | [Bindable(true), Category("Appearance")]
46 | [TypeConverter(typeof(FontStyleConverter))]
47 | public FontStyle FontStyle { get { return (FontStyle)GetValue(FontStyleProperty); } set { SetValue(FontStyleProperty, value); } }
48 |
49 | public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(typeof(TextPath),
50 | new FrameworkPropertyMetadata(SystemFonts.MessageFontWeight,
51 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits,
52 | OnPropertyChanged));
53 | [Bindable(true), Category("Appearance")]
54 | [TypeConverter(typeof(FontWeightConverter))]
55 | public FontWeight FontWeight { get { return (FontWeight)GetValue(FontWeightProperty); } set { SetValue(FontWeightProperty, value); } }
56 |
57 | public static readonly DependencyProperty OriginPointProperty =
58 | DependencyProperty.Register("Origin", typeof(Point), typeof(TextPath),
59 | new FrameworkPropertyMetadata(new Point(0, 0),
60 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure,
61 | OnPropertyChanged));
62 | [Bindable(true), Category("Appearance")]
63 | [TypeConverter(typeof(PointConverter))]
64 | public Point Origin { get { return (Point)GetValue(OriginPointProperty); } set { SetValue(OriginPointProperty, value); } }
65 |
66 | public static readonly DependencyProperty TextProperty =
67 | DependencyProperty.Register("Text", typeof(string), typeof(TextPath),
68 | new FrameworkPropertyMetadata(string.Empty,
69 | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure,
70 | OnPropertyChanged));
71 | [Bindable(true), Category("Appearance")]
72 | public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
73 | #endregion
74 |
75 | protected override Geometry DefiningGeometry => _textGeometry ?? Geometry.Empty;
76 |
77 | private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((TextPath)d).CreateTextGeometry();
78 |
79 | private void CreateTextGeometry() {
80 | var formattedText = new FormattedText(Text, Thread.CurrentThread.CurrentUICulture, FlowDirection.LeftToRight,
81 | new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Brushes.Black);
82 | _textGeometry = formattedText.BuildGeometry(Origin);
83 | }
84 |
85 | protected override Size MeasureOverride(Size availableSize) {
86 | if (_textGeometry == null) CreateTextGeometry();
87 | if (_textGeometry.Bounds == Rect.Empty)
88 | return new Size(0, 0);
89 | // return the desired size
90 | return new Size(Math.Min(availableSize.Width, _textGeometry.Bounds.Width), Math.Min(availableSize.Height, _textGeometry.Bounds.Height));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/D.WpfApp/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
62 |
63 |
64 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/D.WpfApp/config.yml:
--------------------------------------------------------------------------------
1 | QuitOnHealthPercentage: 35
2 | QuitMethod: Both # Other values are: Socket, Menu
3 | # If you receive a hit that put you with less life than the hit you took then it will quit
4 | # this can increase survivability when QuitOnHealthPercentage is too low
5 | QuitOnMajorHit: true
6 |
7 | # To help diagnostic problems, i send error data so i can see if there is a problem. you can turn it off
8 | AnalyticLib:
9 | IsDisabled: false
10 |
--------------------------------------------------------------------------------
/D.WpfApp/run.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | Rem Make powershell read this file, skip a number of lines, and execute it.
3 | Rem This works around .ps1 bad file association as non executables.
4 | PowerShell -Command "Get-Content '%~dpnx0' | Select-Object -Skip 5 | Out-String | Invoke-Expression"
5 | goto :eof
6 |
7 | # preferable to open battle.net since it will log you in
8 | $battleNetLauncherNotOpen = $null -eq (Get-Process "Battle.net" -ErrorAction SilentlyContinue)
9 | $merged = & {
10 | Get-ChildItem HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
11 | Get-ChildItem HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
12 | }
13 | $names = $merged |foreach-object {Get-ItemProperty $_.PsPath}
14 |
15 | Write-Host $battleNetLauncherNotOpen
16 | if($battleNetLauncherNotOpen){
17 | foreach ($name in $names) {
18 | IF (-Not [string]::IsNullOrEmpty($name.DisplayName) -AND $name.DisplayName -eq "Battle.net") {
19 | $battleNetLauncherPath = Join-Path $name.InstallLocation "Battle.net Launcher.exe"
20 | $getOutBotPath = Resolve-Path -Relative ".\GetOutBot.exe"
21 | Start-Process -FilePath $battleNetLauncherPath
22 | Start-Process -FilePath $getOutBotPath
23 | return;
24 | }
25 | }
26 | } else {
27 | foreach ($name in $names) {
28 | IF (-Not [string]::IsNullOrEmpty($name.DisplayName) -AND $name.DisplayName -eq "Diablo II Resurrected") {
29 | $d2rPath = Join-Path $name.InstallLocation "D2R.exe"
30 | $getOutBotPath = Resolve-Path -Relative ".\GetOutBot.exe"
31 | Write-Host $d2rPath
32 | Write-Host $getOutBotPath
33 | Start-Process -FilePath $d2rPath
34 | Start-Process -FilePath $getOutBotPath
35 | return;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/GitVersion.yml:
--------------------------------------------------------------------------------
1 | mode: ContinuousDelivery
2 | branches: {}
3 | ignore:
4 | sha: []
5 | merge-message-formats: {}
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How it work
2 | This is a simple script that keep you alive by quiting the game before you die.
3 | It look at your life and check if it passed a threshold (default is 35% life). When it does it save&quit and kill tcp socket.
4 |
5 | This is not intented for online usage!
6 | Also i do advise testing it on a softcore character before using it on a hardcore 😁
7 |
8 | **[Download](https://github.com/GitMyCode/d2r-chicken-bot/releases)**
9 |
10 | ### Usage
11 | Click on the `run.cmd` to automaticly launch diablo 2 and the bot together. This is more convenient than having to think about launch the bot each time to launch diablo 2. You can make a shortcut from it directly to your desktop
12 |
13 | # Demo
14 | https://user-images.githubusercontent.com/3288428/144772204-4a7184a5-6f7b-45d9-b4af-5b9d27c89f51.mp4
15 |
16 | # Configuration
17 | in the folder there is `config.yml` file that should look like this:
18 | ```
19 | QuitOnHealthPercentage: 35
20 | # If you are offline then put the value: Menu
21 | QuitMethod: Both # Other values are: Socket, Menu
22 | # If you receive a hit that put you with less life than the hit you took then it will quit
23 | # this can increase survivability when QuitOnHealthPercentage is too low
24 | QuitOnMajorHit: true
25 | ```
26 | the number is the percentage of life at which the script will quit.
27 |
28 | # States
29 |
30 | *Looking for player*
31 |
32 | This mean that the bot is searching for your player. If you are not in a game this is normal. If you are this mean there is a problem.
33 |
34 | *In town*
35 |
36 | When you are safe in town you don't need to quit if your life is under the threshold. So it won't do anything in that state.
37 |
38 | *Watching you*
39 |
40 | If you are out then it will be in that state and quit if your life goes under the threshold.
41 |
42 |
43 | # Credits
44 | credits to the nice MapAssists project where i took many piece for the offsets and learned a lot
45 | https://github.com/OneXDeveloper/MapAssist
46 |
--------------------------------------------------------------------------------
/dependencies/Analytics.Lib.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GitMyCode/d2r-chicken-bot/4e2ec516f82eb4f6301817feeb7be231d0a7391e/dependencies/Analytics.Lib.dll
--------------------------------------------------------------------------------
/diablobot.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D.Models", "D.Models\D.Models.csproj", "{AC848FD2-ED36-4178-9F96-36DCA3187037}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D.WpfApp", "D.WpfApp\D.WpfApp.csproj", "{E80F4BAB-2B2C-4260-8BF6-94887A18CAA7}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D.Core", "D.Core\D.Core.csproj", "{94116328-4A42-4437-A8AF-B382454878F4}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D.ConsoleApp", "D.ConsoleApp\D.ConsoleApp.csproj", "{B3E31C2B-3CB1-4E64-AD3B-78EFD3E39CD6}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {AC848FD2-ED36-4178-9F96-36DCA3187037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AC848FD2-ED36-4178-9F96-36DCA3187037}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AC848FD2-ED36-4178-9F96-36DCA3187037}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AC848FD2-ED36-4178-9F96-36DCA3187037}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {E80F4BAB-2B2C-4260-8BF6-94887A18CAA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {E80F4BAB-2B2C-4260-8BF6-94887A18CAA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {E80F4BAB-2B2C-4260-8BF6-94887A18CAA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {E80F4BAB-2B2C-4260-8BF6-94887A18CAA7}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {94116328-4A42-4437-A8AF-B382454878F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {94116328-4A42-4437-A8AF-B382454878F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {94116328-4A42-4437-A8AF-B382454878F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {94116328-4A42-4437-A8AF-B382454878F4}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {B3E31C2B-3CB1-4E64-AD3B-78EFD3E39CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {B3E31C2B-3CB1-4E64-AD3B-78EFD3E39CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {B3E31C2B-3CB1-4E64-AD3B-78EFD3E39CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {B3E31C2B-3CB1-4E64-AD3B-78EFD3E39CD6}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {0134F2DC-18A9-4E43-A59C-E19E1E584BC9}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------