mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-14 11:57:42 +01:00
Compare commits
No commits in common. "master" and "11.0.2.0" have entirely different histories.
1544 changed files with 18626 additions and 1162543 deletions
|
|
@ -6,7 +6,6 @@ charset = utf-8
|
|||
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# 4 space indentation
|
||||
indent_style = space
|
||||
|
|
|
|||
8
.gitattributes
vendored
8
.gitattributes
vendored
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
|
|
@ -46,9 +46,9 @@
|
|||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
|
|
@ -61,5 +61,3 @@
|
|||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
|
||||
imgui/**/Generated/**/*.cs linguist-generated=true
|
||||
|
|
|
|||
308
.github/generate_changelog.py
vendored
308
.github/generate_changelog.py
vendored
|
|
@ -1,308 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate a changelog from git commits between the last two tags and post to Discord webhook.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
from typing import List, Tuple, Optional, Dict, Any
|
||||
|
||||
|
||||
def run_git_command(args: List[str]) -> str:
|
||||
"""Run a git command and return its output."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git"] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
return result.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Git command failed: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_last_two_tags() -> Tuple[str, str]:
|
||||
"""Get the latest two git tags."""
|
||||
tags = run_git_command(["tag", "--sort=-version:refname"])
|
||||
tag_list = [t for t in tags.split("\n") if t]
|
||||
|
||||
# Filter out old tags that start with 'v' (old versioning scheme)
|
||||
tag_list = [t for t in tag_list if not t.startswith('v')]
|
||||
|
||||
if len(tag_list) < 2:
|
||||
print("Error: Need at least 2 tags in the repository", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return tag_list[0], tag_list[1]
|
||||
|
||||
|
||||
def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]:
|
||||
"""Get the commit hash of a submodule at a specific tag."""
|
||||
try:
|
||||
# Get the submodule commit at the specified tag
|
||||
result = run_git_command(["ls-tree", tag, submodule_path])
|
||||
# Format is: "<mode> commit <hash>\t<path>"
|
||||
parts = result.split()
|
||||
if len(parts) >= 3 and parts[1] == "commit":
|
||||
return parts[2]
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def get_repo_info() -> Tuple[str, str]:
|
||||
"""Get repository owner and name from git remote."""
|
||||
try:
|
||||
remote_url = run_git_command(["config", "--get", "remote.origin.url"])
|
||||
|
||||
# Handle both HTTPS and SSH URLs
|
||||
# SSH: git@github.com:owner/repo.git
|
||||
# HTTPS: https://github.com/owner/repo.git
|
||||
match = re.search(r'github\.com[:/](.+?)/(.+?)(?:\.git)?$', remote_url)
|
||||
if match:
|
||||
owner = match.group(1)
|
||||
repo = match.group(2)
|
||||
return owner, repo
|
||||
else:
|
||||
print("Error: Could not parse GitHub repository from remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except:
|
||||
print("Error: Could not get git remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_commits_between_tags(tag1: str, tag2: str) -> List[str]:
|
||||
"""Get commit SHAs between two tags."""
|
||||
log_output = run_git_command([
|
||||
"log",
|
||||
f"{tag2}..{tag1}",
|
||||
"--format=%H"
|
||||
])
|
||||
|
||||
commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()]
|
||||
return commits
|
||||
|
||||
|
||||
def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get PR information for a commit using GitHub API."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}/pulls"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
prs = response.json()
|
||||
|
||||
if prs and len(prs) > 0:
|
||||
# Return the first PR (most relevant one)
|
||||
pr = prs[0]
|
||||
return {
|
||||
"number": pr["number"],
|
||||
"title": pr["title"],
|
||||
"author": pr["user"]["login"],
|
||||
"url": pr["html_url"]
|
||||
}
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
# Commit might not be associated with a PR
|
||||
return None
|
||||
elif e.response.status_code == 403:
|
||||
print("Warning: GitHub API rate limit exceeded. Consider providing a token.", file=sys.stderr)
|
||||
return None
|
||||
else:
|
||||
print(f"Warning: Failed to fetch PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Warning: Error fetching PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_prs_between_tags(tag1: str, tag2: str, owner: str, repo: str, token: str) -> List[Dict[str, Any]]:
|
||||
"""Get PRs between two tags using GitHub API."""
|
||||
commits = get_commits_between_tags(tag1, tag2)
|
||||
print(f"Found {len(commits)} commits, fetching PR information...")
|
||||
|
||||
prs = []
|
||||
seen_pr_numbers = set()
|
||||
|
||||
for i, commit_sha in enumerate(commits, 1):
|
||||
if i % 10 == 0:
|
||||
print(f"Progress: {i}/{len(commits)} commits processed...")
|
||||
|
||||
pr_info = get_pr_for_commit(commit_sha, owner, repo, token)
|
||||
if pr_info and pr_info["number"] not in seen_pr_numbers:
|
||||
seen_pr_numbers.add(pr_info["number"])
|
||||
prs.append(pr_info)
|
||||
|
||||
return prs
|
||||
|
||||
|
||||
def filter_prs(prs: List[Dict[str, Any]], ignore_patterns: List[str]) -> List[Dict[str, Any]]:
|
||||
"""Filter out PRs matching any of the ignore patterns."""
|
||||
compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns]
|
||||
|
||||
filtered = []
|
||||
for pr in prs:
|
||||
if not any(pattern.search(pr["title"]) for pattern in compiled_patterns):
|
||||
filtered.append(pr)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]],
|
||||
cs_commit_new: Optional[str], cs_commit_old: Optional[str],
|
||||
owner: str, repo: str) -> str:
|
||||
"""Generate markdown changelog."""
|
||||
# Calculate statistics
|
||||
pr_count = len(prs)
|
||||
unique_authors = len(set(pr["author"] for pr in prs))
|
||||
|
||||
changelog = f"# Dalamud Release v{version}\n\n"
|
||||
changelog += f"We just released Dalamud v{version}, which should be available to users within a few minutes. "
|
||||
changelog += f"This release includes **{pr_count} PR{'s' if pr_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n"
|
||||
changelog += f"[Click here](<https://github.com/{owner}/{repo}/compare/{prev_version}...{version}>) to see all Dalamud changes.\n\n"
|
||||
|
||||
if cs_commit_new and cs_commit_old and cs_commit_new != cs_commit_old:
|
||||
changelog += f"It ships with an updated **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
|
||||
changelog += f"[Click here](<https://github.com/aers/FFXIVClientStructs/compare/{cs_commit_old}...{cs_commit_new}>) to see all CS changes.\n"
|
||||
elif cs_commit_new:
|
||||
changelog += f"It ships with **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
|
||||
|
||||
changelog += "## Dalamud Changes\n\n"
|
||||
|
||||
for pr in prs:
|
||||
changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n"
|
||||
|
||||
return changelog
|
||||
|
||||
|
||||
def post_to_discord(webhook_url: str, content: str, version: str) -> None:
|
||||
"""Post changelog to Discord webhook as a file attachment."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
filename = f"changelog-v{version}.md"
|
||||
|
||||
# Prepare the payload
|
||||
data = {
|
||||
"content": f"Dalamud v{version} has been released!",
|
||||
"attachments": [
|
||||
{
|
||||
"id": "0",
|
||||
"filename": filename
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Prepare the files
|
||||
files = {
|
||||
"payload_json": (None, json.dumps(data)),
|
||||
"files[0]": (filename, content.encode('utf-8'), 'text/markdown')
|
||||
}
|
||||
|
||||
try:
|
||||
result = requests.post(webhook_url, files=files)
|
||||
result.raise_for_status()
|
||||
print(f"Successfully posted to Discord webhook, code {result.status_code}")
|
||||
except requests.exceptions.HTTPError as err:
|
||||
print(f"Failed to post to Discord: {err}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Failed to post to Discord: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate changelog from git commits and post to Discord webhook"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--webhook-url",
|
||||
required=True,
|
||||
help="Discord webhook URL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--github-token",
|
||||
default=os.environ.get("GITHUB_TOKEN"),
|
||||
help="GitHub API token (or set GITHUB_TOKEN env var). Increases rate limit."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Regex patterns to ignore PRs (can be specified multiple times)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--submodule-path",
|
||||
default="lib/FFXIVClientStructs",
|
||||
help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get repository info
|
||||
owner, repo = get_repo_info()
|
||||
print(f"Repository: {owner}/{repo}")
|
||||
|
||||
# Get the last two tags
|
||||
latest_tag, previous_tag = get_last_two_tags()
|
||||
print(f"Generating changelog between {previous_tag} and {latest_tag}")
|
||||
|
||||
# Get submodule commits at both tags
|
||||
cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag)
|
||||
cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag)
|
||||
|
||||
if cs_commit_new:
|
||||
print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}")
|
||||
if cs_commit_old:
|
||||
print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}")
|
||||
|
||||
# Get PRs between tags
|
||||
prs = get_prs_between_tags(latest_tag, previous_tag, owner, repo, args.github_token)
|
||||
prs.reverse()
|
||||
print(f"Found {len(prs)} PRs")
|
||||
|
||||
# Filter PRs
|
||||
filtered_prs = filter_prs(prs, args.ignore)
|
||||
print(f"After filtering: {len(filtered_prs)} PRs")
|
||||
|
||||
# Generate changelog
|
||||
changelog = generate_changelog(latest_tag, previous_tag, filtered_prs,
|
||||
cs_commit_new, cs_commit_old, owner, repo)
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("Generated Changelog:")
|
||||
print("="*50)
|
||||
print(changelog)
|
||||
print("="*50 + "\n")
|
||||
|
||||
# Post to Discord
|
||||
post_to_discord(args.webhook_url, changelog, latest_tag)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
32
.github/workflows/backup.yml
vendored
32
.github/workflows/backup.yml
vendored
|
|
@ -1,32 +0,0 @@
|
|||
name: Back up code to other forges
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # Run every day at 2 AM
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
push-to-forges:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'goatcorp/Dalamud'
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd #v0.9.1
|
||||
with:
|
||||
ssh-private-key: |
|
||||
${{ secrets.MIRROR_GITLAB_SYNC_KEY }}
|
||||
${{ secrets.MIRROR_CODEBERG_SYNC_KEY }}
|
||||
|
||||
- name: Add remotes & push
|
||||
env:
|
||||
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=accept-new"
|
||||
run: |
|
||||
git remote add gitlab git@gitlab.com:goatcorp/Dalamud.git
|
||||
git push gitlab --all --force
|
||||
git remote add codeberg git@codeberg.org:goatcorp/Dalamud.git
|
||||
git push codeberg --all --force
|
||||
48
.github/workflows/generate-changelog.yml
vendored
48
.github/workflows/generate-changelog.yml
vendored
|
|
@ -1,48 +0,0 @@
|
|||
name: Generate Changelog
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
generate-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history and tags
|
||||
submodules: true # Fetch submodules
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Generate and post changelog
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
run: |
|
||||
python .github/generate_changelog.py \
|
||||
--webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \
|
||||
--ignore "Update ClientStructs" \
|
||||
--ignore "^build:"
|
||||
|
||||
- name: Upload changelog as artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: changelog
|
||||
path: changelog-*.md
|
||||
if-no-files-found: ignore
|
||||
32
.github/workflows/main.yml
vendored
32
.github/workflows/main.yml
vendored
|
|
@ -1,9 +1,8 @@
|
|||
name: Build Dalamud
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
concurrency:
|
||||
group: build_dalamud_${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -23,7 +22,7 @@ jobs:
|
|||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '10.0.100'
|
||||
dotnet-version: '8.0.100'
|
||||
- name: Define VERSION
|
||||
run: |
|
||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||
|
|
@ -33,8 +32,10 @@ jobs:
|
|||
($env:REPO_NAME) >> VERSION
|
||||
($env:BRANCH) >> VERSION
|
||||
($env:COMMIT) >> VERSION
|
||||
- name: Build and Test Dalamud
|
||||
run: .\build.ps1 ci
|
||||
- name: Build Dalamud
|
||||
run: .\build.ps1 compile
|
||||
- name: Test Dalamud
|
||||
run: .\build.ps1 test
|
||||
- name: Sign Dalamud
|
||||
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
||||
env:
|
||||
|
|
@ -54,7 +55,6 @@ jobs:
|
|||
bin/Release/Dalamud.*.dll
|
||||
bin/Release/Dalamud.*.exe
|
||||
bin/Release/FFXIVClientStructs.dll
|
||||
bin/Release/cim*.dll
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
@ -86,9 +86,9 @@ jobs:
|
|||
- name: "Verify Compatibility"
|
||||
run: |
|
||||
$FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll"
|
||||
|
||||
|
||||
$retcode = 0
|
||||
|
||||
|
||||
foreach ($file in $FILES_TO_VALIDATE) {
|
||||
$testout = ""
|
||||
Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ==="
|
||||
|
|
@ -99,7 +99,7 @@ jobs:
|
|||
$retcode = 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exit $retcode
|
||||
|
||||
deploy_stg:
|
||||
|
|
@ -128,18 +128,18 @@ jobs:
|
|||
GH_BRANCH: ${{ steps.extract_branch.outputs.branch }}
|
||||
run: |
|
||||
Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip
|
||||
|
||||
|
||||
$branchName = $env:GH_BRANCH
|
||||
|
||||
|
||||
if ($branchName -eq "master") {
|
||||
$branchName = "stg"
|
||||
}
|
||||
|
||||
|
||||
$newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt")
|
||||
$revision = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\revision.txt")
|
||||
$commitHash = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\commit_hash.txt")
|
||||
Remove-Item -Force -Recurse .\scratch
|
||||
|
||||
|
||||
if (Test-Path -Path $branchName) {
|
||||
$versionData = Get-Content ".\${branchName}\version" | ConvertFrom-Json
|
||||
$oldVersion = $versionData.AssemblyVersion
|
||||
|
|
@ -158,7 +158,7 @@ jobs:
|
|||
Write-Host "Deployment folder doesn't exist. Not doing anything."
|
||||
Remove-Item .\canary.zip
|
||||
}
|
||||
|
||||
|
||||
- name: Commit changes
|
||||
shell: bash
|
||||
env:
|
||||
|
|
@ -166,8 +166,8 @@ jobs:
|
|||
run: |
|
||||
git config --global user.name "Actions User"
|
||||
git config --global user.email "actions@github.com"
|
||||
|
||||
|
||||
git add .
|
||||
git commit -m "[CI] Update staging for ${DVER} on ${GH_BRANCH}" || true
|
||||
|
||||
|
||||
git push origin main || true
|
||||
|
|
|
|||
8
.github/workflows/rollup.yml
vendored
8
.github/workflows/rollup.yml
vendored
|
|
@ -1,8 +1,8 @@
|
|||
name: Rollup changes to next version
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
branches:
|
||||
- api14
|
||||
- net9
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
|||
45
.github/workflows/update-submodules.yml
vendored
45
.github/workflows/update-submodules.yml
vendored
|
|
@ -1,26 +1,16 @@
|
|||
name: Check for Submodule Changes
|
||||
name: Check for FFXIVCS changes
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0,6,12,18 * * *"
|
||||
- cron: "0 0,12,18 */1 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check ${{ matrix.submodule.name }}
|
||||
name: FFXIVCS Check
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branches: [master]
|
||||
submodule:
|
||||
- name: ClientStructs
|
||||
path: lib/FFXIVClientStructs
|
||||
branch: main
|
||||
branch-prefix: csupdate
|
||||
- name: Excel Schema
|
||||
path: lib/Lumina.Excel
|
||||
branch: master
|
||||
branch-prefix: schemaupdate
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
@ -34,41 +24,30 @@ jobs:
|
|||
ref: ${{ matrix.branches }}
|
||||
token: ${{ secrets.UPDATE_PAT }}
|
||||
- name: Create update branch
|
||||
run: git checkout -b ${{ matrix.submodule.branch-prefix }}/${{ matrix.branches }}
|
||||
run: git checkout -b csupdate/${{ matrix.branches }}
|
||||
- name: Initialize mandatory git config
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email noreply@github.com
|
||||
git config --global pull.rebase false
|
||||
- name: Update submodule
|
||||
id: update-submodule
|
||||
run: |
|
||||
git checkout -b ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }}
|
||||
git checkout -b csupdate-${{ matrix.branches }}
|
||||
git reset --hard origin/${{ matrix.branches }}
|
||||
cd ${{ matrix.submodule.path }}
|
||||
cd lib/FFXIVClientStructs
|
||||
git fetch
|
||||
git reset --hard origin/${{ matrix.submodule.branch }}
|
||||
git reset --hard origin/main
|
||||
cd ../..
|
||||
git add ${{ matrix.submodule.path }}
|
||||
|
||||
if [[ -z "$(git status --porcelain --untracked-files=no)" ]]; then
|
||||
echo "No changes detected!"
|
||||
echo "SUBMIT_PR=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit --message "Update ${{ matrix.submodule.name }}"
|
||||
git push origin ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --force
|
||||
echo "SUBMIT_PR=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
git add lib/FFXIVClientStructs
|
||||
git commit --message "Update ClientStructs"
|
||||
git push origin csupdate-${{ matrix.branches }} --force
|
||||
- name: Create PR
|
||||
if: ${{ steps.update-submodule.outputs.SUBMIT_PR == 'true' }}
|
||||
run: |
|
||||
echo ${{ secrets.UPDATE_PAT }} | gh auth login --with-token
|
||||
prNumber=$(gh pr list --base ${{ matrix.branches }} --head ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --state open --json number --template "{{range .}}{{.number}}{{end}}")
|
||||
prNumber=$(gh pr list --base ${{ matrix.branches }} --head csupdate-${{ matrix.branches }} --state open --json number --template "{{range .}}{{.number}}{{end}}")
|
||||
if [ -z "$prNumber" ]; then
|
||||
echo "No PR found, creating one"
|
||||
gh pr create --head ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --title "[${{ matrix.branches }}] Update ${{ matrix.submodule.name }}" --body "" --base ${{ matrix.branches }}
|
||||
gh pr create --head csupdate-${{ matrix.branches }} --title "[${{ matrix.branches }}] Update ClientStructs" --body "" --base ${{ matrix.branches }}
|
||||
else
|
||||
echo "PR already exists, ignoring"
|
||||
fi
|
||||
|
|
|
|||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -327,7 +327,4 @@ ASALocalRun/
|
|||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# HexaGen generated files
|
||||
#imgui/**/Generated/**/*
|
||||
.mfractor/
|
||||
18
.gitmodules
vendored
18
.gitmodules
vendored
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "lib/ImGuiScene"]
|
||||
path = lib/ImGuiScene
|
||||
url = https://github.com/goatcorp/ImGuiScene
|
||||
[submodule "lib/FFXIVClientStructs"]
|
||||
path = lib/FFXIVClientStructs
|
||||
url = https://github.com/aers/FFXIVClientStructs
|
||||
|
|
@ -7,18 +10,3 @@
|
|||
[submodule "lib/TsudaKageyu-minhook"]
|
||||
path = lib/TsudaKageyu-minhook
|
||||
url = https://github.com/TsudaKageyu/minhook.git
|
||||
[submodule "lib/cimgui"]
|
||||
path = lib/cimgui
|
||||
url = https://github.com/goatcorp/gc-cimgui
|
||||
[submodule "lib/cimplot"]
|
||||
path = lib/cimplot
|
||||
url = https://github.com/goatcorp/gc-cimplot
|
||||
[submodule "lib/cimguizmo"]
|
||||
path = lib/cimguizmo
|
||||
url = https://github.com/goatcorp/gc-cimguizmo
|
||||
[submodule "lib/Hexa.NET.ImGui"]
|
||||
path = lib/Hexa.NET.ImGui
|
||||
url = https://github.com/goatcorp/Hexa.NET.ImGui.git
|
||||
[submodule "lib/Lumina.Excel"]
|
||||
path = lib/Lumina.Excel
|
||||
url = https://github.com/NotAdam/Lumina.Excel.git
|
||||
|
|
|
|||
|
|
@ -1,57 +1,19 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Build Schema",
|
||||
"$ref": "#/definitions/build",
|
||||
"definitions": {
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitbucket",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"Rider",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI",
|
||||
"VisualStudio",
|
||||
"VSCode"
|
||||
]
|
||||
},
|
||||
"ExecutableTarget": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"enum": [
|
||||
"Verbose",
|
||||
"Normal",
|
||||
"Minimal",
|
||||
"Quiet"
|
||||
]
|
||||
},
|
||||
"NukeBuild": {
|
||||
"build": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
]
|
||||
},
|
||||
"Continue": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to continue a previously failed build attempt"
|
||||
|
|
@ -61,8 +23,29 @@
|
|||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"$ref": "#/definitions/Host"
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitbucket",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"Rider",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI",
|
||||
"VisualStudio",
|
||||
"VSCode"
|
||||
]
|
||||
},
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
},
|
||||
"NoLogo": {
|
||||
"type": "boolean",
|
||||
|
|
@ -91,46 +74,53 @@
|
|||
"type": "array",
|
||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
},
|
||||
"Target": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||
"$ref": "#/definitions/Verbosity"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
"Minimal",
|
||||
"Normal",
|
||||
"Quiet",
|
||||
"Verbose"
|
||||
]
|
||||
},
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/NukeBuild"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,38 +26,6 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
|
||||
RT_MANIFEST_THEMES RT_MANIFEST "themes.manifest"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// String Table
|
||||
//
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_APPNAME "Dalamud Boot"
|
||||
IDS_MSVCRT_ACTION_OPENDOWNLOAD
|
||||
"Download Microsoft Visual C++ Redistributable 2022\nExit the game and download the latest setup file from Microsoft."
|
||||
IDS_MSVCRT_ACTION_IGNORE
|
||||
"Ignore and Continue\nAttempt to continue with the currently installed version.\nDalamud or plugins may fail to load."
|
||||
IDS_MSVCRT_DIALOG_MAININSTRUCTION
|
||||
"Outdated Microsoft Visual C++ Redistributable"
|
||||
IDS_MSVCRT_DIALOG_CONTENT
|
||||
"The Microsoft Visual C++ Redistributable version detected on this computer (v{0}.{1}.{2}.{3}) is out of date and may not work with Dalamud."
|
||||
IDS_MSVCRT_DOWNLOADURL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
IDS_INITIALIZEFAIL_ACTION_ABORT "Abort\nExit the game."
|
||||
IDS_INITIALIZEFAIL_ACTION_CONTINUE
|
||||
"Load game without Dalamud\nThe game will launch without Dalamud enabled."
|
||||
IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION "Failed to load Dalamud."
|
||||
IDS_INITIALIZEFAIL_DIALOG_CONTENT
|
||||
"An error is preventing Dalamud from being loaded along with the game."
|
||||
END
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_INITIALIZEFAIL_DIALOG_FOOTER
|
||||
"Last operation: {0}\nHRESULT: 0x{1:08X}\nDescription: {2}"
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<PlatformToolset>v143</PlatformToolset>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<OutDir>bin\$(Configuration)\</OutDir>
|
||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
||||
<IntDir>obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
|
@ -48,7 +48,8 @@
|
|||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp23</LanguageStandard>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
|
@ -65,7 +66,6 @@
|
|||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26812</DisableSpecificWarnings>
|
||||
|
|
@ -80,7 +80,6 @@
|
|||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26812</DisableSpecificWarnings>
|
||||
|
|
@ -133,10 +132,6 @@
|
|||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DalamudStartInfo.cpp" />
|
||||
<ClCompile Include="error_info.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hooks.cpp" />
|
||||
<ClCompile Include="logging.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
|
|
@ -180,7 +175,6 @@
|
|||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\trampoline.h" />
|
||||
<ClInclude Include="crashhandler_shared.h" />
|
||||
<ClInclude Include="DalamudStartInfo.h" />
|
||||
<ClInclude Include="error_info.h" />
|
||||
<ClInclude Include="hooks.h" />
|
||||
<ClInclude Include="logging.h" />
|
||||
<ClInclude Include="ntdll.h" />
|
||||
|
|
@ -206,9 +200,8 @@
|
|||
<ItemGroup>
|
||||
<Manifest Include="themes.manifest" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyOutputDlls" AfterTargets="PostBuildEvent">
|
||||
<Copy SourceFiles="$(OutDir)$(TargetName).dll" DestinationFolder="..\bin\$(Configuration)\" />
|
||||
<Copy SourceFiles="$(OutDir)$(TargetName).pdb" DestinationFolder="..\bin\$(Configuration)\" />
|
||||
<Copy SourceFiles="$(OutDir)nethost.dll" DestinationFolder="..\bin\$(Configuration)\" />
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -76,9 +76,6 @@
|
|||
<ClCompile Include="ntdll.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="error_info.cpp">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
|
|
@ -149,9 +146,6 @@
|
|||
<ClInclude Include="ntdll.h">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="error_info.h">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Dalamud.Boot.rc" />
|
||||
|
|
|
|||
|
|
@ -108,13 +108,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
|||
config.LogName = json.value("LogName", config.LogName);
|
||||
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
||||
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
||||
|
||||
if (json.contains("TempDirectory") && !json["TempDirectory"].is_null()) {
|
||||
config.TempDirectory = json.value("TempDirectory", config.TempDirectory);
|
||||
}
|
||||
|
||||
config.Language = json.value("Language", config.Language);
|
||||
config.Platform = json.value("Platform", config.Platform);
|
||||
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
||||
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
|
||||
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
|
||||
|
|
@ -122,7 +116,6 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
|||
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
|
||||
|
||||
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
|
||||
config.BootDebugDirectX = json.value("BootDebugDirectX", config.BootDebugDirectX);
|
||||
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
|
||||
config.BootDisableFallbackConsole = json.value("BootDisableFallbackConsole", config.BootDisableFallbackConsole);
|
||||
config.BootWaitMessageBox = json.value("BootWaitMessageBox", config.BootWaitMessageBox);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct DalamudStartInfo {
|
|||
DirectHook = 1,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, DotNetOpenProcessHookMode&);
|
||||
|
||||
|
||||
enum class ClientLanguage : int {
|
||||
Japanese,
|
||||
English,
|
||||
|
|
@ -44,11 +44,9 @@ struct DalamudStartInfo {
|
|||
std::string ConfigurationPath;
|
||||
std::string LogPath;
|
||||
std::string LogName;
|
||||
std::string TempDirectory;
|
||||
std::string PluginDirectory;
|
||||
std::string AssetDirectory;
|
||||
ClientLanguage Language = ClientLanguage::English;
|
||||
std::string Platform;
|
||||
std::string GameVersion;
|
||||
std::string TroubleshootingPackData;
|
||||
int DelayInitializeMs = 0;
|
||||
|
|
@ -56,7 +54,6 @@ struct DalamudStartInfo {
|
|||
bool NoLoadThirdPartyPlugins;
|
||||
|
||||
std::string BootLogPath;
|
||||
bool BootDebugDirectX = false;
|
||||
bool BootShowConsole = false;
|
||||
bool BootDisableFallbackConsole = false;
|
||||
WaitMessageboxFlags BootWaitMessageBox = WaitMessageboxFlags::None;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
|
||||
|
||||
struct exception_info
|
||||
{
|
||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||
|
|
|
|||
|
|
@ -1,136 +1,17 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_3.h>
|
||||
|
||||
#include "DalamudStartInfo.h"
|
||||
#include "hooks.h"
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
#include "veh.h"
|
||||
#include "xivfixes.h"
|
||||
#include "resource.h"
|
||||
|
||||
HMODULE g_hModule;
|
||||
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
|
||||
|
||||
static void CheckMsvcrtVersion() {
|
||||
// Commit introducing inline mutex ctor: tagged vs-2022-17.14 (2024-06-18)
|
||||
// - https://github.com/microsoft/STL/commit/22a88260db4d754bbc067e2002430144d6ec5391
|
||||
// MSVC Redist versions:
|
||||
// - https://github.com/abbodi1406/vcredist/blob/master/source_links/README.md
|
||||
// - 14.40.33810.0 dsig 2024-04-28
|
||||
// - 14.40.33816.0 dsig 2024-09-11
|
||||
|
||||
constexpr WORD RequiredMsvcrtVersionComponents[] = {14, 40, 33816, 0};
|
||||
constexpr auto RequiredMsvcrtVersion = 0ULL
|
||||
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[0]) << 48)
|
||||
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[1]) << 32)
|
||||
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[2]) << 16)
|
||||
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[3]) << 0);
|
||||
|
||||
constexpr const wchar_t* RuntimeDllNames[] = {
|
||||
#ifdef _DEBUG
|
||||
L"msvcp140d.dll",
|
||||
L"vcruntime140d.dll",
|
||||
L"vcruntime140_1d.dll",
|
||||
#else
|
||||
L"msvcp140.dll",
|
||||
L"vcruntime140.dll",
|
||||
L"vcruntime140_1.dll",
|
||||
#endif
|
||||
};
|
||||
|
||||
uint64_t lowestVersion = 0;
|
||||
for (const auto& runtimeDllName : RuntimeDllNames) {
|
||||
const utils::loaded_module mod(GetModuleHandleW(runtimeDllName));
|
||||
if (!mod) {
|
||||
logging::E("MSVCRT DLL not found: {}", runtimeDllName);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = mod.path()
|
||||
.transform([](const auto& p) { return p.wstring(); })
|
||||
.value_or(runtimeDllName);
|
||||
|
||||
if (const auto versionResult = mod.get_file_version()) {
|
||||
const auto& versionFull = versionResult->get();
|
||||
logging::I("MSVCRT DLL {} has version {}.", path, utils::format_file_version(versionFull));
|
||||
|
||||
const auto version = 0ULL |
|
||||
(static_cast<uint64_t>(versionFull.dwFileVersionMS) << 32) |
|
||||
(static_cast<uint64_t>(versionFull.dwFileVersionLS) << 0);
|
||||
|
||||
if (version < RequiredMsvcrtVersion && (lowestVersion == 0 || lowestVersion > version))
|
||||
lowestVersion = version;
|
||||
} else {
|
||||
logging::E("Failed to detect MSVCRT DLL version for {}: {}", path, versionResult.error().describe());
|
||||
}
|
||||
}
|
||||
|
||||
if (!lowestVersion)
|
||||
return;
|
||||
|
||||
enum IdTaskDialogAction {
|
||||
IdTaskDialogActionOpenDownload = 101,
|
||||
IdTaskDialogActionIgnore,
|
||||
};
|
||||
|
||||
const TASKDIALOG_BUTTON buttons[]{
|
||||
{IdTaskDialogActionOpenDownload, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_OPENDOWNLOAD)},
|
||||
{IdTaskDialogActionIgnore, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_IGNORE)},
|
||||
};
|
||||
|
||||
const WORD lowestVersionComponents[]{
|
||||
static_cast<WORD>(lowestVersion >> 48),
|
||||
static_cast<WORD>(lowestVersion >> 32),
|
||||
static_cast<WORD>(lowestVersion >> 16),
|
||||
static_cast<WORD>(lowestVersion >> 0),
|
||||
};
|
||||
|
||||
const auto dialogContent = std::vformat(
|
||||
utils::get_string_resource(IDS_MSVCRT_DIALOG_CONTENT),
|
||||
std::make_wformat_args(
|
||||
lowestVersionComponents[0],
|
||||
lowestVersionComponents[1],
|
||||
lowestVersionComponents[2],
|
||||
lowestVersionComponents[3]));
|
||||
|
||||
const TASKDIALOGCONFIG config{
|
||||
.cbSize = sizeof config,
|
||||
.hInstance = g_hModule,
|
||||
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS,
|
||||
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
|
||||
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
|
||||
.pszMainInstruction = MAKEINTRESOURCEW(IDS_MSVCRT_DIALOG_MAININSTRUCTION),
|
||||
.pszContent = dialogContent.c_str(),
|
||||
.cButtons = _countof(buttons),
|
||||
.pButtons = buttons,
|
||||
.nDefaultButton = IdTaskDialogActionOpenDownload,
|
||||
};
|
||||
|
||||
int buttonPressed;
|
||||
if (utils::scoped_dpi_awareness_context ctx;
|
||||
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
|
||||
buttonPressed = IdTaskDialogActionOpenDownload;
|
||||
|
||||
switch (buttonPressed) {
|
||||
case IdTaskDialogActionOpenDownload:
|
||||
ShellExecuteW(
|
||||
nullptr,
|
||||
L"open",
|
||||
utils::get_string_resource(IDS_MSVCRT_DOWNLOADURL).c_str(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
SW_SHOW);
|
||||
ExitProcess(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||
g_startInfo.from_envvars();
|
||||
|
||||
|
||||
std::string jsonParseError;
|
||||
try {
|
||||
from_json(nlohmann::json::parse(std::string_view(static_cast<char*>(lpParam))), g_startInfo);
|
||||
|
|
@ -139,8 +20,8 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
}
|
||||
|
||||
if (g_startInfo.BootShowConsole)
|
||||
ConsoleSetup(utils::get_string_resource(IDS_APPNAME).c_str());
|
||||
|
||||
ConsoleSetup(L"Dalamud Boot");
|
||||
|
||||
logging::update_dll_load_status(true);
|
||||
|
||||
const auto logFilePath = unicode::convert<std::wstring>(g_startInfo.BootLogPath);
|
||||
|
|
@ -148,16 +29,16 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
auto attemptFallbackLog = false;
|
||||
if (logFilePath.empty()) {
|
||||
attemptFallbackLog = true;
|
||||
|
||||
|
||||
logging::I("No log file path given; not logging to file.");
|
||||
} else {
|
||||
try {
|
||||
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
|
||||
logging::I("Logging to file: {}", logFilePath);
|
||||
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
attemptFallbackLog = true;
|
||||
|
||||
|
||||
logging::E("Couldn't open log file: {}", logFilePath);
|
||||
logging::E("Error: {} / {}", errno, e.what());
|
||||
}
|
||||
|
|
@ -178,15 +59,15 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
logFilePath += std::format(L"Dalamud.Boot.{:04}{:02}{:02}.{:02}{:02}{:02}.{:03}.{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId());
|
||||
|
||||
|
||||
try {
|
||||
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
|
||||
logging::I("Logging to fallback log file: {}", logFilePath);
|
||||
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
if (!g_startInfo.BootShowConsole && !g_startInfo.BootDisableFallbackConsole)
|
||||
ConsoleSetup(L"Dalamud Boot - Fallback Console");
|
||||
|
||||
|
||||
logging::E("Couldn't open fallback log file: {}", logFilePath);
|
||||
logging::E("Error: {} / {}", errno, e.what());
|
||||
}
|
||||
|
|
@ -202,81 +83,16 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
} else {
|
||||
logging::E("Failed to initialize MinHook (status={}({}))", MH_StatusToString(mhStatus), static_cast<int>(mhStatus));
|
||||
}
|
||||
|
||||
|
||||
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at: " __DATE__ "@" __TIME__);
|
||||
|
||||
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
|
||||
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
|
||||
|
||||
CheckMsvcrtVersion();
|
||||
|
||||
if (g_startInfo.BootDebugDirectX) {
|
||||
logging::I("Enabling DirectX Debugging.");
|
||||
|
||||
const auto hD3D11 = GetModuleHandleW(L"d3d11.dll");
|
||||
const auto hDXGI = GetModuleHandleW(L"dxgi.dll");
|
||||
const auto pfnD3D11CreateDevice = static_cast<decltype(&D3D11CreateDevice)>(
|
||||
hD3D11 ? static_cast<void*>(GetProcAddress(hD3D11, "D3D11CreateDevice")) : nullptr);
|
||||
if (pfnD3D11CreateDevice) {
|
||||
static hooks::direct_hook<decltype(D3D11CreateDevice)> s_hookD3D11CreateDevice(
|
||||
"d3d11.dll!D3D11CreateDevice",
|
||||
pfnD3D11CreateDevice);
|
||||
s_hookD3D11CreateDevice.set_detour([](
|
||||
IDXGIAdapter* pAdapter,
|
||||
D3D_DRIVER_TYPE DriverType,
|
||||
HMODULE Software,
|
||||
UINT Flags,
|
||||
const D3D_FEATURE_LEVEL* pFeatureLevels,
|
||||
UINT FeatureLevels,
|
||||
UINT SDKVersion,
|
||||
ID3D11Device** ppDevice,
|
||||
D3D_FEATURE_LEVEL* pFeatureLevel,
|
||||
ID3D11DeviceContext** ppImmediateContext
|
||||
) -> HRESULT {
|
||||
return s_hookD3D11CreateDevice.call_original(
|
||||
pAdapter,
|
||||
DriverType,
|
||||
Software,
|
||||
(Flags & ~D3D11_CREATE_DEVICE_PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY) | D3D11_CREATE_DEVICE_DEBUG,
|
||||
pFeatureLevels,
|
||||
FeatureLevels,
|
||||
SDKVersion,
|
||||
ppDevice,
|
||||
pFeatureLevel,
|
||||
ppImmediateContext);
|
||||
});
|
||||
} else {
|
||||
logging::W("Could not find d3d11!D3D11CreateDevice.");
|
||||
}
|
||||
|
||||
const auto pfnCreateDXGIFactory = static_cast<decltype(&CreateDXGIFactory)>(
|
||||
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory")) : nullptr);
|
||||
const auto pfnCreateDXGIFactory1 = static_cast<decltype(&CreateDXGIFactory1)>(
|
||||
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory1")) : nullptr);
|
||||
static const auto pfnCreateDXGIFactory2 = static_cast<decltype(&CreateDXGIFactory2)>(
|
||||
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory2")) : nullptr);
|
||||
if (pfnCreateDXGIFactory2) {
|
||||
static hooks::direct_hook<decltype(CreateDXGIFactory)> s_hookCreateDXGIFactory(
|
||||
"dxgi.dll!CreateDXGIFactory",
|
||||
pfnCreateDXGIFactory);
|
||||
static hooks::direct_hook<decltype(CreateDXGIFactory1)> s_hookCreateDXGIFactory1(
|
||||
"dxgi.dll!CreateDXGIFactory1",
|
||||
pfnCreateDXGIFactory1);
|
||||
s_hookCreateDXGIFactory.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
|
||||
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
|
||||
});
|
||||
s_hookCreateDXGIFactory1.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
|
||||
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
|
||||
});
|
||||
} else {
|
||||
logging::W("Could not find dxgi!CreateDXGIFactory2.");
|
||||
}
|
||||
}
|
||||
|
||||
if (minHookLoaded) {
|
||||
logging::I("Applying fixes...");
|
||||
std::thread([] { xivfixes::apply_all(true); }).join();
|
||||
xivfixes::apply_all(true);
|
||||
logging::I("Fixes OK");
|
||||
} else {
|
||||
logging::W("Skipping fixes, as MinHook has failed to load.");
|
||||
|
|
@ -287,14 +103,11 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
while (!IsDebuggerPresent())
|
||||
Sleep(100);
|
||||
logging::I("Debugger attached.");
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
const auto fs_module_path = utils::loaded_module(g_hModule).path();
|
||||
if (!fs_module_path)
|
||||
return fs_module_path.error();
|
||||
const auto runtimeconfig_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
|
||||
const auto module_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.dll").wstring();
|
||||
const auto fs_module_path = utils::get_module_path(g_hModule);
|
||||
const auto runtimeconfig_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
|
||||
const auto module_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.dll").wstring();
|
||||
|
||||
// ============================== CLR ========================================= //
|
||||
|
||||
|
|
@ -331,51 +144,6 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
logging::I("VEH was disabled manually");
|
||||
}
|
||||
|
||||
// ============================== CLR Reporting =================================== //
|
||||
|
||||
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
|
||||
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
|
||||
// Ideally all of this will go away once they get to it.
|
||||
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_report_event_hook;
|
||||
s_report_event_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>(
|
||||
"advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
|
||||
s_report_event_hook->set_detour([hook = s_report_event_hook.get()](
|
||||
HANDLE hEventLog,
|
||||
WORD wType,
|
||||
WORD wCategory,
|
||||
DWORD dwEventID,
|
||||
PSID lpUserSid,
|
||||
WORD wNumStrings,
|
||||
DWORD dwDataSize,
|
||||
LPCWSTR* lpStrings,
|
||||
LPVOID lpRawData)-> BOOL {
|
||||
|
||||
// Check for CLR Error Event IDs
|
||||
// https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370
|
||||
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
|
||||
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
|
||||
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
|
||||
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
|
||||
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
|
||||
{
|
||||
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
}
|
||||
|
||||
if (wNumStrings == 0 || lpStrings == nullptr) {
|
||||
logging::W("ReportEventW called with no strings.");
|
||||
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
}
|
||||
|
||||
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
|
||||
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
|
||||
const std::wstring error_details(lpStrings[0]);
|
||||
veh::raise_external_event(error_details);
|
||||
|
||||
return original_ret;
|
||||
});
|
||||
logging::I("ReportEventW hook installed.");
|
||||
|
||||
// ============================== Dalamud ==================================== //
|
||||
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||
|
|
@ -406,11 +174,11 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese
|
|||
case DLL_PROCESS_DETACH:
|
||||
// do not show debug message boxes on abort() here
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
||||
|
||||
|
||||
// process is terminating; don't bother cleaning up
|
||||
if (lpReserved)
|
||||
return TRUE;
|
||||
|
||||
|
||||
logging::update_dll_load_status(false);
|
||||
|
||||
xivfixes::apply_all(false);
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
#include "error_info.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept
|
||||
: m_dalamudErrorDescription(dalamudErrorDescription)
|
||||
, m_hresult(hresult) {
|
||||
}
|
||||
|
||||
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept
|
||||
: DalamudBootError(dalamudErrorDescription, E_FAIL) {
|
||||
}
|
||||
|
||||
const char* DalamudBootError::describe() const {
|
||||
switch (m_dalamudErrorDescription) {
|
||||
case DalamudBootErrorDescription::ModuleResourceLoadFail:
|
||||
return "Failed to load resource.";
|
||||
case DalamudBootErrorDescription::ModuleResourceVersionReadFail:
|
||||
return "Failed to query version information.";
|
||||
case DalamudBootErrorDescription::ModuleResourceVersionSignatureFail:
|
||||
return "Invalid version info found.";
|
||||
default:
|
||||
return "(unavailable)";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
typedef unsigned long DWORD;
|
||||
typedef _Return_type_success_(return >= 0) long HRESULT;
|
||||
|
||||
enum class DalamudBootErrorDescription {
|
||||
None,
|
||||
ModulePathResolutionFail,
|
||||
ModuleResourceLoadFail,
|
||||
ModuleResourceVersionReadFail,
|
||||
ModuleResourceVersionSignatureFail,
|
||||
};
|
||||
|
||||
class DalamudBootError {
|
||||
DalamudBootErrorDescription m_dalamudErrorDescription;
|
||||
long m_hresult;
|
||||
|
||||
public:
|
||||
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept;
|
||||
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept;
|
||||
|
||||
const char* describe() const;
|
||||
|
||||
operator HRESULT() const {
|
||||
return m_hresult;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using DalamudExpected = std::expected<
|
||||
std::conditional_t<
|
||||
std::is_reference_v<T>,
|
||||
std::reference_wrapper<std::remove_reference_t<T>>,
|
||||
T
|
||||
>,
|
||||
DalamudBootError
|
||||
>;
|
||||
|
||||
using DalamudUnexpected = std::unexpected<DalamudBootError>;
|
||||
|
|
@ -84,13 +84,19 @@ void hooks::getprocaddress_singleton_import_hook::initialize() {
|
|||
const auto dllName = unicode::convert<std::string>(pData->Loaded.FullDllName->Buffer);
|
||||
|
||||
utils::loaded_module mod(pData->Loaded.DllBase);
|
||||
const auto version = mod.get_file_version()
|
||||
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
|
||||
.value_or(L"<unknown>");
|
||||
|
||||
const auto description = mod.get_description()
|
||||
.value_or(L"<unknown>");
|
||||
|
||||
std::wstring version, description;
|
||||
try {
|
||||
version = utils::format_file_version(mod.get_file_version());
|
||||
} catch (...) {
|
||||
version = L"<unknown>";
|
||||
}
|
||||
|
||||
try {
|
||||
description = mod.get_description();
|
||||
} catch (...) {
|
||||
description = L"<unknown>";
|
||||
}
|
||||
|
||||
logging::I(R"({} "{}" ("{}" ver {}) has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)",
|
||||
LogTag, dllName, description, version,
|
||||
reinterpret_cast<size_t>(pData->Loaded.DllBase),
|
||||
|
|
@ -119,9 +125,7 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
|
|||
if (mod.is_current_process())
|
||||
return;
|
||||
|
||||
const auto path = mod.path()
|
||||
.transform([](const auto& p) { return unicode::convert<std::string>(p.wstring()); })
|
||||
.value_or("<unknown>");
|
||||
const auto path = unicode::convert<std::string>(mod.path().wstring());
|
||||
|
||||
for (const auto& [hModule, targetFns] : m_targetFns) {
|
||||
for (const auto& [targetFn, pfnThunk] : targetFns) {
|
||||
|
|
@ -129,7 +133,7 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
|
|||
if (void* pGetProcAddressImport; mod.find_imported_function_pointer(dllName.c_str(), targetFn.c_str(), 0, pGetProcAddressImport)) {
|
||||
auto& hook = m_hooks[hModule][targetFn][mod];
|
||||
if (!hook) {
|
||||
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, path);
|
||||
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert<std::string>(mod.path().wstring()));
|
||||
|
||||
hook.emplace(std::format("getprocaddress_singleton_import_hook::hook_module({}!{})", dllName, targetFn), static_cast<void**>(pGetProcAddressImport), pfnThunk);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
|
||||
// https://developercommunity.visualstudio.com/t/Access-violation-with-std::mutex::lock-a/10664660
|
||||
#define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
|
||||
|
||||
// Windows Header Files (1)
|
||||
#include <Windows.h>
|
||||
|
||||
|
|
@ -24,7 +21,6 @@
|
|||
#include <iphlpapi.h>
|
||||
#include <PathCch.h>
|
||||
#include <Psapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <ShlObj.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <SubAuth.h>
|
||||
|
|
@ -55,7 +51,6 @@
|
|||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
// https://www.akenotsuki.com/misc/srell/en/
|
||||
|
|
|
|||
|
|
@ -3,23 +3,12 @@
|
|||
// Used by Dalamud.Boot.rc
|
||||
//
|
||||
#define IDI_ICON1 101
|
||||
#define IDS_APPNAME 102
|
||||
#define IDS_MSVCRT_ACTION_OPENDOWNLOAD 103
|
||||
#define IDS_MSVCRT_ACTION_IGNORE 104
|
||||
#define IDS_MSVCRT_DIALOG_MAININSTRUCTION 105
|
||||
#define IDS_MSVCRT_DIALOG_CONTENT 106
|
||||
#define IDS_MSVCRT_DOWNLOADURL 107
|
||||
#define IDS_INITIALIZEFAIL_ACTION_ABORT 108
|
||||
#define IDS_INITIALIZEFAIL_ACTION_CONTINUE 109
|
||||
#define IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION 110
|
||||
#define IDS_INITIALIZEFAIL_DIALOG_CONTENT 111
|
||||
#define IDS_INITIALIZEFAIL_DIALOG_FOOTER 112
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 103
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
#include "resource.h"
|
||||
|
||||
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||
|
||||
|
|
@ -380,50 +379,12 @@ extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointPara
|
|||
auto desc = err.Description();
|
||||
if (desc.length() == 0)
|
||||
desc = err.ErrorMessage();
|
||||
|
||||
enum IdTaskDialogAction {
|
||||
IdTaskDialogActionAbort = 101,
|
||||
IdTaskDialogActionContinue,
|
||||
};
|
||||
|
||||
const TASKDIALOG_BUTTON buttons[]{
|
||||
{IdTaskDialogActionAbort, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_ABORT)},
|
||||
{IdTaskDialogActionContinue, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_CONTINUE)},
|
||||
};
|
||||
|
||||
const auto hru32 = static_cast<uint32_t>(hr);
|
||||
const auto footer = std::vformat(
|
||||
utils::get_string_resource(IDS_INITIALIZEFAIL_DIALOG_FOOTER),
|
||||
std::make_wformat_args(
|
||||
last_operation,
|
||||
hru32,
|
||||
desc.GetBSTR()));
|
||||
|
||||
const TASKDIALOGCONFIG config{
|
||||
.cbSize = sizeof config,
|
||||
.hInstance = g_hModule,
|
||||
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS | TDF_EXPAND_FOOTER_AREA,
|
||||
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
|
||||
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
|
||||
.pszMainInstruction = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION),
|
||||
.pszContent = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_CONTENT),
|
||||
.cButtons = _countof(buttons),
|
||||
.pButtons = buttons,
|
||||
.nDefaultButton = IdTaskDialogActionAbort,
|
||||
.pszFooter = footer.c_str(),
|
||||
};
|
||||
|
||||
int buttonPressed;
|
||||
if (utils::scoped_dpi_awareness_context ctx;
|
||||
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
|
||||
buttonPressed = IdTaskDialogActionAbort;
|
||||
|
||||
switch (buttonPressed) {
|
||||
case IdTaskDialogActionAbort:
|
||||
ExitProcess(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (MessageBoxW(nullptr, std::format(
|
||||
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n{}",
|
||||
last_operation,
|
||||
desc.GetBSTR()).c_str(),
|
||||
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
|
||||
ExitProcess(-1);
|
||||
if (hMainThreadContinue) {
|
||||
CloseHandle(hMainThreadContinue);
|
||||
hMainThreadContinue = nullptr;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,23 @@
|
|||
#include "pch.h"
|
||||
#include "DalamudStartInfo.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
DalamudExpected<std::filesystem::path> utils::loaded_module::path() const {
|
||||
for (std::wstring buf(MAX_PATH, L'\0');; buf.resize(buf.size() * 2)) {
|
||||
if (const auto len = GetModuleFileNameW(m_hModule, &buf[0], static_cast<DWORD>(buf.size()));
|
||||
len != buf.size()) {
|
||||
if (!len) {
|
||||
return DalamudUnexpected(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModulePathResolutionFail,
|
||||
HRESULT_FROM_WIN32(GetLastError()));
|
||||
}
|
||||
|
||||
std::filesystem::path utils::loaded_module::path() const {
|
||||
std::wstring buf(MAX_PATH, L'\0');
|
||||
for (;;) {
|
||||
if (const auto len = GetModuleFileNameExW(GetCurrentProcess(), m_hModule, &buf[0], static_cast<DWORD>(buf.size())); len != buf.size()) {
|
||||
if (buf.empty())
|
||||
throw std::runtime_error(std::format("Failed to resolve module path: Win32 error {}", GetLastError()));
|
||||
buf.resize(len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (buf.size() > PATHCCH_MAX_CCH) {
|
||||
return DalamudUnexpected(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModulePathResolutionFail,
|
||||
E_OUTOFMEMORY);
|
||||
}
|
||||
if (buf.size() * 2 < PATHCCH_MAX_CCH)
|
||||
buf.resize(buf.size() * 2);
|
||||
else if (auto p = std::filesystem::path(buf); exists(p))
|
||||
return p;
|
||||
else
|
||||
throw std::runtime_error("Failed to resolve module path: no amount of buffer size would fit the data");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,90 +143,66 @@ void* utils::loaded_module::get_imported_function_pointer(const char* pcszDllNam
|
|||
throw std::runtime_error(std::format("Failed to find import for {}!{} ({}).", pcszDllName, pcszFunctionName ? pcszFunctionName : "<unnamed>", hintOrOrdinal));
|
||||
}
|
||||
|
||||
DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
|
||||
std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
|
||||
const auto hres = FindResourceW(m_hModule, lpName, lpType);
|
||||
if (!hres)
|
||||
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
|
||||
|
||||
throw std::runtime_error("No such resource");
|
||||
|
||||
const auto hRes = LoadResource(m_hModule, hres);
|
||||
if (!hRes)
|
||||
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
|
||||
throw std::runtime_error("LoadResource failure");
|
||||
|
||||
return std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>(hRes, &FreeResource);
|
||||
return {hRes, &FreeResource};
|
||||
}
|
||||
|
||||
DalamudExpected<std::wstring> utils::loaded_module::get_description() const {
|
||||
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
|
||||
if (!rsrc)
|
||||
return DalamudUnexpected(std::move(rsrc.error()));
|
||||
|
||||
const auto pBlock = LockResource(rsrc->get());
|
||||
|
||||
std::wstring utils::loaded_module::get_description() const {
|
||||
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
|
||||
const auto pBlock = LockResource(rsrc.get());
|
||||
|
||||
struct LANGANDCODEPAGE {
|
||||
WORD wLanguage;
|
||||
WORD wCodePage;
|
||||
} * lpTranslate;
|
||||
UINT cbTranslate;
|
||||
if (!VerQueryValueW(pBlock,
|
||||
L"\\VarFileInfo\\Translation",
|
||||
TEXT("\\VarFileInfo\\Translation"),
|
||||
reinterpret_cast<LPVOID*>(&lpTranslate),
|
||||
&cbTranslate)) {
|
||||
return DalamudUnexpected(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
|
||||
HRESULT_FROM_WIN32(GetLastError()));
|
||||
throw std::runtime_error("Invalid version information (1)");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (cbTranslate / sizeof(LANGANDCODEPAGE)); i++) {
|
||||
wchar_t subblockNameBuf[64];
|
||||
*std::format_to_n(
|
||||
subblockNameBuf,
|
||||
_countof(subblockNameBuf),
|
||||
L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
|
||||
lpTranslate[i].wLanguage,
|
||||
lpTranslate[i].wCodePage).out = 0;;
|
||||
|
||||
wchar_t* buf = nullptr;
|
||||
UINT size = 0;
|
||||
if (!VerQueryValueW(pBlock, subblockNameBuf, reinterpret_cast<LPVOID*>(&buf), &size))
|
||||
if (!VerQueryValueW(pBlock,
|
||||
std::format(L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
|
||||
lpTranslate[i].wLanguage,
|
||||
lpTranslate[i].wCodePage).c_str(),
|
||||
reinterpret_cast<LPVOID*>(&buf),
|
||||
&size)) {
|
||||
continue;
|
||||
|
||||
}
|
||||
auto currName = std::wstring_view(buf, size);
|
||||
if (const auto p = currName.find(L'\0'); p != std::string::npos)
|
||||
currName = currName.substr(0, p);
|
||||
while (!currName.empty() && currName.back() == L'\0')
|
||||
currName = currName.substr(0, currName.size() - 1);
|
||||
if (currName.empty())
|
||||
continue;
|
||||
return std::wstring(currName);
|
||||
}
|
||||
|
||||
return DalamudUnexpected(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
|
||||
HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
|
||||
|
||||
throw std::runtime_error("Invalid version information (2)");
|
||||
}
|
||||
|
||||
std::expected<std::reference_wrapper<const VS_FIXEDFILEINFO>, DalamudBootError> utils::loaded_module::get_file_version() const {
|
||||
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
|
||||
if (!rsrc)
|
||||
return DalamudUnexpected(std::move(rsrc.error()));
|
||||
|
||||
const auto pBlock = LockResource(rsrc->get());
|
||||
VS_FIXEDFILEINFO utils::loaded_module::get_file_version() const {
|
||||
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
|
||||
const auto pBlock = LockResource(rsrc.get());
|
||||
UINT size = 0;
|
||||
LPVOID lpBuffer = nullptr;
|
||||
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size)) {
|
||||
return std::unexpected<DalamudBootError>(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
|
||||
HRESULT_FROM_WIN32(GetLastError()));
|
||||
}
|
||||
|
||||
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size))
|
||||
throw std::runtime_error("Failed to query version information.");
|
||||
const VS_FIXEDFILEINFO& versionInfo = *static_cast<const VS_FIXEDFILEINFO*>(lpBuffer);
|
||||
if (versionInfo.dwSignature != 0xfeef04bd) {
|
||||
return std::unexpected<DalamudBootError>(
|
||||
std::in_place,
|
||||
DalamudBootErrorDescription::ModuleResourceVersionSignatureFail);
|
||||
}
|
||||
|
||||
if (versionInfo.dwSignature != 0xfeef04bd)
|
||||
throw std::runtime_error("Invalid version info found.");
|
||||
return versionInfo;
|
||||
}
|
||||
|
||||
|
|
@ -382,7 +352,7 @@ const char* utils::signature_finder::result::resolve_jump_target(size_t instruct
|
|||
nmd_x86_instruction instruction{};
|
||||
if (!nmd_x86_decode(&Match[instructionOffset], NMD_X86_MAXIMUM_INSTRUCTION_LENGTH, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL))
|
||||
throw std::runtime_error("Matched address does not have a valid assembly instruction");
|
||||
|
||||
|
||||
size_t numExplicitOperands = 0;
|
||||
for (size_t i = 0; i < instruction.num_operands; i++)
|
||||
numExplicitOperands += instruction.operands[i].is_implicit ? 0 : 1;
|
||||
|
|
@ -614,14 +584,17 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
|
|||
return res;
|
||||
}
|
||||
|
||||
bool utils::is_running_on_wine() {
|
||||
return g_startInfo.Platform != "WINDOWS";
|
||||
}
|
||||
|
||||
std::wstring utils::get_string_resource(uint32_t resId) {
|
||||
LPCWSTR pstr;
|
||||
const auto len = LoadStringW(g_hModule, resId, reinterpret_cast<LPWSTR>(&pstr), 0);
|
||||
return std::wstring(pstr, len);
|
||||
std::filesystem::path utils::get_module_path(HMODULE hModule) {
|
||||
std::wstring buf(MAX_PATH, L'\0');
|
||||
while (true) {
|
||||
if (const auto res = GetModuleFileNameW(hModule, &buf[0], static_cast<int>(buf.size())); !res)
|
||||
throw std::runtime_error(std::format("GetModuleFileName failure: 0x{:X}", GetLastError()));
|
||||
else if (res < buf.size()) {
|
||||
buf.resize(res);
|
||||
return buf;
|
||||
} else
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
HWND utils::try_find_game_window() {
|
||||
|
|
@ -647,7 +620,7 @@ void utils::wait_for_game_window() {
|
|||
|
||||
std::wstring utils::escape_shell_arg(const std::wstring& arg) {
|
||||
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||
|
||||
|
||||
std::wstring res;
|
||||
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
||||
res.append(arg);
|
||||
|
|
@ -699,22 +672,3 @@ std::wstring utils::format_win32_error(DWORD err) {
|
|||
|
||||
return std::format(L"Win32 error ({}=0x{:X})", err, err);
|
||||
}
|
||||
|
||||
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context()
|
||||
: scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) {
|
||||
}
|
||||
|
||||
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT context) {
|
||||
const auto user32 = GetModuleHandleW(L"user32.dll");
|
||||
m_setThreadDpiAwarenessContext =
|
||||
user32
|
||||
? reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
|
||||
GetProcAddress(user32, "SetThreadDpiAwarenessContext"))
|
||||
: nullptr;
|
||||
m_old = m_setThreadDpiAwarenessContext ? m_setThreadDpiAwarenessContext(context) : DPI_AWARENESS_CONTEXT_UNAWARE;
|
||||
}
|
||||
|
||||
utils::scoped_dpi_awareness_context::~scoped_dpi_awareness_context() {
|
||||
if (m_setThreadDpiAwarenessContext)
|
||||
m_setThreadDpiAwarenessContext(m_old);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
|
@ -8,7 +7,6 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "error_info.h"
|
||||
#include "unicode.h"
|
||||
|
||||
namespace utils {
|
||||
|
|
@ -20,13 +18,14 @@ namespace utils {
|
|||
loaded_module(void* hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
|
||||
loaded_module(size_t hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
|
||||
|
||||
DalamudExpected<std::filesystem::path> path() const;
|
||||
std::filesystem::path path() const;
|
||||
|
||||
bool is_current_process() const { return m_hModule == GetModuleHandleW(nullptr); }
|
||||
bool owns_address(const void* pAddress) const;
|
||||
|
||||
operator HMODULE() const { return m_hModule; }
|
||||
operator bool() const { return m_hModule; }
|
||||
operator HMODULE() const {
|
||||
return m_hModule;
|
||||
}
|
||||
|
||||
size_t address_int() const { return reinterpret_cast<size_t>(m_hModule); }
|
||||
size_t image_size() const { return is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage; }
|
||||
|
|
@ -59,9 +58,9 @@ namespace utils {
|
|||
void* get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const;
|
||||
template<typename TFn> TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast<TFn**>(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); }
|
||||
|
||||
[[nodiscard]] DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
|
||||
[[nodiscard]] DalamudExpected<std::wstring> get_description() const;
|
||||
[[nodiscard]] DalamudExpected<const VS_FIXEDFILEINFO&> get_file_version() const;
|
||||
[[nodiscard]] std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
|
||||
[[nodiscard]] std::wstring get_description() const;
|
||||
[[nodiscard]] VS_FIXEDFILEINFO get_file_version() const;
|
||||
|
||||
static loaded_module current_process();
|
||||
static std::vector<loaded_module> all_modules();
|
||||
|
|
@ -268,9 +267,7 @@ namespace utils {
|
|||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
bool is_running_on_wine();
|
||||
|
||||
std::wstring get_string_resource(uint32_t resId);
|
||||
std::filesystem::path get_module_path(HMODULE hModule);
|
||||
|
||||
/// @brief Find the game main window.
|
||||
/// @return Handle to the game main window, or nullptr if it doesn't exist (yet).
|
||||
|
|
@ -281,18 +278,4 @@ namespace utils {
|
|||
std::wstring escape_shell_arg(const std::wstring& arg);
|
||||
|
||||
std::wstring format_win32_error(DWORD err);
|
||||
|
||||
class scoped_dpi_awareness_context {
|
||||
DPI_AWARENESS_CONTEXT m_old;
|
||||
decltype(&SetThreadDpiAwarenessContext) m_setThreadDpiAwarenessContext;
|
||||
|
||||
public:
|
||||
scoped_dpi_awareness_context();
|
||||
scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT);
|
||||
~scoped_dpi_awareness_context();
|
||||
scoped_dpi_awareness_context(const scoped_dpi_awareness_context&) = delete;
|
||||
scoped_dpi_awareness_context(scoped_dpi_awareness_context&&) = delete;
|
||||
scoped_dpi_awareness_context& operator=(const scoped_dpi_awareness_context&) = delete;
|
||||
scoped_dpi_awareness_context& operator=(scoped_dpi_awareness_context&&) = delete;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ HANDLE g_crashhandler_process = nullptr;
|
|||
HANDLE g_crashhandler_event = nullptr;
|
||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||
|
||||
wchar_t g_external_event_info[16384] = L"";
|
||||
|
||||
std::recursive_mutex g_exception_handler_mutex;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
||||
|
|
@ -104,13 +102,9 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
|
|||
return false;
|
||||
}
|
||||
|
||||
static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstring>& args)
|
||||
static void append_injector_launch_args(std::vector<std::wstring>& args)
|
||||
{
|
||||
if (auto path = utils::loaded_module::current_process().path())
|
||||
args.emplace_back(L"--game=\"" + path->wstring() + L"\"");
|
||||
else
|
||||
return DalamudUnexpected(std::in_place, std::move(path.error()));
|
||||
|
||||
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
|
||||
switch (g_startInfo.DalamudLoadMethod) {
|
||||
case DalamudStartInfo::LoadMethod::Entrypoint:
|
||||
args.emplace_back(L"--mode=entrypoint");
|
||||
|
|
@ -124,7 +118,6 @@ static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstrin
|
|||
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
|
||||
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
|
||||
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
|
||||
args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert<std::wstring>(g_startInfo.TempDirectory) + L"\"");
|
||||
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
|
||||
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
|
||||
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
|
||||
|
|
@ -162,8 +155,6 @@ static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstrin
|
|||
args.emplace_back(szArgList[i]);
|
||||
LocalFree(szArgList);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||
|
|
@ -193,11 +184,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
|||
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||
|
||||
std::wstring stackTrace;
|
||||
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||
{
|
||||
stackTrace = std::wstring(g_external_event_info);
|
||||
}
|
||||
else if (!g_clr)
|
||||
if (!g_clr)
|
||||
{
|
||||
stackTrace = L"(no CLR stack trace available)";
|
||||
}
|
||||
|
|
@ -258,12 +245,6 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
|||
|
||||
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||
{
|
||||
// special case for CLR exceptions, always trigger crash handler
|
||||
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||
{
|
||||
return exception_handler(ex);
|
||||
}
|
||||
|
||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||
{
|
||||
// pass
|
||||
|
|
@ -281,7 +262,7 @@ LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
|||
|
||||
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
|
||||
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
return exception_handler(ex);
|
||||
|
|
@ -310,7 +291,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
|
||||
{
|
||||
hWritePipe.emplace(hWritePipeRaw, &CloseHandle);
|
||||
|
||||
|
||||
if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
|
||||
{
|
||||
hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle);
|
||||
|
|
@ -328,9 +309,9 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
}
|
||||
|
||||
// additional information
|
||||
STARTUPINFOEXW siex{};
|
||||
STARTUPINFOEXW siex{};
|
||||
PROCESS_INFORMATION pi{};
|
||||
|
||||
|
||||
siex.StartupInfo.cb = sizeof siex;
|
||||
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
||||
siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE;
|
||||
|
|
@ -377,20 +358,11 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast<size_t>(hInheritableCurrentProcess)));
|
||||
args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast<size_t>(hReadPipeInheritable->get())));
|
||||
args.emplace_back(std::format(L"--asset-directory={}", unicode::convert<std::wstring>(g_startInfo.AssetDirectory)));
|
||||
if (const auto path = utils::loaded_module(g_hModule).path()) {
|
||||
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
|
||||
? path->parent_path().wstring()
|
||||
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
|
||||
} else {
|
||||
logging::W("Failed to read path of the Dalamud Boot module: {}", path.error().describe());
|
||||
return false;
|
||||
}
|
||||
|
||||
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
|
||||
? utils::loaded_module(g_hModule).path().parent_path().wstring()
|
||||
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
|
||||
args.emplace_back(L"--");
|
||||
if (auto r = append_injector_launch_args(args); !r) {
|
||||
logging::W("Failed to generate injector launch args: {}", r.error().describe());
|
||||
return false;
|
||||
}
|
||||
append_injector_launch_args(args);
|
||||
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
|
|
@ -398,7 +370,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
argstr.push_back(L' ');
|
||||
}
|
||||
argstr.pop_back();
|
||||
|
||||
|
||||
if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr))
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError());
|
||||
|
|
@ -413,7 +385,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
TRUE, // Set handle inheritance to FALSE
|
||||
EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W)
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
nullptr, // Use parent's starting directory
|
||||
&siex.StartupInfo, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
||||
))
|
||||
|
|
@ -429,7 +401,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
|||
}
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
|
||||
g_crashhandler_process = pi.hProcess;
|
||||
g_crashhandler_pipe_write = hWritePipe->release();
|
||||
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
|
||||
|
|
@ -447,16 +419,3 @@ bool veh::remove_handler()
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void veh::raise_external_event(const std::wstring& info)
|
||||
{
|
||||
const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1);
|
||||
wcsncpy_s(g_external_event_info, info.c_str(), info_size);
|
||||
RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport) void BootVehRaiseExternalEventW(LPCWSTR info)
|
||||
{
|
||||
const std::wstring info_wstr(info);
|
||||
veh::raise_external_event(info_wstr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@ namespace veh
|
|||
{
|
||||
bool add_handler(bool doFullDump, const std::string& workingDirectory);
|
||||
bool remove_handler();
|
||||
void raise_external_event(const std::wstring& info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@
|
|||
#include "ntdll.h"
|
||||
#include "utils.h"
|
||||
|
||||
template<typename T>
|
||||
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
|
||||
if (t.empty())
|
||||
throw std::runtime_error(std::format("Unexpected empty span found: {}", descr));
|
||||
return t;
|
||||
}
|
||||
void xivfixes::unhook_dll(bool bApply) {
|
||||
static const auto LogTag = "[xivfixes:unhook_dll]";
|
||||
static const auto LogTagW = L"[xivfixes:unhook_dll]";
|
||||
|
|
@ -17,90 +23,77 @@ void xivfixes::unhook_dll(bool bApply) {
|
|||
|
||||
const auto mods = utils::loaded_module::all_modules();
|
||||
|
||||
for (size_t i = 0; i < mods.size(); i++) {
|
||||
const auto& mod = mods[i];
|
||||
const auto path = mod.path();
|
||||
if (!path) {
|
||||
logging::W(
|
||||
"{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}",
|
||||
LogTag,
|
||||
i + 1,
|
||||
mods.size(),
|
||||
mod.address_int(),
|
||||
path.error().describe());
|
||||
const auto test_module = [&](size_t i, const utils::loaded_module & mod) {
|
||||
std::filesystem::path path;
|
||||
try {
|
||||
path = mod.path();
|
||||
std::wstring version, description;
|
||||
try {
|
||||
version = utils::format_file_version(mod.get_file_version());
|
||||
} catch (...) {
|
||||
version = L"<unknown>";
|
||||
}
|
||||
|
||||
try {
|
||||
description = mod.get_description();
|
||||
} catch (...) {
|
||||
description = L"<unknown>";
|
||||
}
|
||||
|
||||
logging::I(R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))", LogTagW, i + 1, mods.size(), mod.address_int(), mod.address_int() + mod.image_size(), mod.image_size(), path.wstring(), description, version);
|
||||
} catch (const std::exception& e) {
|
||||
logging::W("{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}", LogTag, i + 1, mods.size(), mod.address_int(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto version = mod.get_file_version()
|
||||
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
|
||||
.value_or(L"<unknown>");
|
||||
const auto moduleName = unicode::convert<std::string>(path.filename().wstring());
|
||||
|
||||
const auto description = mod.get_description()
|
||||
.value_or(L"<unknown>");
|
||||
|
||||
logging::I(
|
||||
R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))",
|
||||
LogTagW,
|
||||
i + 1,
|
||||
mods.size(),
|
||||
mod.address_int(),
|
||||
mod.address_int() + mod.image_size(),
|
||||
mod.image_size(),
|
||||
path->wstring(),
|
||||
description,
|
||||
version);
|
||||
|
||||
const auto moduleName = unicode::convert<std::string>(path->filename().wstring());
|
||||
|
||||
const auto& sectionHeader = mod.section_header(".text");
|
||||
const auto section = mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize);
|
||||
if (section.empty()) {
|
||||
logging::W("{} Error: .text[VA:VA + VS] is empty", LogTag);
|
||||
return;
|
||||
}
|
||||
|
||||
auto hFsDllRaw = CreateFileW(path->c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
|
||||
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
auto hFsDll = std::unique_ptr<void, decltype(&CloseHandle)>(hFsDllRaw, &CloseHandle);
|
||||
std::vector<char> buf(section.size());
|
||||
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
|
||||
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
|
||||
if (read < section.size_bytes()) {
|
||||
logging::W("{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
|
||||
std::vector<char> buf;
|
||||
std::string formatBuf;
|
||||
try {
|
||||
const auto& sectionHeader = mod.section_header(".text");
|
||||
const auto section = assume_nonempty_span(mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize), ".text[VA:VA+VS]");
|
||||
auto hFsDllRaw = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
|
||||
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
logging::I("{} ReadFile: Win32 error {}", LogTagW, GetLastError());
|
||||
return;
|
||||
}
|
||||
auto hFsDll = std::unique_ptr<void, decltype(CloseHandle)*>(hFsDllRaw, &CloseHandle);
|
||||
|
||||
buf.resize(section.size());
|
||||
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
|
||||
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
|
||||
if (read < section.size_bytes()) {
|
||||
logging::W("{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
logging::I("{} ReadFile: Win32 error {}", LogTagW, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path.filename().u8string()));
|
||||
|
||||
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path->filename().u8string()));
|
||||
try {
|
||||
std::optional<utils::memory_tenderizer> tenderizer;
|
||||
std::string formatBuf;
|
||||
for (size_t inst = 0, instructionLength = 1, printed = 0; inst < buf.size(); inst += instructionLength) {
|
||||
if (section[inst] == buf[inst]) {
|
||||
for (size_t i = 0, instructionLength = 1, printed = 0; i < buf.size(); i += instructionLength) {
|
||||
if (section[i] == buf[i]) {
|
||||
instructionLength = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto rva = sectionHeader.VirtualAddress + inst;
|
||||
const auto rva = sectionHeader.VirtualAddress + i;
|
||||
nmd_x86_instruction instruction{};
|
||||
if (!nmd_x86_decode(§ion[inst], section.size() - inst, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
|
||||
if (!nmd_x86_decode(§ion[i], section.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
|
||||
instructionLength = 1;
|
||||
if (printed < 64) {
|
||||
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[inst]));
|
||||
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[i]));
|
||||
printed++;
|
||||
}
|
||||
} else {
|
||||
instructionLength = instruction.length;
|
||||
if (printed < 64) {
|
||||
formatBuf.resize(128);
|
||||
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(§ion[inst]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
|
||||
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(§ion[i]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
|
||||
formatBuf.resize(strnlen(&formatBuf[0], formatBuf.size()));
|
||||
|
||||
const auto& directory = mod.data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT);
|
||||
|
|
@ -110,25 +103,25 @@ void xivfixes::unhook_dll(bool bApply) {
|
|||
const auto functions = mod.span_as<DWORD>(exportDirectory.AddressOfFunctions, exportDirectory.NumberOfFunctions);
|
||||
|
||||
std::string resolvedExportName;
|
||||
for (size_t nameIndex = 0; nameIndex < names.size(); ++nameIndex) {
|
||||
for (size_t j = 0; j < names.size(); ++j) {
|
||||
std::string_view name;
|
||||
if (const char* pcszName = mod.address_as<char>(names[nameIndex]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
|
||||
if (const char* pcszName = mod.address_as<char>(names[j]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
|
||||
if (IsBadReadPtr(pcszName, 256)) {
|
||||
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, nameIndex);
|
||||
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, j);
|
||||
continue;
|
||||
}
|
||||
|
||||
name = std::string_view(pcszName, strnlen(pcszName, 256));
|
||||
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, nameIndex, name);
|
||||
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, j, name);
|
||||
}
|
||||
|
||||
if (ordinals[nameIndex] >= functions.size()) {
|
||||
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, nameIndex, ordinals[nameIndex], functions.size());
|
||||
if (ordinals[j] >= functions.size()) {
|
||||
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, j, ordinals[j], functions.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto rva = functions[ordinals[nameIndex]];
|
||||
if (rva == §ion[inst] - mod.address()) {
|
||||
const auto rva = functions[ordinals[j]];
|
||||
if (rva == §ion[i] - mod.address()) {
|
||||
resolvedExportName = std::format("[export:{}]", name);
|
||||
break;
|
||||
}
|
||||
|
|
@ -142,7 +135,7 @@ void xivfixes::unhook_dll(bool bApply) {
|
|||
if (doRestore) {
|
||||
if (!tenderizer)
|
||||
tenderizer.emplace(section, PAGE_EXECUTE_READWRITE);
|
||||
memcpy(§ion[inst], &buf[inst], instructionLength);
|
||||
memcpy(§ion[i], &buf[i], instructionLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +147,21 @@ void xivfixes::unhook_dll(bool bApply) {
|
|||
} catch (const std::exception& e) {
|
||||
logging::W("{} Error: {}", LogTag, e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is needed since try and __try cannot be used in the same function. Lambdas circumvent the limitation.
|
||||
const auto windows_exception_handler = [&]() {
|
||||
for (size_t i = 0; i < mods.size(); i++) {
|
||||
const auto& mod = mods[i];
|
||||
__try {
|
||||
test_module(i, mod);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
logging::W("{} Error: Access Violation", LogTag);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
windows_exception_handler();
|
||||
}
|
||||
|
||||
using TFnGetInputDeviceManager = void* ();
|
||||
|
|
@ -287,11 +294,13 @@ static bool is_xivalex(const std::filesystem::path& dllPath) {
|
|||
static bool is_openprocess_already_dealt_with() {
|
||||
static const auto s_value = [] {
|
||||
for (const auto& mod : utils::loaded_module::all_modules()) {
|
||||
const auto path = mod.path().value_or({});
|
||||
if (path.empty())
|
||||
continue;
|
||||
if (is_xivalex(path))
|
||||
return true;
|
||||
try {
|
||||
if (is_xivalex(mod.path()))
|
||||
return true;
|
||||
|
||||
} catch (...) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
|
@ -639,27 +648,6 @@ void xivfixes::symbol_load_patches(bool bApply) {
|
|||
}
|
||||
}
|
||||
|
||||
void xivfixes::disable_game_debugging_protection(bool bApply) {
|
||||
static const char* LogTag = "[xivfixes:disable_game_debugging_protection]";
|
||||
static std::optional<hooks::import_hook<decltype(IsDebuggerPresent)>> s_hookIsDebuggerPresent;
|
||||
|
||||
if (bApply) {
|
||||
if (!g_startInfo.BootEnabledGameFixes.contains("disable_game_debugging_protection")) {
|
||||
logging::I("{} Turned off via environment variable.", LogTag);
|
||||
return;
|
||||
}
|
||||
|
||||
s_hookIsDebuggerPresent.emplace("kernel32.dll!IsDebuggerPresent", "kernel32.dll", "IsDebuggerPresent", 0);
|
||||
s_hookIsDebuggerPresent->set_detour([]() { return false; });
|
||||
logging::I("{} Enable", LogTag);
|
||||
} else {
|
||||
if (s_hookIsDebuggerPresent) {
|
||||
logging::I("{} Disable", LogTag);
|
||||
s_hookIsDebuggerPresent.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xivfixes::apply_all(bool bApply) {
|
||||
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
||||
{
|
||||
|
|
@ -670,7 +658,6 @@ void xivfixes::apply_all(bool bApply) {
|
|||
{ "backup_userdata_save", &backup_userdata_save },
|
||||
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes },
|
||||
{ "symbol_load_patches", &symbol_load_patches },
|
||||
{ "disable_game_debugging_protection", &disable_game_debugging_protection },
|
||||
}
|
||||
) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ namespace xivfixes {
|
|||
void backup_userdata_save(bool bApply);
|
||||
void prevent_icmphandle_crashes(bool bApply);
|
||||
void symbol_load_patches(bool bApply);
|
||||
void disable_game_debugging_protection(bool bApply);
|
||||
|
||||
void apply_all(bool bApply);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Common.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -16,7 +15,7 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public DalamudStartInfo()
|
||||
{
|
||||
this.Platform = OSPlatform.Create("UNKNOWN");
|
||||
// ignored
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -34,12 +33,6 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public string? ConfigurationPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the directory for temporary files. This directory needs to exist and be writable to the user.
|
||||
/// It should also be predictable and easy for launchers to find.
|
||||
/// </summary>
|
||||
public string? TempDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the log files.
|
||||
/// </summary>
|
||||
|
|
@ -65,12 +58,6 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public ClientLanguage Language { get; set; } = ClientLanguage.English;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying platform<72>Dalamud runs on.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(OSPlatformConverter))]
|
||||
public OSPlatform Platform { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current game version code.
|
||||
/// </summary>
|
||||
|
|
@ -107,11 +94,6 @@ public record DalamudStartInfo
|
|||
/// </summary>
|
||||
public bool BootShowConsole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to enable D3D11 and DXGI debugging if possible.
|
||||
/// </summary>
|
||||
public bool BootDebugDirectX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
|
||||
/// </summary>
|
||||
|
|
@ -138,7 +120,7 @@ public record DalamudStartInfo
|
|||
public bool BootVehFull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether ETW should be enabled.
|
||||
/// Gets or sets a value indicating whether or not ETW should be enabled.
|
||||
/// </summary>
|
||||
public bool BootEnableEtw { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="OSPlatform"/> to and from a string (e.g. <c>"FreeBSD"</c>).
|
||||
/// </summary>
|
||||
public sealed class OSPlatformConverter : JsonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
else if (value is OSPlatform)
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException("Expected OSPlatform object value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the JSON representation of the object.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
|
||||
/// <param name="serializer">The calling serializer.</param>
|
||||
/// <returns>The object value.</returns>
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
return OSPlatform.Create((string)reader.Value!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. Token: {reader.TokenType}, Value: {reader.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can convert the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="objectType">Type of the object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(OSPlatform);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Common.Util;
|
||||
|
||||
public static class EnvironmentUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to get an environment variable using the Try pattern.
|
||||
/// </summary>
|
||||
/// <param name="variableName">The env var to get.</param>
|
||||
/// <param name="value">An output containing the env var, if present.</param>
|
||||
/// <returns>A boolean indicating whether the var was present.</returns>
|
||||
public static bool TryGetEnvironmentVariable(string variableName, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
value = Environment.GetEnvironmentVariable(variableName);
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Dalamud.CorePlugin</AssemblyName>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
|
@ -25,9 +27,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lumina" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PackageReference Include="Lumina" Version="5.6.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
@ -37,6 +40,15 @@
|
|||
<ProjectReference Include="..\Dalamud\Dalamud.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ namespace Dalamud.CorePlugin
|
|||
#else
|
||||
|
||||
private readonly WindowSystem windowSystem = new("Dalamud.CorePlugin");
|
||||
private readonly PluginWindow window;
|
||||
|
||||
private Localization localization;
|
||||
|
||||
private IPluginLog pluginLog;
|
||||
|
|
@ -65,8 +63,7 @@ namespace Dalamud.CorePlugin
|
|||
this.Interface = pluginInterface;
|
||||
this.pluginLog = log;
|
||||
|
||||
this.window = new PluginWindow();
|
||||
this.windowSystem.AddWindow(this.window);
|
||||
this.windowSystem.AddWindow(new PluginWindow());
|
||||
|
||||
this.Interface.UiBuilder.Draw += this.OnDraw;
|
||||
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||
|
|
@ -139,12 +136,12 @@ namespace Dalamud.CorePlugin
|
|||
{
|
||||
this.pluginLog.Information("Command called!");
|
||||
|
||||
this.window.IsOpen ^= true;
|
||||
// this.window.IsOpen = true;
|
||||
}
|
||||
|
||||
private void OnOpenConfigUi()
|
||||
{
|
||||
this.window.IsOpen = true;
|
||||
// this.window.IsOpen = true;
|
||||
}
|
||||
|
||||
private void OnOpenMainUi()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.CorePlugin
|
||||
{
|
||||
|
|
|
|||
110
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
110
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
|
||||
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
|
||||
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<TargetName>Dalamud.Injector</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
||||
<IntDir>obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||
<OptimizeReferences>false</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ItemGroup>
|
||||
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
||||
<Link>nethost.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h" />
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||
</Target>
|
||||
</Project>
|
||||
67
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj.filters
Normal file
67
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj.filters
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
|
||||
<Extensions>ico;rc</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
48
Dalamud.Injector.Boot/main.cpp
Normal file
48
Dalamud.Injector.Boot/main.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <filesystem>
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include "..\Dalamud.Boot\logging.h"
|
||||
#include "..\lib\CoreCLR\CoreCLR.h"
|
||||
#include "..\lib\CoreCLR\boot.h"
|
||||
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
// Take care: don't redirect stderr/out here, we need to write our pid to stdout for XL to read
|
||||
//logging::start_file_logging("dalamud.injector.boot.log", false);
|
||||
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at : " __DATE__ "@" __TIME__);
|
||||
|
||||
wchar_t _module_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
|
||||
std::filesystem::path fs_module_path(_module_path);
|
||||
|
||||
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
|
||||
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
|
||||
|
||||
// =========================================================================== //
|
||||
|
||||
void* entrypoint_vfn;
|
||||
const auto result = InitializeClrAndGetEntryPoint(
|
||||
GetModuleHandleW(nullptr),
|
||||
false,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
|
||||
L"Main",
|
||||
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
||||
&entrypoint_vfn);
|
||||
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
|
||||
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||
|
||||
logging::I("Running Dalamud Injector...");
|
||||
const auto ret = entrypoint_fn(argc, argv);
|
||||
logging::I("Done!");
|
||||
|
||||
return ret;
|
||||
}
|
||||
1
Dalamud.Injector.Boot/pch.h
Normal file
1
Dalamud.Injector.Boot/pch.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
||||
1
Dalamud.Injector.Boot/resources.rc
Normal file
1
Dalamud.Injector.Boot/resources.rc
Normal file
|
|
@ -0,0 +1 @@
|
|||
MAINICON ICON "dalamud.ico"
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
|
|
@ -13,13 +17,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>..\bin\$(Configuration)\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<ApplicationIcon>dalamud.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Documentation">
|
||||
|
|
@ -42,6 +45,10 @@
|
|||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
|
||||
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Warnings">
|
||||
<NoWarn>IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702</NoWarn>
|
||||
|
|
@ -53,18 +60,17 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Iced" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" />
|
||||
<PackageReference Include="PeNet" />
|
||||
<PackageReference Include="Reloaded.Memory" />
|
||||
<PackageReference Include="Reloaded.Memory.Buffers" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PackageReference Include="Iced" Version="1.17.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="PeNet" Version="2.6.4" />
|
||||
<PackageReference Include="Reloaded.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Reloaded.Memory.Buffers" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -11,34 +11,47 @@ using System.Text.RegularExpressions;
|
|||
|
||||
using Dalamud.Common;
|
||||
using Dalamud.Common.Game;
|
||||
using Dalamud.Common.Util;
|
||||
using Newtonsoft.Json;
|
||||
using Reloaded.Memory.Buffers;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
using static Dalamud.Injector.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Entrypoint to the program.
|
||||
/// </summary>
|
||||
public sealed class Program
|
||||
public sealed class EntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
|
||||
/// </summary>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">char** string arguments.</param>
|
||||
/// <returns>Return value (HRESULT).</returns>
|
||||
public delegate int MainDelegate(int argc, IntPtr argvPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Start the Dalamud injector.
|
||||
/// </summary>
|
||||
/// <param name="argsArray">Command line arguments.</param>
|
||||
/// <param name="argc">Count of arguments.</param>
|
||||
/// <param name="argvPtr">byte** string arguments.</param>
|
||||
/// <returns>Return value (HRESULT).</returns>
|
||||
public static int Main(string[] argsArray)
|
||||
public static int Main(int argc, IntPtr argvPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
// API14 TODO: Refactor
|
||||
var args = argsArray.ToList();
|
||||
args.Insert(0, Assembly.GetExecutingAssembly().Location);
|
||||
List<string> args = new(argc);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var argv = (IntPtr*)argvPtr;
|
||||
for (var i = 0; i < argc; i++)
|
||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||
}
|
||||
|
||||
Init(args);
|
||||
args.Remove("-v"); // Remove "verbose" flag
|
||||
|
|
@ -76,13 +89,11 @@ namespace Dalamud.Injector
|
|||
|
||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||
// Remove already handled arguments
|
||||
args.Remove("--debug-directx");
|
||||
args.Remove("--console");
|
||||
args.Remove("--msgbox1");
|
||||
args.Remove("--msgbox2");
|
||||
args.Remove("--msgbox3");
|
||||
args.Remove("--etw");
|
||||
args.Remove("--no-legacy-corrupted-state-exceptions");
|
||||
args.Remove("--veh");
|
||||
args.Remove("--veh-full");
|
||||
args.Remove("--no-plugin");
|
||||
|
|
@ -251,35 +262,6 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static OSPlatform DetectPlatformHeuristic()
|
||||
{
|
||||
var ntdll = Windows.Win32.PInvoke.GetModuleHandle("ntdll.dll");
|
||||
var wineServerCallPtr = Windows.Win32.PInvoke.GetProcAddress(ntdll, "wine_server_call");
|
||||
var wineGetHostVersionPtr = Windows.Win32.PInvoke.GetProcAddress(ntdll, "wine_get_host_version");
|
||||
var winePlatform = GetWinePlatform(wineGetHostVersionPtr);
|
||||
var isWine = wineServerCallPtr != nint.Zero;
|
||||
|
||||
static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr)
|
||||
{
|
||||
if (wineGetHostVersionPtr == nint.Zero) return null;
|
||||
|
||||
var methodDelegate = (delegate* unmanaged[Cdecl]<out char*, out char*, void>)wineGetHostVersionPtr;
|
||||
methodDelegate(out var platformPtr, out var _);
|
||||
|
||||
if (platformPtr == null) return null;
|
||||
|
||||
return Marshal.PtrToStringAnsi((nint)platformPtr);
|
||||
}
|
||||
|
||||
if (!isWine)
|
||||
return OSPlatform.Windows;
|
||||
|
||||
if (winePlatform == "Darwin")
|
||||
return OSPlatform.OSX;
|
||||
|
||||
return OSPlatform.Linux;
|
||||
}
|
||||
|
||||
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
|
||||
{
|
||||
int len;
|
||||
|
|
@ -291,19 +273,13 @@ namespace Dalamud.Injector
|
|||
var configurationPath = startInfo.ConfigurationPath;
|
||||
var pluginDirectory = startInfo.PluginDirectory;
|
||||
var assetDirectory = startInfo.AssetDirectory;
|
||||
var tempDirectory = startInfo.TempDirectory;
|
||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||
var logName = startInfo.LogName;
|
||||
var logPath = startInfo.LogPath;
|
||||
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
|
||||
var platformStr = startInfo.Platform.ToString().ToLowerInvariant();
|
||||
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
|
||||
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
|
||||
|
||||
// env vars are brought in prior to launch args, since args can override them.
|
||||
if (EnvironmentUtils.TryGetEnvironmentVariable("XL_PLATFORM", out var xlPlatformEnv))
|
||||
platformStr = xlPlatformEnv.ToLowerInvariant();
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
||||
|
|
@ -322,10 +298,6 @@ namespace Dalamud.Injector
|
|||
{
|
||||
assetDirectory = args[i][key.Length..];
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-temp-directory="))
|
||||
{
|
||||
tempDirectory = args[i][key.Length..];
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
||||
{
|
||||
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
||||
|
|
@ -334,10 +306,6 @@ namespace Dalamud.Injector
|
|||
{
|
||||
languageStr = args[i][key.Length..].ToLowerInvariant();
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-platform="))
|
||||
{
|
||||
platformStr = args[i][key.Length..].ToLowerInvariant();
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
|
||||
{
|
||||
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
|
||||
|
|
@ -409,38 +377,11 @@ namespace Dalamud.Injector
|
|||
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
|
||||
}
|
||||
|
||||
OSPlatform platform;
|
||||
|
||||
// covers both win32 and Windows
|
||||
if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.Windows;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.Linux;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.OSX;
|
||||
}
|
||||
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len])
|
||||
{
|
||||
platform = OSPlatform.OSX;
|
||||
}
|
||||
else
|
||||
{
|
||||
platform = DetectPlatformHeuristic();
|
||||
Log.Warning("Heuristically determined host system platform as {platform}", platform);
|
||||
}
|
||||
|
||||
startInfo.WorkingDirectory = workingDirectory;
|
||||
startInfo.ConfigurationPath = configurationPath;
|
||||
startInfo.PluginDirectory = pluginDirectory;
|
||||
startInfo.AssetDirectory = assetDirectory;
|
||||
startInfo.TempDirectory = tempDirectory;
|
||||
startInfo.Language = clientLanguage;
|
||||
startInfo.Platform = platform;
|
||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||
startInfo.GameVersion = null;
|
||||
startInfo.TroubleshootingPackData = troubleshootingData;
|
||||
|
|
@ -456,7 +397,6 @@ namespace Dalamud.Injector
|
|||
startInfo.LogName ??= string.Empty;
|
||||
|
||||
// Set boot defaults
|
||||
startInfo.BootDebugDirectX = args.Contains("--debug-directx");
|
||||
startInfo.BootShowConsole = args.Contains("--console");
|
||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
||||
|
|
@ -469,7 +409,6 @@ namespace Dalamud.Injector
|
|||
"backup_userdata_save",
|
||||
"prevent_icmphandle_crashes",
|
||||
"symbol_load_patches",
|
||||
"disable_game_debugging_protection",
|
||||
};
|
||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||
|
|
@ -524,14 +463,13 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory=path]");
|
||||
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
|
||||
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
|
||||
|
||||
Console.WriteLine("Verbose logging:\t[-v]");
|
||||
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
|
||||
Console.WriteLine("Enable ETW:\t[--etw]");
|
||||
Console.WriteLine("Disable legacy corrupted state exceptions:\t[--no-legacy-corrupted-state-exceptions]");
|
||||
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]");
|
||||
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
|
||||
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
|
||||
|
|
@ -612,13 +550,10 @@ namespace Dalamud.Injector
|
|||
|
||||
if (warnManualInjection)
|
||||
{
|
||||
var result = Windows.Win32.PInvoke.MessageBox(
|
||||
HWND.Null,
|
||||
$"Take care: you are manually injecting Dalamud into FFXIV({string.Join(", ", processes.Select(x => $"{x.Id}"))}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.",
|
||||
"Dalamud",
|
||||
MESSAGEBOX_STYLE.MB_ICONWARNING | MESSAGEBOX_STYLE.MB_OKCANCEL);
|
||||
var result = MessageBoxW(IntPtr.Zero, $"Take care: you are manually injecting Dalamud into FFXIV({string.Join(", ", processes.Select(x => $"{x.Id}"))}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.", "Dalamud", MessageBoxType.IconWarning | MessageBoxType.OkCancel);
|
||||
|
||||
if (result == MESSAGEBOX_RESULT.IDCANCEL)
|
||||
// IDCANCEL
|
||||
if (result == 2)
|
||||
{
|
||||
Log.Information("User cancelled injection");
|
||||
return -2;
|
||||
|
|
@ -794,42 +729,15 @@ namespace Dalamud.Injector
|
|||
{
|
||||
try
|
||||
{
|
||||
if (dalamudStartInfo.Platform == OSPlatform.Windows)
|
||||
{
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
|
||||
gamePath = Path.Combine(
|
||||
JsonSerializer.CreateDefault()
|
||||
.Deserialize<Dictionary<string, string>>(
|
||||
new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"],
|
||||
"game",
|
||||
"ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
|
||||
}
|
||||
else if (dalamudStartInfo.Platform == OSPlatform.Linux)
|
||||
{
|
||||
var homeDir = $"Z:\\home\\{Environment.UserName}";
|
||||
var xivlauncherDir = Path.Combine(homeDir, ".xlcore");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini");
|
||||
var config = File.ReadAllLines(launcherConfigPath)
|
||||
.Where(line => line.Contains('='))
|
||||
.ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]);
|
||||
gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var homeDir = $"Z:\\Users\\{Environment.UserName}";
|
||||
var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac");
|
||||
// we could try to parse the binary plist file here if we really wanted to...
|
||||
gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using default game installation path from XOM: {0}", gamePath);
|
||||
}
|
||||
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
|
||||
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
|
||||
gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize<Dictionary<string, string>>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe");
|
||||
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g");
|
||||
Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -884,6 +792,20 @@ namespace Dalamud.Injector
|
|||
if (encryptArguments)
|
||||
{
|
||||
var rawTickCount = (uint)Environment.TickCount;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
[System.Runtime.InteropServices.DllImport("c")]
|
||||
#pragma warning disable SA1300
|
||||
static extern ulong clock_gettime_nsec_np(int clockId);
|
||||
#pragma warning restore SA1300
|
||||
|
||||
const int CLOCK_MONOTONIC_RAW = 4;
|
||||
var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000;
|
||||
Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
|
||||
rawTickCount = (uint)rawTickCountFixed;
|
||||
}
|
||||
|
||||
var ticks = rawTickCount & 0xFFFF_FFFFu;
|
||||
var key = ticks & 0xFFFF_0000u;
|
||||
gameArguments.Insert(0, $"T={ticks}");
|
||||
|
|
@ -928,48 +850,30 @@ namespace Dalamud.Injector
|
|||
Inject(process, startInfo, false);
|
||||
}
|
||||
|
||||
var processHandleForOwner = HANDLE.Null;
|
||||
var processHandleForOwner = IntPtr.Zero;
|
||||
if (handleOwner != IntPtr.Zero)
|
||||
{
|
||||
unsafe
|
||||
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, process.Handle, handleOwner, out processHandleForOwner, 0, false, DuplicateOptions.SameAccess))
|
||||
{
|
||||
if (!Windows.Win32.PInvoke.DuplicateHandle(
|
||||
new HANDLE(Process.GetCurrentProcess().Handle.ToPointer()),
|
||||
new HANDLE(process.Handle.ToPointer()),
|
||||
new HANDLE(handleOwner),
|
||||
&processHandleForOwner,
|
||||
0,
|
||||
false,
|
||||
DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
|
||||
}
|
||||
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {(IntPtr)processHandleForOwner}}}");
|
||||
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {processHandleForOwner}}}");
|
||||
|
||||
Log.CloseAndFlush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static unsafe Process GetInheritableCurrentProcessHandle()
|
||||
private static Process GetInheritableCurrentProcessHandle()
|
||||
{
|
||||
var currentProcessHandle = new HANDLE(Process.GetCurrentProcess().Handle.ToPointer());
|
||||
var inheritableHandle = HANDLE.Null;
|
||||
if (!Windows.Win32.PInvoke.DuplicateHandle(
|
||||
currentProcessHandle,
|
||||
currentProcessHandle,
|
||||
currentProcessHandle,
|
||||
&inheritableHandle,
|
||||
0,
|
||||
true,
|
||||
DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
|
||||
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, out var inheritableCurrentProcessHandle, 0, true, DuplicateOptions.SameAccess))
|
||||
{
|
||||
throw new Win32Exception("Failed to call DuplicateHandle");
|
||||
Log.Error("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ExistingProcess(inheritableHandle);
|
||||
return new ExistingProcess(inheritableCurrentProcessHandle);
|
||||
}
|
||||
|
||||
private static int ProcessLaunchTestCommand(List<string> args)
|
||||
|
|
@ -1060,13 +964,13 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
|
||||
var exitCode = injector.CallRemoteFunction(initAddress, startInfoAddress);
|
||||
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
|
||||
|
||||
// ======================================================
|
||||
|
||||
if (exitCode > 0)
|
||||
{
|
||||
Log.Error("Dalamud.Boot::Initialize returned {ExitCode}", exitCode);
|
||||
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -13,8 +13,8 @@ using Reloaded.Memory.Buffers;
|
|||
using Reloaded.Memory.Sources;
|
||||
using Reloaded.Memory.Utilities;
|
||||
using Serilog;
|
||||
using Windows.Win32.Foundation;
|
||||
|
||||
using static Dalamud.Injector.NativeFunctions;
|
||||
using static Iced.Intel.AssemblerRegisters;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
|
|
@ -88,7 +88,7 @@ namespace Dalamud.Injector
|
|||
if (lpParameter == 0)
|
||||
throw new Exception("Unable to allocate LoadLibraryW parameter");
|
||||
|
||||
var err = this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter);
|
||||
this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter, out var err);
|
||||
this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr, out address);
|
||||
if (address == IntPtr.Zero)
|
||||
throw new Exception($"LoadLibraryW(\"{modulePath}\") failure: {new Win32Exception((int)err).Message} ({err})");
|
||||
|
|
@ -108,7 +108,7 @@ namespace Dalamud.Injector
|
|||
if (lpParameter == 0)
|
||||
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
|
||||
|
||||
var err = this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter);
|
||||
this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter, out var err);
|
||||
this.extMemory.Read<nuint>(this.getProcAddressRetPtr, out address);
|
||||
if (address == 0)
|
||||
throw new Exception($"GetProcAddress(0x{module:X}, \"{functionName}\") failure: {new Win32Exception((int)err).Message} ({err})");
|
||||
|
|
@ -119,30 +119,27 @@ namespace Dalamud.Injector
|
|||
/// </summary>
|
||||
/// <param name="methodAddress">Method address.</param>
|
||||
/// <param name="parameterAddress">Parameter address.</param>
|
||||
/// <returns>Thread exit code.</returns>
|
||||
public unsafe uint CallRemoteFunction(nuint methodAddress, nuint parameterAddress)
|
||||
/// <param name="exitCode">Thread exit code.</param>
|
||||
public void CallRemoteFunction(nuint methodAddress, nuint parameterAddress, out uint exitCode)
|
||||
{
|
||||
// Create and initialize a thread at our address and parameter address.
|
||||
var threadHandle = Windows.Win32.PInvoke.CreateRemoteThread(
|
||||
new HANDLE(this.targetProcess.Handle.ToPointer()),
|
||||
null,
|
||||
var threadHandle = CreateRemoteThread(
|
||||
this.targetProcess.Handle,
|
||||
IntPtr.Zero,
|
||||
UIntPtr.Zero,
|
||||
(delegate* unmanaged[Stdcall]<void*, uint>)methodAddress,
|
||||
parameterAddress.ToPointer(),
|
||||
0, // Run immediately
|
||||
null);
|
||||
methodAddress,
|
||||
parameterAddress,
|
||||
CreateThreadFlags.RunImmediately,
|
||||
out _);
|
||||
|
||||
if (threadHandle == IntPtr.Zero)
|
||||
throw new Exception($"CreateRemoteThread failure: {Marshal.GetLastWin32Error()}");
|
||||
|
||||
_ = Windows.Win32.PInvoke.WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
|
||||
|
||||
uint exitCode = 0;
|
||||
if (!Windows.Win32.PInvoke.GetExitCodeThread(threadHandle, &exitCode))
|
||||
throw new Exception($"GetExitCodeThread failure: {Marshal.GetLastWin32Error()}");
|
||||
GetExitCodeThread(threadHandle, out exitCode);
|
||||
|
||||
Windows.Win32.PInvoke.CloseHandle(threadHandle);
|
||||
return exitCode;
|
||||
CloseHandle(threadHandle);
|
||||
}
|
||||
|
||||
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)
|
||||
|
|
|
|||
914
Dalamud.Injector/NativeFunctions.cs
Normal file
914
Dalamud.Injector/NativeFunctions.cs
Normal file
|
|
@ -0,0 +1,914 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Native user32 functions.
|
||||
/// </summary>
|
||||
internal static partial class NativeFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// MB_* from winuser.
|
||||
/// </summary>
|
||||
public enum MessageBoxType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value for any of the various subtypes.
|
||||
/// </summary>
|
||||
DefaultValue = 0x0,
|
||||
|
||||
// To indicate the buttons displayed in the message box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Abort, Retry, and Ignore.
|
||||
/// </summary>
|
||||
AbortRetryIgnore = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead
|
||||
/// of MB_ABORTRETRYIGNORE.
|
||||
/// </summary>
|
||||
CancelTryContinue = 0x6,
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends
|
||||
/// a WM_HELP message to the owner.
|
||||
/// </summary>
|
||||
Help = 0x4000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains one push button: OK. This is the default.
|
||||
/// </summary>
|
||||
Ok = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: OK and Cancel.
|
||||
/// </summary>
|
||||
OkCancel = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: Retry and Cancel.
|
||||
/// </summary>
|
||||
RetryCancel = 0x5,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: Yes and No.
|
||||
/// </summary>
|
||||
YesNo = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// The message box contains three push buttons: Yes, No, and Cancel.
|
||||
/// </summary>
|
||||
YesNoCancel = 0x3,
|
||||
|
||||
// To display an icon in the message box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// An exclamation-point icon appears in the message box.
|
||||
/// </summary>
|
||||
IconExclamation = 0x30,
|
||||
|
||||
/// <summary>
|
||||
/// An exclamation-point icon appears in the message box.
|
||||
/// </summary>
|
||||
IconWarning = IconExclamation,
|
||||
|
||||
/// <summary>
|
||||
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
|
||||
/// </summary>
|
||||
IconInformation = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
|
||||
/// </summary>
|
||||
IconAsterisk = IconInformation,
|
||||
|
||||
/// <summary>
|
||||
/// A question-mark icon appears in the message box.
|
||||
/// The question-mark message icon is no longer recommended because it does not clearly represent a specific type
|
||||
/// of message and because the phrasing of a message as a question could apply to any message type. In addition,
|
||||
/// users can confuse the message symbol question mark with Help information. Therefore, do not use this question
|
||||
/// mark message symbol in your message boxes. The system continues to support its inclusion only for backward
|
||||
/// compatibility.
|
||||
/// </summary>
|
||||
IconQuestion = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconStop = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconError = IconStop,
|
||||
|
||||
/// <summary>
|
||||
/// A stop-sign icon appears in the message box.
|
||||
/// </summary>
|
||||
IconHand = IconStop,
|
||||
|
||||
// To indicate the default button, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The first button is the default button.
|
||||
/// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified.
|
||||
/// </summary>
|
||||
DefButton1 = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// The second button is the default button.
|
||||
/// </summary>
|
||||
DefButton2 = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// The third button is the default button.
|
||||
/// </summary>
|
||||
DefButton3 = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// The fourth button is the default button.
|
||||
/// </summary>
|
||||
DefButton4 = 0x300,
|
||||
|
||||
// To indicate the modality of the dialog box, specify one of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// The user must respond to the message box before continuing work in the window identified by the hWnd parameter.
|
||||
/// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy
|
||||
/// of windows in the application, the user may be able to move to other windows within the thread. All child windows
|
||||
/// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the
|
||||
/// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified.
|
||||
/// </summary>
|
||||
ApplModal = DefaultValue,
|
||||
|
||||
/// <summary>
|
||||
/// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style.
|
||||
/// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate
|
||||
/// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with
|
||||
/// windows other than those associated with hWnd.
|
||||
/// </summary>
|
||||
SystemModal = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the
|
||||
/// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle
|
||||
/// available but still needs to prevent input to other windows in the calling thread without suspending other threads.
|
||||
/// </summary>
|
||||
TaskModal = 0x2000,
|
||||
|
||||
// To specify other options, use one or more of the following values.
|
||||
|
||||
/// <summary>
|
||||
/// Same as desktop of the interactive window station. For more information, see Window Stations. If the current
|
||||
/// input desktop is not the default desktop, MessageBox does not return until the user switches to the default
|
||||
/// desktop.
|
||||
/// </summary>
|
||||
DefaultDesktopOnly = 0x20000,
|
||||
|
||||
/// <summary>
|
||||
/// The text is right-justified.
|
||||
/// </summary>
|
||||
Right = 0x80000,
|
||||
|
||||
/// <summary>
|
||||
/// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
|
||||
/// </summary>
|
||||
RtlReading = 0x100000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function
|
||||
/// for the message box.
|
||||
/// </summary>
|
||||
SetForeground = 0x10000,
|
||||
|
||||
/// <summary>
|
||||
/// The message box is created with the WS_EX_TOPMOST window style.
|
||||
/// </summary>
|
||||
Topmost = 0x40000,
|
||||
|
||||
/// <summary>
|
||||
/// The caller is a service notifying the user of an event. The function displays a message box on the current active
|
||||
/// desktop, even if there is no user logged on to the computer.
|
||||
/// </summary>
|
||||
ServiceNotification = 0x200000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message,
|
||||
/// such as status or error information. The message box returns an integer value that indicates which button the user
|
||||
/// clicked.
|
||||
/// </summary>
|
||||
/// <param name="hWnd">
|
||||
/// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no
|
||||
/// owner window.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage
|
||||
/// return and/or linefeed character between each line.
|
||||
/// </param>
|
||||
/// <param name="caption">
|
||||
/// The dialog box title. If this parameter is NULL, the default title is Error.</param>
|
||||
/// <param name="type">
|
||||
/// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups
|
||||
/// of flags.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or
|
||||
/// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an
|
||||
/// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK.
|
||||
/// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function
|
||||
/// succeeds, the return value is one of the ID* enum values.
|
||||
/// </returns>
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native kernel32 functions.
|
||||
/// </summary>
|
||||
internal static partial class NativeFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// MEM_* from memoryapi.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AllocationType
|
||||
{
|
||||
/// <summary>
|
||||
/// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce
|
||||
/// placeholders, lpAddress and dwSize must exactly match those of the placeholder.
|
||||
/// </summary>
|
||||
CoalescePlaceholders = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using
|
||||
/// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify
|
||||
/// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER.
|
||||
/// </summary>
|
||||
PreservePlaceholder = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved
|
||||
/// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents
|
||||
/// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.
|
||||
/// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit
|
||||
/// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the
|
||||
/// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit
|
||||
/// a page that is already committed does not cause the function to fail. This means that you can commit pages without
|
||||
/// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave,
|
||||
/// flAllocationType must be MEM_COMMIT.
|
||||
/// </summary>
|
||||
Commit = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory
|
||||
/// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To
|
||||
/// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation
|
||||
/// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released.
|
||||
/// </summary>
|
||||
Reserve = 0x2000,
|
||||
|
||||
/// <summary>
|
||||
/// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state.
|
||||
/// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit
|
||||
/// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported
|
||||
/// when the lpAddress parameter provides the base address for an enclave.
|
||||
/// </summary>
|
||||
Decommit = 0x4000,
|
||||
|
||||
/// <summary>
|
||||
/// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and
|
||||
/// available for other allocations). After this operation, the pages are in the free state. If you specify this
|
||||
/// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function
|
||||
/// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the
|
||||
/// region are committed currently, the function first decommits, and then releases them. The function does not
|
||||
/// fail if you attempt to release pages that are in different states, some reserved and some committed. This means
|
||||
/// that you can release a range of pages without first determining the current commitment state.
|
||||
/// </summary>
|
||||
Release = 0x8000,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages
|
||||
/// should not be read from or written to the paging file. However, the memory block will be used again later, so
|
||||
/// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee
|
||||
/// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit
|
||||
/// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect.
|
||||
/// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns
|
||||
/// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable
|
||||
/// if it is mapped to a paging file.
|
||||
/// </summary>
|
||||
Reset = 0x80000,
|
||||
|
||||
/// <summary>
|
||||
/// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier.
|
||||
/// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to
|
||||
/// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in
|
||||
/// the specified address range is intact. If the function fails, at least some of the data in the address range
|
||||
/// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on
|
||||
/// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the
|
||||
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
|
||||
/// protection value, such as PAGE_NOACCESS.
|
||||
/// </summary>
|
||||
ResetUndo = 0x1000000,
|
||||
|
||||
/// <summary>
|
||||
/// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must
|
||||
/// be used with MEM_RESERVE and no other values.
|
||||
/// </summary>
|
||||
Physical = 0x400000,
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when
|
||||
/// there are many allocations.
|
||||
/// </summary>
|
||||
TopDown = 0x100000,
|
||||
|
||||
/// <summary>
|
||||
/// Causes the system to track pages that are written to in the allocated region. If you specify this value, you
|
||||
/// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region
|
||||
/// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking
|
||||
/// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region
|
||||
/// until the region is freed.
|
||||
/// </summary>
|
||||
WriteWatch = 0x200000,
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum.
|
||||
/// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify
|
||||
/// MEM_RESERVE and MEM_COMMIT.
|
||||
/// </summary>
|
||||
LargePages = 0x20000000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprefixed flags from CreateRemoteThread.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CreateThreadFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// The thread runs immediately after creation.
|
||||
/// </summary>
|
||||
RunImmediately = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// The thread is created in a suspended state, and does not run until the ResumeThread function is called.
|
||||
/// </summary>
|
||||
CreateSuspended = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// The dwStackSize parameter specifies the initial reserve size of the stack. If this flag is not specified, dwStackSize specifies the commit size.
|
||||
/// </summary>
|
||||
StackSizeParamIsReservation = 0x10000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DUPLICATE_* values for DuplicateHandle's dwDesiredAccess.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DuplicateOptions : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Closes the source handle. This occurs regardless of any error status returned.
|
||||
/// </summary>
|
||||
CloseSource = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
|
||||
/// </summary>
|
||||
SameAccess = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PAGE_* from memoryapi.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MemoryProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables execute access to the committed region of pages. An attempt to write to the committed region results
|
||||
/// in an access violation. This flag is not supported by the CreateFileMapping function.
|
||||
/// </summary>
|
||||
Execute = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region
|
||||
/// results in an access violation.
|
||||
/// </summary>
|
||||
ExecuteRead = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Enables execute, read-only, or read/write access to the committed region of pages.
|
||||
/// </summary>
|
||||
ExecuteReadWrite = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to
|
||||
/// write to a committed copy-on-write page results in a private copy of the page being made for the process. The
|
||||
/// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not
|
||||
/// supported by the VirtualAlloc or VirtualAllocEx functions.
|
||||
/// </summary>
|
||||
ExecuteWriteCopy = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed
|
||||
/// region results in an access violation. This flag is not supported by the CreateFileMapping function.
|
||||
/// </summary>
|
||||
NoAccess = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Enables read-only access to the committed region of pages. An attempt to write to the committed region results
|
||||
/// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed
|
||||
/// region results in an access violation.
|
||||
/// </summary>
|
||||
ReadOnly = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled,
|
||||
/// attempting to execute code in the committed region results in an access violation.
|
||||
/// </summary>
|
||||
ReadWrite = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to
|
||||
/// a committed copy-on-write page results in a private copy of the page being made for the process. The private
|
||||
/// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is
|
||||
/// enabled, attempting to execute code in the committed region results in an access violation. This flag is not
|
||||
/// supported by the VirtualAlloc or VirtualAllocEx functions.
|
||||
/// </summary>
|
||||
WriteCopy = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like
|
||||
/// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations
|
||||
/// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable
|
||||
/// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect
|
||||
/// or CreateFileMapping functions.
|
||||
/// </summary>
|
||||
TargetsInvalid = 0x40000000,
|
||||
|
||||
/// <summary>
|
||||
/// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect.
|
||||
/// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information
|
||||
/// will be maintained while the page protection changes. This flag is only valid when the protection changes to
|
||||
/// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY.
|
||||
/// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
|
||||
/// targets for CFG.
|
||||
/// </summary>
|
||||
TargetsNoUpdate = TargetsInvalid,
|
||||
|
||||
/// <summary>
|
||||
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a
|
||||
/// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time
|
||||
/// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn
|
||||
/// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a
|
||||
/// system service, the service typically returns a failure status indicator. This value cannot be used with
|
||||
/// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function.
|
||||
/// </summary>
|
||||
Guard = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required
|
||||
/// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an
|
||||
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS,
|
||||
/// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the
|
||||
/// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared
|
||||
/// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function.
|
||||
/// </summary>
|
||||
NoCache = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required
|
||||
/// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an
|
||||
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS,
|
||||
/// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory
|
||||
/// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access
|
||||
/// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function.
|
||||
/// </summary>
|
||||
WriteCombine = 0x400,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PROCESS_* from processthreadsapi.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// All possible access rights for a process object.
|
||||
/// </summary>
|
||||
AllAccess = 0x001F0FFF,
|
||||
|
||||
/// <summary>
|
||||
/// Required to create a process.
|
||||
/// </summary>
|
||||
CreateProcess = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// Required to create a thread.
|
||||
/// </summary>
|
||||
CreateThread = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Required to duplicate a handle using DuplicateHandle.
|
||||
/// </summary>
|
||||
DupHandle = 0x0040,
|
||||
|
||||
/// <summary>
|
||||
/// Required to retrieve certain information about a process, such as its token, exit code,
|
||||
/// and priority class (see OpenProcessToken).
|
||||
/// </summary>
|
||||
QueryInformation = 0x0400,
|
||||
|
||||
/// <summary>
|
||||
/// Required to retrieve certain information about a process(see GetExitCodeProcess, GetPriorityClass, IsProcessInJob,
|
||||
/// QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted
|
||||
/// PROCESS_QUERY_LIMITED_INFORMATION.
|
||||
/// </summary>
|
||||
QueryLimitedInformation = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Required to set certain information about a process, such as its priority class (see SetPriorityClass).
|
||||
/// </summary>
|
||||
SetInformation = 0x0200,
|
||||
|
||||
/// <summary>
|
||||
/// Required to set memory limits using SetProcessWorkingSetSize.
|
||||
/// </summary>
|
||||
SetQuote = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Required to suspend or resume a process.
|
||||
/// </summary>
|
||||
SuspendResume = 0x0800,
|
||||
|
||||
/// <summary>
|
||||
/// Required to terminate a process using TerminateProcess.
|
||||
/// </summary>
|
||||
Terminate = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Required to perform an operation on the address space of a process(see VirtualProtectEx and WriteProcessMemory).
|
||||
/// </summary>
|
||||
VmOperation = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// Required to read memory in a process using ReadProcessMemory.
|
||||
/// </summary>
|
||||
VmRead = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// Required to write to memory in a process using WriteProcessMemory.
|
||||
/// </summary>
|
||||
VmWrite = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// Required to wait for the process to terminate using the wait functions.
|
||||
/// </summary>
|
||||
Synchronize = 0x00100000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WAIT_* from synchapi.
|
||||
/// </summary>
|
||||
public enum WaitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The specified object is a mutex object that was not released by the thread that owned the mutex object
|
||||
/// before the owning thread terminated.Ownership of the mutex object is granted to the calling thread and
|
||||
/// the mutex state is set to nonsignaled. If the mutex was protecting persistent state information, you
|
||||
/// should check it for consistency.
|
||||
/// </summary>
|
||||
Abandoned = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// The state of the specified object is signaled.
|
||||
/// </summary>
|
||||
Object0 = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// The time-out interval elapsed, and the object's state is nonsignaled.
|
||||
/// </summary>
|
||||
Timeout = 0x102,
|
||||
|
||||
/// <summary>
|
||||
/// The function has failed. To get extended error information, call GetLastError.
|
||||
/// </summary>
|
||||
WAIT_FAILED = 0xFFFFFFF,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes an open object handle.
|
||||
/// </summary>
|
||||
/// <param name="hObject">
|
||||
/// A valid handle to an open object.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error
|
||||
/// information, call GetLastError. If the application is running under a debugger, the function will throw an exception if it receives
|
||||
/// either a handle value that is not valid or a pseudo-handle value. This can happen if you close a handle twice, or if you call
|
||||
/// CloseHandle on a handle returned by the FindFirstFile function instead of calling the FindClose function.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function
|
||||
/// to create a thread that runs in the virtual address space of another process and optionally specify extended attributes.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">
|
||||
/// A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD,
|
||||
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without
|
||||
/// these rights on certain platforms. For more information, see Process Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpThreadAttributes">
|
||||
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether
|
||||
/// child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor
|
||||
/// and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from
|
||||
/// the primary token of the creator.
|
||||
/// </param>
|
||||
/// <param name="dwStackSize">
|
||||
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the
|
||||
/// new thread uses the default size for the executable. For more information, see Thread Stack Size.
|
||||
/// </param>
|
||||
/// <param name="lpStartAddress">
|
||||
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the
|
||||
/// starting address of the thread in the remote process. The function must exist in the remote process. For more information,
|
||||
/// see ThreadProc.
|
||||
/// </param>
|
||||
/// <param name="lpParameter">
|
||||
/// A pointer to a variable to be passed to the thread function.
|
||||
/// </param>
|
||||
/// <param name="dwCreationFlags">
|
||||
/// The flags that control the creation of the thread.
|
||||
/// </param>
|
||||
/// <param name="lpThreadId">
|
||||
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is
|
||||
/// NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress
|
||||
/// points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and
|
||||
/// the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process.
|
||||
/// This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to
|
||||
/// invalid or missing dynamic-link libraries (DLL).
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateRemoteThread(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpThreadAttributes,
|
||||
UIntPtr dwStackSize,
|
||||
nuint lpStartAddress,
|
||||
nuint lpParameter,
|
||||
CreateThreadFlags dwCreationFlags,
|
||||
out uint lpThreadId);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the termination status of the specified thread.
|
||||
/// </summary>
|
||||
/// <param name="hThread">
|
||||
/// A handle to the thread. The handle must have the THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION
|
||||
/// access right.For more information, see Thread Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpExitCode">
|
||||
/// A pointer to a variable to receive the thread termination status.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get
|
||||
/// extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing local process object.
|
||||
/// </summary>
|
||||
/// <param name="dwDesiredAccess">
|
||||
/// The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be one or
|
||||
/// more of the process access rights. If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the
|
||||
/// contents of the security descriptor.
|
||||
/// </param>
|
||||
/// <param name="bInheritHandle">
|
||||
/// If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.
|
||||
/// </param>
|
||||
/// <param name="dwProcessId">
|
||||
/// The identifier of the local process to be opened. If the specified process is the System Idle Process(0x00000000), the function fails and the
|
||||
/// last error code is ERROR_INVALID_PARAMETER.If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS)
|
||||
/// processes, this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from
|
||||
/// opening them. If you are using GetCurrentProcessId as an argument to this function, consider using GetCurrentProcess instead of OpenProcess, for
|
||||
/// improved performance.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is an open handle to the specified process.
|
||||
/// If the function fails, the return value is NULL.To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr OpenProcess(
|
||||
ProcessAccessFlags dwDesiredAccess,
|
||||
bool bInheritHandle,
|
||||
int dwProcessId);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.
|
||||
/// Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process.
|
||||
/// The function initializes the memory it allocates to zero. To specify the NUMA node for the physical memory, see
|
||||
/// VirtualAllocExNuma.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">
|
||||
/// The handle to a process. The function allocates memory within the virtual address space of this process. The handle
|
||||
/// must have the PROCESS_VM_OPERATION access right. For more information, see Process Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpAddress">
|
||||
/// The pointer that specifies a desired starting address for the region of pages that you want to allocate. If you
|
||||
/// are reserving memory, the function rounds this address down to the nearest multiple of the allocation granularity.
|
||||
/// If you are committing memory that is already reserved, the function rounds this address down to the nearest page
|
||||
/// boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo
|
||||
/// function. If lpAddress is NULL, the function determines where to allocate the region. If this address is within
|
||||
/// an enclave that you have not initialized by calling InitializeEnclave, VirtualAllocEx allocates a page of zeros
|
||||
/// for the enclave at that address. The page must be previously uncommitted, and will not be measured with the EEXTEND
|
||||
/// instruction of the Intel Software Guard Extensions programming model. If the address in within an enclave that you
|
||||
/// initialized, then the allocation operation fails with the ERROR_INVALID_ADDRESS error.
|
||||
/// </param>
|
||||
/// <param name="dwSize">
|
||||
/// The size of the region of memory to allocate, in bytes. If lpAddress is NULL, the function rounds dwSize up to the
|
||||
/// next page boundary. If lpAddress is not NULL, the function allocates all pages that contain one or more bytes in
|
||||
/// the range from lpAddress to lpAddress+dwSize. This means, for example, that a 2-byte range that straddles a page
|
||||
/// boundary causes the function to allocate both pages.
|
||||
/// </param>
|
||||
/// <param name="flAllocationType">
|
||||
/// The type of memory allocation. This parameter must contain one of the MEM_* enum values.
|
||||
/// </param>
|
||||
/// <param name="flProtect">
|
||||
/// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify
|
||||
/// any one of the memory protection constants.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is the base address of the allocated region of pages. If the function
|
||||
/// fails, the return value is NULL.To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
||||
public static extern IntPtr VirtualAllocEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
int dwSize,
|
||||
AllocationType flAllocationType,
|
||||
MemoryProtection flProtect);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfreeex.
|
||||
/// Releases, decommits, or releases and decommits a region of memory within the virtual address space of a specified
|
||||
/// process.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">
|
||||
/// A handle to a process. The function frees memory within the virtual address space of the process. The handle must
|
||||
/// have the PROCESS_VM_OPERATION access right.For more information, see Process Security and Access Rights.
|
||||
/// </param>
|
||||
/// <param name="lpAddress">
|
||||
/// A pointer to the starting address of the region of memory to be freed. If the dwFreeType parameter is MEM_RELEASE,
|
||||
/// lpAddress must be the base address returned by the VirtualAllocEx function when the region is reserved.
|
||||
/// </param>
|
||||
/// <param name="dwSize">
|
||||
/// The size of the region of memory to free, in bytes. If the dwFreeType parameter is MEM_RELEASE, dwSize must be 0
|
||||
/// (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAllocEx.
|
||||
/// If dwFreeType is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes in the range
|
||||
/// from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of memory that
|
||||
/// straddles a page boundary causes both pages to be decommitted. If lpAddress is the base address returned by
|
||||
/// VirtualAllocEx and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAllocEx.
|
||||
/// After that, the entire region is in the reserved state.
|
||||
/// </param>
|
||||
/// <param name="dwFreeType">
|
||||
/// The type of free operation. This parameter must be one of the MEM_* enum values.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero).
|
||||
/// To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
||||
public static extern bool VirtualFreeEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
int dwSize,
|
||||
AllocationType dwFreeType);
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the specified object is in the signaled state or the time-out interval elapses. To enter an alertable wait
|
||||
/// state, use the WaitForSingleObjectEx function.To wait for multiple objects, use WaitForMultipleObjects.
|
||||
/// </summary>
|
||||
/// <param name="hHandle">
|
||||
/// A handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.
|
||||
/// If this handle is closed while the wait is still pending, the function's behavior is undefined. The handle must have the
|
||||
/// SYNCHRONIZE access right. For more information, see Standard Access Rights.
|
||||
/// </param>
|
||||
/// <param name="dwMilliseconds">
|
||||
/// The time-out interval, in milliseconds. If a nonzero value is specified, the function waits until the object is signaled
|
||||
/// or the interval elapses. If dwMilliseconds is zero, the function does not enter a wait state if the object is not signaled;
|
||||
/// it always returns immediately. If dwMilliseconds is INFINITE, the function will return only when the object is signaled.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value indicates the event that caused the function to return.
|
||||
/// It can be one of the WaitResult values.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or
|
||||
/// the operation fails.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">
|
||||
/// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access
|
||||
/// to the process.
|
||||
/// </param>
|
||||
/// <param name="lpBaseAddress">
|
||||
/// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the
|
||||
/// system verifies that all data in the base address and memory of the specified size is accessible for write access,
|
||||
/// and if it is not accessible, the function fails.
|
||||
/// </param>
|
||||
/// <param name="lpBuffer">
|
||||
/// A pointer to the buffer that contains data to be written in the address space of the specified process.
|
||||
/// </param>
|
||||
/// <param name="dwSize">
|
||||
/// The number of bytes to be written to the specified process.
|
||||
/// </param>
|
||||
/// <param name="lpNumberOfBytesWritten">
|
||||
/// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter
|
||||
/// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get
|
||||
/// extended error information, call GetLastError.The function fails if the requested write operation crosses into an
|
||||
/// area of the process that is inaccessible.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool WriteProcessMemory(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
byte[] lpBuffer,
|
||||
int dwSize,
|
||||
out IntPtr lpNumberOfBytesWritten);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates an object handle.
|
||||
/// </summary>
|
||||
/// <param name="hSourceProcessHandle">
|
||||
/// A handle to the process with the handle to be duplicated.
|
||||
///
|
||||
/// The handle must have the PROCESS_DUP_HANDLE access right.
|
||||
/// </param>
|
||||
/// <param name="hSourceHandle">
|
||||
/// The handle to be duplicated. This is an open object handle that is valid in the context of the source process.
|
||||
/// For a list of objects whose handles can be duplicated, see the following Remarks section.
|
||||
/// </param>
|
||||
/// <param name="hTargetProcessHandle">
|
||||
/// A handle to the process that is to receive the duplicated handle.
|
||||
///
|
||||
/// The handle must have the PROCESS_DUP_HANDLE access right.
|
||||
/// </param>
|
||||
/// <param name="lpTargetHandle">
|
||||
/// A pointer to a variable that receives the duplicate handle. This handle value is valid in the context of the target process.
|
||||
///
|
||||
/// If hSourceHandle is a pseudo handle returned by GetCurrentProcess or GetCurrentThread, DuplicateHandle converts it to a real handle to a process or thread, respectively.
|
||||
///
|
||||
/// If lpTargetHandle is NULL, the function duplicates the handle, but does not return the duplicate handle value to the caller. This behavior exists only for backward compatibility with previous versions of this function. You should not use this feature, as you will lose system resources until the target process terminates.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="dwDesiredAccess">
|
||||
/// The access requested for the new handle. For the flags that can be specified for each object type, see the following Remarks section.
|
||||
///
|
||||
/// This parameter is ignored if the dwOptions parameter specifies the DUPLICATE_SAME_ACCESS flag. Otherwise, the flags that can be specified depend on the type of object whose handle is to be duplicated.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="bInheritHandle">
|
||||
/// A variable that indicates whether the handle is inheritable. If TRUE, the duplicate handle can be inherited by new processes created by the target process. If FALSE, the new handle cannot be inherited.
|
||||
///
|
||||
/// This parameter is ignored if hTargetProcessHandle is NULL.
|
||||
/// </param>
|
||||
/// <param name="dwOptions">
|
||||
/// Optional actions.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero.
|
||||
///
|
||||
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle.
|
||||
/// </remarks>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool DuplicateHandle(
|
||||
IntPtr hSourceProcessHandle,
|
||||
IntPtr hSourceHandle,
|
||||
IntPtr hTargetProcessHandle,
|
||||
out IntPtr lpTargetHandle,
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
DuplicateOptions dwOptions);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"allowMarshaling": false
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
CreateRemoteThread
|
||||
WaitForSingleObject
|
||||
GetExitCodeThread
|
||||
DuplicateHandle
|
||||
|
||||
MessageBox
|
||||
GetModuleHandle
|
||||
GetProcAddress
|
||||
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>11.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<RootNamespace>Dalamud.Test</RootNamespace>
|
||||
<AssemblyTitle>Dalamud.Test</AssemblyTitle>
|
||||
|
|
@ -42,19 +50,19 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.abstractions" />
|
||||
<PackageReference Include="xunit.analyzers" />
|
||||
<PackageReference Include="xunit.assert" />
|
||||
<PackageReference Include="xunit.core" />
|
||||
<PackageReference Include="xunit.extensibility.core" />
|
||||
<PackageReference Include="xunit.extensibility.execution" />
|
||||
<PackageReference Include="xunit.runner.console">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
|
||||
<PackageReference Include="xunit.assert" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.core" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.console" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Dalamud.Test.Game.Text.SeStringHandling
|
||||
|
|
@ -54,41 +50,19 @@ namespace Dalamud.Test.Game.Text.SeStringHandling
|
|||
var config = new MockConfig { Text = seString };
|
||||
PluginConfigurations.SerializeConfig(config);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestConfigDeserializable()
|
||||
{
|
||||
var builder = new SeStringBuilder();
|
||||
var seString = builder.AddText("Some text").Build();
|
||||
var config = new MockConfig { Text = seString };
|
||||
|
||||
|
||||
// This relies on the type information being maintained, which is why we're using these
|
||||
// static methods instead of default serialization/deserialization.
|
||||
var configSerialized = PluginConfigurations.SerializeConfig(config);
|
||||
var configDeserialized = (MockConfig)PluginConfigurations.DeserializeConfig(configSerialized);
|
||||
Assert.Equal(config, configDeserialized);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(49, 209)]
|
||||
[InlineData(71, 7)]
|
||||
[InlineData(62, 116)]
|
||||
public void TestAutoTranslatePayloadReencode(uint group, uint key)
|
||||
{
|
||||
var payload = new AutoTranslatePayload(group, key);
|
||||
|
||||
Assert.Equal(group, payload.Group);
|
||||
Assert.Equal(key, payload.Key);
|
||||
|
||||
var encoded = payload.Encode();
|
||||
using var stream = new MemoryStream(encoded);
|
||||
using var reader = new BinaryReader(stream);
|
||||
var decodedPayload = Payload.Decode(reader) as AutoTranslatePayload;
|
||||
|
||||
Assert.Equal(group, decodedPayload.Group);
|
||||
Assert.Equal(key, decodedPayload.Key);
|
||||
|
||||
Assert.Equal(encoded, decodedPayload.Encode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,19 +31,19 @@ public class ReliableFileStorageTests
|
|||
.Select(
|
||||
i => Parallel.ForEachAsync(
|
||||
Enumerable.Range(1, 100),
|
||||
async (j, _) =>
|
||||
(j, _) =>
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, j.ToString());
|
||||
rfs.Instance.WriteAllText(tempFile, j.ToString());
|
||||
}
|
||||
else if (i % 3 == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
await rfs.Instance.ReadAllTextAsync(tempFile);
|
||||
rfs.Instance.ReadAllText(tempFile);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
|
@ -54,6 +54,8 @@ public class ReliableFileStorageTests
|
|||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
})));
|
||||
}
|
||||
|
||||
|
|
@ -110,41 +112,41 @@ public class ReliableFileStorageTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Exists_WhenFileInBackup_ReturnsTrue()
|
||||
public void Exists_WhenFileInBackup_ReturnsTrue()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
|
||||
File.Delete(tempFile);
|
||||
Assert.True(rfs.Instance.Exists(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Exists_WhenFileInBackup_WithDifferentContainerId_ReturnsFalse()
|
||||
public void Exists_WhenFileInBackup_WithDifferentContainerId_ReturnsFalse()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
|
||||
File.Delete(tempFile);
|
||||
Assert.False(rfs.Instance.Exists(tempFile, Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAllText_ThrowsIfPathIsEmpty()
|
||||
public void WriteAllText_ThrowsIfPathIsEmpty()
|
||||
{
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await rfs.Instance.WriteAllTextAsync("", TestFileContent1));
|
||||
Assert.Throws<ArgumentException>(() => rfs.Instance.WriteAllText("", TestFileContent1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAllText_ThrowsIfPathIsNull()
|
||||
public void WriteAllText_ThrowsIfPathIsNull()
|
||||
{
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await rfs.Instance.WriteAllTextAsync(null!, TestFileContent1));
|
||||
Assert.Throws<ArgumentNullException>(() => rfs.Instance.WriteAllText(null!, TestFileContent1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -153,26 +155,26 @@ public class ReliableFileStorageTests
|
|||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
|
||||
Assert.True(File.Exists(tempFile));
|
||||
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent1, await File.ReadAllTextAsync(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAllText_SeparatesContainers()
|
||||
public void WriteAllText_SeparatesContainers()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
var containerId = Guid.NewGuid();
|
||||
|
||||
using var rfs = CreateRfs();
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent2, containerId);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent2, containerId);
|
||||
File.Delete(tempFile);
|
||||
|
||||
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent2, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true, containerId));
|
||||
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent2, rfs.Instance.ReadAllText(tempFile, forceBackup: true, containerId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -181,7 +183,7 @@ public class ReliableFileStorageTests
|
|||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateFailedRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
|
||||
Assert.True(File.Exists(tempFile));
|
||||
Assert.Equal(TestFileContent1, await File.ReadAllTextAsync(tempFile));
|
||||
|
|
@ -193,38 +195,38 @@ public class ReliableFileStorageTests
|
|||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent2);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent2);
|
||||
|
||||
Assert.True(File.Exists(tempFile));
|
||||
Assert.Equal(TestFileContent2, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent2, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
|
||||
Assert.Equal(TestFileContent2, await File.ReadAllTextAsync(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAllText_SupportsNullContent()
|
||||
public void WriteAllText_SupportsNullContent()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, null);
|
||||
rfs.Instance.WriteAllText(tempFile, null);
|
||||
|
||||
Assert.True(File.Exists(tempFile));
|
||||
Assert.Equal("", await rfs.Instance.ReadAllTextAsync(tempFile));
|
||||
Assert.Equal("", rfs.Instance.ReadAllText(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_ThrowsIfPathIsEmpty()
|
||||
public void ReadAllText_ThrowsIfPathIsEmpty()
|
||||
{
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await rfs.Instance.ReadAllTextAsync(""));
|
||||
Assert.Throws<ArgumentException>(() => rfs.Instance.ReadAllText(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_ThrowsIfPathIsNull()
|
||||
public void ReadAllText_ThrowsIfPathIsNull()
|
||||
{
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await rfs.Instance.ReadAllTextAsync(null!));
|
||||
Assert.Throws<ArgumentNullException>(() => rfs.Instance.ReadAllText(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -234,40 +236,40 @@ public class ReliableFileStorageTests
|
|||
await File.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile));
|
||||
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_WhenFileMissingWithBackup_ReturnsContent()
|
||||
public void ReadAllText_WhenFileMissingWithBackup_ReturnsContent()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
File.Delete(tempFile);
|
||||
|
||||
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile));
|
||||
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_WhenFileMissingWithBackup_ThrowsWithDifferentContainerId()
|
||||
public void ReadAllText_WhenFileMissingWithBackup_ThrowsWithDifferentContainerId()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
var containerId = Guid.NewGuid();
|
||||
using var rfs = CreateRfs();
|
||||
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
File.Delete(tempFile);
|
||||
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, containerId: containerId));
|
||||
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, containerId: containerId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_WhenFileMissing_ThrowsIfDbFailed()
|
||||
public void ReadAllText_WhenFileMissing_ThrowsIfDbFailed()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateFailedRfs();
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile));
|
||||
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -276,7 +278,7 @@ public class ReliableFileStorageTests
|
|||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
await File.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
using var rfs = CreateRfs();
|
||||
await rfs.Instance.ReadAllTextAsync(tempFile, text => Assert.Equal(TestFileContent1, text));
|
||||
rfs.Instance.ReadAllText(tempFile, text => Assert.Equal(TestFileContent1, text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -288,7 +290,7 @@ public class ReliableFileStorageTests
|
|||
var readerCalledOnce = false;
|
||||
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<FileReadException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, Reader));
|
||||
Assert.Throws<FileReadException>(() => rfs.Instance.ReadAllText(tempFile, Reader));
|
||||
|
||||
return;
|
||||
|
||||
|
|
@ -301,7 +303,7 @@ public class ReliableFileStorageTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAllText_WithReader_WhenReaderThrows_ReadsContentFromBackup()
|
||||
public void ReadAllText_WithReader_WhenReaderThrows_ReadsContentFromBackup()
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
|
||||
|
|
@ -309,10 +311,10 @@ public class ReliableFileStorageTests
|
|||
var assertionCalled = false;
|
||||
|
||||
using var rfs = CreateRfs();
|
||||
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
|
||||
File.Delete(tempFile);
|
||||
|
||||
await rfs.Instance.ReadAllTextAsync(tempFile, Reader);
|
||||
rfs.Instance.ReadAllText(tempFile, Reader);
|
||||
Assert.True(assertionCalled);
|
||||
|
||||
return;
|
||||
|
|
@ -333,17 +335,17 @@ public class ReliableFileStorageTests
|
|||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
await File.WriteAllTextAsync(tempFile, TestFileContent1);
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, _ => throw new FileNotFoundException()));
|
||||
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, _ => throw new FileNotFoundException()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ReadAllText_WhenFileDoesNotExist_Throws(bool forceBackup)
|
||||
public void ReadAllText_WhenFileDoesNotExist_Throws(bool forceBackup)
|
||||
{
|
||||
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
|
||||
using var rfs = CreateRfs();
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup));
|
||||
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, forceBackup));
|
||||
}
|
||||
|
||||
private static DisposableReliableFileStorage CreateRfs()
|
||||
|
|
|
|||
189
Dalamud.sln
189
Dalamud.sln
|
|
@ -1,4 +1,4 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32319.34
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
|
@ -6,28 +6,32 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
|
||||
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
|
||||
Directory.Build.props = Directory.Build.props
|
||||
tools\BannedSymbols.txt = tools\BannedSymbols.txt
|
||||
tools\dalamud.ruleset = tools\dalamud.ruleset
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.csproj", "{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E} = {8430077C-F736-4246-A052-8EA1CECE844E}
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {F258347D-31BE-4605-98CE-40E43BDF6F9D}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boot\Dalamud.Boot.vcxproj", "{55198DC3-A03D-408E-A8EB-2077780C8576}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}"
|
||||
EndProject
|
||||
|
|
@ -45,57 +49,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imgui", "imgui", "{DBE5345E-6594-4A59-B183-1C3D5592269D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CS", "CS", "{8BBACF2D-7AB8-4610-A115-0E363D35C291}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimplot", "external\cimplot\cimplot.vcxproj", "{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimguizmo", "external\cimguizmo\cimguizmo.vcxproj", "{F258347D-31BE-4605-98CE-40E43BDF6F9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGui", "imgui\Dalamud.Bindings.ImGui\Dalamud.Bindings.ImGui.csproj", "{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGuizmo", "imgui\Dalamud.Bindings.ImGuizmo\Dalamud.Bindings.ImGuizmo.csproj", "{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImPlot", "imgui\Dalamud.Bindings.ImPlot\Dalamud.Bindings.ImPlot.csproj", "{9C70BD06-D52C-425E-9C14-5D66BC6046EF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bindings", "Bindings", "{A217B3DF-607A-4EFB-B107-3C4809348043}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StandaloneImGuiTestbed", "imgui\StandaloneImGuiTestbed\StandaloneImGuiTestbed.csproj", "{4702A911-2513-478C-A434-2776393FDE77}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiScene", "imgui\ImGuiScene\ImGuiScene.csproj", "{66753AC7-0029-4373-9CC4-7760B1F46141}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lumina", "Lumina", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "lib\Lumina.Excel\src\Lumina.Excel.Generator\Lumina.Excel.Generator.csproj", "{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source Generators", "Source Generators", "{50BEC23B-FFFD-427B-A95D-27E1D1958FFF}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
generators\Directory.Build.props = generators\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj", "{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Sample", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Sample\Dalamud.EnumGenerator.Sample.csproj", "{8CDAEB2D-5022-450A-A97F-181C6270185F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Tests", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Tests\Dalamud.EnumGenerator.Tests.csproj", "{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|x64
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
|
|
@ -108,10 +69,26 @@ Global
|
|||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
|
|
@ -120,22 +97,18 @@ Global
|
|||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|x64
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
@ -144,85 +117,21 @@ Global
|
|||
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.Build.0 = Release|x64
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.Build.0 = Release|x64
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.Build.0 = Release|x64
|
||||
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.Build.0 = Release|x64
|
||||
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.Build.0 = Release|x64
|
||||
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.Build.0 = Release|x64
|
||||
{4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.Build.0 = Release|x64
|
||||
{66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.Build.0 = Release|x64
|
||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0} = {8F079208-C227-4D96-9427-2BEBE0003944}
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
{A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||
{8430077C-F736-4246-A052-8EA1CECE844E} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
|
||||
{DBE5345E-6594-4A59-B183-1C3D5592269D} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{8BBACF2D-7AB8-4610-A115-0E363D35C291} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
|
||||
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
|
||||
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA} = {A217B3DF-607A-4EFB-B107-3C4809348043}
|
||||
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71} = {A217B3DF-607A-4EFB-B107-3C4809348043}
|
||||
{9C70BD06-D52C-425E-9C14-5D66BC6046EF} = {A217B3DF-607A-4EFB-B107-3C4809348043}
|
||||
{4702A911-2513-478C-A434-2776393FDE77} = {A217B3DF-607A-4EFB-B107-3C4809348043}
|
||||
{66753AC7-0029-4373-9CC4-7760B1F46141} = {A217B3DF-607A-4EFB-B107-3C4809348043}
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||
{8CDAEB2D-5022-450A-A97F-181C6270185F} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{3620414C-7DFC-423E-929F-310E19F5D930} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
{A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@
|
|||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EFormat_002ESettingsUpgrade_002EAlignmentTabFillStyleMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bannedplugin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=clientopcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=collectability/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dalamud/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=FFXIV/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flytext/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
@ -67,7 +66,6 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PLUGINR/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Refilter/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=serveropcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=spiritbond/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Universalis/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsanitized/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uploaders/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface;
|
||||
|
|
@ -13,20 +11,15 @@ using Dalamud.Interface.FontIdentifier;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.ReShadeHandling;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Windowing.Persistence;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Internal.AutoUpdate;
|
||||
using Dalamud.Plugin.Internal.Profiles;
|
||||
using Dalamud.Storage;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -52,8 +45,6 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
[JsonIgnore]
|
||||
private bool isSaveQueued;
|
||||
|
||||
private Task? writeTask;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
|
||||
/// </summary>
|
||||
|
|
@ -71,12 +62,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public List<string>? BadWords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the taskbar should flash once a duty is found.
|
||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderTaskbarFlash { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a message should be sent in chat once a duty is found.
|
||||
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
|
||||
/// </summary>
|
||||
public bool DutyFinderChatMessage { get; set; } = true;
|
||||
|
||||
|
|
@ -93,7 +84,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
/// <summary>
|
||||
/// Gets or sets a dictionary of seen FTUE levels.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> SeenFtueLevels { get; set; } = [];
|
||||
public Dictionary<string, int> SeenFtueLevels { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
|
|
@ -106,29 +97,34 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugin testing builds should be shown.
|
||||
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
|
||||
/// </summary>
|
||||
public bool DoPluginTest { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of custom repos.
|
||||
/// Gets or sets a key to opt into Dalamud staging builds.
|
||||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = [];
|
||||
public string? DalamudBetaKey { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a disclaimer regarding third-party repos has been dismissed.
|
||||
/// Gets or sets a list of custom repos.
|
||||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a disclaimer regarding third-party repos has been dismissed.
|
||||
/// </summary>
|
||||
public bool? ThirdRepoSpeedbumpDismissed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of hidden plugins.
|
||||
/// </summary>
|
||||
public List<string> HiddenPluginInternalName { get; set; } = [];
|
||||
public List<string> HiddenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of seen plugins.
|
||||
/// </summary>
|
||||
public List<string> SeenPluginInternalName { get; set; } = [];
|
||||
public List<string> SeenPluginInternalName { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
|
||||
|
|
@ -136,25 +132,37 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
/// However by specifiying this value manually, you can add arbitrary files outside the normal
|
||||
/// file paths.
|
||||
/// </summary>
|
||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = [];
|
||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
|
||||
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
|
||||
/// injected Dalamud instance.
|
||||
/// </summary>
|
||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = [];
|
||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the global UI scale.
|
||||
/// </summary>
|
||||
public float GlobalUiScale { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
|
||||
/// </summary>
|
||||
[Obsolete($"See {nameof(DefaultFontSpec)}")]
|
||||
public bool UseAxisFontsFromGame { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default font spec.
|
||||
/// </summary>
|
||||
public IFontSpec? DefaultFontSpec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gamma value to apply for Dalamud fonts. Do not use.
|
||||
/// </summary>
|
||||
[Obsolete("It happens that nobody touched this setting", true)]
|
||||
public float FontGammaLevel { get; set; } = 1.4f;
|
||||
|
||||
/// <summary>Gets or sets the opacity of the IME state indicator.</summary>
|
||||
/// <value>0 will hide the state indicator. 1 will make the state indicator fully visible. Values outside the
|
||||
/// range will be clamped to [0, 1].</value>
|
||||
|
|
@ -162,38 +170,38 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public float ImeStateIndicatorOpacity { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugin UI should be hidden.
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
|
||||
/// </summary>
|
||||
public bool ToggleUiHide { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugin UI should be hidden during cutscenes.
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugin UI should be hidden during GPose.
|
||||
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
|
||||
/// </summary>
|
||||
public bool ToggleUiHideDuringGpose { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a message containing Dalamud's current version and the number of loaded plugins should be sent at login.
|
||||
/// Gets or sets a value indicating whether or not a message containing Dalamud's current version and the number of loaded plugins should be sent at login.
|
||||
/// </summary>
|
||||
public bool PrintDalamudWelcomeMsg { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a message containing detailed plugin information should be sent at login.
|
||||
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
|
||||
/// </summary>
|
||||
public bool PrintPluginsWelcomeMsg { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugins should be auto-updated.
|
||||
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
|
||||
/// </summary>
|
||||
[Obsolete("Use AutoUpdateBehavior instead.")]
|
||||
public bool AutoUpdatePlugins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether Dalamud should add buttons to the system menu.
|
||||
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
|
||||
/// </summary>
|
||||
public bool DoButtonsSystemMenu { get; set; } = true;
|
||||
|
||||
|
|
@ -208,12 +216,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public bool LogSynchronously { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the debug log should scroll automatically.
|
||||
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
||||
/// </summary>
|
||||
public bool LogAutoScroll { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the debug log should open at startup.
|
||||
/// Gets or sets a value indicating whether or not the debug log should open at startup.
|
||||
/// </summary>
|
||||
public bool LogOpenAtStartup { get; set; }
|
||||
|
||||
|
|
@ -225,35 +233,36 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
/// <summary>
|
||||
/// Gets or sets a list representing the command history for the Dalamud Console.
|
||||
/// </summary>
|
||||
public List<string> LogCommandHistory { get; set; } = [];
|
||||
public List<string> LogCommandHistory { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dev bar should open at startup.
|
||||
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
|
||||
/// </summary>
|
||||
public bool DevBarOpenAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether ImGui asserts should be enabled at startup.
|
||||
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
|
||||
/// </summary>
|
||||
public bool? ImGuiAssertsEnabledAtStartup { get; set; }
|
||||
public bool AssertsEnabledAtStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether docking should be globally enabled in ImGui.
|
||||
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsDocking { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether plugin user interfaces should trigger sound effects.
|
||||
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
|
||||
/// This setting is effected by the in-game "System Sounds" option and volume.
|
||||
/// </summary>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "ABI")]
|
||||
public bool EnablePluginUISoundEffects { get; set; } = true;
|
||||
public bool EnablePluginUISoundEffects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether an additional button allowing pinning and clickthrough options should be shown
|
||||
/// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown
|
||||
/// on plugin title bars when using the Window System.
|
||||
/// </summary>
|
||||
public bool EnablePluginUiAdditionalOptions { get; set; } = true;
|
||||
[JsonProperty("EnablePluginUiAdditionalOptionsExperimental")]
|
||||
public bool EnablePluginUiAdditionalOptions { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether viewports should always be disabled.
|
||||
|
|
@ -261,22 +270,32 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public bool IsDisableViewport { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether navigation via a gamepad should be globally enabled in ImGui.
|
||||
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
|
||||
/// </summary>
|
||||
public bool IsGamepadNavigationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether focus management is enabled.
|
||||
/// Gets or sets a value indicating whether or not focus management is enabled.
|
||||
/// </summary>
|
||||
public bool IsFocusManagementEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
|
||||
/// </summary>
|
||||
public bool IsAntiAntiDebugEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to resume game main thread after plugins load.
|
||||
/// </summary>
|
||||
public bool IsResumeGameAfterPluginLoad { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any plugin should be loaded when the game is started.
|
||||
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
||||
/// </summary>
|
||||
public string? DalamudBetaKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
||||
/// It is reset immediately when read.
|
||||
/// </summary>
|
||||
public bool PluginSafeMode { get; set; }
|
||||
|
|
@ -288,7 +307,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public int? PluginWaitBeforeFree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether crashes during shutdown should be reported.
|
||||
/// Gets or sets a value indicating whether or not crashes during shutdown should be reported.
|
||||
/// </summary>
|
||||
public bool ReportShutdownCrashes { get; set; }
|
||||
|
||||
|
|
@ -320,20 +339,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public ProfileModel? DefaultProfile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether profiles are enabled.
|
||||
/// Gets or sets a value indicating whether or not profiles are enabled.
|
||||
/// </summary>
|
||||
public bool ProfilesEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user has seen the profiles tutorial.
|
||||
/// Gets or sets a value indicating whether or not the user has seen the profiles tutorial.
|
||||
/// </summary>
|
||||
public bool ProfilesHasSeenTutorial { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default UI preset.
|
||||
/// </summary>
|
||||
public PresetModel DefaultUiPreset { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the order of DTR elements, by title.
|
||||
/// </summary>
|
||||
|
|
@ -369,7 +383,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public bool? ReduceMotions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether market board data should be uploaded.
|
||||
/// Gets or sets a value indicating whether or not market board data should be uploaded.
|
||||
/// </summary>
|
||||
public bool IsMbCollect { get; set; } = true;
|
||||
|
||||
|
|
@ -405,7 +419,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show info on dev bar.
|
||||
/// Gets or sets a value indicating whether or not to show info on dev bar.
|
||||
/// </summary>
|
||||
public bool ShowDevBarInfo { get; set; } = true;
|
||||
|
||||
|
|
@ -470,60 +484,27 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public AutoUpdateBehavior? AutoUpdateBehavior { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether users should be notified regularly about pending updates.
|
||||
/// Gets or sets a value indicating whether or not users should be notified regularly about pending updates.
|
||||
/// </summary>
|
||||
public bool CheckPeriodicallyForUpdates { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether users should be notified about updates in chat.
|
||||
/// </summary>
|
||||
public bool SendUpdateNotificationToChat { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether disabled plugins should be auto-updated.
|
||||
/// </summary>
|
||||
public bool UpdateDisabledPlugins { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating where notifications are anchored to on the screen.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets a list of badge passwords used to unlock badges.
|
||||
/// </summary>
|
||||
public List<string> UsedBadgePasswords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether badges should be shown on the title screen.
|
||||
/// </summary>
|
||||
public bool ShowBadgesOnTitleScreen { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Load a configuration from the provided path.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to read from.</param>
|
||||
/// <param name="fs">File storage.</param>
|
||||
/// <returns>The deserialized configuration file.</returns>
|
||||
public static async Task<DalamudConfiguration> Load(string path, ReliableFileStorage fs)
|
||||
public static DalamudConfiguration Load(string path, ReliableFileStorage fs)
|
||||
{
|
||||
DalamudConfiguration deserialized = null;
|
||||
|
||||
try
|
||||
{
|
||||
await fs.ReadAllTextAsync(path, text =>
|
||||
fs.ReadAllText(path, text =>
|
||||
{
|
||||
deserialized =
|
||||
JsonConvert.DeserializeObject<DalamudConfiguration>(text, SerializerSettings);
|
||||
|
||||
|
||||
// If this reads as null, the file was empty, that's no good
|
||||
if (deserialized == null)
|
||||
throw new Exception("Read config was null.");
|
||||
|
|
@ -549,7 +530,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
{
|
||||
Log.Error(e, "Failed to set defaults for DalamudConfiguration");
|
||||
}
|
||||
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
|
|
@ -567,18 +548,13 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
public void ForceSave()
|
||||
{
|
||||
this.Save();
|
||||
this.isSaveQueued = false;
|
||||
this.writeTask?.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
// Make sure that we save, if a save is queued while we are shutting down
|
||||
this.Update();
|
||||
|
||||
// Wait for the write task to finish
|
||||
this.writeTask?.Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -590,6 +566,8 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
{
|
||||
this.Save();
|
||||
this.isSaveQueued = false;
|
||||
|
||||
Log.Verbose("Config saved");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -601,15 +579,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
{
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
|
||||
var winAnimEnabled = 0;
|
||||
bool success;
|
||||
unsafe
|
||||
{
|
||||
success = Windows.Win32.PInvoke.SystemParametersInfo(
|
||||
SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETCLIENTAREAANIMATION,
|
||||
0,
|
||||
&winAnimEnabled,
|
||||
0);
|
||||
}
|
||||
var success = NativeFunctions.SystemParametersInfo(
|
||||
(uint)NativeFunctions.AccessibilityParameter.SPI_GETCLIENTAREAANIMATION,
|
||||
0,
|
||||
ref winAnimEnabled,
|
||||
0);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
|
|
@ -621,50 +595,22 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
this.ReduceMotions = winAnimEnabled == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Migrate old auto-update setting to new auto-update behavior
|
||||
this.AutoUpdateBehavior ??= this.AutoUpdatePlugins
|
||||
? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll
|
||||
: Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
||||
private void Save()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
if (this.configPath is null)
|
||||
throw new InvalidOperationException("configPath is not set.");
|
||||
|
||||
// Wait for previous write to finish
|
||||
this.writeTask?.Wait();
|
||||
|
||||
this.writeTask = Task.Run(async () =>
|
||||
{
|
||||
await Service<ReliableFileStorage>.Get().WriteAllTextAsync(
|
||||
this.configPath,
|
||||
JsonConvert.SerializeObject(this, SerializerSettings));
|
||||
Log.Verbose("DalamudConfiguration saved");
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(
|
||||
t.Exception,
|
||||
"Failed to save DalamudConfiguration to {Path}",
|
||||
this.configPath);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var action in Delegate.EnumerateInvocationList(this.DalamudConfigurationSaved))
|
||||
{
|
||||
try
|
||||
{
|
||||
action(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||
}
|
||||
}
|
||||
Service<ReliableFileStorage>.Get().WriteAllText(
|
||||
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||
this.DalamudConfigurationSaved?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,24 +12,18 @@ internal sealed class DevPluginSettings
|
|||
/// </summary>
|
||||
public bool StartOnBoot { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether we should show notifications for errors this plugin
|
||||
/// is creating.
|
||||
/// </summary>
|
||||
public bool NotifyForErrors { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||
/// </summary>
|
||||
public bool AutomaticReloading { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
public Guid WorkingPluginId { get; set; } = Guid.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of validation problems that have been dismissed by the user.
|
||||
/// </summary>
|
||||
public List<string> DismissedValidationProblems { get; set; } = [];
|
||||
public List<string> DismissedValidationProblems { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ namespace Dalamud.Configuration.Internal;
|
|||
/// </summary>
|
||||
internal class EnvironmentConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
|
||||
/// </summary>
|
||||
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
|
||||
/// </summary>
|
||||
|
|
@ -21,7 +26,7 @@ internal class EnvironmentConfiguration
|
|||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether Dalamud context menus should be disabled.
|
||||
/// Gets a value indicating whether or not Dalamud context menus should be disabled.
|
||||
/// </summary>
|
||||
public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ using System.IO;
|
|||
using System.Reflection;
|
||||
|
||||
using Dalamud.Storage;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Configuration;
|
||||
|
|
@ -11,7 +9,6 @@ namespace Dalamud.Configuration;
|
|||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// </summary>
|
||||
[Api15ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||
public sealed class PluginConfigurations
|
||||
{
|
||||
private readonly DirectoryInfo configDirectory;
|
||||
|
|
@ -39,7 +36,7 @@ public sealed class PluginConfigurations
|
|||
public void Save(IPluginConfiguration config, string pluginName, Guid workingPluginId)
|
||||
{
|
||||
Service<ReliableFileStorage>.Get()
|
||||
.WriteAllTextAsync(this.GetConfigFile(pluginName).FullName, SerializeConfig(config), workingPluginId).GetAwaiter().GetResult();
|
||||
.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config), workingPluginId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,12 +52,12 @@ public sealed class PluginConfigurations
|
|||
IPluginConfiguration? config = null;
|
||||
try
|
||||
{
|
||||
Service<ReliableFileStorage>.Get().ReadAllTextAsync(path.FullName, text =>
|
||||
Service<ReliableFileStorage>.Get().ReadAllText(path.FullName, text =>
|
||||
{
|
||||
config = DeserializeConfig(text);
|
||||
if (config == null)
|
||||
throw new Exception("Read config was null.");
|
||||
}, workingPluginId).GetAwaiter().GetResult();
|
||||
}, workingPluginId);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ public interface IConsoleEntry
|
|||
/// Gets the name of the entry.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the entry.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a command in the console.
|
||||
/// </summary>
|
||||
|
|
@ -27,7 +27,7 @@ public interface IConsoleCommand : IConsoleEntry
|
|||
/// Execute this command.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments to invoke the entry with.</param>
|
||||
/// <returns>Whether execution succeeded.</returns>
|
||||
/// <returns>Whether or not execution succeeded.</returns>
|
||||
public bool Invoke(IEnumerable<object> arguments);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -17,10 +17,10 @@ namespace Dalamud.Console;
|
|||
[ServiceManager.BlockingEarlyLoadedService("Console is needed by other blocking early loaded services.")]
|
||||
internal partial class ConsoleManager : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<ConsoleManager>();
|
||||
|
||||
private Dictionary<string, IConsoleEntry> entries = [];
|
||||
|
||||
private static readonly ModuleLog Log = new("CON");
|
||||
|
||||
private Dictionary<string, IConsoleEntry> entries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -29,17 +29,17 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
this.AddCommand("toggle", "Toggle a boolean variable.", this.OnToggleVariable);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event that is triggered when a command is processed. Return true to stop the command from being processed any further.
|
||||
/// </summary>
|
||||
public event Func<string, bool>? Invoke;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only dictionary of console entries.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, IConsoleEntry> Entries => this.entries;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
|
|
@ -53,13 +53,13 @@ internal partial class ConsoleManager : IServiceType
|
|||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
ArgumentNullException.ThrowIfNull(func);
|
||||
|
||||
|
||||
if (this.FindEntry(name) != null)
|
||||
throw new InvalidOperationException($"Entry '{name}' already exists.");
|
||||
|
||||
var command = new ConsoleCommand(name, description, func);
|
||||
this.entries.Add(name, command);
|
||||
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
|
|
@ -77,14 +77,14 @@ internal partial class ConsoleManager : IServiceType
|
|||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
Traits.ThrowIfTIsNullableAndNull(defaultValue);
|
||||
|
||||
|
||||
if (this.FindEntry(name) != null)
|
||||
throw new InvalidOperationException($"Entry '{name}' already exists.");
|
||||
|
||||
var variable = new ConsoleVariable<T>(name, description);
|
||||
variable.Value = defaultValue;
|
||||
this.entries.Add(name, variable);
|
||||
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
|
|
@ -98,8 +98,11 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(alias);
|
||||
|
||||
var target = this.FindEntry(name) ?? throw new EntryNotFoundException(name);
|
||||
|
||||
var target = this.FindEntry(name);
|
||||
if (target == null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
if (this.FindEntry(alias) != null)
|
||||
throw new InvalidOperationException($"Entry '{alias}' already exists.");
|
||||
|
||||
|
|
@ -132,21 +135,21 @@ internal partial class ConsoleManager : IServiceType
|
|||
public T GetVariable<T>(string name)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
|
||||
var entry = this.FindEntry(name);
|
||||
|
||||
|
||||
if (entry is ConsoleVariable<T> variable)
|
||||
return variable.Value;
|
||||
|
||||
|
||||
if (entry is ConsoleVariable)
|
||||
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
|
||||
|
||||
|
||||
if (entry is null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
|
||||
throw new InvalidOperationException($"Command '{name}' is not a variable.");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the value of a variable.
|
||||
/// </summary>
|
||||
|
|
@ -159,18 +162,18 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
Traits.ThrowIfTIsNullableAndNull(value);
|
||||
|
||||
|
||||
var entry = this.FindEntry(name);
|
||||
|
||||
|
||||
if (entry is ConsoleVariable<T> variable)
|
||||
variable.Value = value;
|
||||
|
||||
|
||||
if (entry is ConsoleVariable)
|
||||
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
|
||||
|
||||
if (entry is null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
throw new InvalidOperationException($"Command '{name}' is not a variable.");
|
||||
}
|
||||
|
||||
|
|
@ -178,26 +181,16 @@ internal partial class ConsoleManager : IServiceType
|
|||
/// Process a console command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to process.</param>
|
||||
/// <returns>Whether the command was successfully processed.</returns>
|
||||
/// <returns>Whether or not the command was successfully processed.</returns>
|
||||
public bool ProcessCommand(string command)
|
||||
{
|
||||
foreach (var action in Delegate.EnumerateInvocationList(this.Invoke))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (action(command))
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Invoke?.Invoke(command) == true)
|
||||
return true;
|
||||
|
||||
var matches = GetCommandParsingRegex().Matches(command);
|
||||
if (matches.Count == 0)
|
||||
return false;
|
||||
|
||||
|
||||
var entryName = matches[0].Value;
|
||||
if (string.IsNullOrEmpty(entryName) || entryName.Any(char.IsWhiteSpace))
|
||||
{
|
||||
|
|
@ -211,7 +204,7 @@ internal partial class ConsoleManager : IServiceType
|
|||
Log.Error("Command {CommandName} not found", entryName);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var parsedArguments = new List<object>();
|
||||
|
||||
if (entry.ValidArguments != null)
|
||||
|
|
@ -224,13 +217,13 @@ internal partial class ConsoleManager : IServiceType
|
|||
PrintUsage(entry);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var argumentToMatch = entry.ValidArguments[i - 1];
|
||||
|
||||
|
||||
var group = matches[i];
|
||||
if (!group.Success)
|
||||
continue;
|
||||
|
||||
|
||||
var value = group.Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
continue;
|
||||
|
|
@ -269,15 +262,15 @@ internal partial class ConsoleManager : IServiceType
|
|||
throw new Exception("Unhandled argument type.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (parsedArguments.Count != entry.ValidArguments.Count)
|
||||
{
|
||||
// Either fill in the default values or error out
|
||||
|
||||
|
||||
for (var i = parsedArguments.Count; i < entry.ValidArguments.Count; i++)
|
||||
{
|
||||
var argument = entry.ValidArguments[i];
|
||||
|
||||
|
||||
// If the default value is DBNull, we need to error out as that means it was not specified
|
||||
if (argument.DefaultValue == DBNull.Value)
|
||||
{
|
||||
|
|
@ -288,7 +281,7 @@ internal partial class ConsoleManager : IServiceType
|
|||
|
||||
parsedArguments.Add(argument.DefaultValue);
|
||||
}
|
||||
|
||||
|
||||
if (parsedArguments.Count != entry.ValidArguments.Count)
|
||||
{
|
||||
Log.Error("Too many arguments for command {CommandName}", entryName);
|
||||
|
|
@ -309,20 +302,20 @@ internal partial class ConsoleManager : IServiceType
|
|||
|
||||
return entry.Invoke(parsedArguments);
|
||||
}
|
||||
|
||||
|
||||
[GeneratedRegex("""("[^"]+"|[^\s"]+)""", RegexOptions.Compiled)]
|
||||
private static partial Regex GetCommandParsingRegex();
|
||||
|
||||
|
||||
private static void PrintUsage(ConsoleEntry entry, bool error = true)
|
||||
{
|
||||
Log.WriteLog(
|
||||
error ? LogEventLevel.Error : LogEventLevel.Information,
|
||||
error ? LogEventLevel.Error : LogEventLevel.Information,
|
||||
"Usage: {CommandName} {Arguments}",
|
||||
null,
|
||||
entry.Name,
|
||||
string.Join(" ", entry.ValidArguments?.Select(x => $"<{x.Type.ToString().ToLowerInvariant()}>") ?? Enumerable.Empty<string>()));
|
||||
}
|
||||
|
||||
|
||||
private ConsoleEntry? FindEntry(string name)
|
||||
{
|
||||
return this.entries.TryGetValue(name, out var entry) ? entry as ConsoleEntry : null;
|
||||
|
|
@ -340,10 +333,10 @@ internal partial class ConsoleManager : IServiceType
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static class Traits
|
||||
{
|
||||
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
|
||||
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression("argument")] string? paramName = null)
|
||||
{
|
||||
if (argument == null && !typeof(T).IsValueType)
|
||||
throw new ArgumentNullException(paramName);
|
||||
|
|
@ -371,17 +364,17 @@ internal partial class ConsoleManager : IServiceType
|
|||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of valid argument types for this console entry.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ArgumentInfo>? ValidArguments { get; protected set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Execute this command.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments to invoke the entry with.</param>
|
||||
/// <returns>Whether execution succeeded.</returns>
|
||||
/// <returns>Whether or not execution succeeded.</returns>
|
||||
public abstract bool Invoke(IEnumerable<object> arguments);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -395,19 +388,19 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
if (type == typeof(string))
|
||||
return new ArgumentInfo(ConsoleArgumentType.String, defaultValue);
|
||||
|
||||
|
||||
if (type == typeof(int))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Integer, defaultValue);
|
||||
|
||||
|
||||
if (type == typeof(float))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Float, defaultValue);
|
||||
|
||||
|
||||
if (type == typeof(bool))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Bool, defaultValue);
|
||||
|
||||
|
||||
throw new ArgumentException($"Invalid argument type: {type.Name}");
|
||||
}
|
||||
|
||||
|
||||
public record ArgumentInfo(ConsoleArgumentType Type, object? DefaultValue);
|
||||
}
|
||||
|
||||
|
|
@ -443,7 +436,7 @@ internal partial class ConsoleManager : IServiceType
|
|||
private class ConsoleCommand : ConsoleEntry, IConsoleCommand
|
||||
{
|
||||
private readonly Delegate func;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleCommand"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -454,17 +447,17 @@ internal partial class ConsoleManager : IServiceType
|
|||
: base(name, description)
|
||||
{
|
||||
this.func = func;
|
||||
|
||||
|
||||
if (func.Method.ReturnType != typeof(bool))
|
||||
throw new ArgumentException("Console command functions must return a boolean indicating success.");
|
||||
|
||||
|
||||
var validArguments = new List<ArgumentInfo>();
|
||||
foreach (var parameterInfo in func.Method.GetParameters())
|
||||
{
|
||||
var paraT = parameterInfo.ParameterType;
|
||||
validArguments.Add(TypeToArgument(paraT, parameterInfo.DefaultValue));
|
||||
}
|
||||
|
||||
|
||||
this.ValidArguments = validArguments;
|
||||
}
|
||||
|
||||
|
|
@ -498,7 +491,7 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
this.ValidArguments = new List<ArgumentInfo> { TypeToArgument(typeof(T), null) };
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Value { get; set; }
|
||||
|
||||
|
|
@ -514,16 +507,16 @@ internal partial class ConsoleManager : IServiceType
|
|||
{
|
||||
this.Value = (T)(object)!boolValue;
|
||||
}
|
||||
|
||||
|
||||
Log.WriteLog(LogEventLevel.Information, "{VariableName} = {VariableValue}", null, this.Name, this.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (first.GetType() != typeof(T))
|
||||
throw new ArgumentException($"Console variable must be set with an argument of type {typeof(T).Name}.");
|
||||
|
||||
this.Value = (T)first;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
|
@ -11,47 +11,6 @@ namespace Dalamud.Console;
|
|||
|
||||
#pragma warning disable Dalamud001
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for the console manager.
|
||||
/// </summary>
|
||||
internal static partial class ConsoleManagerPluginUtil
|
||||
{
|
||||
private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
|
||||
|
||||
/// <summary>
|
||||
/// Get a sanitized namespace name from a plugin's internal name.
|
||||
/// </summary>
|
||||
/// <param name="pluginInternalName">The plugin's internal name.</param>
|
||||
/// <returns>A sanitized namespace.</returns>
|
||||
public static string GetSanitizedNamespaceName(string pluginInternalName)
|
||||
{
|
||||
// Must be lowercase
|
||||
pluginInternalName = pluginInternalName.ToLowerInvariant();
|
||||
|
||||
// Remove all non-alphabetic characters
|
||||
pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
|
||||
|
||||
// Remove reserved namespaces from the start or end
|
||||
foreach (var reservedNamespace in ReservedNamespaces)
|
||||
{
|
||||
if (pluginInternalName.StartsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[reservedNamespace.Length..];
|
||||
}
|
||||
|
||||
if (pluginInternalName.EndsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
|
||||
}
|
||||
}
|
||||
|
||||
return pluginInternalName;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"[^a-z]")]
|
||||
private static partial Regex NonAlphaRegex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of the console service.
|
||||
/// </summary>
|
||||
|
|
@ -60,12 +19,12 @@ internal static partial class ConsoleManagerPluginUtil
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IConsole>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
||||
public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ConsoleManager console = Service<ConsoleManager>.Get();
|
||||
|
||||
private readonly List<IConsoleEntry> trackedEntries = [];
|
||||
|
||||
private readonly List<IConsoleEntry> trackedEntries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleManagerPluginScoped"/> class.
|
||||
|
|
@ -79,7 +38,7 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
|||
|
||||
/// <inheritdoc/>
|
||||
public string Prefix { get; private set; }
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
|
|
@ -87,7 +46,7 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
|||
{
|
||||
this.console.RemoveEntry(trackedEntry);
|
||||
}
|
||||
|
||||
|
||||
this.trackedEntries.Clear();
|
||||
}
|
||||
|
||||
|
|
@ -149,21 +108,21 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
|||
this.console.RemoveEntry(entry);
|
||||
this.trackedEntries.Remove(entry);
|
||||
}
|
||||
|
||||
|
||||
private string GetPrefixedName(string name)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
|
||||
// If the name is empty, return the prefix to allow for a single command or variable to be top-level.
|
||||
if (name.Length == 0)
|
||||
return this.Prefix;
|
||||
|
||||
|
||||
if (name.Any(char.IsWhiteSpace))
|
||||
throw new ArgumentException("Name cannot contain whitespace.", nameof(name));
|
||||
|
||||
|
||||
return $"{this.Prefix}.{name}";
|
||||
}
|
||||
|
||||
|
||||
private IConsoleCommand InternalAddCommand(string name, string description, Delegate func)
|
||||
{
|
||||
var command = this.console.AddCommand(this.GetPrefixedName(name), description, func);
|
||||
|
|
@ -171,3 +130,44 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
|||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for the console manager.
|
||||
/// </summary>
|
||||
internal static partial class ConsoleManagerPluginUtil
|
||||
{
|
||||
private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
|
||||
|
||||
/// <summary>
|
||||
/// Get a sanitized namespace name from a plugin's internal name.
|
||||
/// </summary>
|
||||
/// <param name="pluginInternalName">The plugin's internal name.</param>
|
||||
/// <returns>A sanitized namespace.</returns>
|
||||
public static string GetSanitizedNamespaceName(string pluginInternalName)
|
||||
{
|
||||
// Must be lowercase
|
||||
pluginInternalName = pluginInternalName.ToLowerInvariant();
|
||||
|
||||
// Remove all non-alphabetic characters
|
||||
pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
|
||||
|
||||
// Remove reserved namespaces from the start or end
|
||||
foreach (var reservedNamespace in ReservedNamespaces)
|
||||
{
|
||||
if (pluginInternalName.StartsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[reservedNamespace.Length..];
|
||||
}
|
||||
|
||||
if (pluginInternalName.EndsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
|
||||
}
|
||||
}
|
||||
|
||||
return pluginInternalName;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"[^a-z]")]
|
||||
private static partial Regex NonAlphaRegex();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,13 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Common;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Hooking.Internal.Verification;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
|
||||
using PInvoke;
|
||||
using Serilog;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Security;
|
||||
|
||||
#if DEBUG
|
||||
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
|
||||
#endif
|
||||
|
|
@ -33,13 +29,13 @@ namespace Dalamud;
|
|||
/// The main Dalamud class containing all subsystems.
|
||||
/// </summary>
|
||||
[ServiceManager.ProvidedService]
|
||||
internal sealed unsafe class Dalamud : IServiceType
|
||||
internal sealed class Dalamud : IServiceType
|
||||
{
|
||||
#region Internals
|
||||
|
||||
private static int shownServiceError = 0;
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -52,15 +48,15 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
this.StartInfo = info;
|
||||
|
||||
|
||||
this.unloadSignal = new ManualResetEvent(false);
|
||||
this.unloadSignal.Reset();
|
||||
|
||||
|
||||
// Directory resolved signatures(CS, our own) will be cached in
|
||||
var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs"));
|
||||
if (!cacheDir.Exists)
|
||||
cacheDir.Create();
|
||||
|
||||
|
||||
// Set up the SigScanner for our target module
|
||||
TargetSigScanner scanner;
|
||||
using (Timings.Start("SigScanner Init"))
|
||||
|
|
@ -75,31 +71,26 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
configuration,
|
||||
scanner,
|
||||
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
||||
|
||||
using (Timings.Start("HookVerifier Init"))
|
||||
{
|
||||
HookVerifier.Initialize(scanner);
|
||||
}
|
||||
|
||||
|
||||
// Set up FFXIVClientStructs
|
||||
this.SetupClientStructsResolver(cacheDir);
|
||||
|
||||
|
||||
void KickoffGameThread()
|
||||
{
|
||||
Log.Verbose("=============== GAME THREAD KICKOFF ===============");
|
||||
Timings.Event("Game thread kickoff");
|
||||
Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent));
|
||||
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
||||
}
|
||||
|
||||
void HandleServiceInitFailure(Task t)
|
||||
{
|
||||
Log.Error(t.Exception!, "Service initialization failure");
|
||||
|
||||
|
||||
if (Interlocked.CompareExchange(ref shownServiceError, 1, 0) != 0)
|
||||
return;
|
||||
|
||||
Util.Fatal(
|
||||
$"Dalamud failed to load all necessary services.\nThe game will continue, but you may not be able to use plugins.\n\n{t.Exception}",
|
||||
"Dalamud failed to load all necessary services.\n\nThe game will continue, but you may not be able to use plugins.",
|
||||
"Dalamud", false);
|
||||
}
|
||||
|
||||
|
|
@ -125,15 +116,15 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
HandleServiceInitFailure(t);
|
||||
});
|
||||
|
||||
this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero);
|
||||
SetExceptionHandler(this.DefaultExceptionFilter);
|
||||
Log.Debug($"SE default exception filter at {new IntPtr(this.DefaultExceptionFilter):X}");
|
||||
this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero);
|
||||
NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter);
|
||||
Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}");
|
||||
|
||||
var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF";
|
||||
this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig);
|
||||
Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start information for this Dalamud instance.
|
||||
/// </summary>
|
||||
|
|
@ -179,9 +170,8 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
if (!reportCrashesSetting && !pmHasDevPlugins)
|
||||
{
|
||||
// Leaking on purpose for now
|
||||
var attribs = default(SECURITY_ATTRIBUTES);
|
||||
attribs.nLength = (uint)Unsafe.SizeOf<SECURITY_ATTRIBUTES>();
|
||||
Windows.Win32.PInvoke.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
|
||||
var attribs = Kernel32.SECURITY_ATTRIBUTES.Create();
|
||||
Kernel32.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
|
||||
}
|
||||
|
||||
this.unloadSignal.Set();
|
||||
|
|
@ -198,30 +188,28 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
/// <summary>
|
||||
/// Replace the current exception handler with the default one.
|
||||
/// </summary>
|
||||
internal void UseDefaultExceptionHandler() =>
|
||||
SetExceptionHandler(this.DefaultExceptionFilter);
|
||||
internal void UseDefaultExceptionHandler() =>
|
||||
this.SetExceptionHandler(this.DefaultExceptionFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Replace the current exception handler with a debug one.
|
||||
/// </summary>
|
||||
internal void UseDebugExceptionHandler() =>
|
||||
SetExceptionHandler(this.DebugExceptionFilter);
|
||||
this.SetExceptionHandler(this.DebugExceptionFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Disable the current exception handler.
|
||||
/// </summary>
|
||||
internal void UseNoExceptionHandler() =>
|
||||
SetExceptionHandler(nint.Zero);
|
||||
this.SetExceptionHandler(nint.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to set the exception handler.
|
||||
/// </summary>
|
||||
private static nint SetExceptionHandler(nint newFilter)
|
||||
private void SetExceptionHandler(nint newFilter)
|
||||
{
|
||||
var oldFilter =
|
||||
Windows.Win32.PInvoke.SetUnhandledExceptionFilter((delegate* unmanaged[Stdcall]<global::Windows.Win32.System.Diagnostics.Debug.EXCEPTION_POINTERS*, int>)newFilter);
|
||||
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, (nint)oldFilter);
|
||||
return (nint)oldFilter;
|
||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter);
|
||||
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter);
|
||||
}
|
||||
|
||||
private void SetupClientStructsResolver(DirectoryInfo cacheDir)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<EnableWindowsTargeting>True</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>11.0.2.0</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<DalamudVersion>14.0.2.1</DalamudVersion>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||
|
|
@ -35,10 +39,6 @@
|
|||
<Deterministic>true</Deterministic>
|
||||
<Nullable>annotations</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<!-- Enable when cswin32 properly supports implementing COM interfaces and we can
|
||||
make IDropTarget work -->
|
||||
<!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> -->
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
|
@ -47,6 +47,10 @@
|
|||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Release'">
|
||||
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
|
||||
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Warnings">
|
||||
<NoWarn>IDE0002;IDE0003;IDE1006;IDE0044;CA1822;CS1591;CS1701;CS1702</NoWarn>
|
||||
|
|
@ -61,59 +65,42 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitFaster.Caching" />
|
||||
<PackageReference Include="CheapLoc" />
|
||||
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="all" />
|
||||
<PackageReference Include="goatcorp.Reloaded.Hooks" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="Lumina" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PackageReference Include="BitFaster.Caching" Version="2.4.1" />
|
||||
<PackageReference Include="CheapLoc" Version="1.1.8" />
|
||||
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.2.4" PrivateAssets="all" />
|
||||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Include="Lumina" Version="5.6.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MinSharp" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="sqlite-net-pcl" />
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PackageReference Include="MinSharp" Version="1.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Reactive" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.0" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="EnumCloneMap.txt"/>
|
||||
<AdditionalFiles Include="EnumCloneMap.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
|
||||
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-vertex.hlsl.bytes">
|
||||
<LogicalName>imgui-vertex.hlsl.bytes</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
|
||||
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImGuizmo\Dalamud.Bindings.ImGuizmo.csproj" />
|
||||
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImGui\Dalamud.Bindings.ImGui.csproj" />
|
||||
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImPlot\Dalamud.Bindings.ImPlot.csproj" />
|
||||
<ProjectReference Include="..\imgui\ImGuiScene\ImGuiScene.csproj" />
|
||||
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
|
||||
<ProjectReference Include="..\lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj" />
|
||||
<ProjectReference Include="..\lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
|
||||
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -125,8 +112,6 @@
|
|||
<Content Include="licenses.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
|
||||
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -136,13 +121,6 @@
|
|||
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.ps.bin" />
|
||||
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.vs.bin" />
|
||||
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.ps.bin" />
|
||||
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.vs.bin" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
|
||||
|
|
@ -167,9 +145,6 @@
|
|||
<Exec Command="git -C "$(ProjectDir.Replace('\','\\'))" describe --tags --always --dirty" ConsoleToMSBuild="true">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="DalamudGitDescribeOutput" />
|
||||
</Exec>
|
||||
<Exec Command="git -C "$(ProjectDir.Replace('\','\\'))" rev-parse --abbrev-ref HEAD" ConsoleToMSBuild="true">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="DalamudGitBranch" />
|
||||
</Exec>
|
||||
<Exec Command="git -C "$(ProjectDir.Replace('\','\\'))\..\lib\FFXIVClientStructs" describe --long --always --dirty" ConsoleToMSBuild="true">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="ClientStructsGitDescribeOutput" />
|
||||
</Exec>
|
||||
|
|
@ -177,7 +152,6 @@
|
|||
<PropertyGroup>
|
||||
<CommitCount>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitCommitCount), @"\t|\n|\r", ""))</CommitCount>
|
||||
<CommitHash>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitCommitHash), @"\t|\n|\r", ""))</CommitHash>
|
||||
<Branch>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitBranch), @"\t|\n|\r", ""))</Branch>
|
||||
<SCMVersion>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitDescribeOutput), @"\t|\n|\r", ""))</SCMVersion>
|
||||
<CommitHashClientStructs>$([System.Text.RegularExpressions.Regex]::Replace($(ClientStructsGitDescribeOutput), @"\t|\n|\r", ""))</CommitHashClientStructs>
|
||||
</PropertyGroup>
|
||||
|
|
@ -191,7 +165,6 @@
|
|||
<!-- stub out version since it takes a while. -->
|
||||
<PropertyGroup>
|
||||
<SCMVersion>Local build at $([System.DateTime]::Now.ToString(yyyy-MM-dd HH:mm:ss))</SCMVersion>
|
||||
<Branch>???</Branch>
|
||||
<CommitHashClientStructs>???</CommitHashClientStructs>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
|
@ -215,10 +188,6 @@
|
|||
<_Parameter1>GitCommitCount</_Parameter1>
|
||||
<_Parameter2>$(CommitCount)</_Parameter2>
|
||||
</AssemblyAttributes>
|
||||
<AssemblyAttributes Include="AssemblyMetadata" Condition="'$(Branch)' != ''">
|
||||
<_Parameter1>GitBranch</_Parameter1>
|
||||
<_Parameter2>$(Branch)</_Parameter2>
|
||||
</AssemblyAttributes>
|
||||
<AssemblyAttributes Include="AssemblyMetadata" Condition="'$(CommitHashClientStructs)' != ''">
|
||||
<_Parameter1>GitHashClientStructs</_Parameter1>
|
||||
<_Parameter2>$(CommitHashClientStructs)</_Parameter2>
|
||||
|
|
@ -231,4 +200,9 @@
|
|||
<!-- writes the attribute to the customAssemblyInfo file -->
|
||||
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Cfontawesome/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Ctextures_005Ctexturewraps_005Cinternal_005Cdrawlisttexturewrap/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Cfontawesome/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Int64 x:Key="/Default/PerformanceThreshold/AnalysisFileSizeThreshold/=CSHARP/@EntryIndexedValue">300000</s:Int64>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public enum DalamudAsset
|
|||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||
TroubleIcon = 1006,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||
/// </summary>
|
||||
|
|
@ -124,13 +124,6 @@ public enum DalamudAsset
|
|||
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||
TitleScreenMenuShade = 1013,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: Atlas containing badges.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "badgeAtlas.png")]
|
||||
BadgeAtlas = 1015,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||
/// </summary>
|
||||
|
|
@ -158,7 +151,7 @@ public enum DalamudAsset
|
|||
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||
[DalamudAssetPath("UIRes", "FontAwesome710FreeSolid.otf")]
|
||||
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
||||
FontAwesomeFreeSolid = 2003,
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
|
||||
using Lumina;
|
||||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Data;
|
||||
|
|
@ -43,7 +41,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
try
|
||||
{
|
||||
Log.Verbose("Starting data load...");
|
||||
|
||||
|
||||
using (Timings.Start("Lumina Init"))
|
||||
{
|
||||
var luminaOptions = new LuminaOptions
|
||||
|
|
@ -55,25 +53,12 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||
};
|
||||
|
||||
try
|
||||
this.GameData = new(
|
||||
Path.Combine(Path.GetDirectoryName(Environment.ProcessPath)!, "sqpack"),
|
||||
luminaOptions)
|
||||
{
|
||||
this.GameData = new(
|
||||
Path.Combine(Path.GetDirectoryName(Environment.ProcessPath)!, "sqpack"),
|
||||
luminaOptions)
|
||||
{
|
||||
StreamPool = new(),
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Lumina GameData init failed");
|
||||
Util.Fatal(
|
||||
"Dalamud could not read required game data files. This likely means your game installation is corrupted or incomplete.\n\n" +
|
||||
"Please repair your installation by right-clicking the login button in XIVLauncher and choosing \"Repair game files\".",
|
||||
"Dalamud");
|
||||
|
||||
return;
|
||||
}
|
||||
StreamPool = new(),
|
||||
};
|
||||
|
||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||
|
||||
|
|
@ -84,14 +69,9 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
var tsInfo =
|
||||
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
||||
dalamud.StartInfo.TroubleshootingPackData);
|
||||
|
||||
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
|
||||
// this.HasModifiedGameDataFiles =
|
||||
// tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
|
||||
|
||||
// TODO: Put above back when check in XL is fixed
|
||||
this.HasModifiedGameDataFiles = false;
|
||||
|
||||
this.HasModifiedGameDataFiles =
|
||||
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
||||
|
||||
if (this.HasModifiedGameDataFiles)
|
||||
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
|
||||
}
|
||||
|
|
@ -150,7 +130,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
#region Lumina Wrappers
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow<T>
|
||||
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow<T>
|
||||
=> this.Excel.GetSheet<T>(language?.ToLumina(), name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -158,7 +138,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
=> this.Excel.GetSubrowSheet<T>(language?.ToLumina(), name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileResource? GetFile(string path)
|
||||
public FileResource? GetFile(string path)
|
||||
=> this.GetFile<FileResource>(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -181,7 +161,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
: Task.FromException<T>(new FileNotFoundException("The file could not be found."));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FileExists(string path)
|
||||
public bool FileExists(string path)
|
||||
=> this.GameData.FileExists(path);
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ using System.Collections.Generic;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
||||
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Data;
|
||||
|
|
@ -15,7 +13,7 @@ namespace Dalamud.Data;
|
|||
/// </summary>
|
||||
internal sealed unsafe class RsvResolver : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<RsvResolver>();
|
||||
private static readonly ModuleLog Log = new("RsvProvider");
|
||||
|
||||
private readonly Hook<LayoutWorld.Delegates.AddRsvString> addRsvStringHook;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -14,15 +14,13 @@ using Dalamud.Plugin.Internal;
|
|||
using Dalamud.Storage;
|
||||
using Dalamud.Support;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using PInvoke;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using static Dalamud.NativeFunctions;
|
||||
|
||||
namespace Dalamud;
|
||||
|
||||
|
|
@ -60,7 +58,7 @@ public sealed class EntryPoint
|
|||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr)!;
|
||||
|
||||
if ((info.BootWaitMessageBox & 4) != 0)
|
||||
Windows.Win32.PInvoke.MessageBox(HWND.Null, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MESSAGEBOX_STYLE.MB_OK);
|
||||
MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
|
||||
|
||||
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
|
||||
}
|
||||
|
|
@ -137,16 +135,13 @@ public sealed class EntryPoint
|
|||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
NativeLibrary.Load(Path.Combine(info.WorkingDirectory!, "cimgui.dll"));
|
||||
|
||||
// Setup logger
|
||||
InitLogging(info.LogPath!, info.BootShowConsole, true, info.LogName);
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
// Load configuration first to get some early persistent state, like log level
|
||||
var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!);
|
||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs)
|
||||
.GetAwaiter().GetResult();
|
||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs);
|
||||
|
||||
// Set the appropriate logging level from the configuration
|
||||
if (!configuration.LogSynchronously)
|
||||
|
|
@ -184,17 +179,16 @@ public sealed class EntryPoint
|
|||
|
||||
Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory);
|
||||
|
||||
// Apply common fixes for culture issues
|
||||
CultureFixes.Apply();
|
||||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
||||
|
||||
// Currently VEH is not fully functional on WINE
|
||||
if (info.Platform != OSPlatform.Windows)
|
||||
if (!Util.IsWine())
|
||||
InitSymbolHandler(info);
|
||||
|
||||
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
|
||||
Versioning.GetScmVersion(),
|
||||
Versioning.GetGitHashClientStructs(),
|
||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
|
||||
Util.GetScmVersion(),
|
||||
Util.GetGitHashClientStructs(),
|
||||
FFXIVClientStructs.ThisAssembly.Git.Commits);
|
||||
|
||||
dalamud.WaitForUnload();
|
||||
|
|
@ -264,12 +258,10 @@ public sealed class EntryPoint
|
|||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||
var searchPath = $".;{symbolPath}";
|
||||
|
||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||
|
||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
||||
SymCleanup(GetCurrentProcess());
|
||||
|
||||
if (!Windows.Win32.PInvoke.SymInitialize(currentProcess, searchPath, true))
|
||||
if (!SymInitialize(GetCurrentProcess(), searchPath, true))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -293,6 +285,7 @@ public sealed class EntryPoint
|
|||
}
|
||||
|
||||
var pluginInfo = string.Empty;
|
||||
var supportText = ", please visit us on Discord for more help";
|
||||
try
|
||||
{
|
||||
var pm = Service<PluginManager>.GetNullable();
|
||||
|
|
@ -300,6 +293,9 @@ public sealed class EntryPoint
|
|||
if (plugin != null)
|
||||
{
|
||||
pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
|
||||
|
||||
if (plugin.IsThirdParty)
|
||||
supportText = string.Empty;
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
@ -307,18 +303,31 @@ public sealed class EntryPoint
|
|||
// ignored
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
|
||||
var result = MessageBoxW(
|
||||
Process.GetCurrentProcess().MainWindowHandle,
|
||||
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\n{ex.GetType().Name}\n{info}\n\n{pluginInfo}More information has been recorded separately{supportText}.\n\nDo you want to disable all plugins the next time you start the game?",
|
||||
"Dalamud",
|
||||
flags);
|
||||
|
||||
ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}");
|
||||
if (result == (int)User32.MessageBoxResult.IDYES)
|
||||
{
|
||||
Log.Information("User chose to disable plugins on next launch...");
|
||||
var config = Service<DalamudConfiguration>.Get();
|
||||
config.PluginSafeMode = true;
|
||||
config.QueueSave();
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# Format: Target.Full.TypeName = Source.Full.EnumTypeName
|
||||
# Example: Generate a local enum MyGeneratedEnum in namespace Sample.Gen mapped to SourceEnums.SampleSourceEnum
|
||||
Dalamud.Game.Agent.AgentId = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Enum describing possible action kinds.
|
||||
/// </summary>
|
||||
public enum ActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// A Trait.
|
||||
/// </summary>
|
||||
Trait = 0,
|
||||
|
||||
/// <summary>
|
||||
/// An Action.
|
||||
/// </summary>
|
||||
Action = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A usable Item.
|
||||
/// </summary>
|
||||
Item = 2, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// A usable EventItem.
|
||||
/// </summary>
|
||||
EventItem = 3, // does not work?
|
||||
|
||||
/// <summary>
|
||||
/// An EventAction.
|
||||
/// </summary>
|
||||
EventAction = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A GeneralAction.
|
||||
/// </summary>
|
||||
GeneralAction = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A BuddyAction.
|
||||
/// </summary>
|
||||
BuddyAction = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A MainCommand.
|
||||
/// </summary>
|
||||
MainCommand = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A Companion.
|
||||
/// </summary>
|
||||
Companion = 8, // unresolved?!
|
||||
|
||||
/// <summary>
|
||||
/// A CraftAction.
|
||||
/// </summary>
|
||||
CraftAction = 9,
|
||||
|
||||
/// <summary>
|
||||
/// An Action (again).
|
||||
/// </summary>
|
||||
Action2 = 10, // what's the difference?
|
||||
|
||||
/// <summary>
|
||||
/// A PetAction.
|
||||
/// </summary>
|
||||
PetAction = 11,
|
||||
|
||||
/// <summary>
|
||||
/// A CompanyAction.
|
||||
/// </summary>
|
||||
CompanyAction = 12,
|
||||
|
||||
/// <summary>
|
||||
/// A Mount.
|
||||
/// </summary>
|
||||
Mount = 13,
|
||||
|
||||
// 14-18 unused
|
||||
|
||||
/// <summary>
|
||||
/// A BgcArmyAction.
|
||||
/// </summary>
|
||||
BgcArmyAction = 19,
|
||||
|
||||
/// <summary>
|
||||
/// An Ornament.
|
||||
/// </summary>
|
||||
Ornament = 20,
|
||||
}
|
||||
107
Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
Normal file
107
Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>Argument pool for Addon Lifecycle services.</summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class AddonLifecyclePooledArgs : IServiceType
|
||||
{
|
||||
private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64];
|
||||
private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64];
|
||||
private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64];
|
||||
private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64];
|
||||
private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64];
|
||||
private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64];
|
||||
private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecyclePooledArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonSetupArgs> Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonFinalizeArgs> Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonDrawArgs> Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonUpdateArgs> Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonRefreshArgs> Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonRequestedUpdateArgs> Rent(out AddonRequestedUpdateArgs arg) =>
|
||||
new(out arg, this.addonRequestedUpdateArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonReceiveEventArgs> Rent(out AddonReceiveEventArgs arg) =>
|
||||
new(out arg, this.addonReceiveEventArgPool);
|
||||
|
||||
/// <summary>Returns the object to the pool on dispose.</summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
public readonly ref struct PooledEntry<T>
|
||||
where T : AddonArgs, new()
|
||||
{
|
||||
private readonly Span<T> pool;
|
||||
private readonly T obj;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="PooledEntry{T}"/> struct.</summary>
|
||||
/// <param name="arg">An instance of the argument.</param>
|
||||
/// <param name="pool">The pool to rent from and return to.</param>
|
||||
public PooledEntry(out T arg, Span<T> pool)
|
||||
{
|
||||
this.pool = pool;
|
||||
foreach (ref var item in pool)
|
||||
{
|
||||
if (Interlocked.Exchange(ref item, null) is { } v)
|
||||
{
|
||||
this.obj = arg = v;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.obj = arg = new();
|
||||
}
|
||||
|
||||
/// <summary>Returns the item to the pool.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
var tmp = this.obj;
|
||||
foreach (ref var item in this.pool)
|
||||
{
|
||||
if (Interlocked.Exchange(ref item, tmp) is not { } tmp2)
|
||||
return;
|
||||
tmp = tmp2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Events;
|
||||
|
|
@ -14,9 +14,9 @@ internal unsafe class AddonEventEntry
|
|||
/// Name of an invalid addon.
|
||||
/// </summary>
|
||||
public const string InvalidAddonName = "NullAddon";
|
||||
|
||||
|
||||
private string? addonName;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
|
|
@ -33,20 +33,20 @@ internal unsafe class AddonEventEntry
|
|||
public required nint Node { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate that gets called when this event is triggered.
|
||||
/// Gets the handler that gets called when this event is triggered.
|
||||
/// </summary>
|
||||
public required IAddonEventManager.AddonEventDelegate Delegate { get; init; }
|
||||
|
||||
public required IAddonEventManager.AddonEventHandler Handler { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id for this event.
|
||||
/// </summary>
|
||||
public required uint ParamKey { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type for this event.
|
||||
/// </summary>
|
||||
public required AddonEventType EventType { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event handle for this event.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ namespace Dalamud.Game.Addon.Events;
|
|||
internal unsafe class AddonEventListener : IDisposable
|
||||
{
|
||||
private ReceiveEventDelegate? receiveEventDelegate;
|
||||
|
||||
|
||||
private AtkEventListener* eventListener;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -24,7 +24,7 @@ internal unsafe class AddonEventListener : IDisposable
|
|||
|
||||
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
|
||||
this.eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)Marshal.AllocHGlobal(sizeof(void*) * 3);
|
||||
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, AtkEventListener*>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, void>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)(delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
|
||||
}
|
||||
|
|
@ -38,17 +38,17 @@ internal unsafe class AddonEventListener : IDisposable
|
|||
/// <param name="eventPtr">Pointer to the AtkEvent.</param>
|
||||
/// <param name="eventDataPtr">Pointer to the AtkEventData.</param>
|
||||
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this listener.
|
||||
/// </summary>
|
||||
public nint Address => (nint)this.eventListener;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.eventListener is null) return;
|
||||
|
||||
|
||||
Marshal.FreeHGlobal((nint)this.eventListener->VirtualTable);
|
||||
Marshal.FreeHGlobal((nint)this.eventListener);
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ internal unsafe class AddonEventListener : IDisposable
|
|||
node->RemoveEvent(eventType, param, this.eventListener, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void NullSub()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
|
@ -9,6 +9,7 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Events;
|
||||
|
|
@ -23,29 +24,33 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
/// PluginName for Dalamud Internal use.
|
||||
/// </summary>
|
||||
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
|
||||
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
|
||||
|
||||
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||
|
||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||
|
||||
private readonly Hook<AtkUnitManager.Delegates.UpdateCursor> onUpdateCursor;
|
||||
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||
|
||||
private AtkCursor.CursorType? cursorOverride;
|
||||
|
||||
|
||||
private AddonCursorType? cursorOverride;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonEventManager()
|
||||
private AddonEventManager(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||
|
||||
|
||||
this.cursorOverride = null;
|
||||
|
||||
this.onUpdateCursor = Hook<AtkUnitManager.Delegates.UpdateCursor>.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour);
|
||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
||||
|
||||
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
||||
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||
|
|
@ -53,6 +58,8 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
this.onUpdateCursor.Enable();
|
||||
}
|
||||
|
||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
|
|
@ -62,7 +69,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
{
|
||||
pluginEventController.Dispose();
|
||||
}
|
||||
|
||||
|
||||
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
|
||||
}
|
||||
|
||||
|
|
@ -73,19 +80,19 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||
/// <param name="atkResNode">The node that will trigger this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
/// <param name="eventDelegate">The delegate to call when event is triggered.</param>
|
||||
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||
internal IAddonEventHandle? AddEvent(Guid pluginId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
|
||||
internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
|
||||
{
|
||||
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventDelegate);
|
||||
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -105,12 +112,12 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
Log.Verbose($"Unable to locate controller for {pluginId}. No event was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Force the game cursor to be the specified cursor.
|
||||
/// </summary>
|
||||
/// <param name="cursor">Which cursor to use.</param>
|
||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor;
|
||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
|
||||
/// <summary>
|
||||
/// Un-forces the game cursor.
|
||||
|
|
@ -160,23 +167,22 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
pluginList.Value.RemoveForAddon(addonInfo.AddonName);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCursorDetour(AtkUnitManager* thisPtr)
|
||||
|
||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||
{
|
||||
try
|
||||
{
|
||||
var atkStage = AtkStage.Instance();
|
||||
|
||||
|
||||
if (this.cursorOverride is not null && atkStage is not null)
|
||||
{
|
||||
ref var atkCursor = ref atkStage->AtkCursor;
|
||||
|
||||
if (atkCursor.Type != this.cursorOverride)
|
||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||
if (cursor != this.cursorOverride)
|
||||
{
|
||||
atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -184,7 +190,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||
}
|
||||
|
||||
this.onUpdateCursor!.Original(thisPtr);
|
||||
return this.onUpdateCursor!.Original(module);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +218,7 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
|
|||
public AddonEventManagerPluginScoped(LocalPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
|
||||
|
||||
this.eventManagerService.AddPluginEventController(plugin.EffectiveWorkingPluginId);
|
||||
}
|
||||
|
||||
|
|
@ -224,34 +230,31 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
|
|||
{
|
||||
this.eventManagerService.ResetCursor();
|
||||
}
|
||||
|
||||
Service<Framework>.Get().RunOnFrameworkThread(() =>
|
||||
{
|
||||
this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId);
|
||||
}).Wait();
|
||||
|
||||
this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
|
||||
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventDelegate);
|
||||
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(IAddonEventHandle eventHandle)
|
||||
=> this.eventManagerService.RemoveEvent(this.plugin.EffectiveWorkingPluginId, eventHandle);
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor)
|
||||
{
|
||||
this.isForcingCursor = true;
|
||||
|
||||
|
||||
this.eventManagerService.SetCursor(cursor);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor()
|
||||
{
|
||||
this.isForcingCursor = false;
|
||||
|
||||
|
||||
this.eventManagerService.ResetCursor();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.Addon.Events;
|
||||
namespace Dalamud.Game.Addon.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of AtkEventType.
|
||||
|
|
@ -9,301 +9,150 @@ public enum AddonEventType : byte
|
|||
/// Mouse Down.
|
||||
/// </summary>
|
||||
MouseDown = 3,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Up.
|
||||
/// </summary>
|
||||
MouseUp = 4,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Move.
|
||||
/// </summary>
|
||||
MouseMove = 5,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Over.
|
||||
/// </summary>
|
||||
MouseOver = 6,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Out.
|
||||
/// </summary>
|
||||
MouseOut = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Wheel.
|
||||
/// </summary>
|
||||
MouseWheel = 8,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Click.
|
||||
/// </summary>
|
||||
MouseClick = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Double Click.
|
||||
/// </summary>
|
||||
MouseDoubleClick = 10,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Input Received.
|
||||
/// </summary>
|
||||
InputReceived = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Input Navigation (LEFT, RIGHT, UP, DOWN, TAB_NEXT, TAB_PREV, TAB_BOTH_NEXT, TAB_BOTH_PREV, PAGEUP, PAGEDOWN).
|
||||
/// </summary>
|
||||
InputNavigation = 13,
|
||||
|
||||
/// <summary>
|
||||
/// InputBase Input Received (AtkComponentTextInput and AtkComponentNumericInput).<br/>
|
||||
/// For example, this is fired for moving the text cursor, deletion of a character and inserting a new line.
|
||||
/// </summary>
|
||||
InputBaseInputReceived = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Fired at the very beginning of AtkInputManager.HandleInput on AtkStage.ViewportEventManager. Used in LovmMiniMap.
|
||||
/// </summary>
|
||||
RawInputData = 16,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Focus Start.
|
||||
/// </summary>
|
||||
FocusStart = 18,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Focus Stop.
|
||||
/// </summary>
|
||||
FocusStop = 19,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resize (ChatLogPanel).
|
||||
/// </summary>
|
||||
Resize = 21,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentButton Press, sent on MouseDown on Button.
|
||||
/// Button Press, sent on MouseDown on Button.
|
||||
/// </summary>
|
||||
ButtonPress = 23,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentButton Release, sent on MouseUp and MouseOut.
|
||||
/// Button Release, sent on MouseUp and MouseOut.
|
||||
/// </summary>
|
||||
ButtonRelease = 24,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentButton Click, sent on MouseUp and MouseClick on button.
|
||||
/// Button Click, sent on MouseUp and MouseClick on button.
|
||||
/// </summary>
|
||||
ButtonClick = 25,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Value Update (NumericInput, ScrollBar, etc.)
|
||||
/// </summary>
|
||||
ValueUpdate = 27,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentSlider Value Update.
|
||||
/// </summary>
|
||||
SliderValueUpdate = 29,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentSlider Released.
|
||||
/// </summary>
|
||||
SliderReleased = 30,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Button Press.
|
||||
/// </summary>
|
||||
ListButtonPress = 31,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Roll Over.
|
||||
/// List Item RollOver.
|
||||
/// </summary>
|
||||
ListItemRollOver = 33,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Roll Out.
|
||||
/// List Item Roll Out.
|
||||
/// </summary>
|
||||
ListItemRollOut = 34,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Click.
|
||||
/// List Item Toggle.
|
||||
/// </summary>
|
||||
ListItemClick = 35,
|
||||
|
||||
ListItemToggle = 35,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Double Click.
|
||||
/// </summary>
|
||||
ListItemDoubleClick = 36,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Highlight.
|
||||
/// </summary>
|
||||
ListItemHighlight = 37,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Select.
|
||||
/// </summary>
|
||||
ListItemSelect = 38,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Pad Drag Drop Begin.
|
||||
/// </summary>
|
||||
ListItemPadDragDropBegin = 40,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Pad Drag Drop End.
|
||||
/// </summary>
|
||||
ListItemPadDragDropEnd = 41,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Pad Drag Drop Insert.
|
||||
/// </summary>
|
||||
ListItemPadDragDropInsert = 42,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Begin.
|
||||
/// Drag Drop Begin.
|
||||
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
||||
/// </summary>
|
||||
DragDropBegin = 50,
|
||||
|
||||
DragDropBegin = 47,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop End.
|
||||
/// </summary>
|
||||
DragDropEnd = 51,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Insert Attempt.
|
||||
/// </summary>
|
||||
DragDropInsertAttempt = 52,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Insert.
|
||||
/// Drag Drop Insert.
|
||||
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
||||
/// </summary>
|
||||
DragDropInsert = 53,
|
||||
|
||||
DragDropInsert = 50,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Can Accept Check.
|
||||
/// Drag Drop Roll Over.
|
||||
/// </summary>
|
||||
DragDropCanAcceptCheck = 54,
|
||||
|
||||
DragDropRollOver = 52,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Roll Over.
|
||||
/// Drag Drop Roll Out.
|
||||
/// </summary>
|
||||
DragDropRollOver = 55,
|
||||
|
||||
DragDropRollOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Roll Out.
|
||||
/// </summary>
|
||||
DragDropRollOut = 56,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Discard.
|
||||
/// Drag Drop Discard.
|
||||
/// Sent when dropping an icon into empty screenspace, eg to remove an action from a hotBar.
|
||||
/// </summary>
|
||||
DragDropDiscard = 57,
|
||||
|
||||
DragDropDiscard = 54,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Click.
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
[Obsolete("Use DragDropDiscard")]
|
||||
DragDropUnk54 = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Cancel.
|
||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||
/// </summary>
|
||||
DragDropClick = 58,
|
||||
|
||||
DragDropCancel = 55,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Cancel.
|
||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
[Obsolete("Renamed to DragDropClick")]
|
||||
DragDropCancel = 58,
|
||||
|
||||
[Obsolete("Use DragDropCancel")]
|
||||
DragDropUnk55 = 55,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentIconText Roll Over.
|
||||
/// Icon Text Roll Over.
|
||||
/// </summary>
|
||||
IconTextRollOver = 59,
|
||||
|
||||
IconTextRollOver = 56,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentIconText Roll Out.
|
||||
/// Icon Text Roll Out.
|
||||
/// </summary>
|
||||
IconTextRollOut = 60,
|
||||
|
||||
IconTextRollOut = 57,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentIconText Click.
|
||||
/// Icon Text Click.
|
||||
/// </summary>
|
||||
IconTextClick = 61,
|
||||
|
||||
IconTextClick = 58,
|
||||
|
||||
/// <summary>
|
||||
/// AtkDialogue Close.
|
||||
/// Window Roll Over.
|
||||
/// </summary>
|
||||
DialogueClose = 62,
|
||||
|
||||
WindowRollOver = 67,
|
||||
|
||||
/// <summary>
|
||||
/// AtkDialogue Submit.
|
||||
/// Window Roll Out.
|
||||
/// </summary>
|
||||
DialogueSubmit = 63,
|
||||
|
||||
WindowRollOut = 68,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimer Tick.
|
||||
/// Window Change Scale.
|
||||
/// </summary>
|
||||
TimerTick = 64,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimer End.
|
||||
/// </summary>
|
||||
TimerEnd = 65,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimer Start.
|
||||
/// </summary>
|
||||
TimerStart = 66,
|
||||
|
||||
/// <summary>
|
||||
/// AtkSimpleTween Progress.
|
||||
/// </summary>
|
||||
TweenProgress = 67,
|
||||
|
||||
/// <summary>
|
||||
/// AtkSimpleTween Complete.
|
||||
/// </summary>
|
||||
TweenComplete = 68,
|
||||
|
||||
/// <summary>
|
||||
/// AtkAddonControl Child Addon Attached.
|
||||
/// </summary>
|
||||
ChildAddonAttached = 69,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentWindow Roll Over.
|
||||
/// </summary>
|
||||
WindowRollOver = 70,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentWindow Roll Out.
|
||||
/// </summary>
|
||||
WindowRollOut = 71,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentWindow Change Scale.
|
||||
/// </summary>
|
||||
WindowChangeScale = 72,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimeline Active Label Changed.
|
||||
/// </summary>
|
||||
TimelineActiveLabelChanged = 75,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTextNode Link Mouse Click.
|
||||
/// </summary>
|
||||
LinkMouseClick = 75,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTextNode Link Mouse Over.
|
||||
/// </summary>
|
||||
LinkMouseOver = 76,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTextNode Link Mouse Out.
|
||||
/// </summary>
|
||||
LinkMouseOut = 77,
|
||||
WindowChangeScale = 69,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Events.EventDataTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Object representing data that is relevant in handling native events.
|
||||
/// </summary>
|
||||
public class AddonEventData
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventData"/> class.
|
||||
/// </summary>
|
||||
internal AddonEventData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventData">Other event data to copy.</param>
|
||||
internal AddonEventData(AddonEventData eventData)
|
||||
{
|
||||
this.AtkEventType = eventData.AtkEventType;
|
||||
this.Param = eventData.Param;
|
||||
this.AtkEventPointer = eventData.AtkEventPointer;
|
||||
this.AtkEventDataPointer = eventData.AtkEventDataPointer;
|
||||
this.AddonPointer = eventData.AddonPointer;
|
||||
this.NodeTargetPointer = eventData.NodeTargetPointer;
|
||||
this.AtkEventListener = eventData.AtkEventListener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkEventType for this event.
|
||||
/// </summary>
|
||||
public AddonEventType AtkEventType { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the param field for this event.
|
||||
/// </summary>
|
||||
public uint Param { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkEvent object for this event.
|
||||
/// </summary>
|
||||
/// <remarks>Note: This is not a pointer to the AtkEventData object.<br/><br/>
|
||||
/// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.</remarks>
|
||||
public nint AtkEventPointer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkEventData object for this event.
|
||||
/// </summary>
|
||||
/// <remarks>This field will contain relevant data such as left vs right click, scroll up vs scroll down.</remarks>
|
||||
public nint AtkEventDataPointer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkUnitBase that is handling this event.
|
||||
/// </summary>
|
||||
public nint AddonPointer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkResNode that triggered this event.
|
||||
/// </summary>
|
||||
public nint NodeTargetPointer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a pointer to the AtkEventListener responsible for handling this event.
|
||||
/// Note: As the event listener is dalamud allocated, there's no reason to expose this field.
|
||||
/// </summary>
|
||||
internal nint AtkEventListener { get; set; }
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using AtkMouseData = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData;
|
||||
using ModifierFlag = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData.ModifierFlag;
|
||||
|
||||
namespace Dalamud.Game.Addon.Events.EventDataTypes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public unsafe class AddonMouseEventData : AddonEventData
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonMouseEventData"/> class.
|
||||
/// </summary>
|
||||
internal AddonMouseEventData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonMouseEventData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventData">Other event data to copy.</param>
|
||||
internal AddonMouseEventData(AddonEventData eventData)
|
||||
: base(eventData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was a Left Mouse Click.
|
||||
/// </summary>
|
||||
public bool IsLeftClick => this.MouseData.ButtonId is 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was a Right Mouse Click.
|
||||
/// </summary>
|
||||
public bool IsRightClick => this.MouseData.ButtonId is 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether there are any modifiers set such as alt, control, shift, or dragging.
|
||||
/// </summary>
|
||||
public bool IsNoModifier => this.MouseData.Modifier is 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether alt was being held when this event triggered.
|
||||
/// </summary>
|
||||
public bool IsAltHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Alt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether control was being held when this event triggered.
|
||||
/// </summary>
|
||||
public bool IsControlHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Ctrl);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether shift was being held when this event triggered.
|
||||
/// </summary>
|
||||
public bool IsShiftHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Shift);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this event is a mouse drag or not.
|
||||
/// </summary>
|
||||
public bool IsDragging => this.MouseData.Modifier.HasFlag(ModifierFlag.Dragging);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was a scroll up.
|
||||
/// </summary>
|
||||
public bool IsScrollUp => this.MouseData.WheelDirection is 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was a scroll down.
|
||||
/// </summary>
|
||||
public bool IsScrollDown => this.MouseData.WheelDirection is -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the mouse when this event was triggered.
|
||||
/// </summary>
|
||||
public Vector2 Position => new(this.MouseData.PosX, this.MouseData.PosY);
|
||||
|
||||
private AtkEventData* AtkEventData => (AtkEventData*)this.AtkEventDataPointer;
|
||||
|
||||
private AtkMouseData MouseData => this.AtkEventData->MouseData;
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Game.Addon.Events.EventDataTypes;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game.Addon.Events;
|
|||
/// </summary>
|
||||
internal unsafe class PluginEventController : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
||||
|
|
@ -26,8 +26,8 @@ internal unsafe class PluginEventController : IDisposable
|
|||
}
|
||||
|
||||
private AddonEventListener EventListener { get; init; }
|
||||
|
||||
private List<AddonEventEntry> Events { get; } = [];
|
||||
|
||||
private List<AddonEventEntry> Events { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tracked event.
|
||||
|
|
@ -35,16 +35,16 @@ internal unsafe class PluginEventController : IDisposable
|
|||
/// <param name="atkUnitBase">The Parent addon for the event.</param>
|
||||
/// <param name="atkResNode">The Node for the event.</param>
|
||||
/// <param name="atkEventType">The Event Type.</param>
|
||||
/// <param name="eventDelegate">The delegate to call when invoking this event.</param>
|
||||
/// <param name="handler">The delegate to call when invoking this event.</param>
|
||||
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventDelegate eventDelegate)
|
||||
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
|
||||
{
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
var eventType = (AtkEventType)atkEventType;
|
||||
var eventId = this.GetNextParamKey();
|
||||
var eventGuid = Guid.NewGuid();
|
||||
|
||||
|
||||
var eventHandle = new AddonEventHandle
|
||||
{
|
||||
AddonName = addon->NameString,
|
||||
|
|
@ -52,11 +52,11 @@ internal unsafe class PluginEventController : IDisposable
|
|||
EventType = atkEventType,
|
||||
EventGuid = eventGuid,
|
||||
};
|
||||
|
||||
|
||||
var eventEntry = new AddonEventEntry
|
||||
{
|
||||
Addon = atkUnitBase,
|
||||
Delegate = eventDelegate,
|
||||
Handler = handler,
|
||||
Node = atkResNode,
|
||||
EventType = atkEventType,
|
||||
ParamKey = eventId,
|
||||
|
|
@ -92,14 +92,14 @@ internal unsafe class PluginEventController : IDisposable
|
|||
if (this.Events.Where(entry => entry.AddonName == addonName).ToList() is { Count: not 0 } events)
|
||||
{
|
||||
Log.Verbose($"Addon: {addonName} is Finalizing, removing {events.Count} events.");
|
||||
|
||||
|
||||
foreach (var registeredEvent in events)
|
||||
{
|
||||
this.RemoveEvent(registeredEvent.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -107,7 +107,7 @@ internal unsafe class PluginEventController : IDisposable
|
|||
{
|
||||
this.RemoveEvent(registeredEvent.Handle);
|
||||
}
|
||||
|
||||
|
||||
this.EventListener.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ internal unsafe class PluginEventController : IDisposable
|
|||
|
||||
throw new OverflowException($"uint.MaxValue number of ParamKeys used for this event controller.");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove a tracked event from native UI.
|
||||
/// This method performs several safety checks to only remove events from a still active addon.
|
||||
|
|
@ -139,22 +139,20 @@ internal unsafe class PluginEventController : IDisposable
|
|||
// Is our stored addon pointer the same as the active addon pointer?
|
||||
if (currentAddonPointer != eventEntry.Addon) return;
|
||||
|
||||
// Make sure the addon is not unloaded
|
||||
var atkUnitBase = currentAddonPointer.Struct;
|
||||
if (atkUnitBase->UldManager.LoadedState == AtkLoadState.Unloaded) return;
|
||||
|
||||
// Does this addon contain the node this event is for? (by address)
|
||||
var atkUnitBase = (AtkUnitBase*)currentAddonPointer;
|
||||
var nodeFound = false;
|
||||
foreach (var node in atkUnitBase->UldManager.Nodes)
|
||||
foreach (var index in Enumerable.Range(0, atkUnitBase->UldManager.NodeListCount))
|
||||
{
|
||||
var node = atkUnitBase->UldManager.NodeList[index];
|
||||
|
||||
// If this node matches our node, then we know our node is still valid.
|
||||
if ((nint)node.Value == eventEntry.Node)
|
||||
if (node is not null && (nint)node == eventEntry.Node)
|
||||
{
|
||||
nodeFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we didn't find the node, we can't remove the event.
|
||||
if (!nodeFound) return;
|
||||
|
||||
|
|
@ -168,41 +166,33 @@ internal unsafe class PluginEventController : IDisposable
|
|||
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
|
||||
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
|
||||
var eventTypeMatches = currentEvent->State.EventType == eventType;
|
||||
|
||||
|
||||
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
|
||||
{
|
||||
eventFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Move to the next event.
|
||||
currentEvent = currentEvent->NextEvent;
|
||||
}
|
||||
|
||||
|
||||
// If we didn't find the event, we can't remove the event.
|
||||
if (!eventFound) return;
|
||||
|
||||
// We have a valid addon, valid node, valid event, and valid key.
|
||||
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
|
||||
}
|
||||
|
||||
|
||||
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (eventPtr is null) return;
|
||||
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
|
||||
|
||||
eventInfo.Delegate.Invoke((AddonEventType)eventType, new AddonEventData
|
||||
{
|
||||
AddonPointer = (nint)eventPtr->Node,
|
||||
NodeTargetPointer = (nint)eventPtr->Target,
|
||||
AtkEventDataPointer = (nint)eventDataPtr,
|
||||
AtkEventListener = (nint)self,
|
||||
AtkEventType = (AddonEventType)eventType,
|
||||
Param = eventParam,
|
||||
AtkEventPointer = (nint)eventPtr,
|
||||
});
|
||||
|
||||
// We stored the AtkUnitBase* in EventData->Node, and EventData->Target contains the node that triggered the event.
|
||||
eventInfo.Handler.Invoke((AddonEventType)eventType, (nint)eventPtr->Node, (nint)eventPtr->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,46 +1,92 @@
|
|||
using Dalamud.Game.NativeWrapper;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Memory;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for AddonLifecycle AddonArgTypes.
|
||||
/// </summary>
|
||||
public class AddonArgs
|
||||
public abstract unsafe class AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant string representing the name of an addon that is invalid.
|
||||
/// </summary>
|
||||
public const string InvalidAddon = "NullAddon";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonArgs()
|
||||
{
|
||||
}
|
||||
private string? addonName;
|
||||
private IntPtr addon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName { get; private set; } = InvalidAddon;
|
||||
public string AddonName => this.GetAddonName();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
public AtkUnitBasePtr Addon
|
||||
public nint Addon
|
||||
{
|
||||
get;
|
||||
internal set
|
||||
{
|
||||
field = value;
|
||||
|
||||
if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
|
||||
this.AddonName = value.Name;
|
||||
}
|
||||
get => this.AddonInternal;
|
||||
init => this.AddonInternal = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public virtual AddonArgsType Type => AddonArgsType.Generic;
|
||||
public abstract AddonArgsType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
internal nint AddonInternal
|
||||
{
|
||||
get => this.addon;
|
||||
set
|
||||
{
|
||||
this.addon = value;
|
||||
|
||||
// Note: always clear addonName on updating the addon being pointed.
|
||||
// Same address may point to a different addon.
|
||||
this.addonName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if addon name matches the given span of char.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to check.</param>
|
||||
/// <returns>Whether it is the case.</returns>
|
||||
internal bool IsAddon(ReadOnlySpan<char> name)
|
||||
{
|
||||
if (this.Addon == nint.Zero) return false;
|
||||
if (name.Length is 0 or > 0x20)
|
||||
return false;
|
||||
|
||||
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||
if (addonPointer->Name[0] == 0) return false;
|
||||
|
||||
// note: might want to rewrite this to just compare to NameString
|
||||
return MemoryHelper.EqualsZeroTerminatedString(
|
||||
name,
|
||||
(nint)Unsafe.AsPointer(ref addonPointer->Name[0]),
|
||||
null,
|
||||
0x20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for ensuring the name of the addon is valid.
|
||||
/// </summary>
|
||||
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
|
||||
private string GetAddonName()
|
||||
{
|
||||
if (this.Addon == nint.Zero) return InvalidAddon;
|
||||
|
||||
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||
if (addonPointer->Name[0] == 0) return InvalidAddon;
|
||||
|
||||
return this.addonName ??= addonPointer->NameString;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Close events.
|
||||
/// </summary>
|
||||
public class AddonCloseArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonCloseArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonCloseArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Close;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the window should fire the callback method on close.
|
||||
/// </summary>
|
||||
public bool FireCallback { get; set; }
|
||||
}
|
||||
24
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
24
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Draw events.
|
||||
/// </summary>
|
||||
public class AddonDrawArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonDrawArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonDrawArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonFinalizeArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonFinalizeArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonFinalizeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for OnFocusChanged events.
|
||||
/// </summary>
|
||||
public class AddonFocusChangedArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonFocusChangedArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonFocusChangedArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.FocusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the window is being focused or unfocused.
|
||||
/// </summary>
|
||||
public bool ShouldFocus { get; set; }
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Hide events.
|
||||
/// </summary>
|
||||
public class AddonHideArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonHideArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonHideArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Hide;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to call the hide callback handler when this hides.
|
||||
/// </summary>
|
||||
public bool CallHideCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flags that the window will set when it Shows/Hides.
|
||||
/// </summary>
|
||||
public uint SetShowHideFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether something for this event message.
|
||||
/// </summary>
|
||||
internal bool UnknownBool { get; set; }
|
||||
}
|
||||
|
|
@ -3,12 +3,13 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonReceiveEventArgs : AddonArgs
|
||||
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonReceiveEventArgs()
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonReceiveEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +32,13 @@ public class AddonReceiveEventArgs : AddonArgs
|
|||
public nint AtkEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to an AtkEventData for this event message.
|
||||
/// Gets or sets the pointer to a block of data for this event message.
|
||||
/// </summary>
|
||||
public nint AtkEventData { get; set; }
|
||||
public nint Data { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.NativeWrapper;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Refresh events.
|
||||
/// </summary>
|
||||
public class AddonRefreshArgs : AddonArgs
|
||||
public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonRefreshArgs()
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonRefreshArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -36,32 +31,11 @@ public class AddonRefreshArgs : AddonArgs
|
|||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||
[Api15ToDo("Make this internal, remove obsolete")]
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||
/// </returns>
|
||||
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
||||
{
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < this.AtkValueCount; i++)
|
||||
{
|
||||
AtkValuePtr ptr;
|
||||
unsafe
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
||||
|
||||
yield return ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for OnRequestedUpdate events.
|
||||
/// </summary>
|
||||
public class AddonRequestedUpdateArgs : AddonArgs
|
||||
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonRequestedUpdateArgs()
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonRequestedUpdateArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -24,4 +25,10 @@ public class AddonRequestedUpdateArgs : AddonArgs
|
|||
/// Gets or sets the StringArrayData** for this event.
|
||||
/// </summary>
|
||||
public nint StringArrayData { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.NativeWrapper;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Setup events.
|
||||
/// </summary>
|
||||
public class AddonSetupArgs : AddonArgs
|
||||
public class AddonSetupArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonSetupArgs()
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonSetupArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -36,32 +31,11 @@ public class AddonSetupArgs : AddonArgs
|
|||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||
[Api15ToDo("Make this internal, remove obsolete")]
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||
/// </returns>
|
||||
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
||||
{
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < this.AtkValueCount; i++)
|
||||
{
|
||||
AtkValuePtr ptr;
|
||||
unsafe
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
||||
|
||||
yield return ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Show events.
|
||||
/// </summary>
|
||||
public class AddonShowArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonShowArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonShowArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Show;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the window should play open sound effects.
|
||||
/// </summary>
|
||||
public bool SilenceOpenSoundEffect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flags that the window will unset when it Shows/Hides.
|
||||
/// </summary>
|
||||
public uint UnsetShowHideFlags { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Update events.
|
||||
/// </summary>
|
||||
public class AddonUpdateArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonUpdateArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonUpdateArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Update;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time since the last update.
|
||||
/// </summary>
|
||||
public float TimeDelta
|
||||
{
|
||||
get => this.TimeDeltaInternal;
|
||||
init => this.TimeDeltaInternal = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time since the last update.
|
||||
/// </summary>
|
||||
internal float TimeDeltaInternal { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
@ -5,48 +5,38 @@
|
|||
/// </summary>
|
||||
public enum AddonArgsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic arg type that contains no meaningful data.
|
||||
/// </summary>
|
||||
Generic,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Setup.
|
||||
/// </summary>
|
||||
Setup,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Update.
|
||||
/// </summary>
|
||||
Update,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Draw.
|
||||
/// </summary>
|
||||
Draw,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Finalize.
|
||||
/// </summary>
|
||||
Finalize,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for RequestedUpdate.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
RequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Refresh.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
Refresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ReceiveEvent.
|
||||
/// </summary>
|
||||
ReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Show.
|
||||
/// </summary>
|
||||
Show,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Hide.
|
||||
/// </summary>
|
||||
Hide,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Close.
|
||||
/// </summary>
|
||||
Close,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for OnFocusChanged.
|
||||
/// </summary>
|
||||
FocusChanged,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public enum AddonEvent
|
|||
/// </summary>
|
||||
/// <seealso cref="AddonSetupArgs"/>
|
||||
PreSetup,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
|
||||
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
|
||||
|
|
@ -29,6 +29,7 @@ public enum AddonEvent
|
|||
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
|
||||
/// is fired every frame that an addon is loaded, regardless of visibility.
|
||||
/// </summary>
|
||||
/// <seealso cref="AddonUpdateArgs"/>
|
||||
PreUpdate,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -41,6 +42,7 @@ public enum AddonEvent
|
|||
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
||||
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
||||
/// </summary>
|
||||
/// <seealso cref="AddonDrawArgs"/>
|
||||
PreDraw,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -60,8 +62,9 @@ public enum AddonEvent
|
|||
/// <br />
|
||||
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
||||
/// </remarks>
|
||||
/// <seealso cref="AddonFinalizeArgs"/>
|
||||
PreFinalize,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
||||
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
||||
|
|
@ -78,13 +81,13 @@ public enum AddonEvent
|
|||
/// to the Free Company's overview.
|
||||
/// </example>
|
||||
PreRequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
||||
/// See <see cref="PreRequestedUpdate"/> for more information.
|
||||
/// </summary>
|
||||
PostRequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
||||
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
||||
|
|
@ -93,13 +96,13 @@ public enum AddonEvent
|
|||
/// <seealso cref="AddonRefreshArgs"/>
|
||||
/// <seealso cref="PostRefresh"/>
|
||||
PreRefresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has finished its refresh.
|
||||
/// See <see cref="PreRefresh"/> for more information.
|
||||
/// </summary>
|
||||
PostRefresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon begins processing a user-driven event via
|
||||
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
||||
|
|
@ -109,108 +112,10 @@ public enum AddonEvent
|
|||
/// <seealso cref="AddonReceiveEventArgs"/>
|
||||
/// <seealso cref="PostReceiveEvent"/>
|
||||
PreReceiveEvent,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
||||
/// See <see cref="PreReceiveEvent"/> for more information.
|
||||
/// </summary>
|
||||
PostReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its open method.
|
||||
/// </summary>
|
||||
PreOpen,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its open method.
|
||||
/// </summary>
|
||||
PostOpen,
|
||||
|
||||
/// <summary>
|
||||
/// An even that is fired before an addon processes its Close method.
|
||||
/// </summary>
|
||||
PreClose,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Close method.
|
||||
/// </summary>
|
||||
PostClose,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Show method.
|
||||
/// </summary>
|
||||
PreShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Show method.
|
||||
/// </summary>
|
||||
PostShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Hide method.
|
||||
/// </summary>
|
||||
PreHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Hide method.
|
||||
/// </summary>
|
||||
PostHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its OnMove method.
|
||||
/// OnMove is triggered only when a move is completed.
|
||||
/// </summary>
|
||||
PreMove,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its OnMove method.
|
||||
/// OnMove is triggered only when a move is completed.
|
||||
/// </summary>
|
||||
PostMove,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its MouseOver method.
|
||||
/// </summary>
|
||||
PreMouseOver,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its MouseOver method.
|
||||
/// </summary>
|
||||
PostMouseOver,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its MouseOut method.
|
||||
/// </summary>
|
||||
PreMouseOut,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its MouseOut method.
|
||||
/// </summary>
|
||||
PostMouseOut,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Focus method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||
/// </remarks>
|
||||
PreFocus,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Focus method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||
/// </remarks>
|
||||
PostFocus,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its FocusChanged method.
|
||||
/// </summary>
|
||||
PreFocusChanged,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after a addon processes its FocusChanged method.
|
||||
/// </summary>
|
||||
PostFocusChanged,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.Internal;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
|
@ -20,56 +21,75 @@ namespace Dalamud.Game.Addon.Lifecycle;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all allocated addon virtual tables.
|
||||
/// </summary>
|
||||
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
||||
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<AddonLifecycle>();
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
||||
private bool isInvokingListeners;
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
||||
|
||||
private readonly nint disallowedReceiveEventAddress;
|
||||
|
||||
private readonly AddonLifecycleAddressResolver address;
|
||||
private readonly AddonSetupHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
|
||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
|
||||
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> onAddonRequestedUpdateHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecycle()
|
||||
private AddonLifecycle(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
|
||||
this.onInitializeAddonHook.Enable();
|
||||
this.address = new AddonLifecycleAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent;
|
||||
|
||||
var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon;
|
||||
|
||||
this.onAddonSetupHook = new AddonSetupHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
||||
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
|
||||
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
|
||||
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
|
||||
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
||||
|
||||
this.onAddonSetupHook.Enable();
|
||||
this.onAddonFinalizeHook.Enable();
|
||||
this.onAddonDrawHook.Enable();
|
||||
this.onAddonUpdateHook.Enable();
|
||||
this.onAddonRefreshHook.Enable();
|
||||
this.onAddonRequestedUpdateHook.Enable();
|
||||
}
|
||||
|
||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
|
||||
/// </summary>
|
||||
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AddonLifecycle Event Listeners.
|
||||
/// </summary> <br/>
|
||||
/// Mapping is: EventType -> AddonName -> ListenerList
|
||||
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
|
||||
/// </summary>
|
||||
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.onInitializeAddonHook?.Dispose();
|
||||
this.onInitializeAddonHook = null;
|
||||
this.onAddonSetupHook.Dispose();
|
||||
this.onAddonFinalizeHook.Dispose();
|
||||
this.onAddonDrawHook.Dispose();
|
||||
this.onAddonUpdateHook.Dispose();
|
||||
this.onAddonRefreshHook.Dispose();
|
||||
this.onAddonRequestedUpdateHook.Dispose();
|
||||
|
||||
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||
AllocatedTables.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a virtual table address to the original virtual table address.
|
||||
/// </summary>
|
||||
/// <param name="tableAddress">The modified address to resolve.</param>
|
||||
/// <returns>The original address.</returns>
|
||||
internal static AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
|
||||
{
|
||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||
if (matchedTable == null)
|
||||
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
||||
{
|
||||
return null;
|
||||
receiveEventListener.Dispose();
|
||||
}
|
||||
|
||||
return matchedTable.OriginalVirtualTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -78,14 +98,20 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="listener">The listener to register.</param>
|
||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||
{
|
||||
if (this.isInvokingListeners)
|
||||
this.framework.RunOnTick(() =>
|
||||
{
|
||||
this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
|
||||
}
|
||||
this.EventListeners.Add(listener);
|
||||
|
||||
// If we want receive event messages have an already active addon, enable the receive event hook.
|
||||
// If the addon isn't active yet, we'll grab the hook when it sets up.
|
||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
||||
{
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
||||
{
|
||||
receiveEventListener.TryEnable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -94,16 +120,27 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="listener">The listener to unregister.</param>
|
||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||
{
|
||||
listener.IsRequestedToClear = true;
|
||||
// Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
|
||||
listener.Removed = true;
|
||||
|
||||
if (this.isInvokingListeners)
|
||||
this.framework.RunOnTick(() =>
|
||||
{
|
||||
this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
|
||||
}
|
||||
this.EventListeners.Remove(listener);
|
||||
|
||||
// If we are disabling an ReceiveEvent listener, check if we should disable the hook.
|
||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
||||
{
|
||||
// Get the ReceiveEvent Listener for this addon
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
||||
{
|
||||
// If there are no other listeners listening for this event, disable the hook.
|
||||
if (!this.EventListeners.Any(listeners => listeners.AddonName.Contains(listener.AddonName) && listener.EventType is AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent))
|
||||
{
|
||||
receiveEventListener.Disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -114,104 +151,220 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="blame">What to blame on errors.</param>
|
||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||
{
|
||||
this.isInvokingListeners = true;
|
||||
|
||||
// Early return if we don't have any listeners of this type
|
||||
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
||||
|
||||
// Handle listeners for this event type that don't care which addon is triggering it
|
||||
if (addonListeners.TryGetValue(string.Empty, out var globalListeners))
|
||||
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
||||
foreach (var listener in this.EventListeners)
|
||||
{
|
||||
foreach (var listener in globalListeners)
|
||||
if (listener.EventType != eventType)
|
||||
continue;
|
||||
|
||||
// If the listener is pending removal, and is waiting until the next Framework Update, don't invoke listener.
|
||||
if (listener.Removed)
|
||||
continue;
|
||||
|
||||
// Match on string.empty for listeners that want events for all addons.
|
||||
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
if (listener.IsRequestedToClear) continue;
|
||||
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
|
||||
}
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle listeners that are listening for this addon and event type specifically
|
||||
if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
|
||||
{
|
||||
foreach (var listener in addonListener)
|
||||
{
|
||||
if (listener.IsRequestedToClear) continue;
|
||||
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isInvokingListeners = false;
|
||||
}
|
||||
|
||||
private void RegisterListenerMethod(AddonLifecycleEventListener listener)
|
||||
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
||||
{
|
||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
|
||||
// Disallows hooking the core internal event handler.
|
||||
var addonName = addon->NameString;
|
||||
var receiveEventAddress = (nint)addon->VirtualTable->ReceiveEvent;
|
||||
if (receiveEventAddress != this.disallowedReceiveEventAddress)
|
||||
{
|
||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||
// If we have a ReceiveEvent listener already made for this hook address, add this addon's name to that handler.
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.FunctionAddress == receiveEventAddress) is { } existingListener)
|
||||
{
|
||||
return;
|
||||
if (!existingListener.AddonNames.Contains(addonName))
|
||||
{
|
||||
existingListener.AddonNames.Add(addonName);
|
||||
}
|
||||
}
|
||||
|
||||
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
|
||||
else
|
||||
{
|
||||
this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
|
||||
}
|
||||
|
||||
// If we have an active listener for this addon already, we need to activate this hook.
|
||||
if (this.EventListeners.Any(listener => (listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent) && listener.AddonName == addonName))
|
||||
{
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener)
|
||||
{
|
||||
receiveEventListener.TryEnable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
||||
{
|
||||
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
||||
}
|
||||
|
||||
private void UnregisterListenerMethod(AddonLifecycleEventListener listener)
|
||||
private void UnregisterReceiveEventHook(string addonName)
|
||||
{
|
||||
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||
// Remove this addons ReceiveEvent Registration
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
|
||||
{
|
||||
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||
eventListener.AddonNames.Remove(addonName);
|
||||
|
||||
// If there are no more listeners let's remove and dispose.
|
||||
if (eventListener.AddonNames.Count is 0)
|
||||
{
|
||||
addonListener.Remove(listener);
|
||||
this.ReceiveEventListeners.Remove(eventListener);
|
||||
eventListener.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonInitialize(AtkUnitBase* addon)
|
||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogInitialize(addon->NameString);
|
||||
|
||||
// AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||
AllocatedTables.Add(new AddonVirtualTable(addon, this));
|
||||
this.RegisterReceiveEventHook(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in AddonLifecycle during OnAddonInitialize.");
|
||||
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
||||
}
|
||||
|
||||
this.onInitializeAddonHook!.Original(addon);
|
||||
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
arg.AtkValueCount = valueCount;
|
||||
arg.AtkValues = (nint)values;
|
||||
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
|
||||
valueCount = arg.AtkValueCount;
|
||||
values = (AtkValue*)arg.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
addon->OnSetup(valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostSetup, arg);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void LogInitialize(string addonName)
|
||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||
{
|
||||
Log.Debug($"Initializing {addonName}");
|
||||
try
|
||||
{
|
||||
var addonName = atkUnitBase[0]->NameString;
|
||||
this.UnregisterReceiveEventHook(addonName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
||||
}
|
||||
|
||||
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
|
||||
arg.AddonInternal = (nint)atkUnitBase[0];
|
||||
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
|
||||
|
||||
try
|
||||
{
|
||||
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonFinalize. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonDraw(AtkUnitBase* addon)
|
||||
{
|
||||
using var returner = this.argsPool.Rent(out AddonDrawArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
this.InvokeListenersSafely(AddonEvent.PreDraw, arg);
|
||||
|
||||
try
|
||||
{
|
||||
addon->Draw();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonDraw. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostDraw, arg);
|
||||
}
|
||||
|
||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||
{
|
||||
using var returner = this.argsPool.Rent(out AddonUpdateArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
arg.TimeDeltaInternal = delta;
|
||||
this.InvokeListenersSafely(AddonEvent.PreUpdate, arg);
|
||||
|
||||
try
|
||||
{
|
||||
addon->Update(delta);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostUpdate, arg);
|
||||
}
|
||||
|
||||
private bool OnAddonRefresh(AtkUnitManager* thisPtr, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
arg.AtkValueCount = valueCount;
|
||||
arg.AtkValues = (nint)values;
|
||||
this.InvokeListenersSafely(AddonEvent.PreRefresh, arg);
|
||||
valueCount = arg.AtkValueCount;
|
||||
values = (AtkValue*)arg.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.onAddonRefreshHook.Original(thisPtr, addon, valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonRefresh. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostRefresh, arg);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
arg.NumberArrayData = (nint)numberArrayData;
|
||||
arg.StringArrayData = (nint)stringArrayData;
|
||||
this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg);
|
||||
numberArrayData = (NumberArrayData**)arg.NumberArrayData;
|
||||
stringArrayData = (StringArrayData**)arg.StringArrayData;
|
||||
|
||||
try
|
||||
{
|
||||
addon->OnRequestedUpdate(numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +381,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = [];
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
|
|
@ -299,14 +452,10 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
|||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.FunctionDelegate != handler) return false;
|
||||
|
||||
|
||||
this.addonLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||
=> (nint)AddonLifecycle.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// AddonLifecycleService memory address resolver.
|
||||
/// </summary>
|
||||
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
|
||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
||||
/// This is called for a majority of all addon OnSetup's.
|
||||
/// </summary>
|
||||
public nint AddonSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
|
||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
||||
/// This seems to be called rarely for specific addons.
|
||||
/// </summary>
|
||||
public nint AddonSetup2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
|
||||
/// </summary>
|
||||
public nint AddonFinalize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon draw hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonDraw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon update hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonOnRequestedUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB");
|
||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
|
||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
|
||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20");
|
||||
}
|
||||
}
|
||||
|
|
@ -25,19 +25,19 @@ internal class AddonLifecycleEventListener
|
|||
/// string.Empty if it wants to be called for any addon.
|
||||
/// </summary>
|
||||
public string AddonName { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this event has been unregistered.
|
||||
/// </summary>
|
||||
public bool Removed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type this listener is looking for.
|
||||
/// </summary>
|
||||
public AddonEvent EventType { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate this listener invokes.
|
||||
/// </summary>
|
||||
public IAddonLifecycle.AddonEventDelegate FunctionDelegate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the listener is requested to be cleared.
|
||||
/// </summary>
|
||||
internal bool IsRequestedToClear { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates for Addon_OnReceiveEvent.
|
||||
/// Multiple addons may use the same ReceiveEvent function, this helper makes sure that those addon events are handled properly.
|
||||
/// </summary>
|
||||
internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="service">AddonLifecycle service instance.</param>
|
||||
/// <param name="addonName">Initial Addon Requesting this listener.</param>
|
||||
/// <param name="receiveEventAddress">Address of Addon's ReceiveEvent function.</param>
|
||||
internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
|
||||
{
|
||||
this.AddonLifecycle = service;
|
||||
this.AddonNames = [addonName];
|
||||
this.FunctionAddress = receiveEventAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of addons that use this receive event hook.
|
||||
/// </summary>
|
||||
public List<string> AddonNames { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
|
||||
/// </summary>
|
||||
public nint FunctionAddress { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contained hook for these addons.
|
||||
/// </summary>
|
||||
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Reference to AddonLifecycle service instance.
|
||||
/// </summary>
|
||||
private AddonLifecycle AddonLifecycle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Try to hook and enable this receive event handler.
|
||||
/// </summary>
|
||||
public void TryEnable()
|
||||
{
|
||||
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
|
||||
this.Hook?.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the hook for this receive event handler.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.Hook?.Disable();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Hook?.Dispose();
|
||||
}
|
||||
|
||||
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
// Check that we didn't get here through a call to another addons handler.
|
||||
var addonName = addon->NameString;
|
||||
if (!this.AddonNames.Contains(addonName))
|
||||
{
|
||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
return;
|
||||
}
|
||||
|
||||
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
|
||||
arg.AddonInternal = (nint)addon;
|
||||
arg.AtkEventType = (byte)eventType;
|
||||
arg.EventParam = eventParam;
|
||||
arg.AtkEvent = (IntPtr)atkEvent;
|
||||
arg.Data = (nint)atkEventData;
|
||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
|
||||
eventType = (AtkEventType)arg.AtkEventType;
|
||||
eventParam = arg.EventParam;
|
||||
atkEvent = (AtkEvent*)arg.AtkEvent;
|
||||
atkEventData = (AtkEventData*)arg.Data;
|
||||
|
||||
try
|
||||
{
|
||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg);
|
||||
}
|
||||
}
|
||||
80
Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
Normal file
80
Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using Reloaded.Hooks.Definitions;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
||||
internal class AddonSetupHook<T> : IDisposable where T : Delegate
|
||||
{
|
||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
||||
|
||||
private T? detour;
|
||||
private bool activated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonSetupHook{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the instruction to replace.</param>
|
||||
/// <param name="detour">Delegate to invoke.</param>
|
||||
internal AddonSetupHook(nint address, T detour)
|
||||
{
|
||||
this.detour = detour;
|
||||
|
||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
||||
var code = new[]
|
||||
{
|
||||
"use64",
|
||||
$"mov r9, 0x{detourPtr:X8}",
|
||||
};
|
||||
|
||||
var opt = new AsmHookOptions
|
||||
{
|
||||
PreferRelativeJump = true,
|
||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
||||
MaxOpcodeSize = 5,
|
||||
};
|
||||
|
||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the hook is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
if (!this.activated)
|
||||
{
|
||||
this.activated = true;
|
||||
this.asmHook.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
this.asmHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
this.detour = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,679 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that holds references to an addons original and modified virtual table entries.
|
||||
/// </summary>
|
||||
internal unsafe class AddonVirtualTable : IDisposable
|
||||
{
|
||||
// This need to be at minimum the largest virtual table size of all addons
|
||||
// Copying extra entries is not problematic, and is considered safe.
|
||||
private const int VirtualTableEntryCount = 200;
|
||||
|
||||
private const bool EnableLogging = false;
|
||||
|
||||
private static readonly ModuleLog Log = new("LifecycleVT");
|
||||
|
||||
private readonly AddonLifecycle lifecycleService;
|
||||
|
||||
// Each addon gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||
private readonly AddonSetupArgs setupArgs = new();
|
||||
private readonly AddonArgs finalizeArgs = new();
|
||||
private readonly AddonArgs drawArgs = new();
|
||||
private readonly AddonArgs updateArgs = new();
|
||||
private readonly AddonRefreshArgs refreshArgs = new();
|
||||
private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new();
|
||||
private readonly AddonReceiveEventArgs receiveEventArgs = new();
|
||||
private readonly AddonArgs openArgs = new();
|
||||
private readonly AddonCloseArgs closeArgs = new();
|
||||
private readonly AddonShowArgs showArgs = new();
|
||||
private readonly AddonHideArgs hideArgs = new();
|
||||
private readonly AddonArgs onMoveArgs = new();
|
||||
private readonly AddonArgs onMouseOverArgs = new();
|
||||
private readonly AddonArgs onMouseOutArgs = new();
|
||||
private readonly AddonArgs focusArgs = new();
|
||||
private readonly AddonFocusChangedArgs focusChangedArgs = new();
|
||||
|
||||
private readonly AtkUnitBase* atkUnitBase;
|
||||
|
||||
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||
private readonly AtkUnitBase.Delegates.Dtor destructorFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnSetup onSetupFunction;
|
||||
private readonly AtkUnitBase.Delegates.Finalizer finalizerFunction;
|
||||
private readonly AtkUnitBase.Delegates.Draw drawFunction;
|
||||
private readonly AtkUnitBase.Delegates.Update updateFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnRefresh onRefreshFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnRequestedUpdate onRequestedUpdateFunction;
|
||||
private readonly AtkUnitBase.Delegates.ReceiveEvent onReceiveEventFunction;
|
||||
private readonly AtkUnitBase.Delegates.Open openFunction;
|
||||
private readonly AtkUnitBase.Delegates.Close closeFunction;
|
||||
private readonly AtkUnitBase.Delegates.Show showFunction;
|
||||
private readonly AtkUnitBase.Delegates.Hide hideFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMove onMoveFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
||||
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnFocusChange onFocusChangeFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addon">AtkUnitBase* for the addon to replace the table of.</param>
|
||||
/// <param name="lifecycleService">Reference to AddonLifecycle service to callback and invoke listeners.</param>
|
||||
internal AddonVirtualTable(AtkUnitBase* addon, AddonLifecycle lifecycleService)
|
||||
{
|
||||
this.atkUnitBase = addon;
|
||||
this.lifecycleService = lifecycleService;
|
||||
|
||||
// Save original virtual table
|
||||
this.OriginalVirtualTable = addon->VirtualTable;
|
||||
|
||||
// Create copy of original table
|
||||
// Note this will copy any derived/overriden functions that this specific addon has.
|
||||
// Note: currently there are 73 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||
this.ModifiedVirtualTable = (AtkUnitBase.AtkUnitBaseVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||
NativeMemory.Copy(addon->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
|
||||
// Overwrite the addons existing virtual table with our own
|
||||
addon->VirtualTable = this.ModifiedVirtualTable;
|
||||
|
||||
// Pin each of our listener functions
|
||||
this.destructorFunction = this.OnAddonDestructor;
|
||||
this.onSetupFunction = this.OnAddonSetup;
|
||||
this.finalizerFunction = this.OnAddonFinalize;
|
||||
this.drawFunction = this.OnAddonDraw;
|
||||
this.updateFunction = this.OnAddonUpdate;
|
||||
this.onRefreshFunction = this.OnAddonRefresh;
|
||||
this.onRequestedUpdateFunction = this.OnRequestedUpdate;
|
||||
this.onReceiveEventFunction = this.OnAddonReceiveEvent;
|
||||
this.openFunction = this.OnAddonOpen;
|
||||
this.closeFunction = this.OnAddonClose;
|
||||
this.showFunction = this.OnAddonShow;
|
||||
this.hideFunction = this.OnAddonHide;
|
||||
this.onMoveFunction = this.OnAddonMove;
|
||||
this.onMouseOverFunction = this.OnAddonMouseOver;
|
||||
this.onMouseOutFunction = this.OnAddonMouseOut;
|
||||
this.focusFunction = this.OnAddonFocus;
|
||||
this.onFocusChangeFunction = this.OnAddonFocusChange;
|
||||
|
||||
// Overwrite specific virtual table entries
|
||||
this.ModifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
||||
this.ModifiedVirtualTable->OnSetup = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, void>)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
|
||||
this.ModifiedVirtualTable->Finalizer = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
|
||||
this.ModifiedVirtualTable->Draw = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
|
||||
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AtkUnitBase*, float, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||
this.ModifiedVirtualTable->OnRefresh = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, bool>)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
|
||||
this.ModifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged<AtkUnitBase*, NumberArrayData**, StringArrayData**, void>)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
|
||||
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AtkUnitBase*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
|
||||
this.ModifiedVirtualTable->Open = (delegate* unmanaged<AtkUnitBase*, uint, bool>)Marshal.GetFunctionPointerForDelegate(this.openFunction);
|
||||
this.ModifiedVirtualTable->Close = (delegate* unmanaged<AtkUnitBase*, bool, bool>)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
|
||||
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AtkUnitBase*, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AtkUnitBase*, bool, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||
this.ModifiedVirtualTable->OnMove = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
|
||||
this.ModifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
||||
this.ModifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
||||
this.ModifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
||||
this.ModifiedVirtualTable->OnFocusChange = (delegate* unmanaged<AtkUnitBase*, bool, void>)Marshal.GetFunctionPointerForDelegate(this.onFocusChangeFunction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original virtual table address for this addon.
|
||||
/// </summary>
|
||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* OriginalVirtualTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modified virtual address for this addon.
|
||||
/// </summary>
|
||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* ModifiedVirtualTable { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Ensure restoration is done atomically.
|
||||
Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.OriginalVirtualTable);
|
||||
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
}
|
||||
|
||||
private AtkEventListener* OnAddonDestructor(AtkUnitBase* thisPtr, byte freeFlags)
|
||||
{
|
||||
AtkEventListener* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->Dtor(thisPtr, freeFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Dtor. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
if ((freeFlags & 1) == 1)
|
||||
{
|
||||
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
AddonLifecycle.AllocatedTables.Remove(this);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDestructor.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.setupArgs.Addon = addon;
|
||||
this.setupArgs.AtkValueCount = valueCount;
|
||||
this.setupArgs.AtkValues = (nint)values;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreSetup, this.setupArgs);
|
||||
|
||||
valueCount = this.setupArgs.AtkValueCount;
|
||||
values = (AtkValue*)this.setupArgs.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnSetup(addon, valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostSetup, this.setupArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonSetup.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.finalizeArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFinalize, this.finalizeArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Finalizer(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Finalizer. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFinalize.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonDraw(AtkUnitBase* addon)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.drawArgs.Addon = addon;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreDraw, this.drawArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Draw(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Draw. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostDraw, this.drawArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDraw.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.updateArgs.Addon = addon;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs);
|
||||
|
||||
// Note: Do not pass or allow manipulation of delta.
|
||||
// It's realistically not something that should be needed.
|
||||
// And even if someone does, they are encouraged to hook Update themselves.
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Update(addon, delta);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostUpdate, this.updateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnAddonRefresh(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.refreshArgs.Addon = addon;
|
||||
this.refreshArgs.AtkValueCount = valueCount;
|
||||
this.refreshArgs.AtkValues = (nint)values;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRefresh, this.refreshArgs);
|
||||
|
||||
valueCount = this.refreshArgs.AtkValueCount;
|
||||
values = (AtkValue*)this.refreshArgs.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->OnRefresh(addon, valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnRefresh. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRefresh, this.refreshArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonRefresh.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.requestedUpdateArgs.Addon = addon;
|
||||
this.requestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
|
||||
this.requestedUpdateArgs.StringArrayData = (nint)stringArrayData;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.requestedUpdateArgs);
|
||||
|
||||
numberArrayData = (NumberArrayData**)this.requestedUpdateArgs.NumberArrayData;
|
||||
stringArrayData = (StringArrayData**)this.requestedUpdateArgs.StringArrayData;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.requestedUpdateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.receiveEventArgs.Addon = (nint)addon;
|
||||
this.receiveEventArgs.AtkEventType = (byte)eventType;
|
||||
this.receiveEventArgs.EventParam = eventParam;
|
||||
this.receiveEventArgs.AtkEvent = (IntPtr)atkEvent;
|
||||
this.receiveEventArgs.AtkEventData = (nint)atkEventData;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||
|
||||
eventType = (AtkEventType)this.receiveEventArgs.AtkEventType;
|
||||
eventParam = this.receiveEventArgs.EventParam;
|
||||
atkEvent = (AtkEvent*)this.receiveEventArgs.AtkEvent;
|
||||
atkEventData = (AtkEventData*)this.receiveEventArgs.AtkEventData;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->ReceiveEvent(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonReceiveEvent.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnAddonOpen(AtkUnitBase* thisPtr, uint depthLayer)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.openArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreOpen, this.openArgs);
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->Open(thisPtr, depthLayer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Open. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostOpen, this.openArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonOpen.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool OnAddonClose(AtkUnitBase* thisPtr, bool fireCallback)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.closeArgs.Addon = thisPtr;
|
||||
this.closeArgs.FireCallback = fireCallback;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs);
|
||||
|
||||
fireCallback = this.closeArgs.FireCallback;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->Close(thisPtr, fireCallback);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Close. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostClose, this.closeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonClose.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonShow(AtkUnitBase* thisPtr, bool silenceOpenSoundEffect, uint unsetShowHideFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.showArgs.Addon = thisPtr;
|
||||
this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect;
|
||||
this.showArgs.UnsetShowHideFlags = unsetShowHideFlags;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs);
|
||||
|
||||
silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect;
|
||||
unsetShowHideFlags = this.showArgs.UnsetShowHideFlags;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostShow, this.showArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonShow.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonHide(AtkUnitBase* thisPtr, bool unkBool, bool callHideCallback, uint setShowHideFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.hideArgs.Addon = thisPtr;
|
||||
this.hideArgs.UnknownBool = unkBool;
|
||||
this.hideArgs.CallHideCallback = callHideCallback;
|
||||
this.hideArgs.SetShowHideFlags = setShowHideFlags;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs);
|
||||
|
||||
unkBool = this.hideArgs.UnknownBool;
|
||||
callHideCallback = this.hideArgs.CallHideCallback;
|
||||
setShowHideFlags = this.hideArgs.SetShowHideFlags;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonHide.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMove(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMoveArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnMove(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMove. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMove.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMouseOver(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMouseOverArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnMouseOver(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMouseOver. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOver.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMouseOut(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMouseOutArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnMouseOut(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMouseOut. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOut.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonFocus(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.focusArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Focus(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Focus. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocus.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonFocusChange(AtkUnitBase* thisPtr, bool isFocused)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.focusChangedArgs.Addon = thisPtr;
|
||||
this.focusChangedArgs.ShouldFocus = isFocused;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocusChanged, this.focusChangedArgs);
|
||||
|
||||
isFocused = this.focusChangedArgs.ShouldFocus;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnFocusChange(thisPtr, isFocused);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnFocusChanged. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocusChanged, this.focusChangedArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocusChange.");
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (loggingEnabled)
|
||||
{
|
||||
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||
if (caller is "OnAddonUpdate" or "OnAddonDraw" or "OnAddonReceiveEvent" or "OnRequestedUpdate")
|
||||
return;
|
||||
|
||||
Log.Debug($"[{caller}]: {this.atkUnitBase->NameString}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
using Dalamud.Game.NativeWrapper;
|
||||
|
||||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for AgentLifecycle AgentArgTypes.
|
||||
/// </summary>
|
||||
public unsafe class AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the Agents AgentInterface*.
|
||||
/// </summary>
|
||||
public AgentInterfacePtr Agent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the agent id.
|
||||
/// </summary>
|
||||
public AgentId AgentId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public virtual AgentArgsType Type => AgentArgsType.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the typed pointer to the Agents AgentInterface*.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">AgentInterface.</typeparam>
|
||||
/// <returns>Typed pointer to contained Agents AgentInterface.</returns>
|
||||
public T* GetAgentPointer<T>() where T : unmanaged
|
||||
=> (T*)this.Agent.Address;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue