Merge branch 'master' of https://github.com/goatcorp/Dalamud into goatcorp-master

This commit is contained in:
karashiiro 2021-07-24 23:28:37 -07:00
commit f215fa77ee
249 changed files with 13902 additions and 5365 deletions

View file

@ -24,6 +24,7 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_code_quality_unused_parameters = non_public
dotnet_naming_rule.event_rule.severity = warning
dotnet_naming_rule.event_rule.style = on_upper_camel_case_style
dotnet_naming_rule.event_rule.symbols = event_symbols
@ -56,11 +57,13 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
dotnet_style_object_initializer = false
# ReSharper properties
resharper_align_linq_query = true
@ -105,8 +108,10 @@ resharper_redundant_base_qualifier_highlighting = none
resharper_suggest_var_or_type_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_highlighting = hint
csharp_style_deconstructed_variable_declaration=true:silent
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 4
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent

View file

@ -12,7 +12,7 @@ jobs:
steps:
- name: Remove old artifacts
uses: c-hive/gha-remove-artifacts@24dc23384a1fa6a058b79c73727ae0cb2200ca4c
uses: c-hive/gha-remove-artifacts@v1.2.0
with:
age: '1 month'
skip-tags: true

View file

@ -7,15 +7,12 @@ jobs:
name: Build on Windows
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Checkout Dalamud
uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup Nuget
uses: nuget/setup-nuget@v1
with:
nuget-version: latest
- name: Restore Nuget Packages
run: nuget restore Dalamud.sln
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.0.2
- name: Define VERSION
run: |
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
@ -25,18 +22,17 @@ jobs:
($env:REPO_NAME) >> VERSION
($env:BRANCH) >> VERSION
($env:COMMIT) >> VERSION
- name: Build DotNet4
run: |
cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\"
.\MSBuild.exe $Env:GITHUB_WORKSPACE\Dalamud.sln /t:Build /p:Configuration=Release /p:DefineConstants=XL_NOAUTOUPDATE
- name: Run xUnit Tests
run: |
${{github.workspace}}\packages\xunit.runner.console.2.4.1\tools\net472\xunit.console.exe ${{github.workspace}}\Dalamud.Test\bin\Release\Dalamud.Test.dll
- name: Build Dalamud
run: .\build.ps1 compile
- name: Test Dalamud
run: .\build.ps1 test
- name: Create hashlist
run: .\CreateHashList.ps1 .\bin\Release
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: dalamud-artifact
path: bin\
path: bin\Release
deploy_stg:
name: Deploy dalamud-distrib staging
@ -54,7 +50,7 @@ jobs:
path: .\scratch
- name: Generate dalamud-distrib version file
shell: powershell
shell: pwsh
run: |
Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip

3
.gitmodules vendored
View file

@ -4,3 +4,6 @@
[submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs
url = https://github.com/goatcorp/FFXIVClientStructs.git
[submodule "lib/SharpDX.Desktop"]
path = lib/SharpDX.Desktop
url = https://github.com/goatcorp/SharpDX.Desktop.git

112
.nuke/build.schema.json Normal file
View file

@ -0,0 +1,112 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI"
]
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"Test"
]
}
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"Test"
]
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
}
}
}
}
}

4
.nuke/parameters.json Normal file
View file

@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": "Dalamud.sln"
}

View file

@ -1,7 +1,11 @@
$hashes = @{}
$hashes = [ordered]@{}
Get-ChildItem $args[0] -Exclude dalamud.txt,*.zip,*.pdb,*.ipdb | Foreach-Object {
$hashes.Add($_.Name, (Get-FileHash $_.FullName -Algorithm MD5).Hash)
Set-Location $args[0]
Get-ChildItem -File -Recurse -Exclude dalamud.txt,*.zip,*.pdb,*.ipdb | Foreach-Object {
$key = ($_.FullName | Resolve-Path -Relative).TrimStart(".\\")
$val = (Get-FileHash $_.FullName -Algorithm MD5).Hash
$hashes.Add($key, $val)
}
ConvertTo-Json $hashes | Out-File -FilePath (Join-Path $args[0] "hashes.json")
$hashes | ConvertTo-Json | Out-File -FilePath "hashes.json"

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{55198DC3-A03D-408E-A8EB-2077780C8576}</ProjectGuid>
<RootNamespace>Dalamud_Boot</RootNamespace>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Platform>x64</Platform>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet>
<OutDir>..\bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>false</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ItemGroup>
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
<Link>nethost.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
<ClCompile Include="..\lib\CoreCLR\pch.cpp" />
<ClCompile Include="dllmain.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\boot.h" />
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
<ClInclude Include="..\lib\CoreCLR\framework.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="..\lib\CoreCLR\pch.h" />
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\libnethost.lib" />
<Library Include="..\lib\CoreCLR\nethost\nethost.lib" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Library Files">
<UniqueIdentifier>{18be40ac-9367-46ff-b848-4c528aa97a8d}</UniqueIdentifier>
<Extensions>lib</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\boot.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\nethost.lib">
<Filter>Library Files</Filter>
</Library>
<Library Include="..\lib\CoreCLR\nethost\libnethost.lib">
<Filter>Library Files</Filter>
</Library>
</ItemGroup>
</Project>

66
Dalamud.Boot/dllmain.cpp Normal file
View file

@ -0,0 +1,66 @@
#define WIN32_LEAN_AND_MEAN
#define DllExport extern "C" __declspec(dllexport)
#include <filesystem>
#include <Windows.h>
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
HMODULE g_hModule;
DllExport DWORD WINAPI Initialize(LPVOID lpParam)
{
#if !defined(NDEBUG)
ConsoleSetup(L"Dalamud Boot");
#endif
wchar_t _module_path[MAX_PATH];
GetModuleFileNameW(g_hModule, _module_path, sizeof _module_path / 2);
std::filesystem::path fs_module_path(_module_path);
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.runtimeconfig.json").c_str());
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.dll").c_str());
// =========================================================================== //
void* entrypoint_vfn;
int result = InitializeClrAndGetEntryPoint(
runtimeconfig_path,
module_path,
L"Dalamud.EntryPoint, Dalamud",
L"Initialize",
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
&entrypoint_vfn);
if (result != 0)
return result;
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(LPVOID);
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
printf("Initializing Dalamud... ");
entrypoint_fn(lpParam);
printf("Done!\n");
// =========================================================================== //
#if !defined(NDEBUG)
FreeConsole();
#endif
return 0;
}
BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
DisableThreadLibraryCalls(hModule);
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_hModule = hModule;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View file

@ -0,0 +1,61 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Dalamud.CorePlugin</AssemblyName>
<TargetFramework>net5.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>9.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<NoWarn>IDE0003</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(appData)\XIVLauncher\devPlugins\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(appData)\XIVLauncher\devPlugins\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lumina" Version="3.3.0" />
<PackageReference Include="Lumina.Excel" Version="5.50.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud\Dalamud.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,17 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
// General
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "I like regions", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "I like regions in elements too", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is annoying", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]

View file

@ -0,0 +1,96 @@
using System;
using System.IO;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
namespace Dalamud.CorePlugin
{
/// <summary>
/// This class is a a plugin testbed for developing new Dalamud features with easy access to Dalamud itself.
/// Be careful to not commit anything extra.
/// </summary>
public sealed class PluginImpl : IDalamudPlugin
{
private readonly WindowSystem windowSystem = new("Dalamud.CorePlugin");
private Localization localizationManager;
/// <inheritdoc/>
public string Name => "Dalamud.CorePlugin";
/// <summary>
/// Gets the plugin interface.
/// </summary>
internal DalamudPluginInterface Interface { get; private set; }
/// <inheritdoc/>
public void Initialize(DalamudPluginInterface pluginInterface)
{
try
{
this.InitLoc();
this.Interface = pluginInterface;
this.windowSystem.AddWindow(new PluginWindow(Dalamud.Instance));
this.Interface.UiBuilder.OnBuildUi += this.OnDraw;
this.Interface.UiBuilder.OnOpenConfigUi += this.OnOpenConfigUi;
this.Interface.CommandManager.AddHandler("/di", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
}
catch (Exception ex)
{
PluginLog.Error(ex, "kaboom");
}
}
/// <inheritdoc/>
public void Dispose()
{
this.Interface.CommandManager.RemoveHandler("/di");
this.Interface.UiBuilder.OnBuildUi -= this.OnDraw;
this.windowSystem.RemoveAllWindows();
this.Interface.Dispose();
}
private void InitLoc()
{
// CheapLoc needs to be reinitialized here because it tracks the setup by assembly name. New assembly, new setup.
this.localizationManager = new Localization(Path.Combine(Dalamud.Instance.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_");
if (!string.IsNullOrEmpty(Dalamud.Instance.Configuration.LanguageOverride))
{
this.localizationManager.SetupWithLangCode(Dalamud.Instance.Configuration.LanguageOverride);
}
else
{
this.localizationManager.SetupWithUiCulture();
}
}
private void OnDraw()
{
try
{
this.windowSystem.Draw();
}
catch (Exception ex)
{
PluginLog.Error(ex, "Boom");
}
}
private void OnCommand(string command, string args)
{
// this.window.IsOpen = true;
}
private void OnOpenConfigUi(object sender, EventArgs e)
{
// this.window.IsOpen = true;
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Numerics;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.CorePlugin
{
/// <summary>
/// Class responsible for drawing the plugin installer.
/// </summary>
internal class PluginWindow : Window, IDisposable
{
private static readonly ModuleLog Log = new("CorePlugin");
private readonly Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="PluginWindow"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
public PluginWindow(Dalamud dalamud)
: base("CorePlugin", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollbar)
{
this.dalamud = dalamud;
this.IsOpen = true;
this.Size = new Vector2(810, 520);
this.SizeCondition = ImGuiCond.Always;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public override void OnOpen()
{
}
/// <inheritdoc/>
public override void Draw()
{
}
}
}

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Platform>x64</Platform>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<TargetName>Dalamud.Injector</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet>
<OutDir>..\bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>false</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ItemGroup>
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
<Link>nethost.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
<ClCompile Include="..\lib\CoreCLR\pch.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\boot.h" />
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
<ClInclude Include="..\lib\CoreCLR\framework.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="..\lib\CoreCLR\pch.h" />
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\libnethost.lib" />
<Library Include="..\lib\CoreCLR\nethost\nethost.lib" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
<Extensions>ico;rc</Extensions>
</Filter>
<Filter Include="Library Files">
<UniqueIdentifier>{6aff1bed-6979-4bc9-94e8-ddafb626e6bf}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\boot.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\nethost.lib">
<Filter>Library Files</Filter>
</Library>
<Library Include="..\lib\CoreCLR\nethost\libnethost.lib">
<Filter>Library Files</Filter>
</Library>
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -0,0 +1,49 @@
#define WIN32_LEAN_AND_MEAN
#include <filesystem>
#include <Windows.h>
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
int wmain(int argc, char** argv)
{
#if !defined(NDEBUG)
ConsoleSetup(L"Dalamud Injector Boot");
#endif
wchar_t _module_path[MAX_PATH];
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
std::filesystem::path fs_module_path(_module_path);
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
// =========================================================================== //
void* entrypoint_vfn;
int result = InitializeClrAndGetEntryPoint(
runtimeconfig_path,
module_path,
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
L"Main",
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
&entrypoint_vfn);
if (result != 0)
return result;
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, char**);
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
printf("Running Dalamud Injector... ");
entrypoint_fn(argc, argv);
printf("Done!\n");
// =========================================================================== //
#if !defined(NDEBUG)
FreeConsole();
#endif
return 0;
}

View file

@ -0,0 +1 @@
MAINICON ICON "dalamud.ico"

View file

@ -1,60 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net48</TargetFramework>
<LangVersion>8.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>WinExe</OutputType>
<OutputPath>$(SolutionDir)bin</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType>
<NoWarn>IDE1006;CS1701;CS1702</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>$(SolutionDir)\bin\Dalamud.Injector.xml</DocumentationFile>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<PropertyGroup Label="Feature">
<InjectorVersion>5.2.4.6</InjectorVersion>
<Description>XIV Launcher addon injector</Description>
<AssemblyVersion>$(InjectorVersion)</AssemblyVersion>
<FileVersion>$(InjectorVersion)</FileVersion>
<Version>$(InjectorVersion)</Version>
</PropertyGroup>
<PropertyGroup Label="Output">
<OutputType>Library</OutputType>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Label="Documentation">
<DocumentationFile></DocumentationFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Build">
<EnableDynamicLoading>true</EnableDynamicLoading>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Deterministic>true</Deterministic>
<Nullable>annotations</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>5.2.4.6</AssemblyVersion>
<FileVersion>5.2.4.6</FileVersion>
<Description>XIVLauncher addon injection</Description>
<Version>5.2.4.6</Version>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageIcon></PackageIcon>
<PackageIconUrl />
<ApplicationIcon>dalamud.ico</ApplicationIcon>
<PropertyGroup Label="Warnings">
<NoWarn>IDE1006;CS1591;CS1701;CS1702</NoWarn>
<!-- IDE1006 - Naming violation -->
<!-- CS1591 - Missing XML comment for publicly visible type or member -->
<!-- CS1701 - Runtime policy may be needed -->
<!-- CS1702 - Runtime policy may be needed -->
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Iced" Version="1.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="PeNet" Version="2.6.3" />
<PackageReference Include="Reloaded.Memory" Version="4.1.1" />
<PackageReference Include="Reloaded.Memory.Buffers" Version="1.3.5" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DalamudDebugStub\DalamudDebugStub.vcxproj" />
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
<!-- This prevents us from having to include Dalamud itself as a dependency -->
<!-- If the files move just update the paths here -->
<Compile Include="..\Dalamud\ClientLanguage.cs" />
<Compile Include="..\Dalamud\DalamudStartInfo.cs" />
<Compile Include="..\Dalamud\Game\GameVersion.cs" />
<Compile Include="..\Dalamud\Game\GameVersionConverter.cs" />
<Compile Include="..\Dalamud\Interface\Internal\SerilogEventSink.cs" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)'=='Release'">
<Exec Command="powershell -ExecutionPolicy Unrestricted $(SolutionDir)CreateHashList.ps1 $(OutputPath)" />
</Target>
</Project>

View file

@ -0,0 +1,275 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Dalamud.Game;
using Dalamud.Interface.Internal;
using Newtonsoft.Json;
using Reloaded.Memory.Buffers;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using static Dalamud.Injector.NativeFunctions;
namespace Dalamud.Injector
{
/// <summary>
/// Entrypoint to the program.
/// </summary>
public sealed class EntryPoint
{
/// <summary>
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">char** string arguments.</param>
public delegate void MainDelegate(int argc, IntPtr argvPtr);
/// <summary>
/// Start the Dalamud injector.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">byte** string arguments.</param>
public static void Main(int argc, IntPtr argvPtr)
{
InitUnhandledException();
InitLogging();
var args = new string[argc];
unsafe
{
var argv = (IntPtr*)argvPtr;
for (var i = 0; i < argc; i++)
{
args[i] = Marshal.PtrToStringUni(argv[i]);
}
}
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
if (cwd.FullName != Directory.GetCurrentDirectory())
{
Log.Debug($"Changing cwd to {cwd}");
Directory.SetCurrentDirectory(cwd.FullName);
}
var process = GetProcess(args.ElementAtOrDefault(1));
var startInfo = GetStartInfo(args.ElementAtOrDefault(2), process);
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
// This seems to help with the STATUS_INTERNAL_ERROR condition
Thread.Sleep(1000);
Inject(process, startInfo);
Thread.Sleep(1000);
}
private static void InitUnhandledException()
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
if (Log.Logger == null)
{
Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}");
}
else
{
var exObj = eventArgs.ExceptionObject;
if (exObj is Exception ex)
{
Log.Error(ex, "A fatal error has occurred.");
}
else
{
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
}
}
#if DEBUG
var caption = "Debug Error";
var message =
$"Couldn't inject.\nMake sure that Dalamud was not injected into your target process " +
$"as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" +
$"{eventArgs.ExceptionObject}";
#else
var caption = "XIVLauncher Error";
var message =
"Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\n" +
"If this keeps happening, please report this error.";
#endif
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
Environment.Exit(0);
};
}
private static void InitLogging()
{
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
#if DEBUG
var logPath = Path.Combine(baseDirectory, "injector.log");
#else
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.injector.log");
#endif
var levelSwitch = new LoggingLevelSwitch();
#if DEBUG
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
#else
levelSwitch.MinimumLevel = LogEventLevel.Information;
#endif
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(logPath))
.WriteTo.Sink(SerilogEventSink.Instance)
.MinimumLevel.ControlledBy(levelSwitch)
.CreateLogger();
}
private static Process GetProcess(string arg)
{
Process process;
var pid = -1;
if (arg != default)
{
pid = int.Parse(arg);
}
switch (pid)
{
case -1:
process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
if (process == default)
{
throw new Exception("Could not find process");
}
break;
case -2:
var exePath = "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe";
var exeArgs = new StringBuilder()
.Append("DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 ")
.Append("DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 ")
.Append("DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 ")
.Append("DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 ")
.Append("DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 ")
.Append("DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 ")
.Append("DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 ")
.Append("DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 ")
.Append("DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 ")
.Append("SYS.Region=0 language=1 version=1.0.0.0 ")
.Append("DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0").ToString();
process = Process.Start(exePath, exeArgs);
Thread.Sleep(1000);
break;
default:
process = Process.GetProcessById(pid);
break;
}
return process;
}
private static DalamudStartInfo GetStartInfo(string arg, Process process)
{
DalamudStartInfo startInfo;
if (arg != default)
{
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(arg)));
}
else
{
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
var gameVer = GameVersion.Parse(gameVerStr);
startInfo = new DalamudStartInfo
{
WorkingDirectory = null,
ConfigurationPath = Path.Combine(xivlauncherDir, "dalamudConfig.json"),
PluginDirectory = Path.Combine(xivlauncherDir, "installedPlugins"),
DefaultPluginDirectory = Path.Combine(xivlauncherDir, "devPlugins"),
AssetDirectory = Path.Combine(xivlauncherDir, "dalamudAssets"),
GameVersion = gameVer,
Language = ClientLanguage.English,
OptOutMbCollection = false,
};
Log.Debug(
"Creating a new StartInfo with:\n" +
$" WorkingDirectory: {startInfo.WorkingDirectory}\n" +
$" ConfigurationPath: {startInfo.ConfigurationPath}\n" +
$" PluginDirectory: {startInfo.PluginDirectory}\n" +
$" DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
$" AssetDirectory: {startInfo.AssetDirectory}\n" +
$" GameVersion: {startInfo.GameVersion}\n" +
$" Language: {startInfo.Language}\n" +
$" OptOutMbCollection: {startInfo.OptOutMbCollection}");
Log.Information("A Dalamud start info was not found in the program arguments. One has been generated for you.");
Log.Information("Copy the following contents into the program arguments:");
var startInfoJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo)));
Log.Information(startInfoJson);
}
return startInfo;
}
private static void Inject(Process process, DalamudStartInfo startInfo)
{
var nethostName = "nethost.dll";
var bootName = "Dalamud.Boot.dll";
var nethostPath = Path.GetFullPath(nethostName);
var bootPath = Path.GetFullPath(bootName);
// ======================================================
using var injector = new Injector(process);
injector.LoadLibrary(nethostPath, out _);
injector.LoadLibrary(bootPath, out var bootModule);
// ======================================================
var startInfoJson = JsonConvert.SerializeObject(startInfo);
var startInfoBytes = Encoding.UTF8.GetBytes(startInfoJson);
using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8);
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
if (startInfoAddress == IntPtr.Zero)
throw new Exception("Unable to allocate start info JSON");
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
// ======================================================
if (exitCode > 0)
{
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
return;
}
Log.Information("Done");
}
}
}

View file

@ -6,14 +6,8 @@
using System.Diagnostics.CodeAnalysis;
// General
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Preventing long lines", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "I like regions", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "I like regions in elements too", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is annoying", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:Single-line comments should not be followed by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
// Program.cs
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used during #if DEBUG", Scope = "member", Target = "~M:Dalamud.Injector.Program.NativeInject(System.Diagnostics.Process)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "I'll make what I want static", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]

View file

@ -0,0 +1,303 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Iced.Intel;
using PeNet;
using PeNet.Header.Pe;
using Reloaded.Memory.Buffers;
using Reloaded.Memory.Sources;
using Reloaded.Memory.Utilities;
using static Dalamud.Injector.NativeFunctions;
using static Iced.Intel.AssemblerRegisters;
namespace Dalamud.Injector
{
/// <summary>
/// This class implements injecting into a remote process. It is a highly stripped down version of the
/// https://github.com/Reloaded-Project injector/assembler implementation due to issues with Lutris and
/// Wine.
/// </summary>
internal sealed class Injector : IDisposable
{
private readonly Process targetProcess;
private readonly ExternalMemory extMemory;
private readonly CircularBuffer circularBuffer;
private readonly PrivateMemoryBuffer privateBuffer;
private IntPtr loadLibraryShellPtr;
private IntPtr loadLibraryRetPtr;
private IntPtr getProcAddressShellPtr;
private IntPtr getProcAddressRetPtr;
/// <summary>
/// Initializes a new instance of the <see cref="Injector"/> class.
/// </summary>
/// <param name="targetProcess">Process to inject.</param>
public Injector(Process targetProcess)
{
this.targetProcess = targetProcess;
this.extMemory = new ExternalMemory(targetProcess);
this.circularBuffer = new CircularBuffer(4096, this.extMemory);
this.privateBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096);
using var kernel32Module = this.GetProcessModule("KERNEL32.DLL");
var kernel32PeFile = new PeFile(kernel32Module.FileName);
var kernel32Exports = kernel32PeFile.ExportedFunctions;
this.SetupLoadLibrary(kernel32Module, kernel32Exports);
this.SetupGetProcAddress(kernel32Module, kernel32Exports);
}
/// <summary>
/// Finalizes an instance of the <see cref="Injector"/> class.
/// </summary>
~Injector() => this.Dispose();
/// <inheritdoc/>
public void Dispose()
{
GC.SuppressFinalize(this);
this.targetProcess?.Dispose();
this.circularBuffer?.Dispose();
this.privateBuffer?.Dispose();
}
/// <summary>
/// Load a module by absolute file path.
/// </summary>
/// <param name="modulePath">Absolute file path.</param>
/// <param name="address">Address to the module.</param>
public void LoadLibrary(string modulePath, out IntPtr address)
{
var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath);
if (lpParameter == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW parameter");
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
UIntPtr.Zero,
this.loadLibraryShellPtr,
lpParameter,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
this.extMemory.Read(this.loadLibraryRetPtr, out address);
if (address == IntPtr.Zero)
throw new Exception($"Error calling LoadLibraryW with {modulePath}");
}
/// <summary>
/// Get the address of an exported module function.
/// </summary>
/// <param name="module">Module address.</param>
/// <param name="functionName">Name of the exported method.</param>
/// <param name="address">Address to the function.</param>
public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address)
{
var getProcAddressParams = new GetProcAddressParams(module, this.WriteNullTerminatedASCIIString(functionName));
var lpParameter = this.circularBuffer.Add(ref getProcAddressParams);
if (lpParameter == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
UIntPtr.Zero,
this.getProcAddressShellPtr,
lpParameter,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
this.extMemory.Read(this.getProcAddressRetPtr, out address);
if (address == IntPtr.Zero)
throw new Exception($"Error calling GetProcAddress with {functionName}");
}
/// <summary>
/// Call a method in a remote process via CreateRemoteThread.
/// </summary>
/// <param name="methodAddress">Method address.</param>
/// <param name="parameterAddress">Parameter address.</param>
/// <param name="exitCode">Thread exit code.</param>
public void CallRemoteFunction(IntPtr methodAddress, IntPtr parameterAddress, out uint exitCode)
{
// Create and initialize a thread at our address and parameter address.
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
UIntPtr.Zero,
methodAddress,
parameterAddress,
CreateThreadFlags.RunImmediately,
out _);
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
GetExitCodeThread(threadHandle, out exitCode);
}
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
{
var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
var functionAddr = kernel32Module.BaseAddress + (int)offset;
var functionPtr = this.privateBuffer.Add(ref functionAddr);
if (functionPtr == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW function ptr");
var dummy = 0L;
this.loadLibraryRetPtr = this.privateBuffer.Add(ref dummy);
if (this.loadLibraryRetPtr == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW return value");
var func = functionPtr.ToInt64();
var retVal = this.loadLibraryRetPtr.ToInt64();
var asm = new Assembler(64);
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
var bytes = this.Assemble(asm);
this.loadLibraryShellPtr = this.privateBuffer.Add(bytes);
if (this.loadLibraryShellPtr == IntPtr.Zero)
throw new Exception("Unable to allocate LoadLibraryW shellcode");
}
private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
{
var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress");
var functionAddr = kernel32Module.BaseAddress + (int)offset;
var functionPtr = this.privateBuffer.Add(ref functionAddr);
if (functionPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress function ptr");
var dummy = 0L;
this.getProcAddressRetPtr = this.privateBuffer.Add(ref dummy);
if (this.getProcAddressRetPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress return value");
var func = functionPtr.ToInt64();
var retVal = this.getProcAddressRetPtr.ToInt64();
var asm = new Assembler(64);
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space
asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName
asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule
asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] //
asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.ret(); // ret // Restore stack ptr. (Callee cleanup)
var bytes = this.Assemble(asm);
this.getProcAddressShellPtr = this.privateBuffer.Add(bytes);
if (this.getProcAddressShellPtr == IntPtr.Zero)
throw new Exception("Unable to allocate GetProcAddress shellcode");
}
private byte[] Assemble(Assembler assembler)
{
using var stream = new MemoryStream();
assembler.Assemble(new StreamCodeWriter(stream), 0);
stream.Position = 0;
var reader = new StreamCodeReader(stream);
int next;
var bytes = new byte[stream.Length];
while ((next = reader.ReadByte()) >= 0)
{
bytes[stream.Position - 1] = (byte)next;
}
return bytes;
}
private ProcessModule GetProcessModule(string moduleName)
{
var modules = this.targetProcess.Modules;
for (var i = 0; i < modules.Count; i++)
{
var module = modules[i];
if (module.ModuleName.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase))
{
return module;
}
}
throw new Exception($"Failed to find {moduleName} in target process' modules");
}
private uint GetExportedFunctionOffset(ExportFunction[] exportFunctions, string functionName)
{
var exportFunction = exportFunctions.FirstOrDefault(func => func.Name == functionName);
if (exportFunction == default)
throw new Exception($"Failed to find exported function {functionName} in target module's exports");
return exportFunction.Address;
}
private IntPtr WriteNullTerminatedASCIIString(string libraryPath)
{
var libraryNameBytes = Encoding.ASCII.GetBytes(libraryPath + '\0');
var value = this.circularBuffer.Add(libraryNameBytes);
if (value == IntPtr.Zero)
throw new Exception("Unable to write ASCII string to buffer");
return value;
}
private IntPtr WriteNullTerminatedUnicodeString(string libraryPath)
{
var libraryNameBytes = Encoding.Unicode.GetBytes(libraryPath + '\0');
var value = this.circularBuffer.Add(libraryNameBytes);
if (value == IntPtr.Zero)
throw new Exception("Unable to write Unicode string to buffer");
return value;
}
[StructLayout(LayoutKind.Sequential)]
private struct GetProcAddressParams
{
public GetProcAddressParams(IntPtr hModule, IntPtr lPProcName)
{
this.HModule = hModule.ToInt64();
this.LPProcName = lPProcName.ToInt64();
}
public long HModule { get; set; }
public long LPProcName { get; set; }
}
}
}

View file

@ -1,14 +1,234 @@
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
namespace Dalamud.Injector
{
/// <summary>
/// Native functions.
/// Native user32 functions.
/// </summary>
internal static class NativeFunctions
internal static partial class NativeFunctions
{
/// <summary>
/// MB_* from winuser.
/// </summary>
public enum MessageBoxType : uint
{
/// <summary>
/// The default value for any of the various subtypes.
/// </summary>
DefaultValue = 0x0,
// To indicate the buttons displayed in the message box, specify one of the following values.
/// <summary>
/// The message box contains three push buttons: Abort, Retry, and Ignore.
/// </summary>
AbortRetryIgnore = 0x2,
/// <summary>
/// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead
/// of MB_ABORTRETRYIGNORE.
/// </summary>
CancelTryContinue = 0x6,
/// <summary>
/// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends
/// a WM_HELP message to the owner.
/// </summary>
Help = 0x4000,
/// <summary>
/// The message box contains one push button: OK. This is the default.
/// </summary>
Ok = DefaultValue,
/// <summary>
/// The message box contains two push buttons: OK and Cancel.
/// </summary>
OkCancel = 0x1,
/// <summary>
/// The message box contains two push buttons: Retry and Cancel.
/// </summary>
RetryCancel = 0x5,
/// <summary>
/// The message box contains two push buttons: Yes and No.
/// </summary>
YesNo = 0x4,
/// <summary>
/// The message box contains three push buttons: Yes, No, and Cancel.
/// </summary>
YesNoCancel = 0x3,
// To display an icon in the message box, specify one of the following values.
/// <summary>
/// An exclamation-point icon appears in the message box.
/// </summary>
IconExclamation = 0x30,
/// <summary>
/// An exclamation-point icon appears in the message box.
/// </summary>
IconWarning = IconExclamation,
/// <summary>
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
/// </summary>
IconInformation = 0x40,
/// <summary>
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
/// </summary>
IconAsterisk = IconInformation,
/// <summary>
/// A question-mark icon appears in the message box.
/// The question-mark message icon is no longer recommended because it does not clearly represent a specific type
/// of message and because the phrasing of a message as a question could apply to any message type. In addition,
/// users can confuse the message symbol question mark with Help information. Therefore, do not use this question
/// mark message symbol in your message boxes. The system continues to support its inclusion only for backward
/// compatibility.
/// </summary>
IconQuestion = 0x20,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconStop = 0x10,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconError = IconStop,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconHand = IconStop,
// To indicate the default button, specify one of the following values.
/// <summary>
/// The first button is the default button.
/// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified.
/// </summary>
DefButton1 = DefaultValue,
/// <summary>
/// The second button is the default button.
/// </summary>
DefButton2 = 0x100,
/// <summary>
/// The third button is the default button.
/// </summary>
DefButton3 = 0x200,
/// <summary>
/// The fourth button is the default button.
/// </summary>
DefButton4 = 0x300,
// To indicate the modality of the dialog box, specify one of the following values.
/// <summary>
/// The user must respond to the message box before continuing work in the window identified by the hWnd parameter.
/// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy
/// of windows in the application, the user may be able to move to other windows within the thread. All child windows
/// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the
/// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified.
/// </summary>
ApplModal = DefaultValue,
/// <summary>
/// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style.
/// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate
/// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with
/// windows other than those associated with hWnd.
/// </summary>
SystemModal = 0x1000,
/// <summary>
/// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the
/// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle
/// available but still needs to prevent input to other windows in the calling thread without suspending other threads.
/// </summary>
TaskModal = 0x2000,
// To specify other options, use one or more of the following values.
/// <summary>
/// Same as desktop of the interactive window station. For more information, see Window Stations. If the current
/// input desktop is not the default desktop, MessageBox does not return until the user switches to the default
/// desktop.
/// </summary>
DefaultDesktopOnly = 0x20000,
/// <summary>
/// The text is right-justified.
/// </summary>
Right = 0x80000,
/// <summary>
/// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
/// </summary>
RtlReading = 0x100000,
/// <summary>
/// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function
/// for the message box.
/// </summary>
SetForeground = 0x10000,
/// <summary>
/// The message box is created with the WS_EX_TOPMOST window style.
/// </summary>
Topmost = 0x40000,
/// <summary>
/// The caller is a service notifying the user of an event. The function displays a message box on the current active
/// desktop, even if there is no user logged on to the computer.
/// </summary>
ServiceNotification = 0x200000,
}
/// <summary>
/// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message,
/// such as status or error information. The message box returns an integer value that indicates which button the user
/// clicked.
/// </summary>
/// <param name="hWnd">
/// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no
/// owner window.
/// </param>
/// <param name="text">
/// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage
/// return and/or linefeed character between each line.
/// </param>
/// <param name="caption">
/// The dialog box title. If this parameter is NULL, the default title is Error.</param>
/// <param name="type">
/// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups
/// of flags.
/// </param>
/// <returns>
/// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or
/// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an
/// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK.
/// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function
/// succeeds, the return value is one of the ID* enum values.
/// </returns>
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type);
}
/// <summary>
/// Native kernel32 functions.
/// </summary>
internal static partial class NativeFunctions
{
/// <summary>
/// MEM_* from memoryapi.
@ -20,14 +240,14 @@ namespace Dalamud.Injector
/// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce
/// placeholders, lpAddress and dwSize must exactly match those of the placeholder.
/// </summary>
CoalescePlaceholders = 0x00000001,
CoalescePlaceholders = 0x1,
/// <summary>
/// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using
/// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify
/// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER.
/// </summary>
PreservePlaceholder = 0x00000002,
PreservePlaceholder = 0x2,
/// <summary>
/// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved
@ -88,7 +308,7 @@ namespace Dalamud.Injector
/// the specified address range is intact. If the function fails, at least some of the data in the address range
/// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on
/// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
/// protection value, such as PAGE_NOACCESS.
/// </summary>
ResetUndo = 0x1000000,
@ -122,6 +342,28 @@ namespace Dalamud.Injector
LargePages = 0x20000000,
}
/// <summary>
/// Unprefixed flags from CreateRemoteThread.
/// </summary>
[Flags]
public enum CreateThreadFlags
{
/// <summary>
/// The thread runs immediately after creation.
/// </summary>
RunImmediately = 0x0,
/// <summary>
/// The thread is created in a suspended state, and does not run until the ResumeThread function is called.
/// </summary>
CreateSuspended = 0x4,
/// <summary>
/// The dwStackSize parameter specifies the initial reserve size of the stack. If this flag is not specified, dwStackSize specifies the commit size.
/// </summary>
StackSizeParamIsReservation = 0x10000,
}
/// <summary>
/// PAGE_* from memoryapi.
/// </summary>
@ -198,7 +440,7 @@ namespace Dalamud.Injector
/// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
/// targets for CFG.
/// </summary>
TargetsNoUpdate = 0x40000000,
TargetsNoUpdate = TargetsInvalid,
/// <summary>
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a
@ -312,23 +554,33 @@ namespace Dalamud.Injector
}
/// <summary>
/// Closes an open object handle.
/// WAIT_* from synchapi.
/// </summary>
/// <param name="hObject">
/// A valid handle to an open object.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.To get extended
/// error information, call GetLastError. If the application is running under a debugger, the function will throw an
/// exception if it receives either a handle value that is not valid or a pseudo-handle value. This can happen if you
/// close a handle twice, or if you call CloseHandle on a handle returned by the FindFirstFile function instead of calling
/// the FindClose function.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
public enum WaitResult
{
/// <summary>
/// The specified object is a mutex object that was not released by the thread that owned the mutex object
/// before the owning thread terminated.Ownership of the mutex object is granted to the calling thread and
/// the mutex state is set to nonsignaled. If the mutex was protecting persistent state information, you
/// should check it for consistency.
/// </summary>
Abandoned = 0x80,
/// <summary>
/// The state of the specified object is signaled.
/// </summary>
Object0 = 0x0,
/// <summary>
/// The time-out interval elapsed, and the object's state is nonsignaled.
/// </summary>
Timeout = 0x102,
/// <summary>
/// The function has failed. To get extended error information, call GetLastError.
/// </summary>
WAIT_FAILED = 0xFFFFFFF,
}
/// <summary>
/// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function
@ -336,23 +588,23 @@ namespace Dalamud.Injector
/// </summary>
/// <param name="hProcess">
/// A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD,
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail
/// without these rights on certain platforms. For more information, see Process Security and Access Rights.
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without
/// these rights on certain platforms. For more information, see Process Security and Access Rights.
/// </param>
/// <param name="lpThreadAttributes">
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines
/// whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default
/// security descriptor and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor
/// for a thread come from the primary token of the creator.
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether
/// child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor
/// and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from
/// the primary token of the creator.
/// </param>
/// <param name="dwStackSize">
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is
/// 0 (zero), the new thread uses the default size for the executable. For more information, see Thread Stack Size.
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the
/// new thread uses the default size for the executable. For more information, see Thread Stack Size.
/// </param>
/// <param name="lpStartAddress">
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and
/// represents the starting address of the thread in the remote process. The function must exist in the remote process.
/// For more information, see ThreadProc.
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the
/// starting address of the thread in the remote process. The function must exist in the remote process. For more information,
/// see ThreadProc.
/// </param>
/// <param name="lpParameter">
/// A pointer to a variable to be passed to the thread function.
@ -361,92 +613,43 @@ namespace Dalamud.Injector
/// The flags that control the creation of the thread.
/// </param>
/// <param name="lpThreadId">
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is
/// not returned.
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
/// </param>
/// <returns>
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value
/// is NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if
/// lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs,
/// an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as
/// an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where
/// the process is created even if it refers to invalid or missing dynamic-link libraries (DLL).
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is
/// NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress
/// points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and
/// the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process.
/// This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to
/// invalid or missing dynamic-link libraries (DLL).
/// </returns>
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
UIntPtr dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
IntPtr lpThreadId);
CreateThreadFlags dwCreationFlags,
out uint lpThreadId);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew.
/// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To
/// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function.
/// Retrieves the termination status of the specified thread.
/// </summary>
/// <param name="lpModuleName">
/// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default library
/// extension .dll is appended. The file name string can include a trailing point character (.) to indicate that the
/// module name has no extension. The string does not have to specify a path. When specifying a path, be sure to use
/// backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules currently
/// mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns a handle
/// to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve handles
/// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
/// <param name="hThread">
/// A handle to the thread. The handle must have the THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION
/// access right.For more information, see Thread Security and Access Rights.
/// </param>
/// <param name="lpExitCode">
/// A pointer to a variable to receive the thread termination status.
/// </param>
/// <returns>
/// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return
/// value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
/// <summary>
/// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
/// </summary>
/// <param name="hModule">
/// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary,
/// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules
/// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx.
/// </param>
/// <param name="procName">
/// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be
/// in the low-order word; the high-order word must be zero.
/// </param>
/// <returns>
/// If the function succeeds, the return value is the address of the exported function or variable. If the function
/// fails, the return value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess.
/// Opens an existing local process object.
/// </summary>
/// <param name="processAccess">
/// The access to the process object. This access right is checked against the security descriptor for the process.
/// This parameter can be one or more of the process access rights. If the caller has enabled the SeDebugPrivilege
/// privilege, the requested access is granted regardless of the contents of the security descriptor.
/// </param>
/// <param name="bInheritHandle">
/// If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do
/// not inherit this handle.
/// </param>
/// <param name="processId">
/// The identifier of the local process to be opened.
/// </param>
/// <returns>
/// If the function succeeds, the return value is an open handle to the specified process. If the function fails, the
/// return value is NULL.To get extended error information, call GetLastError.
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get
/// extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.
@ -530,6 +733,27 @@ namespace Dalamud.Injector
int dwSize,
AllocationType dwFreeType);
/// <summary>
/// Waits until the specified object is in the signaled state or the time-out interval elapses. To enter an alertable wait
/// state, use the WaitForSingleObjectEx function.To wait for multiple objects, use WaitForMultipleObjects.
/// </summary>
/// <param name="hHandle">
/// A handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.
/// If this handle is closed while the wait is still pending, the function's behavior is undefined. The handle must have the
/// SYNCHRONIZE access right. For more information, see Standard Access Rights.
/// </param>
/// <param name="dwMilliseconds">
/// The time-out interval, in milliseconds. If a nonzero value is specified, the function waits until the object is signaled
/// or the interval elapses. If dwMilliseconds is zero, the function does not enter a wait state if the object is not signaled;
/// it always returns immediately. If dwMilliseconds is INFINITE, the function will return only when the object is signaled.
/// </param>
/// <returns>
/// If the function succeeds, the return value indicates the event that caused the function to return.
/// It can be one of the WaitResult values.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
/// <summary>
/// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or
/// the operation fails.

View file

@ -1,208 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using EasyHook;
using Newtonsoft.Json;
namespace Dalamud.Injector
{
/// <summary>
/// Application entrypoint.
/// </summary>
internal static class Program
{
private static Process process = null;
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
File.WriteAllText("InjectorException.txt", eventArgs.ExceptionObject.ToString());
#if !DEBUG
MessageBox.Show("Failed to inject the XIVLauncher in-game addon.\nPlease try restarting your game and your PC.\nIf this keeps happening, please report this error.", "XIVLauncher Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
#else
MessageBox.Show("Couldn't inject.\nMake sure that Dalamud was not injected into your target process as a release build before and that the target process can be accessed with VM_WRITE permissions.\n\n" + eventArgs.ExceptionObject, "Debug Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
#endif
Environment.Exit(0);
};
var pid = -1;
if (args.Length >= 1)
{
pid = int.Parse(args[0]);
}
switch (pid)
{
case -1:
process = Process.GetProcessesByName("ffxiv_dx11")[0];
break;
case -2:
process = Process.Start(
"C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe",
"DEV.TestSID=0 DEV.UseSqPack=1 DEV.DataPathType=1 DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 SYS.Region=0 language=1 version=1.0.0.0 DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0");
Thread.Sleep(1000);
break;
default:
process = Process.GetProcessById(pid);
break;
}
DalamudStartInfo startInfo;
if (args.Length <= 1)
{
startInfo = GetDefaultStartInfo();
Console.WriteLine("\nA Dalamud start info was not found in the program arguments. One has been generated for you.");
Console.WriteLine("\nCopy the following contents into the program arguments:");
Console.WriteLine();
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(startInfo))));
}
else
{
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[1])));
}
startInfo.WorkingDirectory = Directory.GetCurrentDirectory();
// Seems to help with the STATUS_INTERNAL_ERROR condition
Thread.Sleep(1000);
// Thread.Sleep(10000);
// Inject to process
Inject(process, startInfo);
Thread.Sleep(1000);
#if DEBUG
// Inject exception handler
// NativeInject(process);
#endif
}
private static void Inject(Process process, DalamudStartInfo info)
{
Console.WriteLine($"Injecting to {process.Id}");
// File check
var libPath = Path.GetFullPath("Dalamud.dll");
if (!File.Exists(libPath))
{
Console.WriteLine($"Can't find a dll on {libPath}");
return;
}
RemoteHooking.Inject(process.Id, InjectionOptions.DoNotRequireStrongName, libPath, libPath, info);
Console.WriteLine("Injected");
}
private static void NativeInject(Process process)
{
var libPath = Path.GetFullPath("DalamudDebugStub.dll");
var pathBytes = Encoding.Unicode.GetBytes(libPath);
var len = pathBytes.Length + 1;
Console.WriteLine($"Injecting {libPath}...");
var handle = NativeFunctions.OpenProcess(
NativeFunctions.ProcessAccessFlags.AllAccess,
false,
process.Id);
if (handle == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not OpenProcess");
}
var dllMem = NativeFunctions.VirtualAllocEx(
handle,
IntPtr.Zero,
len,
NativeFunctions.AllocationType.Commit,
NativeFunctions.MemoryProtection.ReadWrite);
if (dllMem == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not alloc memory {Marshal.GetLastWin32Error():X}");
}
Console.WriteLine($"dll path at {dllMem.ToInt64():X}");
if (!NativeFunctions.WriteProcessMemory(
handle,
dllMem,
pathBytes,
len,
out var bytesWritten))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not write DLL");
}
Console.WriteLine($"Wrote {bytesWritten}");
var kernel32 = NativeFunctions.GetModuleHandle("Kernel32.dll");
var loadLibA = NativeFunctions.GetProcAddress(kernel32, "LoadLibraryW");
var remoteThread = NativeFunctions.CreateRemoteThread(
handle,
IntPtr.Zero,
0,
loadLibA,
dllMem,
0,
IntPtr.Zero);
if (remoteThread == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not CreateRemoteThread");
}
/*
TODO kill myself
VirtualFreeEx(
handle,
dllMem,
0,
AllocationType.Release);
*/
NativeFunctions.CloseHandle(remoteThread);
NativeFunctions.CloseHandle(handle);
}
private static DalamudStartInfo GetDefaultStartInfo()
{
var ffxivDir = Path.GetDirectoryName(process.MainModule.FileName);
var startInfo = new DalamudStartInfo
{
WorkingDirectory = null,
ConfigurationPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "dalamudConfig.json"),
PluginDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "installedPlugins"),
DefaultPluginDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "devPlugins"),
AssetDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "dalamudAssets"),
GameVersion = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver")),
Language = ClientLanguage.English,
};
Console.WriteLine("Creating a StartInfo with:\n" +
$"ConfigurationPath: {startInfo.ConfigurationPath}\n" +
$"PluginDirectory: {startInfo.PluginDirectory}\n" +
$"DefaultPluginDirectory: {startInfo.DefaultPluginDirectory}\n" +
$"Language: {startInfo.Language}\n" +
$"GameVersion: {startInfo.GameVersion}\n" +
$"OptOutMbCollection: {startInfo.OptOutMbCollection}\n" +
$"AssetDirectory: {startInfo.AssetDirectory}");
return startInfo;
}
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using static Dalamud.Injector.NativeFunctions;
namespace Dalamud.Injector
{
/// <summary>
/// Pin an arbitrary string to a remote process.
/// </summary>
internal class RemotePinnedData : IDisposable
{
private readonly Process process;
private readonly byte[] data;
private readonly IntPtr allocAddr;
/// <summary>
/// Initializes a new instance of the <see cref="RemotePinnedData"/> class.
/// </summary>
/// <param name="process">Process to write in.</param>
/// <param name="data">Data to write.</param>
public unsafe RemotePinnedData(Process process, byte[] data)
{
this.process = process;
this.data = data;
this.allocAddr = VirtualAllocEx(
this.process.Handle,
IntPtr.Zero,
this.data.Length,
AllocationType.Commit,
MemoryProtection.ReadWrite);
if (this.allocAddr == IntPtr.Zero || Marshal.GetLastWin32Error() != 0)
{
throw new Exception("Error allocating memory");
}
var result = WriteProcessMemory(
this.process.Handle,
this.allocAddr,
this.data,
this.data.Length,
out _);
if (!result || Marshal.GetLastWin32Error() != 0)
{
throw new Exception("Error writing memory");
}
}
/// <summary>
/// Gets the address of the pinned data.
/// </summary>
public IntPtr Address => this.allocAddr;
/// <inheritdoc/>
public void Dispose()
{
if (this.allocAddr == IntPtr.Zero)
{
return;
}
var result = VirtualFreeEx(
this.process.Handle,
this.allocAddr,
0,
AllocationType.Release);
if (!result || Marshal.GetLastWin32Error() != 0)
{
throw new Exception("Error freeing memory");
}
}
}
}

View file

@ -1,96 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props" Condition="Exists('..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props')" />
<Import Project="..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props')" />
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C8004563-1806-4329-844F-0EF6274291FC}</ProjectGuid>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dalamud.Test</RootNamespace>
<AssemblyName>Dalamud.Test</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c">
<HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c">
<HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c">
<HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c">
<HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="LocalizationTests.cs" />
<Compile Include="Game\Text\Sanitizer\SanitizerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud\Dalamud.csproj">
<Project>{b92dab43-2279-4a2c-96e3-d9d5910edbea}</Project>
<Name>Dalamud</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net5.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props'))" />
<Error Condition="!Exists('..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props'))" />
</Target>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
<PropertyGroup Label="Feature">
<RootNamespace>Dalamud.Test</RootNamespace>
<AssemblyTitle>Dalamud.Test</AssemblyTitle>
<AssemblyName>Dalamud.Test</AssemblyName>
<Product>Dalamud.Test</Product>
<Description>Unit tests for Dalamud</Description>
<Company>goatcorp</Company>
<Copyright>Copyright © goatcorp 2021</Copyright>
</PropertyGroup>
<PropertyGroup Label="Output">
<OutputType>Library</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View file

@ -0,0 +1,58 @@
using Dalamud.Game;
using Xunit;
namespace Dalamud.Test.Game
{
public class GameVersionTests
{
[Theory]
[InlineData("any", "any")]
[InlineData("2021.01.01.0000.0000", "2021.01.01.0000.0000")]
public void VersionEquality(string ver1, string ver2)
{
var v1 = GameVersion.Parse(ver1);
var v2 = GameVersion.Parse(ver2);
Assert.Equal(v1, v2);
}
[Theory]
[InlineData("2020.06.15.0000.0000", "any")]
[InlineData("2021.01.01.0000.0000", "2021.01.01.0000.0001")]
[InlineData("2021.01.01.0000.0000", "2021.01.01.0001.0000")]
[InlineData("2021.01.01.0000.0000", "2021.01.02.0000.0000")]
[InlineData("2021.01.01.0000.0000", "2021.02.01.0000.0000")]
[InlineData("2021.01.01.0000.0000", "2022.01.01.0000.0000")]
public void VersionComparison(string ver1, string ver2)
{
var v1 = GameVersion.Parse(ver1);
var v2 = GameVersion.Parse(ver2);
Assert.True(v1.CompareTo(v2) < 0);
}
[Theory]
[InlineData("2020.06.15.0000.0000")]
[InlineData("2021.01.01.0000")]
[InlineData("2021.01.01")]
[InlineData("2021.01")]
[InlineData("2021")]
public void VersionConstructor(string ver)
{
var v = GameVersion.Parse(ver);
Assert.True(v != null);
}
[Theory]
[InlineData("2020.06.15.0000.0000.0000")]
[InlineData("")]
public void VersionConstructorInvalid(string ver)
{
var result = GameVersion.TryParse(ver, out var v);
Assert.False(result);
Assert.Null(v);
}
}
}

View file

@ -1,29 +1,29 @@
// ReSharper disable StringLiteralTypo
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Dalamud.Test.Game.Text.Sanitizer {
public class SanitizerTests {
// ReSharper disable StringLiteralTypo
namespace Dalamud.Test.Game.Text.Sanitizer
{
public class SanitizerTests
{
private global::Dalamud.Game.Text.Sanitizer.Sanitizer sanitizer;
[Theory]
[InlineData( ClientLanguage.English, "Pixie Cotton Hood of Healing", "Pixie Cotton Hood of Healing" )]
[InlineData( ClientLanguage.Japanese, "アラガントームストーン:真理", "アラガントームストーン:真理" )]
[InlineData( ClientLanguage.German, "Anemos-Pan\x02\x16\x01\x03zer\x02\x16\x01\x03hand\x02\x16\x01\x03schu\x02\x16\x01\x03he des Drachenbluts", "Anemos-Panzerhandschuhe des Drachenbluts" )]
[InlineData( ClientLanguage.German, "Bienen-Spatha †", "Bienen-Spatha" )]
[InlineData( ClientLanguage.French, "Le Diademe\x02\x1D\x01\x03: terrains de chasse|Le Diademe\x02\x1D\x01\x03: terrains de chasse", "Le Diademe: terrains de chasse|Le Diademe: terrains de chasse" )]
[InlineData( ClientLanguage.French, "Cuir de bœuf", "Cuir de boeuf" )]
public void StringsAreSanitizedCorrectly(
ClientLanguage clientLanguage, string unsanitizedString, string sanitizedString)
[InlineData(ClientLanguage.English, "Pixie Cotton Hood of Healing", "Pixie Cotton Hood of Healing")]
[InlineData(ClientLanguage.Japanese, "アラガントームストーン:真理", "アラガントームストーン:真理")]
[InlineData(ClientLanguage.German, "Anemos-Pan\x02\x16\x01\x03zer\x02\x16\x01\x03hand\x02\x16\x01\x03schu\x02\x16\x01\x03he des Drachenbluts", "Anemos-Panzerhandschuhe des Drachenbluts")]
[InlineData(ClientLanguage.German, "Bienen-Spatha †", "Bienen-Spatha")]
[InlineData(ClientLanguage.French, "Le Diademe\x02\x1D\x01\x03: terrains de chasse|Le Diademe\x02\x1D\x01\x03: terrains de chasse", "Le Diademe: terrains de chasse|Le Diademe: terrains de chasse")]
[InlineData(ClientLanguage.French, "Cuir de bœuf", "Cuir de boeuf")]
public void StringsAreSanitizedCorrectly(ClientLanguage clientLanguage, string unsanitizedString, string sanitizedString)
{
var sanitizedStrings = new List<string> {unsanitizedString};
var sanitizedStrings = new List<string> { unsanitizedString };
sanitizer = new global::Dalamud.Game.Text.Sanitizer.Sanitizer(clientLanguage);
Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString));
Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings).First());
sanitizer = new global::Dalamud.Game.Text.Sanitizer.Sanitizer(ClientLanguage.English);
Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString, clientLanguage));
Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings, clientLanguage).First());

View file

@ -0,0 +1,20 @@
using System.Linq;
using Dalamud.Game.Text.SeStringHandling;
using Xunit;
namespace Dalamud.Test.Game.Text.SeStringHandling
{
public class SeStringManagerTests
{
[Fact]
public void TestNewLinePayload()
{
var manager = new SeStringManager(null);
var newLinePayloadBytes = new byte[] {0x02, 0x10, 0x01, 0x03};
var seString = manager.Parse(newLinePayloadBytes);
Assert.True(newLinePayloadBytes.SequenceEqual(seString.Encode()));
}
}
}

View file

@ -1,20 +1,24 @@
using System.IO;
using System.IO;
using System.Reflection;
using Xunit;
namespace Dalamud.Test {
public class LocalizationTests {
namespace Dalamud.Test
{
public class LocalizationTests
{
private readonly Localization localization;
private string currentLangCode;
public LocalizationTests() {
public LocalizationTests()
{
var workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
this.localization = new Localization(workingDir, "dalamud_");
this.localization.OnLocalizationChanged += code => this.currentLangCode = code;
}
[Fact]
public void SetupWithFallbacks_EventInvoked() {
public void SetupWithFallbacks_EventInvoked()
{
this.localization.SetupWithFallbacks();
Assert.Equal("en", this.currentLangCode);
}

View file

@ -1,35 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Dalamud.Test")]
[assembly: AssemblyDescription("Unit tests for Dalamud")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("goatcorp")]
[assembly: AssemblyProduct("Dalamud.Test")]
[assembly: AssemblyCopyright("Copyright © goatcorp 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("C8004563-1806-4329-844F-0EF6274291FC")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.4.1" targetFramework="net472" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net472" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net472" />
<package id="xunit.assert" version="2.4.1" targetFramework="net472" />
<package id="xunit.core" version="2.4.1" targetFramework="net472" />
<package id="xunit.extensibility.core" version="2.4.1" targetFramework="net472" />
<package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net472" />
<package id="xunit.runner.console" version="2.4.1" targetFramework="net472" developmentDependency="true" />
<package id="xunit.runner.visualstudio" version="2.4.3" targetFramework="net472" developmentDependency="true" />
</packages>

View file

@ -1,137 +1,163 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29215.179
VisualStudioVersion = 16.0.31424.327
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CEF7D22B-CB85-400E-BD64-349A30E3C097}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.csproj", "{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boot\Dalamud.Boot.vcxproj", "{55198DC3-A03D-408E-A8EB-2077780C8576}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{85480198-8711-4355-830E-72FD794AD3F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudDebugStub", "DalamudDebugStub\DalamudDebugStub.vcxproj", "{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\FFXIVClientStructs\FFXIVClientStructs.csproj", "{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj", "{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Common", "lib\FFXIVClientStructs\FFXIVClientStructs.Common\FFXIVClientStructs.Common.csproj", "{F3F0CC3A-DE2E-403F-88B4-B47C62582477}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Generators", "lib\FFXIVClientStructs\FFXIVClientStructs.Generators\FFXIVClientStructs.Generators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.ActiveCfg = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.Build.0 = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.ActiveCfg = Release|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.Build.0 = Release|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.ActiveCfg = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.Build.0 = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.ActiveCfg = Debug|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.Build.0 = Debug|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.ActiveCfg = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.Build.0 = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.ActiveCfg = Release|Any CPU
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.Build.0 = Release|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.ActiveCfg = Debug|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.Build.0 = Debug|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.ActiveCfg = Debug|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.Build.0 = Debug|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.ActiveCfg = Release|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.Build.0 = Release|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.ActiveCfg = Release|x64
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.Build.0 = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.ActiveCfg = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.Build.0 = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.ActiveCfg = Debug|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.Build.0 = Debug|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.ActiveCfg = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.Build.0 = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.ActiveCfg = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.Build.0 = Debug|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.ActiveCfg = Release|Any CPU
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.Build.0 = Release|Any CPU
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.ActiveCfg = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.Build.0 = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.ActiveCfg = Debug|x86
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.Build.0 = Debug|x86
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.Build.0 = Release|Any CPU
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.ActiveCfg = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.Build.0 = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.ActiveCfg = Release|x86
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.Build.0 = Release|x86
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.ActiveCfg = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.Build.0 = Debug|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.ActiveCfg = Release|Any CPU
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.Build.0 = Release|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|Any CPU.ActiveCfg = Debug|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|x64.ActiveCfg = Debug|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|x86.ActiveCfg = Debug|Win32
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|Any CPU.ActiveCfg = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|Any CPU.Build.0 = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x64.ActiveCfg = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x64.Build.0 = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x86.ActiveCfg = Release|Win32
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x86.Build.0 = Release|Win32
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x64.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x86.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x86.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|Any CPU.Build.0 = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x64.ActiveCfg = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x64.Build.0 = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x86.ActiveCfg = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x86.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.ActiveCfg = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.ActiveCfg = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.Build.0 = Release|Any CPU
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.ActiveCfg = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.Build.0 = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.ActiveCfg = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.Build.0 = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.ActiveCfg = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.ActiveCfg = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.Build.0 = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.ActiveCfg = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.Build.0 = Release|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.ActiveCfg = Debug|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.Build.0 = Debug|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.Build.0 = Release|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.ActiveCfg = Release|Any CPU
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.Build.0 = Release|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.Build.0 = Debug|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.ActiveCfg = Release|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.Build.0 = Release|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|x64.ActiveCfg = Debug|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|x64.Build.0 = Debug|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|Any CPU.Build.0 = Release|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|x64.ActiveCfg = Release|Any CPU
{F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|x64.Build.0 = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.ActiveCfg = Debug|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.Build.0 = Debug|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.Build.0 = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.ActiveCfg = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{85480198-8711-4355-830E-72FD794AD3F6} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{F3F0CC3A-DE2E-403F-88B4-B47C62582477} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{05AB2F46-268B-4915-806F-DDF813E2D59D} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}

View file

@ -5,20 +5,21 @@ using System.IO;
using Dalamud.Game.Text;
using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
namespace Dalamud.Configuration
namespace Dalamud.Configuration.Internal
{
/// <summary>
/// Class containing Dalamud settings.
/// </summary>
[Serializable]
internal class DalamudConfiguration
internal sealed class DalamudConfiguration
{
[JsonIgnore]
private string configPath;
/// <summary>
/// Delegate for the <see cref="DalamudConfiguration.OnDalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
/// Delegate for the <see cref="OnDalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
/// </summary>
/// <param name="dalamudConfiguration">The current dalamud configuration.</param>
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
@ -68,15 +69,28 @@ namespace Dalamud.Configuration
/// </summary>
public bool DoDalamudTest { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime.
/// </summary>
public bool DoDalamudRuntime { get; set; }
/// <summary>
/// Gets or sets a list of custom repos.
/// </summary>
public List<ThirdRepoSetting> ThirdRepoList { get; set; } = new List<ThirdRepoSetting>();
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
/// <summary>
/// Gets or sets a list of hidden plugins.
/// </summary>
public List<string> HiddenPluginInternalName { get; set; } = new List<string>();
public List<string> HiddenPluginInternalName { get; set; } = new();
/// <summary>
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
/// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder.
/// However by specifiying this value manually, you can add arbitrary files outside the normal
/// file paths.
/// </summary>
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
/// <summary>
/// Gets or sets the global UI scale.
@ -113,6 +127,11 @@ namespace Dalamud.Configuration
/// </summary>
public bool DoButtonsSystemMenu { get; set; } = true;
/// <summary>
/// Gets or sets the default Dalamud debug log level on startup.
/// </summary>
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
/// <summary>
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
/// </summary>
@ -138,6 +157,21 @@ namespace Dalamud.Configuration
/// </summary>
public bool IsGamepadNavigationEnabled { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
/// </summary>
public bool IsAntiAntiDebugEnabled { get; set; } = false;
/// <summary>
/// Gets or sets the kind of beta to download when <see cref="DoDalamudTest"/> is set to true.
/// </summary>
public string DalamudBetaKind { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded.
/// </summary>
public bool LoadAllApiLevels { get; set; }
/// <summary>
/// Load a configuration from the provided path.
/// </summary>

View file

@ -0,0 +1,18 @@
namespace Dalamud.Configuration.Internal
{
/// <summary>
/// Settings for DevPlugins.
/// </summary>
internal sealed class DevPluginSettings
{
/// <summary>
/// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up.
/// </summary>
public bool StartOnBoot { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
/// </summary>
public bool AutomaticReloading { get; set; } = false;
}
}

View file

@ -3,7 +3,7 @@ namespace Dalamud.Configuration
/// <summary>
/// Third party repository for dalamud plugins.
/// </summary>
internal class ThirdRepoSetting
internal sealed class ThirdPartyRepoSettings
{
/// <summary>
/// Gets or sets the third party repo url.
@ -16,16 +16,14 @@ namespace Dalamud.Configuration
public bool IsEnabled { get; set; }
/// <summary>
/// Create new instance of third party repo object.
/// Gets or sets a short name for the repo url.
/// </summary>
/// <returns>New instance of third party repo.</returns>
public ThirdRepoSetting Clone()
{
return new ThirdRepoSetting
{
Url = this.Url,
IsEnabled = this.IsEnabled,
};
}
public string Name { get; set; }
/// <summary>
/// Clone this object.
/// </summary>
/// <returns>A shallow copy of this object.</returns>
public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
}
}

View file

@ -1,5 +1,6 @@
using System.IO;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace Dalamud.Configuration
@ -7,7 +8,7 @@ namespace Dalamud.Configuration
/// <summary>
/// Configuration to store settings for a dalamud plugin.
/// </summary>
public class PluginConfigurations
public sealed class PluginConfigurations
{
private readonly DirectoryInfo configDirectory;
@ -44,6 +45,7 @@ namespace Dalamud.Configuration
/// </summary>
/// <param name="pluginName">Plugin name.</param>
/// <returns>Plugin configuration.</returns>
[CanBeNull]
public IPluginConfiguration Load(string pluginName)
{
var path = this.GetConfigFile(pluginName);
@ -60,6 +62,22 @@ namespace Dalamud.Configuration
});
}
/// <summary>
/// Delete the configuration file and folder for the specified plugin.
/// This will throw an <see cref="IOException"/> if the plugin did not correctly close its handles.
/// </summary>
/// <param name="pluginName">The name of the plugin.</param>
public void Delete(string pluginName)
{
var directory = this.GetDirectoryPath(pluginName);
if (directory.Exists)
directory.Delete(true);
var file = this.GetConfigFile(pluginName);
if (file.Exists)
file.Delete();
}
/// <summary>
/// Get plugin directory.
/// </summary>

View file

@ -1,10 +1,10 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.Addon;
@ -13,17 +13,24 @@ using Dalamud.Game.Command;
using Dalamud.Game.Internal;
using Dalamud.Game.Network;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Plugin;
using Dalamud.Hooking.Internal;
using Dalamud.Interface.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Internal;
using Serilog;
using Serilog.Core;
#if DEBUG
// This allows for rapid prototyping of Dalamud modules with access to internal objects.
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
#endif
namespace Dalamud
{
/// <summary>
/// The main Dalamud class containing all subsystems.
/// </summary>
public sealed class Dalamud : IDisposable
internal sealed class Dalamud : IDisposable
{
#region Internals
@ -31,8 +38,6 @@ namespace Dalamud
private readonly ManualResetEvent finishUnloadSignal;
private readonly string baseDirectory;
private bool hasDisposedPlugins = false;
#endregion
@ -43,12 +48,17 @@ namespace Dalamud
/// <param name="info">DalamudStartInfo instance.</param>
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
/// <param name="finishSignal">Signal signalling shutdown.</param>
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal)
/// <param name="configuration">The Dalamud configuration.</param>
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration)
{
#if DEBUG
Instance = this;
#endif
this.StartInfo = info;
this.LogLevelSwitch = loggingLevelSwitch;
this.Configuration = configuration;
this.baseDirectory = info.WorkingDirectory;
// this.baseDirectory = info.WorkingDirectory;
this.unloadSignal = new ManualResetEvent(false);
this.unloadSignal.Reset();
@ -57,6 +67,13 @@ namespace Dalamud
this.finishUnloadSignal.Reset();
}
#if DEBUG
/// <summary>
/// Gets the Dalamud singleton instance.
/// </summary>
internal static Dalamud Instance { get; private set; }
#endif
#region Native Game Subsystems
/// <summary>
@ -74,6 +91,11 @@ namespace Dalamud
/// </summary>
internal WinSockHandlers WinSock2 { get; private set; }
/// <summary>
/// Gets Hook management subsystem.
/// </summary>
internal HookManager HookManager { get; private set; }
/// <summary>
/// Gets ImGui Interface subsystem.
/// </summary>
@ -93,11 +115,6 @@ namespace Dalamud
/// </summary>
internal PluginManager PluginManager { get; private set; }
/// <summary>
/// Gets Plugin Repository subsystem.
/// </summary>
internal PluginRepository PluginRepository { get; private set; }
/// <summary>
/// Gets Data provider subsystem.
/// </summary>
@ -203,6 +220,7 @@ namespace Dalamud
// Initialize the process information.
this.TargetModule = Process.GetCurrentProcess().MainModule;
this.SigScanner = new SigScanner(this.TargetModule, true);
this.HookManager = new HookManager(this);
// Initialize game subsystem
this.Framework = new Framework(this.SigScanner, this);
@ -211,6 +229,10 @@ namespace Dalamud
this.Framework.Enable();
Log.Information("[T1] Framework ENABLE!");
// Initialize FFXIVClientStructs function resolver
FFXIVClientStructs.Resolver.Initialize();
Log.Information("[T1] FFXIVClientStructs initialized!");
}
catch (Exception ex)
{
@ -226,11 +248,12 @@ namespace Dalamud
{
try
{
this.Configuration = DalamudConfiguration.Load(this.StartInfo.ConfigurationPath);
this.AntiDebug = new AntiDebug(this.SigScanner);
if (this.Configuration.IsAntiAntiDebugEnabled)
this.AntiDebug.Enable();
#if DEBUG
this.AntiDebug.Enable();
if (!this.AntiDebug.IsEnabled)
this.AntiDebug.Enable();
#endif
Log.Information("[T2] AntiDebug OK!");
@ -286,6 +309,7 @@ namespace Dalamud
Log.Information("[T2] Data OK!");
this.SeStringManager = new SeStringManager(this.Data);
MemoryHelper.Initialize(this); // For SeString handling
Log.Information("[T2] SeString OK!");
@ -324,28 +348,21 @@ namespace Dalamud
{
Log.Information("[T3] START!");
this.PluginRepository =
new PluginRepository(this, this.StartInfo.PluginDirectory, this.StartInfo.GameVersion);
Log.Information("[T3] PREPO OK!");
if (!bool.Parse(Environment.GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS") ?? "false"))
{
try
{
this.PluginRepository.CleanupPlugins();
Log.Information("[T3] PRC OK!");
this.PluginManager = new PluginManager(
this,
this.StartInfo.PluginDirectory,
this.StartInfo.DefaultPluginDirectory);
this.PluginManager.LoadSynchronousPlugins();
Task.Run(() => this.PluginManager.LoadDeferredPlugins());
this.PluginManager = new PluginManager(this);
this.PluginManager.OnInstalledPluginsChanged += () =>
Troubleshooting.LogTroubleshooting(this, this.InterfaceManager.IsReady);
Log.Information("[T3] PM OK!");
this.PluginManager.CleanupPlugins();
Log.Information("[T3] PMC OK!");
this.PluginManager.LoadAllPlugins();
Log.Information("[T3] PML OK!");
}
catch (Exception ex)
{
@ -354,11 +371,9 @@ namespace Dalamud
}
this.DalamudUi = new DalamudInterface(this);
this.InterfaceManager.OnDraw += this.DalamudUi.Draw;
Log.Information("[T3] DUI OK!");
Troubleshooting.LogTroubleshooting(this, this.InterfaceManager != null);
Troubleshooting.LogTroubleshooting(this, this.InterfaceManager.IsReady);
Log.Information("Dalamud is ready.");
}
@ -407,16 +422,9 @@ namespace Dalamud
// use any resources that it freed in its own Dispose method
this.InterfaceManager?.Dispose();
try
{
this.PluginManager.UnloadPlugins();
}
catch (Exception ex)
{
Log.Error(ex, "Plugin unload failed.");
}
this.DalamudUi?.Dispose();
this.PluginManager?.Dispose();
}
/// <summary>
@ -433,20 +441,23 @@ namespace Dalamud
}
this.Framework?.Dispose();
this.ClientState?.Dispose();
this.unloadSignal?.Dispose();
this.WinSock2?.Dispose();
this.SigScanner?.Dispose();
this.Data?.Dispose();
this.AntiDebug?.Dispose();
this.SystemMenu?.Dispose();
this.HookManager?.Dispose();
this.SigScanner?.Dispose();
Log.Debug("Dalamud::Dispose() OK!");
}
catch (Exception ex)

View file

@ -1,105 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net472</TargetFramework>
<TargetFramework>net5.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>9.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>Library</OutputType>
<OutputPath></OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Feature">
<DalamudVersion>5.2.6.1</DalamudVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DalamudVersion>6.0.0.0</DalamudVersion>
<Description>XIV Launcher addon framework</Description>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion>
</PropertyGroup>
<ItemGroup Label="Resources">
<None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
<PropertyGroup Label="Output">
<OutputType>Library</OutputType>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Label="Documentation">
<DocumentationFile>$(OutputPath)Dalamud.xml</DocumentationFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Build">
<UseWindowsForms>true</UseWindowsForms>
<EnableDynamicLoading>true</EnableDynamicLoading>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Deterministic>true</Deterministic>
<Nullable>annotations</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PropertyGroup Label="Configuration">
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<NoWarn>IDE0017;IDE0044;IDE0047;IDE0048;IDE1006;CS1573;CS1591;CS1701;CS1702</NoWarn>
<!-- IDE0017 - Use object initializers -->
<!-- IDE0044 - Add readonly modifier -->
<!-- IDE0047 - Parentheses preferences -->
<!-- IDE0048 - Parentheses preferences -->
<!-- IDE1006 - Naming preferences -->
<!-- CS1573 - Parameter has no matching param tag in the XML comment -->
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
</PropertyGroup>
<PropertyGroup Label="Warnings">
<NoWarn>IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702</NoWarn>
<!-- IDE1006 - Naming violation -->
<!-- CS1591 - Missing XML comment for publicly visible type or member -->
<!-- CS1701 - Runtime policy may be needed -->
<!-- CS1702 - Runtime policy may be needed -->
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Lumina.Generated.dll" />
<None Remove="stylecop.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CheapLoc" Version="1.1.3" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="Lumina" Version="3.1.0" />
<PackageReference Include="CheapLoc" Version="1.1.5" />
<PackageReference Include="CoreHook" Version="1.0.4" />
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
<PackageReference Include="Lib.Harmony" Version="2.1.0" />
<PackageReference Include="Lumina" Version="3.3.0" />
<PackageReference Include="Lumina.Excel" Version="5.50.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="PropertyChanged.Fody" Version="2.6.1" />
<PackageReference Include="Serilog" Version="2.6.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="SharpDX.Desktop" Version="4.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="5.0.1" />
<PackageReference Include="System.Resources.Extensions" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Lumina.Generated.dll">
<None Include="corehook64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Lumina.Generated.dll</TargetPath>
</ContentWithTargetPath>
</None>
</ItemGroup>
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
<ItemGroup>
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
<ContentWithTargetPath Include="$(ProjectRuntimeConfigFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectRuntimeConfigFileName)" />
</ItemGroup>
</Target>
<Target Name="GetGitHash" BeforeTargets="WriteGitHash" Condition="'$(BuildHash)' == ''">
<PropertyGroup>
<!-- temp file for the git version (lives in "obj" folder)-->

View file

@ -1,12 +1,15 @@
using System;
using Dalamud.Game;
using Newtonsoft.Json;
namespace Dalamud
{
/// <summary>
/// Class containing information needed to initialize Dalamud.
/// Struct containing information needed to initialize Dalamud.
/// </summary>
[Serializable]
public sealed class DalamudStartInfo
public struct DalamudStartInfo
{
/// <summary>
/// The working directory of the XIVLauncher installations.
@ -41,7 +44,8 @@ namespace Dalamud
/// <summary>
/// The current game version code.
/// </summary>
public string GameVersion;
[JsonConverter(typeof(GameVersionConverter))]
public GameVersion GameVersion;
/// <summary>
/// Whether or not market board information should be uploaded by default.

View file

@ -7,6 +7,7 @@ using System.Threading;
using Dalamud.Data.LuminaExtensions;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using ImGuiScene;
using JetBrains.Annotations;
using Lumina;
@ -21,7 +22,7 @@ namespace Dalamud.Data
/// <summary>
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
/// </summary>
public class DataManager : IDisposable
public sealed class DataManager : IDisposable
{
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
private readonly InterfaceManager interfaceManager;
@ -32,6 +33,7 @@ namespace Dalamud.Data
private GameData gameData;
private Thread luminaResourceThread;
private CancellationTokenSource luminaCancellationTokenSource;
/// <summary>
/// Initializes a new instance of the <see cref="DataManager"/> class.
@ -41,8 +43,9 @@ namespace Dalamud.Data
internal DataManager(ClientLanguage language, InterfaceManager interfaceManager)
{
this.interfaceManager = interfaceManager;
// Set up default values so plugins do not null-reference when data is being loaded.
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
this.Language = language;
}
@ -99,7 +102,7 @@ namespace Dalamud.Data
ClientLanguage.English => Lumina.Data.Language.English,
ClientLanguage.German => Lumina.Data.Language.German,
ClientLanguage.French => Lumina.Data.Language.French,
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
_ => throw new ArgumentOutOfRangeException(nameof(language), $"Unknown Language: {language}"),
};
return this.Excel.GetSheet<T>(lang);
}
@ -162,7 +165,7 @@ namespace Dalamud.Data
ClientLanguage.English => "en/",
ClientLanguage.German => "de/",
ClientLanguage.French => "fr/",
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
};
return this.GetIcon(type, iconId);
@ -232,7 +235,7 @@ namespace Dalamud.Data
/// </summary>
public void Dispose()
{
this.luminaResourceThread.Abort();
this.luminaCancellationTokenSource.Cancel();
}
/// <summary>
@ -245,14 +248,14 @@ namespace Dalamud.Data
{
Log.Verbose("Starting data load...");
var zoneOpCodeDict =
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict =
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
@ -273,9 +276,7 @@ namespace Dalamud.Data
ClientLanguage.English => Lumina.Data.Language.English,
ClientLanguage.German => Lumina.Data.Language.German,
ClientLanguage.French => Lumina.Data.Language.French,
_ => throw new ArgumentOutOfRangeException(
nameof(this.Language),
@"Unknown Language: " + this.Language),
_ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
},
};
@ -289,9 +290,12 @@ namespace Dalamud.Data
this.IsDataReady = true;
this.luminaResourceThread = new Thread(() =>
this.luminaCancellationTokenSource = new();
var luminaCancellationToken = this.luminaCancellationTokenSource.Token;
this.luminaResourceThread = new(() =>
{
while (true)
while (!luminaCancellationToken.IsCancellationRequested)
{
if (this.gameData.FileHandleManager.HasPendingFileLoads)
{
@ -302,8 +306,6 @@ namespace Dalamud.Data
Thread.Sleep(5);
}
}
// ReSharper disable once FunctionNeverReturns
});
this.luminaResourceThread.Start();
}

View file

@ -1,11 +1,13 @@
using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface;
using EasyHook;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Internal;
using Newtonsoft.Json;
using Serilog;
using Serilog.Core;
using Serilog.Events;
@ -15,28 +17,41 @@ namespace Dalamud
/// <summary>
/// The main entrypoint for the Dalamud system.
/// </summary>
public sealed class EntryPoint : IEntryPoint
public sealed class EntryPoint
{
/// <summary>
/// Initializes a new instance of the <see cref="EntryPoint"/> class.
/// A delegate used during initialization of the CLR from Dalamud.Boot.
/// </summary>
/// <param name="ctx">The <see cref="RemoteHooking.IContext"/> used to load the DLL.</param>
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
public EntryPoint(RemoteHooking.IContext ctx, DalamudStartInfo info)
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
public delegate void InitDelegate(IntPtr infoPtr);
/// <summary>
/// Initialize Dalamud.
/// </summary>
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
public static void Initialize(IntPtr infoPtr)
{
// Required by EasyHook
var infoStr = Marshal.PtrToStringAnsi(infoPtr);
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
new Thread(() => RunThread(info)).Start();
}
/// <summary>
/// Initialize all Dalamud subsystems and start running on the main thread.
/// </summary>
/// <param name="ctx">The <see cref="RemoteHooking.IContext"/> used to load the DLL.</param>
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
public void Run(RemoteHooking.IContext ctx, DalamudStartInfo info)
private static void RunThread(DalamudStartInfo info)
{
// Load configuration first to get some early persistent state, like log level
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
// Setup logger
var (logger, levelSwitch) = this.NewLogger(info.WorkingDirectory);
Log.Logger = logger;
var levelSwitch = InitLogging(info.WorkingDirectory, configuration);
// Log any unhandled exception.
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
var finishSignal = new ManualResetEvent(false);
@ -46,14 +61,9 @@ namespace Dalamud
Log.Information("Initializing a session..");
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
System.Net.ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
// Log any unhandled exception.
AppDomain.CurrentDomain.UnhandledException += this.OnUnhandledException;
TaskScheduler.UnobservedTaskException += this.OnUnobservedTaskException;
var dalamud = new Dalamud(info, levelSwitch, finishSignal);
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration);
Log.Information("Starting a session..");
// Run session
@ -68,7 +78,8 @@ namespace Dalamud
}
finally
{
AppDomain.CurrentDomain.UnhandledException -= this.OnUnhandledException;
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
Log.Information("Session has ended.");
Log.CloseAndFlush();
@ -77,7 +88,7 @@ namespace Dalamud
}
}
private (Logger Logger, LoggingLevelSwitch LevelSwitch) NewLogger(string baseDirectory)
private static LoggingLevelSwitch InitLogging(string baseDirectory, DalamudConfiguration configuration)
{
#if DEBUG
var logPath = Path.Combine(baseDirectory, "dalamud.log");
@ -90,35 +101,34 @@ namespace Dalamud
#if DEBUG
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
#else
levelSwitch.MinimumLevel = LogEventLevel.Information;
levelSwitch.MinimumLevel = configuration.LogLevel;
#endif
var newLogger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(logPath))
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File(logPath, fileSizeLimitBytes: 5 * 1024 * 1024, rollOnFileSizeLimit: true))
.WriteTo.Sink(SerilogEventSink.Instance)
.MinimumLevel.ControlledBy(levelSwitch)
.CreateLogger();
return (newLogger, levelSwitch);
return levelSwitch;
}
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs arg)
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
switch (arg.ExceptionObject)
switch (args.ExceptionObject)
{
case Exception ex:
Log.Fatal(ex, "Unhandled exception on AppDomain");
break;
default:
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", arg.ExceptionObject);
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
break;
}
}
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
{
if (!e.Observed)
Log.Error(e.Exception, "Unobserved exception in Task.");
if (!args.Observed)
Log.Error(args.Exception, "Unobserved exception in Task.");
}
}
}

View file

@ -11,6 +11,16 @@
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
@ -31,6 +41,16 @@
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>

View file

@ -16,11 +16,11 @@ namespace Dalamud.Game.Addon
internal sealed unsafe partial class DalamudSystemMenu
{
private readonly Dalamud dalamud;
private AtkValueChangeType atkValueChangeType;
private AtkValueSetString atkValueSetString;
private Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
private readonly AtkValueChangeType atkValueChangeType;
private readonly AtkValueSetString atkValueSetString;
private readonly Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
// TODO: Make this into events in Framework.Gui
private Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
private readonly Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
/// <summary>
/// Initializes a new instance of the <see cref="DalamudSystemMenu"/> class.
@ -34,13 +34,10 @@ namespace Dalamud.Game.Addon
this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
var atkValueChangeTypeAddress =
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??");
this.atkValueChangeType =
Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
var atkValueChangeTypeAddress = this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??");
this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
var atkValueSetStringAddress =
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
var atkValueSetStringAddress = this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
var uiModuleRequestMainCommandAddress = this.dalamud.SigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
@ -140,10 +137,10 @@ namespace Dalamud.Game.Addon
switch (commandId)
{
case 69420:
this.dalamud.DalamudUi.TogglePluginInstaller();
this.dalamud.DalamudUi.TogglePluginInstallerWindow();
break;
case 69421:
this.dalamud.DalamudUi.ToggleSettings();
this.dalamud.DalamudUi.ToggleSettingsWindow();
break;
default:
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
@ -11,7 +10,6 @@ using CheapLoc;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Serilog;
namespace Dalamud.Game
@ -21,39 +19,39 @@ namespace Dalamud.Game
/// </summary>
public class ChatHandlers
{
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
{
{ "", "<:ffxive071:585847382210642069>" },
{ "", "<:ffxive083:585848592699490329>" },
};
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
// {
// { "", "<:ffxive071:585847382210642069>" },
// { "", "<:ffxive083:585848592699490329>" },
// };
private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
{
{ XivChatType.CrossParty, Color.DodgerBlue },
{ XivChatType.Party, Color.DodgerBlue },
{ XivChatType.FreeCompany, Color.DeepSkyBlue },
{ XivChatType.CrossLinkShell1, Color.ForestGreen },
{ XivChatType.CrossLinkShell2, Color.ForestGreen },
{ XivChatType.CrossLinkShell3, Color.ForestGreen },
{ XivChatType.CrossLinkShell4, Color.ForestGreen },
{ XivChatType.CrossLinkShell5, Color.ForestGreen },
{ XivChatType.CrossLinkShell6, Color.ForestGreen },
{ XivChatType.CrossLinkShell7, Color.ForestGreen },
{ XivChatType.CrossLinkShell8, Color.ForestGreen },
{ XivChatType.Ls1, Color.ForestGreen },
{ XivChatType.Ls2, Color.ForestGreen },
{ XivChatType.Ls3, Color.ForestGreen },
{ XivChatType.Ls4, Color.ForestGreen },
{ XivChatType.Ls5, Color.ForestGreen },
{ XivChatType.Ls6, Color.ForestGreen },
{ XivChatType.Ls7, Color.ForestGreen },
{ XivChatType.Ls8, Color.ForestGreen },
{ XivChatType.TellIncoming, Color.HotPink },
{ XivChatType.PvPTeam, Color.SandyBrown },
{ XivChatType.Urgent, Color.DarkViolet },
{ XivChatType.NoviceNetwork, Color.SaddleBrown },
{ XivChatType.Echo, Color.Gray },
};
// private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
// {
// { XivChatType.CrossParty, Color.DodgerBlue },
// { XivChatType.Party, Color.DodgerBlue },
// { XivChatType.FreeCompany, Color.DeepSkyBlue },
// { XivChatType.CrossLinkShell1, Color.ForestGreen },
// { XivChatType.CrossLinkShell2, Color.ForestGreen },
// { XivChatType.CrossLinkShell3, Color.ForestGreen },
// { XivChatType.CrossLinkShell4, Color.ForestGreen },
// { XivChatType.CrossLinkShell5, Color.ForestGreen },
// { XivChatType.CrossLinkShell6, Color.ForestGreen },
// { XivChatType.CrossLinkShell7, Color.ForestGreen },
// { XivChatType.CrossLinkShell8, Color.ForestGreen },
// { XivChatType.Ls1, Color.ForestGreen },
// { XivChatType.Ls2, Color.ForestGreen },
// { XivChatType.Ls3, Color.ForestGreen },
// { XivChatType.Ls4, Color.ForestGreen },
// { XivChatType.Ls5, Color.ForestGreen },
// { XivChatType.Ls6, Color.ForestGreen },
// { XivChatType.Ls7, Color.ForestGreen },
// { XivChatType.Ls8, Color.ForestGreen },
// { XivChatType.TellIncoming, Color.HotPink },
// { XivChatType.PvPTeam, Color.SandyBrown },
// { XivChatType.Urgent, Color.DarkViolet },
// { XivChatType.NoviceNetwork, Color.SaddleBrown },
// { XivChatType.Echo, Color.Gray },
// };
private readonly Regex rmtRegex = new(
@"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5オ|Off Code( *):|offers Fantasia",
@ -96,14 +94,14 @@ namespace Dalamud.Game
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
private readonly Dalamud dalamud;
private DalamudLinkPayload openInstallerWindowLink;
private readonly DalamudLinkPayload openInstallerWindowLink;
private bool hasSeenLoadingMsg;
/// <summary>
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
/// </summary>
/// <param name="dalamud">Dalamud instance.</param>
public ChatHandlers(Dalamud dalamud)
internal ChatHandlers(Dalamud dalamud)
{
this.dalamud = dalamud;
@ -122,23 +120,20 @@ namespace Dalamud.Game
public string LastLink { get; private set; }
/// <summary>
/// Convert a string to SeString and wrap in italics payloads.
/// Convert a TextPayload to SeString and wrap in italics payloads.
/// </summary>
/// <param name="text">Text to convert.</param>
/// <returns>SeString payload of italicized text.</returns>
private static SeString MakeItalics(string text)
{
// TODO: when the code OnCharMessage is switched to SeString, this can be a straight insertion of the
// italics payloads only, and be a lot cleaner
var italicString = new SeString(new List<Payload>(new Payload[]
{
EmphasisItalicPayload.ItalicsOn,
new TextPayload(text),
EmphasisItalicPayload.ItalicsOff,
}));
public static SeString MakeItalics(string text)
=> MakeItalics(new TextPayload(text));
return italicString;
}
/// <summary>
/// Convert a TextPayload to SeString and wrap in italics payloads.
/// </summary>
/// <param name="text">Text to convert.</param>
/// <returns>SeString payload of italicized text.</returns>
public static SeString MakeItalics(TextPayload text)
=> new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
{
@ -243,13 +238,13 @@ namespace Dalamud.Game
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.Plugins.Count));
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), this.dalamud.PluginManager.InstalledPlugins.Count));
if (this.dalamud.Configuration.PrintPluginsWelcomeMsg)
{
foreach (var plugin in this.dalamud.PluginManager.Plugins.OrderBy(x => x.Plugin.Name))
foreach (var plugin in this.dalamud.PluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name))
{
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Plugin.Name, plugin.Definition.AssemblyVersion));
this.dalamud.Framework.Gui.Chat.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
}
}
@ -257,20 +252,19 @@ namespace Dalamud.Game
{
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
{
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog.")),
Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."),
Type = XivChatType.Notice,
});
if (DalamudChangelogWindow.WarrantsChangelog)
#pragma warning disable CS0162 // Unreachable code detected
this.dalamud.DalamudUi.OpenChangelog();
#pragma warning restore CS0162 // Unreachable code detected
if (this.dalamud.DalamudUi.WarrantsChangelog)
this.dalamud.DalamudUi.OpenChangelogWindow();
this.dalamud.Configuration.LastVersion = assemblyVersion;
this.dalamud.Configuration.Save();
}
Task.Run(() => this.dalamud.PluginRepository.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins)).ContinueWith(t =>
Task.Run(() => this.dalamud.PluginManager.UpdatePlugins(!this.dalamud.Configuration.AutoUpdatePlugins))
.ContinueWith(t =>
{
if (t.IsFaulted)
{
@ -278,19 +272,19 @@ namespace Dalamud.Game
}
else
{
var updatedPlugins = t.Result.UpdatedPlugins;
var updatedPlugins = t.Result;
if (updatedPlugins != null && updatedPlugins.Any())
{
if (this.dalamud.Configuration.AutoUpdatePlugins)
{
this.dalamud.PluginRepository.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
this.dalamud.PluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
}
else
{
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry
{
MessageBytes = new SeString(new List<Payload>()
Message = new SeString(new List<Payload>()
{
new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
new TextPayload(" ["),
@ -300,7 +294,7 @@ namespace Dalamud.Game
RawPayload.LinkTerminator,
new UIForegroundPayload(this.dalamud.Data, 0),
new TextPayload("]"),
}).Encode(),
}),
Type = XivChatType.Urgent,
});
}

View file

@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
@ -14,204 +12,131 @@ namespace Dalamud.Game.ClientState.Actors
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
public sealed partial class ActorTable
{
private const int ActorTableLength = 424;
#region ReadProcessMemory Hack
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
private static readonly IntPtr ActorMem = Marshal.AllocHGlobal(ActorMemSize);
private static readonly IntPtr CurrentProcessHandle = new(-1);
#endregion
private Dalamud dalamud;
private ClientStateAddressResolver address;
private List<Actor> actorsCache;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="ActorTable"/> class.
/// Set up the actor table collection.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
this.address = addressResolver;
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}");
}
/// <summary>
/// Gets the amount of currently spawned actors.
/// </summary>
public int Length => this.ActorsCache.Count;
public int Length
{
get
{
var count = 0;
for (var i = 0; i < ActorTableLength; i++)
{
var ptr = this.GetActorAddress(i);
if (ptr != IntPtr.Zero)
{
count++;
}
}
private List<Actor> ActorsCache => this.actorsCache ??= this.GetActorTable();
return count;
}
}
/// <summary>
/// Get an actor at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
/// <returns>An <see cref="Actor"/> at the specified spawn index.</returns>
[CanBeNull]
public Actor this[int index] => this.ActorsCache[index];
public Actor this[int index]
{
get
{
var address = this.GetActorAddress(index);
return this.CreateActorReference(address);
}
}
/// <summary>
/// Read an actor struct from memory and create the appropriate <see cref="ObjectKind"/> type of actor.
/// Gets the address of the actor at the specified index of the actor table.
/// </summary>
/// <param name="offset">Offset of the actor in the actor table.</param>
/// <returns>An instantiated actor.</returns>
internal Actor ReadActorFromMemory(IntPtr offset)
/// <param name="index">The index of the actor.</param>
/// <returns>The memory address of the actor.</returns>
public unsafe IntPtr GetActorAddress(int index)
{
try
{
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
{
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
return null;
}
if (index >= ActorTableLength)
return IntPtr.Zero;
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(ActorMem);
return *(IntPtr*)(this.address.ActorTable + (8 * index));
}
return actorStruct.ObjectKind switch
{
ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud),
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud),
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud),
_ => new Actor(offset, actorStruct, this.dalamud),
};
}
catch (Exception e)
{
Log.Error(e, "Could not read actor from memory.");
/// <summary>
/// Create a reference to a FFXIV actor.
/// </summary>
/// <param name="address">The address of the actor in memory.</param>
/// <returns><see cref="Actor"/> object or inheritor containing requested data.</returns>
[CanBeNull]
public unsafe Actor CreateActorReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
}
}
private void ResetCache() => this.actorsCache = null;
if (address == IntPtr.Zero)
return null;
private void Framework_OnUpdateEvent(Internal.Framework framework)
{
this.ResetCache();
}
private IntPtr[] GetPointerTable()
{
var ret = new IntPtr[ActorTableLength];
Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
return ret;
}
private List<Actor> GetActorTable()
{
var actors = new List<Actor>();
var ptrTable = this.GetPointerTable();
for (var i = 0; i < ActorTableLength; i++)
var objKind = *(ObjectKind*)(address + ActorOffsets.ObjectKind);
return objKind switch
{
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
}
return actors;
ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new Actor(address, this.dalamud),
};
}
}
/// <summary>
/// Implementing IDisposable.
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable : IDisposable
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection
{
private bool disposed = false;
/// <summary>
/// Finalizes an instance of the <see cref="ActorTable"/> class.
/// </summary>
~ActorTable() => this.Dispose(false);
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (this.disposed)
return;
if (disposing)
{
this.dalamud.Framework.OnUpdateEvent -= this.Framework_OnUpdateEvent;
Marshal.FreeHGlobal(ActorMem);
}
this.disposed = true;
}
}
/// <summary>
/// Implementing IReadOnlyCollection, IEnumerable, and Enumerable.
/// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>
{
/// <summary>
/// Gets the number of elements in the collection.
/// </summary>
/// <returns>The number of elements in the collection.</returns>
/// <inheritdoc/>
int IReadOnlyCollection<Actor>.Count => this.Length;
/// <summary>
/// Gets an enumerator capable of iterating through the actor table.
/// </summary>
/// <returns>An actor enumerable.</returns>
public IEnumerator<Actor> GetEnumerator() => this.ActorsCache.Where(a => a != null).GetEnumerator();
/// <summary>
/// Gets an enumerator capable of iterating through the actor table.
/// </summary>
/// <returns>An actor enumerable.</returns>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
/// <summary>
/// Implementing ICollection.
/// </summary>
public sealed partial class ActorTable : ICollection
{
/// <summary>
/// Gets the number of elements in the collection.
/// </summary>
/// <returns>The number of elements in the collection.</returns>
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <summary>
/// Gets a value indicating whether access to the collection is synchronized (thread safe).
/// </summary>
/// <returns>Whether access is synchronized (thread safe) or not.</returns>
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <summary>
/// Gets an object that can be used to synchronize access to the collection.
/// </summary>
/// <returns>An object that can be used to synchronize access to the collection.</returns>
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <summary>
/// Copies the elements of the collection to an array, starting at a particular index.
/// </summary>
/// <param name="array">
/// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing.
/// </param>
/// <param name="index">
/// The zero-based index in array at which copying begins.
/// </param>
/// <inheritdoc/>
public IEnumerator<Actor> GetEnumerator()
{
for (var i = 0; i < ActorTableLength; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)

View file

@ -1,24 +0,0 @@
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
/// <summary>
/// Base object resolver.
/// </summary>
public abstract class BaseResolver
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="BaseResolver"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
public BaseResolver(Dalamud dalamud)
{
this.dalamud = dalamud;
}
/// <summary>
/// Gets the Dalamud instance.
/// </summary>
protected Dalamud Dalamud => this.dalamud;
}
}

View file

@ -1,31 +0,0 @@
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
/// <summary>
/// This object represents a class or job.
/// </summary>
public class ClassJob : BaseResolver
{
/// <summary>
/// ID of the ClassJob.
/// </summary>
public readonly uint Id;
/// <summary>
/// Initializes a new instance of the <see cref="ClassJob"/> class.
/// Set up the ClassJob resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the classJob.</param>
/// <param name="dalamud">The Dalamud instance.</param>
public ClassJob(byte id, Dalamud dalamud)
: base(dalamud)
{
this.Id = id;
}
/// <summary>
/// Gets GameData linked to this ClassJob.
/// </summary>
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
}
}

View file

@ -1,31 +0,0 @@
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
/// <summary>
/// This object represents a world a character can reside on.
/// </summary>
public class World : BaseResolver
{
/// <summary>
/// ID of the world.
/// </summary>
public readonly uint Id;
/// <summary>
/// Initializes a new instance of the <see cref="World"/> class.
/// Set up the world resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the world.</param>
/// <param name="dalamud">The Dalamud instance.</param>
public World(ushort id, Dalamud dalamud)
: base(dalamud)
{
this.Id = id;
}
/// <summary>
/// Gets GameData linked to this world.
/// </summary>
public Lumina.Excel.GeneratedSheets.World GameData =>
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
}
}

View file

@ -2,6 +2,7 @@ using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Actors
{
@ -10,8 +11,8 @@ namespace Dalamud.Game.ClientState.Actors
/// </summary>
public sealed class Targets
{
private Dalamud dalamud;
private ClientStateAddressResolver address;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
@ -27,26 +28,31 @@ namespace Dalamud.Game.ClientState.Actors
/// <summary>
/// Gets the current target.
/// </summary>
[CanBeNull]
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
/// <summary>
/// Gets the mouseover target.
/// </summary>
[CanBeNull]
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
/// <summary>
/// Gets the focus target.
/// </summary>
[CanBeNull]
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
/// <summary>
/// Gets the previous target.
/// </summary>
[CanBeNull]
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
/// <summary>
/// Gets the soft target.
/// </summary>
[CanBeNull]
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
/// <summary>
@ -91,6 +97,7 @@ namespace Dalamud.Game.ClientState.Actors
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
}
[CanBeNull]
private Actor GetActorByOffset(int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
@ -100,7 +107,7 @@ namespace Dalamud.Game.ClientState.Actors
if (actorAddress == IntPtr.Zero)
return null;
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
return this.dalamud.ClientState.Actors.CreateActorReference(actorAddress);
}
}

View file

@ -1,102 +1,165 @@
using System;
using System.Text;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Structs;
using Serilog;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a basic FFXIV actor.
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public class Actor : IEquatable<Actor>
public unsafe partial class Actor : IEquatable<Actor>
{
private readonly Structs.Actor actorStruct;
private readonly Dalamud dalamud;
private string name;
/// <summary>
/// Initializes a new instance of the <see cref="Actor"/> class.
/// This represents a basic FFXIV actor.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
public Actor(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Actor(IntPtr address, Dalamud dalamud)
{
this.actorStruct = actorStruct;
this.dalamud = dalamud;
this.Dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the position of this <see cref="Actor" />.
/// Gets the address of the actor in memory.
/// </summary>
public Position3 Position => this.ActorStruct.Position;
public IntPtr Address { get; }
/// <summary>
/// Gets the rotation of this <see cref="Actor" />.
/// This ranges from -pi to pi radians.
/// Gets Dalamud itself.
/// </summary>
public float Rotation => this.ActorStruct.Rotation;
private protected Dalamud Dalamud { get; }
/// <summary>
/// This allows you to <c>if (actor) {...}</c> to check for validity.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static implicit operator bool(Actor actor) => IsValid(actor);
public static bool operator ==(Actor actor1, Actor actor2)
{
if (actor1 is null || actor2 is null)
return Equals(actor1, actor2);
return actor1.Equals(actor2);
}
public static bool operator !=(Actor actor1, Actor actor2) => !(actor1 == actor2);
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Actor actor)
{
if (actor == null)
return false;
if (actor.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<Actor>.Equals(Actor other) => this.ActorId == other?.ActorId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<Actor>)this).Equals(obj as Actor);
/// <inheritdoc/>
public override int GetHashCode() => this.ActorId.GetHashCode();
}
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class Actor
{
/// <summary>
/// Gets the displayname of this <see cref="Actor" />.
/// </summary>
public string Name => this.name ??= Util.GetUTF8String(this.actorStruct.Name);
public SeString Name => MemoryHelper.ReadSeString(this.Address + ActorOffsets.Name, 32);
/// <summary>
/// Gets the actor ID of this <see cref="Actor" />.
/// </summary>
public int ActorId => this.ActorStruct.ActorId;
public uint ActorId => *(uint*)(this.Address + ActorOffsets.ActorId);
/// <summary>
/// Gets the hitbox radius of this <see cref="Actor" />.
/// Gets the data ID for linking to other respective game data.
/// </summary>
public float HitboxRadius => this.ActorStruct.HitboxRadius;
public uint DataId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => *(uint*)(this.Address + ActorOffsets.OwnerId);
/// <summary>
/// Gets the entity kind of this <see cref="Actor" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind => this.ActorStruct.ObjectKind;
public ObjectKind ObjectKind => *(ObjectKind*)(this.Address + ActorOffsets.ObjectKind);
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => *(byte*)(this.Address + ActorOffsets.SubKind);
/// <summary>
/// Gets a value indicating whether the actor is friendly.
/// </summary>
public bool IsFriendly => *(int*)(this.Address + ActorOffsets.IsFriendly) > 0;
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => this.ActorStruct.YalmDistanceFromPlayerX;
public byte YalmDistanceX => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectX);
/// <summary>
/// Gets the target status.
/// </summary>
/// <remarks>
/// This is some kind of enum. It may be <see cref="StatusEffect"/>.
/// </remarks>
public byte TargetStatus => *(byte*)(this.Address + ActorOffsets.TargetStatus);
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceY => this.ActorStruct.YalmDistanceFromPlayerY;
public byte YalmDistanceY => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectY);
/// <summary>
/// Gets the target of the actor.
/// Gets the position of this <see cref="Actor" />.
/// </summary>
public virtual int TargetActorID => 0;
public Position3 Position => *(Position3*)(this.Address + ActorOffsets.Position);
/// <summary>
/// Gets status Effects.
/// Gets the rotation of this <see cref="Actor" />.
/// This ranges from -pi to pi radians.
/// </summary>
public StatusEffect[] StatusEffects => this.ActorStruct.UIStatusEffects;
public float Rotation => *(float*)(this.Address + ActorOffsets.Rotation);
/// <summary>
/// Gets the address of this actor in memory.
/// Gets the hitbox radius of this <see cref="Actor" />.
/// </summary>
public readonly IntPtr Address;
public float HitboxRadius => *(float*)(this.Address + ActorOffsets.HitboxRadius);
/// <summary>
/// Gets the memory representation of the base actor.
/// Gets the current target of the Actor.
/// </summary>
internal Structs.Actor ActorStruct => this.actorStruct;
/// <summary>
/// Gets the <see cref="Dalamud"/> backing instance.
/// </summary>
protected Dalamud Dalamud => this.dalamud;
/// <inheritdoc/>
bool IEquatable<Actor>.Equals(Actor other) => this.ActorId == other.ActorId;
public virtual uint TargetActorID => 0;
}
}

View file

@ -0,0 +1,61 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// Memory offsets for the <see cref="Actor"/> type and all that inherit from it.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.")]
public static class ActorOffsets
{
// GameObject(Actor)
// GameObject :: Character
// GameObject :: Character :: BattleChara
// GameObject :: Character :: Companion
public const int Name = 0x30;
public const int ActorId = 0x74;
public const int DataId = 0x80;
public const int OwnerId = 0x84;
public const int ObjectKind = 0x8C;
public const int SubKind = 0x8D;
public const int IsFriendly = 0x8E;
public const int YalmDistanceFromObjectX = 0x90;
public const int TargetStatus = 0x91;
public const int YalmDistanceFromObjectY = 0x92;
public const int Position = 0xA0;
public const int Rotation = 0xB0;
public const int HitboxRadius = 0xC0;
// End GameObject 0x1A0
public const int CurrentHp = 0x1C4;
public const int MaxHp = 0x1C8;
public const int CurrentMp = 0x1CC;
public const int MaxMp = 0x1D0;
public const int CurrentGp = 0x1D4;
public const int MaxGp = 0x1D6;
public const int CurrentCp = 0x1D8;
public const int MaxCp = 0x1DA;
public const int ClassJob = 0x1E2;
public const int Level = 0x1E3;
public const int PlayerCharacterTargetActorId = 0x230;
public const int Customize = 0x1898;
public const int CompanyTag = 0x18B2;
public const int BattleNpcTargetActorId = 0x18D8;
public const int NameId = 0x1940;
public const int CurrentWorld = 0x195C;
public const int HomeWorld = 0x195E;
public const int StatusFlags = 0x19A0;
// End Character 0x19B0
// End Companion 0x19C0
public const int UIStatusEffects = 0x19F8;
public const int IsCasting = 0x1B80;
public const int IsCasting2 = 0x1B82;
public const int CurrentCastSpellActionId = 0x1B84;
public const int CurrentCastTargetActorId = 0x1B90;
public const int CurrentCastTime = 0x1BB4;
public const int TotalCastTime = 0x1BB8;
// End BattleChara 0x2C00
}
}

View file

@ -1,80 +1,124 @@
using System;
using Dalamud.Game.ClientState.Actors.Resolvers;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents the base for non-static entities.
/// </summary>
public class Chara : Actor
public unsafe class Chara : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="Chara"/> class.
/// This represents a non-static entity.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
protected Chara(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
: base(address, actorStruct, dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Chara(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => this.ActorStruct.Level;
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ClassJob ClassJob => new(this.ActorStruct.ClassJob, this.Dalamud);
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public int CurrentHp => this.ActorStruct.CurrentHp;
public uint CurrentHp => *(uint*)(this.Address + ActorOffsets.CurrentHp);
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public int MaxHp => this.ActorStruct.MaxHp;
public uint MaxHp => *(uint*)(this.Address + ActorOffsets.MaxHp);
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public int CurrentMp => this.ActorStruct.CurrentMp;
public uint CurrentMp => *(uint*)(this.Address + ActorOffsets.CurrentMp);
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public int MaxMp => this.ActorStruct.MaxMp;
public uint MaxMp => *(uint*)(this.Address + ActorOffsets.MaxMp);
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public int CurrentGp => this.ActorStruct.CurrentGp;
public uint CurrentGp => *(uint*)(this.Address + ActorOffsets.CurrentGp);
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public int MaxGp => this.ActorStruct.MaxGp;
public uint MaxGp => *(uint*)(this.Address + ActorOffsets.MaxGp);
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public int CurrentCp => this.ActorStruct.CurrentCp;
public uint CurrentCp => *(uint*)(this.Address + ActorOffsets.CurrentCp);
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public int MaxCp => this.ActorStruct.MaxCp;
public uint MaxCp => *(uint*)(this.Address + ActorOffsets.MaxCp);
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ClassJobResolver ClassJob => new(*(byte*)(this.Address + ActorOffsets.ClassJob), this.Dalamud);
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => *(byte*)(this.Address + ActorOffsets.Level);
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => this.ActorStruct.Customize;
public byte[] Customize => MemoryHelper.Read<byte>(this.Address + ActorOffsets.Customize, 28);
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags => *(StatusFlags*)(this.Address + ActorOffsets.StatusFlags);
/// <summary>
/// Gets the current status effects.
/// </summary>
/// <remarks>
/// This copies every time it is invoked, so make sure to only grab it once.
/// </remarks>
public StatusEffect[] StatusEffects => MemoryHelper.Read<StatusEffect>(this.Address + ActorOffsets.UIStatusEffects, 30, true);
/// <summary>
/// Gets a value indicating whether the actor is currently casting.
/// </summary>
public bool IsCasting => *(int*)(this.Address + ActorOffsets.IsCasting) > 0;
/// <summary>
/// Gets a value indicating whether the actor is currently casting (again?).
/// </summary>
public bool IsCasting2 => *(int*)(this.Address + ActorOffsets.IsCasting2) > 0;
/// <summary>
/// Gets the spell action ID currently being cast by the actor.
/// </summary>
public uint CurrentCastSpellActionId => *(uint*)(this.Address + ActorOffsets.CurrentCastSpellActionId);
/// <summary>
/// Gets the actor ID of the target currently being cast at by the actor.
/// </summary>
public uint CurrentCastTargetActorId => *(uint*)(this.Address + ActorOffsets.CurrentCastTargetActorId);
/// <summary>
/// Gets the current casting time of the spell being cast by the actor.
/// </summary>
public float CurrentCastTime => *(float*)(this.Address + ActorOffsets.CurrentCastTime);
/// <summary>
/// Gets the total casting time of the spell being cast by the actor.
/// </summary>
public float TotalCastTime => *(float*)(this.Address + ActorOffsets.TotalCastTime);
}
}

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This enum describes the indices of the Customize array.

View file

@ -5,33 +5,27 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary>
/// This class represents a battle NPC.
/// </summary>
public class BattleNpc : Npc
public unsafe class BattleNpc : Npc
{
/// <summary>
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
/// Set up a new BattleNpc with the provided memory representation.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
public BattleNpc(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
: base(address, actorStruct, dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal BattleNpc(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
/// </summary>
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.ActorStruct.SubKind;
public BattleNpcSubKind BattleNpcKind => *(BattleNpcSubKind*)(this.Address + ActorOffsets.SubKind);
/// <summary>
/// Gets the ID of this BattleNpc's owner.
/// Gets the target of the Battle NPC.
/// </summary>
public int OwnerId => this.ActorStruct.OwnerId;
/// <summary>
/// Gets target of the Battle NPC.
/// </summary>
public override int TargetActorID => this.ActorStruct.BattleNpcTargetActorId;
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.BattleNpcTargetActorId);
}
}

View file

@ -5,23 +5,22 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary>
/// This class represents an EventObj.
/// </summary>
public class EventObj : Actor
public unsafe class EventObj : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class.
/// This represents an Event Object.
/// Set up a new EventObj with the provided memory representation.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
public EventObj(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
: base(address, actorStruct, dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the data ID of the NPC linking to their respective game data.
/// Gets the event object ID of the linking to their respective game data.
/// </summary>
public int DataId => this.ActorStruct.DataId;
public uint EventObjectId => *(uint*)(this.Address + ActorOffsets.DataId);
}
}

View file

@ -5,28 +5,27 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary>
/// This class represents a NPC.
/// </summary>
public class Npc : Chara
public unsafe class Npc : Chara
{
/// <summary>
/// Initializes a new instance of the <see cref="Npc"/> class.
/// This represents a Non-playable Character.
/// Set up a new NPC with the provided memory representation.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
public Npc(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
: base(address, actorStruct, dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Npc(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the data ID of the NPC linking to their respective game data.
/// Gets the data ID of the NPC linking to their assoicated BNpcBase data.
/// </summary>
public int DataId => this.ActorStruct.DataId;
public uint BaseId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary>
/// Gets the name ID of the NPC linking to their respective game data.
/// </summary>
public int NameId => this.ActorStruct.NameId;
public uint NameId => *(uint*)(this.Address + ActorOffsets.NameId);
}
}

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// Enum describing possible entity kinds.

View file

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
@ -7,26 +8,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// </summary>
public class PartyMember
{
/// <summary>
/// The name of the character.
/// </summary>
public string CharacterName;
/// <summary>
/// Unknown.
/// </summary>
public long Unknown;
/// <summary>
/// The actor object that corresponds to this party member.
/// </summary>
public Actor Actor;
/// <summary>
/// The kind or type of actor.
/// </summary>
public ObjectKind ObjectKind;
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
@ -34,9 +15,10 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <param name="rawData">The interop data struct.</param>
public PartyMember(ActorTable table, Structs.PartyMember rawData)
{
this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
this.CharacterName = MemoryHelper.ReadSeString(rawData.namePtr);
this.Unknown = rawData.unknown;
this.Actor = null;
for (var i = 0; i < table.Length; i++)
{
if (table[i] != null && table[i].ActorId == rawData.actorId)
@ -48,5 +30,25 @@ namespace Dalamud.Game.ClientState.Actors.Types
this.ObjectKind = rawData.objectKind;
}
/// <summary>
/// Gets the name of the character.
/// </summary>
public SeString CharacterName { get; }
/// <summary>
/// Gets something unknown.
/// </summary>
public long Unknown { get; }
/// <summary>
/// Gets the actor object that corresponds to this party member.
/// </summary>
public Actor Actor { get; }
/// <summary>
/// Gets the kind or type of actor.
/// </summary>
public ObjectKind ObjectKind { get; }
}
}

View file

@ -1,51 +1,45 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.ClientState.Actors.Resolvers;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a player character.
/// </summary>
public class PlayerCharacter : Chara
public unsafe class PlayerCharacter : Chara
{
/// <summary>
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
/// This represents a player character.
/// </summary>
/// <param name="actorStruct">The memory representation of the base actor.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
/// <param name="address">The address of this actor in memory.</param>
public PlayerCharacter(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud)
: base(address, actorStruct, dalamud)
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal PlayerCharacter(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
var companyTagBytes = new byte[5];
Marshal.Copy(this.Address + ActorOffsets.CompanyTag, companyTagBytes, 0, companyTagBytes.Length);
this.CompanyTag = Encoding.UTF8.GetString(companyTagBytes.TakeWhile(c => c != 0x0).ToArray());
}
/// <summary>
/// Gets the current <see cref="World">world</see> of the character.
/// Gets the current <see cref="WorldResolver">world</see> of the character.
/// </summary>
public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud);
public WorldResolver CurrentWorld => new(*(ushort*)(this.Address + ActorOffsets.CurrentWorld), this.Dalamud);
/// <summary>
/// Gets the home <see cref="World">world</see> of the character.
/// Gets the home <see cref="WorldResolver">world</see> of the character.
/// </summary>
public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud);
public WorldResolver HomeWorld => new(*(ushort*)(this.Address + ActorOffsets.HomeWorld), this.Dalamud);
/// <summary>
/// Gets the Free Company tag of this player.
/// </summary>
public string CompanyTag { get; private set; }
public SeString CompanyTag => MemoryHelper.ReadSeString(this.Address + ActorOffsets.CompanyTag, 6);
/// <summary>
/// Gets the target of the PlayerCharacter.
/// Gets the target actor ID of the PlayerCharacter.
/// </summary>
public override int TargetActorID => this.ActorStruct.PlayerCharacterTargetActorId;
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.PlayerCharacterTargetActorId);
}
}

View file

@ -0,0 +1,56 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// Enum describing possible status flags.
/// </summary>
[Flags]
public enum StatusFlags : byte
{
/// <summary>
/// No status flags set.
/// </summary>
None = 0,
/// <summary>
/// Hostile actor.
/// </summary>
Hostile = 1,
/// <summary>
/// Actor in combat.
/// </summary>
InCombat = 2,
/// <summary>
/// Actor weapon is out.
/// </summary>
WeaponOut = 4,
/// <summary>
/// Actor offhand is out.
/// </summary>
OffhandOut = 8,
/// <summary>
/// Actor is a party member.
/// </summary>
PartyMember = 16,
/// <summary>
/// Actor is a alliance member.
/// </summary>
AllianceMember = 32,
/// <summary>
/// Actor is in friend list.
/// </summary>
Friend = 64,
/// <summary>
/// Actor is casting.
/// </summary>
IsCasting = 128,
}
}

View file

@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Fates;
using Dalamud.Game.Internal;
using Dalamud.Hooking;
using JetBrains.Annotations;
@ -15,58 +16,8 @@ namespace Dalamud.Game.ClientState
/// <summary>
/// This class represents the state of the game client at the time of access.
/// </summary>
public class ClientState : INotifyPropertyChanged, IDisposable
public sealed class ClientState : INotifyPropertyChanged, IDisposable
{
/// <summary>
/// The table of all present actors.
/// </summary>
public readonly ActorTable Actors;
/// <summary>
/// Gets the language of the client.
/// </summary>
public readonly ClientLanguage ClientLanguage;
/// <summary>
/// The current Territory the player resides in.
/// </summary>
public ushort TerritoryType;
/// <summary>
/// The class facilitating Job Gauge data access.
/// </summary>
public JobGauges JobGauges;
/// <summary>
/// The class facilitating party list data access.
/// </summary>
public PartyList PartyList;
/// <summary>
/// Provides access to the keypress state of keyboard keys in game.
/// </summary>
public KeyState KeyState;
/// <summary>
/// Provides access to the button state of gamepad buttons in game.
/// </summary>
public GamepadState GamepadState;
/// <summary>
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
/// </summary>
public Condition Condition;
/// <summary>
/// The class facilitating target data access.
/// </summary>
public Targets Targets;
/// <summary>
/// Event that gets fired when the current Territory changes.
/// </summary>
public EventHandler<ushort> TerritoryChanged;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
@ -80,7 +31,7 @@ namespace Dalamud.Game.ClientState
/// <param name="dalamud">Dalamud instance.</param>
/// <param name="startInfo">StartInfo of the current Dalamud launch.</param>
/// <param name="scanner">Sig scanner.</param>
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
internal ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
{
this.dalamud = dalamud;
this.address = new ClientStateAddressResolver();
@ -92,6 +43,8 @@ namespace Dalamud.Game.ClientState
this.Actors = new ActorTable(dalamud, this.address);
this.Fates = new FateTable(dalamud, this.address);
this.PartyList = new PartyList(dalamud, this.address);
this.JobGauges = new JobGauges(this.address);
@ -104,7 +57,7 @@ namespace Dalamud.Game.ClientState
this.Targets = new Targets(dalamud, this.address);
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", this.address.SetupTerritoryType);
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
@ -122,6 +75,11 @@ namespace Dalamud.Game.ClientState
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore
/// <summary>
/// Event that gets fired when the current Territory changes.
/// </summary>
public event EventHandler<ushort> TerritoryChanged;
/// <summary>
/// Event that fires when a character is logging in.
/// </summary>
@ -137,22 +95,61 @@ namespace Dalamud.Game.ClientState
/// </summary>
public event EventHandler<ContentFinderCondition> CfPop;
/// <summary>
/// Gets the table of all present actors.
/// </summary>
public ActorTable Actors { get; }
/// <summary>
/// Gets the table of all present fates.
/// </summary>
public FateTable Fates { get; }
/// <summary>
/// Gets the language of the client.
/// </summary>
public ClientLanguage ClientLanguage { get; }
/// <summary>
/// Gets the class facilitating Job Gauge data access.
/// </summary>
public JobGauges JobGauges { get; }
/// <summary>
/// Gets the class facilitating party list data access.
/// </summary>
public PartyList PartyList { get; }
/// <summary>
/// Gets access to the keypress state of keyboard keys in game.
/// </summary>
public KeyState KeyState { get; }
/// <summary>
/// Gets access to the button state of gamepad buttons in game.
/// </summary>
public GamepadState GamepadState { get; }
/// <summary>
/// Gets access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
/// </summary>
public Condition Condition { get; }
/// <summary>
/// Gets the class facilitating target data access.
/// </summary>
public Targets Targets { get; }
/// <summary>
/// Gets the current Territory the player resides in.
/// </summary>
public ushort TerritoryType { get; private set; }
/// <summary>
/// Gets the local player character, if one is present.
/// </summary>
[CanBeNull]
public PlayerCharacter LocalPlayer
{
get
{
var actor = this.Actors[0];
if (actor is PlayerCharacter pc)
return pc;
return null;
}
}
public PlayerCharacter LocalPlayer => this.Actors[0] as PlayerCharacter;
/// <summary>
/// Gets the content ID of the local character.
@ -181,7 +178,6 @@ namespace Dalamud.Game.ClientState
{
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose();
this.Actors.Dispose();
this.GamepadState.Dispose();
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.Internal;
@ -16,6 +17,14 @@ namespace Dalamud.Game.ClientState
/// </summary>
public IntPtr ActorTable { get; private set; }
/// <summary>
/// Gets the address of the fate table pointer.
/// </summary>
/// <remarks>
/// This is a static address to a pointer, not the address of the table itself.
/// </remarks>
public IntPtr FateTablePtr { get; private set; }
// public IntPtr ViewportActorTable { get; private set; }
/// <summary>
@ -50,9 +59,6 @@ namespace Dalamud.Game.ClientState
/// </summary>
public IntPtr SetupTerritoryType { get; private set; }
// public IntPtr SomeActorTableAccess { get; private set; }
// public IntPtr PartyListUpdate { get; private set; }
/// <summary>
/// Gets the address of the method which polls the gamepads for data.
/// Called every frame, even when `Enable Gamepad` is off in the settings.
@ -68,14 +74,18 @@ namespace Dalamud.Game.ClientState
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
// This resolves to a fixed offset only, without the base address added in,
// so GetStaticAddressFromSig() can't be used. lea rcx, ds:1DB9F74h[rax*4]
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");

View file

@ -0,0 +1,178 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Fates.Types;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Fates
{
/// <summary>
/// This collection represents the currently available Fate events.
/// </summary>
public sealed partial class FateTable
{
// If the pointer at this offset is 0, do not scan the table
private const int CheckPtrOffset = 0x80;
private const int FirstPtrOffset = 0x90;
private const int LastPtrOffset = 0x98;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="FateTable"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal FateTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
}
/// <summary>
/// Gets the amount of currently active Fates.
/// </summary>
public unsafe int Length
{
get
{
var fateTable = this.FateTableAddress;
if (fateTable == IntPtr.Zero)
return 0;
var check = *(long*)(fateTable + CheckPtrOffset);
if (check == 0)
return 0;
var start = *(long*)(fateTable + FirstPtrOffset);
var end = *(long*)(fateTable + LastPtrOffset);
if (start == 0 || end == 0)
return 0;
return (int)((end - start) / 8);
}
}
private unsafe IntPtr FateTableAddress
{
get
{
if (this.address.FateTablePtr == IntPtr.Zero)
return IntPtr.Zero;
return *(IntPtr*)this.address.FateTablePtr;
}
}
/// <summary>
/// Get an actor at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
[CanBeNull]
public Fate this[int index]
{
get
{
var address = this.GetFateAddress(index);
return this[address];
}
}
/// <summary>
/// Get a Fate at the specified address.
/// </summary>
/// <param name="address">The Fate address.</param>
/// <returns>A <see cref="Fate"/> at the specified address.</returns>
public Fate this[IntPtr address]
{
get
{
if (address == IntPtr.Zero)
return null;
return this.CreateFateReference(address);
}
}
/// <summary>
/// Gets the address of the Fate at the specified index of the fate table.
/// </summary>
/// <param name="index">The index of the Fate.</param>
/// <returns>The memory address of the Fate.</returns>
public unsafe IntPtr GetFateAddress(int index)
{
if (index >= this.Length)
return IntPtr.Zero;
var fateTable = this.FateTableAddress;
if (fateTable == IntPtr.Zero)
return IntPtr.Zero;
var firstFate = *(IntPtr*)(fateTable + FirstPtrOffset);
return *(IntPtr*)(firstFate + (8 * index));
}
/// <summary>
/// Create a reference to a FFXIV actor.
/// </summary>
/// <param name="offset">The offset of the actor in memory.</param>
/// <returns><see cref="Fate"/> object containing requested data.</returns>
[CanBeNull]
internal unsafe Fate CreateFateReference(IntPtr offset)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (offset == IntPtr.Zero)
return null;
return new Fate(offset, this.dalamud);
}
}
/// <summary>
/// This collection represents the currently available Fate events.
/// </summary>
public sealed partial class FateTable : IReadOnlyCollection<Fate>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Fate>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Fate> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -0,0 +1,138 @@
using System;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
namespace Dalamud.Game.ClientState.Fates.Types
{
/// <summary>
/// This class represents an FFXIV Fate.
/// </summary>
public unsafe partial class Fate : IEquatable<Fate>
{
/// <summary>
/// Initializes a new instance of the <see cref="Fate"/> class.
/// </summary>
/// <param name="address">The address of this fate in memory.</param>
/// <param name="dalamud">Dalamud instance.</param>
internal Fate(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.Dalamud = dalamud;
}
/// <summary>
/// Gets the address of this Fate in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets Dalamud itself.
/// </summary>
private protected Dalamud Dalamud { get; }
public static bool operator ==(Fate fate1, Fate fate2)
{
if (fate1 is null || fate2 is null)
return Equals(fate1, fate2);
return fate1.Equals(fate2);
}
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
/// <summary>
/// Gets a value indicating whether this Fate is still valid in memory.
/// </summary>
/// <param name="fate">The fate to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Fate fate)
{
if (fate == null)
return false;
if (fate.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<Fate>.Equals(Fate other) => this.FateId == other?.FateId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate);
/// <inheritdoc/>
public override int GetHashCode() => this.FateId.GetHashCode();
}
/// <summary>
/// This class represents an FFXIV Fate.
/// </summary>
public unsafe partial class Fate
{
/// <summary>
/// Gets the Fate ID of this <see cref="Fate" />.
/// </summary>
public ushort FateId => *(ushort*)(this.Address + FateOffsets.FateId);
/// <summary>
/// Gets game data linked to this Fate.
/// </summary>
public Lumina.Excel.GeneratedSheets.Fate GameData => this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
/// <summary>
/// Gets the time this <see cref="Fate"/> started.
/// </summary>
public int StartTimeEpoch => *(int*)(this.Address + FateOffsets.StartTimeEpoch);
/// <summary>
/// Gets how long this <see cref="Fate"/> will run.
/// </summary>
public short Duration => *(short*)(this.Address + FateOffsets.Duration);
/// <summary>
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
/// </summary>
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
/// <summary>
/// Gets the displayname of this <see cref="Fate" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((Utf8String*)(this.Address + FateOffsets.Name));
/// <summary>
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
/// </summary>
public FateState State => *(FateState*)(this.Address + FateOffsets.State);
/// <summary>
/// Gets the progress amount of this <see cref="Fate"/>.
/// </summary>
public byte Progress => *(byte*)(this.Address + FateOffsets.Progress);
/// <summary>
/// Gets the level of this <see cref="Fate"/>.
/// </summary>
public byte Level => *(byte*)(this.Address + FateOffsets.Level);
/// <summary>
/// Gets the position of this <see cref="Fate"/>.
/// </summary>
public Position3 Position => *(Position3*)(this.Address + FateOffsets.Position);
/// <summary>
/// Gets the territory this <see cref="Fate"/> is located in.
/// </summary>
public TerritoryTypeResolver TerritoryType => new(*(ushort*)(this.Address + FateOffsets.Territory), this.Dalamud);
}
}

View file

@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.ClientState.Fates.Types
{
/// <summary>
/// Memory offsets for the <see cref="Fate"/> type.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.")]
public static class FateOffsets
{
public const int FateId = 0x18;
public const int StartTimeEpoch = 0x20;
public const int Duration = 0x28;
public const int Name = 0xC0;
public const int State = 0x3AC;
public const int Progress = 0x3B8;
public const int Level = 0x3F9;
public const int Position = 0x450;
public const int Territory = 0x74E;
}
}

View file

@ -0,0 +1,33 @@
namespace Dalamud.Game.ClientState.Fates.Types
{
/// <summary>
/// This represents the state of a single Fate.
/// </summary>
public enum FateState : byte
{
/// <summary>
/// The Fate is active.
/// </summary>
Running = 0x02,
/// <summary>
/// The Fate has ended.
/// </summary>
Ended = 0x04,
/// <summary>
/// The player failed the Fate.
/// </summary>
Failed = 0x05,
/// <summary>
/// The Fate is preparing to run.
/// </summary>
Preparation = 0x07,
/// <summary>
/// The Fate is preparing to end.
/// </summary>
WaitingForEnd = 0x08,
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Hooking;
@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState
///
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
/// </summary>
public unsafe class GamepadState
public unsafe class GamepadState : IDisposable
{
private readonly Hook<ControllerPoll> gamepadPoll;
@ -29,12 +29,8 @@ namespace Dalamud.Game.ClientState
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
public GamepadState(ClientStateAddressResolver resolver)
{
#if DEBUG
Log.Verbose("GamepadPoll address {GamepadPoll}", resolver.GamepadPoll);
#endif
this.gamepadPoll = new Hook<ControllerPoll>(
resolver.GamepadPoll,
(ControllerPoll)this.GamepadPollDetour);
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
}
/// <summary>

View file

@ -17,7 +17,7 @@ namespace Dalamud.Game.ClientState
{
this.Address = addressResolver;
Log.Verbose("JobGaugeData address {JobGaugeData}", this.Address.JobGaugeData);
Log.Verbose($"JobGaugeData address 0x{this.Address.JobGaugeData.ToInt64():X}");
}
private ClientStateAddressResolver Address { get; }

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.ClientState
{
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
Log.Verbose($"Keyboard state buffer address {this.bufferBase}");
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
}
/// <summary>

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.ClientState
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
internal PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;

View file

@ -0,0 +1,34 @@
using Lumina.Excel;
namespace Dalamud.Game.ClientState.Resolvers
{
/// <summary>
/// This object represents a class or job.
/// </summary>
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
public class BaseResolver<T> where T : ExcelRow
{
private readonly Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="BaseResolver{T}"/> class.
/// </summary>
/// <param name="id">The ID of the classJob.</param>
/// <param name="dalamud">The Dalamud instance.</param>
internal BaseResolver(uint id, Dalamud dalamud)
{
this.dalamud = dalamud;
this.Id = id;
}
/// <summary>
/// Gets the ID to be resolved.
/// </summary>
public uint Id { get; }
/// <summary>
/// Gets GameData linked to this excel row.
/// </summary>
public T GameData => this.dalamud.Data.GetExcelSheet<T>().GetRow(this.Id);
}
}

View file

@ -0,0 +1,19 @@
namespace Dalamud.Game.ClientState.Resolvers
{
/// <summary>
/// This object represents a class or job.
/// </summary>
public class ClassJobResolver : BaseResolver<Lumina.Excel.GeneratedSheets.ClassJob>
{
/// <summary>
/// Initializes a new instance of the <see cref="ClassJobResolver"/> class.
/// Set up the ClassJob resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the classJob.</param>
/// <param name="dalamud">The Dalamud instance.</param>
internal ClassJobResolver(ushort id, Dalamud dalamud)
: base(id, dalamud)
{
}
}
}

View file

@ -0,0 +1,19 @@
namespace Dalamud.Game.ClientState.Resolvers
{
/// <summary>
/// This object represents a Fate a character can participate in.
/// </summary>
public class FateResolver : BaseResolver<Lumina.Excel.GeneratedSheets.Fate>
{
/// <summary>
/// Initializes a new instance of the <see cref="FateResolver"/> class.
/// Set up the Fate resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the Fate.</param>
/// <param name="dalamud">The Dalamud instance.</param>
internal FateResolver(ushort id, Dalamud dalamud)
: base(id, dalamud)
{
}
}
}

View file

@ -0,0 +1,19 @@
namespace Dalamud.Game.ClientState.Resolvers
{
/// <summary>
/// This object represents a territory a character can be in.
/// </summary>
public class TerritoryTypeResolver : BaseResolver<Lumina.Excel.GeneratedSheets.TerritoryType>
{
/// <summary>
/// Initializes a new instance of the <see cref="TerritoryTypeResolver"/> class.
/// Set up the territory type resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the territory type.</param>
/// <param name="dalamud">The Dalamud instance.</param>
internal TerritoryTypeResolver(ushort id, Dalamud dalamud)
: base(id, dalamud)
{
}
}
}

View file

@ -0,0 +1,19 @@
namespace Dalamud.Game.ClientState.Resolvers
{
/// <summary>
/// This object represents a world a character can reside on.
/// </summary>
public class WorldResolver : BaseResolver<Lumina.Excel.GeneratedSheets.World>
{
/// <summary>
/// Initializes a new instance of the <see cref="WorldResolver"/> class.
/// Set up the world resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the world.</param>
/// <param name="dalamud">The Dalamud instance.</param>
internal WorldResolver(ushort id, Dalamud dalamud)
: base(id, dalamud)
{
}
}
}

View file

@ -1,289 +0,0 @@
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
namespace Dalamud.Game.ClientState.Structs
{
/// <summary>
/// Native memory representation of an FFXIV actor.
/// </summary>
[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct Actor
{
/// <summary>
/// The actor name.
/// </summary>
[FieldOffset(ActorOffsets.Name)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public byte[] Name;
/// <summary>
/// The actor's internal id.
/// </summary>
[FieldOffset(ActorOffsets.ActorId)]
public int ActorId;
/// <summary>
/// The actor's data id.
/// </summary>
[FieldOffset(ActorOffsets.DataId)]
public int DataId;
/// <summary>
/// The actor's owner id. This is useful for pets, summons, and the like.
/// </summary>
[FieldOffset(ActorOffsets.OwnerId)]
public int OwnerId;
/// <summary>
/// The type or kind of actor.
/// </summary>
[FieldOffset(ActorOffsets.ObjectKind)]
public ObjectKind ObjectKind;
/// <summary>
/// The sub-type or sub-kind of actor.
/// </summary>
[FieldOffset(ActorOffsets.SubKind)]
public byte SubKind;
/// <summary>
/// Whether the actor is friendly.
/// </summary>
[FieldOffset(ActorOffsets.IsFriendly)]
public bool IsFriendly;
/// <summary>
/// The horizontal distance in game units from the player.
/// </summary>
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)]
public byte YalmDistanceFromPlayerX;
/// <summary>
/// The player target status.
/// </summary>
/// <remarks>
/// This is some kind of enum.
/// </remarks>
[FieldOffset(ActorOffsets.PlayerTargetStatus)]
public byte PlayerTargetStatus;
/// <summary>
/// The vertical distance in game units from the player.
/// </summary>
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)]
public byte YalmDistanceFromPlayerY;
/// <summary>
/// The (X,Z,Y) position of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Position)]
public Position3 Position;
/// <summary>
/// The rotation of the actor.
/// </summary>
/// <remarks>
/// The rotation is around the vertical axis (yaw), from -pi to pi radians.
/// </remarks>
[FieldOffset(ActorOffsets.Rotation)]
public float Rotation;
/// <summary>
/// The hitbox radius of the actor.
/// </summary>
[FieldOffset(ActorOffsets.HitboxRadius)]
public float HitboxRadius;
/// <summary>
/// The current HP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentHp)]
public int CurrentHp;
/// <summary>
/// The max HP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxHp)]
public int MaxHp;
/// <summary>
/// The current MP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentMp)]
public int CurrentMp;
/// <summary>
/// The max MP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxMp)]
public short MaxMp;
/// <summary>
/// The current GP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentGp)]
public short CurrentGp;
/// <summary>
/// The max GP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxGp)]
public short MaxGp;
/// <summary>
/// The current CP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCp)]
public short CurrentCp;
/// <summary>
/// The max CP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxCp)]
public short MaxCp;
/// <summary>
/// The class-job of the actor.
/// </summary>
[FieldOffset(ActorOffsets.ClassJob)]
public byte ClassJob;
/// <summary>
/// The level of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Level)]
public byte Level;
/// <summary>
/// The (player character) actor ID being targeted by the actor.
/// </summary>
[FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)]
public int PlayerCharacterTargetActorId;
/// <summary>
/// The customization byte/bitfield of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Customize)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
public byte[] Customize;
// Normally pack=2 should work, but ByTVal or Injection breaks this.
// [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
/// <summary>
/// The (battle npc) actor ID being targeted by the actor.
/// </summary>
[FieldOffset(ActorOffsets.BattleNpcTargetActorId)]
public int BattleNpcTargetActorId;
/// <summary>
/// The name ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.NameId)]
public int NameId;
/// <summary>
/// The current world ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentWorld)]
public ushort CurrentWorld;
/// <summary>
/// The home world ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.HomeWorld)]
public ushort HomeWorld;
/// <summary>
/// Whether the actor is currently casting.
/// </summary>
[FieldOffset(ActorOffsets.IsCasting)]
public bool IsCasting;
/// <summary>
/// Whether the actor is currently casting (dup?).
/// </summary>
[FieldOffset(ActorOffsets.IsCasting2)]
public bool IsCasting2;
/// <summary>
/// The spell action ID currently being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastSpellActionId)]
public uint CurrentCastSpellActionId;
/// <summary>
/// The actor ID of the target currently being cast at by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastTargetActorId)]
public uint CurrentCastTargetActorId;
/// <summary>
/// The current casting time of the spell being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastTime)]
public float CurrentCastTime;
/// <summary>
/// The total casting time of the spell being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.TotalCastTime)]
public float TotalCastTime;
/// <summary>
/// The array of status effects that the actor is currently affected by.
/// </summary>
[FieldOffset(ActorOffsets.UIStatusEffects)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public StatusEffect[] UIStatusEffects;
}
/// <summary>
/// Memory offsets for the <see cref="Actor"/> type.
/// </summary>
public static class ActorOffsets
{
// Reference https://github.com/FFXIVAPP/sharlayan-resources/blob/master/structures/5.4/x64.json for more
public const int Name = 48; // 0x0030
public const int ActorId = 116; // 0x0074
// public const int ??? = 120; // 0x0078 NPCID1
public const int DataId = 128; // 0x0080 NPCID2
public const int OwnerId = 132; // 0x0084
public const int ObjectKind = 140; // 0x008C Type
public const int SubKind = 141; // 0x008D
public const int IsFriendly = 142; // 0x008E
public const int YalmDistanceFromPlayerX = 144; // 0x0090
public const int PlayerTargetStatus = 145; // 0x0091
public const int YalmDistanceFromPlayerY = 146; // 0x0092 Distance
public const int Position = 160; // 0x00A0 (X,Z,Y)
public const int Rotation = 176; // 0x00B0 Heading
public const int HitboxRadius = 192; // 0x00C0
public const int CurrentHp = 452; // 0x01C4 HPCurrent
public const int MaxHp = 456; // 0x01C8 HPMax
public const int CurrentMp = 460; // 0x01CC MPCurrent
public const int MaxMp = 464; // 0x01D0 MPMax
public const int CurrentGp = 468; // 0x01D4 GPCurrent
public const int MaxGp = 470; // 0x01D6 GPMax
public const int CurrentCp = 472; // 0x01D8 CPCurrent
public const int MaxCp = 474; // 0x01DA CPMax
public const int ClassJob = 482; // 0x01E2 Job
public const int Level = 483; // 0x01E3 Level
public const int PlayerCharacterTargetActorId = 560; // 0x01F0 TargetID
public const int Customize = 0x1898; // Needs verification
public const int CompanyTag = 0x18B2;
public const int BattleNpcTargetActorId = 0x18D8; // Needs verification
public const int NameId = 0x1940; // Needs verification
public const int CurrentWorld = 0x195C;
public const int HomeWorld = 0x195E;
public const int IsCasting = 0x1B80;
public const int IsCasting2 = 0x1B82;
public const int CurrentCastSpellActionId = 0x1B84;
public const int CurrentCastTargetActorId = 0x1B90;
public const int CurrentCastTime = 0x1BB4;
public const int TotalCastTime = 0x1BB8;
public const int UIStatusEffects = 0x19F8;
}
}

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs
{
@ -40,25 +40,37 @@ namespace Dalamud.Game.ClientState.Structs
/// <summary>
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x98)]
public ushort ButtonsRaw; // bitfield
public ushort ButtonsRaw;
/// <summary>
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x9C)]
public ushort ButtonsPressed; // bitfield
public ushort ButtonsPressed;
/// <summary>
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0xA0)]
public ushort ButtonsReleased; // bitfield
public ushort ButtonsReleased;
/// <summary>
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0xA4)]
public ushort ButtonsRepeat; // bitfield
public ushort ButtonsRepeat;
}
}

View file

@ -8,35 +8,43 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct BLMGauge
{
/// <summary>
/// Gets the time until the next Polyglot stack in milliseconds.
/// </summary>
[FieldOffset(0)]
public short TimeUntilNextPolyglot; // enochian timer
private short timeUntilNextPolyglot; // enochian timer
/// <summary>
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
/// </summary>
[FieldOffset(2)]
public short ElementTimeRemaining; // umbral ice and astral fire timer
private short elementTimeRemaining; // umbral ice and astral fire timer
[FieldOffset(4)]
private byte elementStance; // umbral ice or astral fire
/// <summary>
/// Gets the number of Umbral Hearts remaining.
/// </summary>
[FieldOffset(5)]
public byte NumUmbralHearts;
private byte numUmbralHearts;
[FieldOffset(6)]
private byte numPolyglotStacks;
[FieldOffset(7)]
private byte enochianState;
/// <summary>
/// Gets the time until the next Polyglot stack in milliseconds.
/// </summary>
public short TimeUntilNextPolyglot => this.timeUntilNextPolyglot;
/// <summary>
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
/// </summary>
public short ElementTimeRemaining => this.elementTimeRemaining;
/// <summary>
/// Gets the number of Polyglot stacks remaining.
/// </summary>
[FieldOffset(6)]
public byte NumPolyglotStacks;
public byte NumPolyglotStacks => this.numPolyglotStacks;
[FieldOffset(7)]
private byte enochianState;
/// <summary>
/// Gets the number of Umbral Hearts remaining.
/// </summary>
public byte NumUmbralHearts => this.numUmbralHearts;
/// <summary>
/// Gets if the player is in Umbral Ice.

View file

@ -8,28 +8,36 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct BRDGauge
{
[FieldOffset(0)]
private short songTimer;
[FieldOffset(2)]
private byte numSongStacks;
[FieldOffset(3)]
private byte soulVoiceValue;
[FieldOffset(4)]
private CurrentSong activeSong;
/// <summary>
/// Gets the current song timer in milliseconds.
/// </summary>
[FieldOffset(0)]
public short SongTimer;
public short SongTimer => this.songTimer;
/// <summary>
/// Gets the number of stacks for the current song.
/// </summary>
[FieldOffset(2)]
public byte NumSongStacks;
public byte NumSongStacks => this.numSongStacks;
/// <summary>
/// Gets the amount of Soul Voice accumulated.
/// </summary>
[FieldOffset(3)]
public byte SoulVoiceValue;
public byte SoulVoiceValue => this.soulVoiceValue;
/// <summary>
/// Gets the type of song that is active.
/// </summary>
[FieldOffset(4)]
public CurrentSong ActiveSong;
public CurrentSong ActiveSong => this.activeSong;
}
}

View file

@ -8,30 +8,37 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DNCGauge
{
/// <summary>
/// Gets the number of feathers available.
/// </summary>
[FieldOffset(0)]
public byte NumFeathers;
private byte numFeathers;
/// <summary>
/// Gets the amount of Espirit available.
/// </summary>
[FieldOffset(1)]
public byte Esprit;
private byte esprit;
[FieldOffset(2)]
private fixed byte stepOrder[4];
[FieldOffset(6)]
private byte numCompleteSteps;
/// <summary>
/// Gets the number of feathers available.
/// </summary>
public byte NumFeathers => this.numFeathers;
/// <summary>
/// Gets the amount of Espirit available.
/// </summary>
public byte Esprit => this.esprit;
/// <summary>
/// Gets the number of steps completed for the current dance.
/// </summary>
[FieldOffset(6)]
public byte NumCompleteSteps;
public byte NumCompleteSteps => this.numCompleteSteps;
/// <summary>
/// Gets the next step in the current dance.
/// </summary>
/// <returns>The next dance step action ID.</returns>
public ulong NextStep() => (ulong)(15999 + this.stepOrder[this.NumCompleteSteps] - 1);
/// <summary>

View file

@ -8,22 +8,28 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct DRGGauge
{
[FieldOffset(0)]
private short botdTimer;
[FieldOffset(2)]
private BOTDState botdState;
[FieldOffset(3)]
private byte eyeCount;
/// <summary>
/// Gets the time remaining for Blood of the Dragon in milliseconds.
/// </summary>
[FieldOffset(0)]
public short BOTDTimer;
public short BOTDTimer => this.botdTimer;
/// <summary>
/// Gets the current state of Blood of the Dragon.
/// </summary>
[FieldOffset(2)]
public BOTDState BOTDState;
public BOTDState BOTDState => this.botdState;
/// <summary>
/// Gets the count of eyes opened during Blood of the Dragon.
/// </summary>
[FieldOffset(3)]
public byte EyeCount;
public byte EyeCount => this.eyeCount;
}
}

View file

@ -8,26 +8,32 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct DRKGauge
{
/// <summary>
/// Gets the amount of blood accumulated.
/// </summary>
[FieldOffset(0)]
public byte Blood;
private byte blood;
/// <summary>
/// Gets the Darkside time remaining in milliseconds.
/// </summary>
[FieldOffset(2)]
public ushort DarksideTimeRemaining;
private ushort darksideTimeRemaining;
[FieldOffset(4)]
private byte darkArtsState;
[FieldOffset(6)]
private ushort shadowTimeRemaining;
/// <summary>
/// Gets the amount of blood accumulated.
/// </summary>
public byte Blood => this.blood;
/// <summary>
/// Gets the Darkside time remaining in milliseconds.
/// </summary>
public ushort DarksideTimeRemaining => this.darksideTimeRemaining;
/// <summary>
/// Gets the Shadow time remaining in milliseconds.
/// </summary>
[FieldOffset(6)]
public ushort ShadowTimeRemaining;
public ushort ShadowTimeRemaining => this.shadowTimeRemaining;
/// <summary>
/// Gets if the player has Dark Arts or not.

View file

@ -8,22 +8,28 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct GNBGauge
{
[FieldOffset(0)]
private byte numAmmo;
[FieldOffset(2)]
private short maxTimerDuration;
[FieldOffset(4)]
private byte ammoComboStepNumber;
/// <summary>
/// Gets the amount of ammo available.
/// </summary>
[FieldOffset(0)]
public byte NumAmmo;
public byte NumAmmo => this.numAmmo;
/// <summary>
/// Gets the max combo time of the Gnashing Fang combo.
/// </summary>
[FieldOffset(2)]
public short MaxTimerDuration;
public short MaxTimerDuration => this.maxTimerDuration;
/// <summary>
/// Gets the current step of the Gnashing Fang combo.
/// </summary>
[FieldOffset(4)]
public byte AmmoComboStepNumber;
public byte AmmoComboStepNumber => this.ammoComboStepNumber;
}
}

View file

@ -8,38 +8,48 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct MCHGauge
{
[FieldOffset(0)]
private short overheatTimeRemaining;
[FieldOffset(2)]
private short robotTimeRemaining;
[FieldOffset(4)]
private byte heat;
[FieldOffset(5)]
private byte battery;
[FieldOffset(6)]
private byte lastRobotBatteryPower;
[FieldOffset(7)]
private byte timerActive;
/// <summary>
/// Gets the time time remaining for Overheat in milliseconds.
/// </summary>
[FieldOffset(0)]
public short OverheatTimeRemaining;
public short OverheatTimeRemaining => this.overheatTimeRemaining;
/// <summary>
/// Gets the time remaining for the Rook or Queen in milliseconds.
/// </summary>
[FieldOffset(2)]
public short RobotTimeRemaining;
public short RobotTimeRemaining => this.robotTimeRemaining;
/// <summary>
/// Gets the current Heat level.
/// </summary>
[FieldOffset(4)]
public byte Heat;
public byte Heat => this.heat;
/// <summary>
/// Gets the current Battery level.
/// </summary>
[FieldOffset(5)]
public byte Battery;
public byte Battery => this.battery;
/// <summary>
/// Gets the battery level of the last Robot.
/// </summary>
[FieldOffset(6)]
public byte LastRobotBatteryPower;
[FieldOffset(7)]
private byte timerActive;
public byte LastRobotBatteryPower => this.lastRobotBatteryPower;
/// <summary>
/// Gets if the player is currently Overheated.

View file

@ -1,4 +1,3 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
@ -9,35 +8,12 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct MNKGauge
{
[FieldOffset(0)]
private byte numChakra;
/// <summary>
/// Gets the number of Chakra available.
/// </summary>
[FieldOffset(0)]
public byte NumChakra;
/// <summary>
/// Gets the Greased Lightning timer in milliseconds.
/// </summary>
[Obsolete("GL has been removed from the game")]
[FieldOffset(0)]
public byte GLTimer;
/// <summary>
/// Gets the amount of Greased Lightning stacks.
/// </summary>
[Obsolete("GL has been removed from the game")]
[FieldOffset(2)]
public byte NumGLStacks;
[Obsolete("GL has been removed from the game")]
[FieldOffset(4)]
private byte glTimerFreezeState;
/// <summary>
/// Gets if the Greased Lightning timer has been frozen.
/// </summary>
/// <returns>><c>true</c> or <c>false</c>.</returns>
[Obsolete("GL has been removed from the game")]
public bool IsGLTimerFroze() => false;
public byte NumChakra => this.numChakra;
}
}

View file

@ -9,30 +9,28 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct NINGauge
{
[FieldOffset(0)]
private int hutonTimeLeft;
[FieldOffset(4)]
private byte ninki;
[FieldOffset(5)]
private byte numHutonManualCasts;
/// <summary>
/// Gets the time left on Huton in milliseconds.
/// </summary>
// TODO: Probably a short, confirm.
[FieldOffset(0)]
public int HutonTimeLeft;
public int HutonTimeLeft => this.hutonTimeLeft;
/// <summary>
/// Gets the amount of Ninki available.
/// </summary>
[FieldOffset(4)]
public byte Ninki;
/// <summary>
/// Obsolete.
/// </summary>
[Obsolete("Does not appear to be used")]
[FieldOffset(4)]
public byte TCJMudrasUsed;
public byte Ninki => this.ninki;
/// <summary>
/// Gets the number of times Huton has been cast manually.
/// </summary>
[FieldOffset(5)]
public byte NumHutonManualCasts;
public byte NumHutonManualCasts => this.numHutonManualCasts;
}
}

View file

@ -8,10 +8,12 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct PLDGauge
{
[FieldOffset(0)]
private byte gaugeAmount;
/// <summary>
/// Gets the current level of the Oath gauge.
/// </summary>
[FieldOffset(0)]
public byte GaugeAmount;
public byte GaugeAmount => this.gaugeAmount;
}
}

View file

@ -8,16 +8,20 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct RDMGauge
{
[FieldOffset(0)]
private byte whiteGauge;
[FieldOffset(1)]
private byte blackGauge;
/// <summary>
/// Gets the level of the White gauge.
/// </summary>
[FieldOffset(0)]
public byte WhiteGauge;
public byte WhiteGauge => this.whiteGauge;
/// <summary>
/// Gets the level of the Black gauge.
/// </summary>
[FieldOffset(1)]
public byte BlackGauge;
public byte BlackGauge => this.blackGauge;
}
}

View file

@ -8,23 +8,29 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct SAMGauge
{
[FieldOffset(3)]
private byte kenki;
[FieldOffset(4)]
private byte meditationStacks;
[FieldOffset(5)]
private Sen sen;
/// <summary>
/// Gets the current amount of Kenki available.
/// </summary>
[FieldOffset(3)]
public byte Kenki;
public byte Kenki => this.kenki;
/// <summary>
/// Gets the amount of Meditation stacks.
/// </summary>
[FieldOffset(4)]
public byte MeditationStacks;
public byte MeditationStacks => this.meditationStacks;
/// <summary>
/// Gets the active Sen.
/// </summary>
[FieldOffset(5)]
public Sen Sen;
public Sen Sen => this.sen;
/// <summary>
/// Gets if the Setsu Sen is active.

View file

@ -8,28 +8,36 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct SCHGauge
{
[FieldOffset(2)]
private byte numAetherflowStacks;
[FieldOffset(3)]
private byte fairyGaugeAmount;
[FieldOffset(4)]
private short seraphTimer;
[FieldOffset(6)]
private DismissedFairy dismissedFairy;
/// <summary>
/// Gets the amount of Aetherflow stacks available.
/// </summary>
[FieldOffset(2)]
public byte NumAetherflowStacks;
public byte NumAetherflowStacks => this.numAetherflowStacks;
/// <summary>
/// Gets the current level of the Fairy Gauge.
/// </summary>
[FieldOffset(3)]
public byte FairyGaugeAmount;
public byte FairyGaugeAmount => this.fairyGaugeAmount;
/// <summary>
/// Gets the Seraph time remaining in milliseconds.
/// </summary>
[FieldOffset(4)]
public short SeraphTimer;
public short SeraphTimer => this.seraphTimer;
/// <summary>
/// Gets the last dismissed fairy.
/// </summary>
[FieldOffset(6)]
public DismissedFairy DismissedFairy;
public DismissedFairy DismissedFairy => this.dismissedFairy;
}
}

View file

@ -8,30 +8,38 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct SMNGauge
{
[FieldOffset(0)]
private short timerRemaining;
[FieldOffset(2)]
private SummonPet returnSummon;
[FieldOffset(3)]
private PetGlam returnSummonGlam;
[FieldOffset(4)]
private byte numStacks;
/// <summary>
/// Gets the time remaining for the current summon.
/// </summary>
[FieldOffset(0)]
public short TimerRemaining;
public short TimerRemaining => this.timerRemaining;
/// <summary>
/// Gets the summon that will return after the current summon expires.
/// </summary>
[FieldOffset(2)]
public SummonPet ReturnSummon;
public SummonPet ReturnSummon => this.returnSummon;
/// <summary>
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
/// </summary>
[FieldOffset(3)]
public PetGlam ReturnSummonGlam;
public PetGlam ReturnSummonGlam => this.returnSummonGlam;
/// <summary>
/// Gets the current stacks.
/// Use the summon accessors instead.
/// </summary>
[FieldOffset(4)]
public byte NumStacks;
public byte NumStacks => this.numStacks;
/// <summary>
/// Gets if Phoenix is ready to be summoned.

View file

@ -8,10 +8,12 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct WARGauge
{
[FieldOffset(0)]
private byte beastGaugeAmount;
/// <summary>
/// Gets the amount of wrath in the Beast gauge.
/// </summary>
[FieldOffset(0)]
public byte BeastGaugeAmount;
public byte BeastGaugeAmount => this.beastGaugeAmount;
}
}

View file

@ -8,22 +8,28 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)]
public struct WHMGauge
{
[FieldOffset(2)]
private short lilyTimer;
[FieldOffset(4)]
private byte numLilies;
[FieldOffset(5)]
private byte numBloodLily;
/// <summary>
/// Gets the time to next lily in milliseconds.
/// </summary>
[FieldOffset(2)]
public short LilyTimer;
public short LilyTimer => this.lilyTimer;
/// <summary>
/// Gets the number of Lilies.
/// </summary>
[FieldOffset(4)]
public byte NumLilies;
public byte NumLilies => this.numLilies;
/// <summary>
/// Gets the number of times the blood lily has been nourished.
/// </summary>
[FieldOffset(5)]
public byte NumBloodLily;
public byte NumBloodLily => this.numBloodLily;
}
}

View file

@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
namespace Dalamud.Game.ClientState.Structs
{

View file

@ -27,7 +27,7 @@ namespace Dalamud.Game.Command
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="language">The client language requested.</param>
public CommandManager(Dalamud dalamud, ClientLanguage language)
internal CommandManager(Dalamud dalamud, ClientLanguage language)
{
this.dalamud = dalamud;
@ -128,7 +128,8 @@ namespace Dalamud.Game.Command
/// <returns>If adding was successful.</returns>
public bool AddHandler(string command, CommandInfo info)
{
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
if (info == null)
throw new ArgumentNullException(nameof(info), "Command handler is null.");
try
{

383
Dalamud/Game/GameVersion.cs Normal file
View file

@ -0,0 +1,383 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Dalamud.Game
{
/// <summary>
/// A GameVersion object contains give hierarchical numeric components: year, month,
/// day, major and minor. All components may be unspecified, which is represented
/// internally as a -1. By definition, an unspecified component matches anything
/// (both unspecified and specified), and an unspecified component is "less than" any
/// specified component. It will also equal the string "any" if all components are
/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game
/// installation directory.
/// </summary>
[Serializable]
public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersion>, IEquatable<GameVersion>
{
private static readonly GameVersion AnyVersion = new();
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="version">Version string to parse.</param>
[JsonConstructor]
public GameVersion(string version)
{
var ver = Parse(version);
this.Year = ver.Year;
this.Month = ver.Month;
this.Day = ver.Day;
this.Major = ver.Major;
this.Minor = ver.Minor;
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="year">The year.</param>
/// <param name="month">The month.</param>
/// <param name="day">The day.</param>
/// <param name="major">The major version.</param>
/// <param name="minor">The minor version.</param>
public GameVersion(int year, int month, int day, int major, int minor)
{
if ((this.Year = year) < 0)
throw new ArgumentOutOfRangeException(nameof(year));
if ((this.Month = month) < 0)
throw new ArgumentOutOfRangeException(nameof(month));
if ((this.Day = day) < 0)
throw new ArgumentOutOfRangeException(nameof(day));
if ((this.Major = major) < 0)
throw new ArgumentOutOfRangeException(nameof(major));
if ((this.Minor = minor) < 0)
throw new ArgumentOutOfRangeException(nameof(minor));
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="year">The year.</param>
/// <param name="month">The month.</param>
/// <param name="day">The day.</param>
/// <param name="major">The major version.</param>
public GameVersion(int year, int month, int day, int major)
{
if ((this.Year = year) < 0)
throw new ArgumentOutOfRangeException(nameof(year));
if ((this.Month = month) < 0)
throw new ArgumentOutOfRangeException(nameof(month));
if ((this.Day = day) < 0)
throw new ArgumentOutOfRangeException(nameof(day));
if ((this.Major = major) < 0)
throw new ArgumentOutOfRangeException(nameof(major));
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="year">The year.</param>
/// <param name="month">The month.</param>
/// <param name="day">The day.</param>
public GameVersion(int year, int month, int day)
{
if ((this.Year = year) < 0)
throw new ArgumentOutOfRangeException(nameof(year));
if ((this.Month = month) < 0)
throw new ArgumentOutOfRangeException(nameof(month));
if ((this.Day = day) < 0)
throw new ArgumentOutOfRangeException(nameof(day));
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="year">The year.</param>
/// <param name="month">The month.</param>
public GameVersion(int year, int month)
{
if ((this.Year = year) < 0)
throw new ArgumentOutOfRangeException(nameof(year));
if ((this.Month = month) < 0)
throw new ArgumentOutOfRangeException(nameof(month));
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
/// <param name="year">The year.</param>
public GameVersion(int year)
{
if ((this.Year = year) < 0)
throw new ArgumentOutOfRangeException(nameof(year));
}
/// <summary>
/// Initializes a new instance of the <see cref="GameVersion"/> class.
/// </summary>
public GameVersion()
{
}
/// <summary>
/// Gets the default "any" game version.
/// </summary>
public static GameVersion Any => AnyVersion;
/// <summary>
/// Gets the year component.
/// </summary>
public int Year { get; } = -1;
/// <summary>
/// Gets the month component.
/// </summary>
public int Month { get; } = -1;
/// <summary>
/// Gets the day component.
/// </summary>
public int Day { get; } = -1;
/// <summary>
/// Gets the major version component.
/// </summary>
public int Major { get; } = -1;
/// <summary>
/// Gets the minor version component.
/// </summary>
public int Minor { get; } = -1;
public static implicit operator GameVersion(string ver)
{
return Parse(ver);
}
public static bool operator ==(GameVersion v1, GameVersion v2)
{
if (v1 is null)
{
return v2 is null;
}
return v1.Equals(v2);
}
public static bool operator !=(GameVersion v1, GameVersion v2)
{
return !(v1 == v2);
}
public static bool operator <(GameVersion v1, GameVersion v2)
{
if (v1 is null)
throw new ArgumentNullException(nameof(v1));
return v1.CompareTo(v2) < 0;
}
public static bool operator <=(GameVersion v1, GameVersion v2)
{
if (v1 is null)
throw new ArgumentNullException(nameof(v1));
return v1.CompareTo(v2) <= 0;
}
public static bool operator >(GameVersion v1, GameVersion v2)
{
return v2 < v1;
}
public static bool operator >=(GameVersion v1, GameVersion v2)
{
return v2 <= v1;
}
/// <summary>
/// Parse a version string. YYYY.MM.DD.majr.minr or "any".
/// </summary>
/// <param name="input">Input to parse.</param>
/// <returns>GameVersion object.</returns>
public static GameVersion Parse(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.ToLower(CultureInfo.InvariantCulture) == "any")
return new GameVersion();
var parts = input.Split('.');
var tplParts = parts.Select(p =>
{
var result = int.TryParse(p, out var value);
return (result, value);
}).ToArray();
if (tplParts.Any(t => !t.result))
throw new FormatException("Bad formatting");
var intParts = tplParts.Select(t => t.value).ToArray();
var len = intParts.Length;
if (len == 1)
return new GameVersion(intParts[0]);
else if (len == 2)
return new GameVersion(intParts[0], intParts[1]);
else if (len == 3)
return new GameVersion(intParts[0], intParts[1], intParts[2]);
else if (len == 4)
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]);
else if (len == 5)
return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]);
else
throw new ArgumentException("Too many parts");
}
/// <summary>
/// Try to parse a version string. YYYY.MM.DD.majr.minr or "any".
/// </summary>
/// <param name="input">Input to parse.</param>
/// <param name="result">GameVersion object.</param>
/// <returns>Success or failure.</returns>
public static bool TryParse(string input, out GameVersion result)
{
try
{
result = Parse(input);
return true;
}
catch
{
result = null;
return false;
}
}
/// <inheritdoc/>
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
/// <inheritdoc/>
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (obj is GameVersion value)
{
return this.CompareTo(value);
}
else
{
throw new ArgumentException("Argument must be a GameVersion");
}
}
/// <inheritdoc/>
public int CompareTo(GameVersion value)
{
if (value == null)
return 1;
if (this == value)
return 0;
if (this == AnyVersion)
return 1;
if (value == AnyVersion)
return -1;
if (this.Year != value.Year)
return this.Year > value.Year ? 1 : -1;
if (this.Month != value.Month)
return this.Month > value.Month ? 1 : -1;
if (this.Day != value.Day)
return this.Day > value.Day ? 1 : -1;
if (this.Major != value.Major)
return this.Major > value.Major ? 1 : -1;
if (this.Minor != value.Minor)
return this.Minor > value.Minor ? 1 : -1;
return 0;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is not GameVersion value)
return false;
return this.Equals(value);
}
/// <inheritdoc/>
public bool Equals(GameVersion value)
{
if (value == null)
{
return false;
}
return
(this.Year == value.Year) &&
(this.Month == value.Month) &&
(this.Day == value.Day) &&
(this.Major == value.Major) &&
(this.Minor == value.Minor);
}
/// <inheritdoc/>
public override int GetHashCode()
{
var accumulator = 0;
// This might be horribly wrong, but it isn't used heavily.
accumulator |= this.Year.GetHashCode();
accumulator |= this.Month.GetHashCode();
accumulator |= this.Day.GetHashCode();
accumulator |= this.Major.GetHashCode();
accumulator |= this.Minor.GetHashCode();
return accumulator;
}
/// <inheritdoc/>
public override string ToString()
{
if (this.Year == -1 &&
this.Month == -1 &&
this.Day == -1 &&
this.Major == -1 &&
this.Minor == -1)
return "any";
return new StringBuilder()
.Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year))
.Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month))
.Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day))
.Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major))
.Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor))
.ToString();
}
}
}

View file

@ -0,0 +1,80 @@
using System;
using Newtonsoft.Json;
namespace Dalamud.Game
{
/// <summary>
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
/// </summary>
public sealed class GameVersionConverter : JsonConverter
{
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else if (value is GameVersion)
{
writer.WriteValue(value.ToString());
}
else
{
throw new JsonSerializationException("Expected GameVersion object value");
}
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
if (reader.TokenType == JsonToken.String)
{
try
{
return new GameVersion((string)reader.Value!);
}
catch (Exception ex)
{
throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex);
}
}
else
{
throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}");
}
}
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(GameVersion);
}
}
}

View file

@ -29,13 +29,13 @@ namespace Dalamud.Game.Internal
this.debugCheckAddress = IntPtr.Zero;
}
Log.Verbose("DebugCheck address {DebugCheckAddress}", this.debugCheckAddress);
Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}");
}
/// <summary>
/// Gets a value indicating whether the anti-debugging is enabled.
/// </summary>
public bool IsEnabled { get; private set; }
public bool IsEnabled { get; private set; } = false;
/// <summary>
/// Enables the anti-debugging by overwriting code in memory.
@ -45,7 +45,7 @@ namespace Dalamud.Game.Internal
this.original = new byte[this.nop.Length];
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled)
{
Log.Information($"Overwriting debug check @ 0x{this.debugCheckAddress.ToInt64():X}");
Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}");
SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original);
SafeMemory.WriteBytes(this.debugCheckAddress, this.nop);
}
@ -64,7 +64,7 @@ namespace Dalamud.Game.Internal
{
if (this.debugCheckAddress != IntPtr.Zero && this.original != null)
{
Log.Information($"Reverting debug check @ 0x{this.debugCheckAddress.ToInt64():X}");
Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}");
SafeMemory.WriteBytes(this.debugCheckAddress, this.original);
}
else
@ -110,9 +110,11 @@ namespace Dalamud.Game.Internal
{
// If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded.
// If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the
// check in either situation anyways.
// this.Disable();
// check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it.
this.Disable();
}
this.disposed = true;
}
}
}

Some files were not shown because too many files have changed in this diff Show more