mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-11 01:37:24 +01:00
Compare commits
35 commits
6f8e33a39c
...
7102d22f59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7102d22f59 | ||
|
|
652ff59672 | ||
|
|
094483e5a0 | ||
|
|
c50237cf66 | ||
|
|
b35faf13b5 | ||
|
|
caa869d3ac | ||
|
|
9fd59f736d | ||
|
|
ab5ea34e68 | ||
|
|
501e30e31c | ||
|
|
3d29157391 | ||
|
|
b2d9480f9f | ||
|
|
1ad1343cbc | ||
|
|
61123ce573 | ||
|
|
9f565fafd8 | ||
|
|
88fc933e3f | ||
|
|
e032840ac8 | ||
|
|
1d1db04f04 | ||
|
|
446c7e3877 | ||
|
|
e09c43b8de | ||
|
|
9c2d2b7c1d | ||
|
|
2e5c560ed7 | ||
|
|
3c7dbf9f81 | ||
|
|
a36e11574b | ||
|
|
d94cacaac3 | ||
|
|
7cf20fe102 | ||
|
|
98a4c0d4fd | ||
|
|
f85ef995e3 | ||
|
|
e7d4786a1f | ||
|
|
4d949e4a07 | ||
|
|
68ca60fa8c | ||
|
|
411067219e | ||
|
|
fc983458fa | ||
|
|
ddc3113244 | ||
|
|
da7be64fdf | ||
|
|
0112e17fdb |
46 changed files with 590 additions and 441 deletions
209
.github/generate_changelog.py
vendored
209
.github/generate_changelog.py
vendored
|
|
@ -8,7 +8,8 @@ import re
|
|||
import sys
|
||||
import json
|
||||
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:
|
||||
|
|
@ -30,14 +31,14 @@ def get_last_two_tags() -> Tuple[str, str]:
|
|||
"""Get the latest two git tags."""
|
||||
tags = run_git_command(["tag", "--sort=-version:refname"])
|
||||
tag_list = [t for t in tags.split("\n") if t]
|
||||
|
||||
|
||||
# Filter out old tags that start with 'v' (old versioning scheme)
|
||||
tag_list = [t for t in tag_list if not t.startswith('v')]
|
||||
|
||||
if len(tag_list) < 2:
|
||||
print("Error: Need at least 2 tags in the repository", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
return tag_list[0], tag_list[1]
|
||||
|
||||
|
||||
|
|
@ -55,58 +56,144 @@ def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def get_commits_between_tags(tag1: str, tag2: str) -> List[Tuple[str, str]]:
|
||||
"""Get commits between two tags. Returns list of (message, author) tuples."""
|
||||
def get_repo_info() -> Tuple[str, str]:
|
||||
"""Get repository owner and name from git remote."""
|
||||
try:
|
||||
remote_url = run_git_command(["config", "--get", "remote.origin.url"])
|
||||
|
||||
# Handle both HTTPS and SSH URLs
|
||||
# SSH: git@github.com:owner/repo.git
|
||||
# HTTPS: https://github.com/owner/repo.git
|
||||
match = re.search(r'github\.com[:/](.+?)/(.+?)(?:\.git)?$', remote_url)
|
||||
if match:
|
||||
owner = match.group(1)
|
||||
repo = match.group(2)
|
||||
return owner, repo
|
||||
else:
|
||||
print("Error: Could not parse GitHub repository from remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except:
|
||||
print("Error: Could not get git remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_commits_between_tags(tag1: str, tag2: str) -> List[str]:
|
||||
"""Get commit SHAs between two tags."""
|
||||
log_output = run_git_command([
|
||||
"log",
|
||||
f"{tag2}..{tag1}",
|
||||
"--format=%s|%an|%h"
|
||||
"--format=%H"
|
||||
])
|
||||
|
||||
commits = []
|
||||
for line in log_output.split("\n"):
|
||||
if "|" in line:
|
||||
message, author, sha = line.split("|", 2)
|
||||
commits.append((message.strip(), author.strip(), sha.strip()))
|
||||
|
||||
|
||||
commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()]
|
||||
return commits
|
||||
|
||||
|
||||
def filter_commits(commits: List[Tuple[str, str]], ignore_patterns: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Filter out commits matching any of the ignore patterns."""
|
||||
def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get PR information for a commit using GitHub API."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}/pulls"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
prs = response.json()
|
||||
|
||||
if prs and len(prs) > 0:
|
||||
# Return the first PR (most relevant one)
|
||||
pr = prs[0]
|
||||
return {
|
||||
"number": pr["number"],
|
||||
"title": pr["title"],
|
||||
"author": pr["user"]["login"],
|
||||
"url": pr["html_url"]
|
||||
}
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
# Commit might not be associated with a PR
|
||||
return None
|
||||
elif e.response.status_code == 403:
|
||||
print("Warning: GitHub API rate limit exceeded. Consider providing a token.", file=sys.stderr)
|
||||
return None
|
||||
else:
|
||||
print(f"Warning: Failed to fetch PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Warning: Error fetching PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_prs_between_tags(tag1: str, tag2: str, owner: str, repo: str, token: str) -> List[Dict[str, Any]]:
|
||||
"""Get PRs between two tags using GitHub API."""
|
||||
commits = get_commits_between_tags(tag1, tag2)
|
||||
print(f"Found {len(commits)} commits, fetching PR information...")
|
||||
|
||||
prs = []
|
||||
seen_pr_numbers = set()
|
||||
|
||||
for i, commit_sha in enumerate(commits, 1):
|
||||
if i % 10 == 0:
|
||||
print(f"Progress: {i}/{len(commits)} commits processed...")
|
||||
|
||||
pr_info = get_pr_for_commit(commit_sha, owner, repo, token)
|
||||
if pr_info and pr_info["number"] not in seen_pr_numbers:
|
||||
seen_pr_numbers.add(pr_info["number"])
|
||||
prs.append(pr_info)
|
||||
|
||||
return prs
|
||||
|
||||
|
||||
def filter_prs(prs: List[Dict[str, Any]], ignore_patterns: List[str]) -> List[Dict[str, Any]]:
|
||||
"""Filter out PRs matching any of the ignore patterns."""
|
||||
compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns]
|
||||
|
||||
|
||||
filtered = []
|
||||
for message, author, sha in commits:
|
||||
if not any(pattern.search(message) for pattern in compiled_patterns):
|
||||
filtered.append((message, author, sha))
|
||||
|
||||
for pr in prs:
|
||||
if not any(pattern.search(pr["title"]) for pattern in compiled_patterns):
|
||||
filtered.append(pr)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def generate_changelog(version: str, prev_version: str, commits: List[Tuple[str, str]],
|
||||
cs_commit_new: Optional[str], cs_commit_old: Optional[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],
|
||||
owner: str, repo: str) -> str:
|
||||
"""Generate markdown changelog."""
|
||||
# Calculate statistics
|
||||
commit_count = len(commits)
|
||||
unique_authors = len(set(author for _, author, _ in commits))
|
||||
|
||||
pr_count = len(prs)
|
||||
unique_authors = len(set(pr["author"] for pr in prs))
|
||||
|
||||
changelog = f"# Dalamud Release v{version}\n\n"
|
||||
changelog += f"We just released Dalamud v{version}, which should be available to users within a few minutes. "
|
||||
changelog += f"This release includes **{commit_count} commit{'s' if commit_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"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/{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:
|
||||
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"
|
||||
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 += "## Dalamud Changes\n\n"
|
||||
|
||||
for message, author, sha in commits:
|
||||
changelog += f"* {message} (by **{author}** as [`{sha}`](<https://github.com/goatcorp/Dalamud/commit/{sha}>))\n"
|
||||
|
||||
|
||||
for pr in prs:
|
||||
changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n"
|
||||
|
||||
return changelog
|
||||
|
||||
|
||||
|
|
@ -117,9 +204,9 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None:
|
|||
except ImportError:
|
||||
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
filename = f"changelog-v{version}.md"
|
||||
|
||||
|
||||
# Prepare the payload
|
||||
data = {
|
||||
"content": f"Dalamud v{version} has been released!",
|
||||
|
|
@ -130,13 +217,13 @@ def post_to_discord(webhook_url: str, content: str, version: str) -> None:
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Prepare the files
|
||||
files = {
|
||||
"payload_json": (None, json.dumps(data)),
|
||||
"files[0]": (filename, content.encode('utf-8'), 'text/markdown')
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
result = requests.post(webhook_url, files=files)
|
||||
result.raise_for_status()
|
||||
|
|
@ -158,54 +245,64 @@ def main():
|
|||
required=True,
|
||||
help="Discord webhook URL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--github-token",
|
||||
default=os.environ.get("GITHUB_TOKEN"),
|
||||
help="GitHub API token (or set GITHUB_TOKEN env var). Increases rate limit."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Regex patterns to ignore commits (can be specified multiple times)"
|
||||
help="Regex patterns to ignore PRs (can be specified multiple times)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--submodule-path",
|
||||
default="lib/FFXIVClientStructs",
|
||||
help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)"
|
||||
)
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# Get repository info
|
||||
owner, repo = get_repo_info()
|
||||
print(f"Repository: {owner}/{repo}")
|
||||
|
||||
# Get the last two tags
|
||||
latest_tag, previous_tag = get_last_two_tags()
|
||||
print(f"Generating changelog between {previous_tag} and {latest_tag}")
|
||||
|
||||
|
||||
# Get submodule commits at both tags
|
||||
cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag)
|
||||
cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag)
|
||||
|
||||
|
||||
if cs_commit_new:
|
||||
print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}")
|
||||
if cs_commit_old:
|
||||
print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}")
|
||||
|
||||
# Get commits between tags
|
||||
commits = get_commits_between_tags(latest_tag, previous_tag)
|
||||
print(f"Found {len(commits)} commits")
|
||||
|
||||
# Filter commits
|
||||
filtered_commits = filter_commits(commits, args.ignore)
|
||||
print(f"After filtering: {len(filtered_commits)} commits")
|
||||
|
||||
|
||||
# Get PRs between tags
|
||||
prs = get_prs_between_tags(latest_tag, previous_tag, owner, repo, args.github_token)
|
||||
prs.reverse()
|
||||
print(f"Found {len(prs)} PRs")
|
||||
|
||||
# Filter PRs
|
||||
filtered_prs = filter_prs(prs, args.ignore)
|
||||
print(f"After filtering: {len(filtered_prs)} PRs")
|
||||
|
||||
# Generate changelog
|
||||
changelog = generate_changelog(latest_tag, previous_tag, filtered_commits,
|
||||
cs_commit_new, cs_commit_old)
|
||||
|
||||
changelog = generate_changelog(latest_tag, previous_tag, filtered_prs,
|
||||
cs_commit_new, cs_commit_old, owner, repo)
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("Generated Changelog:")
|
||||
print("="*50)
|
||||
print(changelog)
|
||||
print("="*50 + "\n")
|
||||
|
||||
|
||||
# Post to Discord
|
||||
post_to_discord(args.webhook_url, changelog, latest_tag)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
|
|
|||
24
.github/workflows/generate-changelog.yml
vendored
24
.github/workflows/generate-changelog.yml
vendored
|
|
@ -6,41 +6,43 @@ on:
|
|||
tags:
|
||||
- '*'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
generate-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history and tags
|
||||
submodules: true # Fetch submodules
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
|
||||
- name: Generate and post changelog
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
run: |
|
||||
python .github/generate_changelog.py \
|
||||
--webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \
|
||||
--ignore "^Merge" \
|
||||
--ignore "^build:" \
|
||||
--ignore "^docs:"
|
||||
env:
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
|
||||
--ignore "Update ClientStructs" \
|
||||
--ignore "^build:"
|
||||
|
||||
- name: Upload changelog as artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: changelog
|
||||
path: changelog-*.md
|
||||
if-no-files-found: ignore
|
||||
if-no-files-found: ignore
|
||||
|
|
|
|||
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
|
|
@ -1,9 +1,10 @@
|
|||
name: Build Dalamud
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
# Globally blocking because of git pushes in deploy step
|
||||
concurrency:
|
||||
group: build_dalamud_${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
group: build_dalamud_${{ github.repository_owner }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
|||
|
|
@ -1,19 +1,57 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Build Schema",
|
||||
"$ref": "#/definitions/build",
|
||||
"definitions": {
|
||||
"build": {
|
||||
"type": "object",
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitbucket",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"Rider",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI",
|
||||
"VisualStudio",
|
||||
"VSCode"
|
||||
]
|
||||
},
|
||||
"ExecutableTarget": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"enum": [
|
||||
"Verbose",
|
||||
"Normal",
|
||||
"Minimal",
|
||||
"Quiet"
|
||||
]
|
||||
},
|
||||
"NukeBuild": {
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
]
|
||||
},
|
||||
"Continue": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to continue a previously failed build attempt"
|
||||
|
|
@ -23,29 +61,8 @@
|
|||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitbucket",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"Rider",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI",
|
||||
"VisualStudio",
|
||||
"VSCode"
|
||||
]
|
||||
},
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
"$ref": "#/definitions/Host"
|
||||
},
|
||||
"NoLogo": {
|
||||
"type": "boolean",
|
||||
|
|
@ -74,63 +91,46 @@
|
|||
"type": "array",
|
||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
}
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
},
|
||||
"Target": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
}
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||
"enum": [
|
||||
"Minimal",
|
||||
"Normal",
|
||||
"Quiet",
|
||||
"Verbose"
|
||||
]
|
||||
"$ref": "#/definitions/Verbosity"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
]
|
||||
},
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/NukeBuild"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
|
||||
|
||||
struct exception_info
|
||||
{
|
||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||
|
|
|
|||
|
|
@ -331,6 +331,51 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
logging::I("VEH was disabled manually");
|
||||
}
|
||||
|
||||
// ============================== CLR Reporting =================================== //
|
||||
|
||||
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
|
||||
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
|
||||
// Ideally all of this will go away once they get to it.
|
||||
static std::shared_ptr<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 ==================================== //
|
||||
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ HANDLE g_crashhandler_process = nullptr;
|
|||
HANDLE g_crashhandler_event = nullptr;
|
||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||
|
||||
wchar_t g_external_event_info[16384] = L"";
|
||||
|
||||
std::recursive_mutex g_exception_handler_mutex;
|
||||
|
||||
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);
|
||||
|
||||
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)";
|
||||
}
|
||||
|
|
@ -252,6 +258,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
|||
|
||||
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||
{
|
||||
// special case for CLR exceptions, always trigger crash handler
|
||||
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||
{
|
||||
return exception_handler(ex);
|
||||
}
|
||||
|
||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||
{
|
||||
// pass
|
||||
|
|
@ -435,3 +447,16 @@ bool veh::remove_handler()
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void veh::raise_external_event(const std::wstring& info)
|
||||
{
|
||||
const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1);
|
||||
wcsncpy_s(g_external_event_info, info.c_str(), info_size);
|
||||
RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport) void BootVehRaiseExternalEventW(LPCWSTR info)
|
||||
{
|
||||
const std::wstring info_wstr(info);
|
||||
veh::raise_external_event(info_wstr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ namespace veh
|
|||
{
|
||||
bool add_handler(bool doFullDump, const std::string& workingDirectory);
|
||||
bool remove_handler();
|
||||
void raise_external_event(const std::wstring& info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<PropertyGroup Label="Feature">
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<DalamudVersion>13.0.0.12</DalamudVersion>
|
||||
<DalamudVersion>13.0.0.13</DalamudVersion>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||
|
|
@ -73,8 +73,6 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MinSharp" />
|
||||
<PackageReference Include="SharpDX.Direct3D11" />
|
||||
<PackageReference Include="SharpDX.Mathematics" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
|
|
@ -123,6 +121,8 @@
|
|||
<Content Include="licenses.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
|
||||
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ public sealed class EntryPoint
|
|||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||
var searchPath = $".;{symbolPath}";
|
||||
|
||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||
|
||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
||||
|
|
@ -292,7 +292,6 @@ public sealed class EntryPoint
|
|||
}
|
||||
|
||||
var pluginInfo = string.Empty;
|
||||
var supportText = ", please visit us on Discord for more help";
|
||||
try
|
||||
{
|
||||
var pm = Service<PluginManager>.GetNullable();
|
||||
|
|
@ -300,9 +299,6 @@ public sealed class EntryPoint
|
|||
if (plugin != null)
|
||||
{
|
||||
pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
|
||||
|
||||
if (plugin.IsThirdParty)
|
||||
supportText = string.Empty;
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
@ -310,31 +306,18 @@ public sealed class EntryPoint
|
|||
// 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();
|
||||
Environment.Exit(-1);
|
||||
|
||||
ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}");
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Events;
|
||||
|
|
@ -32,25 +31,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
|
||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||
private readonly Hook<AtkUnitManager.Delegates.UpdateCursor> onUpdateCursor;
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||
|
||||
private AddonCursorType? cursorOverride;
|
||||
private AtkCursor.CursorType? cursorOverride;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonEventManager(TargetSigScanner sigScanner)
|
||||
private AddonEventManager()
|
||||
{
|
||||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||
|
||||
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.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||
|
|
@ -58,8 +53,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
this.onUpdateCursor.Enable();
|
||||
}
|
||||
|
||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
|
|
@ -117,7 +110,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
/// Force the game cursor to be the specified cursor.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// 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
|
||||
{
|
||||
|
|
@ -176,13 +169,14 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
|
||||
if (this.cursorOverride is not null && atkStage is not null)
|
||||
{
|
||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||
if (cursor != this.cursorOverride)
|
||||
ref var atkCursor = ref atkStage->AtkCursor;
|
||||
|
||||
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)
|
||||
|
|
@ -190,7 +184,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||
}
|
||||
|
||||
return this.onUpdateCursor!.Original(module);
|
||||
this.onUpdateCursor!.Original(thisPtr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.Addon.Events;
|
||||
|
||||
/// <summary>
|
||||
/// AddonEventManager memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkModule UpdateCursor method.
|
||||
/// </summary>
|
||||
public nint UpdateCursor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner scanner)
|
||||
{
|
||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
|
||||
}
|
||||
}
|
||||
|
|
@ -201,19 +201,19 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
|||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||
useMinHook = true;
|
||||
|
||||
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
||||
if (moduleHandle.IsInvalid)
|
||||
var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
||||
if (moduleHandle.IsNull)
|
||||
throw new Exception($"Could not get a handle to module {moduleName}");
|
||||
|
||||
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
||||
if (procAddress == IntPtr.Zero)
|
||||
var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
||||
if (procAddress.IsNull)
|
||||
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
||||
|
||||
procAddress = HookManager.FollowJmp(procAddress);
|
||||
var address = HookManager.FollowJmp(procAddress.Value);
|
||||
if (useMinHook)
|
||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||
return new MinHookHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||
else
|
||||
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||
return new ReloadedHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ public interface IObjectWithLocalizableName
|
|||
var result = new Dictionary<string, string>((int)count);
|
||||
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);
|
||||
fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
||||
fn->GetString(i, buf, maxStrLen).ThrowOnError();
|
||||
var value = new string(buf);
|
||||
result[key.ToLowerInvariant()] = value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,8 +133,8 @@ public sealed class SystemFontFamilyId : IFontFamilyId
|
|||
|
||||
var familyIndex = 0u;
|
||||
BOOL exists = false;
|
||||
fixed (void* pName = this.EnglishName)
|
||||
sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError();
|
||||
fixed (char* pName = this.EnglishName)
|
||||
sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError();
|
||||
if (!exists)
|
||||
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ public sealed class SystemFontId : IFontId
|
|||
|
||||
var familyIndex = 0u;
|
||||
BOOL exists = false;
|
||||
fixed (void* name = this.Family.EnglishName)
|
||||
sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError();
|
||||
fixed (char* name = this.Family.EnglishName)
|
||||
sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError();
|
||||
if (!exists)
|
||||
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found.");
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ public sealed class SystemFontId : IFontId
|
|||
flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
|
||||
|
||||
var path = stackalloc char[(int)pathSize + 1];
|
||||
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError();
|
||||
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, path, pathSize + 1).ThrowOnError();
|
||||
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,19 +104,19 @@ internal static unsafe class ReShadePeeler
|
|||
fixed (byte* pfn5 = "glBegin"u8)
|
||||
fixed (byte* pfn6 = "vkCreateDevice"u8)
|
||||
{
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == null)
|
||||
continue;
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0)
|
||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == null)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
|||
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
|
||||
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
|
||||
.GetFunctionPointerForDelegate(this.input.wndProcDelegate),
|
||||
lpszClassName = (ushort*)windowClassNamePtr,
|
||||
lpszClassName = windowClassNamePtr,
|
||||
};
|
||||
|
||||
if (RegisterClassExW(&wcex) == 0)
|
||||
|
|
@ -701,7 +701,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
|||
fixed (char* windowClassNamePtr = WindowClassName)
|
||||
{
|
||||
UnregisterClassW(
|
||||
(ushort*)windowClassNamePtr,
|
||||
windowClassNamePtr,
|
||||
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
|
||||
}
|
||||
|
||||
|
|
@ -815,8 +815,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
|||
{
|
||||
data->Hwnd = CreateWindowExW(
|
||||
(uint)data->DwExStyle,
|
||||
(ushort*)windowClassNamePtr,
|
||||
(ushort*)windowClassNamePtr,
|
||||
windowClassNamePtr,
|
||||
windowClassNamePtr,
|
||||
(uint)data->DwStyle,
|
||||
rect.left,
|
||||
rect.top,
|
||||
|
|
@ -1030,7 +1030,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
|||
{
|
||||
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
|
||||
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
|
||||
SetWindowTextW(data->Hwnd, (ushort*)pwszTitle);
|
||||
SetWindowTextW(data->Hwnd, pwszTitle);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
|
|
|
|||
|
|
@ -161,7 +161,9 @@ internal class SeStringRenderer : IServiceType
|
|||
ImFont* font = null;
|
||||
if (drawParams.Font.HasValue)
|
||||
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();
|
||||
if (font is null)
|
||||
throw new ArgumentException("Specified font is empty.");
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ public record struct SeStringDrawParams
|
|||
/// <see cref="ImGui.GetWindowDrawList"/> (the default).</value>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// Must be set when drawing off the main thread.
|
||||
/// </remarks>
|
||||
public ImDrawListPtr? TargetDrawList { get; set; }
|
||||
|
||||
|
|
@ -29,11 +30,13 @@ public record struct SeStringDrawParams
|
|||
|
||||
/// <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>
|
||||
/// <remarks>Must be set when specifying a target draw-list or drawing off the main thread.</remarks>
|
||||
public ImFontPtr? Font { get; set; }
|
||||
|
||||
/// <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>
|
||||
/// <remarks>Must be set when specifying a target draw-list or drawing off the main thread.</remarks>
|
||||
public float? FontSize { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the line height ratio.</summary>
|
||||
|
|
|
|||
|
|
@ -64,8 +64,20 @@ public unsafe ref struct SeStringDrawState
|
|||
{
|
||||
this.drawList = ssdp.TargetDrawList.Value;
|
||||
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.Color = ssdp.Color ?? uint.MaxValue;
|
||||
this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread.
|
||||
|
|
@ -76,7 +88,7 @@ public unsafe ref struct SeStringDrawState
|
|||
this.splitter = default;
|
||||
this.GetEntity = ssdp.GetEntity;
|
||||
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
||||
this.FontSizeScale = this.FontSize / this.Font->FontSize;
|
||||
this.FontSizeScale = this.FontSize / this.Font.FontSize;
|
||||
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||
this.Opacity = ssdp.EffectiveOpacity;
|
||||
|
|
@ -106,7 +118,7 @@ public unsafe ref struct SeStringDrawState
|
|||
public Vector2 ScreenOffset { get; }
|
||||
|
||||
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
||||
public ImFont* Font { get; }
|
||||
public ImFontPtr Font { get; }
|
||||
|
||||
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
||||
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>
|
||||
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(
|
||||
MathF.Round(g.X0 * this.FontSizeScale),
|
||||
MathF.Round(g.Y0 * this.FontSizeScale));
|
||||
|
|
@ -313,7 +325,7 @@ public unsafe ref struct SeStringDrawState
|
|||
|
||||
offset += this.ScreenOffset;
|
||||
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
||||
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale));
|
||||
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font.Ascent * this.FontSizeScale));
|
||||
|
||||
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
||||
this.DrawList.AddLine(
|
||||
|
|
@ -340,9 +352,9 @@ public unsafe ref struct SeStringDrawState
|
|||
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
||||
{
|
||||
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
||||
? this.Font->FindGlyph((ushort)rune.Value)
|
||||
: this.Font->FallbackGlyph;
|
||||
return ref *(ImGuiHelpers.ImFontGlyphReal*)p;
|
||||
? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value)
|
||||
: this.Font.FallbackGlyph;
|
||||
return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle;
|
||||
}
|
||||
|
||||
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
||||
|
|
@ -375,7 +387,7 @@ public unsafe ref struct SeStringDrawState
|
|||
return 0;
|
||||
|
||||
return MathF.Round(
|
||||
this.Font->GetDistanceAdjustmentForPair(
|
||||
this.Font.GetDistanceAdjustmentForPair(
|
||||
(ushort)left.Value,
|
||||
(ushort)right.Value) * this.FontSizeScale);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -667,6 +667,8 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
{
|
||||
if (this.isImGuiDrawDevMenu)
|
||||
{
|
||||
using var barColor = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0.060f, 0.060f, 0.060f, 0.773f));
|
||||
barColor.Push(ImGuiCol.MenuBarBg, Vector4.Zero);
|
||||
if (ImGui.BeginMainMenuBar())
|
||||
{
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
|
|
@ -839,6 +841,11 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Raise external event through boot"))
|
||||
{
|
||||
ErrorHandling.CrashWithContext("Tést");
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ internal partial class InterfaceManager : IInternalDisposableService
|
|||
var gwh = default(HWND);
|
||||
fixed (char* pClass = "FFXIVGAME")
|
||||
{
|
||||
while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default)
|
||||
while ((gwh = FindWindowExW(default, gwh, pClass, default)) != default)
|
||||
{
|
||||
uint pid;
|
||||
_ = GetWindowThreadProcessId(gwh, &pid);
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ internal sealed unsafe partial class ReShadeAddonInterface
|
|||
|
||||
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];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ internal sealed unsafe partial class ReShadeAddonInterface
|
|||
CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
||||
CERT.CERT_NAME_ISSUER_FLAG,
|
||||
null,
|
||||
(ushort*)Unsafe.AsPointer(ref issuerName[0]),
|
||||
(char*)Unsafe.AsPointer(ref issuerName[0]),
|
||||
pcb);
|
||||
if (pcb == 0)
|
||||
throw new Win32Exception("CertGetNameStringW(2)");
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ internal static unsafe class ReShadeUnwrapper
|
|||
static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name)
|
||||
{
|
||||
fixed (byte* p = name)
|
||||
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0;
|
||||
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ internal partial class StaThreadService : IInternalDisposableService
|
|||
lpfnWndProc = &MessageReceiverWndProcStatic,
|
||||
hInstance = hInstance,
|
||||
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
|
||||
lpszClassName = (ushort*)name,
|
||||
lpszClassName = name,
|
||||
};
|
||||
|
||||
wndClassAtom = RegisterClassExW(&wndClass);
|
||||
|
|
@ -226,8 +226,8 @@ internal partial class StaThreadService : IInternalDisposableService
|
|||
this.messageReceiverHwndTask.SetResult(
|
||||
CreateWindowExW(
|
||||
0,
|
||||
(ushort*)wndClassAtom,
|
||||
(ushort*)name,
|
||||
(char*)wndClassAtom,
|
||||
name,
|
||||
0,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
|
|
@ -275,7 +275,7 @@ internal partial class StaThreadService : IInternalDisposableService
|
|||
_ = OleFlushClipboard();
|
||||
OleUninitialize();
|
||||
if (wndClassAtom != 0)
|
||||
UnregisterClassW((ushort*)wndClassAtom, hInstance);
|
||||
UnregisterClassW((char*)wndClassAtom, hInstance);
|
||||
this.messageReceiverHwndTask.TrySetException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using Dalamud.Interface.Textures.TextureWraps;
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using SharpDX.DXGI;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
|
@ -749,7 +748,7 @@ internal sealed partial class FontAtlasFactory
|
|||
new(
|
||||
width,
|
||||
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),
|
||||
buf,
|
||||
name);
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo
|
|||
|
||||
private static unsafe string ReadStringUsing(
|
||||
IWICBitmapCodecInfo* codecInfo,
|
||||
delegate* unmanaged<IWICBitmapCodecInfo*, uint, ushort*, uint*, int> readFuncPtr)
|
||||
delegate* unmanaged[MemberFunction]<IWICBitmapCodecInfo*, uint, char*, uint*, int> readFuncPtr)
|
||||
{
|
||||
var cch = 0u;
|
||||
_ = readFuncPtr(codecInfo, 0, null, &cch);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,14 +219,14 @@ internal sealed partial class TextureManager
|
|||
|
||||
return;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) =>
|
||||
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
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(
|
||||
pdo,
|
||||
|
|
@ -450,7 +450,7 @@ internal sealed partial class TextureManager
|
|||
try
|
||||
{
|
||||
IStream* pfs;
|
||||
SHCreateStreamOnFileW((ushort*)pPath, sharedRead, &pfs).ThrowOnError();
|
||||
SHCreateStreamOnFileW((char*)pPath, sharedRead, &pfs).ThrowOnError();
|
||||
|
||||
var stgm2 = new STGMEDIUM
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using Dalamud.Interface.FontIdentifier;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
|
@ -151,13 +150,6 @@ public interface IUiBuilder
|
|||
/// </summary>
|
||||
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>
|
||||
/// <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>
|
||||
|
|
@ -303,8 +295,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
|||
private IFontHandle? monoFontHandle;
|
||||
private IFontHandle? iconFontFixedWidthHandle;
|
||||
|
||||
private SharpDX.Direct3D11.Device? sdxDevice;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
|
||||
/// You do not have to call this manually.
|
||||
|
|
@ -494,12 +484,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
|||
this.InterfaceManagerWithScene?.MonoFontHandle
|
||||
?? 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/>
|
||||
public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"useSafeHandles": false,
|
||||
"allowMarshaling": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -12,11 +14,11 @@ namespace Dalamud;
|
|||
/// </remarks>
|
||||
public static class SafeMemory
|
||||
{
|
||||
private static readonly SafeHandle Handle;
|
||||
private static readonly HANDLE Handle;
|
||||
|
||||
static SafeMemory()
|
||||
{
|
||||
Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
||||
Handle = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -28,6 +30,12 @@ public static class SafeMemory
|
|||
/// <returns>Whether the read succeeded.</returns>
|
||||
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
||||
{
|
||||
if (Handle.IsNull)
|
||||
{
|
||||
buffer = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = new byte[count <= 0 ? 0 : count];
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
|
|
@ -54,6 +62,9 @@ public static class SafeMemory
|
|||
/// <returns>Whether the write succeeded.</returns>
|
||||
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
||||
{
|
||||
if (Handle.IsNull)
|
||||
return false;
|
||||
|
||||
if (buffer.Length == 0)
|
||||
return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
|
|
@ -294,18 +294,18 @@ internal sealed class LoadingDialog
|
|||
? null
|
||||
: Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe"));
|
||||
|
||||
fixed (void* pszEmpty = "-")
|
||||
fixed (void* pszWindowTitle = "Dalamud")
|
||||
fixed (void* pszDalamudBoot = "Dalamud.Boot.dll")
|
||||
fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
|
||||
fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
|
||||
fixed (void* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
|
||||
fixed (void* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
|
||||
fixed (char* pszEmpty = "-")
|
||||
fixed (char* pszWindowTitle = "Dalamud")
|
||||
fixed (char* pszDalamudBoot = "Dalamud.Boot.dll")
|
||||
fixed (char* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
|
||||
fixed (char* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
|
||||
fixed (char* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
|
||||
fixed (char* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
|
||||
{
|
||||
var taskDialogButton = new TASKDIALOG_BUTTON
|
||||
{
|
||||
nButtonID = IDOK,
|
||||
pszButtonText = (ushort*)pszHide,
|
||||
pszButtonText = pszHide,
|
||||
};
|
||||
var taskDialogConfig = new TASKDIALOGCONFIG
|
||||
{
|
||||
|
|
@ -318,8 +318,8 @@ internal sealed class LoadingDialog
|
|||
(int)TDF_CALLBACK_TIMER |
|
||||
(extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN),
|
||||
dwCommonButtons = 0,
|
||||
pszWindowTitle = (ushort*)pszWindowTitle,
|
||||
pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (ushort*)extractedIcon.Handle,
|
||||
pszWindowTitle = pszWindowTitle,
|
||||
pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (char*)extractedIcon.Handle,
|
||||
pszMainInstruction = null,
|
||||
pszContent = null,
|
||||
cButtons = 1,
|
||||
|
|
@ -329,9 +329,9 @@ internal sealed class LoadingDialog
|
|||
pRadioButtons = null,
|
||||
nDefaultRadioButton = 0,
|
||||
pszVerificationText = null,
|
||||
pszExpandedInformation = (ushort*)pszEmpty,
|
||||
pszExpandedControlText = (ushort*)pszShowLatestLogs,
|
||||
pszCollapsedControlText = (ushort*)pszHideLatestLogs,
|
||||
pszExpandedInformation = pszEmpty,
|
||||
pszExpandedControlText = pszShowLatestLogs,
|
||||
pszCollapsedControlText = pszHideLatestLogs,
|
||||
pszFooterIcon = null,
|
||||
pszFooter = null,
|
||||
pfCallback = &HResultFuncBinder,
|
||||
|
|
@ -348,8 +348,8 @@ internal sealed class LoadingDialog
|
|||
{
|
||||
cbSize = (uint)sizeof(ACTCTXW),
|
||||
dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID,
|
||||
lpResourceName = (ushort*)pszThemesManifestResourceName,
|
||||
hModule = GetModuleHandleW((ushort*)pszDalamudBoot),
|
||||
lpResourceName = pszThemesManifestResourceName,
|
||||
hModule = GetModuleHandleW(pszDalamudBoot),
|
||||
};
|
||||
hActCtx = CreateActCtxW(&actctx);
|
||||
if (hActCtx == default)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ public enum DalamudAssetPurpose
|
|||
Empty = 0,
|
||||
|
||||
/// <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>
|
||||
TextureFromPng = 10,
|
||||
|
||||
|
||||
/// <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>
|
||||
TextureFromRaw = 1001,
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ internal static class ClipboardFormats
|
|||
private static unsafe uint ClipboardFormatFromName(ReadOnlySpan<char> name)
|
||||
{
|
||||
uint cf;
|
||||
fixed (void* p = name)
|
||||
cf = RegisterClipboardFormatW((ushort*)p);
|
||||
fixed (char* p = name)
|
||||
cf = RegisterClipboardFormatW(p);
|
||||
if (cf != 0)
|
||||
return cf;
|
||||
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ??
|
||||
|
|
|
|||
21
Dalamud/Utility/ErrorHandling.cs
Normal file
21
Dalamud/Utility/ErrorHandling.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
using System.ComponentModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Storage.FileSystem;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
|
@ -47,30 +48,39 @@ public static class FilesystemUtil
|
|||
// Open the temp file
|
||||
var tempPath = path + ".tmp";
|
||||
|
||||
using var tempFile = Windows.Win32.PInvoke.CreateFile(
|
||||
var tempFile = Windows.Win32.PInvoke.CreateFile(
|
||||
tempPath,
|
||||
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
|
||||
FILE_SHARE_MODE.FILE_SHARE_NONE,
|
||||
null,
|
||||
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
|
||||
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL,
|
||||
null);
|
||||
HANDLE.Null);
|
||||
|
||||
if (tempFile.IsInvalid)
|
||||
if (tempFile.IsNull)
|
||||
throw new Win32Exception();
|
||||
|
||||
// Write the data
|
||||
uint bytesWritten = 0;
|
||||
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
|
||||
throw new Win32Exception();
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
if (!Windows.Win32.PInvoke.WriteFile(tempFile, ptr, (uint)bytes.Length, &bytesWritten, null))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
if (bytesWritten != bytes.Length)
|
||||
{
|
||||
Windows.Win32.PInvoke.CloseHandle(tempFile);
|
||||
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
|
||||
}
|
||||
|
||||
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
|
||||
{
|
||||
Windows.Win32.PInvoke.CloseHandle(tempFile);
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
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))
|
||||
throw new Win32Exception();
|
||||
|
|
|
|||
|
|
@ -57,60 +57,60 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
|
|||
static ManagedIStream? ToManagedObject(void* pThis) =>
|
||||
GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) =>
|
||||
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
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);
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) =>
|
||||
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) =>
|
||||
ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int SeekStatic(
|
||||
IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) =>
|
||||
ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) =>
|
||||
ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int CopyToStatic(
|
||||
IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
|
||||
ULARGE_INTEGER* pcbWritten) =>
|
||||
ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int CommitStatic(IStream* pThis, uint grfCommitFlags) =>
|
||||
ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
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) =>
|
||||
ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int UnlockRegionStatic(
|
||||
IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
|
||||
ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
|
||||
static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) =>
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
|
|||
fixed (char* pPath = path)
|
||||
{
|
||||
SHCreateStreamOnFileEx(
|
||||
(ushort*)pPath,
|
||||
pPath,
|
||||
grfMode,
|
||||
(uint)attributes,
|
||||
fCreate,
|
||||
|
|
@ -115,7 +115,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
|
|||
{
|
||||
fixed (char* pName = name)
|
||||
{
|
||||
var option = new PROPBAG2 { pstrName = (ushort*)pName };
|
||||
var option = new PROPBAG2 { pstrName = pName };
|
||||
return obj.Write(1, &option, &varValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +145,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
|
|||
try
|
||||
{
|
||||
fixed (char* pName = name)
|
||||
return obj.SetMetadataByName((ushort*)pName, &propVarValue);
|
||||
return obj.SetMetadataByName(pName, &propVarValue);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -165,7 +165,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
|
|||
public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name)
|
||||
{
|
||||
fixed (char* pName = name)
|
||||
return obj.RemoveMetadataByName((ushort*)pName);
|
||||
return obj.RemoveMetadataByName(pName);
|
||||
}
|
||||
|
||||
[LibraryImport("propsys.dll")]
|
||||
|
|
|
|||
|
|
@ -858,7 +858,7 @@ public static partial class Util
|
|||
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
|
||||
|
||||
var dropFilesSize = sizeof(DROPFILES);
|
||||
var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle(
|
||||
var hGlobal = Win32_PInvoke.GlobalAlloc(
|
||||
GLOBAL_ALLOC_FLAGS.GHND,
|
||||
// struct size + size of encoded strings + null terminator for each
|
||||
// string + two null terminators for end of list
|
||||
|
|
@ -896,12 +896,11 @@ public static partial class Util
|
|||
{
|
||||
Win32_PInvoke.SetClipboardData(
|
||||
(uint)CLIPBOARD_FORMAT.CF_HDROP,
|
||||
hGlobal);
|
||||
(Windows.Win32.Foundation.HANDLE)hGlobal.Value);
|
||||
Win32_PInvoke.CloseClipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
hGlobal.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ std::wstring describe_module(const std::filesystem::path& path) {
|
|||
return std::format(L"<error: GetFileVersionInfoSizeW#2 returned {}>", GetLastError());
|
||||
|
||||
UINT size = 0;
|
||||
|
||||
|
||||
std::wstring version = L"v?.?.?.?";
|
||||
if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) {
|
||||
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);
|
||||
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)) {
|
||||
std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl;
|
||||
std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl;
|
||||
break;
|
||||
} else if (needed > std::span(buf).size_bytes()) {
|
||||
buf.resize(needed / sizeof(HMODULE) + 16);
|
||||
|
|
@ -201,7 +201,7 @@ const std::map<HMODULE, size_t>& get_remote_modules() {
|
|||
|
||||
data[hModule] = nth64.OptionalHeader.SizeOfImage;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
std::vector<EXCEPTION_RECORD> exRecs;
|
||||
if (ex.ExceptionRecord) {
|
||||
if (ex.ExceptionRecord)
|
||||
{
|
||||
size_t rec_index = 0;
|
||||
size_t read;
|
||||
exRecs.emplace_back();
|
||||
|
||||
for (auto pRemoteExRec = ex.ExceptionRecord;
|
||||
pRemoteExRec
|
||||
&& rec_index < 64
|
||||
&& 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()));
|
||||
rec_index++) {
|
||||
pRemoteExRec && rec_index < 64;
|
||||
rec_index++)
|
||||
{
|
||||
exRecs.emplace_back();
|
||||
|
||||
if (!ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read)
|
||||
|| read < offsetof(EXCEPTION_RECORD, ExceptionInformation)
|
||||
|| read < static_cast<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"Address: {:X}\n", exRecs.back().ExceptionCode);
|
||||
log << std::format(L"Flags: {:X}\n", exRecs.back().ExceptionFlags);
|
||||
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(exRecs.back().ExceptionAddress));
|
||||
if (!exRecs.back().NumberParameters)
|
||||
continue;
|
||||
log << L"Parameters: ";
|
||||
for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) {
|
||||
if (i != 0)
|
||||
log << L", ";
|
||||
log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]);
|
||||
if (exRecs.back().NumberParameters)
|
||||
{
|
||||
log << L"Parameters: ";
|
||||
for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
log << L", ";
|
||||
log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pRemoteExRec = exRecs.back().ExceptionRecord;
|
||||
exRecs.emplace_back();
|
||||
}
|
||||
exRecs.pop_back();
|
||||
}
|
||||
|
||||
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) {
|
||||
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||
|
||||
|
||||
std::wstring res;
|
||||
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
||||
res.append(arg);
|
||||
|
|
@ -504,7 +512,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s
|
|||
filePath.emplace(pFilePath);
|
||||
|
||||
std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc);
|
||||
|
||||
|
||||
mz_zip_archive zipa{};
|
||||
zipa.m_pIO_opaque = &fileStream;
|
||||
zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t {
|
||||
|
|
@ -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);
|
||||
if (hLogFile == INVALID_HANDLE_VALUE)
|
||||
throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring())));
|
||||
|
||||
|
||||
std::unique_ptr<void, decltype(&CloseHandle)> hLogFileClose(hLogFile, &CloseHandle);
|
||||
|
||||
LARGE_INTEGER size, baseOffset{};
|
||||
|
|
@ -695,7 +703,7 @@ int main() {
|
|||
|
||||
// IFileSaveDialog only works on STA
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
|
||||
std::vector<std::wstring> args;
|
||||
if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) {
|
||||
for (auto i = 0; i < argc; i++)
|
||||
|
|
@ -823,14 +831,14 @@ int main() {
|
|||
hr = pOleWindow->GetWindow(&hwndProgressDialog);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||
SetForegroundWindow(hwndProgressDialog);
|
||||
}
|
||||
|
||||
|
||||
pOleWindow->Release();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
std::cerr << "Failed to create progress window" << std::endl;
|
||||
|
|
@ -852,14 +860,14 @@ int main() {
|
|||
|
||||
https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp
|
||||
*/
|
||||
|
||||
|
||||
if (g_bSymbolsAvailable) {
|
||||
SymRefreshModuleList(g_hProcess);
|
||||
}
|
||||
else if(!assetDir.empty())
|
||||
{
|
||||
auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring());
|
||||
|
||||
|
||||
g_bSymbolsAvailable = SymInitializeW(g_hProcess, symbol_search_path.c_str(), true);
|
||||
std::wcout << std::format(L"Init symbols with PDB at {}", symbol_search_path) << std::endl;
|
||||
|
||||
|
|
@ -870,12 +878,12 @@ int main() {
|
|||
g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true);
|
||||
std::cout << "Init symbols without PDB" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
if (!g_bSymbolsAvailable) {
|
||||
std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl;
|
||||
}
|
||||
|
||||
if (pProgressDialog)
|
||||
if (pProgressDialog)
|
||||
pProgressDialog->SetLine(3, L"Reading troubleshooting data", FALSE, NULL);
|
||||
|
||||
std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0');
|
||||
|
|
@ -930,13 +938,23 @@ int main() {
|
|||
} while (false);
|
||||
}
|
||||
|
||||
const bool is_external_event = exinfo.ExceptionRecord.ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT;
|
||||
|
||||
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)
|
||||
log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl;
|
||||
|
||||
|
||||
if (dumpPath.empty())
|
||||
log << L"Dump skipped" << std::endl;
|
||||
else if (dumpError.empty())
|
||||
|
|
@ -949,9 +967,19 @@ int main() {
|
|||
if (pProgressDialog)
|
||||
pProgressDialog->SetLine(3, L"Refreshing Module List", FALSE, NULL);
|
||||
|
||||
std::wstring window_log_str;
|
||||
|
||||
// Cut the log here for external events, the rest is unreadable and doesn't matter since we can't get
|
||||
// symbols for mixed-mode stacks yet.
|
||||
if (is_external_event)
|
||||
window_log_str = log.str();
|
||||
|
||||
SymRefreshModuleList(GetCurrentProcess());
|
||||
print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log);
|
||||
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);
|
||||
std::wofstream(logPath) << log.str();
|
||||
|
||||
|
|
@ -986,8 +1014,8 @@ int main() {
|
|||
config.pButtons = buttons;
|
||||
config.cButtons = ARRAYSIZE(buttons);
|
||||
config.nDefaultButton = IdButtonRestart;
|
||||
config.pszExpandedControlText = L"Hide stack trace";
|
||||
config.pszCollapsedControlText = L"Stack trace for plugin developers";
|
||||
config.pszExpandedControlText = L"Hide further information";
|
||||
config.pszCollapsedControlText = L"Further information for developers";
|
||||
config.pszExpandedInformation = window_log_str.c_str();
|
||||
config.pszWindowTitle = L"Dalamud Crash Handler";
|
||||
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"
|
||||
);
|
||||
#endif
|
||||
|
||||
|
||||
// Can't do this, xiv stops pumping messages here
|
||||
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
|
||||
|
||||
|
|
@ -1056,13 +1084,13 @@ int main() {
|
|||
return (*reinterpret_cast<decltype(callback)*>(dwRefData))(hwnd, uNotification, wParam, lParam);
|
||||
};
|
||||
config.lpCallbackData = reinterpret_cast<LONG_PTR>(&callback);
|
||||
|
||||
|
||||
if (pProgressDialog) {
|
||||
pProgressDialog->StopProgressDialog();
|
||||
pProgressDialog->Release();
|
||||
pProgressDialog = NULL;
|
||||
}
|
||||
|
||||
|
||||
const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); };
|
||||
|
||||
if (shutup) {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,8 @@
|
|||
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
|
||||
<!-- DirectX / Win32 -->
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||
<PackageVersion Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.259" />
|
||||
|
||||
<!-- Logging -->
|
||||
<PackageVersion Include="Serilog" Version="4.0.2" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Nuke.Common;
|
||||
using Nuke.Common.Execution;
|
||||
using Nuke.Common.Git;
|
||||
|
|
@ -128,7 +127,7 @@ public class DalamudBuild : NukeBuild
|
|||
if (IsCIBuild)
|
||||
{
|
||||
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
|
||||
// TODO: This fails every build after this because of redefinitions...
|
||||
|
|
@ -238,7 +237,6 @@ public class DalamudBuild : NukeBuild
|
|||
.SetProject(InjectorProjectFile)
|
||||
.SetConfiguration(Configuration));
|
||||
|
||||
FileSystemTasks.DeleteDirectory(ArtifactsDirectory);
|
||||
Directory.CreateDirectory(ArtifactsDirectory);
|
||||
ArtifactsDirectory.CreateOrCleanDirectory();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<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 Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue