chore: initial commit

This commit is contained in:
Anna 2022-01-11 05:22:08 -05:00
commit 7ec5bb3ef2
11 changed files with 2372 additions and 0 deletions

365
.gitignore vendored Normal file
View File

@ -0,0 +1,365 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# Packaging
pack/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

21
LICENCE Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Anna Clemens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
Siggingway.sln Executable file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Siggingway", "Siggingway\Siggingway.csproj", "{6FBC7E71-91E7-460E-8A47-8E7FE04080BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6FBC7E71-91E7-460E-8A47-8E7FE04080BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FBC7E71-91E7-460E-8A47-8E7FE04080BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FBC7E71-91E7-460E-8A47-8E7FE04080BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FBC7E71-91E7-460E-8A47-8E7FE04080BE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

2
Siggingway.sln.DotSettings Executable file
View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Siggingway/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

1635
Siggingway/Annotations.cs Executable file

File diff suppressed because it is too large Load Diff

20
Siggingway/ScanType.cs Executable file
View File

@ -0,0 +1,20 @@
using Dalamud.Game;
namespace Siggingway;
/// <summary>
/// The type of scan to perform with a signature.
/// </summary>
public enum ScanType {
/// <summary>
/// Scan the text section of the executable. Uses
/// <see cref="SigScanner.TryScanText"/>.
/// </summary>
Text,
/// <summary>
/// Scans the text section of the executable in order to find a data section
/// address. Uses <see cref="SigScanner.TryGetStaticAddressFromSig"/>
/// </summary>
StaticAddress,
}

130
Siggingway/Siggingway.cs Executable file
View File

@ -0,0 +1,130 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Hooking;
using Dalamud.Logging;
namespace Siggingway;
/// <summary>
/// The main entry point for Siggingway.
/// </summary>
public static class Siggingway {
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
/// <summary>
/// Initialises an object's fields and properties that are annotated with a
/// <see cref="SignatureAttribute"/>.
/// </summary>
/// <param name="scanner">the SigScanner to use for scanning for signatures</param>
/// <param name="self">the object to initialise</param>
/// <param name="log">if warnings should be logged using <see cref="PluginLog"/></param>
public static void Initialise(SigScanner scanner, object self, bool log = true) {
var selfType = self.GetType();
var fields = selfType.GetFields(Flags).Select(field => (IFieldOrPropertyInfo) new FieldInfoWrapper(field))
.Concat(selfType.GetProperties(Flags).Select(prop => new PropertyInfoWrapper(prop)))
.Select(field => (field, field.GetCustomAttribute<SignatureAttribute>()))
.Where(field => field.Item2 != null);
foreach (var (info, sig) in fields) {
IntPtr ptr;
var success = sig!.ScanType == ScanType.Text
? scanner.TryScanText(sig.Signature, out ptr)
: scanner.TryGetStaticAddressFromSig(sig.Signature, out ptr);
if (!success) {
if (log) {
PluginLog.Warning($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})");
}
continue;
}
void LogInvalid(string message) {
PluginLog.Warning($"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}");
}
var actualType = info.ActualType;
PluginLog.Information($"actualType: {actualType}");
if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
// unwrap the nullable
actualType = actualType.GetGenericArguments()[0];
PluginLog.Information($"unwrapped actualType: {actualType}");
}
switch (sig.UseFlags) {
case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)):
case SignatureUseFlags.Pointer: {
info.SetValue(self, ptr);
break;
}
case SignatureUseFlags.Auto when actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Hook<>):
case SignatureUseFlags.Hook: {
if (!actualType.IsGenericType || actualType.GetGenericTypeDefinition() != typeof(Hook<>)) {
LogInvalid($"{actualType.Name} is not a Hook<T>");
continue;
}
var hookDelegateType = actualType.GenericTypeArguments[0];
Delegate? detour;
if (sig.DetourName == null) {
var matches = selfType.GetMethods(Flags)
.Select(method => method.IsStatic
? Delegate.CreateDelegate(hookDelegateType, method, false)
: Delegate.CreateDelegate(hookDelegateType, self, method, false))
.Where(del => del != null)
.ToArray();
if (matches.Length != 1) {
LogInvalid("Either found no matching detours or found more than one: specify a detour name");
continue;
}
detour = matches[0]!;
} else {
var method = selfType.GetMethod(sig.DetourName, Flags);
if (method == null) {
LogInvalid($"Could not find detour \"{sig.DetourName}\"");
continue;
}
var del = method.IsStatic
? Delegate.CreateDelegate(hookDelegateType, method, false)
: Delegate.CreateDelegate(hookDelegateType, self, method, false);
if (del == null) {
LogInvalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}");
continue;
}
detour = del;
}
var ctor = actualType.GetConstructor(new[] { typeof(IntPtr), hookDelegateType });
if (ctor == null) {
PluginLog.Error("Error in Siggingway: could not find Hook constructor");
continue;
}
var hook = ctor.Invoke(new object?[] { ptr, detour });
info.SetValue(self, hook);
PluginLog.Information($"Hook {selfType.FullName}.{info.Name} set up");
break;
}
case SignatureUseFlags.Auto when actualType.IsPrimitive:
case SignatureUseFlags.Offset: {
var offset = Marshal.PtrToStructure(ptr + sig.Offset, actualType);
info.SetValue(self, offset);
break;
}
default: {
if (log) {
LogInvalid("could not detect desired signature use, set SignatureUseFlags manually");
}
break;
}
}
}
}
}

38
Siggingway/Siggingway.csproj Executable file
View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<TargetFramework>net5.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Title>Siggingway</Title>
<Authors>ascclemens</Authors>
<RepositoryUrl>https://git.annaclemens.io/ascclemens/Siggingway</RepositoryUrl>
<Description>Utilities for working with signatures in Dalamud plugins.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<Dalamud>$(AppData)\XIVLauncher\addon\Hooks\dev</Dalamud>
</PropertyGroup>
<PropertyGroup Condition="'$(IsCI)' == 'true'">
<Dalamud>$(HOME)/dalamud</Dalamud>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(Dalamud)\Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,56 @@
using JetBrains.Annotations;
namespace Siggingway;
/// <summary>
/// The main way to use Siggingway. Apply this attribute to any field/property
/// that should make use of a signature. See the field documentation for more
/// information.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[MeansImplicitUse(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Itself)]
// ReSharper disable once ClassNeverInstantiated.Global
public sealed class SignatureAttribute : Attribute {
/// <summary>
/// The memory signature for this field/property.
/// </summary>
public readonly string Signature;
/// <summary>
/// The way this signature should be used. By default, this is guessed using
/// simple heuristics, but it can be manually specified if Siggingway can't
/// figure it out.
///
/// <seealso cref="SignatureUseFlags"/>
/// </summary>
public SignatureUseFlags UseFlags = SignatureUseFlags.Auto;
/// <summary>
/// The type of scan to perform. By default, this scans the text section of
/// the executable, but this should be set to StaticAddress for static
/// addresses.
/// </summary>
public ScanType ScanType = ScanType.Text;
/// <summary>
/// The detour name if this signature is for a hook. Siggingway will search
/// the type containing this field/property for a method that matches the
/// hook's delegate type, but if it doesn't find one or finds more than one,
/// it will fail. You can specify the name of the method here to avoid this.
/// </summary>
public string? DetourName;
/// <summary>
/// When <see cref="UseFlags"/> is set to Offset, this is the offset from
/// the signature to read memory from.
/// </summary>
public int Offset;
/// <summary>
/// Create a <see cref="SignatureAttribute"/> with the given signature.
/// </summary>
/// <param name="signature">signature to scan for, see <see cref="Signature"/></param>
public SignatureAttribute(string signature) {
this.Signature = signature;
}
}

36
Siggingway/SignatureUseFlags.cs Executable file
View File

@ -0,0 +1,36 @@
using Dalamud.Hooking;
namespace Siggingway;
/// <summary>
/// Use flags for a signature attribute. This tells Siggingway how to use the
/// result of the signature.
/// </summary>
public enum SignatureUseFlags {
/// <summary>
/// Siggingway will use simple heuristics to determine the best signature
/// use for the field/property.
/// </summary>
Auto,
/// <summary>
/// The signature should be used as a plain pointer. This is correct for
/// static addresses, functions, or anything else that's an
/// <see cref="IntPtr"/> at heart.
/// </summary>
Pointer,
/// <summary>
/// The signature should be used as a hook. This is correct for
/// <see cref="Hook{T}"/> fields/properties.
/// </summary>
Hook,
/// <summary>
/// The signature should be used to determine an offset. This is the default
/// for all primitive types. Siggingway will read from the memory at this
/// signature and store the result in the field/property. An offset from the
/// signature can be specified in the <see cref="SignatureAttribute"/>.
/// </summary>
Offset,
}

53
Siggingway/Wrappers.cs Executable file
View File

@ -0,0 +1,53 @@
using System.Reflection;
namespace Siggingway;
internal interface IFieldOrPropertyInfo {
string Name { get; }
Type ActualType { get; }
void SetValue(object? self, object? value);
T? GetCustomAttribute<T>() where T : Attribute;
}
internal sealed class FieldInfoWrapper : IFieldOrPropertyInfo {
private FieldInfo Info { get; }
public FieldInfoWrapper(FieldInfo info) {
this.Info = info;
}
public string Name => this.Info.Name;
public Type ActualType => this.Info.FieldType;
public void SetValue(object? self, object? value) {
this.Info.SetValue(self, value);
}
public T? GetCustomAttribute<T>() where T : Attribute {
return this.Info.GetCustomAttribute<T>();
}
}
internal sealed class PropertyInfoWrapper : IFieldOrPropertyInfo {
private PropertyInfo Info { get; }
public PropertyInfoWrapper(PropertyInfo info) {
this.Info = info;
}
public string Name => this.Info.Name;
public Type ActualType => this.Info.PropertyType;
public void SetValue(object? self, object? value) {
this.Info.SetValue(self, value);
}
public T? GetCustomAttribute<T>() where T : Attribute {
return this.Info.GetCustomAttribute<T>();
}
}