Compare commits

...

35 commits

Author SHA1 Message Date
github-actions[bot]
7102d22f59 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-07 14:53:23 +00:00
goaaats
652ff59672 build: 13.0.0.13
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Failing after 2s
2025-12-07 15:52:26 +01:00
goaaats
094483e5a0 List PRs in changelog generator 2025-12-07 15:52:13 +01:00
goaaats
c50237cf66 Add compatibility changes for SeString API breakage 2025-12-07 15:46:01 +01:00
goaaats
b35faf13b5 Show unhandled exceptions through VEH 2025-12-07 13:04:11 +01:00
goaaats
caa869d3ac Clarify exception and docs regarding off-thread drawing with SeStrings, again 2025-12-07 12:54:13 +01:00
goaaats
9fd59f736d Merge from master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 18:48:31 +01:00
goat
ab5ea34e68
ci: make deploying builds globally blocking, don't cancel in-progress
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
2025-12-06 18:46:06 +01:00
goat
501e30e31c
Merge pull request #2490 from goaaats/feat/catch_clr_errors
Catch CLR exceptions
2025-12-06 18:43:32 +01:00
goaaats
3d29157391 Revert "Add git status checks to workflow to see what's dirty"
This reverts commit a36e11574b.
2025-12-06 18:38:44 +01:00
goaaats
b2d9480f9f Submit nuke schema 2025-12-06 18:38:44 +01:00
goat
1ad1343cbc
Merge pull request #2488 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-06 18:36:40 +01:00
goat
61123ce573
Merge pull request #2485 from Haselnussbomber/update-cswin32
[API14] Update Microsoft.Windows.CsWin32
2025-12-06 18:35:41 +01:00
goat
9f565fafd8
Merge pull request #2489 from MidoriKami/Remove-Sigs
Remove AddonEventManagerAddressResolver
2025-12-06 18:33:42 +01:00
goat
88fc933e3f
Merge pull request #2491 from Haselnussbomber/drawstate-fontptr
[API14] Use ImFontPtr in SeStringDrawState
2025-12-06 18:33:09 +01:00
goaaats
e032840ac8 Clean up crash handler window log for external events 2025-12-06 18:32:03 +01:00
Haselnussbomber
1d1db04f04
Use ImFontPtr in SeStringDrawState 2025-12-06 16:09:42 +01:00
goaaats
446c7e3877 Some logging, cleanup 2025-12-06 15:25:04 +01:00
goaaats
e09c43b8de Fix bad exit condition when looping exception records 2025-12-06 15:07:46 +01:00
goaaats
9c2d2b7c1d Report CLR errors through DalamudCrashHandler/VEH by hooking ReportEventW 2025-12-06 15:07:09 +01:00
github-actions[bot]
2e5c560ed7 Update ClientStructs
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-06 12:48:34 +00:00
MidoriKami
3c7dbf9f81 Remove AddonEventManagerAddressResolver.cs 2025-12-05 16:59:17 -08:00
goat
a36e11574b
Add git status checks to workflow to see what's dirty
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 01:10:00 +01:00
Haselnussbomber
d94cacaac3
Disable SafeHandles 2025-12-05 19:10:31 +01:00
Haselnussbomber
7cf20fe102
Update Microsoft.Windows.CsWin32 2025-12-05 18:58:10 +01:00
goat
98a4c0d4fd
Merge pull request #2479 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-12-05 18:20:01 +01:00
goat
f85ef995e3
Merge pull request #2486 from Haselnussbomber/update-terrafx
[API14] Update TerraFX.Interop.Windows
2025-12-05 18:19:42 +01:00
goat
e7d4786a1f
Oops, wrong version 2025-12-05 18:18:57 +01:00
goat
4d949e4a07
Merge branch 'api14' into update-terrafx 2025-12-05 18:17:52 +01:00
goat
68ca60fa8c
Merge pull request #2484 from Haselnussbomber/sharpdx-removal
[API14] Remove SharpDX
2025-12-05 18:16:00 +01:00
goat
411067219e
Merge pull request #2487 from Haselnussbomber/update-nuke
[API14] Update Nuke
2025-12-05 18:14:22 +01:00
Haselnussbomber
fc983458fa
Update Nuke 2025-12-05 01:44:18 +01:00
Haselnussbomber
ddc3113244
Update TerraFX.Interop.Windows 2025-12-05 01:34:47 +01:00
Haselnussbomber
da7be64fdf
Remove SharpDX 2025-12-04 23:34:11 +01:00
Haselnussbomber
0112e17fdb
Replace internal SharpDX usage with TerraFX 2025-12-04 23:33:48 +01:00
46 changed files with 590 additions and 441 deletions

View file

@ -8,7 +8,8 @@ import re
import sys import sys
import json import json
import argparse import argparse
from typing import List, Tuple, Optional import os
from typing import List, Tuple, Optional, Dict, Any
def run_git_command(args: List[str]) -> str: def run_git_command(args: List[str]) -> str:
@ -30,14 +31,14 @@ def get_last_two_tags() -> Tuple[str, str]:
"""Get the latest two git tags.""" """Get the latest two git tags."""
tags = run_git_command(["tag", "--sort=-version:refname"]) tags = run_git_command(["tag", "--sort=-version:refname"])
tag_list = [t for t in tags.split("\n") if t] tag_list = [t for t in tags.split("\n") if t]
# Filter out old tags that start with 'v' (old versioning scheme) # Filter out old tags that start with 'v' (old versioning scheme)
tag_list = [t for t in tag_list if not t.startswith('v')] tag_list = [t for t in tag_list if not t.startswith('v')]
if len(tag_list) < 2: if len(tag_list) < 2:
print("Error: Need at least 2 tags in the repository", file=sys.stderr) print("Error: Need at least 2 tags in the repository", file=sys.stderr)
sys.exit(1) sys.exit(1)
return tag_list[0], tag_list[1] return tag_list[0], tag_list[1]
@ -55,58 +56,144 @@ def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]:
return None return None
def get_commits_between_tags(tag1: str, tag2: str) -> List[Tuple[str, str]]: def get_repo_info() -> Tuple[str, str]:
"""Get commits between two tags. Returns list of (message, author) tuples.""" """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."""
log_output = run_git_command([ log_output = run_git_command([
"log", "log",
f"{tag2}..{tag1}", f"{tag2}..{tag1}",
"--format=%s|%an|%h" "--format=%H"
]) ])
commits = [] commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()]
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 return commits
def filter_commits(commits: List[Tuple[str, str]], ignore_patterns: List[str]) -> List[Tuple[str, str]]: def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]:
"""Filter out commits matching any of the ignore patterns.""" """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."""
compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns] compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns]
filtered = [] filtered = []
for message, author, sha in commits: for pr in prs:
if not any(pattern.search(message) for pattern in compiled_patterns): if not any(pattern.search(pr["title"]) for pattern in compiled_patterns):
filtered.append((message, author, sha)) filtered.append(pr)
return filtered return filtered
def generate_changelog(version: str, prev_version: str, commits: List[Tuple[str, str]], def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]],
cs_commit_new: Optional[str], cs_commit_old: Optional[str]) -> str: cs_commit_new: Optional[str], cs_commit_old: Optional[str],
owner: str, repo: str) -> str:
"""Generate markdown changelog.""" """Generate markdown changelog."""
# Calculate statistics # Calculate statistics
commit_count = len(commits) pr_count = len(prs)
unique_authors = len(set(author for _, author, _ in commits)) unique_authors = len(set(pr["author"] for pr in prs))
changelog = f"# Dalamud Release v{version}\n\n" 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"We just released Dalamud v{version}, which should be available to users within a few minutes. "
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"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](<https://github.com/goatcorp/Dalamud/compare/{prev_version}...{version}>) to see all Dalamud changes.\n\n" changelog += f"[Click here](<https://github.com/{owner}/{repo}/compare/{prev_version}...{version}>) to see all Dalamud changes.\n\n"
if cs_commit_new and cs_commit_old and cs_commit_new != cs_commit_old: 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]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n" changelog += f"It ships with an updated **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
changelog += f"[Click here](<https://github.com/aers/FFXIVClientStructs/compare/{cs_commit_old}...{cs_commit_new}>) to see all CS changes.\n" changelog += f"[Click here](<https://github.com/aers/FFXIVClientStructs/compare/{cs_commit_old}...{cs_commit_new}>) to see all CS changes.\n"
elif cs_commit_new: elif cs_commit_new:
changelog += f"It ships with **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n" changelog += f"It ships with **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
changelog += "## Dalamud Changes\n\n" changelog += "## Dalamud Changes\n\n"
for message, author, sha in commits: for pr in prs:
changelog += f"* {message} (by **{author}** as [`{sha}`](<https://github.com/goatcorp/Dalamud/commit/{sha}>))\n" changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n"
return changelog return changelog
@ -117,9 +204,9 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None:
except ImportError: except ImportError:
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr) print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
sys.exit(1) sys.exit(1)
filename = f"changelog-v{version}.md" filename = f"changelog-v{version}.md"
# Prepare the payload # Prepare the payload
data = { data = {
"content": f"Dalamud v{version} has been released!", "content": f"Dalamud v{version} has been released!",
@ -130,13 +217,13 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None:
} }
] ]
} }
# Prepare the files # Prepare the files
files = { files = {
"payload_json": (None, json.dumps(data)), "payload_json": (None, json.dumps(data)),
"files[0]": (filename, content.encode('utf-8'), 'text/markdown') "files[0]": (filename, content.encode('utf-8'), 'text/markdown')
} }
try: try:
result = requests.post(webhook_url, files=files) result = requests.post(webhook_url, files=files)
result.raise_for_status() result.raise_for_status()
@ -158,54 +245,64 @@ def main():
required=True, required=True,
help="Discord webhook URL" 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( parser.add_argument(
"--ignore", "--ignore",
action="append", action="append",
default=[], default=[],
help="Regex patterns to ignore commits (can be specified multiple times)" help="Regex patterns to ignore PRs (can be specified multiple times)"
) )
parser.add_argument( parser.add_argument(
"--submodule-path", "--submodule-path",
default="lib/FFXIVClientStructs", default="lib/FFXIVClientStructs",
help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)" help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)"
) )
args = parser.parse_args() args = parser.parse_args()
# Get repository info
owner, repo = get_repo_info()
print(f"Repository: {owner}/{repo}")
# Get the last two tags # Get the last two tags
latest_tag, previous_tag = get_last_two_tags() latest_tag, previous_tag = get_last_two_tags()
print(f"Generating changelog between {previous_tag} and {latest_tag}") print(f"Generating changelog between {previous_tag} and {latest_tag}")
# Get submodule commits at both tags # Get submodule commits at both tags
cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag) cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag)
cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag) cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag)
if cs_commit_new: if cs_commit_new:
print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}") print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}")
if cs_commit_old: if cs_commit_old:
print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}") print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}")
# Get commits between tags # Get PRs between tags
commits = get_commits_between_tags(latest_tag, previous_tag) prs = get_prs_between_tags(latest_tag, previous_tag, owner, repo, args.github_token)
print(f"Found {len(commits)} commits") prs.reverse()
print(f"Found {len(prs)} PRs")
# Filter commits
filtered_commits = filter_commits(commits, args.ignore) # Filter PRs
print(f"After filtering: {len(filtered_commits)} commits") filtered_prs = filter_prs(prs, args.ignore)
print(f"After filtering: {len(filtered_prs)} PRs")
# Generate changelog # Generate changelog
changelog = generate_changelog(latest_tag, previous_tag, filtered_commits, changelog = generate_changelog(latest_tag, previous_tag, filtered_prs,
cs_commit_new, cs_commit_old) cs_commit_new, cs_commit_old, owner, repo)
print("\n" + "="*50) print("\n" + "="*50)
print("Generated Changelog:") print("Generated Changelog:")
print("="*50) print("="*50)
print(changelog) print(changelog)
print("="*50 + "\n") print("="*50 + "\n")
# Post to Discord # Post to Discord
post_to_discord(args.webhook_url, changelog, latest_tag) post_to_discord(args.webhook_url, changelog, latest_tag)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -6,41 +6,43 @@ on:
tags: tags:
- '*' - '*'
permissions: read-all
jobs: jobs:
generate-changelog: generate-changelog:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch all history and tags fetch-depth: 0 # Fetch all history and tags
submodules: true # Fetch submodules submodules: true # Fetch submodules
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.14' python-version: '3.14'
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install requests pip install requests
- name: Generate and post changelog - name: Generate and post changelog
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_TERMINAL_PROMPT: 0
run: | run: |
python .github/generate_changelog.py \ python .github/generate_changelog.py \
--webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \ --webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \
--ignore "^Merge" \ --ignore "Update ClientStructs" \
--ignore "^build:" \ --ignore "^build:"
--ignore "^docs:"
env:
GIT_TERMINAL_PROMPT: 0
- name: Upload changelog as artifact - name: Upload changelog as artifact
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: changelog name: changelog
path: changelog-*.md path: changelog-*.md
if-no-files-found: ignore if-no-files-found: ignore

View file

@ -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:

View file

@ -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"
}
]
}

View file

@ -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;

View file

@ -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))

View file

@ -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,16 @@ 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);
}
extern "C" __declspec(dllexport) void BootVehRaiseExternalEventW(LPCWSTR info)
{
const std::wstring info_wstr(info);
veh::raise_external_event(info_wstr);
}

View file

@ -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);
} }

View file

@ -6,7 +6,7 @@
<PropertyGroup Label="Feature"> <PropertyGroup Label="Feature">
<Description>XIV Launcher addon framework</Description> <Description>XIV Launcher addon framework</Description>
<DalamudVersion>13.0.0.12</DalamudVersion> <DalamudVersion>13.0.0.13</DalamudVersion>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion> <AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version> <Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion> <FileVersion>$(DalamudVersion)</FileVersion>
@ -73,8 +73,6 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MinSharp" /> <PackageReference Include="MinSharp" />
<PackageReference Include="SharpDX.Direct3D11" />
<PackageReference Include="SharpDX.Mathematics" />
<PackageReference Include="Newtonsoft.Json" /> <PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Serilog" /> <PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Async" /> <PackageReference Include="Serilog.Sinks.Async" />
@ -123,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>

View file

@ -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);
@ -292,7 +292,6 @@ public sealed class EntryPoint
} }
var pluginInfo = string.Empty; var pluginInfo = string.Empty;
var supportText = ", please visit us on Discord for more help";
try try
{ {
var pm = Service<PluginManager>.GetNullable(); var pm = Service<PluginManager>.GetNullable();
@ -300,9 +299,6 @@ public sealed class EntryPoint
if (plugin != null) if (plugin != null)
{ {
pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n"; pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
if (plugin.IsThirdParty)
supportText = string.Empty;
} }
} }
catch catch
@ -310,31 +306,18 @@ public sealed class EntryPoint
// ignored // ignored
} }
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);
if (result == MESSAGEBOX_RESULT.IDYES)
{
Log.Information("User chose to disable plugins on next launch...");
var config = Service<DalamudConfiguration>.Get();
config.PluginSafeMode = true;
config.ForceSave();
}
Log.CloseAndFlush(); Log.CloseAndFlush();
Environment.Exit(-1);
ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}");
break; break;
default: default:
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
Log.CloseAndFlush(); Log.CloseAndFlush();
Environment.Exit(-1);
break; break;
} }
Environment.Exit(-1);
} }
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args) private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)

View file

@ -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);
} }
} }

View file

@ -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
}
}

View file

@ -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>

View file

@ -64,9 +64,9 @@ public interface IObjectWithLocalizableName
var result = new Dictionary<string, string>((int)count); var result = new Dictionary<string, string>((int)count);
for (var i = 0u; i < count; i++) for (var i = 0u; i < count; i++)
{ {
fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError(); fn->GetLocaleName(i, buf, maxStrLen).ThrowOnError();
var key = new string(buf); var key = new string(buf);
fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError(); fn->GetString(i, buf, maxStrLen).ThrowOnError();
var value = new string(buf); var value = new string(buf);
result[key.ToLowerInvariant()] = value; result[key.ToLowerInvariant()] = value;
} }

View file

@ -133,8 +133,8 @@ public sealed class SystemFontFamilyId : IFontFamilyId
var familyIndex = 0u; var familyIndex = 0u;
BOOL exists = false; BOOL exists = false;
fixed (void* pName = this.EnglishName) fixed (char* pName = this.EnglishName)
sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError(); sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError();
if (!exists) if (!exists)
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found."); throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");

View file

@ -113,8 +113,8 @@ public sealed class SystemFontId : IFontId
var familyIndex = 0u; var familyIndex = 0u;
BOOL exists = false; BOOL exists = false;
fixed (void* name = this.Family.EnglishName) fixed (char* name = this.Family.EnglishName)
sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError(); sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError();
if (!exists) if (!exists)
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found."); 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(); flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
var path = stackalloc char[(int)pathSize + 1]; var path = stackalloc char[(int)pathSize + 1];
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError(); flocal.Get()->GetFilePathFromKey(refKey, refKeySize, path, pathSize + 1).ThrowOnError();
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex()); return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
} }

View file

@ -104,19 +104,19 @@ internal static unsafe class ReShadePeeler
fixed (byte* pfn5 = "glBegin"u8) fixed (byte* pfn5 = "glBegin"u8)
fixed (byte* pfn6 = "vkCreateDevice"u8) fixed (byte* pfn6 = "vkCreateDevice"u8)
{ {
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == null)
continue; continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0) if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == null)
continue; continue;
} }

View file

@ -672,7 +672,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND), hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
.GetFunctionPointerForDelegate(this.input.wndProcDelegate), .GetFunctionPointerForDelegate(this.input.wndProcDelegate),
lpszClassName = (ushort*)windowClassNamePtr, lpszClassName = windowClassNamePtr,
}; };
if (RegisterClassExW(&wcex) == 0) if (RegisterClassExW(&wcex) == 0)
@ -701,7 +701,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
fixed (char* windowClassNamePtr = WindowClassName) fixed (char* windowClassNamePtr = WindowClassName)
{ {
UnregisterClassW( UnregisterClassW(
(ushort*)windowClassNamePtr, windowClassNamePtr,
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module)); (HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
} }
@ -815,8 +815,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
{ {
data->Hwnd = CreateWindowExW( data->Hwnd = CreateWindowExW(
(uint)data->DwExStyle, (uint)data->DwExStyle,
(ushort*)windowClassNamePtr, windowClassNamePtr,
(ushort*)windowClassNamePtr, windowClassNamePtr,
(uint)data->DwStyle, (uint)data->DwStyle,
rect.left, rect.left,
rect.top, rect.top,
@ -1030,7 +1030,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
{ {
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData; var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title)) fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
SetWindowTextW(data->Hwnd, (ushort*)pwszTitle); SetWindowTextW(data->Hwnd, pwszTitle);
} }
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]

View file

@ -161,7 +161,9 @@ internal class SeStringRenderer : IServiceType
ImFont* font = null; ImFont* font = null;
if (drawParams.Font.HasValue) if (drawParams.Font.HasValue)
font = drawParams.Font.Value; font = drawParams.Font.Value;
if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null)
// API14: Remove commented out code
if (ThreadSafety.IsMainThread /* && drawParams.TargetDrawList is null */ && font is null)
font = ImGui.GetFont(); font = ImGui.GetFont();
if (font is null) if (font is null)
throw new ArgumentException("Specified font is empty."); throw new ArgumentException("Specified font is empty.");

View file

@ -14,8 +14,9 @@ public record struct SeStringDrawParams
/// <see cref="ImGui.GetWindowDrawList"/> (the default).</value> /// <see cref="ImGui.GetWindowDrawList"/> (the default).</value>
/// <remarks> /// <remarks>
/// If this value is set, <see cref="ImGui.Dummy"/> will not be called, and ImGui ID will be ignored. /// If this value is set, <see cref="ImGui.Dummy"/> will not be called, and ImGui ID will be ignored.
/// You <b>must</b> specify a valid draw list and a valid font via <see cref="Font"/> if you set this value, /// You <b>must</b> specify a valid draw list, a valid font via <see cref="Font"/> and <see cref="FontSize"/> if you set this value,
/// since the renderer will not be able to retrieve them from ImGui context. /// since the renderer will not be able to retrieve them from ImGui context.
/// Must be set when drawing off the main thread.
/// </remarks> /// </remarks>
public ImDrawListPtr? TargetDrawList { get; set; } public ImDrawListPtr? TargetDrawList { get; set; }
@ -29,11 +30,13 @@ public record struct SeStringDrawParams
/// <summary>Gets or sets the font to use.</summary> /// <summary>Gets or sets the font to use.</summary>
/// <value>Font to use, or <c>null</c> to use <see cref="ImGui.GetFont"/> (the default).</value> /// <value>Font to use, or <c>null</c> to use <see cref="ImGui.GetFont"/> (the default).</value>
/// <remarks>Must be set when specifying a target draw-list or drawing off the main thread.</remarks>
public ImFontPtr? Font { get; set; } public ImFontPtr? Font { get; set; }
/// <summary>Gets or sets the font size.</summary> /// <summary>Gets or sets the font size.</summary>
/// <value>Font size in pixels, or <c>0</c> to use the current ImGui font size <see cref="ImGui.GetFontSize"/>. /// <value>Font size in pixels, or <c>0</c> to use the current ImGui font size <see cref="ImGui.GetFontSize"/>.
/// </value> /// </value>
/// <remarks>Must be set when specifying a target draw-list or drawing off the main thread.</remarks>
public float? FontSize { get; set; } public float? FontSize { get; set; }
/// <summary>Gets or sets the line height ratio.</summary> /// <summary>Gets or sets the line height ratio.</summary>

View file

@ -64,8 +64,20 @@ public unsafe ref struct SeStringDrawState
{ {
this.drawList = ssdp.TargetDrawList.Value; this.drawList = ssdp.TargetDrawList.Value;
this.ScreenOffset = Vector2.Zero; this.ScreenOffset = Vector2.Zero;
this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
$"{nameof(ssdp.FontSize)} must be set to render outside the main thread."); // 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.WrapWidth = ssdp.WrapWidth ?? float.MaxValue; this.WrapWidth = ssdp.WrapWidth ?? float.MaxValue;
this.Color = ssdp.Color ?? uint.MaxValue; this.Color = ssdp.Color ?? uint.MaxValue;
this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread. this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread.
@ -76,7 +88,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 +118,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 +268,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 +325,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 +352,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 +387,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);
} }

View file

@ -667,6 +667,8 @@ internal class DalamudInterface : IInternalDisposableService
{ {
if (this.isImGuiDrawDevMenu) 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()) if (ImGui.BeginMainMenuBar())
{ {
var pluginManager = Service<PluginManager>.Get(); var pluginManager = Service<PluginManager>.Get();
@ -839,6 +841,11 @@ internal class DalamudInterface : IInternalDisposableService
ImGui.PopStyleVar(); ImGui.PopStyleVar();
} }
if (ImGui.MenuItem("Raise external event through boot"))
{
ErrorHandling.CrashWithContext("Tést");
}
ImGui.EndMenu(); ImGui.EndMenu();
} }

View file

@ -256,7 +256,7 @@ internal partial class InterfaceManager : IInternalDisposableService
var gwh = default(HWND); var gwh = default(HWND);
fixed (char* pClass = "FFXIVGAME") fixed (char* pClass = "FFXIVGAME")
{ {
while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default) while ((gwh = FindWindowExW(default, gwh, pClass, default)) != default)
{ {
uint pid; uint pid;
_ = GetWindowThreadProcessId(gwh, &pid); _ = GetWindowThreadProcessId(gwh, &pid);

View file

@ -63,11 +63,11 @@ internal sealed unsafe partial class ReShadeAddonInterface
return; return;
bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res) static bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
{ {
Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1]; Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1];
name8[Encoding.UTF8.GetBytes(name, name8)] = 0; name8[Encoding.UTF8.GetBytes(name, name8)] = 0;
*(nint*)res = GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0])); *(nint*)res = (nint)GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
return *(nint*)res != 0; return *(nint*)res != 0;
} }
} }
@ -174,7 +174,7 @@ internal sealed unsafe partial class ReShadeAddonInterface
CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT.CERT_NAME_ISSUER_FLAG, CERT.CERT_NAME_ISSUER_FLAG,
null, null,
(ushort*)Unsafe.AsPointer(ref issuerName[0]), (char*)Unsafe.AsPointer(ref issuerName[0]),
pcb); pcb);
if (pcb == 0) if (pcb == 0)
throw new Win32Exception("CertGetNameStringW(2)"); throw new Win32Exception("CertGetNameStringW(2)");

View file

@ -94,7 +94,7 @@ internal static unsafe class ReShadeUnwrapper
static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name) static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name)
{ {
fixed (byte* p = name) fixed (byte* p = name)
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0; return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != null;
} }
} }

View file

@ -216,7 +216,7 @@ internal partial class StaThreadService : IInternalDisposableService
lpfnWndProc = &MessageReceiverWndProcStatic, lpfnWndProc = &MessageReceiverWndProcStatic,
hInstance = hInstance, hInstance = hInstance,
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1), hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
lpszClassName = (ushort*)name, lpszClassName = name,
}; };
wndClassAtom = RegisterClassExW(&wndClass); wndClassAtom = RegisterClassExW(&wndClass);
@ -226,8 +226,8 @@ internal partial class StaThreadService : IInternalDisposableService
this.messageReceiverHwndTask.SetResult( this.messageReceiverHwndTask.SetResult(
CreateWindowExW( CreateWindowExW(
0, 0,
(ushort*)wndClassAtom, (char*)wndClassAtom,
(ushort*)name, name,
0, 0,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
@ -275,7 +275,7 @@ internal partial class StaThreadService : IInternalDisposableService
_ = OleFlushClipboard(); _ = OleFlushClipboard();
OleUninitialize(); OleUninitialize();
if (wndClassAtom != 0) if (wndClassAtom != 0)
UnregisterClassW((ushort*)wndClassAtom, hInstance); UnregisterClassW((char*)wndClassAtom, hInstance);
this.messageReceiverHwndTask.TrySetException(e); this.messageReceiverHwndTask.TrySetException(e);
} }
} }

View file

@ -15,7 +15,6 @@ using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Storage.Assets; using Dalamud.Storage.Assets;
using Dalamud.Utility; using Dalamud.Utility;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.ManagedFontAtlas.Internals; namespace Dalamud.Interface.ManagedFontAtlas.Internals;
@ -749,7 +748,7 @@ internal sealed partial class FontAtlasFactory
new( new(
width, width,
height, height,
(int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm), (int)(use4 ? DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM),
width * bpp), width * bpp),
buf, buf,
name); name);

View file

@ -44,12 +44,12 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo
private static unsafe string ReadStringUsing( private static unsafe string ReadStringUsing(
IWICBitmapCodecInfo* codecInfo, IWICBitmapCodecInfo* codecInfo,
delegate* unmanaged<IWICBitmapCodecInfo*, uint, ushort*, uint*, int> readFuncPtr) delegate* unmanaged[MemberFunction]<IWICBitmapCodecInfo*, uint, char*, uint*, int> readFuncPtr)
{ {
var cch = 0u; var cch = 0u;
_ = readFuncPtr(codecInfo, 0, null, &cch); _ = readFuncPtr(codecInfo, 0, null, &cch);
var buf = stackalloc char[(int)cch + 1]; var buf = stackalloc char[(int)cch + 1];
Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, (ushort*)buf, &cch)); Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch));
return new(buf, 0, (int)cch); return new(buf, 0, (int)cch);
} }
} }

View file

@ -219,14 +219,14 @@ internal sealed partial class TextureManager
return; return;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) => static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) =>
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint AddRefStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0); static uint AddRefStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint ReleaseStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0); static uint ReleaseStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0);
} }

View file

@ -133,7 +133,7 @@ internal sealed partial class TextureManager
}, },
}, },
}; };
namea.AsSpan().CopyTo(new(fgda.fgd.e0.cFileName, 260)); namea.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgda.fgd.e0.cFileName[0]), 260));
AddToDataObject( AddToDataObject(
pdo, pdo,
@ -157,7 +157,7 @@ internal sealed partial class TextureManager
}, },
}, },
}; };
preferredFileNameWithoutExtension.AsSpan().CopyTo(new(fgdw.fgd.e0.cFileName, 260)); preferredFileNameWithoutExtension.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgdw.fgd.e0.cFileName[0]), 260));
AddToDataObject( AddToDataObject(
pdo, pdo,
@ -450,7 +450,7 @@ internal sealed partial class TextureManager
try try
{ {
IStream* pfs; IStream* pfs;
SHCreateStreamOnFileW((ushort*)pPath, sharedRead, &pfs).ThrowOnError(); SHCreateStreamOnFileW((char*)pPath, sharedRead, &pfs).ThrowOnError();
var stgm2 = new STGMEDIUM var stgm2 = new STGMEDIUM
{ {

View file

@ -13,7 +13,6 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility; using Dalamud.Utility;
using Serilog; using Serilog;
@ -151,13 +150,6 @@ public interface IUiBuilder
/// </summary> /// </summary>
public ImFontPtr FontMono { get; } public ImFontPtr FontMono { get; }
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
// 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; }
/// <summary>Gets the game's active Direct3D device.</summary> /// <summary>Gets the game's active Direct3D device.</summary>
/// <value>Pointer to the instance of IUnknown that the game is using and should be containing an ID3D11Device, /// <value>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.</value> /// or 0 if it is not available yet.</value>
@ -303,8 +295,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
private IFontHandle? monoFontHandle; private IFontHandle? monoFontHandle;
private IFontHandle? iconFontFixedWidthHandle; private IFontHandle? iconFontFixedWidthHandle;
private SharpDX.Direct3D11.Device? sdxDevice;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it. /// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
/// You do not have to call this manually. /// You do not have to call this manually.
@ -494,12 +484,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
this.InterfaceManagerWithScene?.MonoFontHandle this.InterfaceManagerWithScene?.MonoFontHandle
?? throw new InvalidOperationException("Scene is not yet ready."))); ?? throw new InvalidOperationException("Scene is not yet ready.")));
/// <inheritdoc/>
// 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);
/// <inheritdoc/> /// <inheritdoc/>
public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0; public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0;

View file

@ -1,4 +1,5 @@
{ {
"$schema": "https://aka.ms/CsWin32.schema.json", "$schema": "https://aka.ms/CsWin32.schema.json",
"useSafeHandles": false,
"allowMarshaling": false "allowMarshaling": false
} }

View file

@ -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;

View file

@ -1,4 +1,4 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
@ -294,18 +294,18 @@ internal sealed class LoadingDialog
? null ? null
: Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe")); : Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe"));
fixed (void* pszEmpty = "-") fixed (char* pszEmpty = "-")
fixed (void* pszWindowTitle = "Dalamud") fixed (char* pszWindowTitle = "Dalamud")
fixed (void* pszDalamudBoot = "Dalamud.Boot.dll") fixed (char* pszDalamudBoot = "Dalamud.Boot.dll")
fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES") fixed (char* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide")) fixed (char* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
fixed (void* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs")) fixed (char* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
fixed (void* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs")) fixed (char* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
{ {
var taskDialogButton = new TASKDIALOG_BUTTON var taskDialogButton = new TASKDIALOG_BUTTON
{ {
nButtonID = IDOK, nButtonID = IDOK,
pszButtonText = (ushort*)pszHide, pszButtonText = pszHide,
}; };
var taskDialogConfig = new TASKDIALOGCONFIG var taskDialogConfig = new TASKDIALOGCONFIG
{ {
@ -318,8 +318,8 @@ internal sealed class LoadingDialog
(int)TDF_CALLBACK_TIMER | (int)TDF_CALLBACK_TIMER |
(extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN), (extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN),
dwCommonButtons = 0, dwCommonButtons = 0,
pszWindowTitle = (ushort*)pszWindowTitle, pszWindowTitle = pszWindowTitle,
pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (ushort*)extractedIcon.Handle, pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (char*)extractedIcon.Handle,
pszMainInstruction = null, pszMainInstruction = null,
pszContent = null, pszContent = null,
cButtons = 1, cButtons = 1,
@ -329,9 +329,9 @@ internal sealed class LoadingDialog
pRadioButtons = null, pRadioButtons = null,
nDefaultRadioButton = 0, nDefaultRadioButton = 0,
pszVerificationText = null, pszVerificationText = null,
pszExpandedInformation = (ushort*)pszEmpty, pszExpandedInformation = pszEmpty,
pszExpandedControlText = (ushort*)pszShowLatestLogs, pszExpandedControlText = pszShowLatestLogs,
pszCollapsedControlText = (ushort*)pszHideLatestLogs, pszCollapsedControlText = pszHideLatestLogs,
pszFooterIcon = null, pszFooterIcon = null,
pszFooter = null, pszFooter = null,
pfCallback = &HResultFuncBinder, pfCallback = &HResultFuncBinder,
@ -348,8 +348,8 @@ internal sealed class LoadingDialog
{ {
cbSize = (uint)sizeof(ACTCTXW), cbSize = (uint)sizeof(ACTCTXW),
dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID, dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID,
lpResourceName = (ushort*)pszThemesManifestResourceName, lpResourceName = pszThemesManifestResourceName,
hModule = GetModuleHandleW((ushort*)pszDalamudBoot), hModule = GetModuleHandleW(pszDalamudBoot),
}; };
hActCtx = CreateActCtxW(&actctx); hActCtx = CreateActCtxW(&actctx);
if (hActCtx == default) if (hActCtx == default)

View file

@ -11,12 +11,12 @@ public enum DalamudAssetPurpose
Empty = 0, Empty = 0,
/// <summary> /// <summary>
/// The asset is a .png file, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>. /// The asset is a .png file, and can be purposed as a <see cref="TerraFX.Interop.DirectX.ID3D11Texture2D"/>.
/// </summary> /// </summary>
TextureFromPng = 10, TextureFromPng = 10,
/// <summary> /// <summary>
/// The asset is a raw texture, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>. /// The asset is a raw texture, and can be purposed as a <see cref="TerraFX.Interop.DirectX.ID3D11Texture2D"/>.
/// </summary> /// </summary>
TextureFromRaw = 1001, TextureFromRaw = 1001,

View file

@ -30,8 +30,8 @@ internal static class ClipboardFormats
private static unsafe uint ClipboardFormatFromName(ReadOnlySpan<char> name) private static unsafe uint ClipboardFormatFromName(ReadOnlySpan<char> name)
{ {
uint cf; uint cf;
fixed (void* p = name) fixed (char* p = name)
cf = RegisterClipboardFormatW((ushort*)p); cf = RegisterClipboardFormatW(p);
if (cf != 0) if (cf != 0)
return cf; return cf;
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ?? throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ??

View file

@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Dalamud.Utility;
/// <summary>
/// Utilities for handling errors inside Dalamud.
/// </summary>
internal static partial class ErrorHandling
{
/// <summary>
/// Crash the game at this point, and show the crash handler with the supplied context.
/// </summary>
/// <param name="context">The context to show in the crash handler.</param>
public static void CrashWithContext(string context)
{
BootVehRaiseExternalEvent(context);
}
[LibraryImport("Dalamud.Boot.dll", EntryPoint = "BootVehRaiseExternalEventW", StringMarshalling = StringMarshalling.Utf16)]
private static partial void BootVehRaiseExternalEvent(string info);
}

View file

@ -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();

View file

@ -57,60 +57,60 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
static ManagedIStream? ToManagedObject(void* pThis) => static ManagedIStream? ToManagedObject(void* pThis) =>
GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream; GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) => static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) =>
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint AddRefStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0); static uint AddRefStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint ReleaseStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0); static uint ReleaseStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0);
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) => static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) =>
ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) => static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) =>
ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int SeekStatic( static int SeekStatic(
IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) => IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) =>
ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) => static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) =>
ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CopyToStatic( static int CopyToStatic(
IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
ULARGE_INTEGER* pcbWritten) => ULARGE_INTEGER* pcbWritten) =>
ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CommitStatic(IStream* pThis, uint grfCommitFlags) => static int CommitStatic(IStream* pThis, uint grfCommitFlags) =>
ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_UNEXPECTED; static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) => static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int UnlockRegionStatic( static int UnlockRegionStatic(
IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) => IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) => static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) =>
ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_UNEXPECTED; ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_UNEXPECTED; static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_UNEXPECTED;
} }

View file

@ -88,7 +88,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
fixed (char* pPath = path) fixed (char* pPath = path)
{ {
SHCreateStreamOnFileEx( SHCreateStreamOnFileEx(
(ushort*)pPath, pPath,
grfMode, grfMode,
(uint)attributes, (uint)attributes,
fCreate, fCreate,
@ -115,7 +115,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
{ {
fixed (char* pName = name) fixed (char* pName = name)
{ {
var option = new PROPBAG2 { pstrName = (ushort*)pName }; var option = new PROPBAG2 { pstrName = pName };
return obj.Write(1, &option, &varValue); return obj.Write(1, &option, &varValue);
} }
} }
@ -145,7 +145,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
try try
{ {
fixed (char* pName = name) fixed (char* pName = name)
return obj.SetMetadataByName((ushort*)pName, &propVarValue); return obj.SetMetadataByName(pName, &propVarValue);
} }
finally finally
{ {
@ -165,7 +165,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name) public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name)
{ {
fixed (char* pName = name) fixed (char* pName = name)
return obj.RemoveMetadataByName((ushort*)pName); return obj.RemoveMetadataByName(pName);
} }
[LibraryImport("propsys.dll")] [LibraryImport("propsys.dll")]

View file

@ -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;
} }

View file

@ -1,51 +0,0 @@
using System.Numerics;
namespace Dalamud.Utility;
/// <summary>
/// Extension methods for System.Numerics.VectorN and SharpDX.VectorN.
/// </summary>
public static class VectorExtensions
{
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y);
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z);
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W);
}

View file

@ -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();
@ -986,8 +1014,8 @@ int main() {
config.pButtons = buttons; config.pButtons = buttons;
config.cButtons = ARRAYSIZE(buttons); config.cButtons = ARRAYSIZE(buttons);
config.nDefaultButton = IdButtonRestart; config.nDefaultButton = IdButtonRestart;
config.pszExpandedControlText = L"Hide stack trace"; config.pszExpandedControlText = L"Hide further information";
config.pszCollapsedControlText = L"Stack trace for plugin developers"; config.pszCollapsedControlText = L"Further information for developers";
config.pszExpandedInformation = window_log_str.c_str(); config.pszExpandedInformation = window_log_str.c_str();
config.pszWindowTitle = L"Dalamud Crash Handler"; config.pszWindowTitle = L"Dalamud Crash Handler";
config.pRadioButtons = radios; config.pRadioButtons = radios;
@ -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) {

View file

@ -26,10 +26,8 @@
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116" /> <PackageVersion Include="sqlite-net-pcl" Version="1.8.116" />
<!-- DirectX / Win32 --> <!-- DirectX / Win32 -->
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.2" /> <PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0" /> <PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.259" />
<PackageVersion Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- Logging --> <!-- Logging -->
<PackageVersion Include="Serilog" Version="4.0.2" /> <PackageVersion Include="Serilog" Version="4.0.2" />

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Nuke.Common; using Nuke.Common;
using Nuke.Common.Execution; using Nuke.Common.Execution;
using Nuke.Common.Git; using Nuke.Common.Git;
@ -128,7 +127,7 @@ public class DalamudBuild : NukeBuild
if (IsCIBuild) if (IsCIBuild)
{ {
s = s s = s
.SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds .SetProcessAdditionalArguments("/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 // 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... // TODO: This fails every build after this because of redefinitions...
@ -238,7 +237,6 @@ public class DalamudBuild : NukeBuild
.SetProject(InjectorProjectFile) .SetProject(InjectorProjectFile)
.SetConfiguration(Configuration)); .SetConfiguration(Configuration));
FileSystemTasks.DeleteDirectory(ArtifactsDirectory); ArtifactsDirectory.CreateOrCleanDirectory();
Directory.CreateDirectory(ArtifactsDirectory);
}); });
} }

View file

@ -11,7 +11,7 @@
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.2.1" /> <PackageReference Include="Nuke.Common" Version="10.1.0" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0" /> <PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0" />
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" /> <PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" />
</ItemGroup> </ItemGroup>