diff --git a/.github/generate_changelog.py b/.github/generate_changelog.py index b07100115..5e921fd6e 100644 --- a/.github/generate_changelog.py +++ b/.github/generate_changelog.py @@ -8,8 +8,7 @@ import re import sys import json import argparse -import os -from typing import List, Tuple, Optional, Dict, Any +from typing import List, Tuple, Optional def run_git_command(args: List[str]) -> str: @@ -31,14 +30,14 @@ def get_last_two_tags() -> Tuple[str, str]: """Get the latest two git tags.""" tags = run_git_command(["tag", "--sort=-version:refname"]) tag_list = [t for t in tags.split("\n") if t] - + # Filter out old tags that start with 'v' (old versioning scheme) tag_list = [t for t in tag_list if not t.startswith('v')] if len(tag_list) < 2: print("Error: Need at least 2 tags in the repository", file=sys.stderr) sys.exit(1) - + return tag_list[0], tag_list[1] @@ -56,144 +55,58 @@ def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]: return None -def get_repo_info() -> Tuple[str, str]: - """Get repository owner and name from git remote.""" - try: - remote_url = run_git_command(["config", "--get", "remote.origin.url"]) - - # Handle both HTTPS and SSH URLs - # SSH: git@github.com:owner/repo.git - # HTTPS: https://github.com/owner/repo.git - match = re.search(r'github\.com[:/](.+?)/(.+?)(?:\.git)?$', remote_url) - if match: - owner = match.group(1) - repo = match.group(2) - return owner, repo - else: - print("Error: Could not parse GitHub repository from remote URL", file=sys.stderr) - sys.exit(1) - except: - print("Error: Could not get git remote URL", file=sys.stderr) - sys.exit(1) - - -def get_commits_between_tags(tag1: str, tag2: str) -> List[str]: - """Get commit SHAs between two tags.""" +def get_commits_between_tags(tag1: str, tag2: str) -> List[Tuple[str, str]]: + """Get commits between two tags. Returns list of (message, author) tuples.""" log_output = run_git_command([ "log", f"{tag2}..{tag1}", - "--format=%H" + "--format=%s|%an|%h" ]) - - commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()] + + commits = [] + for line in log_output.split("\n"): + if "|" in line: + message, author, sha = line.split("|", 2) + commits.append((message.strip(), author.strip(), sha.strip())) + return commits -def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]: - """Get PR information for a commit using GitHub API.""" - try: - import requests - except ImportError: - print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr) - sys.exit(1) - - headers = { - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28" - } - - if token: - headers["Authorization"] = f"Bearer {token}" - - url = f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}/pulls" - - try: - response = requests.get(url, headers=headers) - response.raise_for_status() - prs = response.json() - - if prs and len(prs) > 0: - # Return the first PR (most relevant one) - pr = prs[0] - return { - "number": pr["number"], - "title": pr["title"], - "author": pr["user"]["login"], - "url": pr["html_url"] - } - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - # Commit might not be associated with a PR - return None - elif e.response.status_code == 403: - print("Warning: GitHub API rate limit exceeded. Consider providing a token.", file=sys.stderr) - return None - else: - print(f"Warning: Failed to fetch PR for commit {commit_sha[:7]}: {e}", file=sys.stderr) - return None - except Exception as e: - print(f"Warning: Error fetching PR for commit {commit_sha[:7]}: {e}", file=sys.stderr) - return None - - return None - - -def get_prs_between_tags(tag1: str, tag2: str, owner: str, repo: str, token: str) -> List[Dict[str, Any]]: - """Get PRs between two tags using GitHub API.""" - commits = get_commits_between_tags(tag1, tag2) - print(f"Found {len(commits)} commits, fetching PR information...") - - prs = [] - seen_pr_numbers = set() - - for i, commit_sha in enumerate(commits, 1): - if i % 10 == 0: - print(f"Progress: {i}/{len(commits)} commits processed...") - - pr_info = get_pr_for_commit(commit_sha, owner, repo, token) - if pr_info and pr_info["number"] not in seen_pr_numbers: - seen_pr_numbers.add(pr_info["number"]) - prs.append(pr_info) - - return prs - - -def filter_prs(prs: List[Dict[str, Any]], ignore_patterns: List[str]) -> List[Dict[str, Any]]: - """Filter out PRs matching any of the ignore patterns.""" +def filter_commits(commits: List[Tuple[str, str]], ignore_patterns: List[str]) -> List[Tuple[str, str]]: + """Filter out commits matching any of the ignore patterns.""" compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns] - + filtered = [] - for pr in prs: - if not any(pattern.search(pr["title"]) for pattern in compiled_patterns): - filtered.append(pr) - + for message, author, sha in commits: + if not any(pattern.search(message) for pattern in compiled_patterns): + filtered.append((message, author, sha)) + return filtered -def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]], - cs_commit_new: Optional[str], cs_commit_old: Optional[str], - owner: str, repo: str) -> str: +def generate_changelog(version: str, prev_version: str, commits: List[Tuple[str, str]], + cs_commit_new: Optional[str], cs_commit_old: Optional[str]) -> str: """Generate markdown changelog.""" # Calculate statistics - pr_count = len(prs) - unique_authors = len(set(pr["author"] for pr in prs)) - + commit_count = len(commits) + unique_authors = len(set(author for _, author, _ in commits)) + changelog = f"# Dalamud Release v{version}\n\n" changelog += f"We just released Dalamud v{version}, which should be available to users within a few minutes. " - changelog += f"This release includes **{pr_count} PR{'s' if pr_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n" - changelog += f"[Click here]() to see all Dalamud changes.\n\n" - + changelog += f"This release includes **{commit_count} commit{'s' if commit_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n" + changelog += f"[Click here]() to see all Dalamud changes.\n\n" + if cs_commit_new and cs_commit_old and cs_commit_new != cs_commit_old: changelog += f"It ships with an updated **FFXIVClientStructs [`{cs_commit_new[:7]}`]()**.\n" changelog += f"[Click here]() to see all CS changes.\n" elif cs_commit_new: changelog += f"It ships with **FFXIVClientStructs [`{cs_commit_new[:7]}`]()**.\n" - + changelog += "## Dalamud Changes\n\n" - - for pr in prs: - changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n" - + + for message, author, sha in commits: + changelog += f"* {message} (by **{author}** as [`{sha}`]())\n" + return changelog @@ -204,9 +117,9 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None: except ImportError: print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr) sys.exit(1) - + filename = f"changelog-v{version}.md" - + # Prepare the payload data = { "content": f"Dalamud v{version} has been released!", @@ -217,13 +130,13 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None: } ] } - + # Prepare the files files = { "payload_json": (None, json.dumps(data)), "files[0]": (filename, content.encode('utf-8'), 'text/markdown') } - + try: result = requests.post(webhook_url, files=files) result.raise_for_status() @@ -245,64 +158,54 @@ def main(): required=True, help="Discord webhook URL" ) - parser.add_argument( - "--github-token", - default=os.environ.get("GITHUB_TOKEN"), - help="GitHub API token (or set GITHUB_TOKEN env var). Increases rate limit." - ) parser.add_argument( "--ignore", action="append", default=[], - help="Regex patterns to ignore PRs (can be specified multiple times)" + help="Regex patterns to ignore commits (can be specified multiple times)" ) parser.add_argument( "--submodule-path", default="lib/FFXIVClientStructs", help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)" ) - + args = parser.parse_args() - - # Get repository info - owner, repo = get_repo_info() - print(f"Repository: {owner}/{repo}") - + # Get the last two tags latest_tag, previous_tag = get_last_two_tags() print(f"Generating changelog between {previous_tag} and {latest_tag}") - + # Get submodule commits at both tags cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag) cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag) - + if cs_commit_new: print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}") if cs_commit_old: print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}") - - # Get PRs between tags - prs = get_prs_between_tags(latest_tag, previous_tag, owner, repo, args.github_token) - prs.reverse() - print(f"Found {len(prs)} PRs") - - # Filter PRs - filtered_prs = filter_prs(prs, args.ignore) - print(f"After filtering: {len(filtered_prs)} PRs") - + + # Get commits between tags + commits = get_commits_between_tags(latest_tag, previous_tag) + print(f"Found {len(commits)} commits") + + # Filter commits + filtered_commits = filter_commits(commits, args.ignore) + print(f"After filtering: {len(filtered_commits)} commits") + # Generate changelog - changelog = generate_changelog(latest_tag, previous_tag, filtered_prs, - cs_commit_new, cs_commit_old, owner, repo) - + changelog = generate_changelog(latest_tag, previous_tag, filtered_commits, + cs_commit_new, cs_commit_old) + print("\n" + "="*50) print("Generated Changelog:") print("="*50) print(changelog) print("="*50 + "\n") - + # Post to Discord post_to_discord(args.webhook_url, changelog, latest_tag) if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml index e62d5f37c..5fed3b1eb 100644 --- a/.github/workflows/generate-changelog.yml +++ b/.github/workflows/generate-changelog.yml @@ -6,43 +6,41 @@ on: tags: - '*' -permissions: read-all - jobs: generate-changelog: runs-on: ubuntu-latest - + steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history and tags submodules: true # Fetch submodules - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.14' - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install requests - + - name: Generate and post changelog - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_TERMINAL_PROMPT: 0 run: | python .github/generate_changelog.py \ --webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \ - --ignore "Update ClientStructs" \ - --ignore "^build:" - + --ignore "^Merge" \ + --ignore "^build:" \ + --ignore "^docs:" + env: + GIT_TERMINAL_PROMPT: 0 + - name: Upload changelog as artifact if: always() uses: actions/upload-artifact@v4 with: name: changelog path: changelog-*.md - if-no-files-found: ignore + if-no-files-found: ignore \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9466cb083..299d71e95 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,9 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] -# Globally blocking because of git pushes in deploy step concurrency: - group: build_dalamud_${{ github.repository_owner }} - cancel-in-progress: false + group: build_dalamud_${{ github.ref_name }} + cancel-in-progress: true jobs: build: diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 6ffb3bb01..03211ce8f 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -1,57 +1,19 @@ { "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Build Schema", + "$ref": "#/definitions/build", "definitions": { - "Host": { - "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": { + "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" @@ -61,8 +23,29 @@ "description": "Shows the help text for this build assembly" }, "Host": { + "type": "string", "description": "Host for execution. Default is 'automatic'", - "$ref": "#/definitions/Host" + "enum": [ + "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": { "type": "boolean", @@ -91,46 +74,63 @@ "type": "array", "description": "List of targets to be skipped. Empty list skips all dependencies", "items": { - "$ref": "#/definitions/ExecutableTarget" + "type": "string", + "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": { "type": "array", "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "$ref": "#/definitions/ExecutableTarget" + "type": "string", + "enum": [ + "CI", + "Clean", + "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", + "CompileDalamud", + "CompileDalamudBoot", + "CompileDalamudCrashHandler", + "CompileImGuiNatives", + "CompileInjector", + "Restore", + "SetCILogging", + "Test" + ] } }, "Verbosity": { + "type": "string", "description": "Logging verbosity during build execution. Default is 'Normal'", - "$ref": "#/definitions/Verbosity" - } - } - } - }, - "allOf": [ - { - "properties": { - "Configuration": { - "type": "string", - "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", "enum": [ - "Debug", - "Release" + "Minimal", + "Normal", + "Quiet", + "Verbose" ] - }, - "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" } - ] -} + } +} \ No newline at end of file diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h index 0308306ce..8d93e4460 100644 --- a/Dalamud.Boot/crashhandler_shared.h +++ b/Dalamud.Boot/crashhandler_shared.h @@ -6,8 +6,6 @@ #define WIN32_LEAN_AND_MEAN #include -#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679 - struct exception_info { LPEXCEPTION_POINTERS pExceptionPointers; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 687089f82..80a16f89a 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -331,51 +331,6 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { 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> s_report_event_hook; - s_report_event_hook = std::make_shared>( - "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 ==================================== // if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index c0a0b034a..b75256af8 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -31,8 +31,6 @@ HANDLE g_crashhandler_process = nullptr; HANDLE g_crashhandler_event = nullptr; HANDLE g_crashhandler_pipe_write = nullptr; -wchar_t g_external_event_info[16384] = L""; - std::recursive_mutex g_exception_handler_mutex; std::chrono::time_point g_time_start; @@ -193,11 +191,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS); std::wstring stackTrace; - if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT) - { - stackTrace = std::wstring(g_external_event_info); - } - else if (!g_clr) + if (!g_clr) { stackTrace = L"(no CLR stack trace available)"; } @@ -258,12 +252,6 @@ LONG WINAPI structured_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) { // pass @@ -447,16 +435,3 @@ bool veh::remove_handler() } 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); -} - -extern "C" __declspec(dllexport) void BootVehRaiseExternalEventW(LPCWSTR info) -{ - const std::wstring info_wstr(info); - veh::raise_external_event(info_wstr); -} diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index 2a02c374e..1905272ea 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -4,5 +4,4 @@ namespace veh { bool add_handler(bool doFullDump, const std::string& workingDirectory); bool remove_handler(); - void raise_external_event(const std::wstring& info); } diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 0b0d3db59..090301c10 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 13.0.0.13 + 13.0.0.12 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) @@ -73,6 +73,8 @@ all + + @@ -121,8 +123,6 @@ PreserveNewest - - diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 54e25b6f2..15077f3d8 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -263,7 +263,7 @@ public sealed class EntryPoint var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb"); var searchPath = $".;{symbolPath}"; - var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess(); + var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle(); // Remove any existing Symbol Handler and Init a new one with our search path added Windows.Win32.PInvoke.SymCleanup(currentProcess); @@ -292,6 +292,7 @@ public sealed class EntryPoint } var pluginInfo = string.Empty; + var supportText = ", please visit us on Discord for more help"; try { var pm = Service.GetNullable(); @@ -299,6 +300,9 @@ public sealed class EntryPoint if (plugin != null) { pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n"; + + if (plugin.IsThirdParty) + supportText = string.Empty; } } catch @@ -306,18 +310,31 @@ public sealed class EntryPoint // ignored } - Log.CloseAndFlush(); + const MESSAGEBOX_STYLE flags = MESSAGEBOX_STYLE.MB_YESNO | MESSAGEBOX_STYLE.MB_ICONERROR | MESSAGEBOX_STYLE.MB_SYSTEMMODAL; + var result = Windows.Win32.PInvoke.MessageBox( + new HWND(Process.GetCurrentProcess().MainWindowHandle), + $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\n{ex.GetType().Name}\n{info}\n\n{pluginInfo}More information has been recorded separately{supportText}.\n\nDo you want to disable all plugins the next time you start the game?", + "Dalamud", + flags); - ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}"); + if (result == MESSAGEBOX_RESULT.IDYES) + { + Log.Information("User chose to disable plugins on next launch..."); + var config = Service.Get(); + config.PluginSafeMode = true; + config.ForceSave(); + } + + Log.CloseAndFlush(); + Environment.Exit(-1); break; default: Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); Log.CloseAndFlush(); + Environment.Exit(-1); break; } - - Environment.Exit(-1); } private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args) diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 980404940..945197e2b 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -9,6 +9,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Events; @@ -31,21 +32,25 @@ internal unsafe class AddonEventManager : IInternalDisposableService private readonly AddonLifecycleEventListener finalizeEventListener; - private readonly Hook onUpdateCursor; + private readonly AddonEventManagerAddressResolver address; + private readonly Hook onUpdateCursor; private readonly ConcurrentDictionary pluginEventControllers; - private AtkCursor.CursorType? cursorOverride; + private AddonCursorType? cursorOverride; [ServiceManager.ServiceConstructor] - private AddonEventManager() + private AddonEventManager(TargetSigScanner sigScanner) { + this.address = new AddonEventManagerAddressResolver(); + this.address.Setup(sigScanner); + this.pluginEventControllers = new ConcurrentDictionary(); this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController()); this.cursorOverride = null; - this.onUpdateCursor = Hook.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour); + this.onUpdateCursor = Hook.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour); this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize); this.addonLifecycle.RegisterListener(this.finalizeEventListener); @@ -53,6 +58,8 @@ internal unsafe class AddonEventManager : IInternalDisposableService this.onUpdateCursor.Enable(); } + private delegate nint UpdateCursorDelegate(RaptureAtkModule* module); + /// void IInternalDisposableService.DisposeService() { @@ -110,7 +117,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService /// Force the game cursor to be the specified cursor. /// /// Which cursor to use. - internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor; + internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor; /// /// Un-forces the game cursor. @@ -161,7 +168,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService } } - private void UpdateCursorDetour(AtkUnitManager* thisPtr) + private nint UpdateCursorDetour(RaptureAtkModule* module) { try { @@ -169,14 +176,13 @@ internal unsafe class AddonEventManager : IInternalDisposableService if (this.cursorOverride is not null && atkStage is not null) { - ref var atkCursor = ref atkStage->AtkCursor; - - if (atkCursor.Type != this.cursorOverride) + var cursor = (AddonCursorType)atkStage->AtkCursor.Type; + if (cursor != this.cursorOverride) { - atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); + AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); } - return; + return nint.Zero; } } catch (Exception e) @@ -184,7 +190,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService Log.Error(e, "Exception in UpdateCursorDetour."); } - this.onUpdateCursor!.Original(thisPtr); + return this.onUpdateCursor!.Original(module); } } diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs new file mode 100644 index 000000000..ec1c51a12 --- /dev/null +++ b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs @@ -0,0 +1,23 @@ +using Dalamud.Plugin.Services; + +namespace Dalamud.Game.Addon.Events; + +/// +/// AddonEventManager memory address resolver. +/// +internal class AddonEventManagerAddressResolver : BaseAddressResolver +{ + /// + /// Gets the address of the AtkModule UpdateCursor method. + /// + public nint UpdateCursor { get; private set; } + + /// + /// Scan for and setup any configured address pointers. + /// + /// The signature scanner to facilitate setup. + 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 + } +} diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 1cd3ef91d..faf4658a5 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -201,19 +201,19 @@ public abstract class Hook : IDalamudHook where T : Delegate if (EnvironmentConfiguration.DalamudForceMinHook) useMinHook = true; - var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName); - if (moduleHandle.IsNull) + using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName); + if (moduleHandle.IsInvalid) throw new Exception($"Could not get a handle to module {moduleName}"); - var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName); - if (procAddress.IsNull) + var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName); + if (procAddress == IntPtr.Zero) throw new Exception($"Could not get the address of {moduleName}::{exportName}"); - var address = HookManager.FollowJmp(procAddress.Value); + procAddress = HookManager.FollowJmp(procAddress); if (useMinHook) - return new MinHookHook(address, detour, Assembly.GetCallingAssembly()); + return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); else - return new ReloadedHook(address, detour, Assembly.GetCallingAssembly()); + return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); } /// diff --git a/Dalamud/Interface/FontIdentifier/IObjectWithLocalizableName.cs b/Dalamud/Interface/FontIdentifier/IObjectWithLocalizableName.cs index 4b3860431..2b970a5fd 100644 --- a/Dalamud/Interface/FontIdentifier/IObjectWithLocalizableName.cs +++ b/Dalamud/Interface/FontIdentifier/IObjectWithLocalizableName.cs @@ -64,9 +64,9 @@ public interface IObjectWithLocalizableName var result = new Dictionary((int)count); for (var i = 0u; i < count; i++) { - fn->GetLocaleName(i, buf, maxStrLen).ThrowOnError(); + fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError(); var key = new string(buf); - fn->GetString(i, buf, maxStrLen).ThrowOnError(); + fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError(); var value = new string(buf); result[key.ToLowerInvariant()] = value; } diff --git a/Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs b/Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs index 83a5e810d..420ee77a4 100644 --- a/Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs +++ b/Dalamud/Interface/FontIdentifier/SystemFontFamilyId.cs @@ -133,8 +133,8 @@ public sealed class SystemFontFamilyId : IFontFamilyId var familyIndex = 0u; BOOL exists = false; - fixed (char* pName = this.EnglishName) - sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError(); + fixed (void* pName = this.EnglishName) + sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError(); if (!exists) throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found."); diff --git a/Dalamud/Interface/FontIdentifier/SystemFontId.cs b/Dalamud/Interface/FontIdentifier/SystemFontId.cs index 8401f4c79..e11759a88 100644 --- a/Dalamud/Interface/FontIdentifier/SystemFontId.cs +++ b/Dalamud/Interface/FontIdentifier/SystemFontId.cs @@ -113,8 +113,8 @@ public sealed class SystemFontId : IFontId var familyIndex = 0u; BOOL exists = false; - fixed (char* name = this.Family.EnglishName) - sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError(); + fixed (void* name = this.Family.EnglishName) + sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError(); if (!exists) throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found."); @@ -151,7 +151,7 @@ public sealed class SystemFontId : IFontId flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError(); var path = stackalloc char[(int)pathSize + 1]; - flocal.Get()->GetFilePathFromKey(refKey, refKeySize, path, pathSize + 1).ThrowOnError(); + flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError(); return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex()); } diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs index 3f3c98c26..824ba382a 100644 --- a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs +++ b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs @@ -104,19 +104,19 @@ internal static unsafe class ReShadePeeler fixed (byte* pfn5 = "glBegin"u8) fixed (byte* pfn6 = "vkCreateDevice"u8) { - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0) continue; - if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == null) + if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0) continue; } diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs index a9e9b5a0f..0b2e27b57 100644 --- a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs @@ -672,7 +672,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND), lpfnWndProc = (delegate* unmanaged)Marshal .GetFunctionPointerForDelegate(this.input.wndProcDelegate), - lpszClassName = windowClassNamePtr, + lpszClassName = (ushort*)windowClassNamePtr, }; if (RegisterClassExW(&wcex) == 0) @@ -701,7 +701,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler fixed (char* windowClassNamePtr = WindowClassName) { UnregisterClassW( - windowClassNamePtr, + (ushort*)windowClassNamePtr, (HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module)); } @@ -815,8 +815,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler { data->Hwnd = CreateWindowExW( (uint)data->DwExStyle, - windowClassNamePtr, - windowClassNamePtr, + (ushort*)windowClassNamePtr, + (ushort*)windowClassNamePtr, (uint)data->DwStyle, rect.left, rect.top, @@ -1030,7 +1030,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler { var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title)) - SetWindowTextW(data->Hwnd, pwszTitle); + SetWindowTextW(data->Hwnd, (ushort*)pwszTitle); } [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 397502b30..87df2da2c 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -161,9 +161,7 @@ internal class SeStringRenderer : IServiceType ImFont* font = null; if (drawParams.Font.HasValue) font = drawParams.Font.Value; - - // API14: Remove commented out code - if (ThreadSafety.IsMainThread /* && drawParams.TargetDrawList is null */ && font is null) + if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null) font = ImGui.GetFont(); if (font is null) throw new ArgumentException("Specified font is empty."); diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs index 972013328..1d8126f3b 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs @@ -14,9 +14,8 @@ public record struct SeStringDrawParams /// (the default). /// /// If this value is set, will not be called, and ImGui ID will be ignored. - /// You must specify a valid draw list, a valid font via and if you set this value, + /// You must specify a valid draw list and a valid font via if you set this value, /// since the renderer will not be able to retrieve them from ImGui context. - /// Must be set when drawing off the main thread. /// public ImDrawListPtr? TargetDrawList { get; set; } @@ -30,13 +29,11 @@ public record struct SeStringDrawParams /// Gets or sets the font to use. /// Font to use, or null to use (the default). - /// Must be set when specifying a target draw-list or drawing off the main thread. public ImFontPtr? Font { get; set; } /// Gets or sets the font size. /// Font size in pixels, or 0 to use the current ImGui font size . /// - /// Must be set when specifying a target draw-list or drawing off the main thread. public float? FontSize { get; set; } /// Gets or sets the line height ratio. diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs index b52d011d5..11c1120b4 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -64,20 +64,8 @@ public unsafe ref struct SeStringDrawState { this.drawList = ssdp.TargetDrawList.Value; this.ScreenOffset = Vector2.Zero; - - // API14: Remove, always throw - if (ThreadSafety.IsMainThread) - { - this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); - } - else - { - throw new ArgumentException( - $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state."); - } - - // this.FontSize = ssdp.FontSize ?? throw new ArgumentException( - // $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state."); + this.FontSize = ssdp.FontSize ?? throw new ArgumentException( + $"{nameof(ssdp.FontSize)} must be set to render outside the main thread."); this.WrapWidth = ssdp.WrapWidth ?? float.MaxValue; this.Color = ssdp.Color ?? uint.MaxValue; this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread. @@ -88,7 +76,7 @@ public unsafe ref struct SeStringDrawState this.splitter = default; this.GetEntity = ssdp.GetEntity; 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.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; this.Opacity = ssdp.EffectiveOpacity; @@ -118,7 +106,7 @@ public unsafe ref struct SeStringDrawState public Vector2 ScreenOffset { get; } /// - public ImFontPtr Font { get; } + public ImFont* Font { get; } /// public float FontSize { get; } @@ -268,7 +256,7 @@ public unsafe ref struct SeStringDrawState /// Offset of the glyph in pixels w.r.t. . internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) { - var texId = this.Font.ContainerAtlas.Textures.Ref(g.TextureIndex).TexID; + var texId = this.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID; var xy0 = new Vector2( MathF.Round(g.X0 * this.FontSizeScale), MathF.Round(g.Y0 * this.FontSizeScale)); @@ -325,7 +313,7 @@ public unsafe ref struct SeStringDrawState offset += this.ScreenOffset; 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.DrawList.AddLine( @@ -352,9 +340,9 @@ public unsafe ref struct SeStringDrawState internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) { var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue - ? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value) - : this.Font.FallbackGlyph; - return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle; + ? this.Font->FindGlyph((ushort)rune.Value) + : this.Font->FallbackGlyph; + return ref *(ImGuiHelpers.ImFontGlyphReal*)p; } /// Gets the glyph corresponding to the given codepoint. @@ -387,7 +375,7 @@ public unsafe ref struct SeStringDrawState return 0; return MathF.Round( - this.Font.GetDistanceAdjustmentForPair( + this.Font->GetDistanceAdjustmentForPair( (ushort)left.Value, (ushort)right.Value) * this.FontSizeScale); } diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index bf55a5486..af78c5b0c 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -667,8 +667,6 @@ internal class DalamudInterface : IInternalDisposableService { if (this.isImGuiDrawDevMenu) { - using var barColor = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0.060f, 0.060f, 0.060f, 0.773f)); - barColor.Push(ImGuiCol.MenuBarBg, Vector4.Zero); if (ImGui.BeginMainMenuBar()) { var pluginManager = Service.Get(); @@ -841,11 +839,6 @@ internal class DalamudInterface : IInternalDisposableService ImGui.PopStyleVar(); } - if (ImGui.MenuItem("Raise external event through boot")) - { - ErrorHandling.CrashWithContext("Tést"); - } - ImGui.EndMenu(); } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 96fcb7dfd..76a1b5172 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -256,7 +256,7 @@ internal partial class InterfaceManager : IInternalDisposableService var gwh = default(HWND); fixed (char* pClass = "FFXIVGAME") { - while ((gwh = FindWindowExW(default, gwh, pClass, default)) != default) + while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default) { uint pid; _ = GetWindowThreadProcessId(gwh, &pid); diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs index d7d3b56c3..d8d210076 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs @@ -63,11 +63,11 @@ internal sealed unsafe partial class ReShadeAddonInterface return; - static bool GetProcAddressInto(ProcessModule m, ReadOnlySpan name, void* res) + bool GetProcAddressInto(ProcessModule m, ReadOnlySpan name, void* res) { Span name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1]; name8[Encoding.UTF8.GetBytes(name, name8)] = 0; - *(nint*)res = (nint)GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0])); + *(nint*)res = GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0])); return *(nint*)res != 0; } } @@ -174,7 +174,7 @@ internal sealed unsafe partial class ReShadeAddonInterface CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT.CERT_NAME_ISSUER_FLAG, null, - (char*)Unsafe.AsPointer(ref issuerName[0]), + (ushort*)Unsafe.AsPointer(ref issuerName[0]), pcb); if (pcb == 0) throw new Win32Exception("CertGetNameStringW(2)"); diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs index 711de6eb2..f1210425d 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs @@ -94,7 +94,7 @@ internal static unsafe class ReShadeUnwrapper static bool HasProcExported(ProcessModule m, ReadOnlySpan name) { fixed (byte* p = name) - return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != null; + return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0; } } diff --git a/Dalamud/Interface/Internal/StaThreadService.cs b/Dalamud/Interface/Internal/StaThreadService.cs index bb5caa281..87e003288 100644 --- a/Dalamud/Interface/Internal/StaThreadService.cs +++ b/Dalamud/Interface/Internal/StaThreadService.cs @@ -216,7 +216,7 @@ internal partial class StaThreadService : IInternalDisposableService lpfnWndProc = &MessageReceiverWndProcStatic, hInstance = hInstance, hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1), - lpszClassName = name, + lpszClassName = (ushort*)name, }; wndClassAtom = RegisterClassExW(&wndClass); @@ -226,8 +226,8 @@ internal partial class StaThreadService : IInternalDisposableService this.messageReceiverHwndTask.SetResult( CreateWindowExW( 0, - (char*)wndClassAtom, - name, + (ushort*)wndClassAtom, + (ushort*)name, 0, CW_USEDEFAULT, CW_USEDEFAULT, @@ -275,7 +275,7 @@ internal partial class StaThreadService : IInternalDisposableService _ = OleFlushClipboard(); OleUninitialize(); if (wndClassAtom != 0) - UnregisterClassW((char*)wndClassAtom, hInstance); + UnregisterClassW((ushort*)wndClassAtom, hInstance); this.messageReceiverHwndTask.TrySetException(e); } } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index 41c87fd39..2a93cf093 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -15,6 +15,7 @@ using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Storage.Assets; using Dalamud.Utility; +using SharpDX.DXGI; using TerraFX.Interop.DirectX; namespace Dalamud.Interface.ManagedFontAtlas.Internals; @@ -748,7 +749,7 @@ internal sealed partial class FontAtlasFactory new( width, height, - (int)(use4 ? DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM), + (int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm), width * bpp), buf, name); diff --git a/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs index ec56caadd..3d5456500 100644 --- a/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs +++ b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs @@ -44,12 +44,12 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo private static unsafe string ReadStringUsing( IWICBitmapCodecInfo* codecInfo, - delegate* unmanaged[MemberFunction] readFuncPtr) + delegate* unmanaged readFuncPtr) { var cch = 0u; _ = readFuncPtr(codecInfo, 0, null, &cch); var buf = stackalloc char[(int)cch + 1]; - Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch)); + Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, (ushort*)buf, &cch)); return new(buf, 0, (int)cch); } } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs index fde40d462..837b41271 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs @@ -219,14 +219,14 @@ internal sealed partial class TextureManager return; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) => ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static uint AddRefStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0); - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static uint ReleaseStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0); } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs index 75f7ab975..8a510e967 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.Clipboard.cs @@ -133,7 +133,7 @@ internal sealed partial class TextureManager }, }, }; - namea.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgda.fgd.e0.cFileName[0]), 260)); + namea.AsSpan().CopyTo(new(fgda.fgd.e0.cFileName, 260)); AddToDataObject( pdo, @@ -157,7 +157,7 @@ internal sealed partial class TextureManager }, }, }; - preferredFileNameWithoutExtension.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgdw.fgd.e0.cFileName[0]), 260)); + preferredFileNameWithoutExtension.AsSpan().CopyTo(new(fgdw.fgd.e0.cFileName, 260)); AddToDataObject( pdo, @@ -450,7 +450,7 @@ internal sealed partial class TextureManager try { IStream* pfs; - SHCreateStreamOnFileW((char*)pPath, sharedRead, &pfs).ThrowOnError(); + SHCreateStreamOnFileW((ushort*)pPath, sharedRead, &pfs).ThrowOnError(); var stgm2 = new STGMEDIUM { diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 9f60aba6a..1ea0d9f2f 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -13,6 +13,7 @@ using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.Internal; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; +using Dalamud.Plugin; using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Serilog; @@ -150,6 +151,13 @@ public interface IUiBuilder /// public ImFontPtr FontMono { get; } + /// + /// Gets the game's active Direct3D device. + /// + // TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud. + [Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")] + SharpDX.Direct3D11.Device Device { get; } + /// Gets the game's active Direct3D device. /// Pointer to the instance of IUnknown that the game is using and should be containing an ID3D11Device, /// or 0 if it is not available yet. @@ -295,6 +303,8 @@ public sealed class UiBuilder : IDisposable, IUiBuilder private IFontHandle? monoFontHandle; private IFontHandle? iconFontFixedWidthHandle; + private SharpDX.Direct3D11.Device? sdxDevice; + /// /// Initializes a new instance of the class and registers it. /// You do not have to call this manually. @@ -484,6 +494,12 @@ public sealed class UiBuilder : IDisposable, IUiBuilder this.InterfaceManagerWithScene?.MonoFontHandle ?? throw new InvalidOperationException("Scene is not yet ready."))); + /// + // TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud. + [Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")] + public SharpDX.Direct3D11.Device Device => + this.sdxDevice ??= new(this.InterfaceManagerWithScene!.Backend!.DeviceHandle); + /// public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0; diff --git a/Dalamud/NativeMethods.json b/Dalamud/NativeMethods.json index 46fd3504f..ffb313dfc 100644 --- a/Dalamud/NativeMethods.json +++ b/Dalamud/NativeMethods.json @@ -1,5 +1,4 @@ { "$schema": "https://aka.ms/CsWin32.schema.json", - "useSafeHandles": false, "allowMarshaling": false } diff --git a/Dalamud/SafeMemory.cs b/Dalamud/SafeMemory.cs index ca0c8ff92..a8ac40a5d 100644 --- a/Dalamud/SafeMemory.cs +++ b/Dalamud/SafeMemory.cs @@ -1,8 +1,6 @@ using System.Runtime.InteropServices; using System.Text; -using Windows.Win32.Foundation; - namespace Dalamud; /// @@ -14,11 +12,11 @@ namespace Dalamud; /// public static class SafeMemory { - private static readonly HANDLE Handle; + private static readonly SafeHandle Handle; static SafeMemory() { - Handle = Windows.Win32.PInvoke.GetCurrentProcess(); + Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle(); } /// @@ -30,12 +28,6 @@ public static class SafeMemory /// Whether the read succeeded. 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]; fixed (byte* p = buffer) { @@ -62,9 +54,6 @@ public static class SafeMemory /// Whether the write succeeded. public static unsafe bool WriteBytes(IntPtr address, byte[] buffer) { - if (Handle.IsNull) - return false; - if (buffer.Length == 0) return true; diff --git a/Dalamud/Service/LoadingDialog.cs b/Dalamud/Service/LoadingDialog.cs index ea45d3bb2..424087743 100644 --- a/Dalamud/Service/LoadingDialog.cs +++ b/Dalamud/Service/LoadingDialog.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing; @@ -294,18 +294,18 @@ internal sealed class LoadingDialog ? null : Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe")); - fixed (char* pszEmpty = "-") - fixed (char* pszWindowTitle = "Dalamud") - fixed (char* pszDalamudBoot = "Dalamud.Boot.dll") - fixed (char* pszThemesManifestResourceName = "RT_MANIFEST_THEMES") - fixed (char* pszHide = Loc.Localize("LoadingDialogHide", "Hide")) - fixed (char* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs")) - fixed (char* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs")) + fixed (void* pszEmpty = "-") + fixed (void* pszWindowTitle = "Dalamud") + fixed (void* pszDalamudBoot = "Dalamud.Boot.dll") + fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES") + fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide")) + fixed (void* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs")) + fixed (void* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs")) { var taskDialogButton = new TASKDIALOG_BUTTON { nButtonID = IDOK, - pszButtonText = pszHide, + pszButtonText = (ushort*)pszHide, }; var taskDialogConfig = new TASKDIALOGCONFIG { @@ -318,8 +318,8 @@ internal sealed class LoadingDialog (int)TDF_CALLBACK_TIMER | (extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN), dwCommonButtons = 0, - pszWindowTitle = pszWindowTitle, - pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (char*)extractedIcon.Handle, + pszWindowTitle = (ushort*)pszWindowTitle, + pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (ushort*)extractedIcon.Handle, pszMainInstruction = null, pszContent = null, cButtons = 1, @@ -329,9 +329,9 @@ internal sealed class LoadingDialog pRadioButtons = null, nDefaultRadioButton = 0, pszVerificationText = null, - pszExpandedInformation = pszEmpty, - pszExpandedControlText = pszShowLatestLogs, - pszCollapsedControlText = pszHideLatestLogs, + pszExpandedInformation = (ushort*)pszEmpty, + pszExpandedControlText = (ushort*)pszShowLatestLogs, + pszCollapsedControlText = (ushort*)pszHideLatestLogs, pszFooterIcon = null, pszFooter = null, pfCallback = &HResultFuncBinder, @@ -348,8 +348,8 @@ internal sealed class LoadingDialog { cbSize = (uint)sizeof(ACTCTXW), dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID, - lpResourceName = pszThemesManifestResourceName, - hModule = GetModuleHandleW(pszDalamudBoot), + lpResourceName = (ushort*)pszThemesManifestResourceName, + hModule = GetModuleHandleW((ushort*)pszDalamudBoot), }; hActCtx = CreateActCtxW(&actctx); if (hActCtx == default) diff --git a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs index 69de1f871..e6c7bd920 100644 --- a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs +++ b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs @@ -11,12 +11,12 @@ public enum DalamudAssetPurpose Empty = 0, /// - /// The asset is a .png file, and can be purposed as a . + /// The asset is a .png file, and can be purposed as a . /// TextureFromPng = 10, - + /// - /// The asset is a raw texture, and can be purposed as a . + /// The asset is a raw texture, and can be purposed as a . /// TextureFromRaw = 1001, diff --git a/Dalamud/Utility/ClipboardFormats.cs b/Dalamud/Utility/ClipboardFormats.cs index b80e05dd3..07b6c00d6 100644 --- a/Dalamud/Utility/ClipboardFormats.cs +++ b/Dalamud/Utility/ClipboardFormats.cs @@ -30,8 +30,8 @@ internal static class ClipboardFormats private static unsafe uint ClipboardFormatFromName(ReadOnlySpan name) { uint cf; - fixed (char* p = name) - cf = RegisterClipboardFormatW(p); + fixed (void* p = name) + cf = RegisterClipboardFormatW((ushort*)p); if (cf != 0) return cf; throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ?? diff --git a/Dalamud/Utility/ErrorHandling.cs b/Dalamud/Utility/ErrorHandling.cs deleted file mode 100644 index 3c025a12e..000000000 --- a/Dalamud/Utility/ErrorHandling.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Dalamud.Utility; - -/// -/// Utilities for handling errors inside Dalamud. -/// -internal static partial class ErrorHandling -{ - /// - /// Crash the game at this point, and show the crash handler with the supplied context. - /// - /// The context to show in the crash handler. - public static void CrashWithContext(string context) - { - BootVehRaiseExternalEvent(context); - } - - [LibraryImport("Dalamud.Boot.dll", EntryPoint = "BootVehRaiseExternalEventW", StringMarshalling = StringMarshalling.Utf16)] - private static partial void BootVehRaiseExternalEvent(string info); -} diff --git a/Dalamud/Utility/FilesystemUtil.cs b/Dalamud/Utility/FilesystemUtil.cs index f1b62ee21..3b4298b37 100644 --- a/Dalamud/Utility/FilesystemUtil.cs +++ b/Dalamud/Utility/FilesystemUtil.cs @@ -1,8 +1,7 @@ -using System.ComponentModel; +using System.ComponentModel; using System.IO; using System.Text; -using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; namespace Dalamud.Utility; @@ -48,39 +47,30 @@ public static class FilesystemUtil // Open the temp file var tempPath = path + ".tmp"; - var tempFile = Windows.Win32.PInvoke.CreateFile( + using var tempFile = Windows.Win32.PInvoke.CreateFile( tempPath, (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), FILE_SHARE_MODE.FILE_SHARE_NONE, null, FILE_CREATION_DISPOSITION.CREATE_ALWAYS, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, - HANDLE.Null); + null); - if (tempFile.IsNull) + if (tempFile.IsInvalid) throw new Win32Exception(); // Write the data uint bytesWritten = 0; - fixed (byte* ptr = bytes) - { - if (!Windows.Win32.PInvoke.WriteFile(tempFile, ptr, (uint)bytes.Length, &bytesWritten, null)) - throw new Win32Exception(); - } + if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) + throw new Win32Exception(); if (bytesWritten != bytes.Length) - { - Windows.Win32.PInvoke.CloseHandle(tempFile); throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); - } if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) - { - Windows.Win32.PInvoke.CloseHandle(tempFile); throw new Win32Exception(); - } - Windows.Win32.PInvoke.CloseHandle(tempFile); + tempFile.Close(); if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) throw new Win32Exception(); diff --git a/Dalamud/Utility/TerraFxCom/ManagedIStream.cs b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs index eb1997daf..caec65da2 100644 --- a/Dalamud/Utility/TerraFxCom/ManagedIStream.cs +++ b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs @@ -57,60 +57,60 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable static ManagedIStream? ToManagedObject(void* pThis) => GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) => ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static uint AddRefStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0); - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static uint ReleaseStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0); - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) => ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) => ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int SeekStatic( IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) => ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) => ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int CopyToStatic( IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, ULARGE_INTEGER* pcbWritten) => ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int CommitStatic(IStream* pThis, uint grfCommitFlags) => ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) => ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int UnlockRegionStatic( IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) => ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) => ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_UNEXPECTED; - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + [UnmanagedCallersOnly] static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_UNEXPECTED; } diff --git a/Dalamud/Utility/TerraFxCom/TerraFxComInterfaceExtensions.cs b/Dalamud/Utility/TerraFxCom/TerraFxComInterfaceExtensions.cs index ec108403e..f9252839f 100644 --- a/Dalamud/Utility/TerraFxCom/TerraFxComInterfaceExtensions.cs +++ b/Dalamud/Utility/TerraFxCom/TerraFxComInterfaceExtensions.cs @@ -88,7 +88,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions fixed (char* pPath = path) { SHCreateStreamOnFileEx( - pPath, + (ushort*)pPath, grfMode, (uint)attributes, fCreate, @@ -115,7 +115,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions { fixed (char* pName = name) { - var option = new PROPBAG2 { pstrName = pName }; + var option = new PROPBAG2 { pstrName = (ushort*)pName }; return obj.Write(1, &option, &varValue); } } @@ -145,7 +145,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions try { fixed (char* pName = name) - return obj.SetMetadataByName(pName, &propVarValue); + return obj.SetMetadataByName((ushort*)pName, &propVarValue); } finally { @@ -165,7 +165,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name) { fixed (char* pName = name) - return obj.RemoveMetadataByName(pName); + return obj.RemoveMetadataByName((ushort*)pName); } [LibraryImport("propsys.dll")] diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index f50efcf0d..19610ef64 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -858,7 +858,7 @@ public static partial class Util var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2); var dropFilesSize = sizeof(DROPFILES); - var hGlobal = Win32_PInvoke.GlobalAlloc( + var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle( GLOBAL_ALLOC_FLAGS.GHND, // struct size + size of encoded strings + null terminator for each // string + two null terminators for end of list @@ -896,11 +896,12 @@ public static partial class Util { Win32_PInvoke.SetClipboardData( (uint)CLIPBOARD_FORMAT.CF_HDROP, - (Windows.Win32.Foundation.HANDLE)hGlobal.Value); + hGlobal); Win32_PInvoke.CloseClipboard(); return true; } + hGlobal.Dispose(); return false; } diff --git a/Dalamud/Utility/VectorExtensions.cs b/Dalamud/Utility/VectorExtensions.cs new file mode 100644 index 000000000..f617c8420 --- /dev/null +++ b/Dalamud/Utility/VectorExtensions.cs @@ -0,0 +1,51 @@ +using System.Numerics; + +namespace Dalamud.Utility; + +/// +/// Extension methods for System.Numerics.VectorN and SharpDX.VectorN. +/// +public static class VectorExtensions +{ + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y); + + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); + + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y); + + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); +} diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 3955bd983..ec7115ffd 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -119,7 +119,7 @@ std::wstring describe_module(const std::filesystem::path& path) { return std::format(L"", GetLastError()); UINT size = 0; - + std::wstring version = L"v?.?.?.?"; if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) { const auto& v = *static_cast(lpBuffer); @@ -176,7 +176,7 @@ const std::map& get_remote_modules() { std::vector buf(8192); for (size_t i = 0; i < 64; i++) { if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast(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; } else if (needed > std::span(buf).size_bytes()) { buf.resize(needed / sizeof(HMODULE) + 16); @@ -201,7 +201,7 @@ const std::map& get_remote_modules() { data[hModule] = nth64.OptionalHeader.SizeOfImage; } - + return data; }(); @@ -292,43 +292,35 @@ 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) { std::vector exRecs; - if (ex.ExceptionRecord) - { + if (ex.ExceptionRecord) { size_t rec_index = 0; size_t read; - + exRecs.emplace_back(); for (auto pRemoteExRec = ex.ExceptionRecord; - pRemoteExRec && rec_index < 64; - rec_index++) - { - exRecs.emplace_back(); - - if (!ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read) - || read < offsetof(EXCEPTION_RECORD, ExceptionInformation) - || read < static_cast(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs. - back().NumberParameters]) - reinterpret_cast(&exRecs.back()))) - { - exRecs.pop_back(); - break; - } + pRemoteExRec + && rec_index < 64 + && ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read) + && read >= offsetof(EXCEPTION_RECORD, ExceptionInformation) + && read >= static_cast(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs.back().NumberParameters]) - reinterpret_cast(&exRecs.back())); + 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"Flags: {:X}\n", exRecs.back().ExceptionFlags); log << std::format(L"Address: {:X}\n", reinterpret_cast(exRecs.back().ExceptionAddress)); - if (exRecs.back().NumberParameters) - { - log << L"Parameters: "; - for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) - { - if (i != 0) - log << L", "; - log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]); - } + if (!exRecs.back().NumberParameters) + continue; + log << L"Parameters: "; + for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) { + if (i != 0) + log << L", "; + log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]); } pRemoteExRec = exRecs.back().ExceptionRecord; + exRecs.emplace_back(); } + exRecs.pop_back(); } log << L"\nCall Stack\n{"; @@ -418,7 +410,7 @@ void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT& 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 - + std::wstring res; if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) { res.append(arg); @@ -512,7 +504,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s filePath.emplace(pFilePath); std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - + mz_zip_archive zipa{}; zipa.m_pIO_opaque = &fileStream; zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t { @@ -574,7 +566,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); if (hLogFile == INVALID_HANDLE_VALUE) throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring()))); - + std::unique_ptr hLogFileClose(hLogFile, &CloseHandle); LARGE_INTEGER size, baseOffset{}; @@ -703,7 +695,7 @@ int main() { // IFileSaveDialog only works on STA CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - + std::vector args; if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) { for (auto i = 0; i < argc; i++) @@ -831,14 +823,14 @@ int main() { hr = pOleWindow->GetWindow(&hwndProgressDialog); 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); SetForegroundWindow(hwndProgressDialog); } - + pOleWindow->Release(); } - + } else { std::cerr << "Failed to create progress window" << std::endl; @@ -860,14 +852,14 @@ int main() { https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp */ - + if (g_bSymbolsAvailable) { SymRefreshModuleList(g_hProcess); } else if(!assetDir.empty()) { auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring()); - + 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; @@ -878,12 +870,12 @@ int main() { g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true); std::cout << "Init symbols without PDB" << std::endl; } - + if (!g_bSymbolsAvailable) { 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); std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0'); @@ -938,23 +930,13 @@ int main() { } while (false); } - const bool is_external_event = exinfo.ExceptionRecord.ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT; - std::wostringstream log; - - 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; - } + 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 (shutup) log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl; - + if (dumpPath.empty()) log << L"Dump skipped" << std::endl; else if (dumpError.empty()) @@ -967,19 +949,9 @@ int main() { if (pProgressDialog) 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()); print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log); - - if (!is_external_event) - window_log_str = log.str(); - + const auto window_log_str = log.str(); print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log); std::wofstream(logPath) << log.str(); @@ -1014,8 +986,8 @@ int main() { config.pButtons = buttons; config.cButtons = ARRAYSIZE(buttons); config.nDefaultButton = IdButtonRestart; - config.pszExpandedControlText = L"Hide further information"; - config.pszCollapsedControlText = L"Further information for developers"; + config.pszExpandedControlText = L"Hide stack trace"; + config.pszCollapsedControlText = L"Stack trace for plugin developers"; config.pszExpandedInformation = window_log_str.c_str(); config.pszWindowTitle = L"Dalamud Crash Handler"; config.pRadioButtons = radios; @@ -1031,7 +1003,7 @@ int main() { R"aa(Help | Open log directory | Open log file)aa" ); #endif - + // Can't do this, xiv stops pumping messages here //config.hwndParent = FindWindowA("FFXIVGAME", NULL); @@ -1084,13 +1056,13 @@ int main() { return (*reinterpret_cast(dwRefData))(hwnd, uNotification, wParam, lParam); }; config.lpCallbackData = reinterpret_cast(&callback); - + if (pProgressDialog) { pProgressDialog->StopProgressDialog(); pProgressDialog->Release(); pProgressDialog = NULL; } - + const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); }; if (shutup) { diff --git a/Directory.Packages.props b/Directory.Packages.props index 58e355400..903a8ee88 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,8 +26,10 @@ - - + + + + diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 1a189f2c7..ba2b09a4d 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Nuke.Common; using Nuke.Common.Execution; using Nuke.Common.Git; @@ -127,7 +128,7 @@ public class DalamudBuild : NukeBuild if (IsCIBuild) { s = s - .SetProcessAdditionalArguments("/clp:NoSummary"); // Disable MSBuild summary on CI builds + .SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds } // We need to emit compiler generated files for the docs build, since docfx can't run generators directly // TODO: This fails every build after this because of redefinitions... @@ -237,6 +238,7 @@ public class DalamudBuild : NukeBuild .SetProject(InjectorProjectFile) .SetConfiguration(Configuration)); - ArtifactsDirectory.CreateOrCleanDirectory(); + FileSystemTasks.DeleteDirectory(ArtifactsDirectory); + Directory.CreateDirectory(ArtifactsDirectory); }); } diff --git a/build/build.csproj b/build/build.csproj index 7096c7f8a..1e1416d92 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -11,7 +11,7 @@ false - +