Skip to content

Commit

Permalink
Implement basic pattern scan.
Browse files Browse the repository at this point in the history
  • Loading branch information
Emzi0767 committed Sep 19, 2020
1 parent 6548ad6 commit fa17fe7
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/Emzi0767.AmongUsDirector.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<!-- Project info -->
<PropertyGroup>
<RootNamespace>Emzi0767.AmongUsDirector</RootNamespace>
<Version>1.0.1</Version>
<Version>1.1.0</Version>
<AssemblyVersion>$(Version).0</AssemblyVersion>
<FileVersion>$(Version).0</FileVersion>
</PropertyGroup>
Expand Down
12 changes: 9 additions & 3 deletions src/Emzi0767.AmongUsDirector.Wrapper/GameProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ public sealed class GameProcess : IDisposable

private readonly Process _proc;
private readonly IntPtrEx _module;
private readonly int _moduleSize;

private readonly CancellationTokenSource _cts;
private readonly CancellationToken _ct;
private Thread _eventDispatcherThread;

private GameProcess(Process p, IntPtrEx gameModule)
private GameProcess(Process p, IntPtrEx gameModule, int moduleSize)
{
this._proc = p;
this._module = gameModule;
this._moduleSize = moduleSize;
this._cts = new CancellationTokenSource();
this._ct = this._cts.Token;

Expand Down Expand Up @@ -153,7 +155,7 @@ private void TriggerEvent(object sender, MeetingEndEventArgs ea)
private void EventDispatcherLoop()
{
var mem = new ProcessMemory(this._proc);
var reader = new GameReader(mem, this._module);
var reader = new GameReader(mem, this._module, this._moduleSize);
reader.GameStarted += (o, e) => this.TriggerEvent(o, e);
reader.GameEnded += (o, e) => this.TriggerEvent(o, e);
reader.PlayerJoined += (o, e) => this.TriggerEvent(o, e);
Expand Down Expand Up @@ -217,7 +219,11 @@ public static unsafe GameProcess Attach()
if (module == IntPtr.Zero)
throw new InvalidProcessException();

return new GameProcess(proc, module);
var modInfo = default(ModuleInfo);
if (!PInvoke.GetModuleInformation(proc.Handle, module, &modInfo, sizeof(ModuleInfo)))
throw new InvalidProcessException();

return new GameProcess(proc, module, modInfo.SizeOfImage);
}
}
}
37 changes: 36 additions & 1 deletion src/Emzi0767.AmongUsDirector.Wrapper/GameReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,49 @@ internal sealed class GameReader
{
private readonly ProcessMemory _mem;
private readonly IntPtrEx _module;
private readonly int _moduleSize;

private GameStateInfo _state;

public GameReader(ProcessMemory pmem, IntPtrEx module)
public GameReader(ProcessMemory pmem, IntPtrEx module, int moduleSize)
{
this._mem = pmem;
this._module = module;
this._moduleSize = moduleSize;
this._state = new GameStateInfo();

var offsets = this._mem.FindOffsets(new NamedPattern[]
{
new NamedPattern(Offsets.ClientName, Offsets.ClientPattern, Offsets.ClientPatternPtrLocation),
new NamedPattern(Offsets.MeetingHudName, Offsets.MeetingHudPattern, Offsets.MeetingHudPatternPtrLocation),
new NamedPattern(Offsets.GameDataName, Offsets.GameDataPattern, Offsets.GameDataPatternPtrLocation),
new NamedPattern(Offsets.ShipStatusName, Offsets.ShipStatusPattern, Offsets.ShipStatusPatternPtrLocation)
}, this._module, this._moduleSize);

foreach (var offset in offsets)
{
switch (offset.Name)
{
case Offsets.ClientName:
Offsets.ClientBase = offset.Offset;
break;

case Offsets.MeetingHudName:
Offsets.MeetingHudBase = offset.Offset;
break;

case Offsets.GameDataName:
Offsets.GameDataBase = offset.Offset;
break;

case Offsets.ShipStatusName:
Offsets.ShipStatusBase = offset.Offset;
break;
}
}

if (Offsets.ClientBase == 0 || Offsets.MeetingHudBase == 0 || Offsets.GameDataBase == 0 || Offsets.ShipStatusBase == 0)
throw new InvalidProcessException();
}

public void DoRead()
Expand Down
31 changes: 31 additions & 0 deletions src/Emzi0767.AmongUsDirector.Wrapper/Interop/ModuleInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is part of Among Us Director project.
//
// Copyright 2020 Emzi0767
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Runtime.InteropServices;

namespace Emzi0767.AmongUsDirector
{
[StructLayout(LayoutKind.Sequential)]
internal struct ModuleInfo
{
public IntPtr lpBaseOfDll;

public int SizeOfImage;

public IntPtr EntryPoint;
}
}
31 changes: 31 additions & 0 deletions src/Emzi0767.AmongUsDirector.Wrapper/Interop/NamedOffset.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is part of Among Us Director project.
//
// Copyright 2020 Emzi0767
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Emzi0767.AmongUsDirector
{
internal struct NamedOffset
{
public int Offset { get; }

public string Name { get; }

public NamedOffset(int offset, string name)
{
this.Offset = offset;
this.Name = name;
}
}
}
34 changes: 34 additions & 0 deletions src/Emzi0767.AmongUsDirector.Wrapper/Interop/NamedPattern.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This file is part of Among Us Director project.
//
// Copyright 2020 Emzi0767
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Emzi0767.AmongUsDirector
{
internal struct NamedPattern
{
public string Name { get; }

public byte?[] Pattern { get; }

public int PointerStart { get; }

public NamedPattern(string name, byte?[] pattern, int ptrStart)
{
this.Name = name;
this.Pattern = pattern;
this.PointerStart = ptrStart;
}
}
}
20 changes: 16 additions & 4 deletions src/Emzi0767.AmongUsDirector.Wrapper/Interop/Offsets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,28 @@ internal static class Offsets
public const int StringFirst = 0x0C;

public const string ClientName = "AmongUsClient";
public const int ClientBase = 0xDA5ACC;
public static byte?[] ClientPattern { get; } = new byte?[] { 0xC7, 0x46, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, null, null, null, null, 0x8B, 0x40, 0x5C, 0x8B, 0x30 };
public const int ClientPatternPtrLocation = 8;
//public const int ClientBase = 0xDA5ACC;
public static int ClientBase { get; set; } = 0;

public const string MeetingHudName = "MeetingHud";
public const int MeetingHudBase = 0xDA58D0;
public static byte?[] MeetingHudPattern { get; } = new byte?[] { 0xC7, 0x43, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, null, null, null, null, 0x8B, 0x40, 0x5C, 0x8B, 0x30 };
public const int MeetingHudPatternPtrLocation = 8;
//public const int MeetingHudBase = 0xDA58D0;
public static int MeetingHudBase { get; set; } = 0;

public const string GameDataName = "GameData";
public const int GameDataBase = 0xDA5A60;
public static byte?[] GameDataPattern { get; } = new byte?[] { 0xC7, 0x47, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, null, null, null, null, 0x53, 0x8B, 0x40, 0x5C };
public const int GameDataPatternPtrLocation = 8;
//public const int GameDataBase = 0xDA5A60;
public static int GameDataBase { get; set; } = 0;

public const string ShipStatusName = "ShipStatus";
public const int ShipStatusBase = 0xDA5A50;
public static byte?[] ShipStatusPattern { get; } = new byte?[] { 0x89, 0x43, 0x08, 0xA1, null, null, null, null, 0x46, 0x83, 0xC7, 0x04 };
public const int ShipStatusPatternPtrLocation = 4;
//public const int ShipStatusBase = 0xDA5A50;
public static int ShipStatusBase { get; set; } = 0;

public const string ExileControllerName = "ExileController";
public const string MiraExileControllerName = "MiraExileController";
Expand Down
3 changes: 3 additions & 0 deletions src/Emzi0767.AmongUsDirector.Wrapper/Interop/PInvoke.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ internal static class PInvoke
[DllImport("psapi.dll", EntryPoint = "GetModuleBaseNameW", CharSet = CharSet.Unicode, SetLastError = true)]
public static unsafe extern uint GetModuleBaseName(IntPtr hProcess, IntPtr hModule, char* lpBaseName, uint nSize);

[DllImport("psapi.dll", EntryPoint = "GetModuleInformation", CharSet = CharSet.Unicode, SetLastError = true)]
public static unsafe extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, void* lpmodinfo, int size);

[DllImport("kernel32.dll", SetLastError = true)]
public static unsafe extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, IntPtr dwSize, out IntPtr lpNumberOfBytesRead);
}
Expand Down
118 changes: 117 additions & 1 deletion src/Emzi0767.AmongUsDirector.Wrapper/Interop/ProcessMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
// limitations under the License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Emzi0767.AmongUsDirector
{
/// <summary>
/// Wraps the memory of a foreign process.
/// </summary>
public sealed class ProcessMemory
internal sealed class ProcessMemory
{
private readonly Process _proc;

Expand Down Expand Up @@ -117,6 +120,113 @@ public unsafe string ReadAnsiString(IntPtrEx addr, int maxLength = 256)
return Encoding.ASCII.GetString(spbuff);
}

public unsafe int FindPattern(byte?[] pattern, ReadOnlySpan<byte> buffer)
{
if (pattern == null || pattern.Length == 0)
return 0;

// split into subpatterns
var subpatterns = new List<Subpattern>(4);
var lastNull = false;
var start = 0;
for (var i = 0; i < pattern.Length; i++)
{
if (pattern[i] == null && !lastNull)
{
var subpattern = new byte[i - start];
for (var j = 0; j < subpattern.Length; j++)
subpattern[j] = pattern[j + start].Value;

subpatterns.Add(new Subpattern { Gap = -1, Pattern = subpattern });

start = i;
lastNull = true;
}
else if (pattern[i] != null && lastNull)
{
subpatterns.Add(new Subpattern { Gap = i - start });

start = i;
lastNull = false;
}
}

var sub = new Subpattern
{
Gap = lastNull ? pattern.Length - start : -1,
Pattern = lastNull ? null : new byte[pattern.Length - start]
};
subpatterns.Add(sub);
for (var j = 0; j < sub.Pattern.Length; j++)
sub.Pattern[j] = pattern[j + start].Value;

if (subpatterns[0].Gap != -1) // not be doing dis anyways
return 0;

// scan for first subpattern
var pos = 0;
while (pos < buffer.Length - pattern.Length)
{
var buff = buffer.Slice(pos);

var subi = 0;
var subpattern = subpatterns[subi];

var fpos = buff.IndexOf(subpattern.Pattern);
if (fpos == -1)
break;

// scan for next subpatterns
var spos = pos + fpos;
pos += fpos + subpattern.Pattern.Length;
for (++subi; subi < subpatterns.Count; subi++)
{
subpattern = subpatterns[subi];
if (subpattern.Gap > 0)
{
pos += subpattern.Gap;
continue;
}

buff = buffer.Slice(pos);
if (!buff.Slice(0, subpattern.Pattern.Length).SequenceEqual(subpattern.Pattern))
break;

pos += subpattern.Pattern.Length;
}

// did we find all?
if (subi == subpatterns.Count)
return spos;
}

return 0;
}

public unsafe IEnumerable<NamedOffset> FindOffsets(NamedPattern[] patterns, IntPtrEx @base, int size)
{
var buff = new byte[size];
fixed (byte* ptr = buff)
if (!this.TryReadRawMemory(@base, ptr, size, out var read) || read != size)
return null;

var baseval = @base.Pointer.ToInt32();
var offsets = new NamedOffset[patterns.Length];
for (var i = 0; i < patterns.Length; i++)
{
var pattern = patterns[i];
var off = this.FindPattern(pattern.Pattern, buff);

var ptr = 0;
if (!this.TryReadRawMemory(@base + off + pattern.PointerStart, &ptr, sizeof(int), out var read) || read != sizeof(int))
throw new InvalidProcessException();

offsets[i] = new NamedOffset(ptr - baseval, pattern.Name);
}

return offsets;
}

private unsafe bool TryReadRawMemory(IntPtrEx addr, void* buff, int size, out int read)
{
read = -1;
Expand All @@ -126,5 +236,11 @@ private unsafe bool TryReadRawMemory(IntPtrEx addr, void* buff, int size, out in
read = pread.ToInt32();
return true;
}

public struct Subpattern
{
public int Gap { get; set; }
public byte[] Pattern { get; set; }
}
}
}

0 comments on commit fa17fe7

Please sign in to comment.