Merge branch 'goatcorp-master' into feat/mb-purchases

This commit is contained in:
karashiiro 2021-07-24 23:28:47 -07:00
commit dafb08403e
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_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = 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.severity = warning
dotnet_naming_rule.event_rule.style = on_upper_camel_case_style dotnet_naming_rule.event_rule.style = on_upper_camel_case_style
dotnet_naming_rule.event_rule.symbols = event_symbols 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_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly 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_arithmetic_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members: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 properties
resharper_align_linq_query = true 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_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_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}] [*.{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_style = space
indent_size = 4 indent_size = 4
tab_width = 4 tab_width = 4
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent

View file

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

View file

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

3
.gitmodules vendored
View file

@ -4,3 +4,6 @@
[submodule "lib/FFXIVClientStructs"] [submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs path = lib/FFXIVClientStructs
url = https://github.com/goatcorp/FFXIVClientStructs.git 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 { Set-Location $args[0]
$hashes.Add($_.Name, (Get-FileHash $_.FullName -Algorithm MD5).Hash)
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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target"> <PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget> <TargetFramework>net5.0</TargetFramework>
<TargetFramework>net48</TargetFramework> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<LangVersion>8.0</LangVersion> <PlatformTarget>x64</PlatformTarget>
<Platforms>AnyCPU;x64</Platforms> <Platforms>x64;AnyCPU</Platforms>
</PropertyGroup> <LangVersion>9.0</LangVersion>
<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>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Feature"> <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> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>5.2.4.6</AssemblyVersion> </PropertyGroup>
<FileVersion>5.2.4.6</FileVersion>
<Description>XIVLauncher addon injection</Description> <PropertyGroup Label="Configuration">
<Version>5.2.4.6</Version> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'"> <PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase> <AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap> <PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
<Deterministic>true</Deterministic>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <PropertyGroup Label="Warnings">
<PackageIcon></PackageIcon> <NoWarn>IDE1006;CS1591;CS1701;CS1702</NoWarn>
<PackageIconUrl /> <!-- IDE1006 - Naming violation -->
<ApplicationIcon>dalamud.ico</ApplicationIcon> <!-- CS1591 - Missing XML comment for publicly visible type or member -->
<!-- CS1701 - Runtime policy may be needed -->
<!-- CS1702 - Runtime policy may be needed -->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="stylecop.json" /> <PackageReference Include="Iced" Version="1.12.0" />
</ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<ItemGroup> <PackageReference Include="PeNet" Version="2.6.3" />
<AdditionalFiles Include="stylecop.json" /> <PackageReference Include="Reloaded.Memory" Version="4.1.1" />
</ItemGroup> <PackageReference Include="Reloaded.Memory.Buffers" Version="1.3.5" />
<ItemGroup> <PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="EasyHook" Version="2.7.6270" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333"> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DalamudDebugStub\DalamudDebugStub.vcxproj" /> <AdditionalFiles Include="..\stylecop.json" />
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
</ItemGroup> </ItemGroup>
<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> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)'=='Release'">
<Exec Command="powershell -ExecutionPolicy Unrestricted $(SolutionDir)CreateHashList.ps1 $(OutputPath)" />
</Target>
</Project> </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; using System.Diagnostics.CodeAnalysis;
// General // 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.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
[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", "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", "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.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("Performance", "CA1822:Mark members as static", Justification = "I'll make what I want static", 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)")]

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;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security;
namespace Dalamud.Injector namespace Dalamud.Injector
{ {
/// <summary> /// <summary>
/// Native functions. /// Native user32 functions.
/// </summary> /// </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> /// <summary>
/// MEM_* from memoryapi. /// MEM_* from memoryapi.
@ -20,14 +240,14 @@ namespace Dalamud.Injector
/// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce /// 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. /// placeholders, lpAddress and dwSize must exactly match those of the placeholder.
/// </summary> /// </summary>
CoalescePlaceholders = 0x00000001, CoalescePlaceholders = 0x1,
/// <summary> /// <summary>
/// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using /// 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 /// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify
/// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER. /// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER.
/// </summary> /// </summary>
PreservePlaceholder = 0x00000002, PreservePlaceholder = 0x2,
/// <summary> /// <summary>
/// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved /// 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 /// 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 /// 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 /// 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. /// protection value, such as PAGE_NOACCESS.
/// </summary> /// </summary>
ResetUndo = 0x1000000, ResetUndo = 0x1000000,
@ -122,6 +342,28 @@ namespace Dalamud.Injector
LargePages = 0x20000000, 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> /// <summary>
/// PAGE_* from memoryapi. /// PAGE_* from memoryapi.
/// </summary> /// </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 /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
/// targets for CFG. /// targets for CFG.
/// </summary> /// </summary>
TargetsNoUpdate = 0x40000000, TargetsNoUpdate = TargetsInvalid,
/// <summary> /// <summary>
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a /// 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> /// <summary>
/// Closes an open object handle. /// WAIT_* from synchapi.
/// </summary> /// </summary>
/// <param name="hObject"> public enum WaitResult
/// A valid handle to an open object. {
/// </param> /// <summary>
/// <returns> /// The specified object is a mutex object that was not released by the thread that owned the mutex object
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.To get extended /// before the owning thread terminated.Ownership of the mutex object is granted to the calling thread and
/// error information, call GetLastError. If the application is running under a debugger, the function will throw an /// the mutex state is set to nonsignaled. If the mutex was protecting persistent state information, you
/// exception if it receives either a handle value that is not valid or a pseudo-handle value. This can happen if you /// should check it for consistency.
/// close a handle twice, or if you call CloseHandle on a handle returned by the FindFirstFile function instead of calling /// </summary>
/// the FindClose function. Abandoned = 0x80,
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)] /// <summary>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] /// The state of the specified object is signaled.
[SuppressUnmanagedCodeSecurity] /// </summary>
[return: MarshalAs(UnmanagedType.Bool)] Object0 = 0x0,
public static extern bool CloseHandle(IntPtr hObject);
/// <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> /// <summary>
/// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function /// 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> /// </summary>
/// <param name="hProcess"> /// <param name="hProcess">
/// A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, /// 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 /// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without
/// without these rights on certain platforms. For more information, see Process Security and Access Rights. /// these rights on certain platforms. For more information, see Process Security and Access Rights.
/// </param> /// </param>
/// <param name="lpThreadAttributes"> /// <param name="lpThreadAttributes">
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines /// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether
/// whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default /// child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor
/// security descriptor and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor /// and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from
/// for a thread come from the primary token of the creator. /// the primary token of the creator.
/// </param> /// </param>
/// <param name="dwStackSize"> /// <param name="dwStackSize">
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is /// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the
/// 0 (zero), the new thread uses the default size for the executable. For more information, see Thread Stack Size. /// new thread uses the default size for the executable. For more information, see Thread Stack Size.
/// </param> /// </param>
/// <param name="lpStartAddress"> /// <param name="lpStartAddress">
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and /// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the
/// represents the starting address of the thread in the remote process. The function must exist in the remote process. /// starting address of the thread in the remote process. The function must exist in the remote process. For more information,
/// For more information, see ThreadProc. /// see ThreadProc.
/// </param> /// </param>
/// <param name="lpParameter"> /// <param name="lpParameter">
/// A pointer to a variable to be passed to the thread function. /// 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. /// The flags that control the creation of the thread.
/// </param> /// </param>
/// <param name="lpThreadId"> /// <param name="lpThreadId">
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is /// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
/// not returned.
/// </param> /// </param>
/// <returns> /// <returns>
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value /// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is
/// is NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if /// NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress
/// lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs, /// points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and
/// an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as /// the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process.
/// an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where /// This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to
/// the process is created even if it refers to invalid or missing dynamic-link libraries (DLL). /// invalid or missing dynamic-link libraries (DLL).
/// </returns> /// </returns>
[DllImport("kernel32.dll")] [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread( public static extern IntPtr CreateRemoteThread(
IntPtr hProcess, IntPtr hProcess,
IntPtr lpThreadAttributes, IntPtr lpThreadAttributes,
uint dwStackSize, UIntPtr dwStackSize,
IntPtr lpStartAddress, IntPtr lpStartAddress,
IntPtr lpParameter, IntPtr lpParameter,
uint dwCreationFlags, CreateThreadFlags dwCreationFlags,
IntPtr lpThreadId); out uint lpThreadId);
/// <summary> /// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. /// Retrieves the termination status of the specified thread.
/// 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.
/// </summary> /// </summary>
/// <param name="lpModuleName"> /// <param name="hThread">
/// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default library /// A handle to the thread. The handle must have the THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION
/// extension .dll is appended. The file name string can include a trailing point character (.) to indicate that the /// access right.For more information, see Thread Security and Access Rights.
/// module name has no extension. The string does not have to specify a path. When specifying a path, be sure to use /// </param>
/// backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules currently /// <param name="lpExitCode">
/// mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns a handle /// A pointer to a variable to receive the thread termination status.
/// 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> /// </param>
/// <returns> /// <returns>
/// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get
/// value is NULL.To get extended error information, call GetLastError. /// 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.
/// </returns> /// </returns>
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess( [return: MarshalAs(UnmanagedType.Bool)]
ProcessAccessFlags processAccess, public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
bool bInheritHandle,
int processId);
/// <summary> /// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex. /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.
@ -530,6 +733,27 @@ namespace Dalamud.Injector
int dwSize, int dwSize,
AllocationType dwFreeType); 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> /// <summary>
/// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or /// 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. /// 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"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project Sdk="Microsoft.NET.Sdk">
<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')" /> <PropertyGroup Label="Target">
<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')" /> <TargetFramework>net5.0-windows</TargetFramework>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" /> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PlatformTarget>x64</PlatformTarget>
<PropertyGroup> <Platforms>x64;AnyCPU</Platforms>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <LangVersion>9.0</LangVersion>
<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>
</PropertyGroup> </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'))" /> <PropertyGroup Label="Feature">
<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'))" /> <RootNamespace>Dalamud.Test</RootNamespace>
<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'))" /> <AssemblyTitle>Dalamud.Test</AssemblyTitle>
</Target> <AssemblyName>Dalamud.Test</AssemblyName>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" /> <Product>Dalamud.Test</Product>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <Description>Unit tests for Dalamud</Description>
Other similar extension points exist, see Microsoft.Common.targets. <Company>goatcorp</Company>
<Target Name="BeforeBuild"> <Copyright>Copyright © goatcorp 2021</Copyright>
</Target> </PropertyGroup>
<Target Name="AfterBuild">
</Target> <PropertyGroup Label="Output">
--> <OutputType>Library</OutputType>
</Project> <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.Collections.Generic;
using System.Linq; using System.Linq;
using Xunit; using Xunit;
namespace Dalamud.Test.Game.Text.Sanitizer { // ReSharper disable StringLiteralTypo
namespace Dalamud.Test.Game.Text.Sanitizer
public class SanitizerTests { {
public class SanitizerTests
{
private global::Dalamud.Game.Text.Sanitizer.Sanitizer sanitizer; private global::Dalamud.Game.Text.Sanitizer.Sanitizer sanitizer;
[Theory] [Theory]
[InlineData( ClientLanguage.English, "Pixie Cotton Hood of Healing", "Pixie Cotton Hood of Healing" )] [InlineData(ClientLanguage.English, "Pixie Cotton Hood of Healing", "Pixie Cotton Hood of Healing")]
[InlineData( ClientLanguage.Japanese, "アラガントームストーン:真理", "アラガントームストーン:真理" )] [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, "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.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, "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" )] [InlineData(ClientLanguage.French, "Cuir de bœuf", "Cuir de boeuf")]
public void StringsAreSanitizedCorrectly( public void StringsAreSanitizedCorrectly(ClientLanguage clientLanguage, string unsanitizedString, string sanitizedString)
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); sanitizer = new global::Dalamud.Game.Text.Sanitizer.Sanitizer(clientLanguage);
Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString)); Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString));
Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings).First()); Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings).First());
sanitizer = new global::Dalamud.Game.Text.Sanitizer.Sanitizer(ClientLanguage.English); sanitizer = new global::Dalamud.Game.Text.Sanitizer.Sanitizer(ClientLanguage.English);
Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString, clientLanguage)); Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString, clientLanguage));
Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings, clientLanguage).First()); 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 System.Reflection;
using Xunit; using Xunit;
namespace Dalamud.Test { namespace Dalamud.Test
public class LocalizationTests { {
public class LocalizationTests
{
private readonly Localization localization; private readonly Localization localization;
private string currentLangCode; private string currentLangCode;
public LocalizationTests() { public LocalizationTests()
{
var workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
this.localization = new Localization(workingDir, "dalamud_"); this.localization = new Localization(workingDir, "dalamud_");
this.localization.OnLocalizationChanged += code => this.currentLangCode = code; this.localization.OnLocalizationChanged += code => this.currentLangCode = code;
} }
[Fact] [Fact]
public void SetupWithFallbacks_EventInvoked() { public void SetupWithFallbacks_EventInvoked()
{
this.localization.SetupWithFallbacks(); this.localization.SetupWithFallbacks();
Assert.Equal("en", this.currentLangCode); 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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.29215.179 VisualStudioVersion = 16.0.31424.327
MinimumVisualStudioVersion = 10.0.40219.1 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.csproj", "{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
EndProject 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{85480198-8711-4355-830E-72FD794AD3F6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
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}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject 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 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64 Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {94E5B016-02B1-459B-97D9-E783F28764B2}.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.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.ActiveCfg = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.Build.0 = 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}.Release|Any CPU.ActiveCfg = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.Build.0 = Debug|Any CPU {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|x64
{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|x64.ActiveCfg = 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|x64.Build.0 = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.ActiveCfg = Release|Any CPU {55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.ActiveCfg = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.Build.0 = Release|Any CPU {55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.Build.0 = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.ActiveCfg = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.Build.0 = 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}.Release|Any CPU.ActiveCfg = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.Build.0 = Debug|Any CPU {5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
{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|x64.ActiveCfg = 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|x64.Build.0 = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.ActiveCfg = Debug|Any CPU {8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.Build.0 = Debug|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.ActiveCfg = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.Build.0 = Release|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.ActiveCfg = Debug|x64 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.Build.0 = Debug|x64 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.ActiveCfg = Debug|x86 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.Build.0 = Debug|x86 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.Build.0 = Release|Any CPU {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.ActiveCfg = Release|x64 {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.Build.0 = Release|x64 {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.ActiveCfg = Release|x86 {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.Build.0 = Release|x86 {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|Any CPU {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|Any CPU {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.ActiveCfg = Debug|Any CPU {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.Build.0 = Debug|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.ActiveCfg = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.Build.0 = Release|Any CPU {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.ActiveCfg = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|Any CPU.ActiveCfg = Debug|x64 {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.Build.0 = Release|x64
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|x64.ActiveCfg = Debug|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Debug|x86.ActiveCfg = Debug|Win32 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|Any CPU.ActiveCfg = Release|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.ActiveCfg = Debug|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|Any CPU.Build.0 = Release|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.Build.0 = Debug|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x64.ActiveCfg = Release|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x64.Build.0 = Release|x64 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.Build.0 = Release|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x86.ActiveCfg = Release|Win32 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.ActiveCfg = Release|Any CPU
{9FDA9A5C-50C6-4333-8DCE-DFEB89363F2A}.Release|x86.Build.0 = Release|Win32 {4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.Build.0 = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x64.ActiveCfg = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x64.Build.0 = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x86.ActiveCfg = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Debug|x86.Build.0 = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.ActiveCfg = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|Any CPU.Build.0 = Release|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.Build.0 = Release|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x64.ActiveCfg = Release|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x64.Build.0 = Release|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x86.ActiveCfg = Release|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DBAEE68-9D94-4807-BCB1-E42EDD52B489}.Release|x86.Build.0 = Release|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Debug|x64.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|Any CPU.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.ActiveCfg = Debug|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|x64.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|Any CPU {F3F0CC3A-DE2E-403F-88B4-B47C62582477}.Release|x64.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.ActiveCfg = Debug|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.Build.0 = Debug|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.ActiveCfg = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.Build.0 = Debug|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.Build.0 = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.ActiveCfg = Release|Any CPU
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution 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} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599} SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}

View file

@ -5,20 +5,21 @@ using System.IO;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
using Serilog; using Serilog;
using Serilog.Events;
namespace Dalamud.Configuration namespace Dalamud.Configuration.Internal
{ {
/// <summary> /// <summary>
/// Class containing Dalamud settings. /// Class containing Dalamud settings.
/// </summary> /// </summary>
[Serializable] [Serializable]
internal class DalamudConfiguration internal sealed class DalamudConfiguration
{ {
[JsonIgnore] [JsonIgnore]
private string configPath; private string configPath;
/// <summary> /// <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> /// </summary>
/// <param name="dalamudConfiguration">The current dalamud configuration.</param> /// <param name="dalamudConfiguration">The current dalamud configuration.</param>
public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration); public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration);
@ -68,15 +69,28 @@ namespace Dalamud.Configuration
/// </summary> /// </summary>
public bool DoDalamudTest { get; set; } 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> /// <summary>
/// Gets or sets a list of custom repos. /// Gets or sets a list of custom repos.
/// </summary> /// </summary>
public List<ThirdRepoSetting> ThirdRepoList { get; set; } = new List<ThirdRepoSetting>(); public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
/// <summary> /// <summary>
/// Gets or sets a list of hidden plugins. /// Gets or sets a list of hidden plugins.
/// </summary> /// </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> /// <summary>
/// Gets or sets the global UI scale. /// Gets or sets the global UI scale.
@ -113,6 +127,11 @@ namespace Dalamud.Configuration
/// </summary> /// </summary>
public bool DoButtonsSystemMenu { get; set; } = true; 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> /// <summary>
/// Gets or sets a value indicating whether or not the debug log should scroll automatically. /// Gets or sets a value indicating whether or not the debug log should scroll automatically.
/// </summary> /// </summary>
@ -138,6 +157,21 @@ namespace Dalamud.Configuration
/// </summary> /// </summary>
public bool IsGamepadNavigationEnabled { get; set; } = true; 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> /// <summary>
/// Load a configuration from the provided path. /// Load a configuration from the provided path.
/// </summary> /// </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> /// <summary>
/// Third party repository for dalamud plugins. /// Third party repository for dalamud plugins.
/// </summary> /// </summary>
internal class ThirdRepoSetting internal sealed class ThirdPartyRepoSettings
{ {
/// <summary> /// <summary>
/// Gets or sets the third party repo url. /// Gets or sets the third party repo url.
@ -16,16 +16,14 @@ namespace Dalamud.Configuration
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
/// <summary> /// <summary>
/// Create new instance of third party repo object. /// Gets or sets a short name for the repo url.
/// </summary> /// </summary>
/// <returns>New instance of third party repo.</returns> public string Name { get; set; }
public ThirdRepoSetting Clone()
{ /// <summary>
return new ThirdRepoSetting /// Clone this object.
{ /// </summary>
Url = this.Url, /// <returns>A shallow copy of this object.</returns>
IsEnabled = this.IsEnabled, public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings;
};
}
} }
} }

View file

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Dalamud.Configuration namespace Dalamud.Configuration
@ -7,7 +8,7 @@ namespace Dalamud.Configuration
/// <summary> /// <summary>
/// Configuration to store settings for a dalamud plugin. /// Configuration to store settings for a dalamud plugin.
/// </summary> /// </summary>
public class PluginConfigurations public sealed class PluginConfigurations
{ {
private readonly DirectoryInfo configDirectory; private readonly DirectoryInfo configDirectory;
@ -44,6 +45,7 @@ namespace Dalamud.Configuration
/// </summary> /// </summary>
/// <param name="pluginName">Plugin name.</param> /// <param name="pluginName">Plugin name.</param>
/// <returns>Plugin configuration.</returns> /// <returns>Plugin configuration.</returns>
[CanBeNull]
public IPluginConfiguration Load(string pluginName) public IPluginConfiguration Load(string pluginName)
{ {
var path = this.GetConfigFile(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> /// <summary>
/// Get plugin directory. /// Get plugin directory.
/// </summary> /// </summary>

View file

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

View file

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

View file

@ -1,12 +1,15 @@
using System; using System;
using Dalamud.Game;
using Newtonsoft.Json;
namespace Dalamud namespace Dalamud
{ {
/// <summary> /// <summary>
/// Class containing information needed to initialize Dalamud. /// Struct containing information needed to initialize Dalamud.
/// </summary> /// </summary>
[Serializable] [Serializable]
public sealed class DalamudStartInfo public struct DalamudStartInfo
{ {
/// <summary> /// <summary>
/// The working directory of the XIVLauncher installations. /// The working directory of the XIVLauncher installations.
@ -41,7 +44,8 @@ namespace Dalamud
/// <summary> /// <summary>
/// The current game version code. /// The current game version code.
/// </summary> /// </summary>
public string GameVersion; [JsonConverter(typeof(GameVersionConverter))]
public GameVersion GameVersion;
/// <summary> /// <summary>
/// Whether or not market board information should be uploaded by default. /// 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.Data.LuminaExtensions;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal;
using ImGuiScene; using ImGuiScene;
using JetBrains.Annotations; using JetBrains.Annotations;
using Lumina; using Lumina;
@ -21,7 +22,7 @@ namespace Dalamud.Data
/// <summary> /// <summary>
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
/// </summary> /// </summary>
public class DataManager : IDisposable public sealed class DataManager : IDisposable
{ {
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
private readonly InterfaceManager interfaceManager; private readonly InterfaceManager interfaceManager;
@ -32,6 +33,7 @@ namespace Dalamud.Data
private GameData gameData; private GameData gameData;
private Thread luminaResourceThread; private Thread luminaResourceThread;
private CancellationTokenSource luminaCancellationTokenSource;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DataManager"/> class. /// Initializes a new instance of the <see cref="DataManager"/> class.
@ -41,8 +43,9 @@ namespace Dalamud.Data
internal DataManager(ClientLanguage language, InterfaceManager interfaceManager) internal DataManager(ClientLanguage language, InterfaceManager interfaceManager)
{ {
this.interfaceManager = interfaceManager; this.interfaceManager = interfaceManager;
// Set up default values so plugins do not null-reference when data is being loaded. // 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; this.Language = language;
} }
@ -99,7 +102,7 @@ namespace Dalamud.Data
ClientLanguage.English => Lumina.Data.Language.English, ClientLanguage.English => Lumina.Data.Language.English,
ClientLanguage.German => Lumina.Data.Language.German, ClientLanguage.German => Lumina.Data.Language.German,
ClientLanguage.French => Lumina.Data.Language.French, 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); return this.Excel.GetSheet<T>(lang);
} }
@ -162,7 +165,7 @@ namespace Dalamud.Data
ClientLanguage.English => "en/", ClientLanguage.English => "en/",
ClientLanguage.German => "de/", ClientLanguage.German => "de/",
ClientLanguage.French => "fr/", 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); return this.GetIcon(type, iconId);
@ -232,7 +235,7 @@ namespace Dalamud.Data
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
this.luminaResourceThread.Abort(); this.luminaCancellationTokenSource.Cancel();
} }
/// <summary> /// <summary>
@ -245,14 +248,14 @@ namespace Dalamud.Data
{ {
Log.Verbose("Starting data load..."); Log.Verbose("Starting data load...");
var zoneOpCodeDict = var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict); this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict = var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict); this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
@ -273,9 +276,7 @@ namespace Dalamud.Data
ClientLanguage.English => Lumina.Data.Language.English, ClientLanguage.English => Lumina.Data.Language.English,
ClientLanguage.German => Lumina.Data.Language.German, ClientLanguage.German => Lumina.Data.Language.German,
ClientLanguage.French => Lumina.Data.Language.French, ClientLanguage.French => Lumina.Data.Language.French,
_ => throw new ArgumentOutOfRangeException( _ => throw new ArgumentOutOfRangeException(nameof(this.Language), $"Unknown Language: {this.Language}"),
nameof(this.Language),
@"Unknown Language: " + this.Language),
}, },
}; };
@ -289,9 +290,12 @@ namespace Dalamud.Data
this.IsDataReady = true; 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) if (this.gameData.FileHandleManager.HasPendingFileLoads)
{ {
@ -302,8 +306,6 @@ namespace Dalamud.Data
Thread.Sleep(5); Thread.Sleep(5);
} }
} }
// ReSharper disable once FunctionNeverReturns
}); });
this.luminaResourceThread.Start(); this.luminaResourceThread.Start();
} }

View file

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

View file

@ -16,11 +16,11 @@ namespace Dalamud.Game.Addon
internal sealed unsafe partial class DalamudSystemMenu internal sealed unsafe partial class DalamudSystemMenu
{ {
private readonly Dalamud dalamud; private readonly Dalamud dalamud;
private AtkValueChangeType atkValueChangeType; private readonly AtkValueChangeType atkValueChangeType;
private AtkValueSetString atkValueSetString; private readonly AtkValueSetString atkValueSetString;
private Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu; private readonly Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
// TODO: Make this into events in Framework.Gui // TODO: Make this into events in Framework.Gui
private Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand; private readonly Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DalamudSystemMenu"/> class. /// 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); this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
var atkValueChangeTypeAddress = var atkValueChangeTypeAddress = this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??");
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
this.atkValueChangeType =
Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
var atkValueSetStringAddress = var atkValueSetStringAddress = this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED");
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress); 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 ?? ?? ?? ??"); 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) switch (commandId)
{ {
case 69420: case 69420:
this.dalamud.DalamudUi.TogglePluginInstaller(); this.dalamud.DalamudUi.TogglePluginInstallerWindow();
break; break;
case 69421: case 69421:
this.dalamud.DalamudUi.ToggleSettings(); this.dalamud.DalamudUi.ToggleSettingsWindow();
break; break;
default: default:
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@ -11,7 +10,6 @@ using CheapLoc;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Serilog; using Serilog;
namespace Dalamud.Game namespace Dalamud.Game
@ -21,39 +19,39 @@ namespace Dalamud.Game
/// </summary> /// </summary>
public class ChatHandlers public class ChatHandlers
{ {
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new() // private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
{ // {
{ "", "<:ffxive071:585847382210642069>" }, // { "", "<:ffxive071:585847382210642069>" },
{ "", "<:ffxive083:585848592699490329>" }, // { "", "<:ffxive083:585848592699490329>" },
}; // };
private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new() // private readonly Dictionary<XivChatType, Color> handledChatTypeColors = new()
{ // {
{ XivChatType.CrossParty, Color.DodgerBlue }, // { XivChatType.CrossParty, Color.DodgerBlue },
{ XivChatType.Party, Color.DodgerBlue }, // { XivChatType.Party, Color.DodgerBlue },
{ XivChatType.FreeCompany, Color.DeepSkyBlue }, // { XivChatType.FreeCompany, Color.DeepSkyBlue },
{ XivChatType.CrossLinkShell1, Color.ForestGreen }, // { XivChatType.CrossLinkShell1, Color.ForestGreen },
{ XivChatType.CrossLinkShell2, Color.ForestGreen }, // { XivChatType.CrossLinkShell2, Color.ForestGreen },
{ XivChatType.CrossLinkShell3, Color.ForestGreen }, // { XivChatType.CrossLinkShell3, Color.ForestGreen },
{ XivChatType.CrossLinkShell4, Color.ForestGreen }, // { XivChatType.CrossLinkShell4, Color.ForestGreen },
{ XivChatType.CrossLinkShell5, Color.ForestGreen }, // { XivChatType.CrossLinkShell5, Color.ForestGreen },
{ XivChatType.CrossLinkShell6, Color.ForestGreen }, // { XivChatType.CrossLinkShell6, Color.ForestGreen },
{ XivChatType.CrossLinkShell7, Color.ForestGreen }, // { XivChatType.CrossLinkShell7, Color.ForestGreen },
{ XivChatType.CrossLinkShell8, Color.ForestGreen }, // { XivChatType.CrossLinkShell8, Color.ForestGreen },
{ XivChatType.Ls1, Color.ForestGreen }, // { XivChatType.Ls1, Color.ForestGreen },
{ XivChatType.Ls2, Color.ForestGreen }, // { XivChatType.Ls2, Color.ForestGreen },
{ XivChatType.Ls3, Color.ForestGreen }, // { XivChatType.Ls3, Color.ForestGreen },
{ XivChatType.Ls4, Color.ForestGreen }, // { XivChatType.Ls4, Color.ForestGreen },
{ XivChatType.Ls5, Color.ForestGreen }, // { XivChatType.Ls5, Color.ForestGreen },
{ XivChatType.Ls6, Color.ForestGreen }, // { XivChatType.Ls6, Color.ForestGreen },
{ XivChatType.Ls7, Color.ForestGreen }, // { XivChatType.Ls7, Color.ForestGreen },
{ XivChatType.Ls8, Color.ForestGreen }, // { XivChatType.Ls8, Color.ForestGreen },
{ XivChatType.TellIncoming, Color.HotPink }, // { XivChatType.TellIncoming, Color.HotPink },
{ XivChatType.PvPTeam, Color.SandyBrown }, // { XivChatType.PvPTeam, Color.SandyBrown },
{ XivChatType.Urgent, Color.DarkViolet }, // { XivChatType.Urgent, Color.DarkViolet },
{ XivChatType.NoviceNetwork, Color.SaddleBrown }, // { XivChatType.NoviceNetwork, Color.SaddleBrown },
{ XivChatType.Echo, Color.Gray }, // { XivChatType.Echo, Color.Gray },
}; // };
private readonly Regex rmtRegex = new( 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", @"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 Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
private readonly Dalamud dalamud; private readonly Dalamud dalamud;
private DalamudLinkPayload openInstallerWindowLink; private readonly DalamudLinkPayload openInstallerWindowLink;
private bool hasSeenLoadingMsg; private bool hasSeenLoadingMsg;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChatHandlers"/> class. /// Initializes a new instance of the <see cref="ChatHandlers"/> class.
/// </summary> /// </summary>
/// <param name="dalamud">Dalamud instance.</param> /// <param name="dalamud">Dalamud instance.</param>
public ChatHandlers(Dalamud dalamud) internal ChatHandlers(Dalamud dalamud)
{ {
this.dalamud = dalamud; this.dalamud = dalamud;
@ -122,23 +120,20 @@ namespace Dalamud.Game
public string LastLink { get; private set; } public string LastLink { get; private set; }
/// <summary> /// <summary>
/// Convert a string to SeString and wrap in italics payloads. /// Convert a TextPayload to SeString and wrap in italics payloads.
/// </summary> /// </summary>
/// <param name="text">Text to convert.</param> /// <param name="text">Text to convert.</param>
/// <returns>SeString payload of italicized text.</returns> /// <returns>SeString payload of italicized text.</returns>
private static SeString MakeItalics(string text) public static SeString MakeItalics(string text)
{ => MakeItalics(new TextPayload(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,
}));
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) 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(); 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) 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) 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 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, Type = XivChatType.Notice,
}); });
if (DalamudChangelogWindow.WarrantsChangelog) if (this.dalamud.DalamudUi.WarrantsChangelog)
#pragma warning disable CS0162 // Unreachable code detected this.dalamud.DalamudUi.OpenChangelogWindow();
this.dalamud.DalamudUi.OpenChangelog();
#pragma warning restore CS0162 // Unreachable code detected
this.dalamud.Configuration.LastVersion = assemblyVersion; this.dalamud.Configuration.LastVersion = assemblyVersion;
this.dalamud.Configuration.Save(); 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) if (t.IsFaulted)
{ {
@ -278,19 +272,19 @@ namespace Dalamud.Game
} }
else else
{ {
var updatedPlugins = t.Result.UpdatedPlugins; var updatedPlugins = t.Result;
if (updatedPlugins != null && updatedPlugins.Any()) if (updatedPlugins != null && updatedPlugins.Any())
{ {
if (this.dalamud.Configuration.AutoUpdatePlugins) 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 else
{ {
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry 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(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(" ["), new TextPayload(" ["),
@ -300,7 +294,7 @@ namespace Dalamud.Game
RawPayload.LinkTerminator, RawPayload.LinkTerminator,
new UIForegroundPayload(this.dalamud.Data, 0), new UIForegroundPayload(this.dalamud.Data, 0),
new TextPayload("]"), new TextPayload("]"),
}).Encode(), }),
Type = XivChatType.Urgent, Type = XivChatType.Urgent,
}); });
} }

View file

@ -1,8 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer; using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
@ -14,204 +12,131 @@ namespace Dalamud.Game.ClientState.Actors
/// <summary> /// <summary>
/// This collection represents the currently spawned FFXIV actors. /// This collection represents the currently spawned FFXIV actors.
/// </summary> /// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable public sealed partial class ActorTable
{ {
private const int ActorTableLength = 424; private const int ActorTableLength = 424;
#region ReadProcessMemory Hack private readonly Dalamud dalamud;
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor)); private readonly ClientStateAddressResolver address;
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;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActorTable"/> class. /// Initializes a new instance of the <see cref="ActorTable"/> class.
/// Set up the actor table collection.
/// </summary> /// </summary>
/// <param name="dalamud">The Dalamud instance.</param> /// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param> /// <param name="addressResolver">Client state address resolver.</param>
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver;
this.dalamud = dalamud; this.dalamud = dalamud;
this.address = addressResolver;
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent; Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}");
Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
} }
/// <summary> /// <summary>
/// Gets the amount of currently spawned actors. /// Gets the amount of currently spawned actors.
/// </summary> /// </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> /// <summary>
/// Get an actor at the specified spawn index. /// Get an actor at the specified spawn index.
/// </summary> /// </summary>
/// <param name="index">Spawn index.</param> /// <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] [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> /// <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> /// </summary>
/// <param name="offset">Offset of the actor in the actor table.</param> /// <param name="index">The index of the actor.</param>
/// <returns>An instantiated actor.</returns> /// <returns>The memory address of the actor.</returns>
internal Actor ReadActorFromMemory(IntPtr offset) public unsafe IntPtr GetActorAddress(int index)
{ {
try if (index >= ActorTableLength)
{ return IntPtr.Zero;
// 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;
}
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(ActorMem); return *(IntPtr*)(this.address.ActorTable + (8 * index));
}
return actorStruct.ObjectKind switch /// <summary>
{ /// Create a reference to a FFXIV actor.
ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud), /// </summary>
ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud), /// <param name="address">The address of the actor in memory.</param>
ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud), /// <returns><see cref="Actor"/> object or inheritor containing requested data.</returns>
ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud), [CanBeNull]
_ => new Actor(offset, actorStruct, this.dalamud), public unsafe Actor CreateActorReference(IntPtr address)
}; {
} if (this.dalamud.ClientState.LocalContentId == 0)
catch (Exception e)
{
Log.Error(e, "Could not read actor from memory.");
return null; return null;
}
}
private void ResetCache() => this.actorsCache = null; if (address == IntPtr.Zero)
return null;
private void Framework_OnUpdateEvent(Internal.Framework framework) var objKind = *(ObjectKind*)(address + ActorOffsets.ObjectKind);
{ return objKind switch
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++)
{ {
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null); ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
} ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
return actors; ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new Actor(address, this.dalamud),
};
} }
} }
/// <summary> /// <summary>
/// Implementing IDisposable. /// This collection represents the currently spawned FFXIV actors.
/// </summary> /// </summary>
public sealed partial class ActorTable : IDisposable public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection
{ {
private bool disposed = false; /// <inheritdoc/>
/// <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>
int IReadOnlyCollection<Actor>.Count => this.Length; int IReadOnlyCollection<Actor>.Count => this.Length;
/// <summary> /// <inheritdoc/>
/// 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>
int ICollection.Count => this.Length; int ICollection.Count => this.Length;
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether access to the collection is synchronized (thread safe).
/// </summary>
/// <returns>Whether access is synchronized (thread safe) or not.</returns>
bool ICollection.IsSynchronized => false; bool ICollection.IsSynchronized => false;
/// <summary> /// <inheritdoc/>
/// 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>
object ICollection.SyncRoot => this; object ICollection.SyncRoot => this;
/// <summary> /// <inheritdoc/>
/// Copies the elements of the collection to an array, starting at a particular index. public IEnumerator<Actor> GetEnumerator()
/// </summary> {
/// <param name="array"> for (var i = 0; i < ActorTableLength; i++)
/// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing. {
/// </param> yield return this[i];
/// <param name="index"> }
/// The zero-based index in array at which copying begins. }
/// </param>
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index) void ICollection.CopyTo(Array array, int index)
{ {
for (var i = 0; i < this.Length; i++) 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 System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Actors namespace Dalamud.Game.ClientState.Actors
{ {
@ -10,8 +11,8 @@ namespace Dalamud.Game.ClientState.Actors
/// </summary> /// </summary>
public sealed class Targets public sealed class Targets
{ {
private Dalamud dalamud; private readonly Dalamud dalamud;
private ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class. /// Initializes a new instance of the <see cref="Targets"/> class.
@ -27,26 +28,31 @@ namespace Dalamud.Game.ClientState.Actors
/// <summary> /// <summary>
/// Gets the current target. /// Gets the current target.
/// </summary> /// </summary>
[CanBeNull]
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget); public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
/// <summary> /// <summary>
/// Gets the mouseover target. /// Gets the mouseover target.
/// </summary> /// </summary>
[CanBeNull]
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget); public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
/// <summary> /// <summary>
/// Gets the focus target. /// Gets the focus target.
/// </summary> /// </summary>
[CanBeNull]
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget); public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
/// <summary> /// <summary>
/// Gets the previous target. /// Gets the previous target.
/// </summary> /// </summary>
[CanBeNull]
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget); public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
/// <summary> /// <summary>
/// Gets the soft target. /// Gets the soft target.
/// </summary> /// </summary>
[CanBeNull]
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget); public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
/// <summary> /// <summary>
@ -91,6 +97,7 @@ namespace Dalamud.Game.ClientState.Actors
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress); Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
} }
[CanBeNull]
private Actor GetActorByOffset(int offset) private Actor GetActorByOffset(int offset)
{ {
if (this.address.TargetManager == IntPtr.Zero) if (this.address.TargetManager == IntPtr.Zero)
@ -100,7 +107,7 @@ namespace Dalamud.Game.ClientState.Actors
if (actorAddress == IntPtr.Zero) if (actorAddress == IntPtr.Zero)
return null; 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;
using System.Text; using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Structs; using Dalamud.Game.ClientState.Structs;
using Serilog; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Actors.Types
{ {
/// <summary> /// <summary>
/// This class represents a basic FFXIV actor. /// This class represents a basic actor (GameObject) in FFXIV.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="Actor"/> class. /// Initializes a new instance of the <see cref="Actor"/> class.
/// This represents a basic FFXIV actor.
/// </summary> /// </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> /// <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; this.Address = address;
} }
/// <summary> /// <summary>
/// Gets the position of this <see cref="Actor" />. /// Gets the address of the actor in memory.
/// </summary> /// </summary>
public Position3 Position => this.ActorStruct.Position; public IntPtr Address { get; }
/// <summary> /// <summary>
/// Gets the rotation of this <see cref="Actor" />. /// Gets Dalamud itself.
/// This ranges from -pi to pi radians.
/// </summary> /// </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> /// <summary>
/// Gets the displayname of this <see cref="Actor" />. /// Gets the displayname of this <see cref="Actor" />.
/// </summary> /// </summary>
public string Name => this.name ??= Util.GetUTF8String(this.actorStruct.Name); public SeString Name => MemoryHelper.ReadSeString(this.Address + ActorOffsets.Name, 32);
/// <summary> /// <summary>
/// Gets the actor ID of this <see cref="Actor" />. /// Gets the actor ID of this <see cref="Actor" />.
/// </summary> /// </summary>
public int ActorId => this.ActorStruct.ActorId; public uint ActorId => *(uint*)(this.Address + ActorOffsets.ActorId);
/// <summary> /// <summary>
/// Gets the hitbox radius of this <see cref="Actor" />. /// Gets the data ID for linking to other respective game data.
/// </summary> /// </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> /// <summary>
/// Gets the entity kind of this <see cref="Actor" />. /// Gets the entity kind of this <see cref="Actor" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values. /// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary> /// </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> /// <summary>
/// Gets the X distance from the local player in yalms. /// Gets the X distance from the local player in yalms.
/// </summary> /// </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> /// <summary>
/// Gets the Y distance from the local player in yalms. /// Gets the Y distance from the local player in yalms.
/// </summary> /// </summary>
public byte YalmDistanceY => this.ActorStruct.YalmDistanceFromPlayerY; public byte YalmDistanceY => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectY);
/// <summary> /// <summary>
/// Gets the target of the actor. /// Gets the position of this <see cref="Actor" />.
/// </summary> /// </summary>
public virtual int TargetActorID => 0; public Position3 Position => *(Position3*)(this.Address + ActorOffsets.Position);
/// <summary> /// <summary>
/// Gets status Effects. /// Gets the rotation of this <see cref="Actor" />.
/// This ranges from -pi to pi radians.
/// </summary> /// </summary>
public StatusEffect[] StatusEffects => this.ActorStruct.UIStatusEffects; public float Rotation => *(float*)(this.Address + ActorOffsets.Rotation);
/// <summary> /// <summary>
/// Gets the address of this actor in memory. /// Gets the hitbox radius of this <see cref="Actor" />.
/// </summary> /// </summary>
public readonly IntPtr Address; public float HitboxRadius => *(float*)(this.Address + ActorOffsets.HitboxRadius);
/// <summary> /// <summary>
/// Gets the memory representation of the base actor. /// Gets the current target of the Actor.
/// </summary> /// </summary>
internal Structs.Actor ActorStruct => this.actorStruct; public virtual uint TargetActorID => 0;
/// <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;
} }
} }

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 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 namespace Dalamud.Game.ClientState.Actors.Types
{ {
/// <summary> /// <summary>
/// This class represents the base for non-static entities. /// This class represents the base for non-static entities.
/// </summary> /// </summary>
public class Chara : Actor public unsafe class Chara : Actor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Chara"/> class. /// Initializes a new instance of the <see cref="Chara"/> class.
/// This represents a non-static entity. /// This represents a non-static entity.
/// </summary> /// </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> /// <param name="address">The address of this actor in memory.</param>
protected Chara(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud) /// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
: base(address, actorStruct, dalamud) 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> /// <summary>
/// Gets the current HP of this Chara. /// Gets the current HP of this Chara.
/// </summary> /// </summary>
public int CurrentHp => this.ActorStruct.CurrentHp; public uint CurrentHp => *(uint*)(this.Address + ActorOffsets.CurrentHp);
/// <summary> /// <summary>
/// Gets the maximum HP of this Chara. /// Gets the maximum HP of this Chara.
/// </summary> /// </summary>
public int MaxHp => this.ActorStruct.MaxHp; public uint MaxHp => *(uint*)(this.Address + ActorOffsets.MaxHp);
/// <summary> /// <summary>
/// Gets the current MP of this Chara. /// Gets the current MP of this Chara.
/// </summary> /// </summary>
public int CurrentMp => this.ActorStruct.CurrentMp; public uint CurrentMp => *(uint*)(this.Address + ActorOffsets.CurrentMp);
/// <summary> /// <summary>
/// Gets the maximum MP of this Chara. /// Gets the maximum MP of this Chara.
/// </summary> /// </summary>
public int MaxMp => this.ActorStruct.MaxMp; public uint MaxMp => *(uint*)(this.Address + ActorOffsets.MaxMp);
/// <summary> /// <summary>
/// Gets the current GP of this Chara. /// Gets the current GP of this Chara.
/// </summary> /// </summary>
public int CurrentGp => this.ActorStruct.CurrentGp; public uint CurrentGp => *(uint*)(this.Address + ActorOffsets.CurrentGp);
/// <summary> /// <summary>
/// Gets the maximum GP of this Chara. /// Gets the maximum GP of this Chara.
/// </summary> /// </summary>
public int MaxGp => this.ActorStruct.MaxGp; public uint MaxGp => *(uint*)(this.Address + ActorOffsets.MaxGp);
/// <summary> /// <summary>
/// Gets the current CP of this Chara. /// Gets the current CP of this Chara.
/// </summary> /// </summary>
public int CurrentCp => this.ActorStruct.CurrentCp; public uint CurrentCp => *(uint*)(this.Address + ActorOffsets.CurrentCp);
/// <summary> /// <summary>
/// Gets the maximum CP of this Chara. /// Gets the maximum CP of this Chara.
/// </summary> /// </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> /// <summary>
/// Gets a byte array describing the visual appearance of this Chara. /// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>. /// Indexed by <see cref="CustomizeIndex"/>.
/// </summary> /// </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> /// <summary>
/// This enum describes the indices of the Customize array. /// This enum describes the indices of the Customize array.

View file

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

View file

@ -5,23 +5,22 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary> /// <summary>
/// This class represents an EventObj. /// This class represents an EventObj.
/// </summary> /// </summary>
public class EventObj : Actor public unsafe class EventObj : Actor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class. /// 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> /// </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> /// <param name="address">The address of this actor in memory.</param>
public EventObj(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud) /// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
: base(address, actorStruct, dalamud) internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{ {
} }
/// <summary> /// <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> /// </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> /// <summary>
/// This class represents a NPC. /// This class represents a NPC.
/// </summary> /// </summary>
public class Npc : Chara public unsafe class Npc : Chara
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Npc"/> class. /// 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> /// </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> /// <param name="address">The address of this actor in memory.</param>
public Npc(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud) /// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
: base(address, actorStruct, dalamud) internal Npc(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{ {
} }
/// <summary> /// <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> /// </summary>
public int DataId => this.ActorStruct.DataId; public uint BaseId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary> /// <summary>
/// Gets the name ID of the NPC linking to their respective game data. /// Gets the name ID of the NPC linking to their respective game data.
/// </summary> /// </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> /// <summary>
/// Enum describing possible entity kinds. /// 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 namespace Dalamud.Game.ClientState.Actors.Types
{ {
@ -7,26 +8,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// </summary> /// </summary>
public class PartyMember 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> /// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class. /// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary> /// </summary>
@ -34,9 +15,10 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <param name="rawData">The interop data struct.</param> /// <param name="rawData">The interop data struct.</param>
public PartyMember(ActorTable table, Structs.PartyMember rawData) public PartyMember(ActorTable table, Structs.PartyMember rawData)
{ {
this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr); this.CharacterName = MemoryHelper.ReadSeString(rawData.namePtr);
this.Unknown = rawData.unknown; this.Unknown = rawData.unknown;
this.Actor = null; this.Actor = null;
for (var i = 0; i < table.Length; i++) for (var i = 0; i < table.Length; i++)
{ {
if (table[i] != null && table[i].ActorId == rawData.actorId) if (table[i] != null && table[i].ActorId == rawData.actorId)
@ -48,5 +30,25 @@ namespace Dalamud.Game.ClientState.Actors.Types
this.ObjectKind = rawData.objectKind; 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;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.ClientState.Actors.Resolvers; using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Structs; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Actors.Types
{ {
/// <summary> /// <summary>
/// This class represents a player character. /// This class represents a player character.
/// </summary> /// </summary>
public class PlayerCharacter : Chara public unsafe class PlayerCharacter : Chara
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class. /// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
/// This represents a player character. /// This represents a player character.
/// </summary> /// </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> /// <param name="address">The address of this actor in memory.</param>
public PlayerCharacter(IntPtr address, Structs.Actor actorStruct, Dalamud dalamud) /// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
: base(address, actorStruct, dalamud) 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> /// <summary>
/// Gets the current <see cref="World">world</see> of the character. /// Gets the current <see cref="WorldResolver">world</see> of the character.
/// </summary> /// </summary>
public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud); public WorldResolver CurrentWorld => new(*(ushort*)(this.Address + ActorOffsets.CurrentWorld), this.Dalamud);
/// <summary> /// <summary>
/// Gets the home <see cref="World">world</see> of the character. /// Gets the home <see cref="WorldResolver">world</see> of the character.
/// </summary> /// </summary>
public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud); public WorldResolver HomeWorld => new(*(ushort*)(this.Address + ActorOffsets.HomeWorld), this.Dalamud);
/// <summary> /// <summary>
/// Gets the Free Company tag of this player. /// Gets the Free Company tag of this player.
/// </summary> /// </summary>
public string CompanyTag { get; private set; } public SeString CompanyTag => MemoryHelper.ReadSeString(this.Address + ActorOffsets.CompanyTag, 6);
/// <summary> /// <summary>
/// Gets the target of the PlayerCharacter. /// Gets the target actor ID of the PlayerCharacter.
/// </summary> /// </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;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Fates;
using Dalamud.Game.Internal; using Dalamud.Game.Internal;
using Dalamud.Hooking; using Dalamud.Hooking;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -15,58 +16,8 @@ namespace Dalamud.Game.ClientState
/// <summary> /// <summary>
/// This class represents the state of the game client at the time of access. /// This class represents the state of the game client at the time of access.
/// </summary> /// </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 Dalamud dalamud;
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook; private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
@ -80,7 +31,7 @@ namespace Dalamud.Game.ClientState
/// <param name="dalamud">Dalamud instance.</param> /// <param name="dalamud">Dalamud instance.</param>
/// <param name="startInfo">StartInfo of the current Dalamud launch.</param> /// <param name="startInfo">StartInfo of the current Dalamud launch.</param>
/// <param name="scanner">Sig scanner.</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.dalamud = dalamud;
this.address = new ClientStateAddressResolver(); this.address = new ClientStateAddressResolver();
@ -92,6 +43,8 @@ namespace Dalamud.Game.ClientState
this.Actors = new ActorTable(dalamud, this.address); this.Actors = new ActorTable(dalamud, this.address);
this.Fates = new FateTable(dalamud, this.address);
this.PartyList = new PartyList(dalamud, this.address); this.PartyList = new PartyList(dalamud, this.address);
this.JobGauges = new JobGauges(this.address); this.JobGauges = new JobGauges(this.address);
@ -104,7 +57,7 @@ namespace Dalamud.Game.ClientState
this.Targets = new Targets(dalamud, this.address); 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); this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
@ -122,6 +75,11 @@ namespace Dalamud.Game.ClientState
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore #pragma warning restore
/// <summary>
/// Event that gets fired when the current Territory changes.
/// </summary>
public event EventHandler<ushort> TerritoryChanged;
/// <summary> /// <summary>
/// Event that fires when a character is logging in. /// Event that fires when a character is logging in.
/// </summary> /// </summary>
@ -137,22 +95,61 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public event EventHandler<ContentFinderCondition> CfPop; 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> /// <summary>
/// Gets the local player character, if one is present. /// Gets the local player character, if one is present.
/// </summary> /// </summary>
[CanBeNull] [CanBeNull]
public PlayerCharacter LocalPlayer public PlayerCharacter LocalPlayer => this.Actors[0] as PlayerCharacter;
{
get
{
var actor = this.Actors[0];
if (actor is PlayerCharacter pc)
return pc;
return null;
}
}
/// <summary> /// <summary>
/// Gets the content ID of the local character. /// Gets the content ID of the local character.
@ -181,7 +178,6 @@ namespace Dalamud.Game.ClientState
{ {
this.PartyList.Dispose(); this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose(); this.setupTerritoryTypeHook.Dispose();
this.Actors.Dispose();
this.GamepadState.Dispose(); this.GamepadState.Dispose();
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent; this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices;
using Dalamud.Game.Internal; using Dalamud.Game.Internal;
@ -16,6 +17,14 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public IntPtr ActorTable { get; private set; } 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; } // public IntPtr ViewportActorTable { get; private set; }
/// <summary> /// <summary>
@ -50,9 +59,6 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public IntPtr SetupTerritoryType { get; private set; } public IntPtr SetupTerritoryType { get; private set; }
// public IntPtr SomeActorTableAccess { get; private set; }
// public IntPtr PartyListUpdate { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the method which polls the gamepads for data. /// Gets the address of the method which polls the gamepads for data.
/// Called every frame, even when `Enable Gamepad` is off in the settings. /// 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 // 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; // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); 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.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10; 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.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; 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 ?? ?? ?? ??"); // 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.Game.ClientState.Structs;
using Dalamud.Hooking; using Dalamud.Hooking;
@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState
/// ///
/// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set. /// Will block game's gamepad input if <see cref="ImGuiConfigFlags.NavEnableGamepad"/> is set.
/// </summary> /// </summary>
public unsafe class GamepadState public unsafe class GamepadState : IDisposable
{ {
private readonly Hook<ControllerPoll> gamepadPoll; 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> /// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
public GamepadState(ClientStateAddressResolver resolver) public GamepadState(ClientStateAddressResolver resolver)
{ {
#if DEBUG Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
Log.Verbose("GamepadPoll address {GamepadPoll}", resolver.GamepadPoll); this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
#endif
this.gamepadPoll = new Hook<ControllerPoll>(
resolver.GamepadPoll,
(ControllerPoll)this.GamepadPollDetour);
} }
/// <summary> /// <summary>

View file

@ -17,7 +17,7 @@ namespace Dalamud.Game.ClientState
{ {
this.Address = addressResolver; 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; } private ClientStateAddressResolver Address { get; }

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.ClientState
{ {
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); 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> /// <summary>

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
/// <param name="dalamud">The Dalamud instance.</param> /// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver 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.address = addressResolver;
this.dalamud = dalamud; 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 namespace Dalamud.Game.ClientState.Structs
{ {
@ -40,25 +40,37 @@ namespace Dalamud.Game.ClientState.Structs
/// <summary> /// <summary>
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping. /// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
/// </summary> /// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x98)] [FieldOffset(0x98)]
public ushort ButtonsRaw; // bitfield public ushort ButtonsRaw;
/// <summary> /// <summary>
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping. /// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
/// </summary> /// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x9C)] [FieldOffset(0x9C)]
public ushort ButtonsPressed; // bitfield public ushort ButtonsPressed;
/// <summary> /// <summary>
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping. /// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
/// </summary> /// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0xA0)] [FieldOffset(0xA0)]
public ushort ButtonsReleased; // bitfield public ushort ButtonsReleased;
/// <summary> /// <summary>
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping. /// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
/// </summary> /// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0xA4)] [FieldOffset(0xA4)]
public ushort ButtonsRepeat; // bitfield public ushort ButtonsRepeat;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge namespace Dalamud.Game.ClientState.Structs.JobGauge
@ -9,35 +8,12 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct MNKGauge public struct MNKGauge
{ {
[FieldOffset(0)]
private byte numChakra;
/// <summary> /// <summary>
/// Gets the number of Chakra available. /// Gets the number of Chakra available.
/// </summary> /// </summary>
[FieldOffset(0)] public byte NumChakra => this.numChakra;
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;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ namespace Dalamud.Game.Command
/// </summary> /// </summary>
/// <param name="dalamud">The Dalamud instance.</param> /// <param name="dalamud">The Dalamud instance.</param>
/// <param name="language">The client language requested.</param> /// <param name="language">The client language requested.</param>
public CommandManager(Dalamud dalamud, ClientLanguage language) internal CommandManager(Dalamud dalamud, ClientLanguage language)
{ {
this.dalamud = dalamud; this.dalamud = dalamud;
@ -128,7 +128,8 @@ namespace Dalamud.Game.Command
/// <returns>If adding was successful.</returns> /// <returns>If adding was successful.</returns>
public bool AddHandler(string command, CommandInfo info) 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 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; this.debugCheckAddress = IntPtr.Zero;
} }
Log.Verbose("DebugCheck address {DebugCheckAddress}", this.debugCheckAddress); Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}");
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the anti-debugging is enabled. /// Gets a value indicating whether the anti-debugging is enabled.
/// </summary> /// </summary>
public bool IsEnabled { get; private set; } public bool IsEnabled { get; private set; } = false;
/// <summary> /// <summary>
/// Enables the anti-debugging by overwriting code in memory. /// Enables the anti-debugging by overwriting code in memory.
@ -45,7 +45,7 @@ namespace Dalamud.Game.Internal
this.original = new byte[this.nop.Length]; this.original = new byte[this.nop.Length];
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) 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.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original);
SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); SafeMemory.WriteBytes(this.debugCheckAddress, this.nop);
} }
@ -64,7 +64,7 @@ namespace Dalamud.Game.Internal
{ {
if (this.debugCheckAddress != IntPtr.Zero && this.original != null) 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); SafeMemory.WriteBytes(this.debugCheckAddress, this.original);
} }
else 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 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 // 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. // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it.
// this.Disable(); this.Disable();
} }
this.disposed = true;
} }
} }
} }

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