mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Compare commits
18 commits
a36e11574b
...
9fd59f736d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd59f736d | ||
|
|
ab5ea34e68 | ||
|
|
501e30e31c | ||
|
|
3d29157391 | ||
|
|
b2d9480f9f | ||
|
|
1ad1343cbc | ||
|
|
61123ce573 | ||
|
|
9f565fafd8 | ||
|
|
88fc933e3f | ||
|
|
e032840ac8 | ||
|
|
1d1db04f04 | ||
|
|
446c7e3877 | ||
|
|
e09c43b8de | ||
|
|
9c2d2b7c1d | ||
|
|
2e5c560ed7 | ||
|
|
3c7dbf9f81 | ||
|
|
d94cacaac3 | ||
|
|
7cf20fe102 |
18 changed files with 282 additions and 196 deletions
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
|
|
@ -1,9 +1,10 @@
|
||||||
name: Build Dalamud
|
name: Build Dalamud
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
# Globally blocking because of git pushes in deploy step
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build_dalamud_${{ github.ref_name }}
|
group: build_dalamud_${{ github.repository_owner }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -33,12 +34,8 @@ jobs:
|
||||||
($env:REPO_NAME) >> VERSION
|
($env:REPO_NAME) >> VERSION
|
||||||
($env:BRANCH) >> VERSION
|
($env:BRANCH) >> VERSION
|
||||||
($env:COMMIT) >> VERSION
|
($env:COMMIT) >> VERSION
|
||||||
- name: git status
|
|
||||||
run: git status
|
|
||||||
- name: Build and Test Dalamud
|
- name: Build and Test Dalamud
|
||||||
run: .\build.ps1 ci
|
run: .\build.ps1 ci
|
||||||
- name: git status
|
|
||||||
run: git status
|
|
||||||
- name: Sign Dalamud
|
- name: Sign Dalamud
|
||||||
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,57 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"title": "Build Schema",
|
|
||||||
"$ref": "#/definitions/build",
|
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"build": {
|
"Host": {
|
||||||
"type": "object",
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"AppVeyor",
|
||||||
|
"AzurePipelines",
|
||||||
|
"Bamboo",
|
||||||
|
"Bitbucket",
|
||||||
|
"Bitrise",
|
||||||
|
"GitHubActions",
|
||||||
|
"GitLab",
|
||||||
|
"Jenkins",
|
||||||
|
"Rider",
|
||||||
|
"SpaceAutomation",
|
||||||
|
"TeamCity",
|
||||||
|
"Terminal",
|
||||||
|
"TravisCI",
|
||||||
|
"VisualStudio",
|
||||||
|
"VSCode"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ExecutableTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"CI",
|
||||||
|
"Clean",
|
||||||
|
"Compile",
|
||||||
|
"CompileCImGui",
|
||||||
|
"CompileCImGuizmo",
|
||||||
|
"CompileCImPlot",
|
||||||
|
"CompileDalamud",
|
||||||
|
"CompileDalamudBoot",
|
||||||
|
"CompileDalamudCrashHandler",
|
||||||
|
"CompileImGuiNatives",
|
||||||
|
"CompileInjector",
|
||||||
|
"Restore",
|
||||||
|
"SetCILogging",
|
||||||
|
"Test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Verbosity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"enum": [
|
||||||
|
"Verbose",
|
||||||
|
"Normal",
|
||||||
|
"Minimal",
|
||||||
|
"Quiet"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NukeBuild": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"Configuration": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
|
||||||
"enum": [
|
|
||||||
"Debug",
|
|
||||||
"Release"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Continue": {
|
"Continue": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Indicates to continue a previously failed build attempt"
|
"description": "Indicates to continue a previously failed build attempt"
|
||||||
|
|
@ -23,29 +61,8 @@
|
||||||
"description": "Shows the help text for this build assembly"
|
"description": "Shows the help text for this build assembly"
|
||||||
},
|
},
|
||||||
"Host": {
|
"Host": {
|
||||||
"type": "string",
|
|
||||||
"description": "Host for execution. Default is 'automatic'",
|
"description": "Host for execution. Default is 'automatic'",
|
||||||
"enum": [
|
"$ref": "#/definitions/Host"
|
||||||
"AppVeyor",
|
|
||||||
"AzurePipelines",
|
|
||||||
"Bamboo",
|
|
||||||
"Bitbucket",
|
|
||||||
"Bitrise",
|
|
||||||
"GitHubActions",
|
|
||||||
"GitLab",
|
|
||||||
"Jenkins",
|
|
||||||
"Rider",
|
|
||||||
"SpaceAutomation",
|
|
||||||
"TeamCity",
|
|
||||||
"Terminal",
|
|
||||||
"TravisCI",
|
|
||||||
"VisualStudio",
|
|
||||||
"VSCode"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"IsDocsBuild": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether we are building for documentation - emits generated files"
|
|
||||||
},
|
},
|
||||||
"NoLogo": {
|
"NoLogo": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
@ -74,63 +91,46 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Solution": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to a solution file that is automatically loaded"
|
|
||||||
},
|
|
||||||
"Target": {
|
"Target": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Verbosity": {
|
"Verbosity": {
|
||||||
"type": "string",
|
|
||||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||||
"enum": [
|
"$ref": "#/definitions/Verbosity"
|
||||||
"Minimal",
|
|
||||||
"Normal",
|
|
||||||
"Quiet",
|
|
||||||
"Verbose"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"allOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"Configuration": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||||
|
"enum": [
|
||||||
|
"Debug",
|
||||||
|
"Release"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IsDocsBuild": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether we are building for documentation - emits generated files"
|
||||||
|
},
|
||||||
|
"Solution": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to a solution file that is automatically loaded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/NukeBuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
|
||||||
|
|
||||||
struct exception_info
|
struct exception_info
|
||||||
{
|
{
|
||||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,51 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
logging::I("VEH was disabled manually");
|
logging::I("VEH was disabled manually");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================== CLR Reporting =================================== //
|
||||||
|
|
||||||
|
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
|
||||||
|
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
|
||||||
|
// Ideally all of this will go away once they get to it.
|
||||||
|
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_report_event_hook;
|
||||||
|
s_report_event_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>(
|
||||||
|
"advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
|
||||||
|
s_report_event_hook->set_detour([hook = s_report_event_hook.get()](
|
||||||
|
HANDLE hEventLog,
|
||||||
|
WORD wType,
|
||||||
|
WORD wCategory,
|
||||||
|
DWORD dwEventID,
|
||||||
|
PSID lpUserSid,
|
||||||
|
WORD wNumStrings,
|
||||||
|
DWORD dwDataSize,
|
||||||
|
LPCWSTR* lpStrings,
|
||||||
|
LPVOID lpRawData)-> BOOL {
|
||||||
|
|
||||||
|
// Check for CLR Error Event IDs
|
||||||
|
// https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370
|
||||||
|
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
|
||||||
|
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
|
||||||
|
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
|
||||||
|
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
|
||||||
|
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
|
||||||
|
{
|
||||||
|
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wNumStrings == 0 || lpStrings == nullptr) {
|
||||||
|
logging::W("ReportEventW called with no strings.");
|
||||||
|
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
|
||||||
|
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
|
||||||
|
const std::wstring error_details(lpStrings[0]);
|
||||||
|
veh::raise_external_event(error_details);
|
||||||
|
|
||||||
|
return original_ret;
|
||||||
|
});
|
||||||
|
logging::I("ReportEventW hook installed.");
|
||||||
|
|
||||||
// ============================== Dalamud ==================================== //
|
// ============================== Dalamud ==================================== //
|
||||||
|
|
||||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ HANDLE g_crashhandler_process = nullptr;
|
||||||
HANDLE g_crashhandler_event = nullptr;
|
HANDLE g_crashhandler_event = nullptr;
|
||||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||||
|
|
||||||
|
wchar_t g_external_event_info[16384] = L"";
|
||||||
|
|
||||||
std::recursive_mutex g_exception_handler_mutex;
|
std::recursive_mutex g_exception_handler_mutex;
|
||||||
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
||||||
|
|
@ -191,7 +193,11 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
std::wstring stackTrace;
|
std::wstring stackTrace;
|
||||||
if (!g_clr)
|
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||||
|
{
|
||||||
|
stackTrace = std::wstring(g_external_event_info);
|
||||||
|
}
|
||||||
|
else if (!g_clr)
|
||||||
{
|
{
|
||||||
stackTrace = L"(no CLR stack trace available)";
|
stackTrace = L"(no CLR stack trace available)";
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +258,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
|
|
||||||
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
{
|
{
|
||||||
|
// special case for CLR exceptions, always trigger crash handler
|
||||||
|
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||||
|
{
|
||||||
|
return exception_handler(ex);
|
||||||
|
}
|
||||||
|
|
||||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||||
{
|
{
|
||||||
// pass
|
// pass
|
||||||
|
|
@ -435,3 +447,10 @@ bool veh::remove_handler()
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void veh::raise_external_event(const std::wstring& info)
|
||||||
|
{
|
||||||
|
const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1);
|
||||||
|
wcsncpy_s(g_external_event_info, info.c_str(), info_size);
|
||||||
|
RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ namespace veh
|
||||||
{
|
{
|
||||||
bool add_handler(bool doFullDump, const std::string& workingDirectory);
|
bool add_handler(bool doFullDump, const std::string& workingDirectory);
|
||||||
bool remove_handler();
|
bool remove_handler();
|
||||||
|
void raise_external_event(const std::wstring& info);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,8 @@
|
||||||
<Content Include="licenses.txt">
|
<Content Include="licenses.txt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
|
||||||
|
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ public sealed class EntryPoint
|
||||||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||||
var searchPath = $".;{symbolPath}";
|
var searchPath = $".;{symbolPath}";
|
||||||
|
|
||||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||||
|
|
||||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||||
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Events;
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
@ -32,25 +31,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
|
|
||||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||||
|
|
||||||
private readonly AddonEventManagerAddressResolver address;
|
private readonly Hook<AtkUnitManager.Delegates.UpdateCursor> onUpdateCursor;
|
||||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||||
|
|
||||||
private AddonCursorType? cursorOverride;
|
private AtkCursor.CursorType? cursorOverride;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonEventManager(TargetSigScanner sigScanner)
|
private AddonEventManager()
|
||||||
{
|
{
|
||||||
this.address = new AddonEventManagerAddressResolver();
|
|
||||||
this.address.Setup(sigScanner);
|
|
||||||
|
|
||||||
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||||
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||||
|
|
||||||
this.cursorOverride = null;
|
this.cursorOverride = null;
|
||||||
|
|
||||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
this.onUpdateCursor = Hook<AtkUnitManager.Delegates.UpdateCursor>.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour);
|
||||||
|
|
||||||
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
||||||
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||||
|
|
@ -58,8 +53,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
this.onUpdateCursor.Enable();
|
this.onUpdateCursor.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
|
|
@ -117,7 +110,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
/// Force the game cursor to be the specified cursor.
|
/// Force the game cursor to be the specified cursor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cursor">Which cursor to use.</param>
|
/// <param name="cursor">Which cursor to use.</param>
|
||||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Un-forces the game cursor.
|
/// Un-forces the game cursor.
|
||||||
|
|
@ -168,7 +161,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
private void UpdateCursorDetour(AtkUnitManager* thisPtr)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -176,13 +169,14 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
|
|
||||||
if (this.cursorOverride is not null && atkStage is not null)
|
if (this.cursorOverride is not null && atkStage is not null)
|
||||||
{
|
{
|
||||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
ref var atkCursor = ref atkStage->AtkCursor;
|
||||||
if (cursor != this.cursorOverride)
|
|
||||||
|
if (atkCursor.Type != this.cursorOverride)
|
||||||
{
|
{
|
||||||
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nint.Zero;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -190,7 +184,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.onUpdateCursor!.Original(module);
|
this.onUpdateCursor!.Original(thisPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Events;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AddonEventManager memory address resolver.
|
|
||||||
/// </summary>
|
|
||||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the AtkModule UpdateCursor method.
|
|
||||||
/// </summary>
|
|
||||||
public nint UpdateCursor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for and setup any configured address pointers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
|
||||||
protected override void Setup64Bit(ISigScanner scanner)
|
|
||||||
{
|
|
||||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -201,19 +201,19 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||||
useMinHook = true;
|
useMinHook = true;
|
||||||
|
|
||||||
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
||||||
if (moduleHandle.IsInvalid)
|
if (moduleHandle.IsNull)
|
||||||
throw new Exception($"Could not get a handle to module {moduleName}");
|
throw new Exception($"Could not get a handle to module {moduleName}");
|
||||||
|
|
||||||
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
||||||
if (procAddress == IntPtr.Zero)
|
if (procAddress.IsNull)
|
||||||
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
||||||
|
|
||||||
procAddress = HookManager.FollowJmp(procAddress);
|
var address = HookManager.FollowJmp(procAddress.Value);
|
||||||
if (useMinHook)
|
if (useMinHook)
|
||||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new MinHookHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||||
else
|
else
|
||||||
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new ReloadedHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
this.splitter = default;
|
this.splitter = default;
|
||||||
this.GetEntity = ssdp.GetEntity;
|
this.GetEntity = ssdp.GetEntity;
|
||||||
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
||||||
this.FontSizeScale = this.FontSize / this.Font->FontSize;
|
this.FontSizeScale = this.FontSize / this.Font.FontSize;
|
||||||
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||||
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||||
this.Opacity = ssdp.EffectiveOpacity;
|
this.Opacity = ssdp.EffectiveOpacity;
|
||||||
|
|
@ -106,7 +106,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
public Vector2 ScreenOffset { get; }
|
public Vector2 ScreenOffset { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
||||||
public ImFont* Font { get; }
|
public ImFontPtr Font { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
||||||
public float FontSize { get; }
|
public float FontSize { get; }
|
||||||
|
|
@ -256,7 +256,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
||||||
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
||||||
{
|
{
|
||||||
var texId = this.Font->ContainerAtlas->Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
var texId = this.Font.ContainerAtlas.Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
||||||
var xy0 = new Vector2(
|
var xy0 = new Vector2(
|
||||||
MathF.Round(g.X0 * this.FontSizeScale),
|
MathF.Round(g.X0 * this.FontSizeScale),
|
||||||
MathF.Round(g.Y0 * this.FontSizeScale));
|
MathF.Round(g.Y0 * this.FontSizeScale));
|
||||||
|
|
@ -313,7 +313,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
|
|
||||||
offset += this.ScreenOffset;
|
offset += this.ScreenOffset;
|
||||||
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
||||||
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale));
|
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font.Ascent * this.FontSizeScale));
|
||||||
|
|
||||||
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
||||||
this.DrawList.AddLine(
|
this.DrawList.AddLine(
|
||||||
|
|
@ -340,9 +340,9 @@ public unsafe ref struct SeStringDrawState
|
||||||
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
||||||
{
|
{
|
||||||
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
||||||
? this.Font->FindGlyph((ushort)rune.Value)
|
? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value)
|
||||||
: this.Font->FallbackGlyph;
|
: this.Font.FallbackGlyph;
|
||||||
return ref *(ImGuiHelpers.ImFontGlyphReal*)p;
|
return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
||||||
|
|
@ -375,7 +375,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return MathF.Round(
|
return MathF.Round(
|
||||||
this.Font->GetDistanceAdjustmentForPair(
|
this.Font.GetDistanceAdjustmentForPair(
|
||||||
(ushort)left.Value,
|
(ushort)left.Value,
|
||||||
(ushort)right.Value) * this.FontSizeScale);
|
(ushort)right.Value) * this.FontSizeScale);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||||
|
"useSafeHandles": false,
|
||||||
"allowMarshaling": false
|
"allowMarshaling": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -12,11 +14,11 @@ namespace Dalamud;
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class SafeMemory
|
public static class SafeMemory
|
||||||
{
|
{
|
||||||
private static readonly SafeHandle Handle;
|
private static readonly HANDLE Handle;
|
||||||
|
|
||||||
static SafeMemory()
|
static SafeMemory()
|
||||||
{
|
{
|
||||||
Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
Handle = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -28,6 +30,12 @@ public static class SafeMemory
|
||||||
/// <returns>Whether the read succeeded.</returns>
|
/// <returns>Whether the read succeeded.</returns>
|
||||||
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
||||||
{
|
{
|
||||||
|
if (Handle.IsNull)
|
||||||
|
{
|
||||||
|
buffer = [];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
buffer = new byte[count <= 0 ? 0 : count];
|
buffer = new byte[count <= 0 ? 0 : count];
|
||||||
fixed (byte* p = buffer)
|
fixed (byte* p = buffer)
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +62,9 @@ public static class SafeMemory
|
||||||
/// <returns>Whether the write succeeded.</returns>
|
/// <returns>Whether the write succeeded.</returns>
|
||||||
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
||||||
{
|
{
|
||||||
|
if (Handle.IsNull)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (buffer.Length == 0)
|
if (buffer.Length == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
using Windows.Win32.Storage.FileSystem;
|
using Windows.Win32.Storage.FileSystem;
|
||||||
|
|
||||||
namespace Dalamud.Utility;
|
namespace Dalamud.Utility;
|
||||||
|
|
@ -47,30 +48,39 @@ public static class FilesystemUtil
|
||||||
// Open the temp file
|
// Open the temp file
|
||||||
var tempPath = path + ".tmp";
|
var tempPath = path + ".tmp";
|
||||||
|
|
||||||
using var tempFile = Windows.Win32.PInvoke.CreateFile(
|
var tempFile = Windows.Win32.PInvoke.CreateFile(
|
||||||
tempPath,
|
tempPath,
|
||||||
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
|
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
|
||||||
FILE_SHARE_MODE.FILE_SHARE_NONE,
|
FILE_SHARE_MODE.FILE_SHARE_NONE,
|
||||||
null,
|
null,
|
||||||
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
|
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
|
||||||
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL,
|
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL,
|
||||||
null);
|
HANDLE.Null);
|
||||||
|
|
||||||
if (tempFile.IsInvalid)
|
if (tempFile.IsNull)
|
||||||
throw new Win32Exception();
|
throw new Win32Exception();
|
||||||
|
|
||||||
// Write the data
|
// Write the data
|
||||||
uint bytesWritten = 0;
|
uint bytesWritten = 0;
|
||||||
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
|
fixed (byte* ptr = bytes)
|
||||||
throw new Win32Exception();
|
{
|
||||||
|
if (!Windows.Win32.PInvoke.WriteFile(tempFile, ptr, (uint)bytes.Length, &bytesWritten, null))
|
||||||
|
throw new Win32Exception();
|
||||||
|
}
|
||||||
|
|
||||||
if (bytesWritten != bytes.Length)
|
if (bytesWritten != bytes.Length)
|
||||||
|
{
|
||||||
|
Windows.Win32.PInvoke.CloseHandle(tempFile);
|
||||||
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
|
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
|
||||||
|
}
|
||||||
|
|
||||||
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
|
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
|
||||||
|
{
|
||||||
|
Windows.Win32.PInvoke.CloseHandle(tempFile);
|
||||||
throw new Win32Exception();
|
throw new Win32Exception();
|
||||||
|
}
|
||||||
|
|
||||||
tempFile.Close();
|
Windows.Win32.PInvoke.CloseHandle(tempFile);
|
||||||
|
|
||||||
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
||||||
throw new Win32Exception();
|
throw new Win32Exception();
|
||||||
|
|
|
||||||
|
|
@ -858,7 +858,7 @@ public static partial class Util
|
||||||
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
|
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
|
||||||
|
|
||||||
var dropFilesSize = sizeof(DROPFILES);
|
var dropFilesSize = sizeof(DROPFILES);
|
||||||
var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle(
|
var hGlobal = Win32_PInvoke.GlobalAlloc(
|
||||||
GLOBAL_ALLOC_FLAGS.GHND,
|
GLOBAL_ALLOC_FLAGS.GHND,
|
||||||
// struct size + size of encoded strings + null terminator for each
|
// struct size + size of encoded strings + null terminator for each
|
||||||
// string + two null terminators for end of list
|
// string + two null terminators for end of list
|
||||||
|
|
@ -896,12 +896,11 @@ public static partial class Util
|
||||||
{
|
{
|
||||||
Win32_PInvoke.SetClipboardData(
|
Win32_PInvoke.SetClipboardData(
|
||||||
(uint)CLIPBOARD_FORMAT.CF_HDROP,
|
(uint)CLIPBOARD_FORMAT.CF_HDROP,
|
||||||
hGlobal);
|
(Windows.Win32.Foundation.HANDLE)hGlobal.Value);
|
||||||
Win32_PInvoke.CloseClipboard();
|
Win32_PInvoke.CloseClipboard();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hGlobal.Dispose();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ std::wstring describe_module(const std::filesystem::path& path) {
|
||||||
return std::format(L"<error: GetFileVersionInfoSizeW#2 returned {}>", GetLastError());
|
return std::format(L"<error: GetFileVersionInfoSizeW#2 returned {}>", GetLastError());
|
||||||
|
|
||||||
UINT size = 0;
|
UINT size = 0;
|
||||||
|
|
||||||
std::wstring version = L"v?.?.?.?";
|
std::wstring version = L"v?.?.?.?";
|
||||||
if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) {
|
if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) {
|
||||||
const auto& v = *static_cast<const VS_FIXEDFILEINFO*>(lpBuffer);
|
const auto& v = *static_cast<const VS_FIXEDFILEINFO*>(lpBuffer);
|
||||||
|
|
@ -176,7 +176,7 @@ const std::map<HMODULE, size_t>& get_remote_modules() {
|
||||||
std::vector<HMODULE> buf(8192);
|
std::vector<HMODULE> buf(8192);
|
||||||
for (size_t i = 0; i < 64; i++) {
|
for (size_t i = 0; i < 64; i++) {
|
||||||
if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast<DWORD>(std::span(buf).size_bytes()), &needed)) {
|
if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast<DWORD>(std::span(buf).size_bytes()), &needed)) {
|
||||||
std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl;
|
std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl;
|
||||||
break;
|
break;
|
||||||
} else if (needed > std::span(buf).size_bytes()) {
|
} else if (needed > std::span(buf).size_bytes()) {
|
||||||
buf.resize(needed / sizeof(HMODULE) + 16);
|
buf.resize(needed / sizeof(HMODULE) + 16);
|
||||||
|
|
@ -201,7 +201,7 @@ const std::map<HMODULE, size_t>& get_remote_modules() {
|
||||||
|
|
||||||
data[hModule] = nth64.OptionalHeader.SizeOfImage;
|
data[hModule] = nth64.OptionalHeader.SizeOfImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
|
@ -292,35 +292,43 @@ std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef =
|
||||||
|
|
||||||
void print_exception_info(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) {
|
void print_exception_info(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) {
|
||||||
std::vector<EXCEPTION_RECORD> exRecs;
|
std::vector<EXCEPTION_RECORD> exRecs;
|
||||||
if (ex.ExceptionRecord) {
|
if (ex.ExceptionRecord)
|
||||||
|
{
|
||||||
size_t rec_index = 0;
|
size_t rec_index = 0;
|
||||||
size_t read;
|
size_t read;
|
||||||
exRecs.emplace_back();
|
|
||||||
for (auto pRemoteExRec = ex.ExceptionRecord;
|
for (auto pRemoteExRec = ex.ExceptionRecord;
|
||||||
pRemoteExRec
|
pRemoteExRec && rec_index < 64;
|
||||||
&& rec_index < 64
|
rec_index++)
|
||||||
&& ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read)
|
{
|
||||||
&& read >= offsetof(EXCEPTION_RECORD, ExceptionInformation)
|
exRecs.emplace_back();
|
||||||
&& read >= static_cast<size_t>(reinterpret_cast<const char*>(&exRecs.back().ExceptionInformation[exRecs.back().NumberParameters]) - reinterpret_cast<const char*>(&exRecs.back()));
|
|
||||||
rec_index++) {
|
if (!ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read)
|
||||||
|
|| read < offsetof(EXCEPTION_RECORD, ExceptionInformation)
|
||||||
|
|| read < static_cast<size_t>(reinterpret_cast<const char*>(&exRecs.back().ExceptionInformation[exRecs.
|
||||||
|
back().NumberParameters]) - reinterpret_cast<const char*>(&exRecs.back())))
|
||||||
|
{
|
||||||
|
exRecs.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
log << std::format(L"\nException Info #{}\n", rec_index);
|
log << std::format(L"\nException Info #{}\n", rec_index);
|
||||||
log << std::format(L"Address: {:X}\n", exRecs.back().ExceptionCode);
|
log << std::format(L"Address: {:X}\n", exRecs.back().ExceptionCode);
|
||||||
log << std::format(L"Flags: {:X}\n", exRecs.back().ExceptionFlags);
|
log << std::format(L"Flags: {:X}\n", exRecs.back().ExceptionFlags);
|
||||||
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(exRecs.back().ExceptionAddress));
|
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(exRecs.back().ExceptionAddress));
|
||||||
if (!exRecs.back().NumberParameters)
|
if (exRecs.back().NumberParameters)
|
||||||
continue;
|
{
|
||||||
log << L"Parameters: ";
|
log << L"Parameters: ";
|
||||||
for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) {
|
for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i)
|
||||||
if (i != 0)
|
{
|
||||||
log << L", ";
|
if (i != 0)
|
||||||
log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]);
|
log << L", ";
|
||||||
|
log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pRemoteExRec = exRecs.back().ExceptionRecord;
|
pRemoteExRec = exRecs.back().ExceptionRecord;
|
||||||
exRecs.emplace_back();
|
|
||||||
}
|
}
|
||||||
exRecs.pop_back();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log << L"\nCall Stack\n{";
|
log << L"\nCall Stack\n{";
|
||||||
|
|
@ -410,7 +418,7 @@ void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT&
|
||||||
|
|
||||||
std::wstring escape_shell_arg(const std::wstring& arg) {
|
std::wstring escape_shell_arg(const std::wstring& arg) {
|
||||||
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||||
|
|
||||||
std::wstring res;
|
std::wstring res;
|
||||||
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
||||||
res.append(arg);
|
res.append(arg);
|
||||||
|
|
@ -504,7 +512,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s
|
||||||
filePath.emplace(pFilePath);
|
filePath.emplace(pFilePath);
|
||||||
|
|
||||||
std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc);
|
std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc);
|
||||||
|
|
||||||
mz_zip_archive zipa{};
|
mz_zip_archive zipa{};
|
||||||
zipa.m_pIO_opaque = &fileStream;
|
zipa.m_pIO_opaque = &fileStream;
|
||||||
zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t {
|
zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t {
|
||||||
|
|
@ -566,7 +574,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s
|
||||||
const auto hLogFile = CreateFileW(logFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
const auto hLogFile = CreateFileW(logFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||||
if (hLogFile == INVALID_HANDLE_VALUE)
|
if (hLogFile == INVALID_HANDLE_VALUE)
|
||||||
throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring())));
|
throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring())));
|
||||||
|
|
||||||
std::unique_ptr<void, decltype(&CloseHandle)> hLogFileClose(hLogFile, &CloseHandle);
|
std::unique_ptr<void, decltype(&CloseHandle)> hLogFileClose(hLogFile, &CloseHandle);
|
||||||
|
|
||||||
LARGE_INTEGER size, baseOffset{};
|
LARGE_INTEGER size, baseOffset{};
|
||||||
|
|
@ -695,7 +703,7 @@ int main() {
|
||||||
|
|
||||||
// IFileSaveDialog only works on STA
|
// IFileSaveDialog only works on STA
|
||||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
std::vector<std::wstring> args;
|
std::vector<std::wstring> args;
|
||||||
if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) {
|
if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) {
|
||||||
for (auto i = 0; i < argc; i++)
|
for (auto i = 0; i < argc; i++)
|
||||||
|
|
@ -823,14 +831,14 @@ int main() {
|
||||||
hr = pOleWindow->GetWindow(&hwndProgressDialog);
|
hr = pOleWindow->GetWindow(&hwndProgressDialog);
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0,
|
SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0,
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||||
SetForegroundWindow(hwndProgressDialog);
|
SetForegroundWindow(hwndProgressDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
pOleWindow->Release();
|
pOleWindow->Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cerr << "Failed to create progress window" << std::endl;
|
std::cerr << "Failed to create progress window" << std::endl;
|
||||||
|
|
@ -852,14 +860,14 @@ int main() {
|
||||||
|
|
||||||
https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp
|
https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (g_bSymbolsAvailable) {
|
if (g_bSymbolsAvailable) {
|
||||||
SymRefreshModuleList(g_hProcess);
|
SymRefreshModuleList(g_hProcess);
|
||||||
}
|
}
|
||||||
else if(!assetDir.empty())
|
else if(!assetDir.empty())
|
||||||
{
|
{
|
||||||
auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring());
|
auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring());
|
||||||
|
|
||||||
g_bSymbolsAvailable = SymInitializeW(g_hProcess, symbol_search_path.c_str(), true);
|
g_bSymbolsAvailable = SymInitializeW(g_hProcess, symbol_search_path.c_str(), true);
|
||||||
std::wcout << std::format(L"Init symbols with PDB at {}", symbol_search_path) << std::endl;
|
std::wcout << std::format(L"Init symbols with PDB at {}", symbol_search_path) << std::endl;
|
||||||
|
|
||||||
|
|
@ -870,12 +878,12 @@ int main() {
|
||||||
g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true);
|
g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true);
|
||||||
std::cout << "Init symbols without PDB" << std::endl;
|
std::cout << "Init symbols without PDB" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_bSymbolsAvailable) {
|
if (!g_bSymbolsAvailable) {
|
||||||
std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl;
|
std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pProgressDialog)
|
if (pProgressDialog)
|
||||||
pProgressDialog->SetLine(3, L"Reading troubleshooting data", FALSE, NULL);
|
pProgressDialog->SetLine(3, L"Reading troubleshooting data", FALSE, NULL);
|
||||||
|
|
||||||
std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0');
|
std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0');
|
||||||
|
|
@ -930,13 +938,23 @@ int main() {
|
||||||
} while (false);
|
} while (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool is_external_event = exinfo.ExceptionRecord.ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT;
|
||||||
|
|
||||||
std::wostringstream log;
|
std::wostringstream log;
|
||||||
log << std::format(L"Unhandled native exception occurred at {}", to_address_string(exinfo.ContextRecord.Rip, false)) << std::endl;
|
|
||||||
log << std::format(L"Code: {:X}", exinfo.ExceptionRecord.ExceptionCode) << std::endl;
|
if (!is_external_event)
|
||||||
|
{
|
||||||
|
log << std::format(L"Unhandled native exception occurred at {}", to_address_string(exinfo.ContextRecord.Rip, false)) << std::endl;
|
||||||
|
log << std::format(L"Code: {:X}", exinfo.ExceptionRecord.ExceptionCode) << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log << L"CLR error occurred" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (shutup)
|
if (shutup)
|
||||||
log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl;
|
log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl;
|
||||||
|
|
||||||
if (dumpPath.empty())
|
if (dumpPath.empty())
|
||||||
log << L"Dump skipped" << std::endl;
|
log << L"Dump skipped" << std::endl;
|
||||||
else if (dumpError.empty())
|
else if (dumpError.empty())
|
||||||
|
|
@ -949,9 +967,19 @@ int main() {
|
||||||
if (pProgressDialog)
|
if (pProgressDialog)
|
||||||
pProgressDialog->SetLine(3, L"Refreshing Module List", FALSE, NULL);
|
pProgressDialog->SetLine(3, L"Refreshing Module List", FALSE, NULL);
|
||||||
|
|
||||||
|
std::wstring window_log_str;
|
||||||
|
|
||||||
|
// Cut the log here for external events, the rest is unreadable and doesn't matter since we can't get
|
||||||
|
// symbols for mixed-mode stacks yet.
|
||||||
|
if (is_external_event)
|
||||||
|
window_log_str = log.str();
|
||||||
|
|
||||||
SymRefreshModuleList(GetCurrentProcess());
|
SymRefreshModuleList(GetCurrentProcess());
|
||||||
print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log);
|
print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log);
|
||||||
const auto window_log_str = log.str();
|
|
||||||
|
if (!is_external_event)
|
||||||
|
window_log_str = log.str();
|
||||||
|
|
||||||
print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log);
|
print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log);
|
||||||
std::wofstream(logPath) << log.str();
|
std::wofstream(logPath) << log.str();
|
||||||
|
|
||||||
|
|
@ -1003,7 +1031,7 @@ int main() {
|
||||||
R"aa(<a href="help">Help</a> | <a href="logdir">Open log directory</a> | <a href="logfile">Open log file</a>)aa"
|
R"aa(<a href="help">Help</a> | <a href="logdir">Open log directory</a> | <a href="logfile">Open log file</a>)aa"
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Can't do this, xiv stops pumping messages here
|
// Can't do this, xiv stops pumping messages here
|
||||||
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
|
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
|
||||||
|
|
||||||
|
|
@ -1056,13 +1084,13 @@ int main() {
|
||||||
return (*reinterpret_cast<decltype(callback)*>(dwRefData))(hwnd, uNotification, wParam, lParam);
|
return (*reinterpret_cast<decltype(callback)*>(dwRefData))(hwnd, uNotification, wParam, lParam);
|
||||||
};
|
};
|
||||||
config.lpCallbackData = reinterpret_cast<LONG_PTR>(&callback);
|
config.lpCallbackData = reinterpret_cast<LONG_PTR>(&callback);
|
||||||
|
|
||||||
if (pProgressDialog) {
|
if (pProgressDialog) {
|
||||||
pProgressDialog->StopProgressDialog();
|
pProgressDialog->StopProgressDialog();
|
||||||
pProgressDialog->Release();
|
pProgressDialog->Release();
|
||||||
pProgressDialog = NULL;
|
pProgressDialog = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); };
|
const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); };
|
||||||
|
|
||||||
if (shutup) {
|
if (shutup) {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<!-- DirectX / Win32 -->
|
<!-- DirectX / Win32 -->
|
||||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
||||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.259" />
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- Logging -->
|
||||||
<PackageVersion Include="Serilog" Version="4.0.2" />
|
<PackageVersion Include="Serilog" Version="4.0.2" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue