mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-21 15:27:43 +01:00
Compare commits
No commits in common. "master" and "13.0.0.12" have entirely different histories.
498 changed files with 7561 additions and 10664 deletions
161
.github/generate_changelog.py
vendored
161
.github/generate_changelog.py
vendored
|
|
@ -8,8 +8,7 @@ import re
|
|||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
from typing import List, Tuple, Optional, Dict, Any
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
|
||||
def run_git_command(args: List[str]) -> str:
|
||||
|
|
@ -56,132 +55,46 @@ def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def get_repo_info() -> Tuple[str, str]:
|
||||
"""Get repository owner and name from git remote."""
|
||||
try:
|
||||
remote_url = run_git_command(["config", "--get", "remote.origin.url"])
|
||||
|
||||
# Handle both HTTPS and SSH URLs
|
||||
# SSH: git@github.com:owner/repo.git
|
||||
# HTTPS: https://github.com/owner/repo.git
|
||||
match = re.search(r'github\.com[:/](.+?)/(.+?)(?:\.git)?$', remote_url)
|
||||
if match:
|
||||
owner = match.group(1)
|
||||
repo = match.group(2)
|
||||
return owner, repo
|
||||
else:
|
||||
print("Error: Could not parse GitHub repository from remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except:
|
||||
print("Error: Could not get git remote URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_commits_between_tags(tag1: str, tag2: str) -> List[str]:
|
||||
"""Get commit SHAs between two tags."""
|
||||
def get_commits_between_tags(tag1: str, tag2: str) -> List[Tuple[str, str]]:
|
||||
"""Get commits between two tags. Returns list of (message, author) tuples."""
|
||||
log_output = run_git_command([
|
||||
"log",
|
||||
f"{tag2}..{tag1}",
|
||||
"--format=%H"
|
||||
"--format=%s|%an|%h"
|
||||
])
|
||||
|
||||
commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()]
|
||||
commits = []
|
||||
for line in log_output.split("\n"):
|
||||
if "|" in line:
|
||||
message, author, sha = line.split("|", 2)
|
||||
commits.append((message.strip(), author.strip(), sha.strip()))
|
||||
|
||||
return commits
|
||||
|
||||
|
||||
def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get PR information for a commit using GitHub API."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}/pulls"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
prs = response.json()
|
||||
|
||||
if prs and len(prs) > 0:
|
||||
# Return the first PR (most relevant one)
|
||||
pr = prs[0]
|
||||
return {
|
||||
"number": pr["number"],
|
||||
"title": pr["title"],
|
||||
"author": pr["user"]["login"],
|
||||
"url": pr["html_url"]
|
||||
}
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
# Commit might not be associated with a PR
|
||||
return None
|
||||
elif e.response.status_code == 403:
|
||||
print("Warning: GitHub API rate limit exceeded. Consider providing a token.", file=sys.stderr)
|
||||
return None
|
||||
else:
|
||||
print(f"Warning: Failed to fetch PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Warning: Error fetching PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_prs_between_tags(tag1: str, tag2: str, owner: str, repo: str, token: str) -> List[Dict[str, Any]]:
|
||||
"""Get PRs between two tags using GitHub API."""
|
||||
commits = get_commits_between_tags(tag1, tag2)
|
||||
print(f"Found {len(commits)} commits, fetching PR information...")
|
||||
|
||||
prs = []
|
||||
seen_pr_numbers = set()
|
||||
|
||||
for i, commit_sha in enumerate(commits, 1):
|
||||
if i % 10 == 0:
|
||||
print(f"Progress: {i}/{len(commits)} commits processed...")
|
||||
|
||||
pr_info = get_pr_for_commit(commit_sha, owner, repo, token)
|
||||
if pr_info and pr_info["number"] not in seen_pr_numbers:
|
||||
seen_pr_numbers.add(pr_info["number"])
|
||||
prs.append(pr_info)
|
||||
|
||||
return prs
|
||||
|
||||
|
||||
def filter_prs(prs: List[Dict[str, Any]], ignore_patterns: List[str]) -> List[Dict[str, Any]]:
|
||||
"""Filter out PRs matching any of the ignore patterns."""
|
||||
def filter_commits(commits: List[Tuple[str, str]], ignore_patterns: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Filter out commits matching any of the ignore patterns."""
|
||||
compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns]
|
||||
|
||||
filtered = []
|
||||
for pr in prs:
|
||||
if not any(pattern.search(pr["title"]) for pattern in compiled_patterns):
|
||||
filtered.append(pr)
|
||||
for message, author, sha in commits:
|
||||
if not any(pattern.search(message) for pattern in compiled_patterns):
|
||||
filtered.append((message, author, sha))
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]],
|
||||
cs_commit_new: Optional[str], cs_commit_old: Optional[str],
|
||||
owner: str, repo: str) -> str:
|
||||
def generate_changelog(version: str, prev_version: str, commits: List[Tuple[str, str]],
|
||||
cs_commit_new: Optional[str], cs_commit_old: Optional[str]) -> str:
|
||||
"""Generate markdown changelog."""
|
||||
# Calculate statistics
|
||||
pr_count = len(prs)
|
||||
unique_authors = len(set(pr["author"] for pr in prs))
|
||||
commit_count = len(commits)
|
||||
unique_authors = len(set(author for _, author, _ in commits))
|
||||
|
||||
changelog = f"# Dalamud Release v{version}\n\n"
|
||||
changelog += f"We just released Dalamud v{version}, which should be available to users within a few minutes. "
|
||||
changelog += f"This release includes **{pr_count} PR{'s' if pr_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n"
|
||||
changelog += f"[Click here](<https://github.com/{owner}/{repo}/compare/{prev_version}...{version}>) to see all Dalamud changes.\n\n"
|
||||
changelog += f"This release includes **{commit_count} commit{'s' if commit_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n"
|
||||
changelog += f"[Click here](<https://github.com/goatcorp/Dalamud/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"
|
||||
|
|
@ -191,8 +104,8 @@ def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]
|
|||
|
||||
changelog += "## Dalamud Changes\n\n"
|
||||
|
||||
for pr in prs:
|
||||
changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n"
|
||||
for message, author, sha in commits:
|
||||
changelog += f"* {message} (by **{author}** as [`{sha}`](<https://github.com/goatcorp/Dalamud/commit/{sha}>))\n"
|
||||
|
||||
return changelog
|
||||
|
||||
|
|
@ -245,16 +158,11 @@ def main():
|
|||
required=True,
|
||||
help="Discord webhook URL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--github-token",
|
||||
default=os.environ.get("GITHUB_TOKEN"),
|
||||
help="GitHub API token (or set GITHUB_TOKEN env var). Increases rate limit."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Regex patterns to ignore PRs (can be specified multiple times)"
|
||||
help="Regex patterns to ignore commits (can be specified multiple times)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--submodule-path",
|
||||
|
|
@ -264,10 +172,6 @@ def main():
|
|||
|
||||
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}")
|
||||
|
|
@ -281,18 +185,17 @@ def main():
|
|||
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")
|
||||
# Get commits between tags
|
||||
commits = get_commits_between_tags(latest_tag, previous_tag)
|
||||
print(f"Found {len(commits)} commits")
|
||||
|
||||
# Filter PRs
|
||||
filtered_prs = filter_prs(prs, args.ignore)
|
||||
print(f"After filtering: {len(filtered_prs)} PRs")
|
||||
# Filter commits
|
||||
filtered_commits = filter_commits(commits, args.ignore)
|
||||
print(f"After filtering: {len(filtered_commits)} commits")
|
||||
|
||||
# Generate changelog
|
||||
changelog = generate_changelog(latest_tag, previous_tag, filtered_prs,
|
||||
cs_commit_new, cs_commit_old, owner, repo)
|
||||
changelog = generate_changelog(latest_tag, previous_tag, filtered_commits,
|
||||
cs_commit_new, cs_commit_old)
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("Generated Changelog:")
|
||||
|
|
|
|||
12
.github/workflows/generate-changelog.yml
vendored
12
.github/workflows/generate-changelog.yml
vendored
|
|
@ -6,8 +6,6 @@ on:
|
|||
tags:
|
||||
- '*'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
generate-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -30,14 +28,14 @@ jobs:
|
|||
pip install requests
|
||||
|
||||
- name: Generate and post changelog
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
run: |
|
||||
python .github/generate_changelog.py \
|
||||
--webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \
|
||||
--ignore "Update ClientStructs" \
|
||||
--ignore "^build:"
|
||||
--ignore "^Merge" \
|
||||
--ignore "^build:" \
|
||||
--ignore "^docs:"
|
||||
env:
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
|
||||
- name: Upload changelog as artifact
|
||||
if: always()
|
||||
|
|
|
|||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
|
|
@ -3,7 +3,7 @@ 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 +23,7 @@ jobs:
|
|||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '10.0.100'
|
||||
dotnet-version: '9.0.200'
|
||||
- name: Define VERSION
|
||||
run: |
|
||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||
|
|
|
|||
6
.github/workflows/rollup.yml
vendored
6
.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:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,30 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Build Schema",
|
||||
"$ref": "#/definitions/build",
|
||||
"definitions": {
|
||||
"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"
|
||||
},
|
||||
"Help": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
|
|
@ -21,48 +43,9 @@
|
|||
"VSCode"
|
||||
]
|
||||
},
|
||||
"ExecutableTarget": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"enum": [
|
||||
"Verbose",
|
||||
"Normal",
|
||||
"Minimal",
|
||||
"Quiet"
|
||||
]
|
||||
},
|
||||
"NukeBuild": {
|
||||
"properties": {
|
||||
"Continue": {
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to continue a previously failed build attempt"
|
||||
},
|
||||
"Help": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"$ref": "#/definitions/Host"
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
},
|
||||
"NoLogo": {
|
||||
"type": "boolean",
|
||||
|
|
@ -91,46 +74,65 @@
|
|||
"type": "array",
|
||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
},
|
||||
"Target": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ExecutableTarget"
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CI",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"CompileCImGui",
|
||||
"CompileCImGuizmo",
|
||||
"CompileCImPlot",
|
||||
"CompileDalamud",
|
||||
"CompileDalamudBoot",
|
||||
"CompileDalamudCrashHandler",
|
||||
"CompileImGuiNatives",
|
||||
"CompileInjector",
|
||||
"CompileInjectorBoot",
|
||||
"Restore",
|
||||
"SetCILogging",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||
"$ref": "#/definitions/Verbosity"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
"Minimal",
|
||||
"Normal",
|
||||
"Quiet",
|
||||
"Verbose"
|
||||
]
|
||||
},
|
||||
"IsDocsBuild": {
|
||||
"type": "boolean",
|
||||
"description": "Whether we are building for documentation - emits generated files"
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/NukeBuild"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -108,11 +108,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ struct DalamudStartInfo {
|
|||
std::string ConfigurationPath;
|
||||
std::string LogPath;
|
||||
std::string LogName;
|
||||
std::string TempDirectory;
|
||||
std::string PluginDirectory;
|
||||
std::string AssetDirectory;
|
||||
ClientLanguage Language = ClientLanguage::English;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
|
||||
|
||||
struct exception_info
|
||||
{
|
||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||
|
|
|
|||
|
|
@ -331,51 +331,6 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
logging::I("VEH was disabled manually");
|
||||
}
|
||||
|
||||
// ============================== CLR Reporting =================================== //
|
||||
|
||||
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
|
||||
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
|
||||
// Ideally all of this will go away once they get to it.
|
||||
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_report_event_hook;
|
||||
s_report_event_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>(
|
||||
"advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
|
||||
s_report_event_hook->set_detour([hook = s_report_event_hook.get()](
|
||||
HANDLE hEventLog,
|
||||
WORD wType,
|
||||
WORD wCategory,
|
||||
DWORD dwEventID,
|
||||
PSID lpUserSid,
|
||||
WORD wNumStrings,
|
||||
DWORD dwDataSize,
|
||||
LPCWSTR* lpStrings,
|
||||
LPVOID lpRawData)-> BOOL {
|
||||
|
||||
// Check for CLR Error Event IDs
|
||||
// https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370
|
||||
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
|
||||
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
|
||||
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
|
||||
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
|
||||
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
|
||||
{
|
||||
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
}
|
||||
|
||||
if (wNumStrings == 0 || lpStrings == nullptr) {
|
||||
logging::W("ReportEventW called with no strings.");
|
||||
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
}
|
||||
|
||||
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
|
||||
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
|
||||
|
||||
const std::wstring error_details(lpStrings[0]);
|
||||
veh::raise_external_event(error_details);
|
||||
|
||||
return original_ret;
|
||||
});
|
||||
logging::I("ReportEventW hook installed.");
|
||||
|
||||
// ============================== Dalamud ==================================== //
|
||||
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||
|
|
|
|||
|
|
@ -31,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;
|
||||
|
|
@ -124,7 +122,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
|
||||
|
|
@ -193,11 +190,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 +251,6 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
|||
|
||||
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||
{
|
||||
// special case for CLR exceptions, always trigger crash handler
|
||||
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
|
||||
{
|
||||
return exception_handler(ex);
|
||||
}
|
||||
|
||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||
{
|
||||
// pass
|
||||
|
|
@ -447,16 +434,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,12 +34,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>
|
||||
|
|
|
|||
111
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
111
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?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>stdcpp23</LanguageStandard>
|
||||
<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>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<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>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<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"
|
||||
|
|
@ -13,13 +13,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">
|
||||
|
|
|
|||
|
|
@ -25,20 +25,34 @@ 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
|
||||
|
|
@ -291,7 +305,6 @@ 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;
|
||||
|
|
@ -322,10 +335,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..]);
|
||||
|
|
@ -438,7 +447,6 @@ namespace Dalamud.Injector
|
|||
startInfo.ConfigurationPath = configurationPath;
|
||||
startInfo.PluginDirectory = pluginDirectory;
|
||||
startInfo.AssetDirectory = assetDirectory;
|
||||
startInfo.TempDirectory = tempDirectory;
|
||||
startInfo.Language = clientLanguage;
|
||||
startInfo.Platform = platform;
|
||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||
40
Dalamud.sln
40
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
|
||||
|
|
@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
tools\BannedSymbols.txt = tools\BannedSymbols.txt
|
||||
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
|
||||
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
|
||||
tools\dalamud.ruleset = tools\dalamud.ruleset
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
|
|
@ -25,6 +27,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boo
|
|||
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}"
|
||||
|
|
@ -45,6 +49,8 @@ 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}") = "Injector", "Injector", "{19775C83-7117-4A5F-AA00-18889F46A490}"
|
||||
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}"
|
||||
|
|
@ -75,17 +81,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "l
|
|||
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
|
||||
|
|
@ -108,6 +103,10 @@ 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
|
||||
|
|
@ -184,23 +183,13 @@ Global
|
|||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
||||
{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}
|
||||
|
|
@ -220,9 +209,6 @@ Global
|
|||
{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}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.ReShadeHandling;
|
||||
using Dalamud.Interface.Style;
|
||||
|
|
@ -19,12 +20,9 @@ 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;
|
||||
|
|
@ -93,7 +91,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.
|
||||
|
|
@ -113,7 +111,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
/// <summary>
|
||||
/// Gets or sets a list of custom repos.
|
||||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = [];
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a disclaimer regarding third-party repos has been dismissed.
|
||||
|
|
@ -123,12 +121,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
/// <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,14 +134,14 @@ 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.
|
||||
|
|
@ -225,7 +223,7 @@ 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.
|
||||
|
|
@ -497,16 +495,6 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
#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>
|
||||
|
|
@ -601,7 +589,7 @@ 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;
|
||||
var success = false;
|
||||
unsafe
|
||||
{
|
||||
success = Windows.Win32.PInvoke.SystemParametersInfo(
|
||||
|
|
|
|||
|
|
@ -31,5 +31,5 @@ internal sealed class DevPluginSettings
|
|||
/// <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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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.")]
|
||||
[Api13ToDo("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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -17,9 +17,9 @@ 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 static readonly ModuleLog Log = new("CON");
|
||||
|
||||
private Dictionary<string, IConsoleEntry> entries = [];
|
||||
private Dictionary<string, IConsoleEntry> entries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
|
||||
|
|
@ -99,7 +99,10 @@ 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.");
|
||||
|
||||
|
|
@ -343,7 +346,7 @@ internal partial class ConsoleManager : IServiceType
|
|||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ internal 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.
|
||||
|
|
|
|||
|
|
@ -9,14 +9,11 @@ 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 Serilog;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Security;
|
||||
|
||||
|
|
@ -76,11 +73,6 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
scanner,
|
||||
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
||||
|
||||
using (Timings.Start("HookVerifier Init"))
|
||||
{
|
||||
HookVerifier.Initialize(scanner);
|
||||
}
|
||||
|
||||
// Set up FFXIVClientStructs
|
||||
this.SetupClientStructsResolver(cacheDir);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<PropertyGroup Label="Feature">
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<DalamudVersion>14.0.2.2</DalamudVersion>
|
||||
<DalamudVersion>13.0.0.12</DalamudVersion>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||
|
|
@ -65,6 +65,7 @@
|
|||
<PackageReference Include="CheapLoc" />
|
||||
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="all" />
|
||||
<PackageReference Include="goatcorp.Reloaded.Hooks" />
|
||||
<PackageReference Include="goatcorp.Reloaded.Assembler" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="Lumina" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||
|
|
@ -72,6 +73,8 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MinSharp" />
|
||||
<PackageReference Include="SharpDX.Direct3D11" />
|
||||
<PackageReference Include="SharpDX.Mathematics" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
|
|
@ -82,20 +85,14 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Collections.Immutable" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Reactive" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" />
|
||||
<PackageReference Include="System.Resources.Extensions" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" />
|
||||
</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>
|
||||
|
|
@ -125,8 +122,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>
|
||||
|
|
@ -231,4 +226,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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -84,13 +82,8 @@ 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);
|
||||
|
|
|
|||
|
|
@ -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,8 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -14,13 +16,10 @@ using Dalamud.Plugin.Internal;
|
|||
using Dalamud.Storage;
|
||||
using Dalamud.Support;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
|
|
@ -193,8 +192,8 @@ public sealed class EntryPoint
|
|||
|
||||
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
|
||||
Versioning.GetScmVersion(),
|
||||
Versioning.GetGitHashClientStructs(),
|
||||
Util.GetScmVersion(),
|
||||
Util.GetGitHashClientStructs(),
|
||||
FFXIVClientStructs.ThisAssembly.Git.Commits);
|
||||
|
||||
dalamud.WaitForUnload();
|
||||
|
|
@ -264,7 +263,7 @@ public sealed class EntryPoint
|
|||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||
var searchPath = $".;{symbolPath}";
|
||||
|
||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
||||
|
||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
||||
|
|
@ -293,6 +292,7 @@ public sealed class EntryPoint
|
|||
}
|
||||
|
||||
var pluginInfo = string.Empty;
|
||||
var supportText = ", please visit us on Discord for more help";
|
||||
try
|
||||
{
|
||||
var pm = Service<PluginManager>.GetNullable();
|
||||
|
|
@ -300,6 +300,9 @@ public sealed class EntryPoint
|
|||
if (plugin != null)
|
||||
{
|
||||
pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
|
||||
|
||||
if (plugin.IsThirdParty)
|
||||
supportText = string.Empty;
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
@ -307,18 +310,31 @@ public sealed class EntryPoint
|
|||
// ignored
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
const MESSAGEBOX_STYLE flags = MESSAGEBOX_STYLE.MB_YESNO | MESSAGEBOX_STYLE.MB_ICONERROR | MESSAGEBOX_STYLE.MB_SYSTEMMODAL;
|
||||
var result = Windows.Win32.PInvoke.MessageBox(
|
||||
new HWND(Process.GetCurrentProcess().MainWindowHandle),
|
||||
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\n{ex.GetType().Name}\n{info}\n\n{pluginInfo}More information has been recorded separately{supportText}.\n\nDo you want to disable all plugins the next time you start the game?",
|
||||
"Dalamud",
|
||||
flags);
|
||||
|
||||
ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}");
|
||||
if (result == MESSAGEBOX_RESULT.IDYES)
|
||||
{
|
||||
Log.Information("User chose to disable plugins on next launch...");
|
||||
var config = Service<DalamudConfiguration>.Get();
|
||||
config.PluginSafeMode = true;
|
||||
config.ForceSave();
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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,4 +1,5 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -24,28 +25,32 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
/// </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()
|
||||
{
|
||||
|
|
@ -110,7 +117,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
/// Force the game cursor to be the specified cursor.
|
||||
/// </summary>
|
||||
/// <param name="cursor">Which cursor to use.</param>
|
||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor;
|
||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
|
||||
/// <summary>
|
||||
/// Un-forces the game cursor.
|
||||
|
|
@ -161,7 +168,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateCursorDetour(AtkUnitManager* thisPtr)
|
||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -169,14 +176,13 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
|
||||
if (this.cursorOverride is not null && atkStage is not null)
|
||||
{
|
||||
ref var atkCursor = ref atkStage->AtkCursor;
|
||||
|
||||
if (atkCursor.Type != this.cursorOverride)
|
||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||
if (cursor != this.cursorOverride)
|
||||
{
|
||||
atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
return nint.Zero;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -184,7 +190,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
|||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||
}
|
||||
|
||||
this.onUpdateCursor!.Original(thisPtr);
|
||||
return this.onUpdateCursor!.Original(module);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -61,11 +61,6 @@ public enum AddonEventType : byte
|
|||
/// </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>
|
||||
|
|
@ -112,12 +107,7 @@ public enum AddonEventType : byte
|
|||
SliderReleased = 30,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Button Press.
|
||||
/// </summary>
|
||||
ListButtonPress = 31,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentList Roll Over.
|
||||
/// AtkComponentList RollOver.
|
||||
/// </summary>
|
||||
ListItemRollOver = 33,
|
||||
|
||||
|
|
@ -136,31 +126,11 @@ public enum AddonEventType : byte
|
|||
/// </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.
|
||||
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
||||
|
|
@ -172,22 +142,12 @@ public enum AddonEventType : byte
|
|||
/// </summary>
|
||||
DragDropEnd = 51,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Insert Attempt.
|
||||
/// </summary>
|
||||
DragDropInsertAttempt = 52,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Insert.
|
||||
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
||||
/// </summary>
|
||||
DragDropInsert = 53,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Can Accept Check.
|
||||
/// </summary>
|
||||
DragDropCanAcceptCheck = 54,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Roll Over.
|
||||
/// </summary>
|
||||
|
|
@ -205,18 +165,23 @@ public enum AddonEventType : byte
|
|||
DragDropDiscard = 57,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Click.
|
||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropClick = 58,
|
||||
[Obsolete("Use DragDropDiscard", true)]
|
||||
DragDropUnk54 = 54,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentDragDrop Cancel.
|
||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||
/// </summary>
|
||||
[Obsolete("Renamed to DragDropClick")]
|
||||
DragDropCancel = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
[Obsolete("Use DragDropCancel", true)]
|
||||
DragDropUnk55 = 55,
|
||||
|
||||
/// <summary>
|
||||
/// AtkComponentIconText Roll Over.
|
||||
/// </summary>
|
||||
|
|
@ -252,11 +217,6 @@ public enum AddonEventType : byte
|
|||
/// </summary>
|
||||
TimerEnd = 65,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimer Start.
|
||||
/// </summary>
|
||||
TimerStart = 66,
|
||||
|
||||
/// <summary>
|
||||
/// AtkSimpleTween Progress.
|
||||
/// </summary>
|
||||
|
|
@ -287,11 +247,6 @@ public enum AddonEventType : byte
|
|||
/// </summary>
|
||||
WindowChangeScale = 72,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTimeline Active Label Changed.
|
||||
/// </summary>
|
||||
TimelineActiveLabelChanged = 75,
|
||||
|
||||
/// <summary>
|
||||
/// AtkTextNode Link Mouse Click.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Dalamud.Game.Addon.Events.EventDataTypes;
|
|||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
|
|
@ -15,7 +16,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.
|
||||
|
|
@ -27,7 +28,7 @@ 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.
|
||||
|
|
|
|||
|
|
@ -5,24 +5,19 @@ 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;
|
||||
|
||||
/// <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.
|
||||
|
|
@ -30,17 +25,55 @@ public class AddonArgs
|
|||
public AtkUnitBasePtr Addon
|
||||
{
|
||||
get;
|
||||
internal set
|
||||
{
|
||||
field = value;
|
||||
|
||||
if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
|
||||
this.AddonName = value.Name;
|
||||
}
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public virtual AddonArgsType Type => AddonArgsType.Generic;
|
||||
public abstract AddonArgsType Type { get; }
|
||||
|
||||
/// <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(string name)
|
||||
{
|
||||
if (this.Addon.IsNull)
|
||||
return false;
|
||||
|
||||
if (name.Length is 0 or > 32)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(this.Addon.Name))
|
||||
return false;
|
||||
|
||||
return name == this.Addon.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this AddonArgs values.
|
||||
/// </summary>
|
||||
internal virtual void Clear()
|
||||
{
|
||||
this.addonName = null;
|
||||
this.Addon = 0;
|
||||
}
|
||||
|
||||
/// <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.IsNull) return InvalidAddon;
|
||||
|
||||
var name = this.Addon.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return InvalidAddon;
|
||||
|
||||
return this.addonName ??= name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,23 @@ 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();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.AtkEventType = default;
|
||||
this.EventParam = default;
|
||||
this.AtkEvent = default;
|
||||
this.Data = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,19 @@ 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();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.AtkValueCount = default;
|
||||
this.AtkValues = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ 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,18 @@ 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();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.NumberArrayData = default;
|
||||
this.StringArrayData = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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,19 @@ 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();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.AtkValueCount = default;
|
||||
this.AtkValues = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,45 @@
|
|||
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();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.TimeDeltaInternal = default;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,26 @@
|
|||
/// </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>
|
||||
|
|
@ -29,24 +39,4 @@ public enum AddonArgsType
|
|||
/// 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +62,7 @@ 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>
|
||||
|
|
@ -115,102 +118,4 @@ public enum AddonEvent
|
|||
/// 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)
|
||||
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
||||
{
|
||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||
if (matchedTable == null)
|
||||
{
|
||||
return null;
|
||||
receiveEventListener.Dispose();
|
||||
}
|
||||
|
||||
return matchedTable.OriginalVirtualTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -78,15 +98,21 @@ 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.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 })
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
||||
{
|
||||
receiveEventListener.TryEnable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the listener from events.
|
||||
|
|
@ -94,17 +120,28 @@ 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.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 })
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
|
||||
// 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>
|
||||
/// Invoke listeners for the specified event type.
|
||||
|
|
@ -114,17 +151,19 @@ 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.IsRequestedToClear) continue;
|
||||
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
|
||||
{
|
||||
|
|
@ -132,86 +171,206 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
|
||||
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))
|
||||
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
||||
{
|
||||
foreach (var listener in addonListener)
|
||||
// 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 (listener.IsRequestedToClear) continue;
|
||||
|
||||
try
|
||||
// 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)
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
if (!existingListener.AddonNames.Contains(addonName))
|
||||
{
|
||||
existingListener.AddonNames.Add(addonName);
|
||||
}
|
||||
catch (Exception e)
|
||||
}
|
||||
|
||||
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
|
||||
else
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isInvokingListeners = false;
|
||||
}
|
||||
private void UnregisterReceiveEventHook(string addonName)
|
||||
{
|
||||
// Remove this addons ReceiveEvent Registration
|
||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
|
||||
{
|
||||
eventListener.AddonNames.Remove(addonName);
|
||||
|
||||
private void RegisterListenerMethod(AddonLifecycleEventListener listener)
|
||||
// If there are no more listeners let's remove and dispose.
|
||||
if (eventListener.AddonNames.Count is 0)
|
||||
{
|
||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||
{
|
||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||
{
|
||||
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||
{
|
||||
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.Clear();
|
||||
arg.Addon = (nint)addon;
|
||||
arg.AtkValueCount = valueCount;
|
||||
arg.AtkValues = (nint)values;
|
||||
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
|
||||
valueCount = arg.AtkValueCount;
|
||||
values = (AtkValue*)arg.AtkValues;
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void LogInitialize(string addonName)
|
||||
try
|
||||
{
|
||||
Log.Debug($"Initializing {addonName}");
|
||||
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);
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||
{
|
||||
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.Clear();
|
||||
arg.Addon = (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.Clear();
|
||||
arg.Addon = (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.Clear();
|
||||
arg.Addon = (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.Clear();
|
||||
arg.Addon = (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.Clear();
|
||||
arg.Addon = (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 +387,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()
|
||||
|
|
@ -305,8 +464,4 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 A0 01 00 00 48 8B 5C 24 30");
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,11 @@ internal class AddonLifecycleEventListener
|
|||
/// </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>
|
||||
|
|
@ -35,9 +40,4 @@ internal class AddonLifecycleEventListener
|
|||
/// 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,112 @@
|
|||
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.Clear();
|
||||
arg.Addon = (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 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;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentClassJobChangeArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentClassJobChangeArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentClassJobChangeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.ClassJobChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating what the new ClassJob is.
|
||||
/// </summary>
|
||||
public byte ClassJobId { get; set; }
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentGameEventArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentGameEventArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentGameEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.GameEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value representing which gameEvent was triggered.
|
||||
/// </summary>
|
||||
public int GameEvent { get; set; }
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentLevelChangeArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentLevelChangeArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentLevelChangeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.LevelChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating which ClassJob was switched to.
|
||||
/// </summary>
|
||||
public byte ClassJobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating what the new level is.
|
||||
/// </summary>
|
||||
public ushort Level { get; set; }
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AgentReceiveEventArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentReceiveEventArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentReceiveEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.ReceiveEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue return value for this event message.
|
||||
/// </summary>
|
||||
public nint ReturnValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue array for this event message.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue count for this event message.
|
||||
/// </summary>
|
||||
public uint ValueCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the event kind for this event message.
|
||||
/// </summary>
|
||||
public ulong EventKind { get; set; }
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AgentLifecycle arg data.
|
||||
/// </summary>
|
||||
public enum AgentArgsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic arg type that contains no meaningful data.
|
||||
/// </summary>
|
||||
Generic,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ReceiveEvent.
|
||||
/// </summary>
|
||||
ReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for GameEvent.
|
||||
/// </summary>
|
||||
GameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for LevelChange.
|
||||
/// </summary>
|
||||
LevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ClassJobChange.
|
||||
/// </summary>
|
||||
ClassJobChange,
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AgentLifecycle events.
|
||||
/// </summary>
|
||||
public enum AgentEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Receive Event Function.
|
||||
/// </summary>
|
||||
PreReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Receive Event Function.
|
||||
/// </summary>
|
||||
PostReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Filtered Receive Event Function.
|
||||
/// </summary>
|
||||
PreReceiveEventWithResult,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Filtered Receive Event Function.
|
||||
/// </summary>
|
||||
PostReceiveEventWithResult,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Show Function.
|
||||
/// </summary>
|
||||
PreShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Show Function.
|
||||
/// </summary>
|
||||
PostShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Hide Function.
|
||||
/// </summary>
|
||||
PreHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Hide Function.
|
||||
/// </summary>
|
||||
PostHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Update Function.
|
||||
/// </summary>
|
||||
PreUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Update Function.
|
||||
/// </summary>
|
||||
PostUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Game Event Function.
|
||||
/// </summary>
|
||||
PreGameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Game Event Function.
|
||||
/// </summary>
|
||||
PostGameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Game Event Function.
|
||||
/// </summary>
|
||||
PreLevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Level Change Function.
|
||||
/// </summary>
|
||||
PostLevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its ClassJob Change Function.
|
||||
/// </summary>
|
||||
PreClassJobChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its ClassJob Change Function.
|
||||
/// </summary>
|
||||
PostClassJobChange,
|
||||
}
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Agent.AgentArgTypes;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game agent lifecycles.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AgentLifecycle : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all allocated agent virtual tables.
|
||||
/// </summary>
|
||||
public static readonly List<AgentVirtualTable> AllocatedTables = [];
|
||||
|
||||
private static readonly ModuleLog Log = new("AgentLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private Hook<AgentModule.Delegates.Ctor>? onInitializeAgentsHook;
|
||||
private bool isInvokingListeners;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AgentLifecycle()
|
||||
{
|
||||
var agentModuleInstance = AgentModule.Instance();
|
||||
|
||||
// Hook is only used to determine appropriate timing for replacing Agent Virtual Tables
|
||||
// If the agent module is already initialized, then we can replace the tables safely.
|
||||
if (agentModuleInstance is null)
|
||||
{
|
||||
this.onInitializeAgentsHook = Hook<AgentModule.Delegates.Ctor>.FromAddress((nint)AgentModule.MemberFunctionPointers.Ctor, this.OnAgentModuleInitialize);
|
||||
this.onInitializeAgentsHook.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// For safety because this might be injected async, we will make sure we are on the main thread first.
|
||||
this.framework.RunOnFrameworkThread(() => this.ReplaceVirtualTables(agentModuleInstance));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AgentLifecycle Event Listeners.
|
||||
/// </summary> <br/>
|
||||
/// Mapping is: EventType -> ListenerList
|
||||
internal Dictionary<AgentEvent, Dictionary<AgentId, HashSet<AgentLifecycleEventListener>>> EventListeners { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.onInitializeAgentsHook?.Dispose();
|
||||
this.onInitializeAgentsHook = null;
|
||||
|
||||
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 AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
|
||||
{
|
||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||
if (matchedTable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return matchedTable.OriginalVirtualTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener for the target event and agent.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to register.</param>
|
||||
internal void RegisterListener(AgentLifecycleEventListener listener)
|
||||
{
|
||||
if (this.isInvokingListeners)
|
||||
{
|
||||
this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the listener from events.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to unregister.</param>
|
||||
internal void UnregisterListener(AgentLifecycleEventListener listener)
|
||||
{
|
||||
listener.IsRequestedToClear = true;
|
||||
|
||||
if (this.isInvokingListeners)
|
||||
{
|
||||
this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke listeners for the specified event type.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event Type.</param>
|
||||
/// <param name="args">AgentARgs.</param>
|
||||
/// <param name="blame">What to blame on errors.</param>
|
||||
internal void InvokeListenersSafely(AgentEvent eventType, AgentArgs 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 agentListeners)) return;
|
||||
|
||||
// Handle listeners for this event type that don't care which agent is triggering it
|
||||
if (agentListeners.TryGetValue((AgentId)uint.MaxValue, out var globalListeners))
|
||||
{
|
||||
foreach (var listener in globalListeners)
|
||||
{
|
||||
if (listener.IsRequestedToClear) continue;
|
||||
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global agent event listener.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle listeners that are listening for this agent and event type specifically
|
||||
if (agentListeners.TryGetValue(args.AgentId, out var agentListener))
|
||||
{
|
||||
foreach (var listener in agentListener)
|
||||
{
|
||||
if (listener.IsRequestedToClear) continue;
|
||||
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific agent {args.AgentId}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isInvokingListeners = false;
|
||||
}
|
||||
|
||||
private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule)
|
||||
{
|
||||
this.onInitializeAgentsHook!.Original(thisPtr, uiModule);
|
||||
|
||||
try
|
||||
{
|
||||
this.ReplaceVirtualTables(thisPtr);
|
||||
|
||||
// We don't need this hook anymore, it did its job!
|
||||
this.onInitializeAgentsHook!.Dispose();
|
||||
this.onInitializeAgentsHook = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in AgentLifecycle during AgentModule Ctor.");
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterListenerMethod(AgentLifecycleEventListener listener)
|
||||
{
|
||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||
{
|
||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
|
||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
|
||||
{
|
||||
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
|
||||
}
|
||||
|
||||
private void UnregisterListenerMethod(AgentLifecycleEventListener listener)
|
||||
{
|
||||
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners))
|
||||
{
|
||||
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener))
|
||||
{
|
||||
agentListener.Remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceVirtualTables(AgentModule* agentModule)
|
||||
{
|
||||
foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length))
|
||||
{
|
||||
try
|
||||
{
|
||||
var agentPointer = agentModule->Agents.GetPointer((int)index);
|
||||
|
||||
if (agentPointer is null)
|
||||
{
|
||||
Log.Warning("Null Agent Found?");
|
||||
continue;
|
||||
}
|
||||
|
||||
// AgentVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||
AllocatedTables.Add(new AgentVirtualTable(agentPointer->Value, (AgentId)index, this));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in AgentLifecycle during ReplaceVirtualTables.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AgentLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAgentLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AgentLifecyclePluginScoped : IInternalDisposableService, IAgentLifecycle
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AgentLifecycle agentLifecycleService = Service<AgentLifecycle>.Get();
|
||||
|
||||
private readonly List<AgentLifecycleEventListener> eventListeners = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var listener in this.eventListeners)
|
||||
{
|
||||
this.agentLifecycleService.UnregisterListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
foreach (var agentId in agentIds)
|
||||
{
|
||||
this.RegisterListener(eventType, agentId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
var listener = new AgentLifecycleEventListener(eventType, agentId, handler);
|
||||
this.eventListeners.Add(listener);
|
||||
this.agentLifecycleService.RegisterListener(listener);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
this.RegisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
foreach (var agentId in agentIds)
|
||||
{
|
||||
this.UnregisterListener(eventType, agentId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.EventType != eventType) return false;
|
||||
if (entry.AgentId != agentId) return false;
|
||||
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.agentLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
this.UnregisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(params IAgentLifecycle.AgentEventDelegate[] handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.agentLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||
=> (nint)AgentLifecycle.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates.
|
||||
/// </summary>
|
||||
public class AgentLifecycleEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentLifecycleEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to listen for.</param>
|
||||
/// <param name="agentId">Agent id to listen for.</param>
|
||||
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||
internal AgentLifecycleEventListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate functionDelegate)
|
||||
{
|
||||
this.EventType = eventType;
|
||||
this.AgentId = agentId;
|
||||
this.FunctionDelegate = functionDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the agentId of the agent this listener is looking for.
|
||||
/// uint.MaxValue if it wants to be called for any agent.
|
||||
/// </summary>
|
||||
public AgentId AgentId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type this listener is looking for.
|
||||
/// </summary>
|
||||
public AgentEvent EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate this listener invokes.
|
||||
/// </summary>
|
||||
public IAgentLifecycle.AgentEventDelegate FunctionDelegate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the listener is requested to be cleared.
|
||||
/// </summary>
|
||||
internal bool IsRequestedToClear { get; set; }
|
||||
}
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Agent.AgentArgTypes;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that holds references to an agents original and modified virtual table entries.
|
||||
/// </summary>
|
||||
internal unsafe class AgentVirtualTable : IDisposable
|
||||
{
|
||||
// This need to be at minimum the largest virtual table size of all agents
|
||||
// Copying extra entries is not problematic, and is considered safe.
|
||||
private const int VirtualTableEntryCount = 60;
|
||||
|
||||
private const bool EnableLogging = false;
|
||||
|
||||
private static readonly ModuleLog Log = new("AgentVT");
|
||||
|
||||
private readonly AgentLifecycle lifecycleService;
|
||||
|
||||
private readonly AgentId agentId;
|
||||
|
||||
// Each agent gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||
private readonly AgentReceiveEventArgs receiveEventArgs = new();
|
||||
private readonly AgentReceiveEventArgs filteredReceiveEventArgs = new();
|
||||
private readonly AgentArgs showArgs = new();
|
||||
private readonly AgentArgs hideArgs = new();
|
||||
private readonly AgentArgs updateArgs = new();
|
||||
private readonly AgentGameEventArgs gameEventArgs = new();
|
||||
private readonly AgentLevelChangeArgs levelChangeArgs = new();
|
||||
private readonly AgentClassJobChangeArgs classJobChangeArgs = new();
|
||||
|
||||
private readonly AgentInterface* agentInterface;
|
||||
|
||||
// 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 AgentInterface.Delegates.ReceiveEvent receiveEventFunction;
|
||||
private readonly AgentInterface.Delegates.ReceiveEventWithResult receiveEventWithResultFunction;
|
||||
private readonly AgentInterface.Delegates.Show showFunction;
|
||||
private readonly AgentInterface.Delegates.Hide hideFunction;
|
||||
private readonly AgentInterface.Delegates.Update updateFunction;
|
||||
private readonly AgentInterface.Delegates.OnGameEvent gameEventFunction;
|
||||
private readonly AgentInterface.Delegates.OnLevelChange levelChangeFunction;
|
||||
private readonly AgentInterface.Delegates.OnClassJobChange classJobChangeFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentVirtualTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="agent">AgentInterface* for the agent to replace the table of.</param>
|
||||
/// <param name="agentId">Agent ID.</param>
|
||||
/// <param name="lifecycleService">Reference to AgentLifecycle service to callback and invoke listeners.</param>
|
||||
internal AgentVirtualTable(AgentInterface* agent, AgentId agentId, AgentLifecycle lifecycleService)
|
||||
{
|
||||
this.agentInterface = agent;
|
||||
this.agentId = agentId;
|
||||
this.lifecycleService = lifecycleService;
|
||||
|
||||
// Save original virtual table
|
||||
this.OriginalVirtualTable = agent->VirtualTable;
|
||||
|
||||
// Create copy of original table
|
||||
// Note this will copy any derived/overriden functions that this specific agent has.
|
||||
// Note: currently there are 16 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||
this.ModifiedVirtualTable = (AgentInterface.AgentInterfaceVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||
NativeMemory.Copy(agent->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
|
||||
// Overwrite the agents existing virtual table with our own
|
||||
agent->VirtualTable = this.ModifiedVirtualTable;
|
||||
|
||||
// Pin each of our listener functions
|
||||
this.receiveEventFunction = this.OnAgentReceiveEvent;
|
||||
this.receiveEventWithResultFunction = this.OnAgentReceiveEventWithResult;
|
||||
this.showFunction = this.OnAgentShow;
|
||||
this.hideFunction = this.OnAgentHide;
|
||||
this.updateFunction = this.OnAgentUpdate;
|
||||
this.gameEventFunction = this.OnAgentGameEvent;
|
||||
this.levelChangeFunction = this.OnAgentLevelChange;
|
||||
this.classJobChangeFunction = this.OnClassJobChange;
|
||||
|
||||
// Overwrite specific virtual table entries
|
||||
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventFunction);
|
||||
this.ModifiedVirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventWithResultFunction);
|
||||
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AgentInterface*, uint, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||
this.ModifiedVirtualTable->OnGameEvent = (delegate* unmanaged<AgentInterface*, AgentInterface.GameEvent, void>)Marshal.GetFunctionPointerForDelegate(this.gameEventFunction);
|
||||
this.ModifiedVirtualTable->OnLevelChange = (delegate* unmanaged<AgentInterface*, byte, ushort, void>)Marshal.GetFunctionPointerForDelegate(this.levelChangeFunction);
|
||||
this.ModifiedVirtualTable->OnClassJobChange = (delegate* unmanaged<AgentInterface*, byte, void>)Marshal.GetFunctionPointerForDelegate(this.classJobChangeFunction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original virtual table address for this agent.
|
||||
/// </summary>
|
||||
internal AgentInterface.AgentInterfaceVirtualTable* OriginalVirtualTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modified virtual address for this agent.
|
||||
/// </summary>
|
||||
internal AgentInterface.AgentInterfaceVirtualTable* ModifiedVirtualTable { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Ensure restoration is done atomically.
|
||||
Interlocked.Exchange(ref *(nint*)&this.agentInterface->VirtualTable, (nint)this.OriginalVirtualTable);
|
||||
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
}
|
||||
|
||||
private AtkValue* OnAgentReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||
{
|
||||
AtkValue* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.receiveEventArgs.Agent = thisPtr;
|
||||
this.receiveEventArgs.AgentId = this.agentId;
|
||||
this.receiveEventArgs.ReturnValue = (nint)returnValue;
|
||||
this.receiveEventArgs.AtkValues = (nint)values;
|
||||
this.receiveEventArgs.ValueCount = valueCount;
|
||||
this.receiveEventArgs.EventKind = eventKind;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||
|
||||
returnValue = (AtkValue*)this.receiveEventArgs.ReturnValue;
|
||||
values = (AtkValue*)this.receiveEventArgs.AtkValues;
|
||||
valueCount = this.receiveEventArgs.ValueCount;
|
||||
eventKind = this.receiveEventArgs.EventKind;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->ReceiveEvent(thisPtr, returnValue, values, valueCount, eventKind);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Agent ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEvent.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private AtkValue* OnAgentReceiveEventWithResult(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||
{
|
||||
AtkValue* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.filteredReceiveEventArgs.Agent = thisPtr;
|
||||
this.filteredReceiveEventArgs.AgentId = this.agentId;
|
||||
this.filteredReceiveEventArgs.ReturnValue = (nint)returnValue;
|
||||
this.filteredReceiveEventArgs.AtkValues = (nint)values;
|
||||
this.filteredReceiveEventArgs.ValueCount = valueCount;
|
||||
this.filteredReceiveEventArgs.EventKind = eventKind;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEventWithResult, this.filteredReceiveEventArgs);
|
||||
|
||||
returnValue = (AtkValue*)this.filteredReceiveEventArgs.ReturnValue;
|
||||
values = (AtkValue*)this.filteredReceiveEventArgs.AtkValues;
|
||||
valueCount = this.filteredReceiveEventArgs.ValueCount;
|
||||
eventKind = this.filteredReceiveEventArgs.EventKind;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->ReceiveEventWithResult(thisPtr, returnValue, values, valueCount, eventKind);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Agent FilteredReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEventWithResult, this.filteredReceiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEventWithResult.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAgentShow(AgentInterface* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.showArgs.Agent = thisPtr;
|
||||
this.showArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreShow, this.showArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Show(thisPtr);
|
||||
}
|
||||
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(AgentEvent.PostShow, this.showArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentShow.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentHide(AgentInterface* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.hideArgs.Agent = thisPtr;
|
||||
this.hideArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreHide, this.hideArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Hide(thisPtr);
|
||||
}
|
||||
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(AgentEvent.PostHide, this.hideArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentHide.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentUpdate(AgentInterface* thisPtr, uint frameCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.updateArgs.Agent = thisPtr;
|
||||
this.updateArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreUpdate, this.updateArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Update(thisPtr, frameCount);
|
||||
}
|
||||
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(AgentEvent.PostUpdate, this.updateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentGameEvent(AgentInterface* thisPtr, AgentInterface.GameEvent gameEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.gameEventArgs.Agent = thisPtr;
|
||||
this.gameEventArgs.AgentId = this.agentId;
|
||||
this.gameEventArgs.GameEvent = (int)gameEvent;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreGameEvent, this.gameEventArgs);
|
||||
|
||||
gameEvent = (AgentInterface.GameEvent)this.gameEventArgs.GameEvent;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnGameEvent(thisPtr, gameEvent);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnGameEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostGameEvent, this.gameEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentGameEvent.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentLevelChange(AgentInterface* thisPtr, byte classJobId, ushort level)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.levelChangeArgs.Agent = thisPtr;
|
||||
this.levelChangeArgs.AgentId = this.agentId;
|
||||
this.levelChangeArgs.ClassJobId = classJobId;
|
||||
this.levelChangeArgs.Level = level;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreLevelChange, this.levelChangeArgs);
|
||||
|
||||
classJobId = this.levelChangeArgs.ClassJobId;
|
||||
level = this.levelChangeArgs.Level;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnLevelChange(thisPtr, classJobId, level);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnLevelChange. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostLevelChange, this.levelChangeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentLevelChange.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClassJobChange(AgentInterface* thisPtr, byte classJobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.classJobChangeArgs.Agent = thisPtr;
|
||||
this.classJobChangeArgs.AgentId = this.agentId;
|
||||
this.classJobChangeArgs.ClassJobId = classJobId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreClassJobChange, this.classJobChangeArgs);
|
||||
|
||||
classJobId = this.classJobChangeArgs.ClassJobId;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnClassJobChange(thisPtr, classJobId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnClassJobChange. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostClassJobChange, this.classJobChangeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnClassJobChange.");
|
||||
}
|
||||
}
|
||||
|
||||
[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 "OnAgentUpdate" || this.agentId is AgentId.PadMouseMode)
|
||||
return;
|
||||
|
||||
Log.Debug($"[{caller}]: {this.agentId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -14,7 +12,7 @@ public abstract class BaseAddressResolver
|
|||
/// <summary>
|
||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||
/// </summary>
|
||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = [];
|
||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(ISigScanner)"/> or <see cref="Setup64Bit(ISigScanner)"/>.
|
||||
|
|
|
|||
|
|
@ -1,221 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.Text;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Game.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a log message.
|
||||
/// </summary>
|
||||
public interface ILogMessage : IEquatable<ILogMessage>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the log message in memory.
|
||||
/// </summary>
|
||||
nint Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of this log message.
|
||||
/// </summary>
|
||||
uint LogMessageId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GameData associated with this log message.
|
||||
/// </summary>
|
||||
RowRef<Lumina.Excel.Sheets.LogMessage> GameData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity that is the source of this log message, if any.
|
||||
/// </summary>
|
||||
ILogMessageEntity? SourceEntity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity that is the target of this log message, if any.
|
||||
/// </summary>
|
||||
ILogMessageEntity? TargetEntity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of parameters.
|
||||
/// </summary>
|
||||
int ParameterCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the value of a parameter for the log message if it is an int.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||
/// <param name="value">The value of the parameter.</param>
|
||||
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||
bool TryGetIntParameter(int index, out int value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the value of a parameter for the log message if it is a string.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||
/// <param name="value">The value of the parameter.</param>
|
||||
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||
bool TryGetStringParameter(int index, out ReadOnlySeString value);
|
||||
|
||||
/// <summary>
|
||||
/// Formats this log message into an approximation of the string that will eventually be shown in the log.
|
||||
/// </summary>
|
||||
/// <remarks>This can cause side effects such as playing sound effects and thus should only be used for debugging.</remarks>
|
||||
/// <returns>The formatted string.</returns>
|
||||
ReadOnlySeString FormatLogMessageForDebugging();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents log message in the queue to be added to the chat.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the log message.</param>
|
||||
internal unsafe readonly struct LogMessage(LogMessageQueueItem* ptr) : ILogMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint LogMessageId => ptr->LogMessageId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.LogMessage> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.LogMessage>(ptr->LogMessageId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ILogMessageEntity? ILogMessage.SourceEntity => ptr->SourceKind == EntityRelationKind.None ? null : this.SourceEntity;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ILogMessageEntity? ILogMessage.TargetEntity => ptr->TargetKind == EntityRelationKind.None ? null : this.TargetEntity;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int ParameterCount => ptr->Parameters.Count;
|
||||
|
||||
private LogMessageEntity SourceEntity => new(ptr, true);
|
||||
|
||||
private LogMessageEntity TargetEntity => new(ptr, false);
|
||||
|
||||
public static bool operator ==(LogMessage x, LogMessage y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(LogMessage x, LogMessage y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ILogMessage? other)
|
||||
{
|
||||
return other is LogMessage logMessage && this.Equals(logMessage);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is LogMessage logMessage && this.Equals(logMessage);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.LogMessageId, this.SourceEntity, this.TargetEntity);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetIntParameter(int index, out int value)
|
||||
{
|
||||
value = 0;
|
||||
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||
if (parameter.Type != TextParameterType.Integer) return false;
|
||||
value = parameter.IntValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetStringParameter(int index, out ReadOnlySeString value)
|
||||
{
|
||||
value = default;
|
||||
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||
if (parameter.Type == TextParameterType.String)
|
||||
{
|
||||
value = new(parameter.StringValue.AsSpan());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parameter.Type == TextParameterType.ReferencedUtf8String)
|
||||
{
|
||||
value = new(parameter.ReferencedUtf8StringValue->Utf8String.AsSpan());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySeString FormatLogMessageForDebugging()
|
||||
{
|
||||
var logModule = RaptureLogModule.Instance();
|
||||
|
||||
// the formatting logic is taken from RaptureLogModule_Update
|
||||
|
||||
using var utf8 = new Utf8String();
|
||||
SetName(logModule, this.SourceEntity);
|
||||
SetName(logModule, this.TargetEntity);
|
||||
|
||||
using var rssb = new RentedSeStringBuilder();
|
||||
logModule->RaptureTextModule->FormatString(rssb.Builder.Append(this.GameData.Value.Text).GetViewAsSpan(), &ptr->Parameters, &utf8);
|
||||
|
||||
return new ReadOnlySeString(utf8.AsSpan());
|
||||
|
||||
static void SetName(RaptureLogModule* self, LogMessageEntity item)
|
||||
{
|
||||
var name = item.NameSpan.GetPointer(0);
|
||||
|
||||
if (item.IsPlayer)
|
||||
{
|
||||
var str = self->TempParseMessage.GetPointer(item.IsSourceEntity ? 8 : 9);
|
||||
self->FormatPlayerLink(name, str, null, 0, item.Kind != 1 /* LocalPlayer */, item.HomeWorldId, false, null, false);
|
||||
|
||||
if (item.HomeWorldId != 0 && item.HomeWorldId != AgentLobby.Instance()->LobbyData.HomeWorldId)
|
||||
{
|
||||
var crossWorldSymbol = self->RaptureTextModule->UnkStrings0.GetPointer(3);
|
||||
if (!crossWorldSymbol->StringPtr.HasValue)
|
||||
self->RaptureTextModule->ProcessMacroCode(crossWorldSymbol, "<icon(88)>\0"u8);
|
||||
str->Append(crossWorldSymbol);
|
||||
if (self->UIModule->GetWorldHelper()->AllWorlds.TryGetValuePointer(item.HomeWorldId, out var world))
|
||||
str->ConcatCStr(world->Name);
|
||||
}
|
||||
|
||||
name = str->StringPtr;
|
||||
}
|
||||
|
||||
if (item.IsSourceEntity)
|
||||
{
|
||||
self->RaptureTextModule->SetGlobalTempEntity1(name, item.Sex, item.ObjStrId);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->RaptureTextModule->SetGlobalTempEntity2(name, item.Sex, item.ObjStrId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetParameter(int index, out TextParameter value)
|
||||
{
|
||||
if (index < 0 || index >= ptr->Parameters.Count)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = ptr->Parameters[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool Equals(LogMessage other)
|
||||
{
|
||||
return this.LogMessageId == other.LogMessageId && this.SourceEntity == other.SourceEntity && this.TargetEntity == other.TargetEntity;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Game.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing an entity related to a log message.
|
||||
/// </summary>
|
||||
public interface ILogMessageEntity : IEquatable<ILogMessageEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of this entity.
|
||||
/// </summary>
|
||||
ReadOnlySeString Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the homeworld of this entity, if it is a player.
|
||||
/// </summary>
|
||||
ushort HomeWorldId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the homeworld of this entity, if it is a player.
|
||||
/// </summary>
|
||||
RowRef<World> HomeWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjStr ID of this entity, if not a player. See <seealso cref="ISeStringEvaluator.EvaluateObjStr"/>.
|
||||
/// </summary>
|
||||
uint ObjStrId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this entity is a player.
|
||||
/// </summary>
|
||||
bool IsPlayer { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents an entity related to a log message.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the log message item.</param>
|
||||
/// <param name="source">If <see langword="true"/> represents the source entity of the log message, otherwise represents the target entity.</param>
|
||||
internal unsafe readonly struct LogMessageEntity(LogMessageQueueItem* ptr, bool source) : ILogMessageEntity
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySeString Name => new(this.NameSpan[..this.NameSpan.IndexOf((byte)0)]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort HomeWorldId => source ? ptr->SourceHomeWorld : ptr->TargetHomeWorld;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<World> HomeWorld => LuminaUtils.CreateRef<World>(this.HomeWorldId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ObjStrId => source ? ptr->SourceObjStrId : ptr->TargetObjStrId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPlayer => source ? ptr->SourceIsPlayer : ptr->TargetIsPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Span containing the raw name of this entity.
|
||||
/// </summary>
|
||||
internal Span<byte> NameSpan => source ? ptr->SourceName : ptr->TargetName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of the entity.
|
||||
/// </summary>
|
||||
internal byte Kind => source ? (byte)ptr->SourceKind : (byte)ptr->TargetKind;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sex of this entity.
|
||||
/// </summary>
|
||||
internal byte Sex => source ? ptr->SourceSex : ptr->TargetSex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this entity is the source entity of a log message.
|
||||
/// </summary>
|
||||
internal bool IsSourceEntity => source;
|
||||
|
||||
public static bool operator ==(LogMessageEntity x, LogMessageEntity y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(LogMessageEntity x, LogMessageEntity y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ILogMessageEntity other)
|
||||
{
|
||||
return other is LogMessageEntity entity && this.Equals(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is LogMessageEntity entity && this.Equals(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.Name, this.HomeWorldId, this.ObjStrId, this.Sex, this.IsPlayer);
|
||||
}
|
||||
|
||||
private bool Equals(LogMessageEntity other)
|
||||
{
|
||||
return this.Name == other.Name && this.HomeWorldId == other.HomeWorldId && this.ObjStrId == other.ObjStrId && this.Kind == other.Kind && this.Sex == other.Sex && this.IsPlayer == other.IsPlayer;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using CheapLoc;
|
||||
|
|
@ -22,7 +23,7 @@ namespace Dalamud.Game;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal partial class ChatHandlers : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<ChatHandlers>();
|
||||
private static readonly ModuleLog Log = new("ChatHandlers");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -103,7 +104,7 @@ internal partial class ChatHandlers : IServiceType
|
|||
|
||||
if (this.configuration.PrintDalamudWelcomeMsg)
|
||||
{
|
||||
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Versioning.GetScmVersion())
|
||||
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Util.GetScmVersion())
|
||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ internal partial class ChatHandlers : IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Versioning.GetAssemblyVersion().StartsWith(this.configuration.LastVersion))
|
||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.StartsWith(this.configuration.LastVersion))
|
||||
{
|
||||
var linkPayload = chatGui.AddChatLinkHandler(
|
||||
(_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs));
|
||||
|
|
@ -136,7 +137,7 @@ internal partial class ChatHandlers : IServiceType
|
|||
Type = XivChatType.Notice,
|
||||
});
|
||||
|
||||
this.configuration.LastVersion = Versioning.GetAssemblyVersion();
|
||||
this.configuration.LastVersion = Util.AssemblyVersion;
|
||||
this.configuration.QueueSave();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,37 +63,47 @@ public interface IAetheryteEntry
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents an aetheryte entry available to the game.
|
||||
/// Class representing an aetheryte entry available to the game.
|
||||
/// </summary>
|
||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||
internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
|
||||
internal sealed class AetheryteEntry : IAetheryteEntry
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public uint AetheryteId => data.AetheryteId;
|
||||
private readonly TeleportInfo data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||
internal AetheryteEntry(TeleportInfo data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint TerritoryId => data.TerritoryId;
|
||||
public uint AetheryteId => this.data.AetheryteId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte SubIndex => data.SubIndex;
|
||||
public uint TerritoryId => this.data.TerritoryId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte Ward => data.Ward;
|
||||
public byte SubIndex => this.data.SubIndex;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte Plot => data.Plot;
|
||||
public byte Ward => this.data.Ward;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint GilCost => data.GilCost;
|
||||
public byte Plot => this.data.Plot;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsFavourite => data.IsFavourite;
|
||||
public uint GilCost => this.data.GilCost;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSharedHouse => data.IsSharedHouse;
|
||||
public bool IsFavourite => this.data.IsFavourite;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsApartment => data.IsApartment;
|
||||
public bool IsSharedHouse => this.data.IsSharedHouse;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsApartment => this.data.IsApartment;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Dalamud.Plugin.Services;
|
|||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Aetherytes;
|
||||
|
|
@ -89,7 +88,10 @@ internal sealed partial class AetheryteList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -97,34 +99,4 @@ internal sealed partial class AetheryteList
|
|||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
private struct Enumerator(AetheryteList aetheryteList) : IEnumerator<IAetheryteEntry>
|
||||
{
|
||||
private int index = -1;
|
||||
|
||||
public IAetheryteEntry Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++this.index < aetheryteList.Length)
|
||||
{
|
||||
this.Current = aetheryteList[this.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
|
||||
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
|
@ -24,7 +23,7 @@ namespace Dalamud.Game.ClientState.Buddy;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||
{
|
||||
private const uint InvalidEntityId = 0xE0000000;
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||
|
|
@ -85,37 +84,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetCompanionBuddyMemberAddress()
|
||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
||||
{
|
||||
return (nint)this.BuddyListStruct->CompanionInfo.Companion;
|
||||
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetPetBuddyMemberAddress()
|
||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
||||
{
|
||||
return (nint)this.BuddyListStruct->PetInfo.Pet;
|
||||
return (IntPtr)this.BuddyListStruct->PetInfo.Pet;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetBattleBuddyMemberAddress(int index)
|
||||
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= 3)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||
return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
|
||||
public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||
{
|
||||
if (address == 0)
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
if (this.playerState.ContentId == 0)
|
||||
if (!this.playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
var buddy = new BuddyMember((CSBuddyMember*)address);
|
||||
if (buddy.EntityId == InvalidEntityId)
|
||||
var buddy = new BuddyMember(address);
|
||||
if (buddy.ObjectId == InvalidObjectID)
|
||||
return null;
|
||||
|
||||
return buddy;
|
||||
|
|
@ -133,39 +132,12 @@ internal sealed partial class BuddyList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IBuddyMember> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(BuddyList buddyList) : IEnumerator<IBuddyMember>
|
||||
{
|
||||
private int index = -1;
|
||||
|
||||
public IBuddyMember Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++this.index < buddyList.Length)
|
||||
{
|
||||
this.Current = buddyList[this.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// </summary>
|
||||
public interface IBuddyMember : IEquatable<IBuddyMember>
|
||||
public interface IBuddyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy in memory.
|
||||
/// </summary>
|
||||
nint Address { get; }
|
||||
IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of this buddy.
|
||||
|
|
@ -71,34 +67,42 @@ public interface IBuddyMember : IEquatable<IBuddyMember>
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the BuddyMember.</param>
|
||||
internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
|
||||
internal unsafe class BuddyMember : IBuddyMember
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
/// <inheritdoc />
|
||||
public nint Address => (nint)ptr;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Buddy address.</param>
|
||||
internal BuddyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint ObjectId => this.EntityId;
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint EntityId => ptr->EntityId;
|
||||
public uint ObjectId => this.Struct->EntityId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
|
||||
public uint EntityId => this.Struct->EntityId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint CurrentHP => ptr->CurrentHealth;
|
||||
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint MaxHP => ptr->MaxHealth;
|
||||
public uint CurrentHP => this.Struct->CurrentHealth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint DataID => ptr->DataId;
|
||||
public uint MaxHP => this.Struct->MaxHealth;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint DataID => this.Struct->DataId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
|
||||
|
|
@ -109,25 +113,5 @@ internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
|
|||
/// <inheritdoc />
|
||||
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
|
||||
|
||||
public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(BuddyMember x, BuddyMember y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IBuddyMember? other)
|
||||
{
|
||||
return this.EntityId == other.EntityId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is BuddyMember fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EntityId.GetHashCode();
|
||||
}
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ namespace Dalamud.Game.ClientState;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||
{
|
||||
private static readonly ModuleLog Log = ModuleLog.Create<ClientState>();
|
||||
private static readonly ModuleLog Log = new("ClientState");
|
||||
|
||||
private readonly GameLifecycle lifecycle;
|
||||
private readonly ClientStateAddressResolver address;
|
||||
private readonly Hook<HandleZoneInitPacketDelegate> handleZoneInitPacketHook;
|
||||
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
|
||||
private readonly Hook<SetCurrentInstanceDelegate> setCurrentInstanceHook;
|
||||
|
||||
|
|
@ -71,11 +72,13 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
|
||||
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
|
||||
|
||||
this.handleZoneInitPacketHook = Hook<HandleZoneInitPacketDelegate>.FromAddress(this.AddressResolver.HandleZoneInitPacket, this.HandleZoneInitPacketDetour);
|
||||
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
|
||||
this.setCurrentInstanceHook = Hook<SetCurrentInstanceDelegate>.FromAddress(this.AddressResolver.SetCurrentInstance, this.SetCurrentInstanceDetour);
|
||||
|
||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
|
||||
this.handleZoneInitPacketHook.Enable();
|
||||
this.uiModuleHandlePacketHook.Enable();
|
||||
this.setCurrentInstanceHook.Enable();
|
||||
|
||||
|
|
@ -268,6 +271,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
/// </summary>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.handleZoneInitPacketHook.Dispose();
|
||||
this.uiModuleHandlePacketHook.Dispose();
|
||||
this.onLogoutHook.Dispose();
|
||||
this.setCurrentInstanceHook.Dispose();
|
||||
|
|
@ -290,6 +294,23 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
this.framework.Update += this.OnFrameworkUpdate;
|
||||
}
|
||||
|
||||
private void HandleZoneInitPacketDetour(nint a1, uint localPlayerEntityId, nint packet, byte type)
|
||||
{
|
||||
this.handleZoneInitPacketHook.Original(a1, localPlayerEntityId, packet, type);
|
||||
|
||||
try
|
||||
{
|
||||
var eventArgs = ZoneInitEventArgs.Read(packet);
|
||||
Log.Debug($"ZoneInit: {eventArgs}");
|
||||
this.ZoneInit?.InvokeSafely(eventArgs);
|
||||
this.TerritoryType = (ushort)eventArgs.TerritoryType.RowId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception during ZoneInit");
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void UIModuleHandlePacketDetour(
|
||||
UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet)
|
||||
{
|
||||
|
|
@ -335,15 +356,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
case (UIModulePacketType)5: // TODO: Use UIModulePacketType.InitZone when available
|
||||
{
|
||||
var eventArgs = ZoneInitEventArgs.Read((nint)packet);
|
||||
Log.Debug($"ZoneInit: {eventArgs}");
|
||||
this.ZoneInit?.InvokeSafely(eventArgs);
|
||||
this.TerritoryType = (ushort)eventArgs.TerritoryType.RowId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.ClientState;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -21,6 +19,11 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
|
||||
// Functions
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method that handles the ZoneInit packet.
|
||||
/// </summary>
|
||||
public nint HandleZoneInitPacket { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method that sets the current public instance.
|
||||
/// </summary>
|
||||
|
|
@ -32,6 +35,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.HandleZoneInitPacket = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 45");
|
||||
this.SetCurrentInstance = sig.ScanText("E8 ?? ?? ?? ?? 0F B6 55 ?? 48 8D 0D ?? ?? ?? ?? C0 EA"); // NetworkModuleProxy.SetCurrentInstance
|
||||
|
||||
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ internal sealed class Condition : IInternalDisposableService, ICondition
|
|||
/// <summary>
|
||||
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||
/// </summary>
|
||||
internal const int MaxConditionEntries = 112;
|
||||
internal const int MaxConditionEntries = 104;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
|
|
|||
|
|
@ -520,17 +520,4 @@ public enum ConditionFlag
|
|||
PilotingMech = 102,
|
||||
|
||||
// Unknown103 = 103,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to execute command while editing a strategy board.
|
||||
/// </summary>
|
||||
EditingStrategyBoard = 104,
|
||||
|
||||
// Unknown105 = 105,
|
||||
// Unknown106 = 106,
|
||||
// Unknown107 = 107,
|
||||
// Unknown108 = 108,
|
||||
// Unknown109 = 109,
|
||||
// Unknown110 = 110,
|
||||
// Unknown111 = 111,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,311 +0,0 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Customize;
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents customization data a <see cref="ICharacter"/> has.
|
||||
/// </summary>
|
||||
public interface ICustomizeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current race.
|
||||
/// E.g., Miqo'te, Aura.
|
||||
/// </summary>
|
||||
public byte Race { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current sex.
|
||||
/// </summary>
|
||||
public byte Sex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current body type.
|
||||
/// </summary>
|
||||
public byte BodyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current height (0 to 100).
|
||||
/// </summary>
|
||||
public byte Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tribe.
|
||||
/// E.g., Seeker of the Sun, Keeper of the Moon.
|
||||
/// </summary>
|
||||
public byte Tribe { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current face (1 to 4).
|
||||
/// </summary>
|
||||
public byte Face { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current hairstyle.
|
||||
/// </summary>
|
||||
public byte Hairstyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current skin color.
|
||||
/// </summary>
|
||||
public byte SkinColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current color of the left eye.
|
||||
/// </summary>
|
||||
public byte EyeColorLeft { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current color of the right eye.
|
||||
/// </summary>
|
||||
public byte EyeColorRight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current main hair color.
|
||||
/// </summary>
|
||||
public byte HairColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current highlight hair color.
|
||||
/// </summary>
|
||||
public byte HighlightsColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tattoo color.
|
||||
/// </summary>
|
||||
public byte TattooColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current eyebrow type.
|
||||
/// </summary>
|
||||
public byte Eyebrows { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current nose type.
|
||||
/// </summary>
|
||||
public byte Nose { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current jaw type.
|
||||
/// </summary>
|
||||
public byte Jaw { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current lip color fur pattern.
|
||||
/// </summary>
|
||||
public byte LipColorFurPattern { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current muscle mass value.
|
||||
/// </summary>
|
||||
public byte MuscleMass { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tail type (1 to 4).
|
||||
/// </summary>
|
||||
public byte TailShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current bust size (0 to 100).
|
||||
/// </summary>
|
||||
public byte BustSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current color of the face paint.
|
||||
/// </summary>
|
||||
public byte FacePaintColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether highlight color is used.
|
||||
/// </summary>
|
||||
public bool Highlights { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this facial feature is used.
|
||||
/// </summary>
|
||||
public bool FacialFeature1 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature2 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature3 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature4 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature5 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature6 { get; }
|
||||
|
||||
/// <inheritdoc cref="FacialFeature1"/>
|
||||
public bool FacialFeature7 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the legacy tattoo is used.
|
||||
/// </summary>
|
||||
public bool LegacyTattoo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current eye shape type.
|
||||
/// </summary>
|
||||
public byte EyeShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether small iris is used.
|
||||
/// </summary>
|
||||
public bool SmallIris { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current mouth type.
|
||||
/// </summary>
|
||||
public byte Mouth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether lipstick is used.
|
||||
/// </summary>
|
||||
public bool Lipstick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current face paint type.
|
||||
/// </summary>
|
||||
public byte FacePaint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether face paint reversed is used.
|
||||
/// </summary>
|
||||
public bool FacePaintReversed { get; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal readonly unsafe struct CustomizeData : ICustomizeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the address of the customize data struct in memory.
|
||||
/// </summary>
|
||||
public readonly nint Address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomizeData"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the status list.</param>
|
||||
internal CustomizeData(nint address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Race => this.Struct->Race;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Sex => this.Struct->Sex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte BodyType => this.Struct->BodyType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Height => this.Struct->Height;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Tribe => this.Struct->Tribe;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Face => this.Struct->Face;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Hairstyle => this.Struct->Hairstyle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte SkinColor => this.Struct->SkinColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte EyeColorLeft => this.Struct->EyeColorLeft;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte EyeColorRight => this.Struct->EyeColorRight;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte HairColor => this.Struct->HairColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte HighlightsColor => this.Struct->HighlightsColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte TattooColor => this.Struct->TattooColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Eyebrows => this.Struct->Eyebrows;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Nose => this.Struct->Nose;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Jaw => this.Struct->Jaw;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LipColorFurPattern => this.Struct->LipColorFurPattern;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte MuscleMass => this.Struct->MuscleMass;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte TailShape => this.Struct->TailShape;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte BustSize => this.Struct->BustSize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte FacePaintColor => this.Struct->FacePaintColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Highlights => this.Struct->Highlights;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature1 => this.Struct->FacialFeature1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature2 => this.Struct->FacialFeature2;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature3 => this.Struct->FacialFeature3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature4 => this.Struct->FacialFeature4;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature5 => this.Struct->FacialFeature5;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature6 => this.Struct->FacialFeature6;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacialFeature7 => this.Struct->FacialFeature7;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool LegacyTattoo => this.Struct->LegacyTattoo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte EyeShape => this.Struct->EyeShape;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SmallIris => this.Struct->SmallIris;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Mouth => this.Struct->Mouth;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Lipstick => this.Struct->Lipstick;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte FacePaint => this.Struct->FacePaint;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FacePaintReversed => this.Struct->FacePaintReversed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct =>
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address;
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
|
|
@ -8,12 +7,10 @@ using Dalamud.Memory;
|
|||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a fate entry that can be seen in the current area.
|
||||
/// Interface representing an fate entry that can be seen in the current area.
|
||||
/// </summary>
|
||||
public interface IFate : IEquatable<IFate>
|
||||
{
|
||||
|
|
@ -115,96 +112,129 @@ public interface IFate : IEquatable<IFate>
|
|||
/// <summary>
|
||||
/// Gets the address of this Fate in memory.
|
||||
/// </summary>
|
||||
nint Address { get; }
|
||||
IntPtr Address { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents a Fate.
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the FateContext.</param>
|
||||
internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate
|
||||
internal unsafe partial class Fate
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this fate in memory.</param>
|
||||
internal Fate(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public nint Address => (nint)ptr;
|
||||
public IntPtr Address { get; }
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
|
||||
|
||||
public static bool operator ==(Fate fate1, Fate fate2)
|
||||
{
|
||||
if (fate1 is null || fate2 is null)
|
||||
return Equals(fate1, fate2);
|
||||
|
||||
return fate1.Equals(fate2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
||||
/// </summary>
|
||||
/// <param name="fate">The fate to check.</param>
|
||||
/// <returns>True or false.</returns>
|
||||
public static bool IsValid(Fate fate)
|
||||
{
|
||||
if (fate == null)
|
||||
return false;
|
||||
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
return playerState.IsLoaded == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this actor is still valid in memory.
|
||||
/// </summary>
|
||||
/// <returns>True or false.</returns>
|
||||
public bool IsValid() => IsValid(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort FateId => ptr->FateId;
|
||||
bool IEquatable<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents an FFXIV Fate.
|
||||
/// </summary>
|
||||
internal unsafe partial class Fate : IFate
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ushort FateId => this.Struct->FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int StartTimeEpoch => ptr->StartTimeEpoch;
|
||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public short Duration => ptr->Duration;
|
||||
public short Duration => this.Struct->Duration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => MemoryHelper.ReadSeString(&ptr->Name);
|
||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
|
||||
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
|
||||
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FateState State => (FateState)ptr->State;
|
||||
public FateState State => (FateState)this.Struct->State;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte HandInCount => ptr->HandInCount;
|
||||
public byte HandInCount => this.Struct->HandInCount;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Progress => ptr->Progress;
|
||||
public byte Progress => this.Struct->Progress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBonus => ptr->IsBonus;
|
||||
public bool HasBonus => this.Struct->IsBonus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint IconId => ptr->IconId;
|
||||
public uint IconId => this.Struct->IconId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Level => ptr->Level;
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte MaxLevel => ptr->MaxLevel;
|
||||
public byte MaxLevel => this.Struct->MaxLevel;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector3 Position => ptr->Location;
|
||||
public Vector3 Position => this.Struct->Location;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Radius => ptr->Radius;
|
||||
public float Radius => this.Struct->Radius;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint MapIconId => ptr->MapIconId;
|
||||
public uint MapIconId => this.Struct->MapIconId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
||||
|
||||
public static bool operator ==(Fate x, Fate y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(Fate x, Fate y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IFate? other)
|
||||
{
|
||||
return this.FateId == other.FateId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is Fate fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.FateId.GetHashCode();
|
||||
}
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
|
@ -27,7 +26,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint Address => (nint)CSFateManager.Instance();
|
||||
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe int Length
|
||||
|
|
@ -70,29 +69,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetFateAddress(int index)
|
||||
public unsafe IntPtr GetFateAddress(int index)
|
||||
{
|
||||
if (index >= this.Length)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (nint)fateManager->Fates[index].Value;
|
||||
return (IntPtr)fateManager->Fates[index].Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IFate? CreateFateReference(IntPtr address)
|
||||
public IFate? CreateFateReference(IntPtr offset)
|
||||
{
|
||||
if (address == 0)
|
||||
if (offset == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
if (clientState.LocalContentId == 0)
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
if (!playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
return new Fate((CSFateContext*)address);
|
||||
return new Fate(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,39 +106,12 @@ internal sealed partial class FateTable
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IFate> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(FateTable fateTable) : IEnumerator<IFate>
|
||||
{
|
||||
private int index = -1;
|
||||
|
||||
public IFate Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++this.index < fateTable.Length)
|
||||
{
|
||||
this.Current = fateTable[this.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.GamePad;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ internal class JobGauges : IServiceType, IJobGauges
|
|||
// Since the gauge itself reads from live memory, there isn't much downside to doing this.
|
||||
if (!this.cache.TryGetValue(typeof(T), out var gauge))
|
||||
{
|
||||
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, [this.Address], null);
|
||||
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
|
||||
}
|
||||
|
||||
return (T)gauge;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
|
@ -83,12 +82,12 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
|||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
[
|
||||
return new[]
|
||||
{
|
||||
this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.Mage : Song.None,
|
||||
this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.Army : Song.None,
|
||||
this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.Wanderer : Song.None,
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
using CanvasFlags = Dalamud.Game.ClientState.JobGauge.Enums.CanvasFlags;
|
||||
using CreatureFlags = Dalamud.Game.ClientState.JobGauge.Enums.CreatureFlags;
|
||||
|
|
@ -22,45 +22,45 @@ public unsafe class PCTGauge : JobGaugeBase<PictomancerGauge>
|
|||
/// <summary>
|
||||
/// Gets the use of subjective pallete.
|
||||
/// </summary>
|
||||
public byte PalleteGauge => this.Struct->PalleteGauge;
|
||||
public byte PalleteGauge => Struct->PalleteGauge;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of paint the player has.
|
||||
/// </summary>
|
||||
public byte Paint => this.Struct->Paint;
|
||||
public byte Paint => Struct->Paint;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a creature motif is drawn.
|
||||
/// </summary>
|
||||
public bool CreatureMotifDrawn => this.Struct->CreatureMotifDrawn;
|
||||
public bool CreatureMotifDrawn => Struct->CreatureMotifDrawn;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a weapon motif is drawn.
|
||||
/// </summary>
|
||||
public bool WeaponMotifDrawn => this.Struct->WeaponMotifDrawn;
|
||||
public bool WeaponMotifDrawn => Struct->WeaponMotifDrawn;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a landscape motif is drawn.
|
||||
/// </summary>
|
||||
public bool LandscapeMotifDrawn => this.Struct->LandscapeMotifDrawn;
|
||||
public bool LandscapeMotifDrawn => Struct->LandscapeMotifDrawn;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a moogle portrait is ready.
|
||||
/// </summary>
|
||||
public bool MooglePortraitReady => this.Struct->MooglePortraitReady;
|
||||
public bool MooglePortraitReady => Struct->MooglePortraitReady;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a madeen portrait is ready.
|
||||
/// </summary>
|
||||
public bool MadeenPortraitReady => this.Struct->MadeenPortraitReady;
|
||||
public bool MadeenPortraitReady => Struct->MadeenPortraitReady;
|
||||
|
||||
/// <summary>
|
||||
/// Gets which creature flags are present.
|
||||
/// </summary>
|
||||
public CreatureFlags CreatureFlags => (CreatureFlags)this.Struct->CreatureFlags;
|
||||
public CreatureFlags CreatureFlags => (CreatureFlags)Struct->CreatureFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets which canvas flags are present.
|
||||
/// </summary>
|
||||
public CanvasFlags CanvasFlags => (CanvasFlags)this.Struct->CanvasFlags;
|
||||
public CanvasFlags CanvasFlags => (CanvasFlags)Struct->CanvasFlags;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||
|
||||
using Reloaded.Memory;
|
||||
|
||||
using DreadCombo = Dalamud.Game.ClientState.JobGauge.Enums.DreadCombo;
|
||||
using SerpentCombo = Dalamud.Game.ClientState.JobGauge.Enums.SerpentCombo;
|
||||
|
||||
|
|
@ -22,25 +24,25 @@ public unsafe class VPRGauge : JobGaugeBase<ViperGauge>
|
|||
/// <summary>
|
||||
/// Gets how many uses of uncoiled fury the player has.
|
||||
/// </summary>
|
||||
public byte RattlingCoilStacks => this.Struct->RattlingCoilStacks;
|
||||
public byte RattlingCoilStacks => Struct->RattlingCoilStacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Serpent Offering stacks and gauge.
|
||||
/// </summary>
|
||||
public byte SerpentOffering => this.Struct->SerpentOffering;
|
||||
public byte SerpentOffering => Struct->SerpentOffering;
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating the use of 1st, 2nd, 3rd, 4th generation and Ouroboros.
|
||||
/// </summary>
|
||||
public byte AnguineTribute => this.Struct->AnguineTribute;
|
||||
public byte AnguineTribute => Struct->AnguineTribute;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last Weaponskill used in DreadWinder/Pit of Dread combo.
|
||||
/// </summary>
|
||||
public DreadCombo DreadCombo => (DreadCombo)this.Struct->DreadCombo;
|
||||
public DreadCombo DreadCombo => (DreadCombo)Struct->DreadCombo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets current ability for Serpent's Tail.
|
||||
/// </summary>
|
||||
public SerpentCombo SerpentCombo => (SerpentCombo)this.Struct->SerpentCombo;
|
||||
public SerpentCombo SerpentCombo => (SerpentCombo)Struct->SerpentCombo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ using Dalamud.Utility;
|
|||
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
||||
|
||||
|
|
@ -35,6 +37,8 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
|
||||
private readonly CachedEntry[] cachedObjectTable;
|
||||
|
||||
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe ObjectTable()
|
||||
{
|
||||
|
|
@ -44,6 +48,9 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
||||
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
||||
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
||||
|
||||
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
|
||||
this.frameworkThreadEnumerators[i] = new(this, i);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -236,25 +243,43 @@ internal sealed partial class ObjectTable
|
|||
public IEnumerator<IGameObject> GetEnumerator()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
return new Enumerator(this);
|
||||
|
||||
// If we're on the framework thread, see if there's an already allocated enumerator available for use.
|
||||
foreach (ref var x in this.frameworkThreadEnumerators.AsSpan())
|
||||
{
|
||||
if (x is not null)
|
||||
{
|
||||
var t = x;
|
||||
x = null;
|
||||
t.Reset();
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// No reusable enumerator is available; allocate a new temporary one.
|
||||
return new Enumerator(this, -1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
|
||||
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
|
||||
{
|
||||
private ObjectTable? owner = owner;
|
||||
|
||||
private int index = -1;
|
||||
|
||||
public IGameObject Current { get; private set; }
|
||||
public IGameObject Current { get; private set; } = null!;
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
var cache = owner.cachedObjectTable.AsSpan();
|
||||
if (this.index == objectTableLength)
|
||||
return false;
|
||||
|
||||
while (++this.index < objectTableLength)
|
||||
var cache = this.owner!.cachedObjectTable.AsSpan();
|
||||
for (this.index++; this.index < objectTableLength; this.index++)
|
||||
{
|
||||
if (cache[this.index].Update() is { } ao)
|
||||
{
|
||||
|
|
@ -263,17 +288,24 @@ internal sealed partial class ObjectTable
|
|||
}
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
public void Reset() => this.index = -1;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.owner is not { } o)
|
||||
return;
|
||||
|
||||
if (slotId != -1)
|
||||
o.frameworkThreadEnumerators[slotId] = this;
|
||||
}
|
||||
|
||||
public bool TryReset()
|
||||
{
|
||||
this.Reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
|
||||
|
|
@ -30,50 +29,50 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager
|
|||
/// <inheritdoc/>
|
||||
public IGameObject? Target
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GetHardTarget());
|
||||
set => this.Struct->SetHardTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetHardTarget());
|
||||
set => Struct->SetHardTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? MouseOverTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->MouseOverTarget);
|
||||
set => this.Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||
set => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? FocusTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->FocusTarget);
|
||||
set => this.Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||
set => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? PreviousTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->PreviousTarget);
|
||||
set => this.Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||
set => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? SoftTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GetSoftTarget());
|
||||
set => this.Struct->SetSoftTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetSoftTarget());
|
||||
set => Struct->SetSoftTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? GPoseTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GPoseTarget);
|
||||
set => this.Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget);
|
||||
set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? MouseOverNameplateTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->MouseOverNameplateTarget);
|
||||
set => this.Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget);
|
||||
set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
private TargetSystem* Struct => TargetSystem.Instance();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Customize;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
|
@ -15,73 +16,68 @@ namespace Dalamud.Game.ClientState.Objects.Types;
|
|||
public interface ICharacter : IGameObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current HP of this character.
|
||||
/// Gets the current HP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentHp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this character.
|
||||
/// Gets the maximum HP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxHp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this character.
|
||||
/// Gets the current MP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentMp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this character.
|
||||
/// Gets the maximum MP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxMp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GP of this character.
|
||||
/// Gets the current GP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentGp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum GP of this character.
|
||||
/// Gets the maximum GP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxGp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current CP of this character.
|
||||
/// Gets the current CP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentCp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum CP of this character.
|
||||
/// Gets the maximum CP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxCp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shield percentage of this character.
|
||||
/// Gets the shield percentage of this Chara.
|
||||
/// </summary>
|
||||
public byte ShieldPercentage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob of this character.
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public RowRef<ClassJob> ClassJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this character.
|
||||
/// Gets the level of this Chara.
|
||||
/// </summary>
|
||||
public byte Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte array describing the visual appearance of this character.
|
||||
/// Gets a byte array describing the visual appearance of this Chara.
|
||||
/// Indexed by <see cref="CustomizeIndex"/>.
|
||||
/// </summary>
|
||||
public byte[] Customize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying CustomizeData struct for this character.
|
||||
/// </summary>
|
||||
public ICustomizeData CustomizeData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Free Company tag of this character.
|
||||
/// Gets the Free Company tag of this chara.
|
||||
/// </summary>
|
||||
public SeString CompanyTag { get; }
|
||||
|
||||
|
|
@ -123,7 +119,7 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
/// This represents a non-static entity.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of this character in memory.</param>
|
||||
internal Character(nint address)
|
||||
internal Character(IntPtr address)
|
||||
: base(address)
|
||||
{
|
||||
}
|
||||
|
|
@ -162,12 +158,8 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
public byte Level => this.Struct->CharacterData.Level;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api15ToDo("Do not allocate on each call, use the CS Span and let consumers do allocation if necessary")]
|
||||
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
|
|
@ -44,20 +43,20 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||
public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
|
||||
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<CSPartyMember>();
|
||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
||||
|
||||
private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? this[int index]
|
||||
|
|
@ -82,45 +81,39 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint GetPartyMemberAddress(int index)
|
||||
public IntPtr GetPartyMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= GroupLength)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.GroupListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? CreatePartyMemberReference(nint address)
|
||||
public IPartyMember? CreatePartyMemberReference(IntPtr address)
|
||||
{
|
||||
if (this.playerState.ContentId == 0)
|
||||
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new PartyMember((CSPartyMember*)address);
|
||||
return new PartyMember(address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint GetAllianceMemberAddress(int index)
|
||||
public IntPtr GetAllianceMemberAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= AllianceLength)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPartyMember? CreateAllianceMemberReference(nint address)
|
||||
public IPartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||
{
|
||||
if (this.playerState.ContentId == 0)
|
||||
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
if (address == 0)
|
||||
return null;
|
||||
|
||||
return new PartyMember((CSPartyMember*)address);
|
||||
return new PartyMember(address);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,43 +128,18 @@ internal sealed partial class PartyList
|
|||
/// <inheritdoc/>
|
||||
public IEnumerator<IPartyMember> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
var member = this[i];
|
||||
|
||||
if (member == null)
|
||||
break;
|
||||
|
||||
yield return member;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
private struct Enumerator(PartyList partyList) : IEnumerator<IPartyMember>
|
||||
{
|
||||
private int index = -1;
|
||||
|
||||
public IPartyMember Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (++this.index < partyList.Length)
|
||||
{
|
||||
var partyMember = partyList[this.index];
|
||||
if (partyMember != null)
|
||||
{
|
||||
this.Current = partyMember;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,26 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a party member.
|
||||
/// </summary>
|
||||
public interface IPartyMember : IEquatable<IPartyMember>
|
||||
public interface IPartyMember
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of this party member in memory.
|
||||
/// </summary>
|
||||
nint Address { get; }
|
||||
IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of buffs or debuffs applied to this party member.
|
||||
|
|
@ -111,82 +108,69 @@ public interface IPartyMember : IEquatable<IPartyMember>
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents a party member in the group manager.
|
||||
/// This class represents a party member in the group manager.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the PartyMember.</param>
|
||||
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
|
||||
internal unsafe class PartyMember : IPartyMember
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public nint Address => (nint)ptr;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the party member.</param>
|
||||
internal PartyMember(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StatusList Statuses => new(&ptr->StatusManager);
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector3 Position => ptr->Position;
|
||||
public StatusList Statuses => new(&this.Struct->StatusManager);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api15ToDo("Change type to ulong.")]
|
||||
public long ContentId => (long)ptr->ContentId;
|
||||
public Vector3 Position => this.Struct->Position;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ObjectId => ptr->EntityId;
|
||||
public long ContentId => (long)this.Struct->ContentId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint EntityId => ptr->EntityId;
|
||||
public uint ObjectId => this.Struct->EntityId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint EntityId => this.Struct->EntityId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint CurrentHP => ptr->CurrentHP;
|
||||
public uint CurrentHP => this.Struct->CurrentHP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint MaxHP => ptr->MaxHP;
|
||||
public uint MaxHP => this.Struct->MaxHP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort CurrentMP => ptr->CurrentMP;
|
||||
public ushort CurrentMP => this.Struct->CurrentMP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort MaxMP => ptr->MaxMP;
|
||||
public ushort MaxMP => this.Struct->MaxMP;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
|
||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => SeString.Parse(ptr->Name);
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Sex => ptr->Sex;
|
||||
public byte Sex => this.Struct->Sex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
|
||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Level => ptr->Level;
|
||||
public byte Level => this.Struct->Level;
|
||||
|
||||
public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(PartyMember x, PartyMember y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IPartyMember? other)
|
||||
{
|
||||
return this.EntityId == other.EntityId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is PartyMember fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EntityId.GetHashCode();
|
||||
}
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,61 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a status.
|
||||
/// This class represents a status effect an actor is afflicted by.
|
||||
/// </summary>
|
||||
public interface IStatus : IEquatable<IStatus>
|
||||
public unsafe class Status
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Status address.</param>
|
||||
internal Status(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status in memory.
|
||||
/// </summary>
|
||||
nint Address { get; }
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status ID of this status.
|
||||
/// </summary>
|
||||
uint StatusId { get; }
|
||||
public uint StatusId => this.Struct->StatusId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GameData associated with this status.
|
||||
/// </summary>
|
||||
RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
|
||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter value of the status.
|
||||
/// </summary>
|
||||
ushort Param { get; }
|
||||
public ushort Param => this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack count of this status.
|
||||
/// Only valid if this is a non-food status.
|
||||
/// </summary>
|
||||
[Obsolete($"Replaced with {nameof(Param)}", true)]
|
||||
public byte StackCount => (byte)this.Struct->Param;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining of this status.
|
||||
/// </summary>
|
||||
float RemainingTime { get; }
|
||||
public float RemainingTime => this.Struct->RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source ID of this status.
|
||||
/// </summary>
|
||||
uint SourceId { get; }
|
||||
public uint SourceId => this.Struct->SourceObject.ObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source actor associated with this status.
|
||||
|
|
@ -51,55 +63,7 @@ public interface IStatus : IEquatable<IStatus>
|
|||
/// <remarks>
|
||||
/// This iterates the actor table, it should be used with care.
|
||||
/// </remarks>
|
||||
IGameObject? SourceObject { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct represents a status effect an actor is afflicted by.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the Status.</param>
|
||||
internal unsafe readonly struct Status(CSStatus* ptr) : IStatus
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public nint Address => (nint)ptr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint StatusId => ptr->StatusId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(ptr->StatusId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ushort Param => ptr->Param;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float RemainingTime => ptr->RemainingTime;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint SourceId => ptr->SourceObject.ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
||||
|
||||
public static bool operator ==(Status x, Status y) => x.Equals(y);
|
||||
|
||||
public static bool operator !=(Status x, Status y) => !(x == y);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IStatus? other)
|
||||
{
|
||||
return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is Status fate && this.Equals(fate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime);
|
||||
}
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||
using Dalamud.Game.Player;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public sealed unsafe partial class StatusList
|
|||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the status list.</param>
|
||||
internal StatusList(nint address)
|
||||
internal StatusList(IntPtr address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
|
@ -26,19 +26,19 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="pointer">Pointer to the status list.</param>
|
||||
internal unsafe StatusList(void* pointer)
|
||||
: this((nint)pointer)
|
||||
: this((IntPtr)pointer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the status list in memory.
|
||||
/// </summary>
|
||||
public nint Address { get; }
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of status effect slots the actor has.
|
||||
/// </summary>
|
||||
public int Length => this.Struct->NumValidStatuses;
|
||||
public int Length => Struct->NumValidStatuses;
|
||||
|
||||
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="index">Status Index.</param>
|
||||
/// <returns>The status at the specified index.</returns>
|
||||
public IStatus? this[int index]
|
||||
public Status? this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -66,7 +66,7 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="address">The address of the status list in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static StatusList? CreateStatusListReference(nint address)
|
||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||
{
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
|
@ -74,12 +74,8 @@ public sealed unsafe partial class StatusList
|
|||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||
// here or somewhere else.
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalContentId == 0)
|
||||
return null;
|
||||
|
||||
if (address == 0)
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
if (!playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
return new StatusList(address);
|
||||
|
|
@ -90,15 +86,16 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="address">The address of the status effect in memory.</param>
|
||||
/// <returns>The status object containing the requested data.</returns>
|
||||
public static IStatus? CreateStatusReference(nint address)
|
||||
public static Status? CreateStatusReference(IntPtr address)
|
||||
{
|
||||
if (address == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
if (address == 0)
|
||||
var playerState = Service<PlayerState>.Get();
|
||||
if (!playerState.IsLoaded)
|
||||
return null;
|
||||
|
||||
return new Status((CSStatus*)address);
|
||||
return new Status(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,22 +103,22 @@ public sealed unsafe partial class StatusList
|
|||
/// </summary>
|
||||
/// <param name="index">The index of the status.</param>
|
||||
/// <returns>The memory address of the status.</returns>
|
||||
public nint GetStatusAddress(int index)
|
||||
public IntPtr GetStatusAddress(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.Length)
|
||||
return 0;
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
||||
return (IntPtr)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection represents the status effects an actor is afflicted by.
|
||||
/// </summary>
|
||||
public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollection
|
||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<IStatus>.Count => this.Length;
|
||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
int ICollection.Count => this.Length;
|
||||
|
|
@ -133,9 +130,17 @@ public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollecti
|
|||
object ICollection.SyncRoot => this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<IStatus> GetEnumerator()
|
||||
public IEnumerator<Status> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
for (var i = 0; i < this.Length; i++)
|
||||
{
|
||||
var status = this[i];
|
||||
|
||||
if (status == null || status.StatusId == 0)
|
||||
continue;
|
||||
|
||||
yield return status;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -150,38 +155,4 @@ public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollecti
|
|||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Enumerator(StatusList statusList) : IEnumerator<IStatus>
|
||||
{
|
||||
private int index = -1;
|
||||
|
||||
public IStatus Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (++this.index < statusList.Length)
|
||||
{
|
||||
var status = statusList[this.index];
|
||||
if (status != null && status.StatusId != 0)
|
||||
{
|
||||
this.Current = status;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
Dalamud/Game/ClientState/Structs/StatusEffect.cs
Normal file
35
Dalamud/Game/ClientState/Structs/StatusEffect.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs;
|
||||
|
||||
/// <summary>
|
||||
/// Native memory representation of a FFXIV status effect.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The effect ID.
|
||||
/// </summary>
|
||||
public short EffectId;
|
||||
|
||||
/// <summary>
|
||||
/// How many stacks are present.
|
||||
/// </summary>
|
||||
public byte StackCount;
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters.
|
||||
/// </summary>
|
||||
public byte Param;
|
||||
|
||||
/// <summary>
|
||||
/// The duration remaining.
|
||||
/// </summary>
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the actor that caused this effect.
|
||||
/// </summary>
|
||||
public int OwnerId;
|
||||
}
|
||||
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