mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge branch 'api14' into AddonLifecycleRefactor
This commit is contained in:
commit
4d9751ea5f
79 changed files with 2359 additions and 1509 deletions
209
.github/generate_changelog.py
vendored
209
.github/generate_changelog.py
vendored
|
|
@ -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()
|
||||||
|
|
|
||||||
24
.github/workflows/generate-changelog.yml
vendored
24
.github/workflows/generate-changelog.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
|
|
@ -1,9 +1,10 @@
|
||||||
name: Build Dalamud
|
name: Build Dalamud
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
# Globally blocking because of git pushes in deploy step
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build_dalamud_${{ github.ref_name }}
|
group: build_dalamud_${{ github.repository_owner }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,57 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"title": "Build Schema",
|
|
||||||
"$ref": "#/definitions/build",
|
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"build": {
|
"Host": {
|
||||||
"type": "object",
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"AppVeyor",
|
||||||
|
"AzurePipelines",
|
||||||
|
"Bamboo",
|
||||||
|
"Bitbucket",
|
||||||
|
"Bitrise",
|
||||||
|
"GitHubActions",
|
||||||
|
"GitLab",
|
||||||
|
"Jenkins",
|
||||||
|
"Rider",
|
||||||
|
"SpaceAutomation",
|
||||||
|
"TeamCity",
|
||||||
|
"Terminal",
|
||||||
|
"TravisCI",
|
||||||
|
"VisualStudio",
|
||||||
|
"VSCode"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ExecutableTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"CI",
|
||||||
|
"Clean",
|
||||||
|
"Compile",
|
||||||
|
"CompileCImGui",
|
||||||
|
"CompileCImGuizmo",
|
||||||
|
"CompileCImPlot",
|
||||||
|
"CompileDalamud",
|
||||||
|
"CompileDalamudBoot",
|
||||||
|
"CompileDalamudCrashHandler",
|
||||||
|
"CompileImGuiNatives",
|
||||||
|
"CompileInjector",
|
||||||
|
"Restore",
|
||||||
|
"SetCILogging",
|
||||||
|
"Test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Verbosity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"enum": [
|
||||||
|
"Verbose",
|
||||||
|
"Normal",
|
||||||
|
"Minimal",
|
||||||
|
"Quiet"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NukeBuild": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"Configuration": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
|
||||||
"enum": [
|
|
||||||
"Debug",
|
|
||||||
"Release"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Continue": {
|
"Continue": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Indicates to continue a previously failed build attempt"
|
"description": "Indicates to continue a previously failed build attempt"
|
||||||
|
|
@ -23,29 +61,8 @@
|
||||||
"description": "Shows the help text for this build assembly"
|
"description": "Shows the help text for this build assembly"
|
||||||
},
|
},
|
||||||
"Host": {
|
"Host": {
|
||||||
"type": "string",
|
|
||||||
"description": "Host for execution. Default is 'automatic'",
|
"description": "Host for execution. Default is 'automatic'",
|
||||||
"enum": [
|
"$ref": "#/definitions/Host"
|
||||||
"AppVeyor",
|
|
||||||
"AzurePipelines",
|
|
||||||
"Bamboo",
|
|
||||||
"Bitbucket",
|
|
||||||
"Bitrise",
|
|
||||||
"GitHubActions",
|
|
||||||
"GitLab",
|
|
||||||
"Jenkins",
|
|
||||||
"Rider",
|
|
||||||
"SpaceAutomation",
|
|
||||||
"TeamCity",
|
|
||||||
"Terminal",
|
|
||||||
"TravisCI",
|
|
||||||
"VisualStudio",
|
|
||||||
"VSCode"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"IsDocsBuild": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether we are building for documentation - emits generated files"
|
|
||||||
},
|
},
|
||||||
"NoLogo": {
|
"NoLogo": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
@ -74,63 +91,46 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Solution": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to a solution file that is automatically loaded"
|
|
||||||
},
|
|
||||||
"Target": {
|
"Target": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Verbosity": {
|
"Verbosity": {
|
||||||
"type": "string",
|
|
||||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||||
"enum": [
|
"$ref": "#/definitions/Verbosity"
|
||||||
"Minimal",
|
|
||||||
"Normal",
|
|
||||||
"Quiet",
|
|
||||||
"Verbose"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"allOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"Configuration": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||||
|
"enum": [
|
||||||
|
"Debug",
|
||||||
|
"Release"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IsDocsBuild": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether we are building for documentation - emits generated files"
|
||||||
|
},
|
||||||
|
"Solution": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to a solution file that is automatically loaded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/NukeBuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
|
||||||
|
|
||||||
struct exception_info
|
struct exception_info
|
||||||
{
|
{
|
||||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,51 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
logging::I("VEH was disabled manually");
|
logging::I("VEH was disabled manually");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================== CLR Reporting =================================== //
|
||||||
|
|
||||||
|
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
|
||||||
|
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
|
||||||
|
// Ideally all of this will go away once they get to it.
|
||||||
|
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_report_event_hook;
|
||||||
|
s_report_event_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>(
|
||||||
|
"advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
|
||||||
|
s_report_event_hook->set_detour([hook = s_report_event_hook.get()](
|
||||||
|
HANDLE hEventLog,
|
||||||
|
WORD wType,
|
||||||
|
WORD wCategory,
|
||||||
|
DWORD dwEventID,
|
||||||
|
PSID lpUserSid,
|
||||||
|
WORD wNumStrings,
|
||||||
|
DWORD dwDataSize,
|
||||||
|
LPCWSTR* lpStrings,
|
||||||
|
LPVOID lpRawData)-> BOOL {
|
||||||
|
|
||||||
|
// Check for CLR Error Event IDs
|
||||||
|
// https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370
|
||||||
|
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
|
||||||
|
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
|
||||||
|
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
|
||||||
|
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
|
||||||
|
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
|
||||||
|
{
|
||||||
|
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wNumStrings == 0 || lpStrings == nullptr) {
|
||||||
|
logging::W("ReportEventW called with no strings.");
|
||||||
|
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
|
||||||
|
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||||
|
|
||||||
|
const std::wstring error_details(lpStrings[0]);
|
||||||
|
veh::raise_external_event(error_details);
|
||||||
|
|
||||||
|
return original_ret;
|
||||||
|
});
|
||||||
|
logging::I("ReportEventW hook installed.");
|
||||||
|
|
||||||
// ============================== Dalamud ==================================== //
|
// ============================== Dalamud ==================================== //
|
||||||
|
|
||||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ HANDLE g_crashhandler_process = nullptr;
|
||||||
HANDLE g_crashhandler_event = nullptr;
|
HANDLE g_crashhandler_event = nullptr;
|
||||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||||
|
|
||||||
|
wchar_t g_external_event_info[16384] = L"";
|
||||||
|
|
||||||
std::recursive_mutex g_exception_handler_mutex;
|
std::recursive_mutex g_exception_handler_mutex;
|
||||||
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
||||||
|
|
@ -191,7 +193,11 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
std::wstring stackTrace;
|
std::wstring stackTrace;
|
||||||
if (!g_clr)
|
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||||
|
{
|
||||||
|
stackTrace = std::wstring(g_external_event_info);
|
||||||
|
}
|
||||||
|
else if (!g_clr)
|
||||||
{
|
{
|
||||||
stackTrace = L"(no CLR stack trace available)";
|
stackTrace = L"(no CLR stack trace available)";
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +258,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
|
|
||||||
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
{
|
{
|
||||||
|
// special case for CLR exceptions, always trigger crash handler
|
||||||
|
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||||
|
{
|
||||||
|
return exception_handler(ex);
|
||||||
|
}
|
||||||
|
|
||||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||||
{
|
{
|
||||||
// pass
|
// pass
|
||||||
|
|
@ -435,3 +447,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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -487,6 +487,14 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 NotificationAnchorPosition { get; set; } = new(1f, 1f);
|
public Vector2 NotificationAnchorPosition { get; set; } = new(1f, 1f);
|
||||||
|
|
||||||
|
#pragma warning disable SA1600
|
||||||
|
#pragma warning disable SA1516
|
||||||
|
// XLCore/XoM compatibility until they move it out
|
||||||
|
public string? DalamudBetaKey { get; set; } = null;
|
||||||
|
public string? DalamudBetaKind { get; set; }
|
||||||
|
#pragma warning restore SA1516
|
||||||
|
#pragma warning restore SA1600
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load a configuration from the provided path.
|
/// Load a configuration from the provided path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -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.11</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>
|
||||||
|
|
@ -227,9 +227,4 @@
|
||||||
<!-- writes the attribute to the customAssemblyInfo file -->
|
<!-- writes the attribute to the customAssemblyInfo file -->
|
||||||
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
|
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- Copy plugin .targets folder into distrib -->
|
|
||||||
<Target Name="CopyPluginTargets" AfterTargets="Build">
|
|
||||||
<Copy SourceFiles="$(ProjectDir)\..\targets\Dalamud.Plugin.targets;$(ProjectDir)\..\targets\Dalamud.Plugin.Bootstrap.targets" DestinationFolder="$(OutDir)\targets" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ public enum DalamudAsset
|
||||||
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
[DalamudAssetPath("UIRes", "FontAwesome710FreeSolid.otf")]
|
||||||
FontAwesomeFreeSolid = 2003,
|
FontAwesomeFreeSolid = 2003,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,10 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
||||||
var tsInfo =
|
var tsInfo =
|
||||||
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
||||||
dalamud.StartInfo.TroubleshootingPackData);
|
dalamud.StartInfo.TroubleshootingPackData);
|
||||||
|
|
||||||
|
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
|
||||||
this.HasModifiedGameDataFiles =
|
this.HasModifiedGameDataFiles =
|
||||||
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
|
||||||
|
|
||||||
if (this.HasModifiedGameDataFiles)
|
if (this.HasModifiedGameDataFiles)
|
||||||
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
|
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Events;
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
@ -32,25 +31,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
|
|
||||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||||
|
|
||||||
private readonly AddonEventManagerAddressResolver address;
|
private readonly Hook<AtkUnitManager.Delegates.UpdateCursor> onUpdateCursor;
|
||||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||||
|
|
||||||
private AddonCursorType? cursorOverride;
|
private AtkCursor.CursorType? cursorOverride;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonEventManager(TargetSigScanner sigScanner)
|
private AddonEventManager()
|
||||||
{
|
{
|
||||||
this.address = new AddonEventManagerAddressResolver();
|
|
||||||
this.address.Setup(sigScanner);
|
|
||||||
|
|
||||||
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||||
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||||
|
|
||||||
this.cursorOverride = null;
|
this.cursorOverride = null;
|
||||||
|
|
||||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
this.onUpdateCursor = Hook<AtkUnitManager.Delegates.UpdateCursor>.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour);
|
||||||
|
|
||||||
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
||||||
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||||
|
|
@ -58,8 +53,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
this.onUpdateCursor.Enable();
|
this.onUpdateCursor.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
|
|
@ -117,7 +110,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
/// Force the game cursor to be the specified cursor.
|
/// Force the game cursor to be the specified cursor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cursor">Which cursor to use.</param>
|
/// <param name="cursor">Which cursor to use.</param>
|
||||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Un-forces the game cursor.
|
/// Un-forces the game cursor.
|
||||||
|
|
@ -168,7 +161,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
private void UpdateCursorDetour(AtkUnitManager* thisPtr)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -176,13 +169,14 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
|
|
||||||
if (this.cursorOverride is not null && atkStage is not null)
|
if (this.cursorOverride is not null && atkStage is not null)
|
||||||
{
|
{
|
||||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
ref var atkCursor = ref atkStage->AtkCursor;
|
||||||
if (cursor != this.cursorOverride)
|
|
||||||
|
if (atkCursor.Type != this.cursorOverride)
|
||||||
{
|
{
|
||||||
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nint.Zero;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -190,7 +184,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.onUpdateCursor!.Original(module);
|
this.onUpdateCursor!.Original(thisPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Events;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AddonEventManager memory address resolver.
|
|
||||||
/// </summary>
|
|
||||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the AtkModule UpdateCursor method.
|
|
||||||
/// </summary>
|
|
||||||
public nint UpdateCursor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for and setup any configured address pointers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
|
||||||
protected override void Setup64Bit(ISigScanner scanner)
|
|
||||||
{
|
|
||||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -77,7 +77,7 @@ internal unsafe class PlayerState : IServiceType, IPlayerState
|
||||||
public RowRef<ClassJob> ClassJob => this.IsLoaded ? LuminaUtils.CreateRef<ClassJob>(CSPlayerState.Instance()->CurrentClassJobId) : default;
|
public RowRef<ClassJob> ClassJob => this.IsLoaded ? LuminaUtils.CreateRef<ClassJob>(CSPlayerState.Instance()->CurrentClassJobId) : default;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public short Level => this.IsLoaded ? CSPlayerState.Instance()->CurrentLevel : default;
|
public short Level => this.IsLoaded && this.ClassJob.IsValid ? this.GetClassJobLevel(this.ClassJob.Value) : this.EffectiveLevel;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsLevelSynced => this.IsLoaded && CSPlayerState.Instance()->IsLevelSynced;
|
public bool IsLevelSynced => this.IsLoaded && CSPlayerState.Instance()->IsLevelSynced;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Globalization;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
using LSeString = Lumina.Text.SeString;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.Evaluator;
|
namespace Dalamud.Game.Text.Evaluator;
|
||||||
|
|
||||||
|
|
@ -71,9 +70,6 @@ public readonly struct SeStringParameter
|
||||||
|
|
||||||
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
||||||
|
|
||||||
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
|
|
||||||
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
|
|
||||||
|
|
||||||
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
|
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
|
||||||
|
|
||||||
public static implicit operator SeStringParameter(string value) => new(value);
|
public static implicit operator SeStringParameter(string value) => new(value);
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,8 @@ internal record struct NounParams()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int ColumnOffset => this.SheetName switch
|
public readonly int ColumnOffset => this.SheetName switch
|
||||||
{
|
{
|
||||||
// See "E8 ?? ?? ?? ?? 44 8B 6B 08"
|
// See "E8 ?? ?? ?? ?? 44 8B 66 ?? 8B E8"
|
||||||
nameof(LSheets.BeastTribe) => 10,
|
nameof(LSheets.BeastTribe) => 11,
|
||||||
nameof(LSheets.DeepDungeonItem) => 1,
|
nameof(LSheets.DeepDungeonItem) => 1,
|
||||||
nameof(LSheets.DeepDungeonEquipment) => 1,
|
nameof(LSheets.DeepDungeonEquipment) => 1,
|
||||||
nameof(LSheets.DeepDungeonMagicStone) => 1,
|
nameof(LSheets.DeepDungeonMagicStone) => 1,
|
||||||
|
|
|
||||||
|
|
@ -113,14 +113,6 @@ public class SeString
|
||||||
/// <returns>Equivalent SeString.</returns>
|
/// <returns>Equivalent SeString.</returns>
|
||||||
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="str">string to convert.</param>
|
|
||||||
/// <returns>Equivalent SeString.</returns>
|
|
||||||
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
|
|
||||||
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse a binary game message into an SeString.
|
/// Parse a binary game message into an SeString.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,11 +299,12 @@ internal sealed partial class Win32InputHandler
|
||||||
|
|
||||||
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
|
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
|
||||||
{
|
{
|
||||||
style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW);
|
style = (flags & ImGuiViewportFlags.NoDecoration) != 0 ? unchecked((int)WS.WS_POPUP) : WS.WS_OVERLAPPEDWINDOW;
|
||||||
exStyle =
|
exStyle = (flags & ImGuiViewportFlags.NoTaskBarIcon) != 0 ? WS.WS_EX_TOOLWINDOW : WS.WS_EX_APPWINDOW;
|
||||||
(int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW);
|
|
||||||
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
|
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
|
||||||
if (flags.HasFlag(ImGuiViewportFlags.TopMost))
|
if ((flags & ImGuiViewportFlags.TopMost) != 0)
|
||||||
exStyle |= WS.WS_EX_TOPMOST;
|
exStyle |= WS.WS_EX_TOPMOST;
|
||||||
|
if ((flags & ImGuiViewportFlags.NoInputs) != 0)
|
||||||
|
exStyle |= WS.WS_EX_TRANSPARENT | WS.WS_EX_LAYERED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Text;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -34,11 +35,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
private readonly HCURSOR[] cursors;
|
private readonly HCURSOR[] cursors;
|
||||||
|
|
||||||
private readonly WndProcDelegate wndProcDelegate;
|
private readonly WndProcDelegate wndProcDelegate;
|
||||||
private readonly bool[] imguiMouseIsDown;
|
|
||||||
private readonly nint platformNamePtr;
|
private readonly nint platformNamePtr;
|
||||||
|
|
||||||
private ViewportHandler viewportHandler;
|
private ViewportHandler viewportHandler;
|
||||||
|
|
||||||
|
private int mouseButtonsDown;
|
||||||
|
private bool mouseTracked;
|
||||||
private long lastTime;
|
private long lastTime;
|
||||||
|
|
||||||
private nint iniPathPtr;
|
private nint iniPathPtr;
|
||||||
|
|
@ -64,7 +66,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
|
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
|
||||||
ImGuiBackendFlags.HasSetMousePos |
|
ImGuiBackendFlags.HasSetMousePos |
|
||||||
ImGuiBackendFlags.RendererHasViewports |
|
ImGuiBackendFlags.RendererHasViewports |
|
||||||
ImGuiBackendFlags.PlatformHasViewports;
|
ImGuiBackendFlags.PlatformHasViewports |
|
||||||
|
ImGuiBackendFlags.HasMouseHoveredViewport;
|
||||||
|
|
||||||
this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#");
|
this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#");
|
||||||
io.Handle->BackendPlatformName = (byte*)this.platformNamePtr;
|
io.Handle->BackendPlatformName = (byte*)this.platformNamePtr;
|
||||||
|
|
@ -74,8 +77,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
||||||
this.viewportHandler = new(this);
|
this.viewportHandler = new(this);
|
||||||
|
|
||||||
this.imguiMouseIsDown = new bool[5];
|
|
||||||
|
|
||||||
this.cursors = new HCURSOR[9];
|
this.cursors = new HCURSOR[9];
|
||||||
this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW);
|
this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW);
|
||||||
this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM);
|
this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM);
|
||||||
|
|
@ -95,8 +96,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam);
|
private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam);
|
||||||
|
|
||||||
private delegate BOOL MonitorEnumProcDelegate(HMONITOR monitor, HDC hdc, RECT* rect, LPARAM lparam);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool UpdateCursor { get; set; } = true;
|
public bool UpdateCursor { get; set; } = true;
|
||||||
|
|
||||||
|
|
@ -155,6 +154,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
public void NewFrame(int targetWidth, int targetHeight)
|
public void NewFrame(int targetWidth, int targetHeight)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
|
var focusedWindow = GetForegroundWindow();
|
||||||
|
|
||||||
io.DisplaySize.X = targetWidth;
|
io.DisplaySize.X = targetWidth;
|
||||||
io.DisplaySize.Y = targetHeight;
|
io.DisplaySize.Y = targetHeight;
|
||||||
|
|
@ -168,9 +168,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
this.viewportHandler.UpdateMonitors();
|
this.viewportHandler.UpdateMonitors();
|
||||||
|
|
||||||
this.UpdateMousePos();
|
this.UpdateMouseData(focusedWindow);
|
||||||
|
|
||||||
this.ProcessKeyEventsWorkarounds();
|
this.ProcessKeyEventsWorkarounds(focusedWindow);
|
||||||
|
|
||||||
// TODO: need to figure out some way to unify all this
|
// TODO: need to figure out some way to unify all this
|
||||||
// The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues
|
// The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues
|
||||||
|
|
@ -224,6 +224,40 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
|
case WM.WM_MOUSEMOVE:
|
||||||
|
{
|
||||||
|
if (!this.mouseTracked)
|
||||||
|
{
|
||||||
|
var tme = new TRACKMOUSEEVENT
|
||||||
|
{
|
||||||
|
cbSize = (uint)sizeof(TRACKMOUSEEVENT),
|
||||||
|
dwFlags = TME.TME_LEAVE,
|
||||||
|
hwndTrack = hWndCurrent,
|
||||||
|
};
|
||||||
|
this.mouseTracked = TrackMouseEvent(&tme);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mousePos = new POINT(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
ClientToScreen(hWndCurrent, &mousePos);
|
||||||
|
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM.WM_MOUSELEAVE:
|
||||||
|
{
|
||||||
|
this.mouseTracked = false;
|
||||||
|
var mouseScreenPos = new POINT(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
ClientToScreen(hWndCurrent, &mouseScreenPos);
|
||||||
|
if (this.ViewportFromPoint(mouseScreenPos).IsNull)
|
||||||
|
{
|
||||||
|
var fltMax = ImGuiNative.GETFLTMAX();
|
||||||
|
io.AddMousePosEvent(-fltMax, -fltMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM.WM_LBUTTONDOWN:
|
case WM.WM_LBUTTONDOWN:
|
||||||
case WM.WM_LBUTTONDBLCLK:
|
case WM.WM_LBUTTONDBLCLK:
|
||||||
case WM.WM_RBUTTONDOWN:
|
case WM.WM_RBUTTONDOWN:
|
||||||
|
|
@ -236,11 +270,10 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero)
|
if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero)
|
||||||
SetCapture(hWndCurrent);
|
SetCapture(hWndCurrent);
|
||||||
|
this.mouseButtonsDown |= 1 << button;
|
||||||
io.MouseDown[button] = true;
|
io.AddMouseButtonEvent(button, true);
|
||||||
this.imguiMouseIsDown[button] = true;
|
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,13 +289,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_XBUTTONUP:
|
case WM.WM_XBUTTONUP:
|
||||||
{
|
{
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse && this.imguiMouseIsDown[button])
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
this.mouseButtonsDown &= ~(1 << button);
|
||||||
|
if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent)
|
||||||
ReleaseCapture();
|
ReleaseCapture();
|
||||||
|
io.AddMouseButtonEvent(button, false);
|
||||||
io.MouseDown[button] = false;
|
|
||||||
this.imguiMouseIsDown[button] = false;
|
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +304,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_MOUSEWHEEL:
|
case WM.WM_MOUSEWHEEL:
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
|
io.AddMouseWheelEvent(0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +312,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_MOUSEHWHEEL:
|
case WM.WM_MOUSEHWHEEL:
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
|
io.AddMouseWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,68 +406,86 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
this.viewportHandler.UpdateMonitors();
|
this.viewportHandler.UpdateMonitors();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
|
case WM.WM_SETFOCUS when hWndCurrent == this.hWnd:
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
io.AddFocusEvent(true);
|
||||||
ReleaseCapture();
|
break;
|
||||||
|
|
||||||
ImGui.GetIO().WantCaptureMouse = false;
|
case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
|
||||||
ImGui.ClearWindowFocus();
|
io.AddFocusEvent(false);
|
||||||
|
// if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
||||||
|
// ReleaseCapture();
|
||||||
|
//
|
||||||
|
// ImGui.GetIO().WantCaptureMouse = false;
|
||||||
|
// ImGui.ClearWindowFocus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMousePos()
|
private void UpdateMouseData(HWND focusedWindow)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
var pt = default(POINT);
|
|
||||||
|
|
||||||
// Depending on if Viewports are enabled, we have to change how we process
|
var mouseScreenPos = default(POINT);
|
||||||
// the cursor position. If viewports are enabled, we pass the absolute cursor
|
var hasMouseScreenPos = GetCursorPos(&mouseScreenPos) != 0;
|
||||||
// position to ImGui. Otherwise, we use the old method of passing client-local
|
|
||||||
// mouse position to ImGui.
|
var isAppFocused =
|
||||||
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
focusedWindow != default
|
||||||
|
&& (focusedWindow == this.hWnd
|
||||||
|
|| IsChild(focusedWindow, this.hWnd)
|
||||||
|
|| !ImGui.FindViewportByPlatformHandle(focusedWindow).IsNull);
|
||||||
|
|
||||||
|
if (isAppFocused)
|
||||||
{
|
{
|
||||||
|
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
|
||||||
|
// When multi-viewports are enabled, all Dear ImGui positions are same as OS positions.
|
||||||
if (io.WantSetMousePos)
|
if (io.WantSetMousePos)
|
||||||
{
|
{
|
||||||
SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y);
|
var pos = new POINT((int)io.MousePos.X, (int)io.MousePos.Y);
|
||||||
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
ClientToScreen(this.hWnd, &pos);
|
||||||
|
SetCursorPos(pos.x, pos.y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (GetCursorPos(&pt))
|
// (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured)
|
||||||
{
|
if (!io.WantSetMousePos && !this.mouseTracked && hasMouseScreenPos)
|
||||||
io.MousePos.X = pt.x;
|
{
|
||||||
io.MousePos.Y = pt.y;
|
// Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
|
||||||
}
|
// (This is the position you can get with ::GetCursorPos() + ::ScreenToClient() or WM_MOUSEMOVE.)
|
||||||
else
|
// Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
|
||||||
{
|
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
|
||||||
io.MousePos.X = float.MinValue;
|
var mousePos = mouseScreenPos;
|
||||||
io.MousePos.Y = float.MinValue;
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
|
||||||
}
|
ClientToScreen(focusedWindow, &mousePos);
|
||||||
|
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
|
||||||
|
// If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic.
|
||||||
|
// - [X] Win32 backend correctly ignore viewports with the _NoInputs flag (here using ::WindowFromPoint with WM_NCHITTEST + HTTRANSPARENT in WndProc does that)
|
||||||
|
// Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window
|
||||||
|
// for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported
|
||||||
|
// by the backend, and use its flawed heuristic to guess the viewport behind.
|
||||||
|
// - [X] Win32 backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target).
|
||||||
|
if (hasMouseScreenPos)
|
||||||
|
{
|
||||||
|
var viewport = this.ViewportFromPoint(mouseScreenPos);
|
||||||
|
io.AddMouseViewportEvent(!viewport.IsNull ? viewport.ID : 0u);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (io.WantSetMousePos)
|
io.AddMouseViewportEvent(0);
|
||||||
{
|
|
||||||
pt.x = (int)io.MousePos.X;
|
|
||||||
pt.y = (int)io.MousePos.Y;
|
|
||||||
ClientToScreen(this.hWnd, &pt);
|
|
||||||
SetCursorPos(pt.x, pt.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetCursorPos(&pt) && ScreenToClient(this.hWnd, &pt))
|
|
||||||
{
|
|
||||||
io.MousePos.X = pt.x;
|
|
||||||
io.MousePos.Y = pt.y;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
io.MousePos.X = float.MinValue;
|
|
||||||
io.MousePos.Y = float.MinValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImGuiViewportPtr ViewportFromPoint(POINT mouseScreenPos)
|
||||||
|
{
|
||||||
|
var hoveredHwnd = WindowFromPoint(mouseScreenPos);
|
||||||
|
return hoveredHwnd != default ? ImGui.FindViewportByPlatformHandle(hoveredHwnd) : default;
|
||||||
|
}
|
||||||
|
|
||||||
private bool UpdateMouseCursor()
|
private bool UpdateMouseCursor()
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
|
|
@ -451,7 +501,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessKeyEventsWorkarounds()
|
private void ProcessKeyEventsWorkarounds(HWND focusedWindow)
|
||||||
{
|
{
|
||||||
// Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
|
// Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
|
||||||
if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT))
|
if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT))
|
||||||
|
|
@ -480,7 +530,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
{
|
{
|
||||||
// See: https://github.com/goatcorp/ImGuiScene/pull/13
|
// See: https://github.com/goatcorp/ImGuiScene/pull/13
|
||||||
// > GetForegroundWindow from winuser.h is a surprisingly expensive function.
|
// > GetForegroundWindow from winuser.h is a surprisingly expensive function.
|
||||||
var isForeground = GetForegroundWindow() == this.hWnd;
|
var isForeground = focusedWindow == this.hWnd;
|
||||||
for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
|
for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
|
||||||
{
|
{
|
||||||
// Skip raising modifier keys if the game is focused.
|
// Skip raising modifier keys if the game is focused.
|
||||||
|
|
@ -622,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)
|
||||||
|
|
@ -646,19 +696,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var pio = ImGui.GetPlatformIO();
|
var pio = ImGui.GetPlatformIO();
|
||||||
|
ImGui.GetPlatformIO().Handle->Monitors.Free();
|
||||||
if (ImGui.GetPlatformIO().Handle->Monitors.Data != null)
|
|
||||||
{
|
|
||||||
// We allocated the platform monitor data in OnUpdateMonitors ourselves,
|
|
||||||
// so we have to free it ourselves to ImGui doesn't try to, or else it will crash
|
|
||||||
Marshal.FreeHGlobal(new IntPtr(ImGui.GetPlatformIO().Handle->Monitors.Data));
|
|
||||||
ImGui.GetPlatformIO().Handle->Monitors = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -693,59 +736,50 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
// Here we use a manual ImVector overload, free the existing monitor data,
|
// Here we use a manual ImVector overload, free the existing monitor data,
|
||||||
// and allocate our own, as we are responsible for telling ImGui about monitors
|
// and allocate our own, as we are responsible for telling ImGui about monitors
|
||||||
var pio = ImGui.GetPlatformIO();
|
var pio = ImGui.GetPlatformIO();
|
||||||
var numMonitors = GetSystemMetrics(SM.SM_CMONITORS);
|
pio.Handle->Monitors.Resize(0);
|
||||||
var data = Marshal.AllocHGlobal(Marshal.SizeOf<ImGuiPlatformMonitor>() * numMonitors);
|
|
||||||
if (pio.Handle->Monitors.Data != null)
|
|
||||||
Marshal.FreeHGlobal(new IntPtr(pio.Handle->Monitors.Data));
|
|
||||||
pio.Handle->Monitors = new(numMonitors, numMonitors, (ImGuiPlatformMonitor*)data.ToPointer());
|
|
||||||
|
|
||||||
// ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
|
EnumDisplayMonitors(default, null, &EnumDisplayMonitorsCallback, default);
|
||||||
// Marshal.FreeHGlobal(platformIO.Handle->Monitors.Data);
|
|
||||||
// int numMonitors = GetSystemMetrics(SystemMetric.SM_CMONITORS);
|
|
||||||
// nint data = Marshal.AllocHGlobal(Marshal.SizeOf<ImGuiPlatformMonitor>() * numMonitors);
|
|
||||||
// platformIO.Handle->Monitors = new ImVector(numMonitors, numMonitors, data);
|
|
||||||
|
|
||||||
var monitorIndex = -1;
|
|
||||||
var enumfn = new MonitorEnumProcDelegate(
|
|
||||||
(hMonitor, _, _, _) =>
|
|
||||||
{
|
|
||||||
monitorIndex++;
|
|
||||||
var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) };
|
|
||||||
if (!GetMonitorInfoW(hMonitor, &info))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var monitorLt = new Vector2(info.rcMonitor.left, info.rcMonitor.top);
|
|
||||||
var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom);
|
|
||||||
var workLt = new Vector2(info.rcWork.left, info.rcWork.top);
|
|
||||||
var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom);
|
|
||||||
// Give ImGui the info for this display
|
|
||||||
|
|
||||||
ref var imMonitor = ref ImGui.GetPlatformIO().Monitors.Ref(monitorIndex);
|
|
||||||
imMonitor.MainPos = monitorLt;
|
|
||||||
imMonitor.MainSize = monitorRb - monitorLt;
|
|
||||||
imMonitor.WorkPos = workLt;
|
|
||||||
imMonitor.WorkSize = workRb - workLt;
|
|
||||||
imMonitor.DpiScale = 1f;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
EnumDisplayMonitors(
|
|
||||||
default,
|
|
||||||
null,
|
|
||||||
(delegate* unmanaged<HMONITOR, HDC, RECT*, LPARAM, BOOL>)Marshal.GetFunctionPointerForDelegate(enumfn),
|
|
||||||
default);
|
|
||||||
|
|
||||||
Log.Information("Monitors set up!");
|
Log.Information("Monitors set up!");
|
||||||
for (var i = 0; i < numMonitors; i++)
|
foreach (ref var monitor in pio.Handle->Monitors)
|
||||||
{
|
{
|
||||||
var monitor = pio.Handle->Monitors[i];
|
|
||||||
Log.Information(
|
Log.Information(
|
||||||
"Monitor {Index}: {MainPos} {MainSize} {WorkPos} {WorkSize}",
|
"Monitor: {MainPos} {MainSize} {WorkPos} {WorkSize}",
|
||||||
i,
|
|
||||||
monitor.MainPos,
|
monitor.MainPos,
|
||||||
monitor.MainSize,
|
monitor.MainSize,
|
||||||
monitor.WorkPos,
|
monitor.WorkPos,
|
||||||
monitor.WorkSize);
|
monitor.WorkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static BOOL EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM lParam)
|
||||||
|
{
|
||||||
|
var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) };
|
||||||
|
if (!GetMonitorInfoW(hMonitor, &info))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var monitorLt = new Vector2(info.rcMonitor.left, info.rcMonitor.top);
|
||||||
|
var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom);
|
||||||
|
var workLt = new Vector2(info.rcWork.left, info.rcWork.top);
|
||||||
|
var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom);
|
||||||
|
|
||||||
|
// Give ImGui the info for this display
|
||||||
|
var imMonitor = new ImGuiPlatformMonitor
|
||||||
|
{
|
||||||
|
MainPos = monitorLt,
|
||||||
|
MainSize = monitorRb - monitorLt,
|
||||||
|
WorkPos = workLt,
|
||||||
|
WorkSize = workRb - workLt,
|
||||||
|
DpiScale = 1f,
|
||||||
|
};
|
||||||
|
if ((info.dwFlags & MONITORINFOF_PRIMARY) != 0)
|
||||||
|
ImGui.GetPlatformIO().Monitors.PushFront(imMonitor);
|
||||||
|
else
|
||||||
|
ImGui.GetPlatformIO().Monitors.PushBack(imMonitor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||||
|
|
@ -781,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,
|
||||||
|
|
@ -794,6 +828,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data->Hwnd == 0)
|
||||||
|
Util.Fatal($"CreateWindowExW failed: {GetLastError()}", "ImGui Viewport error");
|
||||||
|
|
||||||
data->HwndOwned = true;
|
data->HwndOwned = true;
|
||||||
viewport.PlatformRequestResize = false;
|
viewport.PlatformRequestResize = false;
|
||||||
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;
|
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;
|
||||||
|
|
@ -993,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)])]
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,6 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
/// <summary>Color stacks to use while evaluating a SeString.</summary>
|
/// <summary>Color stacks to use while evaluating a SeString.</summary>
|
||||||
internal sealed class SeStringColorStackSet
|
internal sealed class SeStringColorStackSet
|
||||||
{
|
{
|
||||||
/// <summary>Parsed <see cref="UIColor"/>, containing colors to use with <see cref="MacroCode.ColorType"/> and
|
|
||||||
/// <see cref="MacroCode.EdgeColorType"/>.</summary>
|
|
||||||
private readonly uint[,] colorTypes;
|
|
||||||
|
|
||||||
/// <summary>Foreground color stack while evaluating a SeString for rendering.</summary>
|
/// <summary>Foreground color stack while evaluating a SeString for rendering.</summary>
|
||||||
/// <remarks>Touched only from the main thread.</remarks>
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
private readonly List<uint> colorStack = [];
|
private readonly List<uint> colorStack = [];
|
||||||
|
|
@ -39,30 +35,38 @@ internal sealed class SeStringColorStackSet
|
||||||
foreach (var row in uiColor)
|
foreach (var row in uiColor)
|
||||||
maxId = (int)Math.Max(row.RowId, maxId);
|
maxId = (int)Math.Max(row.RowId, maxId);
|
||||||
|
|
||||||
this.colorTypes = new uint[maxId + 1, 4];
|
this.ColorTypes = new uint[maxId + 1, 4];
|
||||||
foreach (var row in uiColor)
|
foreach (var row in uiColor)
|
||||||
{
|
{
|
||||||
// Contains ABGR.
|
// Contains ABGR.
|
||||||
this.colorTypes[row.RowId, 0] = row.Dark;
|
this.ColorTypes[row.RowId, 0] = row.Dark;
|
||||||
this.colorTypes[row.RowId, 1] = row.Light;
|
this.ColorTypes[row.RowId, 1] = row.Light;
|
||||||
this.colorTypes[row.RowId, 2] = row.ClassicFF;
|
this.ColorTypes[row.RowId, 2] = row.ClassicFF;
|
||||||
this.colorTypes[row.RowId, 3] = row.ClearBlue;
|
this.ColorTypes[row.RowId, 3] = row.ClearBlue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BitConverter.IsLittleEndian)
|
if (BitConverter.IsLittleEndian)
|
||||||
{
|
{
|
||||||
// ImGui wants RGBA in LE.
|
// ImGui wants RGBA in LE.
|
||||||
fixed (uint* p = this.colorTypes)
|
fixed (uint* p = this.ColorTypes)
|
||||||
{
|
{
|
||||||
foreach (ref var r in new Span<uint>(p, this.colorTypes.GetLength(0) * this.colorTypes.GetLength(1)))
|
foreach (ref var r in new Span<uint>(p, this.ColorTypes.GetLength(0) * this.ColorTypes.GetLength(1)))
|
||||||
r = BinaryPrimitives.ReverseEndianness(r);
|
r = BinaryPrimitives.ReverseEndianness(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="SeStringColorStackSet"/> class.</summary>
|
||||||
|
/// <param name="colorTypes">Color types.</param>
|
||||||
|
public SeStringColorStackSet(uint[,] colorTypes) => this.ColorTypes = colorTypes;
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether at least one color has been pushed to the edge color stack.</summary>
|
/// <summary>Gets a value indicating whether at least one color has been pushed to the edge color stack.</summary>
|
||||||
public bool HasAdditionalEdgeColor { get; private set; }
|
public bool HasAdditionalEdgeColor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the parsed <see cref="UIColor"/> containing colors to use with <see cref="MacroCode.ColorType"/>
|
||||||
|
/// and <see cref="MacroCode.EdgeColorType"/>.</summary>
|
||||||
|
public uint[,] ColorTypes { get; }
|
||||||
|
|
||||||
/// <summary>Resets the colors in the stack.</summary>
|
/// <summary>Resets the colors in the stack.</summary>
|
||||||
/// <param name="drawState">Draw state.</param>
|
/// <param name="drawState">Draw state.</param>
|
||||||
internal void Initialize(scoped ref SeStringDrawState drawState)
|
internal void Initialize(scoped ref SeStringDrawState drawState)
|
||||||
|
|
@ -191,9 +195,9 @@ internal sealed class SeStringColorStackSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opacity component is ignored.
|
// Opacity component is ignored.
|
||||||
var color = themeIndex >= 0 && themeIndex < this.colorTypes.GetLength(1) &&
|
var color = themeIndex >= 0 && themeIndex < this.ColorTypes.GetLength(1) &&
|
||||||
colorTypeIndex < this.colorTypes.GetLength(0)
|
colorTypeIndex < this.ColorTypes.GetLength(0)
|
||||||
? this.colorTypes[colorTypeIndex, themeIndex]
|
? this.ColorTypes[colorTypeIndex, themeIndex]
|
||||||
: 0u;
|
: 0u;
|
||||||
|
|
||||||
rgbaStack.Add(color | 0xFF000000u);
|
rgbaStack.Add(color | 0xFF000000u);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
|
||||||
/// <summary>Draws SeString.</summary>
|
/// <summary>Draws SeString.</summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal unsafe class SeStringRenderer : IInternalDisposableService
|
internal class SeStringRenderer : IServiceType
|
||||||
{
|
{
|
||||||
private const int ImGuiContextCurrentWindowOffset = 0x3FF0;
|
private const int ImGuiContextCurrentWindowOffset = 0x3FF0;
|
||||||
private const int ImGuiWindowDcOffset = 0x118;
|
private const int ImGuiWindowDcOffset = 0x118;
|
||||||
|
|
@ -47,28 +48,19 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
|
|
||||||
/// <summary>Parsed text fragments from a SeString.</summary>
|
/// <summary>Parsed text fragments from a SeString.</summary>
|
||||||
/// <remarks>Touched only from the main thread.</remarks>
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
private readonly List<TextFragment> fragments = [];
|
private readonly List<TextFragment> fragmentsMainThread = [];
|
||||||
|
|
||||||
/// <summary>Color stacks to use while evaluating a SeString for rendering.</summary>
|
/// <summary>Color stacks to use while evaluating a SeString for rendering.</summary>
|
||||||
/// <remarks>Touched only from the main thread.</remarks>
|
/// <remarks>Touched only from the main thread.</remarks>
|
||||||
private readonly SeStringColorStackSet colorStackSet;
|
private readonly SeStringColorStackSet colorStackSetMainThread;
|
||||||
|
|
||||||
/// <summary>Splits a draw list so that different layers of a single glyph can be drawn out of order.</summary>
|
|
||||||
private ImDrawListSplitter* splitter = ImGui.ImDrawListSplitter();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
||||||
{
|
{
|
||||||
this.colorStackSet = new(dm.Excel.GetSheet<UIColor>());
|
this.colorStackSetMainThread = new(dm.Excel.GetSheet<UIColor>());
|
||||||
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Finalizes an instance of the <see cref="SeStringRenderer"/> class.</summary>
|
|
||||||
~SeStringRenderer() => this.ReleaseUnmanagedResources();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources();
|
|
||||||
|
|
||||||
/// <summary>Compiles and caches a SeString from a text macro representation.</summary>
|
/// <summary>Compiles and caches a SeString from a text macro representation.</summary>
|
||||||
/// <param name="text">SeString text macro representation.
|
/// <param name="text">SeString text macro representation.
|
||||||
/// Newline characters will be normalized to newline payloads.</param>
|
/// Newline characters will be normalized to newline payloads.</param>
|
||||||
|
|
@ -80,6 +72,44 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
text.ReplaceLineEndings("<br>"),
|
text.ReplaceLineEndings("<br>"),
|
||||||
new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }));
|
new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }));
|
||||||
|
|
||||||
|
/// <summary>Creates a draw data that will draw the given SeString onto it.</summary>
|
||||||
|
/// <param name="sss">SeString to render.</param>
|
||||||
|
/// <param name="drawParams">Parameters for drawing.</param>
|
||||||
|
/// <returns>A new self-contained draw data.</returns>
|
||||||
|
public unsafe BufferBackedImDrawData CreateDrawData(
|
||||||
|
ReadOnlySeStringSpan sss,
|
||||||
|
scoped in SeStringDrawParams drawParams = default)
|
||||||
|
{
|
||||||
|
if (drawParams.TargetDrawList is not null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"{nameof(SeStringDrawParams.TargetDrawList)} may not be specified.",
|
||||||
|
nameof(drawParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dd = BufferBackedImDrawData.Create();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var size = this.Draw(sss, drawParams with { TargetDrawList = dd.ListPtr }).Size;
|
||||||
|
|
||||||
|
var offset = drawParams.ScreenOffset ?? Vector2.Zero;
|
||||||
|
foreach (var vtx in new Span<ImDrawVert>(dd.ListPtr.VtxBuffer.Data, dd.ListPtr.VtxBuffer.Size))
|
||||||
|
offset = Vector2.Min(offset, vtx.Pos);
|
||||||
|
|
||||||
|
dd.Data.DisplayPos = offset;
|
||||||
|
dd.Data.DisplaySize = size - offset;
|
||||||
|
dd.Data.Valid = 1;
|
||||||
|
dd.UpdateDrawDataStatistics();
|
||||||
|
return dd;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
dd.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Compiles and caches a SeString from a text macro representation, and then draws it.</summary>
|
/// <summary>Compiles and caches a SeString from a text macro representation, and then draws it.</summary>
|
||||||
/// <param name="text">SeString text macro representation.
|
/// <param name="text">SeString text macro representation.
|
||||||
/// Newline characters will be normalized to newline payloads.</param>
|
/// Newline characters will be normalized to newline payloads.</param>
|
||||||
|
|
@ -113,28 +143,44 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
|
/// <param name="imGuiId">ImGui ID, if link functionality is desired.</param>
|
||||||
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
|
/// <param name="buttonFlags">Button flags to use on link interaction.</param>
|
||||||
/// <returns>Interaction result of the rendered text.</returns>
|
/// <returns>Interaction result of the rendered text.</returns>
|
||||||
public SeStringDrawResult Draw(
|
public unsafe SeStringDrawResult Draw(
|
||||||
ReadOnlySeStringSpan sss,
|
ReadOnlySeStringSpan sss,
|
||||||
scoped in SeStringDrawParams drawParams = default,
|
scoped in SeStringDrawParams drawParams = default,
|
||||||
ImGuiId imGuiId = default,
|
ImGuiId imGuiId = default,
|
||||||
ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault)
|
ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault)
|
||||||
{
|
{
|
||||||
// Drawing is only valid if done from the main thread anyway, especially with interactivity.
|
// Interactivity is supported only from the main thread.
|
||||||
ThreadSafety.AssertMainThread();
|
if (!imGuiId.IsEmpty())
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
|
||||||
if (drawParams.TargetDrawList is not null && imGuiId)
|
if (drawParams.TargetDrawList is not null && imGuiId)
|
||||||
throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId));
|
throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId));
|
||||||
|
|
||||||
// This also does argument validation for drawParams. Do it here.
|
using var cleanup = new DisposeSafety.ScopedFinalizer();
|
||||||
var state = new SeStringDrawState(sss, drawParams, this.colorStackSet, this.splitter);
|
|
||||||
|
|
||||||
// Reset and initialize the state.
|
ImFont* font = null;
|
||||||
this.fragments.Clear();
|
if (drawParams.Font.HasValue)
|
||||||
this.colorStackSet.Initialize(ref state);
|
font = drawParams.Font.Value;
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
// This also does argument validation for drawParams. Do it here.
|
||||||
|
// `using var` makes a struct read-only, but we do want to modify it.
|
||||||
|
var stateStorage = new SeStringDrawState(
|
||||||
|
sss,
|
||||||
|
drawParams,
|
||||||
|
ThreadSafety.IsMainThread ? this.colorStackSetMainThread : new(this.colorStackSetMainThread.ColorTypes),
|
||||||
|
ThreadSafety.IsMainThread ? this.fragmentsMainThread : [],
|
||||||
|
font);
|
||||||
|
ref var state = ref Unsafe.AsRef(in stateStorage);
|
||||||
|
|
||||||
// Analyze the provided SeString and break it up to text fragments.
|
// Analyze the provided SeString and break it up to text fragments.
|
||||||
this.CreateTextFragments(ref state);
|
this.CreateTextFragments(ref state);
|
||||||
var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments);
|
var fragmentSpan = CollectionsMarshal.AsSpan(state.Fragments);
|
||||||
|
|
||||||
// Calculate size.
|
// Calculate size.
|
||||||
var size = Vector2.Zero;
|
var size = Vector2.Zero;
|
||||||
|
|
@ -147,24 +193,17 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
|
|
||||||
state.SplitDrawList();
|
state.SplitDrawList();
|
||||||
|
|
||||||
// Handle cases where ImGui.AlignTextToFramePadding has been called.
|
|
||||||
var context = ImGui.GetCurrentContext();
|
|
||||||
var currLineTextBaseOffset = 0f;
|
|
||||||
if (!context.IsNull)
|
|
||||||
{
|
|
||||||
var currentWindow = context.CurrentWindow;
|
|
||||||
if (!currentWindow.IsNull)
|
|
||||||
{
|
|
||||||
currLineTextBaseOffset = currentWindow.DC.CurrLineTextBaseOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemSize = size;
|
var itemSize = size;
|
||||||
if (currLineTextBaseOffset != 0f)
|
if (drawParams.TargetDrawList is null)
|
||||||
{
|
{
|
||||||
itemSize.Y += 2 * currLineTextBaseOffset;
|
// Handle cases where ImGui.AlignTextToFramePadding has been called.
|
||||||
foreach (ref var f in fragmentSpan)
|
var currLineTextBaseOffset = ImGui.GetCurrentContext().CurrentWindow.DC.CurrLineTextBaseOffset;
|
||||||
f.Offset += new Vector2(0, currLineTextBaseOffset);
|
if (currLineTextBaseOffset != 0f)
|
||||||
|
{
|
||||||
|
itemSize.Y += 2 * currLineTextBaseOffset;
|
||||||
|
foreach (ref var f in fragmentSpan)
|
||||||
|
f.Offset += new Vector2(0, currLineTextBaseOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw all text fragments.
|
// Draw all text fragments.
|
||||||
|
|
@ -280,15 +319,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
return displayRune.Value != 0;
|
return displayRune.Value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReleaseUnmanagedResources()
|
|
||||||
{
|
|
||||||
if (this.splitter is not null)
|
|
||||||
{
|
|
||||||
this.splitter->Destroy();
|
|
||||||
this.splitter = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Creates text fragment, taking line and word breaking into account.</summary>
|
/// <summary>Creates text fragment, taking line and word breaking into account.</summary>
|
||||||
/// <param name="state">Draw state.</param>
|
/// <param name="state">Draw state.</param>
|
||||||
private void CreateTextFragments(ref SeStringDrawState state)
|
private void CreateTextFragments(ref SeStringDrawState state)
|
||||||
|
|
@ -391,7 +421,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth;
|
var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth;
|
||||||
|
|
||||||
// Test if the fragment does not fit into the current line and the current line is not empty.
|
// Test if the fragment does not fit into the current line and the current line is not empty.
|
||||||
if (xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows)
|
if (xy.X != 0 && state.Fragments.Count > 0 && !state.Fragments[^1].BreakAfter && overflows)
|
||||||
{
|
{
|
||||||
// Introduce break if this is the first time testing the current break unit or the current fragment
|
// Introduce break if this is the first time testing the current break unit or the current fragment
|
||||||
// is an entity.
|
// is an entity.
|
||||||
|
|
@ -401,7 +431,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
xy.X = 0;
|
xy.X = 0;
|
||||||
xy.Y += state.LineHeight;
|
xy.Y += state.LineHeight;
|
||||||
w = 0;
|
w = 0;
|
||||||
CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true;
|
CollectionsMarshal.AsSpan(state.Fragments)[^1].BreakAfter = true;
|
||||||
fragment.Offset = xy;
|
fragment.Offset = xy;
|
||||||
|
|
||||||
// Now that the fragment is given its own line, test if it overflows again.
|
// Now that the fragment is given its own line, test if it overflows again.
|
||||||
|
|
@ -419,16 +449,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth);
|
fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.fragments.Count > 0 && xy.X != 0)
|
else if (state.Fragments.Count > 0 && xy.X != 0)
|
||||||
{
|
{
|
||||||
// New fragment fits into the current line, and it has a previous fragment in the same line.
|
// New fragment fits into the current line, and it has a previous fragment in the same line.
|
||||||
// If the previous fragment ends with a soft hyphen, adjust its width so that the width of its
|
// If the previous fragment ends with a soft hyphen, adjust its width so that the width of its
|
||||||
// trailing soft hyphens are not considered.
|
// trailing soft hyphens are not considered.
|
||||||
if (this.fragments[^1].EndsWithSoftHyphen)
|
if (state.Fragments[^1].EndsWithSoftHyphen)
|
||||||
xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth;
|
xy.X += state.Fragments[^1].AdvanceWidthWithoutSoftHyphen - state.Fragments[^1].AdvanceWidth;
|
||||||
|
|
||||||
// Adjust this fragment's offset from kerning distance.
|
// Adjust this fragment's offset from kerning distance.
|
||||||
xy.X += state.CalculateScaledDistance(this.fragments[^1].LastRune, fragment.FirstRune);
|
xy.X += state.CalculateScaledDistance(state.Fragments[^1].LastRune, fragment.FirstRune);
|
||||||
fragment.Offset = xy;
|
fragment.Offset = xy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +469,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
w = Math.Max(w, xy.X + fragment.VisibleWidth);
|
w = Math.Max(w, xy.X + fragment.VisibleWidth);
|
||||||
xy.X += fragment.AdvanceWidth;
|
xy.X += fragment.AdvanceWidth;
|
||||||
prev = fragment.To;
|
prev = fragment.To;
|
||||||
this.fragments.Add(fragment);
|
state.Fragments.Add(fragment);
|
||||||
|
|
||||||
if (fragment.BreakAfter)
|
if (fragment.BreakAfter)
|
||||||
{
|
{
|
||||||
|
|
@ -491,7 +521,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
if (gfdTextureSrv != 0)
|
if (gfdTextureSrv != 0)
|
||||||
{
|
{
|
||||||
state.Draw(
|
state.Draw(
|
||||||
new ImTextureID(gfdTextureSrv),
|
new(gfdTextureSrv),
|
||||||
offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)),
|
offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)),
|
||||||
size,
|
size,
|
||||||
useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0,
|
useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0,
|
||||||
|
|
@ -528,7 +558,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
static nint GetGfdTextureSrv()
|
static unsafe nint GetGfdTextureSrv()
|
||||||
{
|
{
|
||||||
var uim = UIModule.Instance();
|
var uim = UIModule.Instance();
|
||||||
if (uim is null)
|
if (uim is null)
|
||||||
|
|
@ -553,7 +583,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
/// <summary>Determines a bitmap icon to display for the given SeString payload.</summary>
|
/// <summary>Determines a bitmap icon to display for the given SeString payload.</summary>
|
||||||
/// <param name="sss">Byte span that should include a SeString payload.</param>
|
/// <param name="sss">Byte span that should include a SeString payload.</param>
|
||||||
/// <returns>Icon to display, or <see cref="None"/> if it should not be displayed as an icon.</returns>
|
/// <returns>Icon to display, or <see cref="None"/> if it should not be displayed as an icon.</returns>
|
||||||
private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan<byte> sss)
|
private unsafe BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan<byte> sss)
|
||||||
{
|
{
|
||||||
var e = new ReadOnlySeStringSpan(sss).GetEnumerator();
|
var e = new ReadOnlySeStringSpan(sss).GetEnumerator();
|
||||||
if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2)
|
if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2)
|
||||||
|
|
@ -710,38 +740,4 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
firstDisplayRune ?? default,
|
firstDisplayRune ?? default,
|
||||||
lastNonSoftHyphenRune);
|
lastNonSoftHyphenRune);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Represents a text fragment in a SeString span.</summary>
|
|
||||||
/// <param name="From">Starting byte offset (inclusive) in a SeString.</param>
|
|
||||||
/// <param name="To">Ending byte offset (exclusive) in a SeString.</param>
|
|
||||||
/// <param name="Link">Byte offset of the link that decorates this text fragment, or <c>-1</c> if none.</param>
|
|
||||||
/// <param name="Offset">Offset in pixels w.r.t. <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
|
||||||
/// <param name="Entity">Replacement entity, if any.</param>
|
|
||||||
/// <param name="VisibleWidth">Visible width of this text fragment. This is the width required to draw everything
|
|
||||||
/// without clipping.</param>
|
|
||||||
/// <param name="AdvanceWidth">Advance width of this text fragment. This is the width required to add to the cursor
|
|
||||||
/// to position the next fragment correctly.</param>
|
|
||||||
/// <param name="AdvanceWidthWithoutSoftHyphen">Same with <paramref name="AdvanceWidth"/>, but trimming all the
|
|
||||||
/// trailing soft hyphens.</param>
|
|
||||||
/// <param name="BreakAfter">Whether to insert a line break after this text fragment.</param>
|
|
||||||
/// <param name="EndsWithSoftHyphen">Whether this text fragment ends with one or more soft hyphens.</param>
|
|
||||||
/// <param name="FirstRune">First rune in this text fragment.</param>
|
|
||||||
/// <param name="LastRune">Last rune in this text fragment, for the purpose of calculating kerning distance with
|
|
||||||
/// the following text fragment in the same line, if any.</param>
|
|
||||||
private record struct TextFragment(
|
|
||||||
int From,
|
|
||||||
int To,
|
|
||||||
int Link,
|
|
||||||
Vector2 Offset,
|
|
||||||
SeStringReplacementEntity Entity,
|
|
||||||
float VisibleWidth,
|
|
||||||
float AdvanceWidth,
|
|
||||||
float AdvanceWidthWithoutSoftHyphen,
|
|
||||||
bool BreakAfter,
|
|
||||||
bool EndsWithSoftHyphen,
|
|
||||||
Rune FirstRune,
|
|
||||||
Rune LastRune)
|
|
||||||
{
|
|
||||||
public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
|
||||||
|
/// <summary>Represents a text fragment in a SeString span.</summary>
|
||||||
|
/// <param name="From">Starting byte offset (inclusive) in a SeString.</param>
|
||||||
|
/// <param name="To">Ending byte offset (exclusive) in a SeString.</param>
|
||||||
|
/// <param name="Link">Byte offset of the link that decorates this text fragment, or <c>-1</c> if none.</param>
|
||||||
|
/// <param name="Offset">Offset in pixels w.r.t. <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
||||||
|
/// <param name="Entity">Replacement entity, if any.</param>
|
||||||
|
/// <param name="VisibleWidth">Visible width of this text fragment. This is the width required to draw everything
|
||||||
|
/// without clipping.</param>
|
||||||
|
/// <param name="AdvanceWidth">Advance width of this text fragment. This is the width required to add to the cursor
|
||||||
|
/// to position the next fragment correctly.</param>
|
||||||
|
/// <param name="AdvanceWidthWithoutSoftHyphen">Same with <paramref name="AdvanceWidth"/>, but trimming all the
|
||||||
|
/// trailing soft hyphens.</param>
|
||||||
|
/// <param name="BreakAfter">Whether to insert a line break after this text fragment.</param>
|
||||||
|
/// <param name="EndsWithSoftHyphen">Whether this text fragment ends with one or more soft hyphens.</param>
|
||||||
|
/// <param name="FirstRune">First rune in this text fragment.</param>
|
||||||
|
/// <param name="LastRune">Last rune in this text fragment, for the purpose of calculating kerning distance with
|
||||||
|
/// the following text fragment in the same line, if any.</param>
|
||||||
|
internal record struct TextFragment(
|
||||||
|
int From,
|
||||||
|
int To,
|
||||||
|
int Link,
|
||||||
|
Vector2 Offset,
|
||||||
|
SeStringReplacementEntity Entity,
|
||||||
|
float VisibleWidth,
|
||||||
|
float AdvanceWidth,
|
||||||
|
float AdvanceWidthWithoutSoftHyphen,
|
||||||
|
bool BreakAfter,
|
||||||
|
bool EndsWithSoftHyphen,
|
||||||
|
Rune FirstRune,
|
||||||
|
Rune LastRune)
|
||||||
|
{
|
||||||
|
/// <summary>Gets a value indicating whether the fragment ends with a visible soft hyphen.</summary>
|
||||||
|
public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter;
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,11 @@ public record struct SeStringDrawParams
|
||||||
/// <summary>Gets or sets the target draw list.</summary>
|
/// <summary>Gets or sets the target draw list.</summary>
|
||||||
/// <value>Target draw list, <c>default(ImDrawListPtr)</c> to not draw, or <c>null</c> to use
|
/// <value>Target draw list, <c>default(ImDrawListPtr)</c> to not draw, or <c>null</c> to use
|
||||||
/// <see cref="ImGui.GetWindowDrawList"/> (the default).</value>
|
/// <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.
|
/// <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, 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>
|
/// </remarks>
|
||||||
public ImDrawListPtr? TargetDrawList { get; set; }
|
public ImDrawListPtr? TargetDrawList { get; set; }
|
||||||
|
|
||||||
|
|
@ -21,16 +25,20 @@ public record struct SeStringDrawParams
|
||||||
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; }
|
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
||||||
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos()"/>.</value>
|
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos()"/>, if no <see cref="TargetDrawList"/>
|
||||||
|
/// is specified. Otherwise, you must specify it (for example, by passing <see cref="ImGui.GetCursorScreenPos()"/> when passing the window
|
||||||
|
/// draw list.</value>
|
||||||
public Vector2? ScreenOffset { get; set; }
|
public Vector2? ScreenOffset { get; set; }
|
||||||
|
|
||||||
/// <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>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
@ -6,6 +7,8 @@ using System.Text;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
@ -19,46 +22,88 @@ public unsafe ref struct SeStringDrawState
|
||||||
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
||||||
|
|
||||||
private readonly ImDrawList* drawList;
|
private readonly ImDrawList* drawList;
|
||||||
private readonly SeStringColorStackSet colorStackSet;
|
|
||||||
private readonly ImDrawListSplitter* splitter;
|
private ImDrawListSplitter splitter;
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="SeStringDrawState"/> struct.</summary>
|
/// <summary>Initializes a new instance of the <see cref="SeStringDrawState"/> struct.</summary>
|
||||||
/// <param name="span">Raw SeString byte span.</param>
|
/// <param name="span">Raw SeString byte span.</param>
|
||||||
/// <param name="ssdp">Instance of <see cref="SeStringDrawParams"/> to initialize from.</param>
|
/// <param name="ssdp">Instance of <see cref="SeStringDrawParams"/> to initialize from.</param>
|
||||||
/// <param name="colorStackSet">Instance of <see cref="SeStringColorStackSet"/> to use.</param>
|
/// <param name="colorStackSet">Instance of <see cref="SeStringColorStackSet"/> to use.</param>
|
||||||
/// <param name="splitter">Instance of ImGui Splitter to use.</param>
|
/// <param name="fragments">Fragments.</param>
|
||||||
|
/// <param name="font">Font to use.</param>
|
||||||
internal SeStringDrawState(
|
internal SeStringDrawState(
|
||||||
ReadOnlySpan<byte> span,
|
ReadOnlySpan<byte> span,
|
||||||
scoped in SeStringDrawParams ssdp,
|
scoped in SeStringDrawParams ssdp,
|
||||||
SeStringColorStackSet colorStackSet,
|
SeStringColorStackSet colorStackSet,
|
||||||
ImDrawListSplitter* splitter)
|
List<TextFragment> fragments,
|
||||||
|
ImFont* font)
|
||||||
{
|
{
|
||||||
this.colorStackSet = colorStackSet;
|
|
||||||
this.splitter = splitter;
|
|
||||||
this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
|
||||||
this.Span = span;
|
this.Span = span;
|
||||||
|
this.ColorStackSet = colorStackSet;
|
||||||
|
this.Fragments = fragments;
|
||||||
|
this.Font = font;
|
||||||
|
|
||||||
|
if (ssdp.TargetDrawList is null)
|
||||||
|
{
|
||||||
|
if (!ThreadSafety.IsMainThread)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"{nameof(ssdp.TargetDrawList)} must be set to render outside the main thread.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList();
|
||||||
|
this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
||||||
|
this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
||||||
|
this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
||||||
|
this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
||||||
|
this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
||||||
|
this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
||||||
|
this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.drawList = ssdp.TargetDrawList.Value;
|
||||||
|
this.ScreenOffset = ssdp.ScreenOffset ?? Vector2.Zero;
|
||||||
|
|
||||||
|
// API14: Remove, always throw
|
||||||
|
if (ThreadSafety.IsMainThread)
|
||||||
|
{
|
||||||
|
this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
||||||
|
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.
|
||||||
|
this.LinkActiveBackColor = 0; // Interactivity is unused outside the main thread.
|
||||||
|
this.ThemeIndex = ssdp.ThemeIndex ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.splitter = default;
|
||||||
this.GetEntity = ssdp.GetEntity;
|
this.GetEntity = ssdp.GetEntity;
|
||||||
this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
|
|
||||||
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.Font = ssdp.EffectiveFont;
|
this.FontSizeScale = this.FontSize / this.Font.FontSize;
|
||||||
this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
|
||||||
this.FontSizeScale = this.FontSize / this.Font->FontSize;
|
|
||||||
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||||
this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X;
|
|
||||||
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||||
this.Opacity = ssdp.EffectiveOpacity;
|
this.Opacity = ssdp.EffectiveOpacity;
|
||||||
this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity;
|
this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity;
|
||||||
this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text);
|
|
||||||
this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000;
|
this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000;
|
||||||
this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000;
|
this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000;
|
||||||
this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
|
||||||
this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
|
||||||
this.ForceEdgeColor = ssdp.ForceEdgeColor;
|
this.ForceEdgeColor = ssdp.ForceEdgeColor;
|
||||||
this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
|
||||||
this.Bold = ssdp.Bold;
|
this.Bold = ssdp.Bold;
|
||||||
this.Italic = ssdp.Italic;
|
this.Italic = ssdp.Italic;
|
||||||
this.Edge = ssdp.Edge;
|
this.Edge = ssdp.Edge;
|
||||||
this.Shadow = ssdp.Shadow;
|
this.Shadow = ssdp.Shadow;
|
||||||
|
|
||||||
|
this.ColorStackSet.Initialize(ref this);
|
||||||
|
fragments.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
/// <inheritdoc cref="SeStringDrawParams.TargetDrawList"/>
|
||||||
|
|
@ -74,7 +119,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; }
|
||||||
|
|
@ -135,7 +180,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
public readonly bool ShouldDrawEdge =>
|
public readonly bool ShouldDrawEdge =>
|
||||||
(this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000;
|
(this.Edge || this.ColorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000;
|
||||||
|
|
||||||
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 };
|
public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 };
|
||||||
|
|
@ -143,11 +188,17 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
/// <summary>Gets a value indicating whether the edge should be drawn.</summary>
|
||||||
public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 };
|
public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 };
|
||||||
|
|
||||||
|
/// <summary>Gets the color stacks.</summary>
|
||||||
|
internal SeStringColorStackSet ColorStackSet { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the text fragments.</summary>
|
||||||
|
internal List<TextFragment> Fragments { get; }
|
||||||
|
|
||||||
/// <summary>Sets the current channel in the ImGui draw list splitter.</summary>
|
/// <summary>Sets the current channel in the ImGui draw list splitter.</summary>
|
||||||
/// <param name="channelIndex">Channel to switch to.</param>
|
/// <param name="channelIndex">Channel to switch to.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void SetCurrentChannel(SeStringDrawChannel channelIndex) =>
|
public void SetCurrentChannel(SeStringDrawChannel channelIndex) =>
|
||||||
this.splitter->SetCurrentChannel(this.drawList, (int)channelIndex);
|
this.splitter.SetCurrentChannel(this.drawList, (int)channelIndex);
|
||||||
|
|
||||||
/// <summary>Draws a single texture.</summary>
|
/// <summary>Draws a single texture.</summary>
|
||||||
/// <param name="igTextureId">ImGui texture ID to draw from.</param>
|
/// <param name="igTextureId">ImGui texture ID to draw from.</param>
|
||||||
|
|
@ -216,9 +267,9 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <summary>Draws a single glyph using current styling configurations.</summary>
|
/// <summary>Draws a single glyph using current styling configurations.</summary>
|
||||||
/// <param name="g">Glyph to draw.</param>
|
/// <param name="g">Glyph to draw.</param>
|
||||||
/// <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 readonly 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));
|
||||||
|
|
@ -268,14 +319,14 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <param name="offset">Offset of the glyph in pixels w.r.t.
|
/// <param name="offset">Offset of the glyph in pixels w.r.t.
|
||||||
/// <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
/// <see cref="SeStringDrawParams.ScreenOffset"/>.</param>
|
||||||
/// <param name="advanceWidth">Advance width of the glyph.</param>
|
/// <param name="advanceWidth">Advance width of the glyph.</param>
|
||||||
internal readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth)
|
internal void DrawLinkUnderline(Vector2 offset, float advanceWidth)
|
||||||
{
|
{
|
||||||
if (this.LinkUnderlineThickness < 1f)
|
if (this.LinkUnderlineThickness < 1f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
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(
|
||||||
|
|
@ -302,9 +353,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>
|
||||||
|
|
@ -337,7 +388,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);
|
||||||
}
|
}
|
||||||
|
|
@ -350,15 +401,15 @@ public unsafe ref struct SeStringDrawState
|
||||||
switch (payload.MacroCode)
|
switch (payload.MacroCode)
|
||||||
{
|
{
|
||||||
case MacroCode.Color:
|
case MacroCode.Color:
|
||||||
this.colorStackSet.HandleColorPayload(ref this, payload);
|
this.ColorStackSet.HandleColorPayload(ref this, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MacroCode.EdgeColor:
|
case MacroCode.EdgeColor:
|
||||||
this.colorStackSet.HandleEdgeColorPayload(ref this, payload);
|
this.ColorStackSet.HandleEdgeColorPayload(ref this, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MacroCode.ShadowColor:
|
case MacroCode.ShadowColor:
|
||||||
this.colorStackSet.HandleShadowColorPayload(ref this, payload);
|
this.ColorStackSet.HandleShadowColorPayload(ref this, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u):
|
||||||
|
|
@ -379,11 +430,11 @@ public unsafe ref struct SeStringDrawState
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MacroCode.ColorType:
|
case MacroCode.ColorType:
|
||||||
this.colorStackSet.HandleColorTypePayload(ref this, payload);
|
this.ColorStackSet.HandleColorTypePayload(ref this, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MacroCode.EdgeColorType:
|
case MacroCode.EdgeColorType:
|
||||||
this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload);
|
this.ColorStackSet.HandleEdgeColorTypePayload(ref this, payload);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -393,10 +444,9 @@ public unsafe ref struct SeStringDrawState
|
||||||
|
|
||||||
/// <summary>Splits the draw list.</summary>
|
/// <summary>Splits the draw list.</summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal readonly void SplitDrawList() =>
|
internal void SplitDrawList() => this.splitter.Split(this.drawList, ChannelCount);
|
||||||
this.splitter->Split(this.drawList, ChannelCount);
|
|
||||||
|
|
||||||
/// <summary>Merges the draw list.</summary>
|
/// <summary>Merges the draw list.</summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal readonly void MergeDrawList() => this.splitter->Merge(this.drawList);
|
internal void MergeDrawList() => this.splitter.Merge(this.drawList);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -533,6 +533,13 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
this.creditsDarkeningAnimation.Restart();
|
this.creditsDarkeningAnimation.Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="DataWindow.GetWidget{T}"/>
|
||||||
|
public T GetDataWindowWidget<T>() where T : IDataWindowWidget => this.dataWindow.GetWidget<T>();
|
||||||
|
|
||||||
|
/// <summary>Sets the data window current widget.</summary>
|
||||||
|
/// <param name="widget">Widget to set current.</param>
|
||||||
|
public void SetDataWindowWidget(IDataWindowWidget widget) => this.dataWindow.CurrentWidget = widget;
|
||||||
|
|
||||||
private void OnDraw()
|
private void OnDraw()
|
||||||
{
|
{
|
||||||
this.FrameCount++;
|
this.FrameCount++;
|
||||||
|
|
@ -660,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();
|
||||||
|
|
@ -832,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ internal class DataWindow : Window, IDisposable
|
||||||
|
|
||||||
private bool isExcept;
|
private bool isExcept;
|
||||||
private bool selectionCollapsed;
|
private bool selectionCollapsed;
|
||||||
private IDataWindowWidget currentWidget;
|
|
||||||
private bool isLoaded;
|
private bool isLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -82,9 +82,12 @@ internal class DataWindow : Window, IDisposable
|
||||||
|
|
||||||
this.RespectCloseHotkey = false;
|
this.RespectCloseHotkey = false;
|
||||||
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
||||||
this.currentWidget = this.orderedModules.First();
|
this.CurrentWidget = this.orderedModules.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets or sets the current widget.</summary>
|
||||||
|
public IDataWindowWidget CurrentWidget { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose() => this.modules.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
public void Dispose() => this.modules.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
||||||
|
|
||||||
|
|
@ -99,6 +102,20 @@ internal class DataWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the data window widget of the specified type.</summary>
|
||||||
|
/// <typeparam name="T">Type of the data window widget to find.</typeparam>
|
||||||
|
/// <returns>Found widget.</returns>
|
||||||
|
public T GetWidget<T>() where T : IDataWindowWidget
|
||||||
|
{
|
||||||
|
foreach (var m in this.modules)
|
||||||
|
{
|
||||||
|
if (m is T w)
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"No widget of type {typeof(T).FullName} found.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the DataKind dropdown menu.
|
/// Set the DataKind dropdown menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -110,7 +127,7 @@ internal class DataWindow : Window, IDisposable
|
||||||
|
|
||||||
if (this.modules.FirstOrDefault(module => module.IsWidgetCommand(dataKind)) is { } targetModule)
|
if (this.modules.FirstOrDefault(module => module.IsWidgetCommand(dataKind)) is { } targetModule)
|
||||||
{
|
{
|
||||||
this.currentWidget = targetModule;
|
this.CurrentWidget = targetModule;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -153,9 +170,9 @@ internal class DataWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
foreach (var widget in this.orderedModules)
|
foreach (var widget in this.orderedModules)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(widget.DisplayName, this.currentWidget == widget))
|
if (ImGui.Selectable(widget.DisplayName, this.CurrentWidget == widget))
|
||||||
{
|
{
|
||||||
this.currentWidget = widget;
|
this.CurrentWidget = widget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,9 +223,9 @@ internal class DataWindow : Window, IDisposable
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (this.currentWidget is { Ready: true })
|
if (this.CurrentWidget is { Ready: true })
|
||||||
{
|
{
|
||||||
this.currentWidget.Draw();
|
this.CurrentWidget.Draw();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Internal;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
||||||
|
|
@ -87,12 +93,30 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.ScaledDummy(10f);
|
ImGuiHelpers.ScaledDummy(10f);
|
||||||
for (var i = 0; i < this.icons?.Count; i++)
|
for (var i = 0; i < this.icons?.Count; i++)
|
||||||
{
|
{
|
||||||
|
if (this.icons[i] == FontAwesomeIcon.None)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.Text($"0x{(int)this.icons[i].ToIconChar():X}");
|
ImGui.Text($"0x{(int)this.icons[i].ToIconChar():X}");
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
||||||
ImGui.Text($"{this.iconNames?[i]}");
|
ImGui.Text($"{this.iconNames?[i]}");
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
||||||
ImGui.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont);
|
ImGui.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont);
|
||||||
ImGui.Text(this.icons[i].ToIconString());
|
ImGui.Text(this.icons[i].ToIconString());
|
||||||
|
ImGuiHelpers.ScaledRelativeSameLine(320f);
|
||||||
|
if (this.useFixedWidth
|
||||||
|
? ImGui.Button($"{(char)this.icons[i]}##FontAwesomeIconButton{i}")
|
||||||
|
: ImGuiComponents.IconButton($"##FontAwesomeIconButton{i}", this.icons[i]))
|
||||||
|
{
|
||||||
|
_ = Service<DevTextureSaveMenu>.Get().ShowTextureSaveMenuAsync(
|
||||||
|
this.DisplayName,
|
||||||
|
this.icons[i].ToString(),
|
||||||
|
Task.FromResult(
|
||||||
|
Service<TextureManager>.Get().CreateTextureFromSeString(
|
||||||
|
ReadOnlySeString.FromText(this.icons[i].ToIconString()),
|
||||||
|
new() { Font = ImGui.GetFont(), FontSize = ImGui.GetFontSize() })));
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
ImGuiHelpers.ScaledDummy(2f);
|
ImGuiHelpers.ScaledDummy(2f);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -9,11 +10,13 @@ using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Internal;
|
||||||
using Dalamud.Storage.Assets;
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
using Lumina.Text;
|
using Lumina.Text;
|
||||||
|
using Lumina.Text.Parse;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
|
@ -56,11 +59,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ?? ImGui.GetColorU32(ImGuiCol.Text));
|
var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ??= ImGui.GetColorU32(ImGuiCol.Text));
|
||||||
if (ImGui.ColorEdit4("Color", ref t2))
|
if (ImGui.ColorEdit4("Color", ref t2))
|
||||||
this.style.Color = ImGui.ColorConvertFloat4ToU32(t2);
|
this.style.Color = ImGui.ColorConvertFloat4ToU32(t2);
|
||||||
|
|
||||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ?? 0xFF000000u);
|
t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ??= 0xFF000000u);
|
||||||
if (ImGui.ColorEdit4("Edge Color", ref t2))
|
if (ImGui.ColorEdit4("Edge Color", ref t2))
|
||||||
this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2);
|
this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||||
|
|
||||||
|
|
@ -69,27 +72,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
if (ImGui.Checkbox("Forced"u8, ref t))
|
if (ImGui.Checkbox("Forced"u8, ref t))
|
||||||
this.style.ForceEdgeColor = t;
|
this.style.ForceEdgeColor = t;
|
||||||
|
|
||||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ?? 0xFF000000u);
|
t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ??= 0xFF000000u);
|
||||||
if (ImGui.ColorEdit4("Shadow Color", ref t2))
|
if (ImGui.ColorEdit4("Shadow Color"u8, ref t2))
|
||||||
this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2);
|
this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||||
|
|
||||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered));
|
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonHovered));
|
||||||
if (ImGui.ColorEdit4("Link Hover Color", ref t2))
|
if (ImGui.ColorEdit4("Link Hover Color"u8, ref t2))
|
||||||
this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||||
|
|
||||||
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive));
|
t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonActive));
|
||||||
if (ImGui.ColorEdit4("Link Active Color", ref t2))
|
if (ImGui.ColorEdit4("Link Active Color"u8, ref t2))
|
||||||
this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2);
|
||||||
|
|
||||||
var t3 = this.style.LineHeight ?? 1f;
|
var t3 = this.style.LineHeight ??= 1f;
|
||||||
if (ImGui.DragFloat("Line Height"u8, ref t3, 0.01f, 0.4f, 3f, "%.02f"))
|
if (ImGui.DragFloat("Line Height"u8, ref t3, 0.01f, 0.4f, 3f, "%.02f"))
|
||||||
this.style.LineHeight = t3;
|
this.style.LineHeight = t3;
|
||||||
|
|
||||||
t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha;
|
t3 = this.style.Opacity ??= 1f;
|
||||||
if (ImGui.DragFloat("Opacity"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
if (ImGui.DragFloat("Opacity"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||||
this.style.Opacity = t3;
|
this.style.Opacity = t3;
|
||||||
|
|
||||||
t3 = this.style.EdgeStrength ?? 0.25f;
|
t3 = this.style.EdgeStrength ??= 0.25f;
|
||||||
if (ImGui.DragFloat("Edge Strength"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
if (ImGui.DragFloat("Edge Strength"u8, ref t3, 0.005f, 0f, 1f, "%.02f"))
|
||||||
this.style.EdgeStrength = t3;
|
this.style.EdgeStrength = t3;
|
||||||
|
|
||||||
|
|
@ -174,6 +177,24 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style);
|
ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader("Draw into drawlist"))
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(100);
|
||||||
|
ImGui.SetCursorScreenPos(ImGui.GetItemRectMin() + ImGui.GetStyle().FramePadding);
|
||||||
|
var clipMin = ImGui.GetItemRectMin() + ImGui.GetStyle().FramePadding;
|
||||||
|
var clipMax = ImGui.GetItemRectMax() - ImGui.GetStyle().FramePadding;
|
||||||
|
clipMin.Y = MathF.Max(clipMin.Y, ImGui.GetWindowPos().Y);
|
||||||
|
clipMax.Y = MathF.Min(clipMax.Y, ImGui.GetWindowPos().Y + ImGui.GetWindowHeight());
|
||||||
|
|
||||||
|
var dl = ImGui.GetWindowDrawList();
|
||||||
|
dl.PushClipRect(clipMin, clipMax);
|
||||||
|
ImGuiHelpers.CompileSeStringWrapped(
|
||||||
|
"<icon(1)>Test test<icon(1)>",
|
||||||
|
new SeStringDrawParams
|
||||||
|
{ Color = 0xFFFFFFFF, WrapWidth = float.MaxValue, TargetDrawList = dl });
|
||||||
|
dl.PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Addon Table"u8))
|
if (ImGui.CollapsingHeader("Addon Table"u8))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTable("Addon Sheet"u8, 3))
|
if (ImGui.BeginTable("Addon Sheet"u8, 3))
|
||||||
|
|
@ -240,6 +261,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Copy as Image"))
|
||||||
|
{
|
||||||
|
_ = Service<DevTextureSaveMenu>.Get().ShowTextureSaveMenuAsync(
|
||||||
|
this.DisplayName,
|
||||||
|
$"From {nameof(SeStringRendererTestWidget)}",
|
||||||
|
Task.FromResult(
|
||||||
|
Service<TextureManager>.Get().CreateTextureFromSeString(
|
||||||
|
ReadOnlySeString.FromMacroString(
|
||||||
|
this.testString,
|
||||||
|
new(ExceptionMode: MacroStringParseExceptionMode.EmbedError)),
|
||||||
|
this.style with
|
||||||
|
{
|
||||||
|
Font = ImGui.GetFont(),
|
||||||
|
FontSize = ImGui.GetFontSize(),
|
||||||
|
WrapWidth = ImGui.GetContentRegionAvail().X,
|
||||||
|
ThemeIndex = AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(3);
|
ImGuiHelpers.ScaledDummy(3);
|
||||||
ImGuiHelpers.CompileSeStringWrapped(
|
ImGuiHelpers.CompileSeStringWrapped(
|
||||||
"Optional features implemented for the following test input:<br>" +
|
"Optional features implemented for the following test input:<br>" +
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
@ -306,12 +306,12 @@ internal class TexWidget : IDataWindowWidget
|
||||||
pres->Release();
|
pres->Release();
|
||||||
|
|
||||||
ImGui.Text($"RC: Resource({rcres})/View({rcsrv})");
|
ImGui.Text($"RC: Resource({rcres})/View({rcsrv})");
|
||||||
ImGui.Text(source.ToString());
|
ImGui.Text($"{source.Width} x {source.Height} | {source}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.Text("RC: -"u8);
|
ImGui.Text("RC: -");
|
||||||
ImGui.Text(" "u8);
|
ImGui.Text(string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,6 +342,10 @@ internal class TexWidget : IDataWindowWidget
|
||||||
runLater?.Invoke();
|
runLater?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Adds a texture wrap for debug display purposes.</summary>
|
||||||
|
/// <param name="textureTask">Task returning a texture.</param>
|
||||||
|
public void AddTexture(Task<IDalamudTextureWrap> textureTask) => this.addedTextures.Add(new(Api10: textureTask));
|
||||||
|
|
||||||
private unsafe void DrawBlame(List<TextureManager.IBlameableDalamudTextureWrap> allBlames)
|
private unsafe void DrawBlame(List<TextureManager.IBlameableDalamudTextureWrap> allBlames)
|
||||||
{
|
{
|
||||||
var im = Service<InterfaceManager>.Get();
|
var im = Service<InterfaceManager>.Get();
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,29 @@ internal class NounProcessorSelfTestStep : ISelfTestStep
|
||||||
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"),
|
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"),
|
||||||
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"),
|
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"),
|
||||||
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"),
|
new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"),
|
||||||
|
|
||||||
|
// ColumnOffset tests
|
||||||
|
|
||||||
|
new(nameof(LSheets.BeastTribe), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a Amalj'aa"),
|
||||||
|
new(nameof(LSheets.BeastTribe), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the Amalj'aa"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.DeepDungeonEquipment), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "an aetherpool arm"),
|
||||||
|
new(nameof(LSheets.DeepDungeonEquipment), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the aetherpool arm"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.DeepDungeonItem), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a pomander of safety"),
|
||||||
|
new(nameof(LSheets.DeepDungeonItem), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the pomander of safety"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.DeepDungeonMagicStone), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a splinter of Inferno magicite"),
|
||||||
|
new(nameof(LSheets.DeepDungeonMagicStone), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the splinter of Inferno magicite"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.DeepDungeonDemiclone), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "an Unei demiclone"),
|
||||||
|
new(nameof(LSheets.DeepDungeonDemiclone), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the Unei demiclone"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.Glasses), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a pair of oval spectacles"),
|
||||||
|
new(nameof(LSheets.Glasses), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the pair of oval spectacles"),
|
||||||
|
|
||||||
|
new(nameof(LSheets.GlassesStyle), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a shaded spectacles"),
|
||||||
|
new(nameof(LSheets.GlassesStyle), 1, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the shaded spectacles"),
|
||||||
];
|
];
|
||||||
|
|
||||||
private enum GermanCases
|
private enum GermanCases
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
: base(
|
: base(
|
||||||
"TitleScreenMenuOverlay",
|
"TitleScreenMenuOverlay",
|
||||||
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
||||||
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus |
|
||||||
|
ImGuiWindowFlags.NoDocking)
|
||||||
{
|
{
|
||||||
this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true);
|
this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
|
@ -33,10 +34,22 @@ public interface IFontHandle : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.<br />
|
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.<br />
|
||||||
/// Alternatively, use <see cref="WaitAsync"/> to wait for this property to become <c>true</c>.
|
/// Alternatively, use <see cref="WaitAsync()"/> to wait for this property to become <c>true</c>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
bool Available { get; }
|
bool Available { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to lock the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
||||||
|
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
||||||
|
/// Modification of the font will exhibit undefined behavior if some other thread also uses the font.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="errorMessage">The error message, if any.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <see cref="ILockedImFont"/> that <b>must</b> be disposed after use on success;
|
||||||
|
/// <c>null</c> with <paramref name="errorMessage"/> populated on failure.
|
||||||
|
/// </returns>
|
||||||
|
ILockedImFont? TryLock(out string? errorMessage);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locks the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
/// Locks the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
||||||
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
||||||
|
|
@ -92,4 +105,11 @@ public interface IFontHandle : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
||||||
Task<IFontHandle> WaitAsync();
|
Task<IFontHandle> WaitAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for <see cref="Available"/> to become <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
||||||
|
Task<IFontHandle> WaitAsync(CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -238,12 +238,17 @@ internal abstract class FontHandle : IFontHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task<IFontHandle> WaitAsync()
|
public Task<IFontHandle> WaitAsync() => this.WaitAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Task<IFontHandle> WaitAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (this.Available)
|
if (this.Available)
|
||||||
return Task.FromResult<IFontHandle>(this);
|
return Task.FromResult<IFontHandle>(this);
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<IFontHandle>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource<IFontHandle>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
cancellationToken.Register(() => tcs.TrySetCanceled());
|
||||||
|
|
||||||
this.ImFontChanged += OnImFontChanged;
|
this.ImFontChanged += OnImFontChanged;
|
||||||
this.Disposed += OnDisposed;
|
this.Disposed += OnDisposed;
|
||||||
if (this.Available)
|
if (this.Available)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Textures.Internal;
|
||||||
|
|
||||||
|
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||||
|
internal sealed partial class TextureManager
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly SeStringRenderer seStringRenderer = Service<SeStringRenderer>.Get();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||||
|
ReadOnlySpan<byte> text,
|
||||||
|
scoped in SeStringDrawParams drawParams = default,
|
||||||
|
string? debugName = null)
|
||||||
|
{
|
||||||
|
ThreadSafety.AssertMainThread();
|
||||||
|
using var dd = this.seStringRenderer.CreateDrawData(text, drawParams);
|
||||||
|
var texture = this.CreateDrawListTexture(debugName ?? nameof(this.CreateTextureFromSeString));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
texture.Size = dd.Data.DisplaySize;
|
||||||
|
texture.Draw(dd.DataPtr);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
texture.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
|
@ -248,7 +249,7 @@ internal sealed partial class TextureManager
|
||||||
usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC;
|
usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC;
|
||||||
else
|
else
|
||||||
usage = D3D11_USAGE.D3D11_USAGE_DEFAULT;
|
usage = D3D11_USAGE.D3D11_USAGE_DEFAULT;
|
||||||
|
|
||||||
using var texture = this.device.CreateTexture2D(
|
using var texture = this.device.CreateTexture2D(
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
|
|
@ -283,6 +284,18 @@ internal sealed class TextureManagerPluginScoped
|
||||||
return textureWrap;
|
return textureWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||||
|
ReadOnlySpan<byte> text,
|
||||||
|
scoped in SeStringDrawParams drawParams = default,
|
||||||
|
string? debugName = null)
|
||||||
|
{
|
||||||
|
var manager = this.ManagerOrThrow;
|
||||||
|
var textureWrap = manager.CreateTextureFromSeString(text, drawParams, debugName);
|
||||||
|
manager.Blame(textureWrap, this.plugin);
|
||||||
|
return textureWrap;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
||||||
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
@ -12,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;
|
||||||
|
|
@ -150,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>
|
||||||
|
|
@ -226,6 +219,12 @@ public interface IUiBuilder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ShouldUseReducedMotion { get; }
|
bool ShouldUseReducedMotion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the user has enabled the "Enable sound effects for plugin windows" setting.<br />
|
||||||
|
/// This setting is effected by the in-game "System Sounds" option and volume.
|
||||||
|
/// </summary>
|
||||||
|
bool PluginUISoundEffectsEnabled { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an ULD file that can load textures containing multiple icons in a single texture.
|
/// Loads an ULD file that can load textures containing multiple icons in a single texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -302,8 +301,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.
|
||||||
|
|
@ -493,12 +490,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;
|
||||||
|
|
||||||
|
|
@ -575,6 +566,9 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShouldUseReducedMotion => Service<DalamudConfiguration>.Get().ReduceMotions ?? false;
|
public bool ShouldUseReducedMotion => Service<DalamudConfiguration>.Get().ReduceMotions ?? false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool PluginUISoundEffectsEnabled => Service<DalamudConfiguration>.Get().EnablePluginUISoundEffects;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -691,13 +685,14 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
||||||
FontAtlasAutoRebuildMode autoRebuildMode,
|
FontAtlasAutoRebuildMode autoRebuildMode,
|
||||||
bool isGlobalScaled = true,
|
bool isGlobalScaled = true,
|
||||||
string? debugName = null) =>
|
string? debugName = null) =>
|
||||||
this.scopedFinalizer.Add(Service<FontAtlasFactory>
|
this.scopedFinalizer.Add(
|
||||||
.Get()
|
Service<FontAtlasFactory>
|
||||||
.CreateFontAtlas(
|
.Get()
|
||||||
this.namespaceName + ":" + (debugName ?? "custom"),
|
.CreateFontAtlas(
|
||||||
autoRebuildMode,
|
this.namespaceName + ":" + (debugName ?? "custom"),
|
||||||
isGlobalScaled,
|
autoRebuildMode,
|
||||||
this.plugin));
|
isGlobalScaled,
|
||||||
|
this.plugin));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||||
|
|
@ -868,6 +863,15 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
||||||
// Note: do not dispose w; we do not own it
|
// Note: do not dispose w; we do not own it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ILockedImFont? TryLock(out string? errorMessage)
|
||||||
|
{
|
||||||
|
if (this.wrapped is { } w)
|
||||||
|
return w.TryLock(out errorMessage);
|
||||||
|
|
||||||
|
errorMessage = nameof(ObjectDisposedException);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public ILockedImFont Lock() =>
|
public ILockedImFont Lock() =>
|
||||||
this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||||
|
|
||||||
|
|
@ -876,7 +880,13 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
|
||||||
public void Pop() => this.WrappedNotDisposed.Pop();
|
public void Pop() => this.WrappedNotDisposed.Pop();
|
||||||
|
|
||||||
public Task<IFontHandle> WaitAsync() =>
|
public Task<IFontHandle> WaitAsync() =>
|
||||||
this.WrappedNotDisposed.WaitAsync().ContinueWith(_ => (IFontHandle)this);
|
this.wrapped?.WaitAsync().ContinueWith(_ => (IFontHandle)this)
|
||||||
|
?? Task.FromException<IFontHandle>(new ObjectDisposedException(nameof(FontHandleWrapper)));
|
||||||
|
|
||||||
|
public Task<IFontHandle> WaitAsync(CancellationToken cancellationToken) =>
|
||||||
|
this.wrapped?.WaitAsync(cancellationToken)
|
||||||
|
.ContinueWith(_ => (IFontHandle)this, cancellationToken)
|
||||||
|
?? Task.FromException<IFontHandle>(new ObjectDisposedException(nameof(FontHandleWrapper)));
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"{nameof(FontHandleWrapper)}({this.wrapped?.ToString() ?? "disposed"})";
|
$"{nameof(FontHandleWrapper)}({this.wrapped?.ToString() ?? "disposed"})";
|
||||||
|
|
|
||||||
92
Dalamud/Interface/Utility/BufferBackedImDrawData.cs
Normal file
92
Dalamud/Interface/Utility/BufferBackedImDrawData.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
/// <summary>Wrapper aroundx <see cref="ImDrawData"/> containing one <see cref="ImDrawList"/>.</summary>
|
||||||
|
public unsafe struct BufferBackedImDrawData : IDisposable
|
||||||
|
{
|
||||||
|
private nint buffer;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the <see cref="BufferBackedImDrawData"/> struct.</summary>
|
||||||
|
/// <param name="buffer">Address of buffer to use.</param>
|
||||||
|
private BufferBackedImDrawData(nint buffer) => this.buffer = buffer;
|
||||||
|
|
||||||
|
/// <summary>Gets the <see cref="ImDrawData"/> stored in this buffer.</summary>
|
||||||
|
public readonly ref ImDrawData Data => ref ((DataStruct*)this.buffer)->Data;
|
||||||
|
|
||||||
|
/// <summary>Gets the <see cref="ImDrawDataPtr"/> stored in this buffer.</summary>
|
||||||
|
public readonly ImDrawDataPtr DataPtr => new((ImDrawData*)Unsafe.AsPointer(ref this.Data));
|
||||||
|
|
||||||
|
/// <summary>Gets the <see cref="ImDrawList"/> stored in this buffer.</summary>
|
||||||
|
public readonly ref ImDrawList List => ref ((DataStruct*)this.buffer)->List;
|
||||||
|
|
||||||
|
/// <summary>Gets the <see cref="ImDrawListPtr"/> stored in this buffer.</summary>
|
||||||
|
public readonly ImDrawListPtr ListPtr => new((ImDrawList*)Unsafe.AsPointer(ref this.List));
|
||||||
|
|
||||||
|
/// <summary>Creates a new instance of <see cref="BufferBackedImDrawData"/>.</summary>
|
||||||
|
/// <returns>A new instance of <see cref="BufferBackedImDrawData"/>.</returns>
|
||||||
|
public static BufferBackedImDrawData Create()
|
||||||
|
{
|
||||||
|
if (ImGui.GetCurrentContext().IsNull || ImGui.GetIO().FontDefault.Handle is null)
|
||||||
|
throw new("ImGui is not ready");
|
||||||
|
|
||||||
|
var res = new BufferBackedImDrawData(Marshal.AllocHGlobal(sizeof(DataStruct)));
|
||||||
|
var ds = (DataStruct*)res.buffer;
|
||||||
|
*ds = default;
|
||||||
|
|
||||||
|
var atlas = ImGui.GetIO().Fonts;
|
||||||
|
ds->SharedData = *ImGui.GetDrawListSharedData().Handle;
|
||||||
|
ds->SharedData.TexIdCommon = atlas.Textures[atlas.TextureIndexCommon].TexID;
|
||||||
|
ds->SharedData.TexUvWhitePixel = atlas.TexUvWhitePixel;
|
||||||
|
ds->SharedData.TexUvLines = (Vector4*)Unsafe.AsPointer(ref atlas.TexUvLines[0]);
|
||||||
|
ds->SharedData.Font = ImGui.GetIO().FontDefault;
|
||||||
|
ds->SharedData.FontSize = ds->SharedData.Font->FontSize;
|
||||||
|
ds->SharedData.ClipRectFullscreen = new(
|
||||||
|
float.NegativeInfinity,
|
||||||
|
float.NegativeInfinity,
|
||||||
|
float.PositiveInfinity,
|
||||||
|
float.PositiveInfinity);
|
||||||
|
|
||||||
|
ds->List.Data = &ds->SharedData;
|
||||||
|
ds->ListPtr = &ds->List;
|
||||||
|
ds->Data.CmdLists = &ds->ListPtr;
|
||||||
|
ds->Data.CmdListsCount = 1;
|
||||||
|
ds->Data.FramebufferScale = Vector2.One;
|
||||||
|
|
||||||
|
res.ListPtr._ResetForNewFrame();
|
||||||
|
res.ListPtr.PushClipRectFullScreen();
|
||||||
|
res.ListPtr.PushTextureID(new(atlas.TextureIndexCommon));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates the statistics information stored in <see cref="DataPtr"/> from <see cref="ListPtr"/>.</summary>
|
||||||
|
public readonly void UpdateDrawDataStatistics()
|
||||||
|
{
|
||||||
|
this.Data.TotalIdxCount = this.List.IdxBuffer.Size;
|
||||||
|
this.Data.TotalVtxCount = this.List.VtxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.buffer != 0)
|
||||||
|
{
|
||||||
|
this.ListPtr._ClearFreeMemory();
|
||||||
|
Marshal.FreeHGlobal(this.buffer);
|
||||||
|
this.buffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct DataStruct
|
||||||
|
{
|
||||||
|
public ImDrawData Data;
|
||||||
|
public ImDrawList* ListPtr;
|
||||||
|
public ImDrawList List;
|
||||||
|
public ImDrawListSharedData SharedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -575,6 +575,15 @@ public static partial class ImGuiHelpers
|
||||||
public static unsafe ImFontPtr OrElse(this ImFontPtr self, ImFontPtr other) =>
|
public static unsafe ImFontPtr OrElse(this ImFontPtr self, ImFontPtr other) =>
|
||||||
self.IsNull ? other : self;
|
self.IsNull ? other : self;
|
||||||
|
|
||||||
|
/// <summary>Creates a draw data that will draw the given SeString onto it.</summary>
|
||||||
|
/// <param name="sss">SeString to render.</param>
|
||||||
|
/// <param name="style">Initial rendering style.</param>
|
||||||
|
/// <returns>A new self-contained draw data.</returns>
|
||||||
|
internal static BufferBackedImDrawData CreateDrawData(
|
||||||
|
ReadOnlySpan<byte> sss,
|
||||||
|
scoped in SeStringDrawParams style = default) =>
|
||||||
|
Service<SeStringRenderer>.Get().CreateDrawData(sss, style);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mark 4K page as used, after adding a codepoint to a font.
|
/// Mark 4K page as used, after adding a codepoint to a font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game;
|
||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -33,6 +35,14 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
this.interfaceManager.Draw += this.InterfaceManagerOnDraw;
|
this.interfaceManager.Draw += this.InterfaceManagerOnDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum ContextMenuActionType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
SaveAsFile,
|
||||||
|
CopyToClipboard,
|
||||||
|
SendToTexWidget,
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService() => this.interfaceManager.Draw -= this.InterfaceManagerOnDraw;
|
void IInternalDisposableService.DisposeService() => this.interfaceManager.Draw -= this.InterfaceManagerOnDraw;
|
||||||
|
|
||||||
|
|
@ -66,15 +76,16 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
var textureManager = await Service<TextureManager>.GetAsync();
|
var textureManager = await Service<TextureManager>.GetAsync();
|
||||||
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.Handle.Handle:X}";
|
var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.Handle.Handle:X}";
|
||||||
|
|
||||||
|
ContextMenuActionType action;
|
||||||
BitmapCodecInfo? encoder;
|
BitmapCodecInfo? encoder;
|
||||||
{
|
{
|
||||||
var first = true;
|
var first = true;
|
||||||
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
|
var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList();
|
||||||
var tcs = new TaskCompletionSource<BitmapCodecInfo?>(
|
var tcs = new TaskCompletionSource<(ContextMenuActionType Action, BitmapCodecInfo? Codec)>(
|
||||||
TaskCreationOptions.RunContinuationsAsynchronously);
|
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
Service<InterfaceManager>.Get().Draw += DrawChoices;
|
Service<InterfaceManager>.Get().Draw += DrawChoices;
|
||||||
|
|
||||||
encoder = await tcs.Task;
|
(action, encoder) = await tcs.Task;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "This shall not escape")]
|
[SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "This shall not escape")]
|
||||||
void DrawChoices()
|
void DrawChoices()
|
||||||
|
|
@ -98,13 +109,20 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Copy"u8))
|
if (ImGui.Selectable("Copy"u8))
|
||||||
tcs.TrySetResult(null);
|
tcs.TrySetResult((ContextMenuActionType.CopyToClipboard, null));
|
||||||
|
if (ImGui.Selectable("Send to TexWidget"u8))
|
||||||
|
tcs.TrySetResult((ContextMenuActionType.SendToTexWidget, null));
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
foreach (var encoder2 in encoders)
|
foreach (var encoder2 in encoders)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(encoder2.Name))
|
if (ImGui.Selectable(encoder2.Name))
|
||||||
tcs.TrySetResult(encoder2);
|
tcs.TrySetResult((ContextMenuActionType.SaveAsFile, encoder2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
const float previewImageWidth = 320;
|
const float previewImageWidth = 320;
|
||||||
var size = textureWrap.Size;
|
var size = textureWrap.Size;
|
||||||
if (size.X > previewImageWidth)
|
if (size.X > previewImageWidth)
|
||||||
|
|
@ -120,50 +138,68 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder is null)
|
switch (action)
|
||||||
{
|
{
|
||||||
isCopy = true;
|
case ContextMenuActionType.CopyToClipboard:
|
||||||
await textureManager.CopyToClipboardAsync(textureWrap, name, true);
|
isCopy = true;
|
||||||
}
|
await textureManager.CopyToClipboardAsync(textureWrap, name, true);
|
||||||
else
|
break;
|
||||||
{
|
|
||||||
var props = new Dictionary<string, object>();
|
|
||||||
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
|
||||||
props["CompressionQuality"] = 1.0f;
|
|
||||||
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
|
||||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
|
||||||
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
|
||||||
props["ImageQuality"] = 1.0f;
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
case ContextMenuActionType.SendToTexWidget:
|
||||||
this.fileDialogManager.SaveFileDialog(
|
|
||||||
"Save texture...",
|
|
||||||
$"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
|
|
||||||
name + encoder.Extensions.First(),
|
|
||||||
encoder.Extensions.First(),
|
|
||||||
(ok, path2) =>
|
|
||||||
{
|
|
||||||
if (!ok)
|
|
||||||
tcs.SetCanceled();
|
|
||||||
else
|
|
||||||
tcs.SetResult(path2);
|
|
||||||
});
|
|
||||||
var path = await tcs.Task.ConfigureAwait(false);
|
|
||||||
|
|
||||||
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
|
||||||
|
|
||||||
var notif = Service<NotificationManager>.Get().AddNotification(
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Content = $"File saved to: {path}",
|
|
||||||
Title = initiatorName,
|
|
||||||
Type = NotificationType.Success,
|
|
||||||
});
|
|
||||||
notif.Click += n =>
|
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
var framework = await Service<Framework>.GetAsync();
|
||||||
n.Notification.DismissNow();
|
var dalamudInterface = await Service<DalamudInterface>.GetAsync();
|
||||||
};
|
await framework.RunOnFrameworkThread(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var texWidget = dalamudInterface.GetDataWindowWidget<TexWidget>();
|
||||||
|
dalamudInterface.SetDataWindowWidget(texWidget);
|
||||||
|
texWidget.AddTexture(Task.FromResult(textureWrap.CreateWrapSharingLowLevelResource()));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ContextMenuActionType.SaveAsFile when encoder is not null:
|
||||||
|
{
|
||||||
|
var props = new Dictionary<string, object>();
|
||||||
|
if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
|
||||||
|
props["CompressionQuality"] = 1.0f;
|
||||||
|
else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
|
||||||
|
encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
|
||||||
|
encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
|
||||||
|
props["ImageQuality"] = 1.0f;
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
this.fileDialogManager.SaveFileDialog(
|
||||||
|
"Save texture...",
|
||||||
|
$"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
|
||||||
|
name + encoder.Extensions.First(),
|
||||||
|
encoder.Extensions.First(),
|
||||||
|
(ok, path2) =>
|
||||||
|
{
|
||||||
|
if (!ok)
|
||||||
|
tcs.SetCanceled();
|
||||||
|
else
|
||||||
|
tcs.SetResult(path2);
|
||||||
|
});
|
||||||
|
var path = await tcs.Task.ConfigureAwait(false);
|
||||||
|
|
||||||
|
await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props);
|
||||||
|
|
||||||
|
var notif = Service<NotificationManager>.Get().AddNotification(
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Content = $"File saved to: {path}",
|
||||||
|
Title = initiatorName,
|
||||||
|
Type = NotificationType.Success,
|
||||||
|
});
|
||||||
|
notif.Click += n =>
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||||
|
n.Notification.DismissNow();
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -19,10 +16,13 @@ using Dalamud.Interface.Utility.Internal;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing.Persistence;
|
using Dalamud.Interface.Windowing.Persistence;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Windowing;
|
namespace Dalamud.Interface.Windowing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -31,11 +31,15 @@ namespace Dalamud.Interface.Windowing;
|
||||||
public abstract class Window
|
public abstract class Window
|
||||||
{
|
{
|
||||||
private const float FadeInOutTime = 0.072f;
|
private const float FadeInOutTime = 0.072f;
|
||||||
|
private const string AdditionsPopupName = "WindowSystemContextActions";
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("WindowSystem");
|
private static readonly ModuleLog Log = new("WindowSystem");
|
||||||
|
|
||||||
private static bool wasEscPressedLastFrame = false;
|
private static bool wasEscPressedLastFrame = false;
|
||||||
|
|
||||||
|
private readonly TitleBarButton additionsButton;
|
||||||
|
private readonly List<TitleBarButton> allButtons = [];
|
||||||
|
|
||||||
private bool internalLastIsOpen = false;
|
private bool internalLastIsOpen = false;
|
||||||
private bool internalIsOpen = false;
|
private bool internalIsOpen = false;
|
||||||
private bool internalIsPinned = false;
|
private bool internalIsPinned = false;
|
||||||
|
|
@ -72,6 +76,20 @@ public abstract class Window
|
||||||
this.WindowName = name;
|
this.WindowName = name;
|
||||||
this.Flags = flags;
|
this.Flags = flags;
|
||||||
this.ForceMainWindow = forceMainWindow;
|
this.ForceMainWindow = forceMainWindow;
|
||||||
|
|
||||||
|
this.additionsButton = new()
|
||||||
|
{
|
||||||
|
Icon = FontAwesomeIcon.Bars,
|
||||||
|
IconOffset = new Vector2(2.5f, 1),
|
||||||
|
Click = _ =>
|
||||||
|
{
|
||||||
|
this.internalIsClickthrough = false;
|
||||||
|
this.presetDirty = false;
|
||||||
|
ImGui.OpenPopup(AdditionsPopupName);
|
||||||
|
},
|
||||||
|
Priority = int.MinValue,
|
||||||
|
AvailableClickthrough = true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -425,8 +443,17 @@ public abstract class Window
|
||||||
UIGlobals.PlaySoundEffect(this.OnOpenSfxId);
|
UIGlobals.PlaySoundEffect(this.OnOpenSfxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.PreDraw();
|
var isErrorStylePushed = false;
|
||||||
this.ApplyConditionals();
|
if (!this.hasError)
|
||||||
|
{
|
||||||
|
this.PreDraw();
|
||||||
|
this.ApplyConditionals();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Style.StyleModelV1.DalamudStandard.Push();
|
||||||
|
isErrorStylePushed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.ForceMainWindow)
|
if (this.ForceMainWindow)
|
||||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||||
|
|
@ -448,10 +475,22 @@ public abstract class Window
|
||||||
var flags = this.Flags;
|
var flags = this.Flags;
|
||||||
|
|
||||||
if (this.internalIsPinned || this.internalIsClickthrough)
|
if (this.internalIsPinned || this.internalIsClickthrough)
|
||||||
|
{
|
||||||
flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
|
flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.internalIsClickthrough)
|
if (this.internalIsClickthrough)
|
||||||
|
{
|
||||||
flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
|
flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an error, reset all flags to default, and unlock window size.
|
||||||
|
if (this.hasError)
|
||||||
|
{
|
||||||
|
flags = ImGuiWindowFlags.None;
|
||||||
|
ImGui.SetNextWindowCollapsed(false, ImGuiCond.Once);
|
||||||
|
ImGui.SetNextWindowSizeConstraints(Vector2.Zero, Vector2.PositiveInfinity);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags))
|
if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags))
|
||||||
{
|
{
|
||||||
|
|
@ -461,14 +500,12 @@ public abstract class Window
|
||||||
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
|
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not supported yet on non-main viewports
|
if (ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
||||||
if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) &&
|
|
||||||
ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
|
||||||
{
|
{
|
||||||
this.internalAlpha = null;
|
if ((flags & ImGuiWindowFlags.NoInputs) == ImGuiWindowFlags.NoInputs)
|
||||||
this.internalIsPinned = false;
|
ImGui.GetWindowViewport().Flags |= ImGuiViewportFlags.NoInputs;
|
||||||
this.internalIsClickthrough = false;
|
else
|
||||||
this.presetDirty = true;
|
ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasError)
|
if (this.hasError)
|
||||||
|
|
@ -492,7 +529,6 @@ public abstract class Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const string additionsPopupName = "WindowSystemContextActions";
|
|
||||||
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
|
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
|
||||||
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
|
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
|
||||||
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
||||||
|
|
@ -503,13 +539,8 @@ public abstract class Window
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
||||||
|
|
||||||
if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove))
|
if (ImGui.BeginPopup(AdditionsPopupName, ImGuiWindowFlags.NoMove))
|
||||||
{
|
{
|
||||||
var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
|
|
||||||
|
|
||||||
if (!isAvailable)
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
|
|
||||||
if (this.internalIsClickthrough)
|
if (this.internalIsClickthrough)
|
||||||
ImGui.BeginDisabled();
|
ImGui.BeginDisabled();
|
||||||
|
|
||||||
|
|
@ -557,21 +588,11 @@ public abstract class Window
|
||||||
this.presetDirty = true;
|
this.presetDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAvailable)
|
ImGui.TextColored(
|
||||||
{
|
ImGuiColors.DalamudGrey,
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey,
|
Loc.Localize(
|
||||||
Loc.Localize("WindowSystemContextActionClickthroughDisclaimer",
|
"WindowSystemContextActionClickthroughDisclaimer",
|
||||||
"Open this menu again by clicking the three dashes to disable clickthrough."));
|
"Open this menu again by clicking the three dashes to disable clickthrough."));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey,
|
|
||||||
Loc.Localize("WindowSystemContextActionViewportDisclaimer",
|
|
||||||
"These features are only available if this window is inside the game window."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAvailable)
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
|
if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
|
||||||
printWindow = true;
|
printWindow = true;
|
||||||
|
|
@ -582,34 +603,15 @@ public abstract class Window
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe
|
if (flagsApplicableForTitleBarIcons)
|
||||||
{
|
{
|
||||||
var window = ImGuiP.GetCurrentWindow();
|
this.allButtons.Clear();
|
||||||
|
this.allButtons.EnsureCapacity(this.TitleBarButtons.Count + 1);
|
||||||
ImRect outRect;
|
this.allButtons.AddRange(this.TitleBarButtons);
|
||||||
ImGuiP.TitleBarRect(&outRect, window);
|
if (showAdditions)
|
||||||
|
this.allButtons.Add(this.additionsButton);
|
||||||
var additionsButton = new TitleBarButton
|
this.allButtons.Sort(static (a, b) => b.Priority - a.Priority);
|
||||||
{
|
this.DrawTitleBarButtons();
|
||||||
Icon = FontAwesomeIcon.Bars,
|
|
||||||
IconOffset = new Vector2(2.5f, 1),
|
|
||||||
Click = _ =>
|
|
||||||
{
|
|
||||||
this.internalIsClickthrough = false;
|
|
||||||
this.presetDirty = false;
|
|
||||||
ImGui.OpenPopup(additionsPopupName);
|
|
||||||
},
|
|
||||||
Priority = int.MinValue,
|
|
||||||
AvailableClickthrough = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (flagsApplicableForTitleBarIcons)
|
|
||||||
{
|
|
||||||
this.DrawTitleBarButtons(window, flags, outRect,
|
|
||||||
showAdditions
|
|
||||||
? this.TitleBarButtons.Append(additionsButton)
|
|
||||||
: this.TitleBarButtons);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasFocused)
|
if (wasFocused)
|
||||||
|
|
@ -670,7 +672,17 @@ public abstract class Window
|
||||||
Task.FromResult<IDalamudTextureWrap>(tex));
|
Task.FromResult<IDalamudTextureWrap>(tex));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.PostDraw();
|
if (!this.hasError)
|
||||||
|
{
|
||||||
|
this.PostDraw();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isErrorStylePushed)
|
||||||
|
{
|
||||||
|
Style.StyleModelV1.DalamudStandard.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.PostHandlePreset(persistence);
|
this.PostHandlePreset(persistence);
|
||||||
|
|
||||||
|
|
@ -766,8 +778,11 @@ public abstract class Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable<TitleBarButton> buttons)
|
private unsafe void DrawTitleBarButtons()
|
||||||
{
|
{
|
||||||
|
var window = ImGuiP.GetCurrentWindow();
|
||||||
|
var flags = window.Flags;
|
||||||
|
var titleBarRect = window.TitleBarRect();
|
||||||
ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
|
ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
|
||||||
|
|
||||||
var style = ImGui.GetStyle();
|
var style = ImGui.GetStyle();
|
||||||
|
|
@ -802,26 +817,22 @@ public abstract class Window
|
||||||
var max = pos + new Vector2(fontSize, fontSize);
|
var max = pos + new Vector2(fontSize, fontSize);
|
||||||
ImRect bb = new(pos, max);
|
ImRect bb = new(pos, max);
|
||||||
var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
|
var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
|
||||||
bool hovered, held;
|
bool hovered, held, pressed;
|
||||||
var pressed = false;
|
|
||||||
|
|
||||||
if (this.internalIsClickthrough)
|
if (this.internalIsClickthrough)
|
||||||
{
|
{
|
||||||
hovered = false;
|
|
||||||
held = false;
|
|
||||||
|
|
||||||
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
|
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
|
||||||
if (ImGui.IsMouseHoveringRect(pos, max))
|
var pad = ImGui.GetStyle().TouchExtraPadding;
|
||||||
{
|
var rect = new ImRect(pos - pad, max + pad);
|
||||||
hovered = true;
|
hovered = rect.Contains(ImGui.GetMousePos());
|
||||||
|
|
||||||
// We can't use ImGui native functions here, because they don't work with clickthrough
|
// Temporarily enable inputs
|
||||||
if ((Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
|
// This will be reset on next frame, and then enabled again if it is still being hovered
|
||||||
{
|
if (hovered && ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
||||||
held = true;
|
ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
|
||||||
pressed = true;
|
|
||||||
}
|
// We can't use ImGui native functions here, because they don't work with clickthrough
|
||||||
}
|
pressed = held = hovered && (GetKeyState(VK.VK_LBUTTON) & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -850,7 +861,7 @@ public abstract class Window
|
||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var button in buttons.OrderBy(x => x.Priority))
|
foreach (var button in this.allButtons)
|
||||||
{
|
{
|
||||||
if (this.internalIsClickthrough && !button.AvailableClickthrough)
|
if (this.internalIsClickthrough && !button.AvailableClickthrough)
|
||||||
return;
|
return;
|
||||||
|
|
@ -897,7 +908,7 @@ public abstract class Window
|
||||||
private void DrawErrorMessage()
|
private void DrawErrorMessage()
|
||||||
{
|
{
|
||||||
// TODO: Once window systems are services, offer to reload the plugin
|
// TODO: Once window systems are services, offer to reload the plugin
|
||||||
ImGui.TextColoredWrapped(ImGuiColors.DalamudRed,Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
|
ImGui.TextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
|
|
@ -1004,7 +1015,7 @@ public abstract class Window
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called when the button is clicked.
|
/// Gets or sets an action that is called when the button is clicked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<ImGuiMouseButton> Click { get; set; }
|
public Action<ImGuiMouseButton>? Click { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the priority the button shall be shown in.
|
/// Gets or sets the priority the button shall be shown in.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||||
|
"useSafeHandles": false,
|
||||||
"allowMarshaling": false
|
"allowMarshaling": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ public interface IPlayerState : IDalamudService
|
||||||
bool IsLevelSynced { get; }
|
bool IsLevelSynced { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the effective level of the local character.
|
/// Gets the effective level of the local character, taking level sync into account.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
short EffectiveLevel { get; }
|
short EffectiveLevel { get; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
using Dalamud.Interface.Textures;
|
using Dalamud.Interface.Textures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
|
@ -186,6 +187,17 @@ public interface ITextureProvider : IDalamudService
|
||||||
string? debugName = null,
|
string? debugName = null,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>Creates a texture by drawing a SeString onto it.</summary>
|
||||||
|
/// <param name="text">SeString to render.</param>
|
||||||
|
/// <param name="drawParams">Parameters for drawing.</param>
|
||||||
|
/// <param name="debugName">Name for debug display purposes.</param>
|
||||||
|
/// <returns>The new texture.</returns>
|
||||||
|
/// <remarks>Can be only be used from the main thread.</remarks>
|
||||||
|
public IDalamudTextureWrap CreateTextureFromSeString(
|
||||||
|
ReadOnlySpan<byte> text,
|
||||||
|
scoped in SeStringDrawParams drawParams = default,
|
||||||
|
string? debugName = null);
|
||||||
|
|
||||||
/// <summary>Gets the supported bitmap decoders.</summary>
|
/// <summary>Gets the supported bitmap decoders.</summary>
|
||||||
/// <returns>The supported bitmap decoders.</returns>
|
/// <returns>The supported bitmap decoders.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -12,11 +14,11 @@ namespace Dalamud;
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class SafeMemory
|
public static class SafeMemory
|
||||||
{
|
{
|
||||||
private static readonly SafeHandle Handle;
|
private static readonly HANDLE Handle;
|
||||||
|
|
||||||
static SafeMemory()
|
static SafeMemory()
|
||||||
{
|
{
|
||||||
Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
Handle = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -28,6 +30,12 @@ public static class SafeMemory
|
||||||
/// <returns>Whether the read succeeded.</returns>
|
/// <returns>Whether the read succeeded.</returns>
|
||||||
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
|
||||||
{
|
{
|
||||||
|
if (Handle.IsNull)
|
||||||
|
{
|
||||||
|
buffer = [];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
buffer = new byte[count <= 0 ? 0 : count];
|
buffer = new byte[count <= 0 ? 0 : count];
|
||||||
fixed (byte* p = buffer)
|
fixed (byte* p = buffer)
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +62,9 @@ public static class SafeMemory
|
||||||
/// <returns>Whether the write succeeded.</returns>
|
/// <returns>Whether the write succeeded.</returns>
|
||||||
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
|
||||||
{
|
{
|
||||||
|
if (Handle.IsNull)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (buffer.Length == 0)
|
if (buffer.Length == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()) ??
|
||||||
|
|
|
||||||
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.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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")]
|
||||||
|
|
|
||||||
|
|
@ -158,16 +158,6 @@ public static partial class Util
|
||||||
return branchInternal = gitBranch;
|
return branchInternal = gitBranch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
|
|
||||||
/// downloaded from webservices.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The name of the track, or null.</returns>
|
|
||||||
internal static string? GetActiveTrack()
|
|
||||||
{
|
|
||||||
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="DescribeAddress(nint)"/>
|
/// <inheritdoc cref="DescribeAddress(nint)"/>
|
||||||
public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p);
|
public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p);
|
||||||
|
|
||||||
|
|
@ -703,6 +693,16 @@ public static partial class Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
|
||||||
|
/// downloaded from webservices.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The name of the track, or null.</returns>
|
||||||
|
internal static string? GetActiveTrack()
|
||||||
|
{
|
||||||
|
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a random, inoffensive, human-friendly string.
|
/// Gets a random, inoffensive, human-friendly string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Bindings.ImGui;
|
namespace Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A structure representing a dynamic array for unmanaged types.
|
||||||
|
/// </summary>
|
||||||
public unsafe struct ImVector
|
public unsafe struct ImVector
|
||||||
{
|
{
|
||||||
public readonly int Size;
|
public readonly int Size;
|
||||||
|
|
@ -15,23 +20,23 @@ public unsafe struct ImVector
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T Ref<T>(int index)
|
public readonly ref T Ref<T>(int index) => ref Unsafe.AsRef<T>((byte*)this.Data + (index * Unsafe.SizeOf<T>()));
|
||||||
{
|
|
||||||
return ref Unsafe.AsRef<T>((byte*)Data + index * Unsafe.SizeOf<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntPtr Address<T>(int index)
|
public readonly nint Address<T>(int index) => (nint)((byte*)this.Data + (index * Unsafe.SizeOf<T>()));
|
||||||
{
|
|
||||||
return (IntPtr)((byte*)Data + index * Unsafe.SizeOf<T>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A structure representing a dynamic array for unmanaged types.
|
/// A structure representing a dynamic array for unmanaged types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of elements in the vector, must be unmanaged.</typeparam>
|
/// <typeparam name="T">The type of elements in the vector, must be unmanaged.</typeparam>
|
||||||
public unsafe struct ImVector<T> where T : unmanaged
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public unsafe struct ImVector<T> : IEnumerable<T>
|
||||||
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
|
private int size;
|
||||||
|
private int capacity;
|
||||||
|
private T* data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ImVector{T}"/> struct with the specified size, capacity, and data pointer.
|
/// Initializes a new instance of the <see cref="ImVector{T}"/> struct with the specified size, capacity, and data pointer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -45,11 +50,6 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int size;
|
|
||||||
private int capacity;
|
|
||||||
private unsafe T* data;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the element at the specified index.
|
/// Gets or sets the element at the specified index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -58,80 +58,72 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception>
|
/// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception>
|
||||||
public T this[int index]
|
public T this[int index]
|
||||||
{
|
{
|
||||||
get
|
readonly get
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= size)
|
if (index < 0 || index >= this.size)
|
||||||
{
|
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
}
|
return this.data[index];
|
||||||
return data[index];
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= size)
|
if (index < 0 || index >= this.size)
|
||||||
{
|
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
}
|
this.data[index] = value;
|
||||||
data[index] = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the first element of the vector.
|
/// Gets a pointer to the first element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Data => data;
|
public readonly T* Data => this.data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the first element of the vector.
|
/// Gets a pointer to the first element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Front => data;
|
public readonly T* Front => this.data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the last element of the vector.
|
/// Gets a pointer to the last element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Back => size > 0 ? data + size - 1 : null;
|
public readonly T* Back => this.size > 0 ? this.data + this.size - 1 : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the capacity of the vector.
|
/// Gets or sets the capacity of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Capacity
|
public int Capacity
|
||||||
{
|
{
|
||||||
readonly get => capacity;
|
readonly get => this.capacity;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (capacity == value)
|
ArgumentOutOfRangeException.ThrowIfLessThan(value, this.size, nameof(Capacity));
|
||||||
{
|
if (this.capacity == value)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (data == null)
|
if (this.data == null)
|
||||||
{
|
{
|
||||||
data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
this.data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int newSize = Math.Min(size, value);
|
var newSize = Math.Min(this.size, value);
|
||||||
T* newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
var newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
||||||
Buffer.MemoryCopy(data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T)));
|
Buffer.MemoryCopy(this.data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T)));
|
||||||
ImGui.MemFree(data);
|
ImGui.MemFree(this.data);
|
||||||
data = newData;
|
this.data = newData;
|
||||||
size = newSize;
|
this.size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
capacity = value;
|
this.capacity = value;
|
||||||
|
|
||||||
// Clear the rest of the data
|
// Clear the rest of the data
|
||||||
for (int i = size; i < capacity; i++)
|
new Span<T>(this.data + this.size, this.capacity - this.size).Clear();
|
||||||
{
|
|
||||||
data[i] = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of elements in the vector.
|
/// Gets the number of elements in the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int Size => size;
|
public readonly int Size => this.size;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Grows the capacity of the vector to at least the specified value.
|
/// Grows the capacity of the vector to at least the specified value.
|
||||||
|
|
@ -139,10 +131,8 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <param name="newCapacity">The new capacity.</param>
|
/// <param name="newCapacity">The new capacity.</param>
|
||||||
public void Grow(int newCapacity)
|
public void Grow(int newCapacity)
|
||||||
{
|
{
|
||||||
if (newCapacity > capacity)
|
var newCapacity2 = this.capacity > 0 ? this.capacity + (this.capacity / 2) : 8;
|
||||||
{
|
this.Capacity = newCapacity2 > newCapacity ? newCapacity2 : newCapacity;
|
||||||
Capacity = newCapacity * 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -151,10 +141,8 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <param name="size">The minimum capacity required.</param>
|
/// <param name="size">The minimum capacity required.</param>
|
||||||
public void EnsureCapacity(int size)
|
public void EnsureCapacity(int size)
|
||||||
{
|
{
|
||||||
if (size > capacity)
|
if (size > this.capacity)
|
||||||
{
|
|
||||||
Grow(size);
|
Grow(size);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -164,25 +152,46 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize)
|
||||||
{
|
{
|
||||||
EnsureCapacity(newSize);
|
EnsureCapacity(newSize);
|
||||||
size = newSize;
|
this.size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears all elements from the vector.
|
/// Clears all elements from the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear() => this.size = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an element to the end of the vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to add.</param>
|
||||||
|
[OverloadResolutionPriority(1)]
|
||||||
|
public void PushBack(T value)
|
||||||
{
|
{
|
||||||
size = 0;
|
this.EnsureCapacity(this.size + 1);
|
||||||
|
this.data[this.size++] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds an element to the end of the vector.
|
/// Adds an element to the end of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value to add.</param>
|
/// <param name="value">The value to add.</param>
|
||||||
public void PushBack(T value)
|
[OverloadResolutionPriority(2)]
|
||||||
|
public void PushBack(in T value)
|
||||||
{
|
{
|
||||||
EnsureCapacity(size + 1);
|
EnsureCapacity(this.size + 1);
|
||||||
data[size++] = value;
|
this.data[this.size++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an element to the front of the vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to add.</param>
|
||||||
|
public void PushFront(in T value)
|
||||||
|
{
|
||||||
|
if (this.size == 0)
|
||||||
|
this.PushBack(value);
|
||||||
|
else
|
||||||
|
this.Insert(0, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -190,48 +199,126 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PopBack()
|
public void PopBack()
|
||||||
{
|
{
|
||||||
if (size > 0)
|
if (this.size > 0)
|
||||||
{
|
{
|
||||||
size--;
|
this.size--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ref T Insert(int index, in T v) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index, nameof(index));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, this.size, nameof(index));
|
||||||
|
this.EnsureCapacity(this.size + 1);
|
||||||
|
if (index < this.size)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(
|
||||||
|
this.data + index,
|
||||||
|
this.data + index + 1,
|
||||||
|
(this.size - index) * sizeof(T),
|
||||||
|
(this.size - index) * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data[index] = v;
|
||||||
|
this.size++;
|
||||||
|
return ref this.data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<T> InsertRange(int index, ReadOnlySpan<T> v)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index, nameof(index));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, this.size, nameof(index));
|
||||||
|
this.EnsureCapacity(this.size + v.Length);
|
||||||
|
if (index < this.size)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(
|
||||||
|
this.data + index,
|
||||||
|
this.data + index + v.Length,
|
||||||
|
(this.size - index) * sizeof(T),
|
||||||
|
(this.size - index) * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dstSpan = new Span<T>(this.data + index, v.Length);
|
||||||
|
v.CopyTo(new(this.data + index, v.Length));
|
||||||
|
this.size += v.Length;
|
||||||
|
return dstSpan;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees the memory allocated for the vector.
|
/// Frees the memory allocated for the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Free()
|
public void Free()
|
||||||
{
|
{
|
||||||
if (data != null)
|
if (this.data != null)
|
||||||
{
|
{
|
||||||
ImGui.MemFree(data);
|
ImGui.MemFree(this.data);
|
||||||
data = null;
|
this.data = null;
|
||||||
size = 0;
|
this.size = 0;
|
||||||
capacity = 0;
|
this.capacity = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T Ref(int index)
|
public readonly ref T Ref(int index)
|
||||||
{
|
{
|
||||||
return ref Unsafe.AsRef<T>((byte*)Data + index * Unsafe.SizeOf<T>());
|
return ref Unsafe.AsRef<T>((byte*)Data + (index * Unsafe.SizeOf<T>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref TCast Ref<TCast>(int index)
|
public readonly ref TCast Ref<TCast>(int index)
|
||||||
{
|
{
|
||||||
return ref Unsafe.AsRef<TCast>((byte*)Data + index * Unsafe.SizeOf<TCast>());
|
return ref Unsafe.AsRef<TCast>((byte*)Data + (index * Unsafe.SizeOf<TCast>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void* Address(int index)
|
public readonly void* Address(int index)
|
||||||
{
|
{
|
||||||
return (byte*)Data + index * Unsafe.SizeOf<T>();
|
return (byte*)Data + (index * Unsafe.SizeOf<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void* Address<TCast>(int index)
|
public readonly void* Address<TCast>(int index)
|
||||||
{
|
{
|
||||||
return (byte*)Data + index * Unsafe.SizeOf<TCast>();
|
return (byte*)Data + (index * Unsafe.SizeOf<TCast>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImVector* ToUntyped()
|
public readonly ImVector* ToUntyped()
|
||||||
{
|
{
|
||||||
return (ImVector*)Unsafe.AsPointer(ref this);
|
return (ImVector*)Unsafe.AsPointer(ref Unsafe.AsRef(in this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Span<T> AsSpan() => new(this.data, this.size);
|
||||||
|
|
||||||
|
public readonly Enumerator GetEnumerator() => new(this.data, this.data + this.size);
|
||||||
|
|
||||||
|
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
public struct Enumerator(T* begin, T* end) : IEnumerator<T>, IEnumerable<T>
|
||||||
|
{
|
||||||
|
private T* current = null;
|
||||||
|
|
||||||
|
public readonly ref T Current => ref *this.current;
|
||||||
|
|
||||||
|
readonly T IEnumerator<T>.Current => this.Current;
|
||||||
|
|
||||||
|
readonly object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var next = this.current == null ? begin : this.current + 1;
|
||||||
|
if (next == end)
|
||||||
|
return false;
|
||||||
|
this.current = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() => this.current = null;
|
||||||
|
|
||||||
|
public readonly Enumerator GetEnumerator() => new(begin, end);
|
||||||
|
|
||||||
|
readonly void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit e5f586630ef06fa48d5dc0d8c0fa679323093c77
|
Subproject commit e5dedba42a3fea8f050ea54ac583a5874bf51c6f
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 27c8565f631b004c3266373890e41ecc627f775b
|
Subproject commit bc327296758d57d3bdc963cb6ce71dd5b0c7e54c
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
|
||||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
|
|
||||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/</DalamudLibPath>
|
|
||||||
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<Import Project="$(DalamudLibPath)/targets/Dalamud.Plugin.targets"/>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
|
||||||
<Platforms>x64</Platforms>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
|
||||||
<AssemblySearchPaths>$(AssemblySearchPaths);$(DalamudLibPath)</AssemblySearchPaths>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="DalamudPackager" Version="11.0.0" />
|
|
||||||
<Reference Include="FFXIVClientStructs" Private="false" />
|
|
||||||
<Reference Include="Newtonsoft.Json" Private="false" />
|
|
||||||
<Reference Include="Dalamud" Private="false" />
|
|
||||||
<Reference Include="ImGui.NET" Private="false" />
|
|
||||||
<Reference Include="ImGuiScene" Private="false" />
|
|
||||||
<Reference Include="Lumina" Private="false" />
|
|
||||||
<Reference Include="Lumina.Excel" Private="false" />
|
|
||||||
<Reference Include="Serilog" Private="false" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Target Name="Message" BeforeTargets="BeforeBuild">
|
|
||||||
<Message Text="Dalamud.Plugin: root at $(DalamudLibPath)" Importance="high" />
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<Target Name="DeprecationNotice" BeforeTargets="BeforeBuild">
|
|
||||||
<Warning Text="Using the targets file to include the Dalamud SDK is no longer recommended. Please upgrade to Dalamud.NET.Sdk - learn more here: https://dalamud.dev/plugin-development/how-tos/v12-sdk-migration" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue