diff --git a/.editorconfig b/.editorconfig index 141e8c9c9..7b1cbadda 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,7 @@ charset = utf-8 end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true # 4 space indentation indent_style = space diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c564cc81..be44afacc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,6 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] + concurrency: group: build_dalamud_${{ github.ref_name }} cancel-in-progress: true @@ -22,7 +23,7 @@ jobs: uses: microsoft/setup-msbuild@v1.0.2 - uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.100' + dotnet-version: '9.0.200' - name: Define VERSION run: | $env:COMMIT = $env:GITHUB_SHA.Substring(0, 7) @@ -32,10 +33,8 @@ jobs: ($env:REPO_NAME) >> VERSION ($env:BRANCH) >> VERSION ($env:COMMIT) >> VERSION - - name: Build Dalamud - run: .\build.ps1 compile - - name: Test Dalamud - run: .\build.ps1 test + - name: Build and Test Dalamud + run: .\build.ps1 ci - name: Sign Dalamud if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }} env: @@ -55,8 +54,9 @@ jobs: bin/Release/Dalamud.*.dll bin/Release/Dalamud.*.exe bin/Release/FFXIVClientStructs.dll + bin/Release/cim*.dll - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: dalamud-artifact path: bin\Release @@ -75,7 +75,7 @@ jobs: run: | dotnet tool install -g Microsoft.DotNet.ApiCompat.Tool - name: "Download Proposed Artifacts" - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: dalamud-artifact path: .\right @@ -86,9 +86,9 @@ jobs: - name: "Verify Compatibility" run: | $FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll" - + $retcode = 0 - + foreach ($file in $FILES_TO_VALIDATE) { $testout = "" Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ===" @@ -99,7 +99,7 @@ jobs: $retcode = 1 } } - + exit $retcode deploy_stg: @@ -112,7 +112,7 @@ jobs: with: repository: goatcorp/dalamud-distrib token: ${{ secrets.UPDATE_PAT }} - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4.1.7 with: name: dalamud-artifact path: .\scratch @@ -128,18 +128,18 @@ jobs: GH_BRANCH: ${{ steps.extract_branch.outputs.branch }} run: | Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip - + $branchName = $env:GH_BRANCH - + if ($branchName -eq "master") { $branchName = "stg" } - + $newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt") $revision = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\revision.txt") $commitHash = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\commit_hash.txt") Remove-Item -Force -Recurse .\scratch - + if (Test-Path -Path $branchName) { $versionData = Get-Content ".\${branchName}\version" | ConvertFrom-Json $oldVersion = $versionData.AssemblyVersion @@ -158,7 +158,7 @@ jobs: Write-Host "Deployment folder doesn't exist. Not doing anything." Remove-Item .\canary.zip } - + - name: Commit changes shell: bash env: @@ -166,8 +166,8 @@ jobs: run: | git config --global user.name "Actions User" git config --global user.email "actions@github.com" - + git add . git commit -m "[CI] Update staging for ${DVER} on ${GH_BRANCH}" || true - + git push origin main || true diff --git a/.github/workflows/rollup.yml b/.github/workflows/rollup.yml index 70a8fc7b7..8bc9a3c51 100644 --- a/.github/workflows/rollup.yml +++ b/.github/workflows/rollup.yml @@ -1,8 +1,8 @@ name: Rollup changes to next version on: - push: - branches: - - master +# push: +# branches: +# - master workflow_dispatch: jobs: @@ -11,7 +11,7 @@ jobs: strategy: matrix: branches: - - WORKFLOW_DISABLED_REMOVE_BEFORE_RUNNING + - net9 defaults: run: diff --git a/.gitmodules b/.gitmodules index d379480d9..4aa969fda 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,12 @@ [submodule "lib/ImGui.NET"] path = lib/ImGui.NET url = https://github.com/goatcorp/ImGui.NET.git +[submodule "lib/cimgui"] + path = lib/cimgui + url = https://github.com/goatcorp/gc-cimgui +[submodule "lib/cimplot"] + path = lib/cimplot + url = https://github.com/goatcorp/gc-cimplot +[submodule "lib/cimguizmo"] + path = lib/cimguizmo + url = https://github.com/goatcorp/gc-cimguizmo diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 497f2b89a..8331affcc 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -76,14 +76,20 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", "CompileDalamud", "CompileDalamudBoot", "CompileDalamudCrashHandler", + "CompileImGuiNatives", "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } @@ -98,14 +104,20 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", "CompileDalamud", "CompileDalamudBoot", "CompileDalamudCrashHandler", + "CompileImGuiNatives", "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 80435cd67..c18045027 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -28,7 +28,7 @@ v143 false Unicode - ..\bin\$(Configuration)\ + bin\$(Configuration)\ obj\$(Configuration)\ @@ -200,8 +200,10 @@ - - - + + + + + - \ No newline at end of file + diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index f342973b0..5be8f97d0 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -109,6 +109,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory); config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory); config.Language = json.value("Language", config.Language); + config.Platform = json.value("Platform", config.Platform); config.GameVersion = json.value("GameVersion", config.GameVersion); config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{}); config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index d6524968e..0eeaddeed 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -17,7 +17,7 @@ struct DalamudStartInfo { DirectHook = 1, }; friend void from_json(const nlohmann::json&, DotNetOpenProcessHookMode&); - + enum class ClientLanguage : int { Japanese, English, @@ -47,6 +47,7 @@ struct DalamudStartInfo { std::string PluginDirectory; std::string AssetDirectory; ClientLanguage Language = ClientLanguage::English; + std::string Platform; std::string GameVersion; std::string TroubleshootingPackData; int DelayInitializeMs = 0; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index d191c9e97..5e73962ec 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -15,7 +15,7 @@ HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr); HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { g_startInfo.from_envvars(); - + std::string jsonParseError; try { from_json(nlohmann::json::parse(std::string_view(static_cast(lpParam))), g_startInfo); @@ -25,7 +25,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { if (g_startInfo.BootShowConsole) ConsoleSetup(L"Dalamud Boot"); - + logging::update_dll_load_status(true); const auto logFilePath = unicode::convert(g_startInfo.BootLogPath); @@ -33,16 +33,16 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { auto attemptFallbackLog = false; if (logFilePath.empty()) { attemptFallbackLog = true; - + logging::I("No log file path given; not logging to file."); } else { try { logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole); logging::I("Logging to file: {}", logFilePath); - + } catch (const std::exception& e) { attemptFallbackLog = true; - + logging::E("Couldn't open log file: {}", logFilePath); logging::E("Error: {} / {}", errno, e.what()); } @@ -63,15 +63,15 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { SYSTEMTIME st; GetLocalTime(&st); logFilePath += std::format(L"Dalamud.Boot.{:04}{:02}{:02}.{:02}{:02}{:02}.{:03}.{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId()); - + try { logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole); logging::I("Logging to fallback log file: {}", logFilePath); - + } catch (const std::exception& e) { if (!g_startInfo.BootShowConsole && !g_startInfo.BootDisableFallbackConsole) ConsoleSetup(L"Dalamud Boot - Fallback Console"); - + logging::E("Couldn't open fallback log file: {}", logFilePath); logging::E("Error: {} / {}", errno, e.what()); } @@ -87,7 +87,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { } else { logging::E("Failed to initialize MinHook (status={}({}))", MH_StatusToString(mhStatus), static_cast(mhStatus)); } - + logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors"); logging::I("Built at: " __DATE__ "@" __TIME__); @@ -241,11 +241,11 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese case DLL_PROCESS_DETACH: // do not show debug message boxes on abort() here _set_abort_behavior(0, _WRITE_ABORT_MSG); - + // process is terminating; don't bother cleaning up if (lpReserved) return TRUE; - + logging::update_dll_load_status(false); xivfixes::apply_all(false); diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index 65018add4..dbfcf39ee 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "DalamudStartInfo.h" #include "utils.h" @@ -103,7 +104,7 @@ bool utils::loaded_module::find_imported_function_pointer(const char* pcszDllNam ppFunctionAddress = nullptr; // This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries. - for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof IMAGE_IMPORT_DESCRIPTOR)) { + for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR))) { // Having all zero values signals the end of the table. We didn't find anything. if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk) @@ -584,6 +585,10 @@ std::vector utils::get_env_list(const wchar_t* pcszName) { return res; } +bool utils::is_running_on_wine() { + return g_startInfo.Platform != "WINDOWS"; +} + std::filesystem::path utils::get_module_path(HMODULE hModule) { std::wstring buf(MAX_PATH, L'\0'); while (true) { diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index f10e277c0..2cdaf60a7 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -121,7 +121,7 @@ namespace utils { memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect); template&& std::is_standard_layout_v>> - memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {} + memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof(T), dwNewProtect) {} template memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {} @@ -267,6 +267,8 @@ namespace utils { return get_env_list(unicode::convert(pcszName).c_str()); } + bool is_running_on_wine(); + std::filesystem::path get_module_path(HMODULE hModule); /// @brief Find the game main window. diff --git a/Dalamud.Common/Dalamud.Common.csproj b/Dalamud.Common/Dalamud.Common.csproj index 594e09021..54a182210 100644 --- a/Dalamud.Common/Dalamud.Common.csproj +++ b/Dalamud.Common/Dalamud.Common.csproj @@ -1,7 +1,6 @@ - net8.0 enable enable diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index b27d4b00d..a0d7f8b0b 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Dalamud.Common.Game; using Newtonsoft.Json; @@ -15,7 +16,7 @@ public record DalamudStartInfo /// public DalamudStartInfo() { - // ignored + this.Platform = OSPlatform.Create("UNKNOWN"); } /// @@ -58,6 +59,12 @@ public record DalamudStartInfo /// public ClientLanguage Language { get; set; } = ClientLanguage.English; + /// + /// Gets or sets the underlying platform�Dalamud runs on. + /// + [JsonConverter(typeof(OSPlatformConverter))] + public OSPlatform Platform { get; set; } + /// /// Gets or sets the current game version code. /// @@ -125,7 +132,7 @@ public record DalamudStartInfo public bool BootVehFull { get; set; } /// - /// Gets or sets a value indicating whether or not ETW should be enabled. + /// Gets or sets a value indicating whether ETW should be enabled. /// public bool BootEnableEtw { get; set; } diff --git a/Dalamud.Common/OSPlatformConverter.cs b/Dalamud.Common/OSPlatformConverter.cs new file mode 100644 index 000000000..62d2996d4 --- /dev/null +++ b/Dalamud.Common/OSPlatformConverter.cs @@ -0,0 +1,78 @@ +using System.Runtime.InteropServices; +using Newtonsoft.Json; + +namespace Dalamud.Common; + +/// +/// Converts a to and from a string (e.g. "FreeBSD"). +/// +public sealed class OSPlatformConverter : JsonConverter +{ + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else if (value is OSPlatform) + { + writer.WriteValue(value.ToString()); + } + else + { + throw new JsonSerializationException("Expected OSPlatform object value"); + } + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + else + { + if (reader.TokenType == JsonToken.String) + { + try + { + return OSPlatform.Create((string)reader.Value!); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex); + } + } + else + { + throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. Token: {reader.TokenType}, Value: {reader.Value}"); + } + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(OSPlatform); + } +} diff --git a/Dalamud.Common/Util/EnvironmentUtils.cs b/Dalamud.Common/Util/EnvironmentUtils.cs new file mode 100644 index 000000000..d6cf65e3d --- /dev/null +++ b/Dalamud.Common/Util/EnvironmentUtils.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Common.Util; + +public static class EnvironmentUtils +{ + /// + /// Attempts to get an environment variable using the Try pattern. + /// + /// The env var to get. + /// An output containing the env var, if present. + /// A boolean indicating whether the var was present. + public static bool TryGetEnvironmentVariable(string variableName, [NotNullWhen(true)] out string? value) + { + value = Environment.GetEnvironmentVariable(variableName); + return value != null; + } +} diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index b9bc63cd1..f51622c7e 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -1,9 +1,7 @@ Dalamud.CorePlugin - net8.0-windows x64 - 10.0 true false false @@ -27,9 +25,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 1ff29ea66..c8b648f6d 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -1,11 +1,7 @@ - net8.0 win-x64 - x64 - x64;AnyCPU - 10.0 @@ -45,10 +41,6 @@ DEBUG;TRACE - - $(MSBuildProjectDirectory)\ - $(AppOutputBase)=C:\goatsoft\companysecrets\injector\ - IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702 diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 54025d5e2..97cb1fd9e 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -11,6 +11,8 @@ using System.Text.RegularExpressions; using Dalamud.Common; using Dalamud.Common.Game; +using Dalamud.Common.Util; + using Newtonsoft.Json; using Reloaded.Memory.Buffers; using Serilog; @@ -95,6 +97,7 @@ namespace Dalamud.Injector args.Remove("--msgbox2"); args.Remove("--msgbox3"); args.Remove("--etw"); + args.Remove("--no-legacy-corrupted-state-exceptions"); args.Remove("--veh"); args.Remove("--veh-full"); args.Remove("--no-plugin"); @@ -263,6 +266,35 @@ namespace Dalamud.Injector } } + private static OSPlatform DetectPlatformHeuristic() + { + var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll"); + var wineServerCallPtr = NativeFunctions.GetProcAddress(ntdll, "wine_server_call"); + var wineGetHostVersionPtr = NativeFunctions.GetProcAddress(ntdll, "wine_get_host_version"); + var winePlatform = GetWinePlatform(wineGetHostVersionPtr); + var isWine = wineServerCallPtr != nint.Zero; + + static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr) + { + if (wineGetHostVersionPtr == nint.Zero) return null; + + var methodDelegate = (delegate* unmanaged[Cdecl])wineGetHostVersionPtr; + methodDelegate(out var platformPtr, out var _); + + if (platformPtr == null) return null; + + return Marshal.PtrToStringAnsi((nint)platformPtr); + } + + if (!isWine) + return OSPlatform.Windows; + + if (winePlatform == "Darwin") + return OSPlatform.OSX; + + return OSPlatform.Linux; + } + private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List args) { int len; @@ -278,9 +310,14 @@ namespace Dalamud.Injector var logName = startInfo.LogName; var logPath = startInfo.LogPath; var languageStr = startInfo.Language.ToString().ToLowerInvariant(); + var platformStr = startInfo.Platform.ToString().ToLowerInvariant(); var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; + // env vars are brought in prior to launch args, since args can override them. + if (EnvironmentUtils.TryGetEnvironmentVariable("XL_PLATFORM", out var xlPlatformEnv)) + platformStr = xlPlatformEnv.ToLowerInvariant(); + for (var i = 2; i < args.Count; i++) { if (args[i].StartsWith(key = "--dalamud-working-directory=")) @@ -307,6 +344,10 @@ namespace Dalamud.Injector { languageStr = args[i][key.Length..].ToLowerInvariant(); } + else if (args[i].StartsWith(key = "--dalamud-platform=")) + { + platformStr = args[i][key.Length..].ToLowerInvariant(); + } else if (args[i].StartsWith(key = "--dalamud-tspack-b64=")) { troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..])); @@ -378,11 +419,37 @@ namespace Dalamud.Injector throw new CommandLineException($"\"{languageStr}\" is not a valid supported language."); } + OSPlatform platform; + + // covers both win32 and Windows + if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len]) + { + platform = OSPlatform.Windows; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len]) + { + platform = OSPlatform.Linux; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len]) + { + platform = OSPlatform.OSX; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len]) + { + platform = OSPlatform.OSX; + } + else + { + platform = DetectPlatformHeuristic(); + Log.Warning("Heuristically determined host system platform as {platform}", platform); + } + startInfo.WorkingDirectory = workingDirectory; startInfo.ConfigurationPath = configurationPath; startInfo.PluginDirectory = pluginDirectory; startInfo.AssetDirectory = assetDirectory; startInfo.Language = clientLanguage; + startInfo.Platform = platform; startInfo.DelayInitializeMs = delayInitializeMs; startInfo.GameVersion = null; startInfo.TroubleshootingPackData = troubleshootingData; @@ -465,13 +532,14 @@ namespace Dalamud.Injector } Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]"); - Console.WriteLine(" [--dalamud-plugin-directory=path]"); + Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]"); Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]"); Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]"); Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]"); Console.WriteLine("Enable ETW:\t[--etw]"); + Console.WriteLine("Disable legacy corrupted state exceptions:\t[--no-legacy-corrupted-state-exceptions]"); Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]"); Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]"); Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]"); @@ -731,15 +799,42 @@ namespace Dalamud.Injector { try { - var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); - var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); - gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe"); - Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + if (dalamudStartInfo.Platform == OSPlatform.Windows) + { + var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); + var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); + gamePath = Path.Combine( + JsonSerializer.CreateDefault() + .Deserialize>( + new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], + "game", + "ffxiv_dx11.exe"); + Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + } + else if (dalamudStartInfo.Platform == OSPlatform.Linux) + { + var homeDir = $"Z:\\home\\{Environment.UserName}"; + var xivlauncherDir = Path.Combine(homeDir, ".xlcore"); + var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini"); + var config = File.ReadAllLines(launcherConfigPath) + .Where(line => line.Contains('=')) + .ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]); + gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe"); + Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath); + } + else + { + var homeDir = $"Z:\\Users\\{Environment.UserName}"; + var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac"); + // we could try to parse the binary plist file here if we really wanted to... + gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe"); + Log.Information("Using default game installation path from XOM: {0}", gamePath); + } } catch (Exception) { - Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g"); + Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g"); return -1; } @@ -794,20 +889,6 @@ namespace Dalamud.Injector if (encryptArguments) { var rawTickCount = (uint)Environment.TickCount; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - [System.Runtime.InteropServices.DllImport("c")] -#pragma warning disable SA1300 - static extern ulong clock_gettime_nsec_np(int clockId); -#pragma warning restore SA1300 - - const int CLOCK_MONOTONIC_RAW = 4; - var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000; - Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed); - rawTickCount = (uint)rawTickCountFixed; - } - var ticks = rawTickCount & 0xFFFF_FFFFu; var key = ticks & 0xFFFF_0000u; gameArguments.Insert(0, $"T={ticks}"); diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index 2a4654aaf..06add3acc 100644 --- a/Dalamud.Injector/NativeFunctions.cs +++ b/Dalamud.Injector/NativeFunctions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Dalamud.Injector @@ -910,5 +911,46 @@ namespace Dalamud.Injector uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. + /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To + /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. + /// + /// + /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default + /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate + /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure + /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules + /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns + /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve + /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return + /// value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetModuleHandleW(string lpModuleName); + + /// + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// + /// + /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, + /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules + /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be + /// in the low-order word; the high-order word must be zero. + /// + /// + /// If the function succeeds, the return value is the address of the exported function or variable. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); } } diff --git a/Dalamud.Test/Dalamud.Test.csproj b/Dalamud.Test/Dalamud.Test.csproj index 28e326238..c6c8f8e8a 100644 --- a/Dalamud.Test/Dalamud.Test.csproj +++ b/Dalamud.Test/Dalamud.Test.csproj @@ -1,13 +1,5 @@ - - net8.0-windows - win-x64 - x64 - x64;AnyCPU - 11.0 - - Dalamud.Test Dalamud.Test diff --git a/Dalamud.sln.DotSettings b/Dalamud.sln.DotSettings index b0f66b736..6d0e1fdcd 100644 --- a/Dalamud.sln.DotSettings +++ b/Dalamud.sln.DotSettings @@ -54,6 +54,7 @@ True True True + True True True True @@ -66,6 +67,7 @@ True True True + True True True True diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 5b49f5c72..515556b7e 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Dalamud.Game.Text; using Dalamud.Interface; @@ -11,6 +12,7 @@ using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.Style; +using Dalamud.Interface.Windowing.Persistence; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.AutoUpdate; using Dalamud.Plugin.Internal.Profiles; @@ -45,6 +47,8 @@ internal sealed class DalamudConfiguration : IInternalDisposableService [JsonIgnore] private bool isSaveQueued; + private Task? writeTask; + /// /// Delegate for the event that occurs when the dalamud configuration is saved. /// @@ -57,7 +61,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved; /// - /// Gets or sets a list of muted works. + /// Gets or sets a list of muted words. /// public List? BadWords { get; set; } @@ -243,13 +247,13 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. /// - public bool AssertsEnabledAtStartup { get; set; } + public bool? ImGuiAssertsEnabledAtStartup { get; set; } /// /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. /// public bool IsDocking { get; set; } - + /// /// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects. /// This setting is effected by the in-game "System Sounds" option and volume. @@ -261,8 +265,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown /// on plugin title bars when using the Window System. /// - [JsonProperty("EnablePluginUiAdditionalOptionsExperimental")] - public bool EnablePluginUiAdditionalOptions { get; set; } = false; + public bool EnablePluginUiAdditionalOptions { get; set; } = true; /// /// Gets or sets a value indicating whether viewports should always be disabled. @@ -348,6 +351,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// public bool ProfilesHasSeenTutorial { get; set; } = false; + /// + /// Gets or sets the default UI preset. + /// + public PresetModel DefaultUiPreset { get; set; } = new(); + /// /// Gets or sets the order of DTR elements, by title. /// @@ -484,10 +492,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService public AutoUpdateBehavior? AutoUpdateBehavior { get; set; } = null; /// - /// Gets or sets a value indicating whether or not users should be notified regularly about pending updates. + /// Gets or sets a value indicating whether users should be notified regularly about pending updates. /// public bool CheckPeriodicallyForUpdates { get; set; } = true; + /// + /// Gets or sets a value indicating whether users should be notified about updates in chat. + /// + public bool SendUpdateNotificationToChat { get; set; } = false; + /// /// Load a configuration from the provided path. /// @@ -504,7 +517,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { deserialized = JsonConvert.DeserializeObject(text, SerializerSettings); - + // If this reads as null, the file was empty, that's no good if (deserialized == null) throw new Exception("Read config was null."); @@ -530,7 +543,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { Log.Error(e, "Failed to set defaults for DalamudConfiguration"); } - + return deserialized; } @@ -549,12 +562,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { this.Save(); } - + /// void IInternalDisposableService.DisposeService() { // Make sure that we save, if a save is queued while we are shutting down this.Update(); + + // Wait for the write task to finish + this.writeTask?.Wait(); } /// @@ -595,22 +611,36 @@ internal sealed class DalamudConfiguration : IInternalDisposableService this.ReduceMotions = winAnimEnabled == 0; } } - + // Migrate old auto-update setting to new auto-update behavior this.AutoUpdateBehavior ??= this.AutoUpdatePlugins ? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll : Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify; #pragma warning restore CS0618 } - + private void Save() { ThreadSafety.AssertMainThread(); if (this.configPath is null) throw new InvalidOperationException("configPath is not set."); - Service.Get().WriteAllText( - this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); + // Wait for previous write to finish + this.writeTask?.Wait(); + + this.writeTask = Task.Run(() => + { + Service.Get().WriteAllText( + this.configPath, + JsonConvert.SerializeObject(this, SerializerSettings)); + }).ContinueWith(t => + { + if (t.IsFaulted) + { + Log.Error(t.Exception, "Failed to save DalamudConfiguration to {Path}", this.configPath); + } + }); + this.DalamudConfigurationSaved?.Invoke(this); } } diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index 2df9ec5fe..11a8d3567 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal; /// internal class EnvironmentConfiguration { - /// - /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. - /// - public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); - /// /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. /// diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index d080a1622..7cc5bae96 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -1,16 +1,12 @@ - net8.0-windows - x64 - x64;AnyCPU - 12.0 True - 10.0.0.7 XIV Launcher addon framework + 12.0.0.7 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) @@ -47,10 +43,6 @@ DEBUG;TRACE - - $(MSBuildProjectDirectory)\ - $(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\ - IDE0002;IDE0003;IDE1006;IDE0044;CA1822;CS1591;CS1701;CS1702 @@ -71,8 +63,8 @@ - - + + all @@ -83,12 +75,13 @@ - - - - + + + + + all @@ -113,6 +106,7 @@ + @@ -126,6 +120,13 @@ + + + + + + + @@ -165,7 +166,7 @@ - + @@ -173,7 +174,7 @@ ??? - + diff --git a/Dalamud/DalamudAsset.cs b/Dalamud/DalamudAsset.cs index 0d91a4b75..27771116e 100644 --- a/Dalamud/DalamudAsset.cs +++ b/Dalamud/DalamudAsset.cs @@ -9,6 +9,7 @@ namespace Dalamud; /// Any asset can cease to exist at any point, even if the enum value exists.
/// Either ship your own assets, or be prepared for errors. /// +// Implementation notes: avoid specifying numbers too high here. Lookup table is currently implemented as an array. public enum DalamudAsset { /// diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 58eb930a0..d017bf85a 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; using Dalamud.Game; using Dalamud.IoC; @@ -10,6 +11,7 @@ using Dalamud.Utility.Timing; using Lumina; using Lumina.Data; using Lumina.Excel; + using Newtonsoft.Json; using Serilog; @@ -27,12 +29,15 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { private readonly Thread luminaResourceThread; private readonly CancellationTokenSource luminaCancellationTokenSource; + private readonly RsvResolver rsvResolver; [ServiceManager.ServiceConstructor] private DataManager(Dalamud dalamud) { this.Language = (ClientLanguage)dalamud.StartInfo.Language; + this.rsvResolver = new(); + try { Log.Verbose("Starting data load..."); @@ -43,11 +48,8 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { LoadMultithreaded = true, CacheFileResources = true, -#if NEVER // Lumina bug PanicOnSheetChecksumMismatch = true, -#else - PanicOnSheetChecksumMismatch = false, -#endif + RsvResolver = this.rsvResolver.TryResolve, DefaultExcelLanguage = this.Language.ToLumina(), }; @@ -128,12 +130,12 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager #region Lumina Wrappers /// - public ExcelSheet? GetExcelSheet() where T : ExcelRow - => this.Excel.GetSheet(); + public ExcelSheet GetExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow + => this.Excel.GetSheet(language?.ToLumina(), name); /// - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow - => this.Excel.GetSheet(language.ToLumina()); + public SubrowExcelSheet GetSubrowExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelSubrow + => this.Excel.GetSubrowSheet(language?.ToLumina(), name); /// public FileResource? GetFile(string path) @@ -148,6 +150,16 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; } + /// + public Task GetFileAsync(string path, CancellationToken cancellationToken) where T : FileResource => + GameData.ParseFilePath(path) is { } filePath && + this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) + ? Task.Run( + () => repository.GetFile(filePath.Category, filePath) ?? throw new FileNotFoundException( + "Failed to load file, most likely because the file could not be found."), + cancellationToken) + : Task.FromException(new FileNotFoundException("The file could not be found.")); + /// public bool FileExists(string path) => this.GameData.FileExists(path); @@ -159,6 +171,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { this.luminaCancellationTokenSource.Cancel(); this.GameData.Dispose(); + this.rsvResolver.Dispose(); } private class LauncherTroubleshootingInfo diff --git a/Dalamud/Data/LuminaUtils.cs b/Dalamud/Data/LuminaUtils.cs new file mode 100644 index 000000000..6da67f426 --- /dev/null +++ b/Dalamud/Data/LuminaUtils.cs @@ -0,0 +1,22 @@ +using Lumina.Excel; + +namespace Dalamud.Data; + +/// +/// A helper class to easily resolve Lumina data within Dalamud. +/// +internal static class LuminaUtils +{ + private static ExcelModule Module => Service.Get().Excel; + + /// + /// Initializes a new instance of the class using the default . + /// + /// The type of Lumina sheet to resolve. + /// The id of the row to resolve. + /// A new object. + public static RowRef CreateRef(uint rowId) where T : struct, IExcelRow + { + return new(Module, rowId); + } +} diff --git a/Dalamud/Data/RsvResolver.cs b/Dalamud/Data/RsvResolver.cs new file mode 100644 index 000000000..3f507ff1d --- /dev/null +++ b/Dalamud/Data/RsvResolver.cs @@ -0,0 +1,51 @@ +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; + +/// +/// Provides functionality for resolving RSV strings. +/// +internal sealed unsafe class RsvResolver : IDisposable +{ + private static readonly ModuleLog Log = new("RsvProvider"); + + private readonly Hook addRsvStringHook; + + /// + /// Initializes a new instance of the class. + /// + public RsvResolver() + { + this.addRsvStringHook = Hook.FromAddress((nint)LayoutWorld.MemberFunctionPointers.AddRsvString, this.AddRsvStringDetour); + + this.addRsvStringHook.Enable(); + } + + private Dictionary Lookup { get; } = []; + + /// Attemps to resolve an RSV string. + /// + public bool TryResolve(ReadOnlySeString rsvString, out ReadOnlySeString resolvedString) => + this.Lookup.TryGetValue(rsvString, out resolvedString); + + /// + public void Dispose() + { + this.addRsvStringHook.Dispose(); + } + + private bool AddRsvStringDetour(LayoutWorld* @this, byte* rsvString, byte* resolvedString, nuint resolvedStringSize) + { + var rsv = new ReadOnlySeString(MemoryHelper.ReadRawNullTerminated((nint)rsvString)); + var resolved = new ReadOnlySeString(new ReadOnlySpan(resolvedString, (int)resolvedStringSize).ToArray()); + Log.Debug($"Resolving RSV \"{rsv}\" to \"{resolved}\"."); + this.Lookup[rsv] = resolved; + return this.addRsvStringHook.Original(@this, rsvString, resolvedString, resolvedStringSize); + } +} diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 512acd4cc..f6ba990e6 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -179,16 +179,17 @@ public sealed class EntryPoint Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory); - // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; + // Apply common fixes for culture issues + CultureFixes.Apply(); - if (!Util.IsWine()) + // Currently VEH is not fully functional on WINE + if (info.Platform != OSPlatform.Windows) InitSymbolHandler(info); var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent); - Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", - Util.GetScmVersion(), - Util.GetGitHashClientStructs(), + Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", + Util.GetScmVersion(), + Util.GetGitHashClientStructs(), FFXIVClientStructs.ThisAssembly.Git.Commits); dalamud.WaitForUnload(); diff --git a/Dalamud/Game/ActionKind.cs b/Dalamud/Game/ActionKind.cs new file mode 100644 index 000000000..9a574f9a8 --- /dev/null +++ b/Dalamud/Game/ActionKind.cs @@ -0,0 +1,89 @@ +namespace Dalamud.Game; + +/// +/// Enum describing possible action kinds. +/// +public enum ActionKind +{ + /// + /// A Trait. + /// + Trait = 0, + + /// + /// An Action. + /// + Action = 1, + + /// + /// A usable Item. + /// + Item = 2, // does not work? + + /// + /// A usable EventItem. + /// + EventItem = 3, // does not work? + + /// + /// An EventAction. + /// + EventAction = 4, + + /// + /// A GeneralAction. + /// + GeneralAction = 5, + + /// + /// A BuddyAction. + /// + BuddyAction = 6, + + /// + /// A MainCommand. + /// + MainCommand = 7, + + /// + /// A Companion. + /// + Companion = 8, // unresolved?! + + /// + /// A CraftAction. + /// + CraftAction = 9, + + /// + /// An Action (again). + /// + Action2 = 10, // what's the difference? + + /// + /// A PetAction. + /// + PetAction = 11, + + /// + /// A CompanyAction. + /// + CompanyAction = 12, + + /// + /// A Mount. + /// + Mount = 13, + + // 14-18 unused + + /// + /// A BgcArmyAction. + /// + BgcArmyAction = 19, + + /// + /// An Ornament. + /// + Ornament = 20, +} diff --git a/Dalamud/Game/Addon/Events/AddonEventListener.cs b/Dalamud/Game/Addon/Events/AddonEventListener.cs index cc6416fe8..515785d72 100644 --- a/Dalamud/Game/Addon/Events/AddonEventListener.cs +++ b/Dalamud/Game/Addon/Events/AddonEventListener.cs @@ -11,9 +11,9 @@ namespace Dalamud.Game.Addon.Events; internal unsafe class AddonEventListener : IDisposable { private ReceiveEventDelegate? receiveEventDelegate; - + private AtkEventListener* eventListener; - + /// /// Initializes a new instance of the class. /// @@ -24,7 +24,7 @@ internal unsafe class AddonEventListener : IDisposable this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener)); this.eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)Marshal.AllocHGlobal(sizeof(void*) * 3); - this.eventListener->VirtualTable->Dtor = (delegate* unmanaged)(delegate* unmanaged)&NullSub; + this.eventListener->VirtualTable->Dtor = (delegate* unmanaged)(delegate* unmanaged)&NullSub; this.eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged)(delegate* unmanaged)&NullSub; this.eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate); } @@ -38,17 +38,17 @@ internal unsafe class AddonEventListener : IDisposable /// Pointer to the AtkEvent. /// Pointer to the AtkEventData. public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr); - + /// /// Gets the address of this listener. /// public nint Address => (nint)this.eventListener; - + /// public void Dispose() { if (this.eventListener is null) return; - + Marshal.FreeHGlobal((nint)this.eventListener->VirtualTable); Marshal.FreeHGlobal((nint)this.eventListener); @@ -88,7 +88,7 @@ internal unsafe class AddonEventListener : IDisposable node->RemoveEvent(eventType, param, this.eventListener, false); }); } - + [UnmanagedCallersOnly] private static void NullSub() { diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs index 927ed87ab..415e1b169 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs @@ -16,6 +16,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. 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"); + 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 } } diff --git a/Dalamud/Game/Addon/Events/PluginEventController.cs b/Dalamud/Game/Addon/Events/PluginEventController.cs index d33e2e253..403a812db 100644 --- a/Dalamud/Game/Addon/Events/PluginEventController.cs +++ b/Dalamud/Game/Addon/Events/PluginEventController.cs @@ -165,7 +165,7 @@ internal unsafe class PluginEventController : IDisposable { var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey; var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address; - var eventTypeMatches = currentEvent->Type == eventType; + var eventTypeMatches = currentEvent->State.EventType == eventType; if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches) { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs index 91b9dd51f..5fd0ac964 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs @@ -52,7 +52,7 @@ public enum AddonEvent PostDraw, /// - /// An event that is fired immediately before an addon is finalized via and + /// An event that is fired immediately before an addon is finalized via and /// destroyed. After this event, the addon will destruct its UI node data as well as free any allocated memory. /// This event can be used for cleanup and tracking tasks. /// diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs index baf8bb86c..854d666fd 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs @@ -13,19 +13,19 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver /// This is called for a majority of all addon OnSetup's. /// public nint AddonSetup { get; private set; } - + /// /// 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. /// public nint AddonSetup2 { get; private set; } - + /// /// Gets the address of the addon finalize hook invoked by the AtkUnitManager. /// public nint AddonFinalize { get; private set; } - + /// /// Gets the address of the addon draw hook invoked by virtual function call. /// @@ -35,7 +35,7 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver /// Gets the address of the addon update hook invoked by virtual function call. /// public nint AddonUpdate { get; private set; } - + /// /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call. /// @@ -51,6 +51,6 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5"); this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2"); - this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20"); + this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30"); } } diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index a41c6ff7a..c40744ca4 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -1,22 +1,14 @@ -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using CheapLoc; + using Dalamud.Configuration.Internal; -using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal; using Dalamud.Utility; @@ -27,49 +19,10 @@ namespace Dalamud.Game; /// Chat events and public helper functions. /// [ServiceManager.EarlyLoadedService] -internal class ChatHandlers : IServiceType +internal partial class ChatHandlers : IServiceType { - private static readonly ModuleLog Log = new("CHATHANDLER"); + private static readonly ModuleLog Log = new("ChatHandlers"); - private readonly Dictionary retainerSaleRegexes = new() - { - { - ClientLanguage.Japanese, - new Regex[] - { - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.English, - new Regex[] - { - new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.German, - new Regex[] - { - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.French, - new Regex[] - { - new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled), - } - }, - }; - - private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); - - [ServiceManager.ServiceDependency] - private readonly Dalamud dalamud = Service.Get(); - [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -92,6 +45,9 @@ internal class ChatHandlers : IServiceType /// public bool IsAutoUpdateComplete { get; private set; } + [GeneratedRegex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled)] + private static partial Regex CompiledUrlRegex(); + private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) { var textVal = message.TextValue; @@ -100,7 +56,7 @@ internal class ChatHandlers : IServiceType this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) { // This seems to be in the user block list - let's not show it - Log.Debug("Blocklist triggered"); + Log.Debug("Filtered a message that contained a muted word"); isHandled = true; return; } @@ -127,41 +83,10 @@ internal class ChatHandlers : IServiceType return; #endif - if (type == XivChatType.RetainerSale) - { - foreach (var regex in this.retainerSaleRegexes[(ClientLanguage)this.dalamud.StartInfo.Language]) - { - var matchInfo = regex.Match(message.TextValue); - - // we no longer really need to do/validate the item matching since we read the id from the byte array - // but we'd be checking the main match anyway - var itemInfo = matchInfo.Groups["item"]; - if (!itemInfo.Success) - continue; - - var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; - if (itemLink == default) - { - Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); - break; - } - - Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); - - var valueInfo = matchInfo.Groups["value"]; - // not sure if using a culture here would work correctly, so just strip symbols instead - if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) - continue; - - // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); - break; - } - } - var messageCopy = message; var senderCopy = sender; - var linkMatch = this.urlRegex.Match(message.TextValue); + var linkMatch = CompiledUrlRegex().Match(message.TextValue); if (linkMatch.Value.Length > 0) this.LastLink = linkMatch.Value; } diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs index 058d6c0c2..244989476 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs @@ -1,6 +1,9 @@ -using Dalamud.Game.ClientState.Resolvers; +using Dalamud.Data; + using FFXIVClientStructs.FFXIV.Client.Game.UI; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Aetherytes; /// @@ -56,7 +59,7 @@ public interface IAetheryteEntry /// /// Gets the Aetheryte data related to this aetheryte. /// - ExcelResolver AetheryteData { get; } + RowRef AetheryteData { get; } } /// @@ -103,5 +106,5 @@ internal sealed class AetheryteEntry : IAetheryteEntry public bool IsApartment => this.data.IsApartment; /// - public ExcelResolver AetheryteData => new(this.AetheryteId); + public RowRef AetheryteData => LuminaUtils.CreateRef(this.AetheryteId); } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index db9b8e562..84cfd24a3 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -1,14 +1,12 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using FFXIVClientStructs.FFXIV.Client.Game.UI; namespace Dalamud.Game.ClientState.Buddy; @@ -28,14 +26,9 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private BuddyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}"); } /// @@ -76,14 +69,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList } } - /// - /// Gets the address of the buddy list. - /// - internal IntPtr BuddyListAddress => this.address.BuddyList; - - private static int BuddyMemberSize { get; } = Marshal.SizeOf(); - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy; /// public IBuddyMember? this[int index] diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs index a650a7b44..025de611d 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -1,6 +1,8 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; + +using Lumina.Excel; namespace Dalamud.Game.ClientState.Buddy; @@ -45,17 +47,17 @@ public interface IBuddyMember /// /// Gets the Mount data related to this buddy. It should only be used with companion buddies. /// - ExcelResolver MountData { get; } + RowRef MountData { get; } /// /// Gets the Pet data related to this buddy. It should only be used with pet buddies. /// - ExcelResolver PetData { get; } + RowRef PetData { get; } /// /// Gets the Trust data related to this buddy. It should only be used with battle buddies. /// - ExcelResolver TrustData { get; } + RowRef TrustData { get; } } /// @@ -94,13 +96,13 @@ internal unsafe class BuddyMember : IBuddyMember public uint DataID => this.Struct->DataId; /// - public ExcelResolver MountData => new(this.DataID); + public RowRef MountData => LuminaUtils.CreateRef(this.DataID); /// - public ExcelResolver PetData => new(this.DataID); + public RowRef PetData => LuminaUtils.CreateRef(this.DataID); /// - public ExcelResolver TrustData => new(this.DataID); + public RowRef TrustData => LuminaUtils.CreateRef(this.DataID); private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index b31bcf780..da9873d8d 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Runtime.InteropServices; using Dalamud.Data; using Dalamud.Game.ClientState.Conditions; @@ -13,10 +12,15 @@ using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Event; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Action = System.Action; @@ -29,22 +33,23 @@ namespace Dalamud.Game.ClientState; internal sealed class ClientState : IInternalDisposableService, IClientState { private static readonly ModuleLog Log = new("ClientState"); - + private readonly GameLifecycle lifecycle; private readonly ClientStateAddressResolver address; - private readonly Hook setupTerritoryTypeHook; + private readonly Hook setupTerritoryTypeHook; + private readonly Hook uiModuleHandlePacketHook; + private readonly Hook onLogoutHook; [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly NetworkHandlers networkHandlers = Service.Get(); - + private bool lastConditionNone = true; - private bool lastFramePvP; [ServiceManager.ServiceConstructor] - private ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) + private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) { this.lifecycle = lifecycle; this.address = new ClientStateAddressResolver(); @@ -54,28 +59,37 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language; - Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}"); + var setTerritoryTypeAddr = EventFramework.Addresses.SetTerritoryTypeId.Value; + Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(setTerritoryTypeAddr)}"); - this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + this.setupTerritoryTypeHook = Hook.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour); + this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); + this.onLogoutHook = Hook.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); this.framework.Update += this.FrameworkOnOnUpdateEvent; - this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); + this.uiModuleHandlePacketHook.Enable(); + this.onLogoutHook.Enable(); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); + private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); /// public event Action? TerritoryChanged; + /// + public event IClientState.ClassJobChangeDelegate? ClassJobChanged; + + /// + public event IClientState.LevelChangeDelegate? LevelChanged; + /// public event Action? Login; /// - public event Action? Logout; + public event IClientState.LogoutDelegate? Logout; /// public event Action? EnterPvP; @@ -98,7 +112,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState get { var agentMap = AgentMap.Instance(); - return agentMap != null ? AgentMap.Instance()->CurrentMapId : 0; + return agentMap != null ? agentMap->CurrentMapId : 0; } } @@ -106,16 +120,23 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public IPlayerCharacter? LocalPlayer => Service.GetNullable()?[0] as IPlayerCharacter; /// - public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); + public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId; /// - public bool IsLoggedIn { get; private set; } + public unsafe bool IsLoggedIn + { + get + { + var agentLobby = AgentLobby.Instance(); + return agentLobby != null && agentLobby->IsLoggedIn; + } + } /// public bool IsPvP { get; private set; } /// - public bool IsPvPExcludingDen { get; private set; } + public bool IsPvPExcludingDen => this.IsPvP && this.TerritoryType != 250; /// public bool IsGPosing => GameMain.IsInGPose(); @@ -124,25 +145,25 @@ internal sealed class ClientState : IInternalDisposableService, IClientState /// Gets client state address resolver. /// internal ClientStateAddressResolver AddressResolver => this.address; - + /// public bool IsClientIdle(out ConditionFlag blockingFlag) { blockingFlag = 0; if (this.LocalPlayer is null) return true; - + var condition = Service.GetNullable(); - + var blockingConditions = condition.AsReadOnlySet().Except([ - ConditionFlag.NormalConditions, - ConditionFlag.Jumping, - ConditionFlag.Mounted, + ConditionFlag.NormalConditions, + ConditionFlag.Jumping, + ConditionFlag.Mounted, ConditionFlag.UsingParasol]); blockingFlag = blockingConditions.FirstOrDefault(); return blockingFlag == 0; } - + /// public bool IsClientIdle() => this.IsClientIdle(out _); @@ -152,23 +173,89 @@ internal sealed class ClientState : IInternalDisposableService, IClientState void IInternalDisposableService.DisposeService() { this.setupTerritoryTypeHook.Dispose(); - this.framework.Update -= this.FrameworkOnOnUpdateEvent; + this.uiModuleHandlePacketHook.Dispose(); + this.onLogoutHook.Dispose(); + + this.framework.Update -= this.FrameworkOnOnUpdateEvent; this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; } - private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) + private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType) { - this.TerritoryType = terriType; - this.TerritoryChanged?.InvokeSafely(terriType); + Log.Debug("TerritoryType changed: {0}", territoryType); - Log.Debug("TerritoryType changed: {0}", terriType); + this.TerritoryType = territoryType; + this.TerritoryChanged?.InvokeSafely(territoryType); - return this.setupTerritoryTypeHook.Original(manager, terriType); + var rowRef = LuminaUtils.CreateRef(territoryType); + if (rowRef.IsValid) + { + var isPvP = rowRef.Value.IsPvpZone; + if (isPvP != this.IsPvP) + { + this.IsPvP = isPvP; + + if (this.IsPvP) + { + Log.Debug("EnterPvP"); + this.EnterPvP?.InvokeSafely(); + } + else + { + Log.Debug("LeavePvP"); + this.LeavePvP?.InvokeSafely(); + } + } + } + + this.setupTerritoryTypeHook.Original(eventFramework, territoryType); } - private void NetworkHandlersOnCfPop(ContentFinderCondition e) + private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet) { - this.CfPop?.InvokeSafely(e); + this.uiModuleHandlePacketHook.Original(thisPtr, type, uintParam, packet); + + switch (type) + { + case UIModulePacketType.ClassJobChange when this.ClassJobChanged is { } callback: + { + var classJobId = uintParam; + + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(classJobId); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + + break; + } + + case UIModulePacketType.LevelChange when this.LevelChanged is { } callback: + { + var classJobId = *(uint*)packet; + var level = *(ushort*)((nint)packet + 4); + + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(classJobId, level); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + + break; + } + } } private void FrameworkOnOnUpdateEvent(IFramework framework1) @@ -184,40 +271,58 @@ internal sealed class ClientState : IInternalDisposableService, IClientState { Log.Debug("Is login"); this.lastConditionNone = false; - this.IsLoggedIn = true; this.Login?.InvokeSafely(); gameGui.ResetUiHideState(); this.lifecycle.ResetLogout(); } + } - if (!condition.Any() && this.lastConditionNone == false) + private unsafe void OnLogoutDetour(LogoutCallbackInterface* thisPtr, LogoutCallbackInterface.LogoutParams* logoutParams) + { + var gameGui = Service.GetNullable(); + + if (logoutParams != null) { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.InvokeSafely(); - gameGui.ResetUiHideState(); - - this.lifecycle.SetLogout(); - } - - this.IsPvP = GameMain.IsInPvPArea(); - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; - - if (this.IsPvP != this.lastFramePvP) - { - this.lastFramePvP = this.IsPvP; - - if (this.IsPvP) + try { - this.EnterPvP?.InvokeSafely(); + var type = logoutParams->Type; + var code = logoutParams->Code; + + Log.Debug("Logout: Type {type}, Code {code}", type, code); + + if (this.Logout is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(type, code); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } + + gameGui?.ResetUiHideState(); + this.lastConditionNone = true; // unblock login flag + + this.lifecycle.SetLogout(); } - else + catch (Exception ex) { - this.LeavePvP?.InvokeSafely(); + Log.Error(ex, "Exception during OnLogoutDetour"); } } + + this.onLogoutHook.Original(thisPtr, logoutParams); + } + + private void NetworkHandlersOnCfPop(ContentFinderCondition e) + { + this.CfPop?.InvokeSafely(e); } } @@ -240,28 +345,36 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat internal ClientStatePluginScoped() { this.clientStateService.TerritoryChanged += this.TerritoryChangedForward; + this.clientStateService.ClassJobChanged += this.ClassJobChangedForward; + this.clientStateService.LevelChanged += this.LevelChangedForward; this.clientStateService.Login += this.LoginForward; this.clientStateService.Logout += this.LogoutForward; this.clientStateService.EnterPvP += this.EnterPvPForward; this.clientStateService.LeavePvP += this.ExitPvPForward; this.clientStateService.CfPop += this.ContentFinderPopForward; } - + /// public event Action? TerritoryChanged; - + + /// + public event IClientState.ClassJobChangeDelegate? ClassJobChanged; + + /// + public event IClientState.LevelChangeDelegate? LevelChanged; + /// public event Action? Login; - + /// - public event Action? Logout; - + public event IClientState.LogoutDelegate? Logout; + /// public event Action? EnterPvP; - + /// public event Action? LeavePvP; - + /// public event Action? CfPop; @@ -270,7 +383,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat /// public ushort TerritoryType => this.clientStateService.TerritoryType; - + /// public uint MapId => this.clientStateService.MapId; @@ -302,6 +415,8 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat void IInternalDisposableService.DisposeService() { this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward; + this.clientStateService.ClassJobChanged -= this.ClassJobChangedForward; + this.clientStateService.LevelChanged -= this.LevelChangedForward; this.clientStateService.Login -= this.LoginForward; this.clientStateService.Logout -= this.LogoutForward; this.clientStateService.EnterPvP -= this.EnterPvPForward; @@ -317,13 +432,17 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat } private void TerritoryChangedForward(ushort territoryId) => this.TerritoryChanged?.Invoke(territoryId); - + + private void ClassJobChangedForward(uint classJobId) => this.ClassJobChanged?.Invoke(classJobId); + + private void LevelChangedForward(uint classJobId, uint level) => this.LevelChanged?.Invoke(classJobId, level); + private void LoginForward() => this.Login?.Invoke(); - - private void LogoutForward() => this.Logout?.Invoke(); - + + private void LogoutForward(int type, int code) => this.Logout?.Invoke(type, code); + private void EnterPvPForward() => this.EnterPvP?.Invoke(); - + private void ExitPvPForward() => this.LeavePvP?.Invoke(); private void ContentFinderPopForward(ContentFinderCondition cfc) => this.CfPop?.Invoke(cfc); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 5a5351a38..97bc5dae1 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -7,39 +7,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver { // Static offsets - /// - /// Gets the address of the actor table. - /// - public IntPtr ObjectTable { get; private set; } - - /// - /// Gets the address of the buddy list. - /// - public IntPtr BuddyList { get; private set; } - - /// - /// Gets the address of a pointer to the fate table. - /// - /// - /// This is a static address to a pointer, not the address of the table itself. - /// - public IntPtr FateTablePtr { get; private set; } - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManager { get; private set; } - - /// - /// Gets the address of the local content id. - /// - public IntPtr LocalContentId { get; private set; } - - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } - /// /// Gets the address of the keyboard state. /// @@ -50,23 +17,12 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// public IntPtr KeyboardStateIndexArray { get; private set; } - /// - /// Gets the address of the condition flag array. - /// - public IntPtr ConditionFlags { get; private set; } - // Functions /// - /// Gets the address of the method which sets the territory type. + /// Gets the address of the method which sets up the player. /// - public IntPtr SetupTerritoryType { get; private set; } - - /// - /// Gets the address of the method which polls the gamepads for data. - /// Called every frame, even when `Enable Gamepad` is off in the settings. - /// - public IntPtr GamepadPoll { get; private set; } + public IntPtr ProcessPacketPlayerSetup { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -74,27 +30,12 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??"); - - this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); - - this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F1 44 0F B7 41"); - - this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 77 71"); - - this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 0D ?? ?? ?? ?? 48 8D 57 08"); - this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8; - - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA"); + this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); // not in cs struct // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. // lea rcx, ds:1DB9F74h[rax*4] KeyboardState // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - - this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 8B D3 E8 ?? ?? ?? ?? 32 C0 48 83 C4 20"); - - this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); } } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 6fee02fd6..8f39df46f 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -28,10 +28,9 @@ internal sealed class Condition : IInternalDisposableService, ICondition private bool isDisposed; [ServiceManager.ServiceConstructor] - private Condition(ClientState clientState) + private unsafe Condition() { - var resolver = clientState.AddressResolver; - this.Address = resolver.ConditionFlags; + this.Address = (nint)FFXIVClientStructs.FFXIV.Client.Game.Conditions.Instance(); // Initialization for (var i = 0; i < MaxConditionEntries; i++) diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 2d32cc04c..f48e66ad6 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -1,10 +1,11 @@ using System.Numerics; using Dalamud.Data; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Fates; /// @@ -20,7 +21,7 @@ public interface IFate : IEquatable /// /// Gets game data linked to this Fate. /// - Lumina.Excel.GeneratedSheets.Fate GameData { get; } + RowRef GameData { get; } /// /// Gets the time this started. @@ -70,8 +71,14 @@ public interface IFate : IEquatable /// /// Gets a value indicating whether or not this has a EXP bonus. /// + [Obsolete($"Use {nameof(HasBonus)} instead")] bool HasExpBonus { get; } + /// + /// Gets a value indicating whether or not this has a bonus. + /// + bool HasBonus { get; } + /// /// Gets the icon id of this . /// @@ -105,7 +112,7 @@ public interface IFate : IEquatable /// /// Gets the territory this is located in. /// - ExcelResolver TerritoryType { get; } + RowRef TerritoryType { get; } /// /// Gets the address of this Fate in memory. @@ -185,7 +192,7 @@ internal unsafe partial class Fate : IFate public ushort FateId => this.Struct->FateId; /// - public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); + public RowRef GameData => LuminaUtils.CreateRef(this.FateId); /// public int StartTimeEpoch => this.Struct->StartTimeEpoch; @@ -215,8 +222,12 @@ internal unsafe partial class Fate : IFate public byte Progress => this.Struct->Progress; /// + [Obsolete($"Use {nameof(HasBonus)} instead")] public bool HasExpBonus => this.Struct->IsExpBonus; + /// + public bool HasBonus => this.Struct->IsBonus; + /// public uint IconId => this.Struct->IconId; @@ -238,5 +249,5 @@ internal unsafe partial class Fate : IFate /// /// Gets the territory this is located in. /// - public ExcelResolver TerritoryType => new(this.Struct->TerritoryId); + public RowRef TerritoryType => LuminaUtils.CreateRef(this.Struct->MapMarkers[0].TerritoryId); } diff --git a/Dalamud/Game/ClientState/Fates/FateState.cs b/Dalamud/Game/ClientState/Fates/FateState.cs index 8f2ef85cc..ad605a99c 100644 --- a/Dalamud/Game/ClientState/Fates/FateState.cs +++ b/Dalamud/Game/ClientState/Fates/FateState.cs @@ -8,25 +8,25 @@ public enum FateState : byte /// /// The Fate is active. /// - Running = 0x02, + Running = 0x04, /// /// The Fate has ended. /// - Ended = 0x04, + Ended = 0x07, /// /// The player failed the Fate. /// - Failed = 0x05, + Failed = 0x08, /// /// The Fate is preparing to run. /// - Preparation = 0x07, + Preparation = 0x03, /// /// The Fate is preparing to end. /// - WaitingForEnd = 0x08, + WaitingForEnd = 0x05, } diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index abd5bac41..1bf557ad5 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager; namespace Dalamud.Game.ClientState.Fates; @@ -20,55 +19,34 @@ namespace Dalamud.Game.ClientState.Fates; #pragma warning restore SA1015 internal sealed partial class FateTable : IServiceType, IFateTable { - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] - private FateTable(ClientState clientState) + private FateTable() { - this.address = clientState.AddressResolver; - - Log.Verbose($"Fate table address {Util.DescribeAddress(this.address.FateTablePtr)}"); } /// - public IntPtr Address => this.address.FateTablePtr; + public unsafe IntPtr Address => (nint)CSFateManager.Instance(); /// public unsafe int Length { get { - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) + var fateManager = CSFateManager.Instance(); + if (fateManager == null) return 0; // Sonar used this to check if the table was safe to read - if (Struct->FateDirector == null) + if (fateManager->FateDirector == null) return 0; - if (Struct->Fates.First == null || Struct->Fates.Last == null) + if (fateManager->Fates.First == null || fateManager->Fates.Last == null) return 0; - return Struct->Fates.Count; + return fateManager->Fates.Count; } } - /// - /// Gets the address of the Fate table. - /// - internal unsafe IntPtr FateTableAddress - { - get - { - if (this.address.FateTablePtr == IntPtr.Zero) - return IntPtr.Zero; - - return *(IntPtr*)this.address.FateTablePtr; - } - } - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; - /// public IFate? this[int index] { @@ -99,11 +77,11 @@ internal sealed partial class FateTable : IServiceType, IFateTable if (index >= this.Length) return IntPtr.Zero; - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) + var fateManager = CSFateManager.Instance(); + if (fateManager == null) return IntPtr.Zero; - return (IntPtr)this.Struct->Fates[index].Value; + return (IntPtr)fateManager->Fates[index].Value; } /// diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs deleted file mode 100644 index 32439cd08..000000000 --- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Dalamud.Game.ClientState.GamePad; - -/// -/// Struct which gets populated by polling the gamepads. -/// -/// Has an array of gamepads, among many other things (here not mapped). -/// All we really care about is the final data which the game uses to determine input. -/// -/// The size is definitely bigger than only the following fields but I do not know how big. -/// -[StructLayout(LayoutKind.Explicit)] -public struct GamepadInput -{ - /// - /// Left analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x88)] - public int LeftStickX; - - /// - /// Left analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x8C)] - public int LeftStickY; - - /// - /// Right analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x90)] - public int RightStickX; - - /// - /// Right analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x94)] - public int RightStickY; - - /// - /// Raw input, set the whole time while a button is held. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x98)] - public ushort ButtonsRaw; - - /// - /// Button pressed, set once when the button is pressed. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x9C)] - public ushort ButtonsPressed; - - /// - /// Button released input, set once right after the button is not hold anymore. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA0)] - public ushort ButtonsReleased; - - /// - /// Repeatedly emits the held button input in fixed intervals. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA4)] - public ushort ButtonsRepeat; -} diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 05d691823..5237c6f0c 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -4,7 +4,8 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.Input; using ImGuiNET; using Serilog; @@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.GamePad; #pragma warning restore SA1015 internal unsafe class GamepadState : IInternalDisposableService, IGamepadState { - private readonly Hook? gamepadPoll; + private readonly Hook? gamepadPoll; private bool isDisposed; @@ -35,25 +36,21 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState [ServiceManager.ServiceConstructor] private GamepadState(ClientState clientState) { - var resolver = clientState.AddressResolver; - Log.Verbose($"GamepadPoll address {Util.DescribeAddress(resolver.GamepadPoll)}"); - this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour); + this.gamepadPoll = Hook.FromAddress((nint)PadDevice.StaticVirtualTablePointer->Poll, this.GamepadPollDetour); this.gamepadPoll?.Enable(); } - private delegate int ControllerPoll(IntPtr controllerInput); - /// /// Gets the pointer to the current instance of the GamepadInput struct. /// public IntPtr GamepadInputAddress { get; private set; } /// - public Vector2 LeftStick => + public Vector2 LeftStick => new(this.leftStickX, this.leftStickY); - + /// - public Vector2 RightStick => + public Vector2 RightStick => new(this.rightStickX, this.rightStickY); /// @@ -61,28 +58,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsPressed { get; private set; } + internal GamepadButtons ButtonsPressed { get; private set; } /// /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsRaw { get; private set; } + internal GamepadButtons ButtonsRaw { get; private set; } /// /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsReleased { get; private set; } + internal GamepadButtons ButtonsReleased { get; private set; } /// /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsRepeat { get; private set; } + internal GamepadButtons ButtonsRepeat { get; private set; } /// /// Gets or sets a value indicating whether detour should block gamepad input for game. @@ -95,16 +92,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState internal bool NavEnableGamepad { get; set; } /// - public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; + public float Pressed(GamepadButtons button) => (this.ButtonsPressed & button) > 0 ? 1 : 0; /// - public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; + public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & button) > 0 ? 1 : 0; /// - public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; + public float Released(GamepadButtons button) => (this.ButtonsReleased & button) > 0 ? 1 : 0; /// - public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; + public float Raw(GamepadButtons button) => (this.ButtonsRaw & button) > 0 ? 1 : 0; /// /// Disposes this instance, alongside its hooks. @@ -115,28 +112,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState GC.SuppressFinalize(this); } - private int GamepadPollDetour(IntPtr gamepadInput) + private nint GamepadPollDetour(PadDevice* gamepadInput) { var original = this.gamepadPoll!.Original(gamepadInput); try { - this.GamepadInputAddress = gamepadInput; - var input = (GamepadInput*)gamepadInput; - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.ButtonsRaw = input->ButtonsRaw; - this.ButtonsPressed = input->ButtonsPressed; - this.ButtonsReleased = input->ButtonsReleased; - this.ButtonsRepeat = input->ButtonsRepeat; + this.GamepadInputAddress = (nint)gamepadInput; + + this.leftStickX = gamepadInput->GamepadInputData.LeftStickX; + this.leftStickY = gamepadInput->GamepadInputData.LeftStickY; + this.rightStickX = gamepadInput->GamepadInputData.RightStickX; + this.rightStickY = gamepadInput->GamepadInputData.RightStickY; + this.ButtonsRaw = (GamepadButtons)gamepadInput->GamepadInputData.Buttons; + this.ButtonsPressed = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsPressed; + this.ButtonsReleased = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsReleased; + this.ButtonsRepeat = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsRepeat; if (this.NavEnableGamepad) { - input->LeftStickX = 0; - input->LeftStickY = 0; - input->RightStickX = 0; - input->RightStickY = 0; + gamepadInput->GamepadInputData.LeftStickX = 0; + gamepadInput->GamepadInputData.LeftStickY = 0; + gamepadInput->GamepadInputData.RightStickX = 0; + gamepadInput->GamepadInputData.RightStickY = 0; // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). @@ -153,16 +150,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. // This is debatable. // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - const ushort deletionMask = (ushort)(~GamepadButtons.L2 - & ~GamepadButtons.R2 - & ~GamepadButtons.DpadDown - & ~GamepadButtons.DpadLeft - & ~GamepadButtons.DpadUp - & ~GamepadButtons.DpadRight); - input->ButtonsRaw &= deletionMask; - input->ButtonsPressed = 0; - input->ButtonsReleased = 0; - input->ButtonsRepeat = 0; + const GamepadButtonsFlags deletionMask = ~GamepadButtonsFlags.L2 + & ~GamepadButtonsFlags.R2 + & ~GamepadButtonsFlags.DPadDown + & ~GamepadButtonsFlags.DPadLeft + & ~GamepadButtonsFlags.DPadUp + & ~GamepadButtonsFlags.DPadRight; + gamepadInput->GamepadInputData.Buttons &= deletionMask; + gamepadInput->GamepadInputData.ButtonsPressed = 0; + gamepadInput->GamepadInputData.ButtonsReleased = 0; + gamepadInput->GamepadInputData.ButtonsRepeat = 0; return 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs index bbe4ad70d..9191ca020 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs @@ -8,20 +8,20 @@ public enum BeastChakra : byte /// /// No chakra. /// - NONE = 0, + None = 0, /// /// The Opo-Opo chakra. /// - OPOOPO = 1, + OpoOpo = 1, /// /// The Raptor chakra. /// - RAPTOR = 2, + Raptor = 2, /// /// The Coeurl chakra. /// - COEURL = 3, + Coeurl = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs index 24ffc2b19..89fceed96 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs @@ -8,45 +8,45 @@ public enum CardType : byte /// /// No card. /// - NONE = 0, + None = 0, /// /// The Balance card. /// - BALANCE = 1, + Balance = 1, /// /// The Bole card. /// - BOLE = 2, + Bole = 2, /// /// The Arrow card. /// - ARROW = 3, + Arrow = 3, /// /// The Spear card. /// - SPEAR = 4, + Spear = 4, /// /// The Ewer card. /// - EWER = 5, + Ewer = 5, /// /// The Spire card. /// - SPIRE = 6, + Spire = 6, /// /// The Lord of Crowns card. /// - LORD = 7, + Lord = 7, /// /// The Lady of Crowns card. /// - LADY = 8, + Lady = 8, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs new file mode 100644 index 000000000..d2a41b93c --- /dev/null +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs @@ -0,0 +1,22 @@ +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// Enum representing the current step of Delirium. +/// +public enum DeliriumStep +{ + /// + /// Scarlet Delirium can be used. + /// + ScarletDelirium = 0, + + /// + /// Comeuppance can be used. + /// + Comeuppance = 1, + + /// + /// Torcleaver can be used. + /// + Torcleaver = 2, +} diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs index b674d11b8..446489bd1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs @@ -8,10 +8,10 @@ public enum DismissedFairy : byte /// /// Dismissed fairy is Eos. /// - EOS = 6, + Eos = 6, /// /// Dismissed fairy is Selene. /// - SELENE = 7, + Selene = 7, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs index f8833d6d0..619059daa 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs @@ -8,10 +8,10 @@ public enum DrawType : byte /// /// Astral Draw active. /// - ASTRAL = 0, + Astral = 0, /// /// Umbral Draw active. /// - UMBRAL = 1, + Umbral = 1, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs index e35dcc7f9..86e58771b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs @@ -8,25 +8,25 @@ public enum Kaeshi : byte /// /// No Kaeshi is active. /// - NONE = 0, + None = 0, /// /// Kaeshi: Higanbana type. /// - HIGANBANA = 1, + Higanbana = 1, /// /// Kaeshi: Goken type. /// - GOKEN = 2, + Goken = 2, /// /// Kaeshi: Setsugekka type. /// - SETSUGEKKA = 3, + Setsugekka = 3, /// /// Kaeshi: Namikiri type. /// - NAMIKIRI = 4, + Namikiri = 4, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs index 31ee6dac1..8955c0735 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs @@ -8,15 +8,15 @@ public enum Mudras : byte /// /// Ten mudra. /// - TEN = 1, + Ten = 1, /// /// Chi mudra. /// - CHI = 2, + Chi = 2, /// /// Jin mudra. /// - JIN = 3, + Jin = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs index 8be5c739e..f61e54104 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs @@ -9,15 +9,15 @@ public enum Nadi : byte /// /// No nadi. /// - NONE = 0, + None = 0, /// /// The Lunar nadi. /// - LUNAR = 1, + Lunar = 1, /// /// The Solar nadi. /// - SOLAR = 2, + Solar = 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs index 248caa396..c10c369d3 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs @@ -8,40 +8,40 @@ public enum PetGlam : byte /// /// No pet glam. /// - NONE = 0, + None = 0, /// /// Emerald carbuncle pet glam. /// - EMERALD = 1, + Emerald = 1, /// /// Topaz carbuncle pet glam. /// - TOPAZ = 2, + Topaz = 2, /// /// Ruby carbuncle pet glam. /// - RUBY = 3, + Ruby = 3, /// /// Normal carbuncle pet glam. /// - CARBUNCLE = 4, + Carbuncle = 4, /// /// Ifrit Egi pet glam. /// - IFRIT = 5, + Ifrit = 5, /// /// Titan Egi pet glam. /// - TITAN = 6, + Titan = 6, /// /// Garuda Egi pet glam. /// - GARUDA = 7, + Garuda = 7, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs index a1a6035c6..0eb86b1b1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs @@ -9,20 +9,20 @@ public enum Sen : byte /// /// No Sen. /// - NONE = 0, + None = 0, /// /// Setsu Sen type. /// - SETSU = 1 << 0, + Setsu = 1 << 0, /// /// Getsu Sen type. /// - GETSU = 1 << 1, + Getsu = 1 << 1, /// /// Ka Sen type. /// - KA = 1 << 2, + Ka = 1 << 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs index 0fc50d87a..f4850cf8c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs @@ -8,35 +8,35 @@ public enum SerpentCombo : byte /// /// No Serpent combo is active. /// - NONE = 0, + None = 0, /// /// Death Rattle action. /// - DEATHRATTLE = 1, + DeathRattle = 1, /// /// Last Lash action. /// - LASTLASH = 2, + LastLash = 2, /// /// First Legacy action. /// - FIRSTLEGACY = 3, + FirstLegacy = 3, /// /// Second Legacy action. /// - SECONDLEGACY = 4, + SecondLegacy = 4, /// /// Third Legacy action. /// - THIRDLEGACY = 5, + ThirdLegacy = 5, /// /// Fourth Legacy action. /// - FOURTHLEGACY = 6, + FourthLegacy = 6, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs index c23fbbbff..7ca6b6c07 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs @@ -8,20 +8,20 @@ public enum Song : byte /// /// No song is active type. /// - NONE = 0, + None = 0, /// /// Mage's Ballad type. /// - MAGE = 1, + Mage = 1, /// /// Army's Paeon type. /// - ARMY = 2, + Army = 2, /// /// The Wanderer's Minuet type. /// - WANDERER = 3, + Wanderer = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs new file mode 100644 index 000000000..a07c0d04f --- /dev/null +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs @@ -0,0 +1,30 @@ +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// Enum representing the current attunement of a summoner. +/// +public enum SummonAttunement +{ + /// + /// No attunement. + /// + None = 0, + + /// + /// Attuned to the summon Ifrit. + /// Same as . + /// + Ifrit = 1, + + /// + /// Attuned to the summon Titan. + /// Same as . + /// + Titan = 2, + + /// + /// Attuned to the summon Garuda. + /// Same as . + /// + Garuda = 3, +} diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs index 30cc0fff0..37569f4bf 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs @@ -8,10 +8,10 @@ public enum SummonPet : byte /// /// No pet. /// - NONE = 0, + None = 0, /// /// The summoned pet Carbuncle. /// - CARBUNCLE = 23, + Carbuncle = 23, } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index e0ae986c5..67429956b 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -5,9 +5,8 @@ using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSJobGaugeManager = FFXIVClientStructs.FFXIV.Client.Game.JobGaugeManager; namespace Dalamud.Game.ClientState.JobGauge; @@ -21,18 +20,15 @@ namespace Dalamud.Game.ClientState.JobGauge; #pragma warning restore SA1015 internal class JobGauges : IServiceType, IJobGauges { - private Dictionary cache = new(); + private Dictionary cache = []; [ServiceManager.ServiceConstructor] - private JobGauges(ClientState clientState) + private JobGauges() { - this.Address = clientState.AddressResolver.JobGaugeData; - - Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}"); } /// - public IntPtr Address { get; } + public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->EmptyGauge); /// public T Get() where T : JobGaugeBase diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs index ebf70abe7..c4058132a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs @@ -19,11 +19,6 @@ public unsafe class BLMGauge : JobGaugeBase public short EnochianTimer => this.Struct->EnochianTimer; - /// - /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. - /// - public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; - /// /// Gets the number of Polyglot stacks remaining. /// diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs index bfcf3cc38..8880c3555 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs @@ -40,15 +40,15 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.HasFlag(SongFlags.WanderersMinuet)) - return Song.WANDERER; + return Song.Wanderer; if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon)) - return Song.ARMY; + return Song.Army; if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad)) - return Song.MAGE; + return Song.Mage; - return Song.NONE; + return Song.None; } } @@ -60,15 +60,15 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed)) - return Song.WANDERER; + return Song.Wanderer; if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed)) - return Song.ARMY; + return Song.Army; if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed)) - return Song.MAGE; + return Song.Mage; - return Song.NONE; + return Song.None; } } @@ -76,7 +76,7 @@ public unsafe class BRDGauge : JobGaugeBase /// - /// This will always return an array of size 3, inactive Coda are represented by . + /// This will always return an array of size 3, inactive Coda are represented by . /// public Song[] Coda { @@ -84,9 +84,9 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.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, + 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, }; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs index 834087040..c56d03db0 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs @@ -1,9 +1,12 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; + namespace Dalamud.Game.ClientState.JobGauge.Types; /// /// In-memory DRK job gauge. /// -public unsafe class DRKGauge : JobGaugeBase +public unsafe class DRKGauge : JobGaugeBase { /// /// Initializes a new instance of the class. @@ -34,4 +37,16 @@ public unsafe class DRKGauge : JobGaugeBase /// true or false. public bool HasDarkArts => this.Struct->DarkArtsState > 0; + + /// + /// Gets the step of the Delirium Combo (Scarlet Delirium, Comeuppance, + /// Torcleaver) that the player is on.
+ /// Does not in any way consider whether the player is still under Delirium, or + /// if the player still has stacks of Delirium to use. + ///
+ /// + /// Value will persist until combo is finished OR + /// if the combo is not completed then the value will stay until about halfway into Delirium's cooldown. + /// + public DeliriumStep DeliriumComboStep => (DeliriumStep)this.Struct->DeliriumStep; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs index 803740f33..31a5ceb9b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs @@ -27,7 +27,7 @@ public unsafe class MNKGauge : JobGaugeBase /// - /// This will always return an array of size 3, inactive Beast Chakra are represented by . + /// This will always return an array of size 3, inactive Beast Chakra are represented by . /// public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray(); diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs index f3417f002..52dc0e51a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs @@ -40,17 +40,17 @@ public unsafe class SAMGauge : JobGaugeBase /// true or false. - public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; + public bool HasSetsu => (this.Sen & Sen.Setsu) != 0; /// /// Gets a value indicating whether the Getsu Sen is active. /// /// true or false. - public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; + public bool HasGetsu => (this.Sen & Sen.Getsu) != 0; /// /// Gets a value indicating whether the Ka Sen is active. /// /// true or false. - public bool HasKa => (this.Sen & Sen.KA) != 0; + public bool HasKa => (this.Sen & Sen.Ka) != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index aa7e27fd2..899ea78eb 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -25,25 +25,46 @@ public unsafe class SMNGauge : JobGaugeBase /// /// Gets the time remaining for the current attunement. /// - public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer; + [Obsolete("Typo fixed. Use AttunementTimerRemaining instead.", true)] + public ushort AttunmentTimerRemaining => this.AttunementTimerRemaining; + + /// + /// Gets the time remaining for the current attunement. + /// + public ushort AttunementTimerRemaining => this.Struct->AttunementTimer; /// /// Gets the summon that will return after the current summon expires. - /// This maps to the sheet. + /// This maps to the sheet. /// public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; /// /// Gets the summon glam for the . - /// This maps to the sheet. + /// This maps to the sheet. /// public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; /// - /// Gets the amount of aspected Attunment remaining. + /// Gets the amount of aspected Attunement remaining. /// + /// + /// As of 7.01, this should be treated as a bit field. + /// Use and instead. + /// public byte Attunement => this.Struct->Attunement; + /// + /// Gets the count of attunement cost resource available. + /// + public byte AttunementCount => this.Struct->AttunementCount; + + /// + /// Gets the type of attunement available. + /// Use the summon attuned accessors instead. + /// + public SummonAttunement AttunementType => (SummonAttunement)this.Struct->AttunementType; + /// /// Gets the current aether flags. /// Use the summon accessors instead. @@ -84,19 +105,19 @@ public unsafe class SMNGauge : JobGaugeBase /// Gets a value indicating whether if Ifrit is currently attuned. /// /// true or false. - public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsIfritAttuned => this.AttunementType == SummonAttunement.Ifrit; /// /// Gets a value indicating whether if Titan is currently attuned. /// /// true or false. - public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsTitanAttuned => this.AttunementType == SummonAttunement.Titan; /// /// Gets a value indicating whether if Garuda is currently attuned. /// /// true or false. - public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsGarudaAttuned => this.AttunementType == SummonAttunement.Garuda; /// /// Gets a value indicating whether there are any Aetherflow stacks available. diff --git a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs index 9c10a84ab..ff356989a 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs @@ -27,7 +27,12 @@ public enum BattleNpcSubKind : byte Chocobo = 3, /// - /// BattleNpc representing a standard enemy. + /// BattleNpc representing a standard enemy. This includes allies (overworld guards and allies in single-player duties). /// Enemy = 5, + + /// + /// BattleNpc representing an NPC party member (from Duty Support, Trust, or Grand Company Command Mission). + /// + NpcPartyMember = 9, } diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 50f4e81ce..f97385fce 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -7,15 +7,15 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Plugin.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.Interop; + using Microsoft.Extensions.ObjectPool; -using Serilog; - using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; +using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager; namespace Dalamud.Game.ClientState.Objects; @@ -29,62 +29,58 @@ namespace Dalamud.Game.ClientState.Objects; #pragma warning restore SA1015 internal sealed partial class ObjectTable : IServiceType, IObjectTable { - private const int ObjectTableLength = 599; + private static int objectTableLength; private readonly ClientState clientState; - private readonly CachedEntry[] cachedObjectTable = new CachedEntry[ObjectTableLength]; - - private readonly ObjectPool multiThreadedEnumerators = - new DefaultObjectPoolProvider().Create(); + private readonly CachedEntry[] cachedObjectTable; private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4]; - private long nextMultithreadedUsageWarnTime; - [ServiceManager.ServiceConstructor] private unsafe ObjectTable(ClientState clientState) { this.clientState = clientState; - var nativeObjectTableAddress = (CSGameObject**)this.clientState.AddressResolver.ObjectTable; + var nativeObjectTable = CSGameObjectManager.Instance()->Objects.IndexSorted; + objectTableLength = nativeObjectTable.Length; + + this.cachedObjectTable = new CachedEntry[objectTableLength]; for (var i = 0; i < this.cachedObjectTable.Length; i++) - this.cachedObjectTable[i] = new(nativeObjectTableAddress, i); + this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i)); for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++) this.frameworkThreadEnumerators[i] = new(this, i); - - Log.Verbose($"Object table address {Util.DescribeAddress(this.clientState.AddressResolver.ObjectTable)}"); } /// - public nint Address + public unsafe nint Address { get { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); - return this.clientState.AddressResolver.ObjectTable; + return (nint)(&CSGameObjectManager.Instance()->Objects); } } /// - public int Length => ObjectTableLength; + public int Length => objectTableLength; /// public IGameObject? this[int index] { get { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); - return index is >= ObjectTableLength or < 0 ? null : this.cachedObjectTable[index].Update(); + return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update(); } } /// public IGameObject? SearchById(ulong gameObjectId) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (gameObjectId is 0) return null; @@ -101,7 +97,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public IGameObject? SearchByEntityId(uint entityId) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (entityId is 0 or 0xE0000000) return null; @@ -118,15 +114,15 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public unsafe nint GetObjectAddress(int index) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); - return index is < 0 or >= ObjectTableLength ? nint.Zero : (nint)this.cachedObjectTable[index].Address; + return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address; } /// public unsafe IGameObject? CreateObjectReference(nint address) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (this.clientState.LocalContentId == 0) return null; @@ -150,55 +146,22 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable }; } - [Api10ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool WarnMultithreadedUsage() - { - if (ThreadSafety.IsMainThread) - return false; - - var n = Environment.TickCount64; - if (this.nextMultithreadedUsageWarnTime < n) - { - this.nextMultithreadedUsageWarnTime = n + 30000; - - Log.Warning( - "{plugin} is accessing {objectTable} outside the main thread. This is deprecated.", - Service.Get().FindCallingPlugin()?.Name ?? "", - nameof(ObjectTable)); - } - - return true; - } - /// Stores an object table entry, with preallocated concrete types. - internal readonly unsafe struct CachedEntry + /// Initializes a new instance of the struct. + /// A pointer to the object table entry this entry should be pointing to. + internal readonly unsafe struct CachedEntry(Pointer* gameObjectPtr) { - private readonly CSGameObject** gameObjectPtrPtr; - private readonly PlayerCharacter playerCharacter; - private readonly BattleNpc battleNpc; - private readonly Npc npc; - private readonly EventObj eventObj; - private readonly GameObject gameObject; - - /// Initializes a new instance of the struct. - /// The object table that this entry should be pointing to. - /// The slot index inside the table. - public CachedEntry(CSGameObject** ownerTable, int slot) - { - this.gameObjectPtrPtr = ownerTable + slot; - this.playerCharacter = new(nint.Zero); - this.battleNpc = new(nint.Zero); - this.npc = new(nint.Zero); - this.eventObj = new(nint.Zero); - this.gameObject = new(nint.Zero); - } + private readonly PlayerCharacter playerCharacter = new(nint.Zero); + private readonly BattleNpc battleNpc = new(nint.Zero); + private readonly Npc npc = new(nint.Zero); + private readonly EventObj eventObj = new(nint.Zero); + private readonly GameObject gameObject = new(nint.Zero); /// Gets the address of the underlying native object. May be null. public CSGameObject* Address { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => *this.gameObjectPtrPtr; + get => gameObjectPtr->Value; } /// Updates and gets the wrapped game object pointed by this struct. @@ -235,14 +198,7 @@ internal sealed partial class ObjectTable /// public IEnumerator GetEnumerator() { - // If something's trying to enumerate outside the framework thread, we use the ObjectPool. - if (this.WarnMultithreadedUsage()) - { - // let's not - var e = this.multiThreadedEnumerators.Get(); - e.InitializeForPooledObjects(this); - return e; - } + ThreadSafety.AssertMainThread(); // 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()) @@ -263,32 +219,23 @@ internal sealed partial class ObjectTable /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - private sealed class Enumerator : IEnumerator, IResettable + private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator, IResettable { - private readonly int slotId; - private ObjectTable? owner; + private ObjectTable? owner = owner; private int index = -1; - public Enumerator() => this.slotId = -1; - - public Enumerator(ObjectTable owner, int slotId) - { - this.owner = owner; - this.slotId = slotId; - } - public IGameObject Current { get; private set; } = null!; object IEnumerator.Current => this.Current; public bool MoveNext() { - if (this.index == ObjectTableLength) + if (this.index == objectTableLength) return false; var cache = this.owner!.cachedObjectTable.AsSpan(); - for (this.index++; this.index < ObjectTableLength; this.index++) + for (this.index++; this.index < objectTableLength; this.index++) { if (cache[this.index].Update() is { } ao) { @@ -300,8 +247,6 @@ internal sealed partial class ObjectTable return false; } - public void InitializeForPooledObjects(ObjectTable ot) => this.owner = ot; - public void Reset() => this.index = -1; public void Dispose() @@ -309,10 +254,8 @@ internal sealed partial class ObjectTable if (this.owner is not { } o) return; - if (this.slotId == -1) - o.multiThreadedEnumerators.Return(this); - else - o.frameworkThreadEnumerators[this.slotId] = this; + if (slotId != -1) + o.frameworkThreadEnumerators[slotId] = this; } public bool TryReset() diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs index 87fbf39c3..2fd723071 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs @@ -1,12 +1,8 @@ -using System.Numerics; - -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; -using Dalamud.Game.ClientState.Statuses; -using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.ClientState.Objects.SubKinds; @@ -16,14 +12,14 @@ namespace Dalamud.Game.ClientState.Objects.SubKinds; public interface IPlayerCharacter : IBattleChara { /// - /// Gets the current world of the character. + /// Gets the current world of the character. /// - ExcelResolver CurrentWorld { get; } + RowRef CurrentWorld { get; } /// - /// Gets the home world of the character. + /// Gets the home world of the character. /// - ExcelResolver HomeWorld { get; } + RowRef HomeWorld { get; } } /// @@ -42,10 +38,10 @@ internal unsafe class PlayerCharacter : BattleChara, IPlayerCharacter } /// - public ExcelResolver CurrentWorld => new(this.Struct->CurrentWorld); + public RowRef CurrentWorld => LuminaUtils.CreateRef(this.Struct->CurrentWorld); /// - public ExcelResolver HomeWorld => new(this.Struct->HomeWorld); + public RowRef HomeWorld => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the target actor ID of the PlayerCharacter. diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index 5d7ae8945..f81154693 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -20,7 +20,7 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager { [ServiceManager.ServiceDependency] private readonly ObjectTable objectTable = Service.Get(); - + [ServiceManager.ServiceConstructor] private TargetManager() { @@ -29,8 +29,8 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager /// public IGameObject? Target { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target); - set => Struct->Target = (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); } /// @@ -57,8 +57,8 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager /// public IGameObject? SoftTarget { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget); - set => Struct->SoftTarget = (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); } /// @@ -67,7 +67,7 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget); set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; } - + /// public IGameObject? MouseOverNameplateTarget { diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index 72f6a9950..a91ecc230 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -1,10 +1,12 @@ using System.Runtime.CompilerServices; +using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -using Lumina.Excel.GeneratedSheets; + +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.ClientState.Objects.Types; @@ -61,7 +63,7 @@ public interface ICharacter : IGameObject /// /// Gets the ClassJob of this Chara. /// - public ExcelResolver ClassJob { get; } + public RowRef ClassJob { get; } /// /// Gets the level of this Chara. @@ -87,7 +89,7 @@ public interface ICharacter : IGameObject /// /// Gets the current online status of the character. /// - public ExcelResolver OnlineStatus { get; } + public RowRef OnlineStatus { get; } /// /// Gets the status flags. @@ -97,14 +99,14 @@ public interface ICharacter : IGameObject /// /// Gets the current mount for this character. Will be null if the character doesn't have a mount. /// - public ExcelResolver? CurrentMount { get; } + public RowRef? CurrentMount { get; } /// /// Gets the current minion summoned for this character. Will be null if the character doesn't have a minion. /// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a /// mount. /// - public ExcelResolver? CurrentMinion { get; } + public RowRef? CurrentMinion { get; } } /// @@ -150,7 +152,7 @@ internal unsafe class Character : GameObject, ICharacter public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue; /// - public ExcelResolver ClassJob => new(this.Struct->CharacterData.ClassJob); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->CharacterData.ClassJob); /// public byte Level => this.Struct->CharacterData.Level; @@ -159,7 +161,7 @@ internal unsafe class Character : GameObject, ICharacter public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray(); /// - public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6); + public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag); /// /// Gets the target object ID of the character. @@ -170,7 +172,7 @@ internal unsafe class Character : GameObject, ICharacter public uint NameId => this.Struct->NameId; /// - public ExcelResolver OnlineStatus => new(this.Struct->CharacterData.OnlineStatus); + public RowRef OnlineStatus => LuminaUtils.CreateRef(this.Struct->CharacterData.OnlineStatus); /// /// Gets the status flags. @@ -186,28 +188,28 @@ internal unsafe class Character : GameObject, ICharacter (this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None); /// - public ExcelResolver? CurrentMount + public RowRef? CurrentMount { get { if (this.Struct->IsNotMounted()) return null; // just for safety. var mountId = this.Struct->Mount.MountId; - return mountId == 0 ? null : new ExcelResolver(mountId); + return mountId == 0 ? null : LuminaUtils.CreateRef(mountId); } } /// - public ExcelResolver? CurrentMinion + public RowRef? CurrentMinion { get { if (this.Struct->CompanionObject != null) - return new ExcelResolver(this.Struct->CompanionObject->BaseId); + return LuminaUtils.CreateRef(this.Struct->CompanionObject->BaseId); // this is only present if a minion is summoned but hidden (e.g. the player's on a mount). var hiddenCompanionId = this.Struct->CompanionData.CompanionId; - return hiddenCompanionId == 0 ? null : new ExcelResolver(hiddenCompanionId); + return hiddenCompanionId == 0 ? null : LuminaUtils.CreateRef(hiddenCompanionId); } } diff --git a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs index f9fd87bf4..4209100f0 100644 --- a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs +++ b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs @@ -197,7 +197,7 @@ internal partial class GameObject internal unsafe partial class GameObject : IGameObject { /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64); + public SeString Name => SeString.Parse(this.Struct->Name); /// public ulong GameObjectId => this.Struct->GetGameObjectId(); diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index 4fbd8fdfb..a016a8211 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -6,9 +6,8 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager; namespace Dalamud.Game.ClientState.Party; @@ -28,14 +27,9 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private PartyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}"); } /// @@ -48,7 +42,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0; /// - public IntPtr GroupManagerAddress => this.address.GroupManager; + public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance(); /// public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index 34cd31dec..cf620a7ef 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -1,13 +1,15 @@ 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.Resolvers; using Dalamud.Game.ClientState.Statuses; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Party; /// @@ -71,12 +73,12 @@ public interface IPartyMember /// /// Gets the territory this party member is located in. /// - ExcelResolver Territory { get; } + RowRef Territory { get; } /// /// Gets the World this party member resides in. /// - ExcelResolver World { get; } + RowRef World { get; } /// /// Gets the displayname of this party member. @@ -91,7 +93,7 @@ public interface IPartyMember /// /// Gets the classjob of this party member. /// - ExcelResolver ClassJob { get; } + RowRef ClassJob { get; } /// /// Gets the level of this party member. @@ -169,17 +171,17 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the territory this party member is located in. /// - public ExcelResolver Territory => new(this.Struct->TerritoryType); + public RowRef Territory => LuminaUtils.CreateRef(this.Struct->TerritoryType); /// /// Gets the World this party member resides in. /// - public ExcelResolver World => new(this.Struct->HomeWorld); + public RowRef World => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the displayname of this party member. /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref Struct->Name[0]), 0x40); + public SeString Name => SeString.Parse(this.Struct->Name); /// /// Gets the sex of this party member. @@ -189,7 +191,7 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the classjob of this party member. /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->ClassJob); /// /// Gets the level of this party member. diff --git a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs deleted file mode 100644 index 04003d9aa..000000000 --- a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Dalamud.Data; - -using Lumina.Excel; - -namespace Dalamud.Game.ClientState.Resolvers; - -/// -/// This object resolves a rowID within an Excel sheet. -/// -/// The type of Lumina sheet to resolve. -public class ExcelResolver where T : ExcelRow -{ - /// - /// Initializes a new instance of the class. - /// - /// The ID of the classJob. - internal ExcelResolver(uint id) - { - this.Id = id; - } - - /// - /// Gets the ID to be resolved. - /// - public uint Id { get; } - - /// - /// Gets GameData linked to this excel row. - /// - public T? GameData => Service.Get().GetExcelSheet()?.GetRow(this.Id); - - /// - /// Gets GameData linked to this excel row with the specified language. - /// - /// The language. - /// The ExcelRow in the specified language. - public T? GetWithLanguage(ClientLanguage language) => Service.Get().GetExcelSheet(language)?.GetRow(this.Id); -} diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index ad1a24b6a..c3493ce55 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -1,6 +1,8 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; + +using Lumina.Excel; namespace Dalamud.Game.ClientState.Statuses; @@ -31,7 +33,7 @@ public unsafe class Status /// /// Gets the GameData associated with this status. /// - public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusId).GameData; + public RowRef GameData => LuminaUtils.CreateRef(this.Struct->StatusId); /// /// Gets the parameter value of the status. @@ -40,8 +42,10 @@ public unsafe class Status /// /// Gets the stack count of this status. + /// Only valid if this is a non-food status. /// - public byte StackCount => this.Struct->StackCount; + [Obsolete($"Replaced with {nameof(Param)}", true)] + public byte StackCount => (byte)this.Struct->Param; /// /// Gets the time remaining of this status. diff --git a/Dalamud/Game/Command/CommandInfo.cs b/Dalamud/Game/Command/CommandInfo.cs index 8aed817d0..16462a831 100644 --- a/Dalamud/Game/Command/CommandInfo.cs +++ b/Dalamud/Game/Command/CommandInfo.cs @@ -11,7 +11,7 @@ public interface IReadOnlyCommandInfo /// The command itself. /// The arguments supplied to the command, ready for parsing. public delegate void HandlerDelegate(string command, string arguments); - + /// /// Gets a which will be called when the command is dispatched. /// @@ -26,6 +26,11 @@ public interface IReadOnlyCommandInfo /// Gets a value indicating whether if this command should be shown in the help output. /// bool ShowInHelp { get; } + + /// + /// Gets the display order of this command. Defaults to alphabetical ordering. + /// + int DisplayOrder { get; } } /// @@ -51,4 +56,7 @@ public sealed class CommandInfo : IReadOnlyCommandInfo /// public bool ShowInHelp { get; set; } = true; + + /// + public int DisplayOrder { get; set; } = -1; } diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index aa6798171..fdaa5833b 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -2,17 +2,19 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text.RegularExpressions; using Dalamud.Console; -using Dalamud.Game.Gui; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.Shell; namespace Dalamud.Game.Command; @@ -20,38 +22,26 @@ namespace Dalamud.Game.Command; /// This class manages registered in-game slash commands. /// [ServiceManager.EarlyLoadedService] -internal sealed class CommandManager : IInternalDisposableService, ICommandManager +internal sealed unsafe class CommandManager : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); private readonly ConcurrentDictionary commandMap = new(); private readonly ConcurrentDictionary<(string, IReadOnlyCommandInfo), string> commandAssemblyNameMap = new(); - private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); - private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); - private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); - private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); - private readonly Regex commandRegexCn = new(@"^^(“|「)(?.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); - private readonly Regex currentLangCommandRegex; - [ServiceManager.ServiceDependency] - private readonly ChatGui chatGui = Service.Get(); - + private readonly Hook? tryInvokeDebugCommandHook; + [ServiceManager.ServiceDependency] private readonly ConsoleManager console = Service.Get(); [ServiceManager.ServiceConstructor] private CommandManager(Dalamud dalamud) { - this.currentLangCommandRegex = (ClientLanguage)dalamud.StartInfo.Language switch - { - ClientLanguage.Japanese => this.commandRegexJp, - ClientLanguage.English => this.commandRegexEn, - ClientLanguage.German => this.commandRegexDe, - ClientLanguage.French => this.commandRegexFr, - _ => this.commandRegexEn, - }; + this.tryInvokeDebugCommandHook = Hook.FromAddress( + (nint)ShellCommands.MemberFunctionPointers.TryInvokeDebugCommand, + this.OnTryInvokeDebugCommand); + this.tryInvokeDebugCommandHook.Enable(); - this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled; this.console.Invoke += this.ConsoleOnInvoke; } @@ -113,7 +103,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); } } - + /// /// Add a command handler, which you can use to add your own custom commands to the in-game chat. /// @@ -131,7 +121,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag Log.Error("Command {CommandName} is already registered", command); return false; } - + if (!this.commandAssemblyNameMap.TryAdd((command, info), loaderAssemblyName)) { this.commandMap.Remove(command, out _); @@ -160,6 +150,11 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag /// public bool RemoveHandler(string command) { + if (this.commandAssemblyNameMap.FindFirst(c => c.Key.Item1 == command, out var assemblyKeyValuePair)) + { + this.commandAssemblyNameMap.TryRemove(assemblyKeyValuePair.Key, out _); + } + return this.commandMap.Remove(command, out _); } @@ -184,7 +179,8 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag /// /// The name of the assembly. /// A list of commands and their associated activation string. - public List> GetHandlersByAssemblyName(string assemblyName) + public List> GetHandlersByAssemblyName( + string assemblyName) { return this.commandAssemblyNameMap.Where(c => c.Value == assemblyName).ToList(); } @@ -193,37 +189,20 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag void IInternalDisposableService.DisposeService() { this.console.Invoke -= this.ConsoleOnInvoke; - this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; + this.tryInvokeDebugCommandHook?.Dispose(); } - + private bool ConsoleOnInvoke(string arg) { return arg.StartsWith('/') && this.ProcessCommand(arg); } - private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) + private int OnTryInvokeDebugCommand(ShellCommands* self, Utf8String* command, UIModule* uiModule) { - if (type == XivChatType.ErrorMessage && timestamp == 0) - { - var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) - { - // Yes, it's a chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; - } - else - { - // Always match for china, since they patch in language files without changing the ClientLanguage. - cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) - { - // Yes, it's a Chinese fallback chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; - } - } - } + var result = this.tryInvokeDebugCommandHook!.OriginalDisposeSafe(self, command, uiModule); + if (result != -1) return result; + + return this.ProcessCommand(command->ToString()) ? 0 : result; } } @@ -238,7 +217,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); - + [ServiceManager.ServiceDependency] private readonly CommandManager commandManagerService = Service.Get(); @@ -253,10 +232,10 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand { this.pluginInfo = localPlugin; } - + /// public ReadOnlyDictionary Commands => this.commandManagerService.Commands; - + /// void IInternalDisposableService.DisposeService() { @@ -264,7 +243,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand { this.commandManagerService.RemoveHandler(command); } - + this.pluginRegisteredCommands.Clear(); } @@ -275,7 +254,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand /// public void DispatchCommand(string command, string argument, IReadOnlyCommandInfo info) => this.commandManagerService.DispatchCommand(command, argument, info); - + /// public bool AddHandler(string command, CommandInfo info) { @@ -294,7 +273,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand return false; } - + /// public bool RemoveHandler(string command) { diff --git a/Dalamud/Game/Config/GameConfig.cs b/Dalamud/Game/Config/GameConfig.cs index bfb58fd3c..9579d84bc 100644 --- a/Dalamud/Game/Config/GameConfig.cs +++ b/Dalamud/Game/Config/GameConfig.cs @@ -121,7 +121,10 @@ internal sealed class GameConfig : IInternalDisposableService, IGameConfig /// public bool TryGet(SystemConfigOption option, out StringConfigProperties? properties) => this.System.TryGetProperties(option.GetName(), out properties); - + + /// + public bool TryGet(SystemConfigOption option, out PadButtonValue value) => this.System.TryGetStringAsEnum(option.GetName(), out value); + /// public bool TryGet(UiConfigOption option, out bool value) => this.UiConfig.TryGet(option.GetName(), out value); @@ -346,7 +349,11 @@ internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig /// public bool TryGet(SystemConfigOption option, out StringConfigProperties? properties) => this.gameConfigService.TryGet(option, out properties); - + + /// + public bool TryGet(SystemConfigOption option, out PadButtonValue value) + => this.gameConfigService.TryGet(option, out value); + /// public bool TryGet(UiConfigOption option, out bool value) => this.gameConfigService.TryGet(option, out value); diff --git a/Dalamud/Game/Config/GameConfigAddressResolver.cs b/Dalamud/Game/Config/GameConfigAddressResolver.cs index c171932a9..2491c4033 100644 --- a/Dalamud/Game/Config/GameConfigAddressResolver.cs +++ b/Dalamud/Game/Config/GameConfigAddressResolver.cs @@ -13,6 +13,6 @@ internal sealed class GameConfigAddressResolver : BaseAddressResolver /// protected override void Setup64Bit(ISigScanner scanner) { - this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); + this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); // unnamed in CS } } diff --git a/Dalamud/Game/Config/GameConfigSection.cs b/Dalamud/Game/Config/GameConfigSection.cs index 31e4a0b3f..8ebab8a60 100644 --- a/Dalamud/Game/Config/GameConfigSection.cs +++ b/Dalamud/Game/Config/GameConfigSection.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Text; using Dalamud.Memory; using Dalamud.Utility; @@ -51,7 +52,7 @@ public class GameConfigSection /// /// Event which is fired when a game config option is changed within the section. /// - internal event EventHandler? Changed; + internal event EventHandler? Changed; /// /// Gets the number of config entries contained within the section. @@ -357,6 +358,40 @@ public class GameConfigSection return value; } + /// Attempts to get a string config value as an enum value. + /// Name of the config option. + /// The returned value of the config option. + /// Type of the enum. Name of each enum fields are compared against. + /// A value representing the success. + public unsafe bool TryGetStringAsEnum(string name, out T value) where T : struct, Enum + { + value = default; + if (!this.TryGetIndex(name, out var index)) + { + return false; + } + + if (!this.TryGetEntry(index, out var entry)) + { + return false; + } + + if (entry->Type != 4) + { + return false; + } + + if (entry->Value.String == null) + { + return false; + } + + var n8 = entry->Value.String->AsSpan(); + Span n16 = stackalloc char[Encoding.UTF8.GetCharCount(n8)]; + Encoding.UTF8.GetChars(n8, n16); + return Enum.TryParse(n16, out value); + } + /// /// Set a string config option. /// Note: Not all config options will be be immediately reflected in the game. @@ -491,8 +526,8 @@ public class GameConfigSection { if (!this.enumMap.TryGetValue(entry->Index, out var enumObject)) { - if (entry->Name == null) return null; - var name = MemoryHelper.ReadStringNullTerminated(new IntPtr(entry->Name)); + if (entry->Name.Value == null) return null; + var name = entry->Name.ToString(); if (Enum.TryParse(typeof(TEnum), name, out enumObject)) { this.enumMap.TryAdd(entry->Index, enumObject); @@ -509,7 +544,7 @@ public class GameConfigSection this.Changed?.InvokeSafely(this, eventArgs); return eventArgs; } - + private unsafe bool TryGetIndex(string name, out uint index) { if (this.indexMap.TryGetValue(name, out index)) @@ -521,12 +556,12 @@ public class GameConfigSection var e = configBase->ConfigEntry; for (var i = 0U; i < configBase->ConfigCount; i++, e++) { - if (e->Name == null) + if (e->Name.Value == null) { continue; } - var eName = MemoryHelper.ReadStringNullTerminated(new IntPtr(e->Name)); + var eName = e->Name.ToString(); if (eName.Equals(name)) { this.indexMap.TryAdd(name, i); diff --git a/Dalamud/Game/Config/PadButtonValue.cs b/Dalamud/Game/Config/PadButtonValue.cs new file mode 100644 index 000000000..bd6da48bb --- /dev/null +++ b/Dalamud/Game/Config/PadButtonValue.cs @@ -0,0 +1,85 @@ +namespace Dalamud.Game.Config; + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo + +/// Valid values for PadButton options under . +/// Names are the valid part. Enum values are exclusively for use with current Dalamud version. +public enum PadButtonValue +{ + /// Auto-run. + Autorun_Support, + + /// Change Hotbar Set. + Hotbar_Set_Change, + + /// Highlight Left Hotbar. + XHB_Left_Start, + + /// Highlight Right Hotbar. + XHB_Right_Start, + + /// Not directly referenced by Gamepad button customization window. + Cursor_Operation, + + /// Draw Weapon/Lock On. + Lockon_and_Sword, + + /// Sit/Lock On. + Lockon_and_Sit, + + /// Change Camera. + Camera_Modechange, + + /// Reset Camera Position. + Camera_Reset, + + /// Draw/Sheathe Weapon. + Drawn_Sword, + + /// Lock On. + Camera_Lockononly, + + /// Face Target. + FaceTarget, + + /// Assist Target. + AssistTarget, + + /// Face Camera. + LookCamera, + + /// Execute Macro #98 (Exclusive). + Macro98, + + /// Execute Macro #99 (Exclusive). + Macro99, + + /// Not Assigned. + Notset, + + /// Jump/Cancel Casting. + Jump, + + /// Select Target/Confirm. + Accept, + + /// Cancel. + Cancel, + + /// Open Map/Subcommands. + Map_Sub, + + /// Open Main Menu. + MainCommand, + + /// Select HUD. + HUD_Select, + + /// Move Character. + Move_Operation, + + /// Move Camera. + Camera_Operation, +} diff --git a/Dalamud/Game/Config/SystemConfigOption.cs b/Dalamud/Game/Config/SystemConfigOption.cs index 5305e06d9..154992637 100644 --- a/Dalamud/Game/Config/SystemConfigOption.cs +++ b/Dalamud/Game/Config/SystemConfigOption.cs @@ -597,6 +597,20 @@ public enum SystemConfigOption [GameConfigOption("EnablePsFunction", ConfigType.UInt)] EnablePsFunction, + /// + /// System option with the internal name ActiveInstanceGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveInstanceGuid", ConfigType.String)] + ActiveInstanceGuid, + + /// + /// System option with the internal name ActiveProductGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveProductGuid", ConfigType.String)] + ActiveProductGuid, + /// /// System option with the internal name WaterWet. /// This option is a UInt. @@ -996,6 +1010,27 @@ public enum SystemConfigOption [GameConfigOption("AutoChangeCameraMode", ConfigType.UInt)] AutoChangeCameraMode, + /// + /// System option with the internal name MsqProgress. + /// This option is a UInt. + /// + [GameConfigOption("MsqProgress", ConfigType.UInt)] + MsqProgress, + + /// + /// System option with the internal name PromptConfigUpdate. + /// This option is a UInt. + /// + [GameConfigOption("PromptConfigUpdate", ConfigType.UInt)] + PromptConfigUpdate, + + /// + /// System option with the internal name TitleScreenType. + /// This option is a UInt. + /// + [GameConfigOption("TitleScreenType", ConfigType.UInt)] + TitleScreenType, + /// /// System option with the internal name AccessibilitySoundVisualEnable. /// This option is a UInt. @@ -1059,6 +1094,13 @@ public enum SystemConfigOption [GameConfigOption("IdlingCameraAFK", ConfigType.UInt)] IdlingCameraAFK, + /// + /// System option with the internal name FirstConfigBackup. + /// This option is a UInt. + /// + [GameConfigOption("FirstConfigBackup", ConfigType.UInt)] + FirstConfigBackup, + /// /// System option with the internal name MouseSpeed. /// This option is a Float. diff --git a/Dalamud/Game/Config/UiConfigOption.cs b/Dalamud/Game/Config/UiConfigOption.cs index bf329bd6c..1a59b8945 100644 --- a/Dalamud/Game/Config/UiConfigOption.cs +++ b/Dalamud/Game/Config/UiConfigOption.cs @@ -10,3572 +10,3607 @@ public enum UiConfigOption { /// - /// System option with the internal name BattleEffectSelf. + /// UiConfig option with the internal name BattleEffectSelf. /// This option is a UInt. /// [GameConfigOption("BattleEffectSelf", ConfigType.UInt)] BattleEffectSelf, /// - /// System option with the internal name BattleEffectParty. + /// UiConfig option with the internal name BattleEffectParty. /// This option is a UInt. /// [GameConfigOption("BattleEffectParty", ConfigType.UInt)] BattleEffectParty, /// - /// System option with the internal name BattleEffectOther. + /// UiConfig option with the internal name BattleEffectOther. /// This option is a UInt. /// [GameConfigOption("BattleEffectOther", ConfigType.UInt)] BattleEffectOther, /// - /// System option with the internal name BattleEffectPvPEnemyPc. + /// UiConfig option with the internal name BattleEffectPvPEnemyPc. /// This option is a UInt. /// [GameConfigOption("BattleEffectPvPEnemyPc", ConfigType.UInt)] BattleEffectPvPEnemyPc, /// - /// System option with the internal name WeaponAutoPutAway. + /// UiConfig option with the internal name PadMode. + /// This option is a UInt. + /// + [GameConfigOption("PadMode", ConfigType.UInt)] + PadMode, + + /// + /// UiConfig option with the internal name WeaponAutoPutAway. /// This option is a UInt. /// [GameConfigOption("WeaponAutoPutAway", ConfigType.UInt)] WeaponAutoPutAway, /// - /// System option with the internal name WeaponAutoPutAwayTime. + /// UiConfig option with the internal name WeaponAutoPutAwayTime. /// This option is a UInt. /// [GameConfigOption("WeaponAutoPutAwayTime", ConfigType.UInt)] WeaponAutoPutAwayTime, /// - /// System option with the internal name LipMotionType. + /// UiConfig option with the internal name LipMotionType. /// This option is a UInt. /// [GameConfigOption("LipMotionType", ConfigType.UInt)] LipMotionType, /// - /// System option with the internal name FirstPersonDefaultYAngle. + /// UiConfig option with the internal name FirstPersonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultYAngle", ConfigType.Float)] FirstPersonDefaultYAngle, /// - /// System option with the internal name FirstPersonDefaultZoom. + /// UiConfig option with the internal name FirstPersonDefaultZoom. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultZoom", ConfigType.Float)] FirstPersonDefaultZoom, /// - /// System option with the internal name FirstPersonDefaultDistance. + /// UiConfig option with the internal name FirstPersonDefaultDistance. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultDistance", ConfigType.Float)] FirstPersonDefaultDistance, /// - /// System option with the internal name ThirdPersonDefaultYAngle. + /// UiConfig option with the internal name ThirdPersonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultYAngle", ConfigType.Float)] ThirdPersonDefaultYAngle, /// - /// System option with the internal name ThirdPersonDefaultZoom. + /// UiConfig option with the internal name ThirdPersonDefaultZoom. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultZoom", ConfigType.Float)] ThirdPersonDefaultZoom, /// - /// System option with the internal name ThirdPersonDefaultDistance. + /// UiConfig option with the internal name ThirdPersonDefaultDistance. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultDistance", ConfigType.Float)] ThirdPersonDefaultDistance, /// - /// System option with the internal name LockonDefaultYAngle. + /// UiConfig option with the internal name LockonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("LockonDefaultYAngle", ConfigType.Float)] LockonDefaultYAngle, /// - /// System option with the internal name LockonDefaultZoom. + /// UiConfig option with the internal name LockonDefaultZoom. /// This option is a Float. /// [GameConfigOption("LockonDefaultZoom", ConfigType.Float)] LockonDefaultZoom, /// - /// System option with the internal name LockonDefaultZoom_179. - /// This option is a Float. - /// - [GameConfigOption("LockonDefaultZoom_179", ConfigType.Float)] - LockonDefaultZoom_179, - - /// - /// System option with the internal name CameraProductionOfAction. + /// UiConfig option with the internal name CameraProductionOfAction. /// This option is a UInt. /// [GameConfigOption("CameraProductionOfAction", ConfigType.UInt)] CameraProductionOfAction, /// - /// System option with the internal name FPSCameraInterpolationType. + /// UiConfig option with the internal name FPSCameraInterpolationType. /// This option is a UInt. /// [GameConfigOption("FPSCameraInterpolationType", ConfigType.UInt)] FPSCameraInterpolationType, /// - /// System option with the internal name FPSCameraVerticalInterpolation. + /// UiConfig option with the internal name FPSCameraVerticalInterpolation. /// This option is a UInt. /// [GameConfigOption("FPSCameraVerticalInterpolation", ConfigType.UInt)] FPSCameraVerticalInterpolation, /// - /// System option with the internal name LegacyCameraCorrectionFix. + /// UiConfig option with the internal name LegacyCameraCorrectionFix. /// This option is a UInt. /// [GameConfigOption("LegacyCameraCorrectionFix", ConfigType.UInt)] LegacyCameraCorrectionFix, /// - /// System option with the internal name LegacyCameraType. + /// UiConfig option with the internal name LegacyCameraType. /// This option is a UInt. /// [GameConfigOption("LegacyCameraType", ConfigType.UInt)] LegacyCameraType, /// - /// System option with the internal name EventCameraAutoControl. + /// UiConfig option with the internal name EventCameraAutoControl. /// This option is a UInt. /// [GameConfigOption("EventCameraAutoControl", ConfigType.UInt)] EventCameraAutoControl, /// - /// System option with the internal name CameraLookBlinkType. + /// UiConfig option with the internal name CameraLookBlinkType. /// This option is a UInt. /// [GameConfigOption("CameraLookBlinkType", ConfigType.UInt)] CameraLookBlinkType, /// - /// System option with the internal name IdleEmoteTime. + /// UiConfig option with the internal name IdleEmoteTime. /// This option is a UInt. /// [GameConfigOption("IdleEmoteTime", ConfigType.UInt)] IdleEmoteTime, /// - /// System option with the internal name IdleEmoteRandomType. + /// UiConfig option with the internal name IdleEmoteRandomType. /// This option is a UInt. /// [GameConfigOption("IdleEmoteRandomType", ConfigType.UInt)] IdleEmoteRandomType, /// - /// System option with the internal name CutsceneSkipIsShip. + /// UiConfig option with the internal name CutsceneSkipIsShip. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsShip", ConfigType.UInt)] CutsceneSkipIsShip, /// - /// System option with the internal name CutsceneSkipIsContents. + /// UiConfig option with the internal name CutsceneSkipIsContents. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsContents", ConfigType.UInt)] CutsceneSkipIsContents, /// - /// System option with the internal name CutsceneSkipIsHousing. + /// UiConfig option with the internal name CutsceneSkipIsHousing. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsHousing", ConfigType.UInt)] CutsceneSkipIsHousing, /// - /// System option with the internal name PetTargetOffInCombat. + /// UiConfig option with the internal name PetTargetOffInCombat. /// This option is a UInt. /// [GameConfigOption("PetTargetOffInCombat", ConfigType.UInt)] PetTargetOffInCombat, /// - /// System option with the internal name GroundTargetFPSPosX. + /// UiConfig option with the internal name GroundTargetFPSPosX. /// This option is a UInt. /// [GameConfigOption("GroundTargetFPSPosX", ConfigType.UInt)] GroundTargetFPSPosX, /// - /// System option with the internal name GroundTargetFPSPosY. + /// UiConfig option with the internal name GroundTargetFPSPosY. /// This option is a UInt. /// [GameConfigOption("GroundTargetFPSPosY", ConfigType.UInt)] GroundTargetFPSPosY, /// - /// System option with the internal name GroundTargetTPSPosX. + /// UiConfig option with the internal name GroundTargetTPSPosX. /// This option is a UInt. /// [GameConfigOption("GroundTargetTPSPosX", ConfigType.UInt)] GroundTargetTPSPosX, /// - /// System option with the internal name GroundTargetTPSPosY. + /// UiConfig option with the internal name GroundTargetTPSPosY. /// This option is a UInt. /// [GameConfigOption("GroundTargetTPSPosY", ConfigType.UInt)] GroundTargetTPSPosY, /// - /// System option with the internal name TargetDisableAnchor. + /// UiConfig option with the internal name TargetDisableAnchor. /// This option is a UInt. /// [GameConfigOption("TargetDisableAnchor", ConfigType.UInt)] TargetDisableAnchor, /// - /// System option with the internal name TargetCircleClickFilterEnableNearestCursor. + /// UiConfig option with the internal name TargetCircleClickFilterEnableNearestCursor. /// This option is a UInt. /// [GameConfigOption("TargetCircleClickFilterEnableNearestCursor", ConfigType.UInt)] TargetCircleClickFilterEnableNearestCursor, /// - /// System option with the internal name TargetEnableMouseOverSelect. + /// UiConfig option with the internal name TargetEnableMouseOverSelect. /// This option is a UInt. /// [GameConfigOption("TargetEnableMouseOverSelect", ConfigType.UInt)] TargetEnableMouseOverSelect, /// - /// System option with the internal name GroundTargetCursorCorrectType. + /// UiConfig option with the internal name GroundTargetCursorCorrectType. /// This option is a UInt. /// [GameConfigOption("GroundTargetCursorCorrectType", ConfigType.UInt)] GroundTargetCursorCorrectType, /// - /// System option with the internal name GroundTargetActionExcuteType. + /// UiConfig option with the internal name GroundTargetActionExcuteType. /// This option is a UInt. /// [GameConfigOption("GroundTargetActionExcuteType", ConfigType.UInt)] GroundTargetActionExcuteType, /// - /// System option with the internal name AutoNearestTarget. + /// UiConfig option with the internal name AutoNearestTarget. /// This option is a UInt. /// [GameConfigOption("AutoNearestTarget", ConfigType.UInt)] AutoNearestTarget, /// - /// System option with the internal name AutoNearestTargetType. + /// UiConfig option with the internal name AutoNearestTargetType. /// This option is a UInt. /// [GameConfigOption("AutoNearestTargetType", ConfigType.UInt)] AutoNearestTargetType, /// - /// System option with the internal name RightClickExclusionPC. + /// UiConfig option with the internal name RightClickExclusionPC. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionPC", ConfigType.UInt)] RightClickExclusionPC, /// - /// System option with the internal name RightClickExclusionBNPC. + /// UiConfig option with the internal name RightClickExclusionBNPC. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionBNPC", ConfigType.UInt)] RightClickExclusionBNPC, /// - /// System option with the internal name RightClickExclusionMinion. + /// UiConfig option with the internal name RightClickExclusionMinion. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionMinion", ConfigType.UInt)] RightClickExclusionMinion, /// - /// System option with the internal name TurnSpeed. + /// UiConfig option with the internal name EnableMoveTiltCharacter. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)] + EnableMoveTiltCharacter, + + /// + /// UiConfig option with the internal name EnableMoveTiltMountGround. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)] + EnableMoveTiltMountGround, + + /// + /// UiConfig option with the internal name EnableMoveTiltMountFly. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)] + EnableMoveTiltMountFly, + + /// + /// UiConfig option with the internal name TurnSpeed. /// This option is a UInt. /// [GameConfigOption("TurnSpeed", ConfigType.UInt)] TurnSpeed, /// - /// System option with the internal name FootEffect. + /// UiConfig option with the internal name FootEffect. /// This option is a UInt. /// [GameConfigOption("FootEffect", ConfigType.UInt)] FootEffect, /// - /// System option with the internal name LegacySeal. + /// UiConfig option with the internal name LegacySeal. /// This option is a UInt. /// [GameConfigOption("LegacySeal", ConfigType.UInt)] LegacySeal, /// - /// System option with the internal name GBarrelDisp. + /// UiConfig option with the internal name GBarrelDisp. /// This option is a UInt. /// [GameConfigOption("GBarrelDisp", ConfigType.UInt)] GBarrelDisp, /// - /// System option with the internal name EgiMirageTypeGaruda. + /// UiConfig option with the internal name EgiMirageTypeGaruda. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeGaruda", ConfigType.UInt)] EgiMirageTypeGaruda, /// - /// System option with the internal name EgiMirageTypeTitan. + /// UiConfig option with the internal name EgiMirageTypeTitan. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeTitan", ConfigType.UInt)] EgiMirageTypeTitan, /// - /// System option with the internal name EgiMirageTypeIfrit. + /// UiConfig option with the internal name EgiMirageTypeIfrit. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeIfrit", ConfigType.UInt)] EgiMirageTypeIfrit, /// - /// System option with the internal name BahamutSize. + /// UiConfig option with the internal name BahamutSize. /// This option is a UInt. /// [GameConfigOption("BahamutSize", ConfigType.UInt)] BahamutSize, /// - /// System option with the internal name PetMirageTypeCarbuncleSupport. + /// UiConfig option with the internal name PetMirageTypeCarbuncleSupport. /// This option is a UInt. /// [GameConfigOption("PetMirageTypeCarbuncleSupport", ConfigType.UInt)] PetMirageTypeCarbuncleSupport, /// - /// System option with the internal name PhoenixSize. + /// UiConfig option with the internal name PhoenixSize. /// This option is a UInt. /// [GameConfigOption("PhoenixSize", ConfigType.UInt)] PhoenixSize, /// - /// System option with the internal name GarudaSize. + /// UiConfig option with the internal name GarudaSize. /// This option is a UInt. /// [GameConfigOption("GarudaSize", ConfigType.UInt)] GarudaSize, /// - /// System option with the internal name TitanSize. + /// UiConfig option with the internal name TitanSize. /// This option is a UInt. /// [GameConfigOption("TitanSize", ConfigType.UInt)] TitanSize, /// - /// System option with the internal name IfritSize. + /// UiConfig option with the internal name IfritSize. /// This option is a UInt. /// [GameConfigOption("IfritSize", ConfigType.UInt)] IfritSize, /// - /// System option with the internal name SolBahamutSize. + /// UiConfig option with the internal name SolBahamutSize. /// This option is a UInt. /// [GameConfigOption("SolBahamutSize", ConfigType.UInt)] SolBahamutSize, /// - /// System option with the internal name PetMirageTypeFairy. + /// UiConfig option with the internal name PetMirageTypeFairy. /// This option is a UInt. /// [GameConfigOption("PetMirageTypeFairy", ConfigType.UInt)] PetMirageTypeFairy, /// - /// System option with the internal name TimeMode. + /// UiConfig option with the internal name TimeMode. /// This option is a UInt. /// [GameConfigOption("TimeMode", ConfigType.UInt)] TimeMode, /// - /// System option with the internal name Time12. + /// UiConfig option with the internal name Time12. /// This option is a UInt. /// [GameConfigOption("Time12", ConfigType.UInt)] Time12, /// - /// System option with the internal name TimeEorzea. + /// UiConfig option with the internal name TimeEorzea. /// This option is a UInt. /// [GameConfigOption("TimeEorzea", ConfigType.UInt)] TimeEorzea, /// - /// System option with the internal name TimeLocal. + /// UiConfig option with the internal name TimeLocal. /// This option is a UInt. /// [GameConfigOption("TimeLocal", ConfigType.UInt)] TimeLocal, /// - /// System option with the internal name TimeServer. + /// UiConfig option with the internal name TimeServer. /// This option is a UInt. /// [GameConfigOption("TimeServer", ConfigType.UInt)] TimeServer, /// - /// System option with the internal name ActiveLS_H. + /// UiConfig option with the internal name ActiveLS_H. /// This option is a UInt. /// [GameConfigOption("ActiveLS_H", ConfigType.UInt)] ActiveLS_H, /// - /// System option with the internal name ActiveLS_L. + /// UiConfig option with the internal name ActiveLS_L. /// This option is a UInt. /// [GameConfigOption("ActiveLS_L", ConfigType.UInt)] ActiveLS_L, /// - /// System option with the internal name HotbarLock. + /// UiConfig option with the internal name HotbarLock. /// This option is a UInt. /// [GameConfigOption("HotbarLock", ConfigType.UInt)] HotbarLock, /// - /// System option with the internal name HotbarDispRecastTime. + /// UiConfig option with the internal name HotbarDispRecastTime. /// This option is a UInt. /// [GameConfigOption("HotbarDispRecastTime", ConfigType.UInt)] HotbarDispRecastTime, /// - /// System option with the internal name HotbarCrossContentsActionEnableInput. + /// UiConfig option with the internal name HotbarCrossContentsActionEnableInput. /// This option is a UInt. /// [GameConfigOption("HotbarCrossContentsActionEnableInput", ConfigType.UInt)] HotbarCrossContentsActionEnableInput, /// - /// System option with the internal name HotbarDispRecastTimeDispType. + /// UiConfig option with the internal name HotbarDispRecastTimeDispType. /// This option is a UInt. /// [GameConfigOption("HotbarDispRecastTimeDispType", ConfigType.UInt)] HotbarDispRecastTimeDispType, /// - /// System option with the internal name ExHotbarChangeHotbar1. + /// UiConfig option with the internal name ExHotbarChangeHotbar1. /// This option is a UInt. /// [GameConfigOption("ExHotbarChangeHotbar1", ConfigType.UInt)] ExHotbarChangeHotbar1, /// - /// System option with the internal name HotbarCommon01. + /// UiConfig option with the internal name HotbarCommon01. /// This option is a UInt. /// [GameConfigOption("HotbarCommon01", ConfigType.UInt)] HotbarCommon01, /// - /// System option with the internal name HotbarCommon02. + /// UiConfig option with the internal name HotbarCommon02. /// This option is a UInt. /// [GameConfigOption("HotbarCommon02", ConfigType.UInt)] HotbarCommon02, /// - /// System option with the internal name HotbarCommon03. + /// UiConfig option with the internal name HotbarCommon03. /// This option is a UInt. /// [GameConfigOption("HotbarCommon03", ConfigType.UInt)] HotbarCommon03, /// - /// System option with the internal name HotbarCommon04. + /// UiConfig option with the internal name HotbarCommon04. /// This option is a UInt. /// [GameConfigOption("HotbarCommon04", ConfigType.UInt)] HotbarCommon04, /// - /// System option with the internal name HotbarCommon05. + /// UiConfig option with the internal name HotbarCommon05. /// This option is a UInt. /// [GameConfigOption("HotbarCommon05", ConfigType.UInt)] HotbarCommon05, /// - /// System option with the internal name HotbarCommon06. + /// UiConfig option with the internal name HotbarCommon06. /// This option is a UInt. /// [GameConfigOption("HotbarCommon06", ConfigType.UInt)] HotbarCommon06, /// - /// System option with the internal name HotbarCommon07. + /// UiConfig option with the internal name HotbarCommon07. /// This option is a UInt. /// [GameConfigOption("HotbarCommon07", ConfigType.UInt)] HotbarCommon07, /// - /// System option with the internal name HotbarCommon08. + /// UiConfig option with the internal name HotbarCommon08. /// This option is a UInt. /// [GameConfigOption("HotbarCommon08", ConfigType.UInt)] HotbarCommon08, /// - /// System option with the internal name HotbarCommon09. + /// UiConfig option with the internal name HotbarCommon09. /// This option is a UInt. /// [GameConfigOption("HotbarCommon09", ConfigType.UInt)] HotbarCommon09, /// - /// System option with the internal name HotbarCommon10. + /// UiConfig option with the internal name HotbarCommon10. /// This option is a UInt. /// [GameConfigOption("HotbarCommon10", ConfigType.UInt)] HotbarCommon10, /// - /// System option with the internal name HotbarCrossCommon01. + /// UiConfig option with the internal name HotbarCrossCommon01. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon01", ConfigType.UInt)] HotbarCrossCommon01, /// - /// System option with the internal name HotbarCrossCommon02. + /// UiConfig option with the internal name HotbarCrossCommon02. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon02", ConfigType.UInt)] HotbarCrossCommon02, /// - /// System option with the internal name HotbarCrossCommon03. + /// UiConfig option with the internal name HotbarCrossCommon03. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon03", ConfigType.UInt)] HotbarCrossCommon03, /// - /// System option with the internal name HotbarCrossCommon04. + /// UiConfig option with the internal name HotbarCrossCommon04. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon04", ConfigType.UInt)] HotbarCrossCommon04, /// - /// System option with the internal name HotbarCrossCommon05. + /// UiConfig option with the internal name HotbarCrossCommon05. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon05", ConfigType.UInt)] HotbarCrossCommon05, /// - /// System option with the internal name HotbarCrossCommon06. + /// UiConfig option with the internal name HotbarCrossCommon06. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon06", ConfigType.UInt)] HotbarCrossCommon06, /// - /// System option with the internal name HotbarCrossCommon07. + /// UiConfig option with the internal name HotbarCrossCommon07. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon07", ConfigType.UInt)] HotbarCrossCommon07, /// - /// System option with the internal name HotbarCrossCommon08. + /// UiConfig option with the internal name HotbarCrossCommon08. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon08", ConfigType.UInt)] HotbarCrossCommon08, /// - /// System option with the internal name HotbarCrossHelpDisp. + /// UiConfig option with the internal name HotbarCrossHelpDisp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossHelpDisp", ConfigType.UInt)] HotbarCrossHelpDisp, /// - /// System option with the internal name HotbarCrossOperation. + /// UiConfig option with the internal name HotbarCrossOperation. /// This option is a UInt. /// [GameConfigOption("HotbarCrossOperation", ConfigType.UInt)] HotbarCrossOperation, /// - /// System option with the internal name HotbarCrossDisp. + /// UiConfig option with the internal name HotbarCrossDisp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDisp", ConfigType.UInt)] HotbarCrossDisp, /// - /// System option with the internal name HotbarCrossLock. + /// UiConfig option with the internal name HotbarCrossLock. /// This option is a UInt. /// [GameConfigOption("HotbarCrossLock", ConfigType.UInt)] HotbarCrossLock, /// - /// System option with the internal name HotbarCrossUsePadGuide. + /// UiConfig option with the internal name HotbarCrossUsePadGuide. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUsePadGuide", ConfigType.UInt)] HotbarCrossUsePadGuide, /// - /// System option with the internal name HotbarCrossActiveSet. + /// UiConfig option with the internal name HotbarCrossActiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarCrossActiveSet", ConfigType.UInt)] HotbarCrossActiveSet, /// - /// System option with the internal name HotbarCrossActiveSetPvP. + /// UiConfig option with the internal name HotbarCrossActiveSetPvP. /// This option is a UInt. /// [GameConfigOption("HotbarCrossActiveSetPvP", ConfigType.UInt)] HotbarCrossActiveSetPvP, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsAuto. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsAuto. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsAuto", ConfigType.UInt)] HotbarCrossSetChangeCustomIsAuto, /// - /// System option with the internal name HotbarCrossSetChangeCustom. + /// UiConfig option with the internal name HotbarCrossSetChangeCustom. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustom", ConfigType.UInt)] HotbarCrossSetChangeCustom, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet1. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet1. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet1", ConfigType.UInt)] HotbarCrossSetChangeCustomSet1, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet2. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet2. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet2", ConfigType.UInt)] HotbarCrossSetChangeCustomSet2, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet3. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet3. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet3", ConfigType.UInt)] HotbarCrossSetChangeCustomSet3, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet4. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet4. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet4", ConfigType.UInt)] HotbarCrossSetChangeCustomSet4, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet5. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet5. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet5", ConfigType.UInt)] HotbarCrossSetChangeCustomSet5, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet6. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet6. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet6", ConfigType.UInt)] HotbarCrossSetChangeCustomSet6, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet7. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet7. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet7", ConfigType.UInt)] HotbarCrossSetChangeCustomSet7, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet8. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet8. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet8", ConfigType.UInt)] HotbarCrossSetChangeCustomSet8, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSword. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSword. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSword", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSword, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet1. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet1. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet1", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet1, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet2. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet2. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet2", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet2, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet3. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet3. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet3", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet3, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet4. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet4. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet4", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet4, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet5. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet5. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet5", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet5, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet6. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet6. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet6", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet6, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet7. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet7. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet7", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet7, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet8. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet8. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet8", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet8, /// - /// System option with the internal name HotbarCrossAdvancedSetting. + /// UiConfig option with the internal name HotbarCrossAdvancedSetting. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSetting", ConfigType.UInt)] HotbarCrossAdvancedSetting, /// - /// System option with the internal name HotbarCrossAdvancedSettingLeft. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingLeft. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingLeft", ConfigType.UInt)] HotbarCrossAdvancedSettingLeft, /// - /// System option with the internal name HotbarCrossAdvancedSettingRight. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingRight. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingRight", ConfigType.UInt)] HotbarCrossAdvancedSettingRight, /// - /// System option with the internal name HotbarCrossSetPvpModeActive. + /// UiConfig option with the internal name HotbarCrossSetPvpModeActive. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetPvpModeActive", ConfigType.UInt)] HotbarCrossSetPvpModeActive, /// - /// System option with the internal name HotbarCrossSetChangeCustomPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsAutoPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsAutoPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsAutoPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsAutoPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet1Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet1Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet1Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet1Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet2Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet2Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet2Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet2Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet3Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet3Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet3Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet3Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet4Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet4Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet4Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet4Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet5Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet5Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet5Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet5Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet6Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet6Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet6Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet6Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet7Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet7Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet7Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet7Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet8Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet8Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet8Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet8Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet1Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet1Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet1Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet1Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet2Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet2Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet2Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet2Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet3Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet3Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet3Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet3Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet4Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet4Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet4Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet4Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet5Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet5Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet5Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet5Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet6Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet6Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet6Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet6Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet7Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet7Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet7Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet7Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet8Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet8Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet8Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet8Pvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingPvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingLeftPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingLeftPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingLeftPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingLeftPvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingRightPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingRightPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingRightPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingRightPvp, /// - /// System option with the internal name HotbarWXHBEnable. + /// UiConfig option with the internal name HotbarWXHBEnable. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBEnable", ConfigType.UInt)] HotbarWXHBEnable, /// - /// System option with the internal name HotbarWXHBSetLeft. + /// UiConfig option with the internal name HotbarWXHBSetLeft. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetLeft", ConfigType.UInt)] HotbarWXHBSetLeft, /// - /// System option with the internal name HotbarWXHBSetRight. + /// UiConfig option with the internal name HotbarWXHBSetRight. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetRight", ConfigType.UInt)] HotbarWXHBSetRight, /// - /// System option with the internal name HotbarWXHBEnablePvP. + /// UiConfig option with the internal name HotbarWXHBEnablePvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBEnablePvP", ConfigType.UInt)] HotbarWXHBEnablePvP, /// - /// System option with the internal name HotbarWXHBSetLeftPvP. + /// UiConfig option with the internal name HotbarWXHBSetLeftPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetLeftPvP", ConfigType.UInt)] HotbarWXHBSetLeftPvP, /// - /// System option with the internal name HotbarWXHBSetRightPvP. + /// UiConfig option with the internal name HotbarWXHBSetRightPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetRightPvP", ConfigType.UInt)] HotbarWXHBSetRightPvP, /// - /// System option with the internal name HotbarWXHB8Button. + /// UiConfig option with the internal name HotbarWXHB8Button. /// This option is a UInt. /// [GameConfigOption("HotbarWXHB8Button", ConfigType.UInt)] HotbarWXHB8Button, /// - /// System option with the internal name HotbarWXHB8ButtonPvP. + /// UiConfig option with the internal name HotbarWXHB8ButtonPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHB8ButtonPvP", ConfigType.UInt)] HotbarWXHB8ButtonPvP, /// - /// System option with the internal name HotbarWXHBSetInputTime. + /// UiConfig option with the internal name HotbarWXHBSetInputTime. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetInputTime", ConfigType.UInt)] HotbarWXHBSetInputTime, /// - /// System option with the internal name HotbarWXHBDisplay. + /// UiConfig option with the internal name HotbarWXHBDisplay. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBDisplay", ConfigType.UInt)] HotbarWXHBDisplay, /// - /// System option with the internal name HotbarWXHBFreeLayout. + /// UiConfig option with the internal name HotbarWXHBFreeLayout. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBFreeLayout", ConfigType.UInt)] HotbarWXHBFreeLayout, /// - /// System option with the internal name HotbarXHBActiveTransmissionAlpha. + /// UiConfig option with the internal name HotbarXHBActiveTransmissionAlpha. /// This option is a UInt. /// [GameConfigOption("HotbarXHBActiveTransmissionAlpha", ConfigType.UInt)] HotbarXHBActiveTransmissionAlpha, /// - /// System option with the internal name HotbarXHBAlphaDefault. + /// UiConfig option with the internal name HotbarXHBAlphaDefault. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaDefault", ConfigType.UInt)] HotbarXHBAlphaDefault, /// - /// System option with the internal name HotbarXHBAlphaActiveSet. + /// UiConfig option with the internal name HotbarXHBAlphaActiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaActiveSet", ConfigType.UInt)] HotbarXHBAlphaActiveSet, /// - /// System option with the internal name HotbarXHBAlphaInactiveSet. + /// UiConfig option with the internal name HotbarXHBAlphaInactiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaInactiveSet", ConfigType.UInt)] HotbarXHBAlphaInactiveSet, /// - /// System option with the internal name HotbarWXHBInputOnce. + /// UiConfig option with the internal name HotbarWXHBInputOnce. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBInputOnce", ConfigType.UInt)] HotbarWXHBInputOnce, /// - /// System option with the internal name ExHotbarChangeHotbar1IsFashion. + /// UiConfig option with the internal name ExHotbarChangeHotbar1IsFashion. /// This option is a UInt. /// [GameConfigOption("ExHotbarChangeHotbar1IsFashion", ConfigType.UInt)] ExHotbarChangeHotbar1IsFashion, /// - /// System option with the internal name HotbarCrossUseExDirectionAutoSwitch. + /// UiConfig option with the internal name HotbarCrossUseExDirectionAutoSwitch. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseExDirectionAutoSwitch", ConfigType.UInt)] HotbarCrossUseExDirectionAutoSwitch, /// - /// System option with the internal name IdlingCameraSwitchType. + /// UiConfig option with the internal name IdlingCameraSwitchType. /// This option is a UInt. /// [GameConfigOption("IdlingCameraSwitchType", ConfigType.UInt)] IdlingCameraSwitchType, /// - /// System option with the internal name HotbarXHBEditEnable. + /// UiConfig option with the internal name HotbarXHBEditEnable. /// This option is a UInt. /// [GameConfigOption("HotbarXHBEditEnable", ConfigType.UInt)] HotbarXHBEditEnable, /// - /// System option with the internal name PlateType. + /// UiConfig option with the internal name HotbarContentsAction2ReverseOperation. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReverseOperation", ConfigType.UInt)] + HotbarContentsAction2ReverseOperation, + + /// + /// UiConfig option with the internal name HotbarContentsAction2ReturnInitialSlot. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReturnInitialSlot", ConfigType.UInt)] + HotbarContentsAction2ReturnInitialSlot, + + /// + /// UiConfig option with the internal name HotbarContentsAction2ReverseRotate. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReverseRotate", ConfigType.UInt)] + HotbarContentsAction2ReverseRotate, + + /// + /// UiConfig option with the internal name PlateType. /// This option is a UInt. /// [GameConfigOption("PlateType", ConfigType.UInt)] PlateType, /// - /// System option with the internal name PlateDispHPBar. + /// UiConfig option with the internal name PlateDispHPBar. /// This option is a UInt. /// [GameConfigOption("PlateDispHPBar", ConfigType.UInt)] PlateDispHPBar, /// - /// System option with the internal name PlateDisableMaxHPBar. + /// UiConfig option with the internal name PlateDisableMaxHPBar. /// This option is a UInt. /// [GameConfigOption("PlateDisableMaxHPBar", ConfigType.UInt)] PlateDisableMaxHPBar, /// - /// System option with the internal name NamePlateHpSizeType. + /// UiConfig option with the internal name NamePlateHpSizeType. /// This option is a UInt. /// [GameConfigOption("NamePlateHpSizeType", ConfigType.UInt)] NamePlateHpSizeType, /// - /// System option with the internal name NamePlateColorSelf. + /// UiConfig option with the internal name NamePlateColorSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelf", ConfigType.UInt)] NamePlateColorSelf, /// - /// System option with the internal name NamePlateEdgeSelf. + /// UiConfig option with the internal name NamePlateEdgeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelf", ConfigType.UInt)] NamePlateEdgeSelf, /// - /// System option with the internal name NamePlateDispTypeSelf. + /// UiConfig option with the internal name NamePlateDispTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelf", ConfigType.UInt)] NamePlateDispTypeSelf, /// - /// System option with the internal name NamePlateNameTypeSelf. + /// UiConfig option with the internal name NamePlateNameTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeSelf", ConfigType.UInt)] NamePlateNameTypeSelf, /// - /// System option with the internal name NamePlateHpTypeSelf. + /// UiConfig option with the internal name NamePlateHpTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelf", ConfigType.UInt)] NamePlateHpTypeSelf, /// - /// System option with the internal name NamePlateColorSelfBuddy. + /// UiConfig option with the internal name NamePlateColorSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelfBuddy", ConfigType.UInt)] NamePlateColorSelfBuddy, /// - /// System option with the internal name NamePlateEdgeSelfBuddy. + /// UiConfig option with the internal name NamePlateEdgeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelfBuddy", ConfigType.UInt)] NamePlateEdgeSelfBuddy, /// - /// System option with the internal name NamePlateDispTypeSelfBuddy. + /// UiConfig option with the internal name NamePlateDispTypeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelfBuddy", ConfigType.UInt)] NamePlateDispTypeSelfBuddy, /// - /// System option with the internal name NamePlateHpTypeSelfBuddy. + /// UiConfig option with the internal name NamePlateHpTypeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelfBuddy", ConfigType.UInt)] NamePlateHpTypeSelfBuddy, /// - /// System option with the internal name NamePlateColorSelfPet. + /// UiConfig option with the internal name NamePlateColorSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelfPet", ConfigType.UInt)] NamePlateColorSelfPet, /// - /// System option with the internal name NamePlateEdgeSelfPet. + /// UiConfig option with the internal name NamePlateEdgeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelfPet", ConfigType.UInt)] NamePlateEdgeSelfPet, /// - /// System option with the internal name NamePlateDispTypeSelfPet. + /// UiConfig option with the internal name NamePlateDispTypeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelfPet", ConfigType.UInt)] NamePlateDispTypeSelfPet, /// - /// System option with the internal name NamePlateHpTypeSelfPet. + /// UiConfig option with the internal name NamePlateHpTypeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelfPet", ConfigType.UInt)] NamePlateHpTypeSelfPet, /// - /// System option with the internal name NamePlateColorParty. + /// UiConfig option with the internal name NamePlateColorParty. /// This option is a UInt. /// [GameConfigOption("NamePlateColorParty", ConfigType.UInt)] NamePlateColorParty, /// - /// System option with the internal name NamePlateEdgeParty. + /// UiConfig option with the internal name NamePlateEdgeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeParty", ConfigType.UInt)] NamePlateEdgeParty, /// - /// System option with the internal name NamePlateDispTypeParty. + /// UiConfig option with the internal name NamePlateDispTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeParty", ConfigType.UInt)] NamePlateDispTypeParty, /// - /// System option with the internal name NamePlateNameTypeParty. + /// UiConfig option with the internal name NamePlateNameTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeParty", ConfigType.UInt)] NamePlateNameTypeParty, /// - /// System option with the internal name NamePlateHpTypeParty. + /// UiConfig option with the internal name NamePlateHpTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeParty", ConfigType.UInt)] NamePlateHpTypeParty, /// - /// System option with the internal name NamePlateDispTypePartyPet. + /// UiConfig option with the internal name NamePlateDispTypePartyPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypePartyPet", ConfigType.UInt)] NamePlateDispTypePartyPet, /// - /// System option with the internal name NamePlateHpTypePartyPet. + /// UiConfig option with the internal name NamePlateHpTypePartyPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypePartyPet", ConfigType.UInt)] NamePlateHpTypePartyPet, /// - /// System option with the internal name NamePlateDispTypePartyBuddy. + /// UiConfig option with the internal name NamePlateDispTypePartyBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypePartyBuddy", ConfigType.UInt)] NamePlateDispTypePartyBuddy, /// - /// System option with the internal name NamePlateHpTypePartyBuddy. + /// UiConfig option with the internal name NamePlateHpTypePartyBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypePartyBuddy", ConfigType.UInt)] NamePlateHpTypePartyBuddy, /// - /// System option with the internal name NamePlateColorAlliance. + /// UiConfig option with the internal name NamePlateColorAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateColorAlliance", ConfigType.UInt)] NamePlateColorAlliance, /// - /// System option with the internal name NamePlateEdgeAlliance. + /// UiConfig option with the internal name NamePlateEdgeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeAlliance", ConfigType.UInt)] NamePlateEdgeAlliance, /// - /// System option with the internal name NamePlateDispTypeAlliance. + /// UiConfig option with the internal name NamePlateDispTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeAlliance", ConfigType.UInt)] NamePlateDispTypeAlliance, /// - /// System option with the internal name NamePlateNameTypeAlliance. + /// UiConfig option with the internal name NamePlateNameTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeAlliance", ConfigType.UInt)] NamePlateNameTypeAlliance, /// - /// System option with the internal name NamePlateHpTypeAlliance. + /// UiConfig option with the internal name NamePlateHpTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeAlliance", ConfigType.UInt)] NamePlateHpTypeAlliance, /// - /// System option with the internal name NamePlateDispTypeAlliancePet. + /// UiConfig option with the internal name NamePlateDispTypeAlliancePet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeAlliancePet", ConfigType.UInt)] NamePlateDispTypeAlliancePet, /// - /// System option with the internal name NamePlateHpTypeAlliancePet. + /// UiConfig option with the internal name NamePlateHpTypeAlliancePet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeAlliancePet", ConfigType.UInt)] NamePlateHpTypeAlliancePet, /// - /// System option with the internal name NamePlateColorOther. + /// UiConfig option with the internal name NamePlateColorOther. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOther", ConfigType.UInt)] NamePlateColorOther, /// - /// System option with the internal name NamePlateEdgeOther. + /// UiConfig option with the internal name NamePlateEdgeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOther", ConfigType.UInt)] NamePlateEdgeOther, /// - /// System option with the internal name NamePlateDispTypeOther. + /// UiConfig option with the internal name NamePlateDispTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOther", ConfigType.UInt)] NamePlateDispTypeOther, /// - /// System option with the internal name NamePlateNameTypeOther. + /// UiConfig option with the internal name NamePlateNameTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeOther", ConfigType.UInt)] NamePlateNameTypeOther, /// - /// System option with the internal name NamePlateHpTypeOther. + /// UiConfig option with the internal name NamePlateHpTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOther", ConfigType.UInt)] NamePlateHpTypeOther, /// - /// System option with the internal name NamePlateDispTypeOtherPet. + /// UiConfig option with the internal name NamePlateDispTypeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOtherPet", ConfigType.UInt)] NamePlateDispTypeOtherPet, /// - /// System option with the internal name NamePlateHpTypeOtherPet. + /// UiConfig option with the internal name NamePlateHpTypeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOtherPet", ConfigType.UInt)] NamePlateHpTypeOtherPet, /// - /// System option with the internal name NamePlateDispTypeOtherBuddy. + /// UiConfig option with the internal name NamePlateDispTypeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOtherBuddy", ConfigType.UInt)] NamePlateDispTypeOtherBuddy, /// - /// System option with the internal name NamePlateHpTypeOtherBuddy. + /// UiConfig option with the internal name NamePlateHpTypeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOtherBuddy", ConfigType.UInt)] NamePlateHpTypeOtherBuddy, /// - /// System option with the internal name NamePlateColorUnengagedEnemy. + /// UiConfig option with the internal name NamePlateColorUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUnengagedEnemy", ConfigType.UInt)] NamePlateColorUnengagedEnemy, /// - /// System option with the internal name NamePlateEdgeUnengagedEnemy. + /// UiConfig option with the internal name NamePlateEdgeUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeUnengagedEnemy", ConfigType.UInt)] NamePlateEdgeUnengagedEnemy, /// - /// System option with the internal name NamePlateDispTypeUnengagedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeUnengagedEnemy", ConfigType.UInt)] NamePlateDispTypeUnengagedEnemy, /// - /// System option with the internal name NamePlateHpTypeUnengagedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeUnengagedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeUnengagedEmemy", ConfigType.UInt)] NamePlateHpTypeUnengagedEmemy, /// - /// System option with the internal name NamePlateColorEngagedEnemy. + /// UiConfig option with the internal name NamePlateColorEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorEngagedEnemy", ConfigType.UInt)] NamePlateColorEngagedEnemy, /// - /// System option with the internal name NamePlateEdgeEngagedEnemy. + /// UiConfig option with the internal name NamePlateEdgeEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeEngagedEnemy", ConfigType.UInt)] NamePlateEdgeEngagedEnemy, /// - /// System option with the internal name NamePlateDispTypeEngagedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeEngagedEnemy", ConfigType.UInt)] NamePlateDispTypeEngagedEnemy, /// - /// System option with the internal name NamePlateHpTypeEngagedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeEngagedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeEngagedEmemy", ConfigType.UInt)] NamePlateHpTypeEngagedEmemy, /// - /// System option with the internal name NamePlateColorClaimedEnemy. + /// UiConfig option with the internal name NamePlateColorClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorClaimedEnemy", ConfigType.UInt)] NamePlateColorClaimedEnemy, /// - /// System option with the internal name NamePlateEdgeClaimedEnemy. + /// UiConfig option with the internal name NamePlateEdgeClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeClaimedEnemy", ConfigType.UInt)] NamePlateEdgeClaimedEnemy, /// - /// System option with the internal name NamePlateDispTypeClaimedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeClaimedEnemy", ConfigType.UInt)] NamePlateDispTypeClaimedEnemy, /// - /// System option with the internal name NamePlateHpTypeClaimedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeClaimedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeClaimedEmemy", ConfigType.UInt)] NamePlateHpTypeClaimedEmemy, /// - /// System option with the internal name NamePlateColorUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateColorUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUnclaimedEnemy", ConfigType.UInt)] NamePlateColorUnclaimedEnemy, /// - /// System option with the internal name NamePlateEdgeUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateEdgeUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeUnclaimedEnemy", ConfigType.UInt)] NamePlateEdgeUnclaimedEnemy, /// - /// System option with the internal name NamePlateDispTypeUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeUnclaimedEnemy", ConfigType.UInt)] NamePlateDispTypeUnclaimedEnemy, /// - /// System option with the internal name NamePlateHpTypeUnclaimedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeUnclaimedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeUnclaimedEmemy", ConfigType.UInt)] NamePlateHpTypeUnclaimedEmemy, /// - /// System option with the internal name NamePlateColorNpc. + /// UiConfig option with the internal name NamePlateColorNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateColorNpc", ConfigType.UInt)] NamePlateColorNpc, /// - /// System option with the internal name NamePlateEdgeNpc. + /// UiConfig option with the internal name NamePlateEdgeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeNpc", ConfigType.UInt)] NamePlateEdgeNpc, /// - /// System option with the internal name NamePlateDispTypeNpc. + /// UiConfig option with the internal name NamePlateDispTypeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeNpc", ConfigType.UInt)] NamePlateDispTypeNpc, /// - /// System option with the internal name NamePlateHpTypeNpc. + /// UiConfig option with the internal name NamePlateHpTypeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeNpc", ConfigType.UInt)] NamePlateHpTypeNpc, /// - /// System option with the internal name NamePlateColorObject. + /// UiConfig option with the internal name NamePlateColorObject. /// This option is a UInt. /// [GameConfigOption("NamePlateColorObject", ConfigType.UInt)] NamePlateColorObject, /// - /// System option with the internal name NamePlateEdgeObject. + /// UiConfig option with the internal name NamePlateEdgeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeObject", ConfigType.UInt)] NamePlateEdgeObject, /// - /// System option with the internal name NamePlateDispTypeObject. + /// UiConfig option with the internal name NamePlateDispTypeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeObject", ConfigType.UInt)] NamePlateDispTypeObject, /// - /// System option with the internal name NamePlateHpTypeObject. + /// UiConfig option with the internal name NamePlateHpTypeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeObject", ConfigType.UInt)] NamePlateHpTypeObject, /// - /// System option with the internal name NamePlateColorMinion. + /// UiConfig option with the internal name NamePlateColorMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateColorMinion", ConfigType.UInt)] NamePlateColorMinion, /// - /// System option with the internal name NamePlateEdgeMinion. + /// UiConfig option with the internal name NamePlateEdgeMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeMinion", ConfigType.UInt)] NamePlateEdgeMinion, /// - /// System option with the internal name NamePlateDispTypeMinion. + /// UiConfig option with the internal name NamePlateDispTypeMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeMinion", ConfigType.UInt)] NamePlateDispTypeMinion, /// - /// System option with the internal name NamePlateColorOtherBuddy. + /// UiConfig option with the internal name NamePlateColorOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherBuddy", ConfigType.UInt)] NamePlateColorOtherBuddy, /// - /// System option with the internal name NamePlateEdgeOtherBuddy. + /// UiConfig option with the internal name NamePlateEdgeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherBuddy", ConfigType.UInt)] NamePlateEdgeOtherBuddy, /// - /// System option with the internal name NamePlateColorOtherPet. + /// UiConfig option with the internal name NamePlateColorOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherPet", ConfigType.UInt)] NamePlateColorOtherPet, /// - /// System option with the internal name NamePlateEdgeOtherPet. + /// UiConfig option with the internal name NamePlateEdgeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherPet", ConfigType.UInt)] NamePlateEdgeOtherPet, /// - /// System option with the internal name NamePlateNameTitleTypeSelf. + /// UiConfig option with the internal name NamePlateNameTitleTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeSelf", ConfigType.UInt)] NamePlateNameTitleTypeSelf, /// - /// System option with the internal name NamePlateNameTitleTypeParty. + /// UiConfig option with the internal name NamePlateNameTitleTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeParty", ConfigType.UInt)] NamePlateNameTitleTypeParty, /// - /// System option with the internal name NamePlateNameTitleTypeAlliance. + /// UiConfig option with the internal name NamePlateNameTitleTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeAlliance", ConfigType.UInt)] NamePlateNameTitleTypeAlliance, /// - /// System option with the internal name NamePlateNameTitleTypeOther. + /// UiConfig option with the internal name NamePlateNameTitleTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeOther", ConfigType.UInt)] NamePlateNameTitleTypeOther, /// - /// System option with the internal name NamePlateNameTitleTypeFriend. + /// UiConfig option with the internal name NamePlateNameTitleTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeFriend", ConfigType.UInt)] NamePlateNameTitleTypeFriend, /// - /// System option with the internal name NamePlateColorFriend. + /// UiConfig option with the internal name NamePlateColorFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateColorFriend", ConfigType.UInt)] NamePlateColorFriend, /// - /// System option with the internal name NamePlateColorFriendEdge. + /// UiConfig option with the internal name NamePlateColorFriendEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorFriendEdge", ConfigType.UInt)] NamePlateColorFriendEdge, /// - /// System option with the internal name NamePlateDispTypeFriend. + /// UiConfig option with the internal name NamePlateDispTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriend", ConfigType.UInt)] NamePlateDispTypeFriend, /// - /// System option with the internal name NamePlateNameTypeFriend. + /// UiConfig option with the internal name NamePlateNameTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeFriend", ConfigType.UInt)] NamePlateNameTypeFriend, /// - /// System option with the internal name NamePlateHpTypeFriend. + /// UiConfig option with the internal name NamePlateHpTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriend", ConfigType.UInt)] NamePlateHpTypeFriend, /// - /// System option with the internal name NamePlateDispTypeFriendPet. + /// UiConfig option with the internal name NamePlateDispTypeFriendPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriendPet", ConfigType.UInt)] NamePlateDispTypeFriendPet, /// - /// System option with the internal name NamePlateHpTypeFriendPet. + /// UiConfig option with the internal name NamePlateHpTypeFriendPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriendPet", ConfigType.UInt)] NamePlateHpTypeFriendPet, /// - /// System option with the internal name NamePlateDispTypeFriendBuddy. + /// UiConfig option with the internal name NamePlateDispTypeFriendBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriendBuddy", ConfigType.UInt)] NamePlateDispTypeFriendBuddy, /// - /// System option with the internal name NamePlateHpTypeFriendBuddy. + /// UiConfig option with the internal name NamePlateHpTypeFriendBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriendBuddy", ConfigType.UInt)] NamePlateHpTypeFriendBuddy, /// - /// System option with the internal name NamePlateColorLim. + /// UiConfig option with the internal name NamePlateColorLim. /// This option is a UInt. /// [GameConfigOption("NamePlateColorLim", ConfigType.UInt)] NamePlateColorLim, /// - /// System option with the internal name NamePlateColorLimEdge. + /// UiConfig option with the internal name NamePlateColorLimEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorLimEdge", ConfigType.UInt)] NamePlateColorLimEdge, /// - /// System option with the internal name NamePlateColorGri. + /// UiConfig option with the internal name NamePlateColorGri. /// This option is a UInt. /// [GameConfigOption("NamePlateColorGri", ConfigType.UInt)] NamePlateColorGri, /// - /// System option with the internal name NamePlateColorGriEdge. + /// UiConfig option with the internal name NamePlateColorGriEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorGriEdge", ConfigType.UInt)] NamePlateColorGriEdge, /// - /// System option with the internal name NamePlateColorUld. + /// UiConfig option with the internal name NamePlateColorUld. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUld", ConfigType.UInt)] NamePlateColorUld, /// - /// System option with the internal name NamePlateColorUldEdge. + /// UiConfig option with the internal name NamePlateColorUldEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUldEdge", ConfigType.UInt)] NamePlateColorUldEdge, /// - /// System option with the internal name NamePlateColorHousingFurniture. + /// UiConfig option with the internal name NamePlateColorHousingFurniture. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFurniture", ConfigType.UInt)] NamePlateColorHousingFurniture, /// - /// System option with the internal name NamePlateColorHousingFurnitureEdge. + /// UiConfig option with the internal name NamePlateColorHousingFurnitureEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFurnitureEdge", ConfigType.UInt)] NamePlateColorHousingFurnitureEdge, /// - /// System option with the internal name NamePlateDispTypeHousingFurniture. + /// UiConfig option with the internal name NamePlateDispTypeHousingFurniture. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeHousingFurniture", ConfigType.UInt)] NamePlateDispTypeHousingFurniture, /// - /// System option with the internal name NamePlateColorHousingField. + /// UiConfig option with the internal name NamePlateColorHousingField. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingField", ConfigType.UInt)] NamePlateColorHousingField, /// - /// System option with the internal name NamePlateColorHousingFieldEdge. + /// UiConfig option with the internal name NamePlateColorHousingFieldEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFieldEdge", ConfigType.UInt)] NamePlateColorHousingFieldEdge, /// - /// System option with the internal name NamePlateDispTypeHousingField. + /// UiConfig option with the internal name NamePlateDispTypeHousingField. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeHousingField", ConfigType.UInt)] NamePlateDispTypeHousingField, /// - /// System option with the internal name NamePlateNameTypePvPEnemy. + /// UiConfig option with the internal name NamePlateNameTypePvPEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypePvPEnemy", ConfigType.UInt)] NamePlateNameTypePvPEnemy, /// - /// System option with the internal name NamePlateDispTypeFeast. + /// UiConfig option with the internal name NamePlateDispTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFeast", ConfigType.UInt)] NamePlateDispTypeFeast, /// - /// System option with the internal name NamePlateNameTypeFeast. + /// UiConfig option with the internal name NamePlateNameTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeFeast", ConfigType.UInt)] NamePlateNameTypeFeast, /// - /// System option with the internal name NamePlateHpTypeFeast. + /// UiConfig option with the internal name NamePlateHpTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFeast", ConfigType.UInt)] NamePlateHpTypeFeast, /// - /// System option with the internal name NamePlateDispTypeFeastPet. + /// UiConfig option with the internal name NamePlateDispTypeFeastPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFeastPet", ConfigType.UInt)] NamePlateDispTypeFeastPet, /// - /// System option with the internal name NamePlateHpTypeFeastPet. + /// UiConfig option with the internal name NamePlateHpTypeFeastPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFeastPet", ConfigType.UInt)] NamePlateHpTypeFeastPet, /// - /// System option with the internal name NamePlateNameTitleTypeFeast. + /// UiConfig option with the internal name NamePlateNameTitleTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeFeast", ConfigType.UInt)] NamePlateNameTitleTypeFeast, /// - /// System option with the internal name NamePlateDispSize. + /// UiConfig option with the internal name NamePlateDispSize. /// This option is a UInt. /// [GameConfigOption("NamePlateDispSize", ConfigType.UInt)] NamePlateDispSize, /// - /// System option with the internal name NamePlateDispJobIcon. + /// UiConfig option with the internal name NamePlateDispJobIcon. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIcon", ConfigType.UInt)] NamePlateDispJobIcon, /// - /// System option with the internal name NamePlateDispJobIconType. + /// UiConfig option with the internal name NamePlateDispJobIconType. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconType", ConfigType.UInt)] NamePlateDispJobIconType, /// - /// System option with the internal name NamePlateSetRoleColor. + /// UiConfig option with the internal name NamePlateSetRoleColor. /// This option is a UInt. /// [GameConfigOption("NamePlateSetRoleColor", ConfigType.UInt)] NamePlateSetRoleColor, /// - /// System option with the internal name NamePlateColorTank. + /// UiConfig option with the internal name NamePlateColorTank. /// This option is a UInt. /// [GameConfigOption("NamePlateColorTank", ConfigType.UInt)] NamePlateColorTank, /// - /// System option with the internal name NamePlateEdgeTank. + /// UiConfig option with the internal name NamePlateEdgeTank. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeTank", ConfigType.UInt)] NamePlateEdgeTank, /// - /// System option with the internal name NamePlateColorHealer. + /// UiConfig option with the internal name NamePlateColorHealer. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHealer", ConfigType.UInt)] NamePlateColorHealer, /// - /// System option with the internal name NamePlateEdgeHealer. + /// UiConfig option with the internal name NamePlateEdgeHealer. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeHealer", ConfigType.UInt)] NamePlateEdgeHealer, /// - /// System option with the internal name NamePlateColorDps. + /// UiConfig option with the internal name NamePlateColorDps. /// This option is a UInt. /// [GameConfigOption("NamePlateColorDps", ConfigType.UInt)] NamePlateColorDps, /// - /// System option with the internal name NamePlateEdgeDps. + /// UiConfig option with the internal name NamePlateEdgeDps. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeDps", ConfigType.UInt)] NamePlateEdgeDps, /// - /// System option with the internal name NamePlateColorOtherClass. + /// UiConfig option with the internal name NamePlateColorOtherClass. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherClass", ConfigType.UInt)] NamePlateColorOtherClass, /// - /// System option with the internal name NamePlateEdgeOtherClass. + /// UiConfig option with the internal name NamePlateEdgeOtherClass. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherClass", ConfigType.UInt)] NamePlateEdgeOtherClass, /// - /// System option with the internal name NamePlateDispWorldTravel. + /// UiConfig option with the internal name NamePlateDispWorldTravel. /// This option is a UInt. /// [GameConfigOption("NamePlateDispWorldTravel", ConfigType.UInt)] NamePlateDispWorldTravel, /// - /// System option with the internal name NamePlateDispJobIconInPublicParty. + /// UiConfig option with the internal name NamePlateDispJobIconInPublicParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInPublicParty", ConfigType.UInt)] NamePlateDispJobIconInPublicParty, /// - /// System option with the internal name NamePlateDispJobIconInPublicOther. + /// UiConfig option with the internal name NamePlateDispJobIconInPublicOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInPublicOther", ConfigType.UInt)] NamePlateDispJobIconInPublicOther, /// - /// System option with the internal name NamePlateDispJobIconInInstanceParty. + /// UiConfig option with the internal name NamePlateDispJobIconInInstanceParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInInstanceParty", ConfigType.UInt)] NamePlateDispJobIconInInstanceParty, /// - /// System option with the internal name NamePlateDispJobIconInInstanceOther. + /// UiConfig option with the internal name NamePlateDispJobIconInInstanceOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInInstanceOther", ConfigType.UInt)] NamePlateDispJobIconInInstanceOther, /// - /// System option with the internal name ActiveInfo. + /// UiConfig option with the internal name ActiveInfo. /// This option is a UInt. /// [GameConfigOption("ActiveInfo", ConfigType.UInt)] ActiveInfo, /// - /// System option with the internal name PartyList. + /// UiConfig option with the internal name PartyList. /// This option is a UInt. /// [GameConfigOption("PartyList", ConfigType.UInt)] PartyList, /// - /// System option with the internal name PartyListStatus. + /// UiConfig option with the internal name PartyListStatus. /// This option is a UInt. /// [GameConfigOption("PartyListStatus", ConfigType.UInt)] PartyListStatus, /// - /// System option with the internal name PartyListStatusTimer. + /// UiConfig option with the internal name PartyListStatusTimer. /// This option is a UInt. /// [GameConfigOption("PartyListStatusTimer", ConfigType.UInt)] PartyListStatusTimer, /// - /// System option with the internal name EnemyList. + /// UiConfig option with the internal name EnemyList. /// This option is a UInt. /// [GameConfigOption("EnemyList", ConfigType.UInt)] EnemyList, /// - /// System option with the internal name TargetInfo. + /// UiConfig option with the internal name TargetInfo. /// This option is a UInt. /// [GameConfigOption("TargetInfo", ConfigType.UInt)] TargetInfo, /// - /// System option with the internal name Gil. + /// UiConfig option with the internal name Gil. /// This option is a UInt. /// [GameConfigOption("Gil", ConfigType.UInt)] Gil, /// - /// System option with the internal name DTR. + /// UiConfig option with the internal name DTR. /// This option is a UInt. /// [GameConfigOption("DTR", ConfigType.UInt)] DTR, /// - /// System option with the internal name PlayerInfo. + /// UiConfig option with the internal name PlayerInfo. /// This option is a UInt. /// [GameConfigOption("PlayerInfo", ConfigType.UInt)] PlayerInfo, /// - /// System option with the internal name NaviMap. + /// UiConfig option with the internal name NaviMap. /// This option is a UInt. /// [GameConfigOption("NaviMap", ConfigType.UInt)] NaviMap, /// - /// System option with the internal name Help. + /// UiConfig option with the internal name Help. /// This option is a UInt. /// [GameConfigOption("Help", ConfigType.UInt)] Help, /// - /// System option with the internal name CrossMainHelp. + /// UiConfig option with the internal name CrossMainHelp. /// This option is a UInt. /// [GameConfigOption("CrossMainHelp", ConfigType.UInt)] CrossMainHelp, /// - /// System option with the internal name HousingLocatePreview. + /// UiConfig option with the internal name HousingLocatePreview. /// This option is a UInt. /// [GameConfigOption("HousingLocatePreview", ConfigType.UInt)] HousingLocatePreview, /// - /// System option with the internal name Log. + /// UiConfig option with the internal name Log. /// This option is a UInt. /// [GameConfigOption("Log", ConfigType.UInt)] Log, /// - /// System option with the internal name LogTell. + /// UiConfig option with the internal name LogTell. /// This option is a UInt. /// [GameConfigOption("LogTell", ConfigType.UInt)] LogTell, /// - /// System option with the internal name LogFontSize. + /// UiConfig option with the internal name LogFontSize. /// This option is a UInt. /// [GameConfigOption("LogFontSize", ConfigType.UInt)] LogFontSize, /// - /// System option with the internal name LogTabName2. + /// UiConfig option with the internal name LogTabName2. /// This option is a String. /// [GameConfigOption("LogTabName2", ConfigType.String)] LogTabName2, /// - /// System option with the internal name LogTabName3. + /// UiConfig option with the internal name LogTabName3. /// This option is a String. /// [GameConfigOption("LogTabName3", ConfigType.String)] LogTabName3, /// - /// System option with the internal name LogTabFilter0. + /// UiConfig option with the internal name LogTabFilter0. /// This option is a UInt. /// [GameConfigOption("LogTabFilter0", ConfigType.UInt)] LogTabFilter0, /// - /// System option with the internal name LogTabFilter1. + /// UiConfig option with the internal name LogTabFilter1. /// This option is a UInt. /// [GameConfigOption("LogTabFilter1", ConfigType.UInt)] LogTabFilter1, /// - /// System option with the internal name LogTabFilter2. + /// UiConfig option with the internal name LogTabFilter2. /// This option is a UInt. /// [GameConfigOption("LogTabFilter2", ConfigType.UInt)] LogTabFilter2, /// - /// System option with the internal name LogTabFilter3. + /// UiConfig option with the internal name LogTabFilter3. /// This option is a UInt. /// [GameConfigOption("LogTabFilter3", ConfigType.UInt)] LogTabFilter3, /// - /// System option with the internal name LogChatFilter. + /// UiConfig option with the internal name LogChatFilter. /// This option is a UInt. /// [GameConfigOption("LogChatFilter", ConfigType.UInt)] LogChatFilter, /// - /// System option with the internal name LogEnableErrMsgLv1. + /// UiConfig option with the internal name LogEnableErrMsgLv1. /// This option is a UInt. /// [GameConfigOption("LogEnableErrMsgLv1", ConfigType.UInt)] LogEnableErrMsgLv1, /// - /// System option with the internal name LogNameType. + /// UiConfig option with the internal name LogNameType. /// This option is a UInt. /// [GameConfigOption("LogNameType", ConfigType.UInt)] LogNameType, /// - /// System option with the internal name LogTimeDisp. + /// UiConfig option with the internal name LogTimeDisp. /// This option is a UInt. /// [GameConfigOption("LogTimeDisp", ConfigType.UInt)] LogTimeDisp, /// - /// System option with the internal name LogTimeSettingType. + /// UiConfig option with the internal name LogTimeSettingType. /// This option is a UInt. /// [GameConfigOption("LogTimeSettingType", ConfigType.UInt)] LogTimeSettingType, /// - /// System option with the internal name LogTimeDispType. + /// UiConfig option with the internal name LogTimeDispType. /// This option is a UInt. /// [GameConfigOption("LogTimeDispType", ConfigType.UInt)] LogTimeDispType, /// - /// System option with the internal name IsLogTell. + /// UiConfig option with the internal name IsLogTell. /// This option is a UInt. /// [GameConfigOption("IsLogTell", ConfigType.UInt)] IsLogTell, /// - /// System option with the internal name IsLogParty. + /// UiConfig option with the internal name IsLogParty. /// This option is a UInt. /// [GameConfigOption("IsLogParty", ConfigType.UInt)] IsLogParty, /// - /// System option with the internal name LogParty. + /// UiConfig option with the internal name LogParty. /// This option is a UInt. /// [GameConfigOption("LogParty", ConfigType.UInt)] LogParty, /// - /// System option with the internal name IsLogAlliance. + /// UiConfig option with the internal name IsLogAlliance. /// This option is a UInt. /// [GameConfigOption("IsLogAlliance", ConfigType.UInt)] IsLogAlliance, /// - /// System option with the internal name LogAlliance. + /// UiConfig option with the internal name LogAlliance. /// This option is a UInt. /// [GameConfigOption("LogAlliance", ConfigType.UInt)] LogAlliance, /// - /// System option with the internal name IsLogFc. + /// UiConfig option with the internal name IsLogFc. /// This option is a UInt. /// [GameConfigOption("IsLogFc", ConfigType.UInt)] IsLogFc, /// - /// System option with the internal name LogFc. + /// UiConfig option with the internal name LogFc. /// This option is a UInt. /// [GameConfigOption("LogFc", ConfigType.UInt)] LogFc, /// - /// System option with the internal name IsLogPvpTeam. + /// UiConfig option with the internal name IsLogPvpTeam. /// This option is a UInt. /// [GameConfigOption("IsLogPvpTeam", ConfigType.UInt)] IsLogPvpTeam, /// - /// System option with the internal name LogPvpTeam. + /// UiConfig option with the internal name LogPvpTeam. /// This option is a UInt. /// [GameConfigOption("LogPvpTeam", ConfigType.UInt)] LogPvpTeam, /// - /// System option with the internal name IsLogLs1. + /// UiConfig option with the internal name IsLogLs1. /// This option is a UInt. /// [GameConfigOption("IsLogLs1", ConfigType.UInt)] IsLogLs1, /// - /// System option with the internal name LogLs1. + /// UiConfig option with the internal name LogLs1. /// This option is a UInt. /// [GameConfigOption("LogLs1", ConfigType.UInt)] LogLs1, /// - /// System option with the internal name IsLogLs2. + /// UiConfig option with the internal name IsLogLs2. /// This option is a UInt. /// [GameConfigOption("IsLogLs2", ConfigType.UInt)] IsLogLs2, /// - /// System option with the internal name LogLs2. + /// UiConfig option with the internal name LogLs2. /// This option is a UInt. /// [GameConfigOption("LogLs2", ConfigType.UInt)] LogLs2, /// - /// System option with the internal name IsLogLs3. + /// UiConfig option with the internal name IsLogLs3. /// This option is a UInt. /// [GameConfigOption("IsLogLs3", ConfigType.UInt)] IsLogLs3, /// - /// System option with the internal name LogLs3. + /// UiConfig option with the internal name LogLs3. /// This option is a UInt. /// [GameConfigOption("LogLs3", ConfigType.UInt)] LogLs3, /// - /// System option with the internal name IsLogLs4. + /// UiConfig option with the internal name IsLogLs4. /// This option is a UInt. /// [GameConfigOption("IsLogLs4", ConfigType.UInt)] IsLogLs4, /// - /// System option with the internal name LogLs4. + /// UiConfig option with the internal name LogLs4. /// This option is a UInt. /// [GameConfigOption("LogLs4", ConfigType.UInt)] LogLs4, /// - /// System option with the internal name IsLogLs5. + /// UiConfig option with the internal name IsLogLs5. /// This option is a UInt. /// [GameConfigOption("IsLogLs5", ConfigType.UInt)] IsLogLs5, /// - /// System option with the internal name LogLs5. + /// UiConfig option with the internal name LogLs5. /// This option is a UInt. /// [GameConfigOption("LogLs5", ConfigType.UInt)] LogLs5, /// - /// System option with the internal name IsLogLs6. + /// UiConfig option with the internal name IsLogLs6. /// This option is a UInt. /// [GameConfigOption("IsLogLs6", ConfigType.UInt)] IsLogLs6, /// - /// System option with the internal name LogLs6. + /// UiConfig option with the internal name LogLs6. /// This option is a UInt. /// [GameConfigOption("LogLs6", ConfigType.UInt)] LogLs6, /// - /// System option with the internal name IsLogLs7. + /// UiConfig option with the internal name IsLogLs7. /// This option is a UInt. /// [GameConfigOption("IsLogLs7", ConfigType.UInt)] IsLogLs7, /// - /// System option with the internal name LogLs7. + /// UiConfig option with the internal name LogLs7. /// This option is a UInt. /// [GameConfigOption("LogLs7", ConfigType.UInt)] LogLs7, /// - /// System option with the internal name IsLogLs8. + /// UiConfig option with the internal name IsLogLs8. /// This option is a UInt. /// [GameConfigOption("IsLogLs8", ConfigType.UInt)] IsLogLs8, /// - /// System option with the internal name LogLs8. + /// UiConfig option with the internal name LogLs8. /// This option is a UInt. /// [GameConfigOption("LogLs8", ConfigType.UInt)] LogLs8, /// - /// System option with the internal name IsLogBeginner. + /// UiConfig option with the internal name IsLogBeginner. /// This option is a UInt. /// [GameConfigOption("IsLogBeginner", ConfigType.UInt)] IsLogBeginner, /// - /// System option with the internal name LogBeginner. + /// UiConfig option with the internal name LogBeginner. /// This option is a UInt. /// [GameConfigOption("LogBeginner", ConfigType.UInt)] LogBeginner, /// - /// System option with the internal name IsLogCwls. + /// UiConfig option with the internal name IsLogCwls. /// This option is a UInt. /// [GameConfigOption("IsLogCwls", ConfigType.UInt)] IsLogCwls, /// - /// System option with the internal name IsLogCwls2. + /// UiConfig option with the internal name IsLogCwls2. /// This option is a UInt. /// [GameConfigOption("IsLogCwls2", ConfigType.UInt)] IsLogCwls2, /// - /// System option with the internal name IsLogCwls3. + /// UiConfig option with the internal name IsLogCwls3. /// This option is a UInt. /// [GameConfigOption("IsLogCwls3", ConfigType.UInt)] IsLogCwls3, /// - /// System option with the internal name IsLogCwls4. + /// UiConfig option with the internal name IsLogCwls4. /// This option is a UInt. /// [GameConfigOption("IsLogCwls4", ConfigType.UInt)] IsLogCwls4, /// - /// System option with the internal name IsLogCwls5. + /// UiConfig option with the internal name IsLogCwls5. /// This option is a UInt. /// [GameConfigOption("IsLogCwls5", ConfigType.UInt)] IsLogCwls5, /// - /// System option with the internal name IsLogCwls6. + /// UiConfig option with the internal name IsLogCwls6. /// This option is a UInt. /// [GameConfigOption("IsLogCwls6", ConfigType.UInt)] IsLogCwls6, /// - /// System option with the internal name IsLogCwls7. + /// UiConfig option with the internal name IsLogCwls7. /// This option is a UInt. /// [GameConfigOption("IsLogCwls7", ConfigType.UInt)] IsLogCwls7, /// - /// System option with the internal name IsLogCwls8. + /// UiConfig option with the internal name IsLogCwls8. /// This option is a UInt. /// [GameConfigOption("IsLogCwls8", ConfigType.UInt)] IsLogCwls8, /// - /// System option with the internal name LogCwls. + /// UiConfig option with the internal name LogCwls. /// This option is a UInt. /// [GameConfigOption("LogCwls", ConfigType.UInt)] LogCwls, /// - /// System option with the internal name LogCwls2. + /// UiConfig option with the internal name LogCwls2. /// This option is a UInt. /// [GameConfigOption("LogCwls2", ConfigType.UInt)] LogCwls2, /// - /// System option with the internal name LogCwls3. + /// UiConfig option with the internal name LogCwls3. /// This option is a UInt. /// [GameConfigOption("LogCwls3", ConfigType.UInt)] LogCwls3, /// - /// System option with the internal name LogCwls4. + /// UiConfig option with the internal name LogCwls4. /// This option is a UInt. /// [GameConfigOption("LogCwls4", ConfigType.UInt)] LogCwls4, /// - /// System option with the internal name LogCwls5. + /// UiConfig option with the internal name LogCwls5. /// This option is a UInt. /// [GameConfigOption("LogCwls5", ConfigType.UInt)] LogCwls5, /// - /// System option with the internal name LogCwls6. + /// UiConfig option with the internal name LogCwls6. /// This option is a UInt. /// [GameConfigOption("LogCwls6", ConfigType.UInt)] LogCwls6, /// - /// System option with the internal name LogCwls7. + /// UiConfig option with the internal name LogCwls7. /// This option is a UInt. /// [GameConfigOption("LogCwls7", ConfigType.UInt)] LogCwls7, /// - /// System option with the internal name LogCwls8. + /// UiConfig option with the internal name LogCwls8. /// This option is a UInt. /// [GameConfigOption("LogCwls8", ConfigType.UInt)] LogCwls8, /// - /// System option with the internal name LogRecastActionErrDisp. + /// UiConfig option with the internal name LogRecastActionErrDisp. /// This option is a UInt. /// [GameConfigOption("LogRecastActionErrDisp", ConfigType.UInt)] LogRecastActionErrDisp, /// - /// System option with the internal name LogPermeationRate. + /// UiConfig option with the internal name LogPermeationRate. /// This option is a UInt. /// [GameConfigOption("LogPermeationRate", ConfigType.UInt)] LogPermeationRate, /// - /// System option with the internal name LogFontSizeForm. + /// UiConfig option with the internal name LogFontSizeForm. /// This option is a UInt. /// [GameConfigOption("LogFontSizeForm", ConfigType.UInt)] LogFontSizeForm, /// - /// System option with the internal name LogItemLinkEnableType. + /// UiConfig option with the internal name LogItemLinkEnableType. /// This option is a UInt. /// [GameConfigOption("LogItemLinkEnableType", ConfigType.UInt)] LogItemLinkEnableType, /// - /// System option with the internal name LogFontSizeLog2. + /// UiConfig option with the internal name LogFontSizeLog2. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog2", ConfigType.UInt)] LogFontSizeLog2, /// - /// System option with the internal name LogTimeDispLog2. + /// UiConfig option with the internal name LogTimeDispLog2. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog2", ConfigType.UInt)] LogTimeDispLog2, /// - /// System option with the internal name LogPermeationRateLog2. + /// UiConfig option with the internal name LogPermeationRateLog2. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog2", ConfigType.UInt)] LogPermeationRateLog2, /// - /// System option with the internal name LogFontSizeLog3. + /// UiConfig option with the internal name LogFontSizeLog3. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog3", ConfigType.UInt)] LogFontSizeLog3, /// - /// System option with the internal name LogTimeDispLog3. + /// UiConfig option with the internal name LogTimeDispLog3. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog3", ConfigType.UInt)] LogTimeDispLog3, /// - /// System option with the internal name LogPermeationRateLog3. + /// UiConfig option with the internal name LogPermeationRateLog3. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog3", ConfigType.UInt)] LogPermeationRateLog3, /// - /// System option with the internal name LogFontSizeLog4. + /// UiConfig option with the internal name LogFontSizeLog4. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog4", ConfigType.UInt)] LogFontSizeLog4, /// - /// System option with the internal name LogTimeDispLog4. + /// UiConfig option with the internal name LogTimeDispLog4. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog4", ConfigType.UInt)] LogTimeDispLog4, /// - /// System option with the internal name LogPermeationRateLog4. + /// UiConfig option with the internal name LogPermeationRateLog4. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog4", ConfigType.UInt)] LogPermeationRateLog4, /// - /// System option with the internal name LogFlyingHeightMaxErrDisp. + /// UiConfig option with the internal name LogFlyingHeightMaxErrDisp. /// This option is a UInt. /// [GameConfigOption("LogFlyingHeightMaxErrDisp", ConfigType.UInt)] LogFlyingHeightMaxErrDisp, /// - /// System option with the internal name LogCrossWorldName. + /// UiConfig option with the internal name LogCrossWorldName. /// This option is a UInt. /// [GameConfigOption("LogCrossWorldName", ConfigType.UInt)] LogCrossWorldName, /// - /// System option with the internal name LogDragResize. + /// UiConfig option with the internal name LogDragResize. /// This option is a UInt. /// [GameConfigOption("LogDragResize", ConfigType.UInt)] LogDragResize, /// - /// System option with the internal name LogNameIconType. + /// UiConfig option with the internal name LogNameIconType. /// This option is a UInt. /// [GameConfigOption("LogNameIconType", ConfigType.UInt)] LogNameIconType, /// - /// System option with the internal name LogDispClassJobName. + /// UiConfig option with the internal name LogDispClassJobName. /// This option is a UInt. /// [GameConfigOption("LogDispClassJobName", ConfigType.UInt)] LogDispClassJobName, /// - /// System option with the internal name LogSetRoleColor. + /// UiConfig option with the internal name LogSetRoleColor. /// This option is a UInt. /// [GameConfigOption("LogSetRoleColor", ConfigType.UInt)] LogSetRoleColor, /// - /// System option with the internal name LogColorRoleTank. + /// UiConfig option with the internal name LogColorRoleTank. /// This option is a UInt. /// [GameConfigOption("LogColorRoleTank", ConfigType.UInt)] LogColorRoleTank, /// - /// System option with the internal name LogColorRoleHealer. + /// UiConfig option with the internal name LogColorRoleHealer. /// This option is a UInt. /// [GameConfigOption("LogColorRoleHealer", ConfigType.UInt)] LogColorRoleHealer, /// - /// System option with the internal name LogColorRoleDPS. + /// UiConfig option with the internal name LogColorRoleDPS. /// This option is a UInt. /// [GameConfigOption("LogColorRoleDPS", ConfigType.UInt)] LogColorRoleDPS, /// - /// System option with the internal name LogColorOtherClass. + /// UiConfig option with the internal name LogColorOtherClass. /// This option is a UInt. /// [GameConfigOption("LogColorOtherClass", ConfigType.UInt)] LogColorOtherClass, /// - /// System option with the internal name ChatType. + /// UiConfig option with the internal name ChatType. /// This option is a UInt. /// [GameConfigOption("ChatType", ConfigType.UInt)] ChatType, /// - /// System option with the internal name ShopSell. + /// UiConfig option with the internal name ShopSell. /// This option is a UInt. /// [GameConfigOption("ShopSell", ConfigType.UInt)] ShopSell, /// - /// System option with the internal name ColorSay. + /// UiConfig option with the internal name ColorSay. /// This option is a UInt. /// [GameConfigOption("ColorSay", ConfigType.UInt)] ColorSay, /// - /// System option with the internal name ColorShout. + /// UiConfig option with the internal name ColorShout. /// This option is a UInt. /// [GameConfigOption("ColorShout", ConfigType.UInt)] ColorShout, /// - /// System option with the internal name ColorTell. + /// UiConfig option with the internal name ColorTell. /// This option is a UInt. /// [GameConfigOption("ColorTell", ConfigType.UInt)] ColorTell, /// - /// System option with the internal name ColorParty. + /// UiConfig option with the internal name ColorParty. /// This option is a UInt. /// [GameConfigOption("ColorParty", ConfigType.UInt)] ColorParty, /// - /// System option with the internal name ColorAlliance. + /// UiConfig option with the internal name ColorAlliance. /// This option is a UInt. /// [GameConfigOption("ColorAlliance", ConfigType.UInt)] ColorAlliance, /// - /// System option with the internal name ColorLS1. + /// UiConfig option with the internal name ColorLS1. /// This option is a UInt. /// [GameConfigOption("ColorLS1", ConfigType.UInt)] ColorLS1, /// - /// System option with the internal name ColorLS2. + /// UiConfig option with the internal name ColorLS2. /// This option is a UInt. /// [GameConfigOption("ColorLS2", ConfigType.UInt)] ColorLS2, /// - /// System option with the internal name ColorLS3. + /// UiConfig option with the internal name ColorLS3. /// This option is a UInt. /// [GameConfigOption("ColorLS3", ConfigType.UInt)] ColorLS3, /// - /// System option with the internal name ColorLS4. + /// UiConfig option with the internal name ColorLS4. /// This option is a UInt. /// [GameConfigOption("ColorLS4", ConfigType.UInt)] ColorLS4, /// - /// System option with the internal name ColorLS5. + /// UiConfig option with the internal name ColorLS5. /// This option is a UInt. /// [GameConfigOption("ColorLS5", ConfigType.UInt)] ColorLS5, /// - /// System option with the internal name ColorLS6. + /// UiConfig option with the internal name ColorLS6. /// This option is a UInt. /// [GameConfigOption("ColorLS6", ConfigType.UInt)] ColorLS6, /// - /// System option with the internal name ColorLS7. + /// UiConfig option with the internal name ColorLS7. /// This option is a UInt. /// [GameConfigOption("ColorLS7", ConfigType.UInt)] ColorLS7, /// - /// System option with the internal name ColorLS8. + /// UiConfig option with the internal name ColorLS8. /// This option is a UInt. /// [GameConfigOption("ColorLS8", ConfigType.UInt)] ColorLS8, /// - /// System option with the internal name ColorFCompany. + /// UiConfig option with the internal name ColorFCompany. /// This option is a UInt. /// [GameConfigOption("ColorFCompany", ConfigType.UInt)] ColorFCompany, /// - /// System option with the internal name ColorPvPGroup. + /// UiConfig option with the internal name ColorPvPGroup. /// This option is a UInt. /// [GameConfigOption("ColorPvPGroup", ConfigType.UInt)] ColorPvPGroup, /// - /// System option with the internal name ColorPvPGroupAnnounce. + /// UiConfig option with the internal name ColorPvPGroupAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorPvPGroupAnnounce", ConfigType.UInt)] ColorPvPGroupAnnounce, /// - /// System option with the internal name ColorBeginner. + /// UiConfig option with the internal name ColorBeginner. /// This option is a UInt. /// [GameConfigOption("ColorBeginner", ConfigType.UInt)] ColorBeginner, /// - /// System option with the internal name ColorEmoteUser. + /// UiConfig option with the internal name ColorEmoteUser. /// This option is a UInt. /// [GameConfigOption("ColorEmoteUser", ConfigType.UInt)] ColorEmoteUser, /// - /// System option with the internal name ColorEmote. + /// UiConfig option with the internal name ColorEmote. /// This option is a UInt. /// [GameConfigOption("ColorEmote", ConfigType.UInt)] ColorEmote, /// - /// System option with the internal name ColorYell. + /// UiConfig option with the internal name ColorYell. /// This option is a UInt. /// [GameConfigOption("ColorYell", ConfigType.UInt)] ColorYell, /// - /// System option with the internal name ColorBeginnerAnnounce. + /// UiConfig option with the internal name ColorBeginnerAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorBeginnerAnnounce", ConfigType.UInt)] ColorBeginnerAnnounce, /// - /// System option with the internal name ColorCWLS. + /// UiConfig option with the internal name ColorCWLS. /// This option is a UInt. /// [GameConfigOption("ColorCWLS", ConfigType.UInt)] ColorCWLS, /// - /// System option with the internal name ColorCWLS2. + /// UiConfig option with the internal name ColorCWLS2. /// This option is a UInt. /// [GameConfigOption("ColorCWLS2", ConfigType.UInt)] ColorCWLS2, /// - /// System option with the internal name ColorCWLS3. + /// UiConfig option with the internal name ColorCWLS3. /// This option is a UInt. /// [GameConfigOption("ColorCWLS3", ConfigType.UInt)] ColorCWLS3, /// - /// System option with the internal name ColorCWLS4. + /// UiConfig option with the internal name ColorCWLS4. /// This option is a UInt. /// [GameConfigOption("ColorCWLS4", ConfigType.UInt)] ColorCWLS4, /// - /// System option with the internal name ColorCWLS5. + /// UiConfig option with the internal name ColorCWLS5. /// This option is a UInt. /// [GameConfigOption("ColorCWLS5", ConfigType.UInt)] ColorCWLS5, /// - /// System option with the internal name ColorCWLS6. + /// UiConfig option with the internal name ColorCWLS6. /// This option is a UInt. /// [GameConfigOption("ColorCWLS6", ConfigType.UInt)] ColorCWLS6, /// - /// System option with the internal name ColorCWLS7. + /// UiConfig option with the internal name ColorCWLS7. /// This option is a UInt. /// [GameConfigOption("ColorCWLS7", ConfigType.UInt)] ColorCWLS7, /// - /// System option with the internal name ColorCWLS8. + /// UiConfig option with the internal name ColorCWLS8. /// This option is a UInt. /// [GameConfigOption("ColorCWLS8", ConfigType.UInt)] ColorCWLS8, /// - /// System option with the internal name ColorAttackSuccess. + /// UiConfig option with the internal name ColorAttackSuccess. /// This option is a UInt. /// [GameConfigOption("ColorAttackSuccess", ConfigType.UInt)] ColorAttackSuccess, /// - /// System option with the internal name ColorAttackFailure. + /// UiConfig option with the internal name ColorAttackFailure. /// This option is a UInt. /// [GameConfigOption("ColorAttackFailure", ConfigType.UInt)] ColorAttackFailure, /// - /// System option with the internal name ColorAction. + /// UiConfig option with the internal name ColorAction. /// This option is a UInt. /// [GameConfigOption("ColorAction", ConfigType.UInt)] ColorAction, /// - /// System option with the internal name ColorItem. + /// UiConfig option with the internal name ColorItem. /// This option is a UInt. /// [GameConfigOption("ColorItem", ConfigType.UInt)] ColorItem, /// - /// System option with the internal name ColorCureGive. + /// UiConfig option with the internal name ColorCureGive. /// This option is a UInt. /// [GameConfigOption("ColorCureGive", ConfigType.UInt)] ColorCureGive, /// - /// System option with the internal name ColorBuffGive. + /// UiConfig option with the internal name ColorBuffGive. /// This option is a UInt. /// [GameConfigOption("ColorBuffGive", ConfigType.UInt)] ColorBuffGive, /// - /// System option with the internal name ColorDebuffGive. + /// UiConfig option with the internal name ColorDebuffGive. /// This option is a UInt. /// [GameConfigOption("ColorDebuffGive", ConfigType.UInt)] ColorDebuffGive, /// - /// System option with the internal name ColorEcho. + /// UiConfig option with the internal name ColorEcho. /// This option is a UInt. /// [GameConfigOption("ColorEcho", ConfigType.UInt)] ColorEcho, /// - /// System option with the internal name ColorSysMsg. + /// UiConfig option with the internal name ColorSysMsg. /// This option is a UInt. /// [GameConfigOption("ColorSysMsg", ConfigType.UInt)] ColorSysMsg, /// - /// System option with the internal name ColorFCAnnounce. + /// UiConfig option with the internal name ColorFCAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorFCAnnounce", ConfigType.UInt)] ColorFCAnnounce, /// - /// System option with the internal name ColorSysBattle. + /// UiConfig option with the internal name ColorSysBattle. /// This option is a UInt. /// [GameConfigOption("ColorSysBattle", ConfigType.UInt)] ColorSysBattle, /// - /// System option with the internal name ColorSysGathering. + /// UiConfig option with the internal name ColorSysGathering. /// This option is a UInt. /// [GameConfigOption("ColorSysGathering", ConfigType.UInt)] ColorSysGathering, /// - /// System option with the internal name ColorSysErr. + /// UiConfig option with the internal name ColorSysErr. /// This option is a UInt. /// [GameConfigOption("ColorSysErr", ConfigType.UInt)] ColorSysErr, /// - /// System option with the internal name ColorNpcSay. + /// UiConfig option with the internal name ColorNpcSay. /// This option is a UInt. /// [GameConfigOption("ColorNpcSay", ConfigType.UInt)] ColorNpcSay, /// - /// System option with the internal name ColorItemNotice. + /// UiConfig option with the internal name ColorItemNotice. /// This option is a UInt. /// [GameConfigOption("ColorItemNotice", ConfigType.UInt)] ColorItemNotice, /// - /// System option with the internal name ColorGrowup. + /// UiConfig option with the internal name ColorGrowup. /// This option is a UInt. /// [GameConfigOption("ColorGrowup", ConfigType.UInt)] ColorGrowup, /// - /// System option with the internal name ColorLoot. + /// UiConfig option with the internal name ColorLoot. /// This option is a UInt. /// [GameConfigOption("ColorLoot", ConfigType.UInt)] ColorLoot, /// - /// System option with the internal name ColorCraft. + /// UiConfig option with the internal name ColorCraft. /// This option is a UInt. /// [GameConfigOption("ColorCraft", ConfigType.UInt)] ColorCraft, /// - /// System option with the internal name ColorGathering. + /// UiConfig option with the internal name ColorGathering. /// This option is a UInt. /// [GameConfigOption("ColorGathering", ConfigType.UInt)] ColorGathering, /// - /// System option with the internal name ShopConfirm. + /// UiConfig option with the internal name ShopConfirm. /// This option is a UInt. /// [GameConfigOption("ShopConfirm", ConfigType.UInt)] ShopConfirm, /// - /// System option with the internal name ShopConfirmMateria. + /// UiConfig option with the internal name ShopConfirmMateria. /// This option is a UInt. /// [GameConfigOption("ShopConfirmMateria", ConfigType.UInt)] ShopConfirmMateria, /// - /// System option with the internal name ShopConfirmExRare. + /// UiConfig option with the internal name ShopConfirmExRare. /// This option is a UInt. /// [GameConfigOption("ShopConfirmExRare", ConfigType.UInt)] ShopConfirmExRare, /// - /// System option with the internal name ShopConfirmSpiritBondMax. + /// UiConfig option with the internal name ShopConfirmSpiritBondMax. /// This option is a UInt. /// [GameConfigOption("ShopConfirmSpiritBondMax", ConfigType.UInt)] ShopConfirmSpiritBondMax, /// - /// System option with the internal name ItemSortItemCategory. + /// UiConfig option with the internal name ItemSortItemCategory. /// This option is a UInt. /// [GameConfigOption("ItemSortItemCategory", ConfigType.UInt)] ItemSortItemCategory, /// - /// System option with the internal name ItemSortEquipLevel. + /// UiConfig option with the internal name ItemSortEquipLevel. /// This option is a UInt. /// [GameConfigOption("ItemSortEquipLevel", ConfigType.UInt)] ItemSortEquipLevel, /// - /// System option with the internal name ItemSortItemLevel. + /// UiConfig option with the internal name ItemSortItemLevel. /// This option is a UInt. /// [GameConfigOption("ItemSortItemLevel", ConfigType.UInt)] ItemSortItemLevel, /// - /// System option with the internal name ItemSortItemStack. + /// UiConfig option with the internal name ItemSortItemStack. /// This option is a UInt. /// [GameConfigOption("ItemSortItemStack", ConfigType.UInt)] ItemSortItemStack, /// - /// System option with the internal name ItemSortTidyingType. + /// UiConfig option with the internal name ItemSortTidyingType. /// This option is a UInt. /// [GameConfigOption("ItemSortTidyingType", ConfigType.UInt)] ItemSortTidyingType, /// - /// System option with the internal name ItemNoArmoryMaskOff. + /// UiConfig option with the internal name ItemNoArmoryMaskOff. /// This option is a UInt. /// [GameConfigOption("ItemNoArmoryMaskOff", ConfigType.UInt)] ItemNoArmoryMaskOff, /// - /// System option with the internal name ItemInventryStoreEnd. + /// UiConfig option with the internal name ItemInventryStoreEnd. /// This option is a UInt. /// [GameConfigOption("ItemInventryStoreEnd", ConfigType.UInt)] ItemInventryStoreEnd, /// - /// System option with the internal name InfoSettingDispWorldNameType. + /// UiConfig option with the internal name InfoSettingDispWorldNameType. /// This option is a UInt. /// [GameConfigOption("InfoSettingDispWorldNameType", ConfigType.UInt)] InfoSettingDispWorldNameType, /// - /// System option with the internal name TargetNamePlateNameType. + /// UiConfig option with the internal name TargetNamePlateNameType. /// This option is a UInt. /// [GameConfigOption("TargetNamePlateNameType", ConfigType.UInt)] TargetNamePlateNameType, /// - /// System option with the internal name FocusTargetNamePlateNameType. + /// UiConfig option with the internal name FocusTargetNamePlateNameType. /// This option is a UInt. /// [GameConfigOption("FocusTargetNamePlateNameType", ConfigType.UInt)] FocusTargetNamePlateNameType, /// - /// System option with the internal name ItemDetailTemporarilySwitch. + /// UiConfig option with the internal name ItemDetailTemporarilySwitch. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilySwitch", ConfigType.UInt)] ItemDetailTemporarilySwitch, /// - /// System option with the internal name ItemDetailTemporarilySwitchKey. + /// UiConfig option with the internal name ItemDetailTemporarilySwitchKey. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilySwitchKey", ConfigType.UInt)] ItemDetailTemporarilySwitchKey, /// - /// System option with the internal name ItemDetailTemporarilyHide. + /// UiConfig option with the internal name ItemDetailTemporarilyHide. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilyHide", ConfigType.UInt)] ItemDetailTemporarilyHide, /// - /// System option with the internal name ItemDetailTemporarilyHideKey. + /// UiConfig option with the internal name ItemDetailTemporarilyHideKey. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilyHideKey", ConfigType.UInt)] ItemDetailTemporarilyHideKey, /// - /// System option with the internal name ToolTipDispSize. + /// UiConfig option with the internal name ToolTipDispSize. /// This option is a UInt. /// [GameConfigOption("ToolTipDispSize", ConfigType.UInt)] ToolTipDispSize, /// - /// System option with the internal name RecommendLoginDisp. + /// UiConfig option with the internal name RecommendLoginDisp. /// This option is a UInt. /// [GameConfigOption("RecommendLoginDisp", ConfigType.UInt)] RecommendLoginDisp, /// - /// System option with the internal name RecommendAreaChangeDisp. + /// UiConfig option with the internal name RecommendAreaChangeDisp. /// This option is a UInt. /// [GameConfigOption("RecommendAreaChangeDisp", ConfigType.UInt)] RecommendAreaChangeDisp, /// - /// System option with the internal name PlayGuideLoginDisp. + /// UiConfig option with the internal name PlayGuideLoginDisp. /// This option is a UInt. /// [GameConfigOption("PlayGuideLoginDisp", ConfigType.UInt)] PlayGuideLoginDisp, /// - /// System option with the internal name PlayGuideAreaChangeDisp. + /// UiConfig option with the internal name PlayGuideAreaChangeDisp. /// This option is a UInt. /// [GameConfigOption("PlayGuideAreaChangeDisp", ConfigType.UInt)] PlayGuideAreaChangeDisp, /// - /// System option with the internal name MapPadOperationYReverse. + /// UiConfig option with the internal name MapPadOperationYReverse. /// This option is a UInt. /// [GameConfigOption("MapPadOperationYReverse", ConfigType.UInt)] MapPadOperationYReverse, /// - /// System option with the internal name MapPadOperationXReverse. + /// UiConfig option with the internal name MapPadOperationXReverse. /// This option is a UInt. /// [GameConfigOption("MapPadOperationXReverse", ConfigType.UInt)] MapPadOperationXReverse, /// - /// System option with the internal name MapDispSize. + /// UiConfig option with the internal name MapDispSize. /// This option is a UInt. /// [GameConfigOption("MapDispSize", ConfigType.UInt)] MapDispSize, /// - /// System option with the internal name FlyTextDispSize. + /// UiConfig option with the internal name FlyTextDispSize. /// This option is a UInt. /// [GameConfigOption("FlyTextDispSize", ConfigType.UInt)] FlyTextDispSize, /// - /// System option with the internal name PopUpTextDispSize. + /// UiConfig option with the internal name PopUpTextDispSize. /// This option is a UInt. /// [GameConfigOption("PopUpTextDispSize", ConfigType.UInt)] PopUpTextDispSize, /// - /// System option with the internal name DetailDispDelayType. + /// UiConfig option with the internal name DetailDispDelayType. /// This option is a UInt. /// [GameConfigOption("DetailDispDelayType", ConfigType.UInt)] DetailDispDelayType, /// - /// System option with the internal name PartyListSortTypeTank. + /// UiConfig option with the internal name PartyListSortTypeTank. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeTank", ConfigType.UInt)] PartyListSortTypeTank, /// - /// System option with the internal name PartyListSortTypeHealer. + /// UiConfig option with the internal name PartyListSortTypeHealer. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeHealer", ConfigType.UInt)] PartyListSortTypeHealer, /// - /// System option with the internal name PartyListSortTypeDps. + /// UiConfig option with the internal name PartyListSortTypeDps. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeDps", ConfigType.UInt)] PartyListSortTypeDps, /// - /// System option with the internal name PartyListSortTypeOther. + /// UiConfig option with the internal name PartyListSortTypeOther. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeOther", ConfigType.UInt)] PartyListSortTypeOther, /// - /// System option with the internal name RatioHpDisp. + /// UiConfig option with the internal name RatioHpDisp. /// This option is a UInt. /// [GameConfigOption("RatioHpDisp", ConfigType.UInt)] RatioHpDisp, /// - /// System option with the internal name BuffDispType. + /// UiConfig option with the internal name BuffDispType. /// This option is a UInt. /// [GameConfigOption("BuffDispType", ConfigType.UInt)] BuffDispType, /// - /// System option with the internal name ContentsFinderListSortType. + /// UiConfig option with the internal name ContentsFinderListSortType. /// This option is a UInt. /// [GameConfigOption("ContentsFinderListSortType", ConfigType.UInt)] ContentsFinderListSortType, /// - /// System option with the internal name ContentsFinderSupplyEnable. + /// UiConfig option with the internal name ContentsFinderSupplyEnable. /// This option is a UInt. /// [GameConfigOption("ContentsFinderSupplyEnable", ConfigType.UInt)] ContentsFinderSupplyEnable, /// - /// System option with the internal name EnemyListCastbarEnable. + /// UiConfig option with the internal name EnemyListCastbarEnable. /// This option is a UInt. /// [GameConfigOption("EnemyListCastbarEnable", ConfigType.UInt)] EnemyListCastbarEnable, /// - /// System option with the internal name AchievementAppealLoginDisp. + /// UiConfig option with the internal name AchievementAppealLoginDisp. /// This option is a UInt. /// [GameConfigOption("AchievementAppealLoginDisp", ConfigType.UInt)] AchievementAppealLoginDisp, /// - /// System option with the internal name ContentsFinderUseLangTypeJA. + /// UiConfig option with the internal name ContentsFinderUseLangTypeJA. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeJA", ConfigType.UInt)] ContentsFinderUseLangTypeJA, /// - /// System option with the internal name ContentsFinderUseLangTypeEN. + /// UiConfig option with the internal name ContentsFinderUseLangTypeEN. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeEN", ConfigType.UInt)] ContentsFinderUseLangTypeEN, /// - /// System option with the internal name ContentsFinderUseLangTypeDE. + /// UiConfig option with the internal name ContentsFinderUseLangTypeDE. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeDE", ConfigType.UInt)] ContentsFinderUseLangTypeDE, /// - /// System option with the internal name ContentsFinderUseLangTypeFR. + /// UiConfig option with the internal name ContentsFinderUseLangTypeFR. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeFR", ConfigType.UInt)] ContentsFinderUseLangTypeFR, /// - /// System option with the internal name ItemInventryWindowSizeType. + /// UiConfig option with the internal name ItemInventryWindowSizeType. /// This option is a UInt. /// [GameConfigOption("ItemInventryWindowSizeType", ConfigType.UInt)] ItemInventryWindowSizeType, /// - /// System option with the internal name ItemInventryRetainerWindowSizeType. + /// UiConfig option with the internal name ItemInventryRetainerWindowSizeType. /// This option is a UInt. /// [GameConfigOption("ItemInventryRetainerWindowSizeType", ConfigType.UInt)] ItemInventryRetainerWindowSizeType, /// - /// System option with the internal name BattleTalkShowFace. + /// UiConfig option with the internal name BattleTalkShowFace. /// This option is a UInt. /// [GameConfigOption("BattleTalkShowFace", ConfigType.UInt)] BattleTalkShowFace, /// - /// System option with the internal name BannerContentsDispType. + /// UiConfig option with the internal name BannerContentsDispType. /// This option is a UInt. /// [GameConfigOption("BannerContentsDispType", ConfigType.UInt)] BannerContentsDispType, /// - /// System option with the internal name BannerContentsNotice. + /// UiConfig option with the internal name BannerContentsNotice. /// This option is a UInt. /// [GameConfigOption("BannerContentsNotice", ConfigType.UInt)] BannerContentsNotice, /// - /// System option with the internal name MipDispType. + /// UiConfig option with the internal name MipDispType. /// This option is a UInt. /// [GameConfigOption("MipDispType", ConfigType.UInt)] MipDispType, /// - /// System option with the internal name BannerContentsOrderType. + /// UiConfig option with the internal name BannerContentsOrderType. /// This option is a UInt. /// [GameConfigOption("BannerContentsOrderType", ConfigType.UInt)] BannerContentsOrderType, /// - /// System option with the internal name CCProgressAllyFixLeftSide. + /// UiConfig option with the internal name CCProgressAllyFixLeftSide. /// This option is a UInt. /// [GameConfigOption("CCProgressAllyFixLeftSide", ConfigType.UInt)] CCProgressAllyFixLeftSide, /// - /// System option with the internal name CCMapAllyFixLeftSide. + /// UiConfig option with the internal name CCMapAllyFixLeftSide. /// This option is a UInt. /// [GameConfigOption("CCMapAllyFixLeftSide", ConfigType.UInt)] CCMapAllyFixLeftSide, /// - /// System option with the internal name DispCCCountDown. + /// UiConfig option with the internal name DispCCCountDown. /// This option is a UInt. /// [GameConfigOption("DispCCCountDown", ConfigType.UInt)] DispCCCountDown, /// - /// System option with the internal name EmoteTextType. + /// UiConfig option with the internal name EmoteTextType. /// This option is a UInt. /// [GameConfigOption("EmoteTextType", ConfigType.UInt)] EmoteTextType, /// - /// System option with the internal name IsEmoteSe. + /// UiConfig option with the internal name IsEmoteSe. /// This option is a UInt. /// [GameConfigOption("IsEmoteSe", ConfigType.UInt)] IsEmoteSe, /// - /// System option with the internal name EmoteSeType. + /// UiConfig option with the internal name EmoteSeType. /// This option is a UInt. /// [GameConfigOption("EmoteSeType", ConfigType.UInt)] EmoteSeType, /// - /// System option with the internal name PartyFinderNewArrivalDisp. + /// UiConfig option with the internal name PartyFinderNewArrivalDisp. /// This option is a UInt. /// [GameConfigOption("PartyFinderNewArrivalDisp", ConfigType.UInt)] PartyFinderNewArrivalDisp, /// - /// System option with the internal name GPoseTargetFilterNPCLookAt. + /// UiConfig option with the internal name GPoseTargetFilterNPCLookAt. /// This option is a UInt. /// [GameConfigOption("GPoseTargetFilterNPCLookAt", ConfigType.UInt)] GPoseTargetFilterNPCLookAt, /// - /// System option with the internal name GPoseMotionFilterAction. + /// UiConfig option with the internal name GPoseMotionFilterAction. /// This option is a UInt. /// [GameConfigOption("GPoseMotionFilterAction", ConfigType.UInt)] GPoseMotionFilterAction, /// - /// System option with the internal name LsListSortPriority. + /// UiConfig option with the internal name LsListSortPriority. /// This option is a UInt. /// [GameConfigOption("LsListSortPriority", ConfigType.UInt)] LsListSortPriority, /// - /// System option with the internal name FriendListSortPriority. + /// UiConfig option with the internal name FriendListSortPriority. /// This option is a UInt. /// [GameConfigOption("FriendListSortPriority", ConfigType.UInt)] FriendListSortPriority, /// - /// System option with the internal name FriendListFilterType. + /// UiConfig option with the internal name FriendListFilterType. /// This option is a UInt. /// [GameConfigOption("FriendListFilterType", ConfigType.UInt)] FriendListFilterType, /// - /// System option with the internal name FriendListSortType. + /// UiConfig option with the internal name FriendListSortType. /// This option is a UInt. /// [GameConfigOption("FriendListSortType", ConfigType.UInt)] FriendListSortType, /// - /// System option with the internal name LetterListFilterType. + /// UiConfig option with the internal name LetterListFilterType. /// This option is a UInt. /// [GameConfigOption("LetterListFilterType", ConfigType.UInt)] LetterListFilterType, /// - /// System option with the internal name LetterListSortType. + /// UiConfig option with the internal name LetterListSortType. /// This option is a UInt. /// [GameConfigOption("LetterListSortType", ConfigType.UInt)] LetterListSortType, /// - /// System option with the internal name ContentsReplayEnable. + /// UiConfig option with the internal name ContentsReplayEnable. /// This option is a UInt. /// [GameConfigOption("ContentsReplayEnable", ConfigType.UInt)] ContentsReplayEnable, /// - /// System option with the internal name MouseWheelOperationUp. + /// UiConfig option with the internal name MouseWheelOperationUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationUp", ConfigType.UInt)] MouseWheelOperationUp, /// - /// System option with the internal name MouseWheelOperationDown. + /// UiConfig option with the internal name MouseWheelOperationDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationDown", ConfigType.UInt)] MouseWheelOperationDown, /// - /// System option with the internal name MouseWheelOperationCtrlUp. + /// UiConfig option with the internal name MouseWheelOperationCtrlUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationCtrlUp", ConfigType.UInt)] MouseWheelOperationCtrlUp, /// - /// System option with the internal name MouseWheelOperationCtrlDown. + /// UiConfig option with the internal name MouseWheelOperationCtrlDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationCtrlDown", ConfigType.UInt)] MouseWheelOperationCtrlDown, /// - /// System option with the internal name MouseWheelOperationAltUp. + /// UiConfig option with the internal name MouseWheelOperationAltUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationAltUp", ConfigType.UInt)] MouseWheelOperationAltUp, /// - /// System option with the internal name MouseWheelOperationAltDown. + /// UiConfig option with the internal name MouseWheelOperationAltDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationAltDown", ConfigType.UInt)] MouseWheelOperationAltDown, /// - /// System option with the internal name MouseWheelOperationShiftUp. + /// UiConfig option with the internal name MouseWheelOperationShiftUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationShiftUp", ConfigType.UInt)] MouseWheelOperationShiftUp, /// - /// System option with the internal name MouseWheelOperationShiftDown. + /// UiConfig option with the internal name MouseWheelOperationShiftDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationShiftDown", ConfigType.UInt)] MouseWheelOperationShiftDown, /// - /// System option with the internal name TelepoTicketUseType. + /// UiConfig option with the internal name TelepoTicketUseType. /// This option is a UInt. /// [GameConfigOption("TelepoTicketUseType", ConfigType.UInt)] TelepoTicketUseType, /// - /// System option with the internal name TelepoTicketGilSetting. + /// UiConfig option with the internal name TelepoTicketGilSetting. /// This option is a UInt. /// [GameConfigOption("TelepoTicketGilSetting", ConfigType.UInt)] TelepoTicketGilSetting, /// - /// System option with the internal name TelepoCategoryType. + /// UiConfig option with the internal name TelepoCategoryType. /// This option is a UInt. /// [GameConfigOption("TelepoCategoryType", ConfigType.UInt)] TelepoCategoryType, /// - /// System option with the internal name HidePcAroundQuestProgressNpc. + /// UiConfig option with the internal name HidePcAroundQuestProgressNpc. /// This option is a UInt. /// [GameConfigOption("HidePcAroundQuestProgressNpc", ConfigType.UInt)] HidePcAroundQuestProgressNpc, /// - /// System option with the internal name HidePcAroundQuestProgressNpcIncludeParty. + /// UiConfig option with the internal name HidePcAroundQuestProgressNpcIncludeParty. /// This option is a UInt. /// [GameConfigOption("HidePcAroundQuestProgressNpcIncludeParty", ConfigType.UInt)] HidePcAroundQuestProgressNpcIncludeParty, /// - /// System option with the internal name HidePcAroundNpcAccessingQuest. + /// UiConfig option with the internal name HidePcAroundNpcAccessingQuest. /// This option is a UInt. /// [GameConfigOption("HidePcAroundNpcAccessingQuest", ConfigType.UInt)] HidePcAroundNpcAccessingQuest, /// - /// System option with the internal name HidePcAroundNpcAccessingQuestIncludeParty. + /// UiConfig option with the internal name HidePcAroundNpcAccessingQuestIncludeParty. /// This option is a UInt. /// [GameConfigOption("HidePcAroundNpcAccessingQuestIncludeParty", ConfigType.UInt)] HidePcAroundNpcAccessingQuestIncludeParty, /// - /// System option with the internal name PvPFrontlinesGCFree. + /// UiConfig option with the internal name PvPFrontlinesGCFree. /// This option is a UInt. /// [GameConfigOption("PvPFrontlinesGCFree", ConfigType.UInt)] PvPFrontlinesGCFree, - - /// - /// System option with the internal name PadMode. - /// This option is a UInt. - /// - [GameConfigOption("PadMode", ConfigType.UInt)] - PadMode, } diff --git a/Dalamud/Game/Config/UiControlOption.cs b/Dalamud/Game/Config/UiControlOption.cs index 5d36ee84d..99fb44c3b 100644 --- a/Dalamud/Game/Config/UiControlOption.cs +++ b/Dalamud/Game/Config/UiControlOption.cs @@ -10,1267 +10,1267 @@ public enum UiControlOption { /// - /// System option with the internal name AutoChangePointOfView. + /// UiControl option with the internal name AutoChangePointOfView. /// This option is a UInt. /// [GameConfigOption("AutoChangePointOfView", ConfigType.UInt)] AutoChangePointOfView, /// - /// System option with the internal name KeyboardCameraInterpolationType. + /// UiControl option with the internal name KeyboardCameraInterpolationType. /// This option is a UInt. /// [GameConfigOption("KeyboardCameraInterpolationType", ConfigType.UInt)] KeyboardCameraInterpolationType, /// - /// System option with the internal name KeyboardCameraVerticalInterpolation. + /// UiControl option with the internal name KeyboardCameraVerticalInterpolation. /// This option is a UInt. /// [GameConfigOption("KeyboardCameraVerticalInterpolation", ConfigType.UInt)] KeyboardCameraVerticalInterpolation, /// - /// System option with the internal name TiltOffset. + /// UiControl option with the internal name TiltOffset. /// This option is a Float. /// [GameConfigOption("TiltOffset", ConfigType.Float)] TiltOffset, /// - /// System option with the internal name KeyboardSpeed. + /// UiControl option with the internal name KeyboardSpeed. /// This option is a Float. /// [GameConfigOption("KeyboardSpeed", ConfigType.Float)] KeyboardSpeed, /// - /// System option with the internal name PadSpeed. + /// UiControl option with the internal name PadSpeed. /// This option is a Float. /// [GameConfigOption("PadSpeed", ConfigType.Float)] PadSpeed, /// - /// System option with the internal name PadFpsXReverse. + /// UiControl option with the internal name PadFpsXReverse. /// This option is a UInt. /// [GameConfigOption("PadFpsXReverse", ConfigType.UInt)] PadFpsXReverse, /// - /// System option with the internal name PadFpsYReverse. + /// UiControl option with the internal name PadFpsYReverse. /// This option is a UInt. /// [GameConfigOption("PadFpsYReverse", ConfigType.UInt)] PadFpsYReverse, /// - /// System option with the internal name PadTpsXReverse. + /// UiControl option with the internal name PadTpsXReverse. /// This option is a UInt. /// [GameConfigOption("PadTpsXReverse", ConfigType.UInt)] PadTpsXReverse, /// - /// System option with the internal name PadTpsYReverse. + /// UiControl option with the internal name PadTpsYReverse. /// This option is a UInt. /// [GameConfigOption("PadTpsYReverse", ConfigType.UInt)] PadTpsYReverse, /// - /// System option with the internal name MouseFpsXReverse. + /// UiControl option with the internal name MouseFpsXReverse. /// This option is a UInt. /// [GameConfigOption("MouseFpsXReverse", ConfigType.UInt)] MouseFpsXReverse, /// - /// System option with the internal name MouseFpsYReverse. + /// UiControl option with the internal name MouseFpsYReverse. /// This option is a UInt. /// [GameConfigOption("MouseFpsYReverse", ConfigType.UInt)] MouseFpsYReverse, /// - /// System option with the internal name MouseTpsXReverse. + /// UiControl option with the internal name MouseTpsXReverse. /// This option is a UInt. /// [GameConfigOption("MouseTpsXReverse", ConfigType.UInt)] MouseTpsXReverse, /// - /// System option with the internal name MouseTpsYReverse. + /// UiControl option with the internal name MouseTpsYReverse. /// This option is a UInt. /// [GameConfigOption("MouseTpsYReverse", ConfigType.UInt)] MouseTpsYReverse, /// - /// System option with the internal name MouseCharaViewRotYReverse. + /// UiControl option with the internal name MouseCharaViewRotYReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewRotYReverse", ConfigType.UInt)] MouseCharaViewRotYReverse, /// - /// System option with the internal name MouseCharaViewRotXReverse. + /// UiControl option with the internal name MouseCharaViewRotXReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewRotXReverse", ConfigType.UInt)] MouseCharaViewRotXReverse, /// - /// System option with the internal name MouseCharaViewMoveYReverse. + /// UiControl option with the internal name MouseCharaViewMoveYReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewMoveYReverse", ConfigType.UInt)] MouseCharaViewMoveYReverse, /// - /// System option with the internal name MouseCharaViewMoveXReverse. + /// UiControl option with the internal name MouseCharaViewMoveXReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewMoveXReverse", ConfigType.UInt)] MouseCharaViewMoveXReverse, /// - /// System option with the internal name PADCharaViewRotYReverse. + /// UiControl option with the internal name PADCharaViewRotYReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewRotYReverse", ConfigType.UInt)] PADCharaViewRotYReverse, /// - /// System option with the internal name PADCharaViewRotXReverse. + /// UiControl option with the internal name PADCharaViewRotXReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewRotXReverse", ConfigType.UInt)] PADCharaViewRotXReverse, /// - /// System option with the internal name PADCharaViewMoveYReverse. + /// UiControl option with the internal name PADCharaViewMoveYReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewMoveYReverse", ConfigType.UInt)] PADCharaViewMoveYReverse, /// - /// System option with the internal name PADCharaViewMoveXReverse. + /// UiControl option with the internal name PADCharaViewMoveXReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewMoveXReverse", ConfigType.UInt)] PADCharaViewMoveXReverse, /// - /// System option with the internal name FlyingControlType. + /// UiControl option with the internal name FlyingControlType. /// This option is a UInt. /// [GameConfigOption("FlyingControlType", ConfigType.UInt)] FlyingControlType, /// - /// System option with the internal name FlyingLegacyAutorun. + /// UiControl option with the internal name FlyingLegacyAutorun. /// This option is a UInt. /// [GameConfigOption("FlyingLegacyAutorun", ConfigType.UInt)] FlyingLegacyAutorun, /// - /// System option with the internal name AutoFaceTargetOnAction. + /// UiControl option with the internal name AutoFaceTargetOnAction. /// This option is a UInt. /// [GameConfigOption("AutoFaceTargetOnAction", ConfigType.UInt)] AutoFaceTargetOnAction, /// - /// System option with the internal name SelfClick. + /// UiControl option with the internal name SelfClick. /// This option is a UInt. /// [GameConfigOption("SelfClick", ConfigType.UInt)] SelfClick, /// - /// System option with the internal name NoTargetClickCancel. + /// UiControl option with the internal name NoTargetClickCancel. /// This option is a UInt. /// [GameConfigOption("NoTargetClickCancel", ConfigType.UInt)] NoTargetClickCancel, /// - /// System option with the internal name AutoTarget. + /// UiControl option with the internal name AutoTarget. /// This option is a UInt. /// [GameConfigOption("AutoTarget", ConfigType.UInt)] AutoTarget, /// - /// System option with the internal name TargetTypeSelect. + /// UiControl option with the internal name TargetTypeSelect. /// This option is a UInt. /// [GameConfigOption("TargetTypeSelect", ConfigType.UInt)] TargetTypeSelect, /// - /// System option with the internal name AutoLockOn. + /// UiControl option with the internal name AutoLockOn. /// This option is a UInt. /// [GameConfigOption("AutoLockOn", ConfigType.UInt)] AutoLockOn, /// - /// System option with the internal name CircleBattleModeAutoChange. + /// UiControl option with the internal name CircleBattleModeAutoChange. /// This option is a UInt. /// [GameConfigOption("CircleBattleModeAutoChange", ConfigType.UInt)] CircleBattleModeAutoChange, /// - /// System option with the internal name CircleIsCustom. + /// UiControl option with the internal name CircleIsCustom. /// This option is a UInt. /// [GameConfigOption("CircleIsCustom", ConfigType.UInt)] CircleIsCustom, /// - /// System option with the internal name CircleSwordDrawnIsActive. + /// UiControl option with the internal name CircleSwordDrawnIsActive. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnIsActive", ConfigType.UInt)] CircleSwordDrawnIsActive, /// - /// System option with the internal name CircleSwordDrawnNonPartyPc. + /// UiControl option with the internal name CircleSwordDrawnNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnNonPartyPc", ConfigType.UInt)] CircleSwordDrawnNonPartyPc, /// - /// System option with the internal name CircleSwordDrawnParty. + /// UiControl option with the internal name CircleSwordDrawnParty. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnParty", ConfigType.UInt)] CircleSwordDrawnParty, /// - /// System option with the internal name CircleSwordDrawnEnemy. + /// UiControl option with the internal name CircleSwordDrawnEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnEnemy", ConfigType.UInt)] CircleSwordDrawnEnemy, /// - /// System option with the internal name CircleSwordDrawnAggro. + /// UiControl option with the internal name CircleSwordDrawnAggro. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnAggro", ConfigType.UInt)] CircleSwordDrawnAggro, /// - /// System option with the internal name CircleSwordDrawnNpcOrObject. + /// UiControl option with the internal name CircleSwordDrawnNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnNpcOrObject", ConfigType.UInt)] CircleSwordDrawnNpcOrObject, /// - /// System option with the internal name CircleSwordDrawnMinion. + /// UiControl option with the internal name CircleSwordDrawnMinion. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnMinion", ConfigType.UInt)] CircleSwordDrawnMinion, /// - /// System option with the internal name CircleSwordDrawnDutyEnemy. + /// UiControl option with the internal name CircleSwordDrawnDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnDutyEnemy", ConfigType.UInt)] CircleSwordDrawnDutyEnemy, /// - /// System option with the internal name CircleSwordDrawnPet. + /// UiControl option with the internal name CircleSwordDrawnPet. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnPet", ConfigType.UInt)] CircleSwordDrawnPet, /// - /// System option with the internal name CircleSwordDrawnAlliance. + /// UiControl option with the internal name CircleSwordDrawnAlliance. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnAlliance", ConfigType.UInt)] CircleSwordDrawnAlliance, /// - /// System option with the internal name CircleSwordDrawnMark. + /// UiControl option with the internal name CircleSwordDrawnMark. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnMark", ConfigType.UInt)] CircleSwordDrawnMark, /// - /// System option with the internal name CircleSheathedIsActive. + /// UiControl option with the internal name CircleSheathedIsActive. /// This option is a UInt. /// [GameConfigOption("CircleSheathedIsActive", ConfigType.UInt)] CircleSheathedIsActive, /// - /// System option with the internal name CircleSheathedNonPartyPc. + /// UiControl option with the internal name CircleSheathedNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleSheathedNonPartyPc", ConfigType.UInt)] CircleSheathedNonPartyPc, /// - /// System option with the internal name CircleSheathedParty. + /// UiControl option with the internal name CircleSheathedParty. /// This option is a UInt. /// [GameConfigOption("CircleSheathedParty", ConfigType.UInt)] CircleSheathedParty, /// - /// System option with the internal name CircleSheathedEnemy. + /// UiControl option with the internal name CircleSheathedEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSheathedEnemy", ConfigType.UInt)] CircleSheathedEnemy, /// - /// System option with the internal name CircleSheathedAggro. + /// UiControl option with the internal name CircleSheathedAggro. /// This option is a UInt. /// [GameConfigOption("CircleSheathedAggro", ConfigType.UInt)] CircleSheathedAggro, /// - /// System option with the internal name CircleSheathedNpcOrObject. + /// UiControl option with the internal name CircleSheathedNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleSheathedNpcOrObject", ConfigType.UInt)] CircleSheathedNpcOrObject, /// - /// System option with the internal name CircleSheathedMinion. + /// UiControl option with the internal name CircleSheathedMinion. /// This option is a UInt. /// [GameConfigOption("CircleSheathedMinion", ConfigType.UInt)] CircleSheathedMinion, /// - /// System option with the internal name CircleSheathedDutyEnemy. + /// UiControl option with the internal name CircleSheathedDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSheathedDutyEnemy", ConfigType.UInt)] CircleSheathedDutyEnemy, /// - /// System option with the internal name CircleSheathedPet. + /// UiControl option with the internal name CircleSheathedPet. /// This option is a UInt. /// [GameConfigOption("CircleSheathedPet", ConfigType.UInt)] CircleSheathedPet, /// - /// System option with the internal name CircleSheathedAlliance. + /// UiControl option with the internal name CircleSheathedAlliance. /// This option is a UInt. /// [GameConfigOption("CircleSheathedAlliance", ConfigType.UInt)] CircleSheathedAlliance, /// - /// System option with the internal name CircleSheathedMark. + /// UiControl option with the internal name CircleSheathedMark. /// This option is a UInt. /// [GameConfigOption("CircleSheathedMark", ConfigType.UInt)] CircleSheathedMark, /// - /// System option with the internal name CircleClickIsActive. + /// UiControl option with the internal name CircleClickIsActive. /// This option is a UInt. /// [GameConfigOption("CircleClickIsActive", ConfigType.UInt)] CircleClickIsActive, /// - /// System option with the internal name CircleClickNonPartyPc. + /// UiControl option with the internal name CircleClickNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleClickNonPartyPc", ConfigType.UInt)] CircleClickNonPartyPc, /// - /// System option with the internal name CircleClickParty. + /// UiControl option with the internal name CircleClickParty. /// This option is a UInt. /// [GameConfigOption("CircleClickParty", ConfigType.UInt)] CircleClickParty, /// - /// System option with the internal name CircleClickEnemy. + /// UiControl option with the internal name CircleClickEnemy. /// This option is a UInt. /// [GameConfigOption("CircleClickEnemy", ConfigType.UInt)] CircleClickEnemy, /// - /// System option with the internal name CircleClickAggro. + /// UiControl option with the internal name CircleClickAggro. /// This option is a UInt. /// [GameConfigOption("CircleClickAggro", ConfigType.UInt)] CircleClickAggro, /// - /// System option with the internal name CircleClickNpcOrObject. + /// UiControl option with the internal name CircleClickNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleClickNpcOrObject", ConfigType.UInt)] CircleClickNpcOrObject, /// - /// System option with the internal name CircleClickMinion. + /// UiControl option with the internal name CircleClickMinion. /// This option is a UInt. /// [GameConfigOption("CircleClickMinion", ConfigType.UInt)] CircleClickMinion, /// - /// System option with the internal name CircleClickDutyEnemy. + /// UiControl option with the internal name CircleClickDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleClickDutyEnemy", ConfigType.UInt)] CircleClickDutyEnemy, /// - /// System option with the internal name CircleClickPet. + /// UiControl option with the internal name CircleClickPet. /// This option is a UInt. /// [GameConfigOption("CircleClickPet", ConfigType.UInt)] CircleClickPet, /// - /// System option with the internal name CircleClickAlliance. + /// UiControl option with the internal name CircleClickAlliance. /// This option is a UInt. /// [GameConfigOption("CircleClickAlliance", ConfigType.UInt)] CircleClickAlliance, /// - /// System option with the internal name CircleClickMark. + /// UiControl option with the internal name CircleClickMark. /// This option is a UInt. /// [GameConfigOption("CircleClickMark", ConfigType.UInt)] CircleClickMark, /// - /// System option with the internal name CircleXButtonIsActive. + /// UiControl option with the internal name CircleXButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleXButtonIsActive", ConfigType.UInt)] CircleXButtonIsActive, /// - /// System option with the internal name CircleXButtonNonPartyPc. + /// UiControl option with the internal name CircleXButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleXButtonNonPartyPc", ConfigType.UInt)] CircleXButtonNonPartyPc, /// - /// System option with the internal name CircleXButtonParty. + /// UiControl option with the internal name CircleXButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleXButtonParty", ConfigType.UInt)] CircleXButtonParty, /// - /// System option with the internal name CircleXButtonEnemy. + /// UiControl option with the internal name CircleXButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleXButtonEnemy", ConfigType.UInt)] CircleXButtonEnemy, /// - /// System option with the internal name CircleXButtonAggro. + /// UiControl option with the internal name CircleXButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleXButtonAggro", ConfigType.UInt)] CircleXButtonAggro, /// - /// System option with the internal name CircleXButtonNpcOrObject. + /// UiControl option with the internal name CircleXButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleXButtonNpcOrObject", ConfigType.UInt)] CircleXButtonNpcOrObject, /// - /// System option with the internal name CircleXButtonMinion. + /// UiControl option with the internal name CircleXButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleXButtonMinion", ConfigType.UInt)] CircleXButtonMinion, /// - /// System option with the internal name CircleXButtonDutyEnemy. + /// UiControl option with the internal name CircleXButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleXButtonDutyEnemy", ConfigType.UInt)] CircleXButtonDutyEnemy, /// - /// System option with the internal name CircleXButtonPet. + /// UiControl option with the internal name CircleXButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleXButtonPet", ConfigType.UInt)] CircleXButtonPet, /// - /// System option with the internal name CircleXButtonAlliance. + /// UiControl option with the internal name CircleXButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleXButtonAlliance", ConfigType.UInt)] CircleXButtonAlliance, /// - /// System option with the internal name CircleXButtonMark. + /// UiControl option with the internal name CircleXButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleXButtonMark", ConfigType.UInt)] CircleXButtonMark, /// - /// System option with the internal name CircleYButtonIsActive. + /// UiControl option with the internal name CircleYButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleYButtonIsActive", ConfigType.UInt)] CircleYButtonIsActive, /// - /// System option with the internal name CircleYButtonNonPartyPc. + /// UiControl option with the internal name CircleYButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleYButtonNonPartyPc", ConfigType.UInt)] CircleYButtonNonPartyPc, /// - /// System option with the internal name CircleYButtonParty. + /// UiControl option with the internal name CircleYButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleYButtonParty", ConfigType.UInt)] CircleYButtonParty, /// - /// System option with the internal name CircleYButtonEnemy. + /// UiControl option with the internal name CircleYButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleYButtonEnemy", ConfigType.UInt)] CircleYButtonEnemy, /// - /// System option with the internal name CircleYButtonAggro. + /// UiControl option with the internal name CircleYButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleYButtonAggro", ConfigType.UInt)] CircleYButtonAggro, /// - /// System option with the internal name CircleYButtonNpcOrObject. + /// UiControl option with the internal name CircleYButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleYButtonNpcOrObject", ConfigType.UInt)] CircleYButtonNpcOrObject, /// - /// System option with the internal name CircleYButtonMinion. + /// UiControl option with the internal name CircleYButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleYButtonMinion", ConfigType.UInt)] CircleYButtonMinion, /// - /// System option with the internal name CircleYButtonDutyEnemy. + /// UiControl option with the internal name CircleYButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleYButtonDutyEnemy", ConfigType.UInt)] CircleYButtonDutyEnemy, /// - /// System option with the internal name CircleYButtonPet. + /// UiControl option with the internal name CircleYButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleYButtonPet", ConfigType.UInt)] CircleYButtonPet, /// - /// System option with the internal name CircleYButtonAlliance. + /// UiControl option with the internal name CircleYButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleYButtonAlliance", ConfigType.UInt)] CircleYButtonAlliance, /// - /// System option with the internal name CircleYButtonMark. + /// UiControl option with the internal name CircleYButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleYButtonMark", ConfigType.UInt)] CircleYButtonMark, /// - /// System option with the internal name CircleBButtonIsActive. + /// UiControl option with the internal name CircleBButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleBButtonIsActive", ConfigType.UInt)] CircleBButtonIsActive, /// - /// System option with the internal name CircleBButtonNonPartyPc. + /// UiControl option with the internal name CircleBButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleBButtonNonPartyPc", ConfigType.UInt)] CircleBButtonNonPartyPc, /// - /// System option with the internal name CircleBButtonParty. + /// UiControl option with the internal name CircleBButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleBButtonParty", ConfigType.UInt)] CircleBButtonParty, /// - /// System option with the internal name CircleBButtonEnemy. + /// UiControl option with the internal name CircleBButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleBButtonEnemy", ConfigType.UInt)] CircleBButtonEnemy, /// - /// System option with the internal name CircleBButtonAggro. + /// UiControl option with the internal name CircleBButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleBButtonAggro", ConfigType.UInt)] CircleBButtonAggro, /// - /// System option with the internal name CircleBButtonNpcOrObject. + /// UiControl option with the internal name CircleBButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleBButtonNpcOrObject", ConfigType.UInt)] CircleBButtonNpcOrObject, /// - /// System option with the internal name CircleBButtonMinion. + /// UiControl option with the internal name CircleBButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleBButtonMinion", ConfigType.UInt)] CircleBButtonMinion, /// - /// System option with the internal name CircleBButtonDutyEnemy. + /// UiControl option with the internal name CircleBButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleBButtonDutyEnemy", ConfigType.UInt)] CircleBButtonDutyEnemy, /// - /// System option with the internal name CircleBButtonPet. + /// UiControl option with the internal name CircleBButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleBButtonPet", ConfigType.UInt)] CircleBButtonPet, /// - /// System option with the internal name CircleBButtonAlliance. + /// UiControl option with the internal name CircleBButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleBButtonAlliance", ConfigType.UInt)] CircleBButtonAlliance, /// - /// System option with the internal name CircleBButtonMark. + /// UiControl option with the internal name CircleBButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleBButtonMark", ConfigType.UInt)] CircleBButtonMark, /// - /// System option with the internal name CircleAButtonIsActive. + /// UiControl option with the internal name CircleAButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleAButtonIsActive", ConfigType.UInt)] CircleAButtonIsActive, /// - /// System option with the internal name CircleAButtonNonPartyPc. + /// UiControl option with the internal name CircleAButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleAButtonNonPartyPc", ConfigType.UInt)] CircleAButtonNonPartyPc, /// - /// System option with the internal name CircleAButtonParty. + /// UiControl option with the internal name CircleAButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleAButtonParty", ConfigType.UInt)] CircleAButtonParty, /// - /// System option with the internal name CircleAButtonEnemy. + /// UiControl option with the internal name CircleAButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleAButtonEnemy", ConfigType.UInt)] CircleAButtonEnemy, /// - /// System option with the internal name CircleAButtonAggro. + /// UiControl option with the internal name CircleAButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleAButtonAggro", ConfigType.UInt)] CircleAButtonAggro, /// - /// System option with the internal name CircleAButtonNpcOrObject. + /// UiControl option with the internal name CircleAButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleAButtonNpcOrObject", ConfigType.UInt)] CircleAButtonNpcOrObject, /// - /// System option with the internal name CircleAButtonMinion. + /// UiControl option with the internal name CircleAButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleAButtonMinion", ConfigType.UInt)] CircleAButtonMinion, /// - /// System option with the internal name CircleAButtonDutyEnemy. + /// UiControl option with the internal name CircleAButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleAButtonDutyEnemy", ConfigType.UInt)] CircleAButtonDutyEnemy, /// - /// System option with the internal name CircleAButtonPet. + /// UiControl option with the internal name CircleAButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleAButtonPet", ConfigType.UInt)] CircleAButtonPet, /// - /// System option with the internal name CircleAButtonAlliance. + /// UiControl option with the internal name CircleAButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleAButtonAlliance", ConfigType.UInt)] CircleAButtonAlliance, /// - /// System option with the internal name CircleAButtonMark. + /// UiControl option with the internal name CircleAButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleAButtonMark", ConfigType.UInt)] CircleAButtonMark, /// - /// System option with the internal name GroundTargetType. + /// UiControl option with the internal name GroundTargetType. /// This option is a UInt. /// [GameConfigOption("GroundTargetType", ConfigType.UInt)] GroundTargetType, /// - /// System option with the internal name GroundTargetCursorSpeed. + /// UiControl option with the internal name GroundTargetCursorSpeed. /// This option is a UInt. /// [GameConfigOption("GroundTargetCursorSpeed", ConfigType.UInt)] GroundTargetCursorSpeed, /// - /// System option with the internal name TargetCircleType. + /// UiControl option with the internal name TargetCircleType. /// This option is a UInt. /// [GameConfigOption("TargetCircleType", ConfigType.UInt)] TargetCircleType, /// - /// System option with the internal name TargetLineType. + /// UiControl option with the internal name TargetLineType. /// This option is a UInt. /// [GameConfigOption("TargetLineType", ConfigType.UInt)] TargetLineType, /// - /// System option with the internal name LinkLineType. + /// UiControl option with the internal name LinkLineType. /// This option is a UInt. /// [GameConfigOption("LinkLineType", ConfigType.UInt)] LinkLineType, /// - /// System option with the internal name ObjectBorderingType. + /// UiControl option with the internal name ObjectBorderingType. /// This option is a UInt. /// [GameConfigOption("ObjectBorderingType", ConfigType.UInt)] ObjectBorderingType, /// - /// System option with the internal name MoveMode. + /// UiControl option with the internal name MoveMode. /// This option is a UInt. /// [GameConfigOption("MoveMode", ConfigType.UInt)] MoveMode, /// - /// System option with the internal name HotbarDisp. + /// UiControl option with the internal name HotbarDisp. /// This option is a UInt. /// [GameConfigOption("HotbarDisp", ConfigType.UInt)] HotbarDisp, /// - /// System option with the internal name HotbarEmptyVisible. + /// UiControl option with the internal name HotbarEmptyVisible. /// This option is a UInt. /// [GameConfigOption("HotbarEmptyVisible", ConfigType.UInt)] HotbarEmptyVisible, /// - /// System option with the internal name HotbarNoneSlotDisp01. + /// UiControl option with the internal name HotbarNoneSlotDisp01. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp01", ConfigType.UInt)] HotbarNoneSlotDisp01, /// - /// System option with the internal name HotbarNoneSlotDisp02. + /// UiControl option with the internal name HotbarNoneSlotDisp02. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp02", ConfigType.UInt)] HotbarNoneSlotDisp02, /// - /// System option with the internal name HotbarNoneSlotDisp03. + /// UiControl option with the internal name HotbarNoneSlotDisp03. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp03", ConfigType.UInt)] HotbarNoneSlotDisp03, /// - /// System option with the internal name HotbarNoneSlotDisp04. + /// UiControl option with the internal name HotbarNoneSlotDisp04. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp04", ConfigType.UInt)] HotbarNoneSlotDisp04, /// - /// System option with the internal name HotbarNoneSlotDisp05. + /// UiControl option with the internal name HotbarNoneSlotDisp05. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp05", ConfigType.UInt)] HotbarNoneSlotDisp05, /// - /// System option with the internal name HotbarNoneSlotDisp06. + /// UiControl option with the internal name HotbarNoneSlotDisp06. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp06", ConfigType.UInt)] HotbarNoneSlotDisp06, /// - /// System option with the internal name HotbarNoneSlotDisp07. + /// UiControl option with the internal name HotbarNoneSlotDisp07. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp07", ConfigType.UInt)] HotbarNoneSlotDisp07, /// - /// System option with the internal name HotbarNoneSlotDisp08. + /// UiControl option with the internal name HotbarNoneSlotDisp08. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp08", ConfigType.UInt)] HotbarNoneSlotDisp08, /// - /// System option with the internal name HotbarNoneSlotDisp09. + /// UiControl option with the internal name HotbarNoneSlotDisp09. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp09", ConfigType.UInt)] HotbarNoneSlotDisp09, /// - /// System option with the internal name HotbarNoneSlotDisp10. + /// UiControl option with the internal name HotbarNoneSlotDisp10. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp10", ConfigType.UInt)] HotbarNoneSlotDisp10, /// - /// System option with the internal name HotbarNoneSlotDispEX. + /// UiControl option with the internal name HotbarNoneSlotDispEX. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDispEX", ConfigType.UInt)] HotbarNoneSlotDispEX, /// - /// System option with the internal name ExHotbarSetting. + /// UiControl option with the internal name ExHotbarSetting. /// This option is a UInt. /// [GameConfigOption("ExHotbarSetting", ConfigType.UInt)] ExHotbarSetting, /// - /// System option with the internal name HotbarExHotbarUseSetting. + /// UiControl option with the internal name HotbarExHotbarUseSetting. /// This option is a UInt. /// [GameConfigOption("HotbarExHotbarUseSetting", ConfigType.UInt)] HotbarExHotbarUseSetting, /// - /// System option with the internal name HotbarCrossUseEx. + /// UiControl option with the internal name HotbarCrossUseEx. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseEx", ConfigType.UInt)] HotbarCrossUseEx, /// - /// System option with the internal name HotbarCrossUseExDirection. + /// UiControl option with the internal name HotbarCrossUseExDirection. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseExDirection", ConfigType.UInt)] HotbarCrossUseExDirection, /// - /// System option with the internal name HotbarCrossDispType. + /// UiControl option with the internal name HotbarCrossDispType. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDispType", ConfigType.UInt)] HotbarCrossDispType, /// - /// System option with the internal name PartyListSoloOff. + /// UiControl option with the internal name PartyListSoloOff. /// This option is a UInt. /// [GameConfigOption("PartyListSoloOff", ConfigType.UInt)] PartyListSoloOff, /// - /// System option with the internal name HowTo. + /// UiControl option with the internal name HowTo. /// This option is a UInt. /// [GameConfigOption("HowTo", ConfigType.UInt)] HowTo, /// - /// System option with the internal name HousingFurnitureBindConfirm. + /// UiControl option with the internal name HousingFurnitureBindConfirm. /// This option is a UInt. /// [GameConfigOption("HousingFurnitureBindConfirm", ConfigType.UInt)] HousingFurnitureBindConfirm, /// - /// System option with the internal name DirectChat. + /// UiControl option with the internal name DirectChat. /// This option is a UInt. /// [GameConfigOption("DirectChat", ConfigType.UInt)] DirectChat, /// - /// System option with the internal name CharaParamDisp. + /// UiControl option with the internal name CharaParamDisp. /// This option is a UInt. /// [GameConfigOption("CharaParamDisp", ConfigType.UInt)] CharaParamDisp, /// - /// System option with the internal name LimitBreakGaugeDisp. + /// UiControl option with the internal name LimitBreakGaugeDisp. /// This option is a UInt. /// [GameConfigOption("LimitBreakGaugeDisp", ConfigType.UInt)] LimitBreakGaugeDisp, /// - /// System option with the internal name ScenarioTreeDisp. + /// UiControl option with the internal name ScenarioTreeDisp. /// This option is a UInt. /// [GameConfigOption("ScenarioTreeDisp", ConfigType.UInt)] ScenarioTreeDisp, /// - /// System option with the internal name ScenarioTreeCompleteDisp. + /// UiControl option with the internal name ScenarioTreeCompleteDisp. /// This option is a UInt. /// [GameConfigOption("ScenarioTreeCompleteDisp", ConfigType.UInt)] ScenarioTreeCompleteDisp, /// - /// System option with the internal name HotbarCrossDispAlways. + /// UiControl option with the internal name HotbarCrossDispAlways. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDispAlways", ConfigType.UInt)] HotbarCrossDispAlways, /// - /// System option with the internal name ExpDisp. + /// UiControl option with the internal name ExpDisp. /// This option is a UInt. /// [GameConfigOption("ExpDisp", ConfigType.UInt)] ExpDisp, /// - /// System option with the internal name InventryStatusDisp. + /// UiControl option with the internal name InventryStatusDisp. /// This option is a UInt. /// [GameConfigOption("InventryStatusDisp", ConfigType.UInt)] InventryStatusDisp, /// - /// System option with the internal name DutyListDisp. + /// UiControl option with the internal name DutyListDisp. /// This option is a UInt. /// [GameConfigOption("DutyListDisp", ConfigType.UInt)] DutyListDisp, /// - /// System option with the internal name NaviMapDisp. + /// UiControl option with the internal name NaviMapDisp. /// This option is a UInt. /// [GameConfigOption("NaviMapDisp", ConfigType.UInt)] NaviMapDisp, /// - /// System option with the internal name GilStatusDisp. + /// UiControl option with the internal name GilStatusDisp. /// This option is a UInt. /// [GameConfigOption("GilStatusDisp", ConfigType.UInt)] GilStatusDisp, /// - /// System option with the internal name InfoSettingDisp. + /// UiControl option with the internal name InfoSettingDisp. /// This option is a UInt. /// [GameConfigOption("InfoSettingDisp", ConfigType.UInt)] InfoSettingDisp, /// - /// System option with the internal name InfoSettingDispType. + /// UiControl option with the internal name InfoSettingDispType. /// This option is a UInt. /// [GameConfigOption("InfoSettingDispType", ConfigType.UInt)] InfoSettingDispType, /// - /// System option with the internal name TargetInfoDisp. + /// UiControl option with the internal name TargetInfoDisp. /// This option is a UInt. /// [GameConfigOption("TargetInfoDisp", ConfigType.UInt)] TargetInfoDisp, /// - /// System option with the internal name EnemyListDisp. + /// UiControl option with the internal name EnemyListDisp. /// This option is a UInt. /// [GameConfigOption("EnemyListDisp", ConfigType.UInt)] EnemyListDisp, /// - /// System option with the internal name FocusTargetDisp. + /// UiControl option with the internal name FocusTargetDisp. /// This option is a UInt. /// [GameConfigOption("FocusTargetDisp", ConfigType.UInt)] FocusTargetDisp, /// - /// System option with the internal name ItemDetailDisp. + /// UiControl option with the internal name ItemDetailDisp. /// This option is a UInt. /// [GameConfigOption("ItemDetailDisp", ConfigType.UInt)] ItemDetailDisp, /// - /// System option with the internal name ActionDetailDisp. + /// UiControl option with the internal name ActionDetailDisp. /// This option is a UInt. /// [GameConfigOption("ActionDetailDisp", ConfigType.UInt)] ActionDetailDisp, /// - /// System option with the internal name DetailTrackingType. + /// UiControl option with the internal name DetailTrackingType. /// This option is a UInt. /// [GameConfigOption("DetailTrackingType", ConfigType.UInt)] DetailTrackingType, /// - /// System option with the internal name ToolTipDisp. + /// UiControl option with the internal name ToolTipDisp. /// This option is a UInt. /// [GameConfigOption("ToolTipDisp", ConfigType.UInt)] ToolTipDisp, /// - /// System option with the internal name MapPermeationRate. + /// UiControl option with the internal name MapPermeationRate. /// This option is a UInt. /// [GameConfigOption("MapPermeationRate", ConfigType.UInt)] MapPermeationRate, /// - /// System option with the internal name MapOperationType. + /// UiControl option with the internal name MapOperationType. /// This option is a UInt. /// [GameConfigOption("MapOperationType", ConfigType.UInt)] MapOperationType, /// - /// System option with the internal name PartyListDisp. + /// UiControl option with the internal name PartyListDisp. /// This option is a UInt. /// [GameConfigOption("PartyListDisp", ConfigType.UInt)] PartyListDisp, /// - /// System option with the internal name PartyListNameType. + /// UiControl option with the internal name PartyListNameType. /// This option is a UInt. /// [GameConfigOption("PartyListNameType", ConfigType.UInt)] PartyListNameType, /// - /// System option with the internal name FlyTextDisp. + /// UiControl option with the internal name FlyTextDisp. /// This option is a UInt. /// [GameConfigOption("FlyTextDisp", ConfigType.UInt)] FlyTextDisp, /// - /// System option with the internal name MapPermeationMode. + /// UiControl option with the internal name MapPermeationMode. /// This option is a UInt. /// [GameConfigOption("MapPermeationMode", ConfigType.UInt)] MapPermeationMode, /// - /// System option with the internal name AllianceList1Disp. + /// UiControl option with the internal name AllianceList1Disp. /// This option is a UInt. /// [GameConfigOption("AllianceList1Disp", ConfigType.UInt)] AllianceList1Disp, /// - /// System option with the internal name AllianceList2Disp. + /// UiControl option with the internal name AllianceList2Disp. /// This option is a UInt. /// [GameConfigOption("AllianceList2Disp", ConfigType.UInt)] AllianceList2Disp, /// - /// System option with the internal name TargetInfoSelfBuff. + /// UiControl option with the internal name TargetInfoSelfBuff. /// This option is a UInt. /// [GameConfigOption("TargetInfoSelfBuff", ConfigType.UInt)] TargetInfoSelfBuff, /// - /// System option with the internal name PopUpTextDisp. + /// UiControl option with the internal name PopUpTextDisp. /// This option is a UInt. /// [GameConfigOption("PopUpTextDisp", ConfigType.UInt)] PopUpTextDisp, /// - /// System option with the internal name ContentsInfoDisp. + /// UiControl option with the internal name ContentsInfoDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoDisp", ConfigType.UInt)] ContentsInfoDisp, /// - /// System option with the internal name DutyListHideWhenCntInfoDisp. + /// UiControl option with the internal name DutyListHideWhenCntInfoDisp. /// This option is a UInt. /// [GameConfigOption("DutyListHideWhenCntInfoDisp", ConfigType.UInt)] DutyListHideWhenCntInfoDisp, /// - /// System option with the internal name DutyListNumDisp. + /// UiControl option with the internal name DutyListNumDisp. /// This option is a UInt. /// [GameConfigOption("DutyListNumDisp", ConfigType.UInt)] DutyListNumDisp, /// - /// System option with the internal name InInstanceContentDutyListDisp. + /// UiControl option with the internal name InInstanceContentDutyListDisp. /// This option is a UInt. /// [GameConfigOption("InInstanceContentDutyListDisp", ConfigType.UInt)] InInstanceContentDutyListDisp, /// - /// System option with the internal name InPublicContentDutyListDisp. + /// UiControl option with the internal name InPublicContentDutyListDisp. /// This option is a UInt. /// [GameConfigOption("InPublicContentDutyListDisp", ConfigType.UInt)] InPublicContentDutyListDisp, /// - /// System option with the internal name ContentsInfoJoiningRequestDisp. + /// UiControl option with the internal name ContentsInfoJoiningRequestDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoJoiningRequestDisp", ConfigType.UInt)] ContentsInfoJoiningRequestDisp, /// - /// System option with the internal name ContentsInfoJoiningRequestSituationDisp. + /// UiControl option with the internal name ContentsInfoJoiningRequestSituationDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoJoiningRequestSituationDisp", ConfigType.UInt)] ContentsInfoJoiningRequestSituationDisp, /// - /// System option with the internal name HotbarDispSetNum. + /// UiControl option with the internal name HotbarDispSetNum. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetNum", ConfigType.UInt)] HotbarDispSetNum, /// - /// System option with the internal name HotbarDispSetChangeType. + /// UiControl option with the internal name HotbarDispSetChangeType. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetChangeType", ConfigType.UInt)] HotbarDispSetChangeType, /// - /// System option with the internal name HotbarDispSetDragType. + /// UiControl option with the internal name HotbarDispSetDragType. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetDragType", ConfigType.UInt)] HotbarDispSetDragType, /// - /// System option with the internal name MainCommandType. + /// UiControl option with the internal name MainCommandType. /// This option is a UInt. /// [GameConfigOption("MainCommandType", ConfigType.UInt)] MainCommandType, /// - /// System option with the internal name MainCommandDisp. + /// UiControl option with the internal name MainCommandDisp. /// This option is a UInt. /// [GameConfigOption("MainCommandDisp", ConfigType.UInt)] MainCommandDisp, /// - /// System option with the internal name MainCommandDragShortcut. + /// UiControl option with the internal name MainCommandDragShortcut. /// This option is a UInt. /// [GameConfigOption("MainCommandDragShortcut", ConfigType.UInt)] MainCommandDragShortcut, /// - /// System option with the internal name HotbarDispLookNum. + /// UiControl option with the internal name HotbarDispLookNum. /// This option is a UInt. /// [GameConfigOption("HotbarDispLookNum", ConfigType.UInt)] diff --git a/Dalamud/Game/DutyState/DutyState.cs b/Dalamud/Game/DutyState/DutyState.cs index 3a3afbab0..4d719685b 100644 --- a/Dalamud/Game/DutyState/DutyState.cs +++ b/Dalamud/Game/DutyState/DutyState.cs @@ -1,10 +1,11 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Conditions; using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; namespace Dalamud.Game.DutyState; @@ -81,33 +82,33 @@ internal unsafe class DutyState : IInternalDisposableService, IDutyState // Duty Commenced case 0x4000_0001: this.IsDutyStarted = true; - this.DutyStarted?.Invoke(this, this.clientState.TerritoryType); + this.DutyStarted?.InvokeSafely(this, this.clientState.TerritoryType); break; // Party Wipe case 0x4000_0005: this.IsDutyStarted = false; - this.DutyWiped?.Invoke(this, this.clientState.TerritoryType); + this.DutyWiped?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Recommence case 0x4000_0006: this.IsDutyStarted = true; - this.DutyRecommenced?.Invoke(this, this.clientState.TerritoryType); + this.DutyRecommenced?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Completed Flytext Shown case 0x4000_0002 when !this.CompletedThisTerritory: this.IsDutyStarted = false; this.CompletedThisTerritory = true; - this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType); + this.DutyCompleted?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Completed case 0x4000_0003 when !this.CompletedThisTerritory: this.IsDutyStarted = false; this.CompletedThisTerritory = true; - this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType); + this.DutyCompleted?.InvokeSafely(this, this.clientState.TerritoryType); break; } } diff --git a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs index 01a0d39b7..1bca93efb 100644 --- a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs +++ b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs @@ -16,6 +16,6 @@ internal class DutyStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); + this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); // unnamed in cs } } diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 07942f780..82c7f5f6c 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -16,6 +15,8 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using CSFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework; + namespace Dalamud.Game; /// @@ -31,11 +32,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework private readonly Stopwatch updateStopwatch = new(); private readonly HitchDetector hitchDetector; - private readonly Hook updateHook; - private readonly Hook destroyHook; + private readonly Hook updateHook; + private readonly Hook destroyHook; - private readonly FrameworkAddressResolver addressResolver; - [ServiceManager.ServiceDependency] private readonly GameLifecycle lifecycle = Service.Get(); @@ -51,13 +50,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework private ulong tickCounter; [ServiceManager.ServiceConstructor] - private Framework(TargetSigScanner sigScanner) + private unsafe Framework() { this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch); - this.addressResolver = new FrameworkAddressResolver(); - this.addressResolver.Setup(sigScanner); - this.frameworkDestroy = new(); this.frameworkThreadTaskScheduler = new(); this.FrameworkThreadTaskFactory = new( @@ -66,23 +62,13 @@ internal sealed class Framework : IInternalDisposableService, IFramework TaskContinuationOptions.None, this.frameworkThreadTaskScheduler); - this.updateHook = Hook.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate); - this.destroyHook = Hook.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy); + this.updateHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Tick, this.HandleFrameworkUpdate); + this.destroyHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Destroy, this.HandleFrameworkDestroy); this.updateHook.Enable(); this.destroyHook.Enable(); } - /// - /// A delegate type used during the native Framework::destroy. - /// - /// The native Framework address. - /// A value indicating if the call was successful. - public delegate bool OnRealDestroyDelegate(IntPtr framework); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); - /// public event IFramework.OnUpdateDelegate? Update; @@ -390,7 +376,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework } } - private bool HandleFrameworkUpdate(IntPtr framework) + private unsafe bool HandleFrameworkUpdate(CSFramework* thisPtr) { this.frameworkThreadTaskScheduler.BoundThread ??= Thread.CurrentThread; @@ -483,10 +469,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework this.hitchDetector.Stop(); original: - return this.updateHook.OriginalDisposeSafe(framework); + return this.updateHook.OriginalDisposeSafe(thisPtr); } - private bool HandleFrameworkDestroy(IntPtr framework) + private unsafe bool HandleFrameworkDestroy(CSFramework* thisPtr) { this.frameworkDestroy.Cancel(); this.DispatchUpdateEvents = false; @@ -504,7 +490,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework ServiceManager.WaitForServiceUnload(); Log.Information("Framework::Destroy OK!"); - return this.destroyHook.OriginalDisposeSafe(framework); + return this.destroyHook.OriginalDisposeSafe(thisPtr); } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs deleted file mode 100644 index 8ea371f2c..000000000 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Dalamud.Game; - -/// -/// The address resolver for the class. -/// -internal sealed class FrameworkAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address for the function that is called once the Framework is destroyed. - /// - public IntPtr DestroyAddress { get; private set; } - - /// - /// Gets the address for the function that is called once the Framework is free'd. - /// - public IntPtr FreeAddress { get; private set; } - - /// - /// Gets the function that is called every tick. - /// - public IntPtr TickAddress { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.SetupFramework(sig); - } - - private void SetupFramework(ISigScanner scanner) - { - this.DestroyAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF"); - - this.FreeAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ?? 48 85 C9"); - - this.TickAddress = - scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??"); - } -} diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 6779f32a8..791cbb97a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -14,8 +14,20 @@ using Dalamud.Logging.Internal; using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using Lumina.Text; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using SeString = Dalamud.Game.Text.SeStringHandling.SeString; +using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; namespace Dalamud.Game.Gui; @@ -27,14 +39,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui { private static readonly ModuleLog Log = new("ChatGui"); - private readonly ChatGuiAddressResolver address; - private readonly Queue chatQueue = new(); private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; + private readonly Hook inventoryItemCopyHook; + private readonly Hook handleLinkClickHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -42,29 +52,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private ImmutableDictionary<(string PluginName, uint CommandId), Action>? dalamudLinkHandlersCopy; [ServiceManager.ServiceConstructor] - private ChatGui(TargetSigScanner sigScanner) + private ChatGui() { - this.address = new ChatGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.printMessageHook = Hook.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); - this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); + this.printMessageHook = Hook.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); + this.inventoryItemCopyHook = Hook.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour); + this.handleLinkClickHook = Hook.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour); this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); + this.inventoryItemCopyHook.Enable(); + this.handleLinkClickHook.Enable(); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); - /// public event IChatGui.OnMessageDelegate? ChatMessage; @@ -78,7 +79,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; /// - public int LastLinkedItemId { get; private set; } + public uint LastLinkedItemId { get; private set; } /// public byte LastLinkedItemFlags { get; private set; } @@ -106,10 +107,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui void IInternalDisposableService.DisposeService() { this.printMessageHook.Dispose(); - this.populateItemLinkHook.Dispose(); - this.interactableLinkClickedHook.Dispose(); + this.inventoryItemCopyHook.Dispose(); + this.handleLinkClickHook.Dispose(); } + #region DalamudSeString + /// public void Print(XivChatEntry chat) { @@ -140,43 +143,74 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); } + #endregion + + #region LuminaSeString + + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); + } + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); + } + + #endregion + /// /// Process a chat queue. /// public void UpdateQueue() { - while (this.chatQueue.Count > 0) - { - var chat = this.chatQueue.Dequeue(); - var replacedMessage = new SeStringBuilder(); + if (this.chatQueue.Count == 0) + return; - // Normalize Unicode NBSP to the built-in one, as the former won't renderl - foreach (var payload in chat.Message.Payloads) + var sb = LSeStringBuilder.SharedPool.Get(); + Span namebuf = stackalloc byte[256]; + using var sender = new Utf8String(); + using var message = new Utf8String(); + while (this.chatQueue.TryDequeue(out var chat)) + { + sb.Clear(); + foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString)) { - if (payload is TextPayload { Text: not null } textPayload) - { - var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE - for (var i = 0; i < split.Length; i++) - { - replacedMessage.AddText(split[i]); - if (i + 1 < split.Length) - replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03])); - } - } + if (c.IsSeStringPayload) + sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength)); + else if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); else - { - replacedMessage.Add(payload); - } + sb.Append(c); } - var sender = Utf8String.FromSequence(chat.Name.Encode()); - var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode()); + if (chat.NameBytes.Length + 1 < namebuf.Length) + { + chat.NameBytes.AsSpan().CopyTo(namebuf); + namebuf[chat.NameBytes.Length] = 0; + sender.SetString(namebuf); + } + else + { + sender.SetString(chat.NameBytes.NullTerminate()); + } - this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); + message.SetString(sb.GetViewAsSpan()); - sender->Dtor(true); - message->Dtor(true); + var targetChannel = chat.Type ?? this.configuration.GeneralChatType; + + this.HandlePrintMessageDetour( + RaptureLogModule.Instance(), + targetChannel, + &sender, + &message, + chat.Timestamp, + (byte)(chat.Silent ? 1 : 0)); } + + LSeStringBuilder.SharedPool.Return(sb); } /// @@ -229,29 +263,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } } - private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color) - { - var builder = new SeStringBuilder(); - - if (!tag.IsNullOrEmpty()) - { - if (color is not null) - { - builder.AddUiForeground($"[{tag}] ", color.Value); - } - else - { - builder.AddText($"[{tag}] "); - } - } - - this.Print(new XivChatEntry - { - Message = builder.AddText(message).Build(), - Type = channel, - }); - } - private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color) { var builder = new SeStringBuilder(); @@ -275,21 +286,47 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui }); } - private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) + private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) { + var sb = LSeStringBuilder.SharedPool.Get(); + + if (!tag.IsNullOrEmpty()) + { + if (color is not null) + { + sb.PushColorType(color.Value); + sb.Append($"[{tag}] "); + sb.PopColorType(); + } + else + { + sb.Append($"[{tag}] "); + } + } + + this.Print(new XivChatEntry + { + MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(), + Type = channel, + }); + + LSeStringBuilder.SharedPool.Return(sb); + } + + private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) + { + this.inventoryItemCopyHook.Original(thisPtr, otherPtr); + try { - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + this.LastLinkedItemId = otherPtr->ItemId; + this.LastLinkedItemFlags = (byte)otherPtr->Flags; - this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); - this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); - - // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); + // Log.Verbose($"InventoryItemCopyDetour {thisPtr} {otherPtr} - linked:{this.LastLinkedItemId}"); } catch (Exception ex) { - Log.Error(ex, "Exception onPopulateItemLink hook."); - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + Log.Error(ex, "Exception in InventoryItemCopyHook"); } } @@ -299,58 +336,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui try { - var originalSenderData = sender->AsSpan().ToArray(); - var originalMessageData = message->AsSpan().ToArray(); + var parsedSender = SeString.Parse(sender->AsSpan()); + var parsedMessage = SeString.Parse(message->AsSpan()); - var parsedSender = SeString.Parse(originalSenderData); - var parsedMessage = SeString.Parse(originalMessageData); + var terminatedSender = parsedSender.EncodeWithNullTerminator(); + var terminatedMessage = parsedMessage.EncodeWithNullTerminator(); // Call events var isHandled = false; - var invocationList = this.CheckMessageHandled!.GetInvocationList(); - foreach (var @delegate in invocationList) + if (this.CheckMessageHandled is { } handledCallback) { - try - { - var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate; - messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); - } - catch (Exception e) - { - Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name); - } - } - - if (!isHandled) - { - invocationList = this.ChatMessage!.GetInvocationList(); - foreach (var @delegate in invocationList) + foreach (var action in handledCallback.GetInvocationList().Cast()) { try { - var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate; - messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); + action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); } catch (Exception e) { - Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name); + Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", action.Method); } } } - var possiblyModifiedSenderData = parsedSender.Encode(); - var possiblyModifiedMessageData = parsedMessage.Encode(); - - if (!Util.FastByteArrayCompare(originalSenderData, possiblyModifiedSenderData)) + if (!isHandled && this.ChatMessage is { } callback) { - Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(originalSenderData)} -> {parsedSender}"); + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); + } + catch (Exception e) + { + Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", action.Method); + } + } + } + + var possiblyModifiedSenderData = parsedSender.EncodeWithNullTerminator(); + var possiblyModifiedMessageData = parsedMessage.EncodeWithNullTerminator(); + + if (!terminatedSender.SequenceEqual(possiblyModifiedSenderData)) + { + Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(terminatedSender)} -> {parsedSender}"); sender->SetString(possiblyModifiedSenderData); } - if (!Util.FastByteArrayCompare(originalMessageData, possiblyModifiedMessageData)) + if (!terminatedMessage.SequenceEqual(possiblyModifiedMessageData)) { - Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(originalMessageData)} -> {parsedMessage}"); + Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(terminatedMessage)} -> {parsedMessage}"); message->SetString(possiblyModifiedMessageData); } @@ -374,42 +410,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui return messageId; } - private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) + private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData) { + if (linkData == null || linkData->Payload == null || (Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) + { + this.handleLinkClickHook.Original(thisPtr, linkData); + return; + } + + Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + + var sb = LSeStringBuilder.SharedPool.Get(); try { - var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); + var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload); - if (interactableType != Payload.EmbeddedInfoType.DalamudLink) + // read until link terminator + foreach (var payload in seStringSpan) { - this.interactableLinkClickedHook.Original(managerPtr, messagePtr); - return; + sb.Append(payload); + + if (payload.Type == ReadOnlySePayloadType.Macro && + payload.MacroCode == MacroCode.Link && + payload.TryGetExpression(out var expr1) && + expr1.TryGetInt(out var expr1Val) && + expr1Val == (int)LinkMacroPayloadType.Terminator) + { + break; + } } - Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + var seStr = SeString.Parse(sb.ToArray()); + if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link) + return; - var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); - var seStr = MemoryHelper.ReadSeStringNullTerminated(payloadPtr); - var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); - var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; - if (payloads.Count == 0) return; - var linkPayload = payloads[0]; - if (linkPayload is DalamudLinkPayload link) + if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value)) { - if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value)) - { - Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); - value.Invoke(link.CommandId, new SeString(payloads)); - } - else - { - Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); - } + Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); + value.Invoke(link.CommandId, seStr); + } + else + { + Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); } } catch (Exception ex) { - Log.Error(ex, "Exception on InteractableLinkClicked hook"); + Log.Error(ex, "Exception in HandleLinkClickDetour"); + } + finally + { + LSeStringBuilder.SharedPool.Return(sb); } } } @@ -451,7 +502,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; /// - public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId; + public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId; /// public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags; @@ -493,6 +544,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) => this.chatGuiService.PrintError(message, messageTag, tagColor); + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.Print(message, messageTag, tagColor); + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.PrintError(message, messageTag, tagColor); + private void OnMessageForward(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) => this.ChatMessage?.Invoke(type, timestamp, ref sender, ref message, ref isHandled); diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs deleted file mode 100644 index 366e79fc6..000000000 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Dalamud.Game.Gui; - -/// -/// The address resolver for the class. -/// -internal sealed class ChatGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } - - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - - // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 - this.PopulateItemLinkObject = sig.ScanText("E8 ?? ?? ?? ?? 8B 4E FC"); - - this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 4B ?? E8 ?? ?? ?? ?? 33 D2"); - } -} diff --git a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs index 604af5ac7..fb78e6b80 100644 --- a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs +++ b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs @@ -47,9 +47,9 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM } private delegate ushort AtkModuleVf22OpenAddonByAgentDelegate(AtkModule* module, byte* addonName, int valueCount, AtkValue* values, AgentInterface* agent, nint a7, bool a8); - + private delegate bool AddonContextMenuOnMenuSelectedDelegate(AddonContextMenu* addon, int selectedIdx, byte a3); - + private delegate ushort RaptureAtkModuleOpenAddonDelegate(RaptureAtkModule* a1, uint addonNameId, uint valueCount, AtkValue* values, AgentInterface* parentAgent, ulong unk, ushort parentAddonId, int unk2); /// @@ -92,16 +92,22 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM /// void IInternalDisposableService.DisposeService() { + this.atkModuleVf22OpenAddonByAgentHook.Dispose(); + this.addonContextMenuOnMenuSelectedHook.Dispose(); + var manager = RaptureAtkUnitManager.Instance(); + if (manager == null) + return; + var menu = manager->GetAddonByName("ContextMenu"); var submenu = manager->GetAddonByName("AddonContextSub"); + if (menu == null || submenu == null) + return; + if (menu->IsVisible) menu->FireCallbackInt(-1); if (submenu->IsVisible) submenu->FireCallbackInt(-1); - - this.atkModuleVf22OpenAddonByAgentHook.Dispose(); - this.addonContextMenuOnMenuSelectedHook.Dispose(); } /// diff --git a/Dalamud/Game/Gui/ContextMenu/MenuItem.cs b/Dalamud/Game/Gui/ContextMenu/MenuItem.cs index 9b7cc2bc1..df1cb54a7 100644 --- a/Dalamud/Game/Gui/ContextMenu/MenuItem.cs +++ b/Dalamud/Game/Gui/ContextMenu/MenuItem.cs @@ -1,7 +1,7 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.ContextMenu; diff --git a/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs b/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs index 26365ab14..f09b3e105 100644 --- a/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs +++ b/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs @@ -1,11 +1,12 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Network.Structures.InfoProxy; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.ContextMenu; @@ -46,7 +47,7 @@ public sealed unsafe class MenuTargetDefault : MenuTarget /// /// Gets the home world id of the target. /// - public ExcelResolver TargetHomeWorld => new((uint)this.Context->TargetHomeWorldId); + public RowRef TargetHomeWorld => LuminaUtils.CreateRef((uint)this.Context->TargetHomeWorldId); /// /// Gets the currently targeted character. Only shows up for specific targets, like friends, party finder listings, or party members. diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 55b2573f0..c6208fb2f 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -1,6 +1,7 @@ -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Threading; using Dalamud.Configuration.Internal; using Dalamud.Game.Addon.Events; @@ -10,6 +11,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics; @@ -28,7 +30,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private const uint BaseNodeId = 1000; private static readonly ModuleLog Log = new("DtrBar"); - + [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -48,13 +50,15 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private readonly AddonLifecycleEventListener dtrPostRequestedUpdateListener; private readonly AddonLifecycleEventListener dtrPreFinalizeListener; - private readonly ConcurrentBag newEntries = new(); - private readonly List entries = new(); + private readonly ReaderWriterLockSlim entriesLock = new(); + private readonly List entries = []; private readonly Dictionary> eventHandles = new(); + private ImmutableList? entriesReadOnlyCopy; + private Utf8String* emptyString; - + private uint runningNodeIds = BaseNodeId; private float entryStartPos = float.NaN; @@ -68,55 +72,157 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.addonLifecycle.RegisterListener(this.dtrPostDrawListener); this.addonLifecycle.RegisterListener(this.dtrPostRequestedUpdateListener); this.addonLifecycle.RegisterListener(this.dtrPreFinalizeListener); - + this.framework.Update += this.Update; - this.configuration.DtrOrder ??= new List(); - this.configuration.DtrIgnore ??= new List(); + this.configuration.DtrOrder ??= []; + this.configuration.DtrIgnore ??= []; this.configuration.QueueSave(); } - /// - /// Event type fired each time a DtrEntry was removed. - /// - /// The title of the bar entry. - internal delegate void DtrEntryRemovedDelegate(string title); - - /// - /// Event fired each time a DtrEntry was removed. - /// - internal event DtrEntryRemovedDelegate? DtrEntryRemoved; - /// - public IReadOnlyList Entries => this.entries; - - /// - public IDtrBarEntry Get(string title, SeString? text = null) + public IReadOnlyList Entries { - if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title)) - throw new ArgumentException("An entry with the same title already exists."); - - var entry = new DtrBarEntry(this.configuration, title, null); - entry.Text = text; - - // Add the entry to the end of the order list, if it's not there already. - if (!this.configuration.DtrOrder!.Contains(title)) - this.configuration.DtrOrder!.Add(title); - - this.newEntries.Add(entry); - - return entry; - } - - /// - public void Remove(string title) - { - if (this.entries.FirstOrDefault(entry => entry.Title == title) is { } dtrBarEntry) + get { - dtrBarEntry.Remove(); + var erc = this.entriesReadOnlyCopy; + if (erc is null) + { + this.entriesLock.EnterReadLock(); + this.entriesReadOnlyCopy = erc = [..this.entries]; + this.entriesLock.ExitReadLock(); + } + + return erc; } } + /// + /// Get a DTR bar entry. + /// This allows you to add your own text, and users to sort it. + /// + /// Plugin that owns the DTR bar, or null if owned by Dalamud. + /// A user-friendly name for sorting. + /// The text the entry shows. + /// The entry object used to update, hide and remove the entry. + /// Thrown when an entry with the specified title exists. + public IDtrBarEntry Get(LocalPlugin? plugin, string title, SeString? text = null) + { + this.entriesLock.EnterUpgradeableReadLock(); + + foreach (var existingEntry in this.entries) + { + if (existingEntry.Title == title) + { + if (existingEntry.ShouldBeRemoved) + { + if (plugin == existingEntry.OwnerPlugin) + { + Log.Debug( + "Reviving entry: {what}; owner: {plugin}({pluginId})", + title, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + } + else + { + Log.Debug( + "Reviving entry: {what}; old owner: {old}({oldId}); new owner: {new}({newId})", + title, + existingEntry.OwnerPlugin?.InternalName, + existingEntry.OwnerPlugin?.EffectiveWorkingPluginId, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + existingEntry.OwnerPlugin = plugin; + } + + existingEntry.ShouldBeRemoved = false; + } + + this.entriesLock.ExitUpgradeableReadLock(); + if (plugin == existingEntry.OwnerPlugin) + return existingEntry; + + Log.Debug( + "Entry already has a different owner: {what}; owner: {old}({oldId}); requester: {new}({newId})", + title, + existingEntry.OwnerPlugin?.InternalName, + existingEntry.OwnerPlugin?.EffectiveWorkingPluginId, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + throw new ArgumentException("An entry with the same title already exists."); + } + } + + this.entriesLock.EnterWriteLock(); + var entry = new DtrBarEntry(this.configuration, title, null) { Text = text, OwnerPlugin = plugin }; + this.entries.Add(entry); + Log.Debug( + "Adding entry: {what}; owner: {owner}({id})", + title, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + + // Add the entry to the end of the order list, if it's not there already. + var dtrOrder = this.configuration.DtrOrder ??= []; + if (!dtrOrder.Contains(entry.Title)) + dtrOrder.Add(entry.Title); + this.ApplySortUnsafe(dtrOrder); + + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + + this.entriesLock.ExitUpgradeableReadLock(); + return entry; + } + + /// + public IDtrBarEntry Get(string title, SeString? text = null) => this.Get(null, title, text); + + /// + /// Removes a DTR bar entry from the system. + /// + /// Plugin that owns the DTR bar, or null if owned by Dalamud. + /// Title of the entry to remove, or null to remove all entries under the plugin. + /// Remove operation is not immediate. If you try to add right after removing, the operation may fail. + /// + public void Remove(LocalPlugin? plugin, string? title) + { + this.entriesLock.EnterUpgradeableReadLock(); + + foreach (var entry in this.entries) + { + if ((title is null || entry.Title == title) && (plugin is null || entry.OwnerPlugin == plugin)) + { + if (!entry.Added) + { + Log.Debug("Removing entry immediately because it is not added yet: {what}", entry.Title); + this.entriesLock.EnterWriteLock(); + this.RemoveEntry(entry); + this.entries.Remove(entry); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + } + else if (!entry.ShouldBeRemoved) + { + Log.Debug("Queueing entry for removal: {what}", entry.Title); + entry.Remove(); + } + else + { + Log.Debug("Entry is already marked for removal: {what}", entry.Title); + } + + break; + } + } + + this.entriesLock.ExitUpgradeableReadLock(); + } + + /// + public void Remove(string title) => this.Remove(null, title); + /// void IInternalDisposableService.DisposeService() { @@ -124,10 +230,17 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener); this.addonLifecycle.UnregisterListener(this.dtrPreFinalizeListener); - foreach (var entry in this.entries) - this.RemoveEntry(entry); + this.framework.RunOnFrameworkThread( + () => + { + this.entriesLock.EnterWriteLock(); + foreach (var entry in this.entries) + this.RemoveEntry(entry); + this.entries.Clear(); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + }).Wait(); - this.entries.Clear(); this.framework.Update -= this.Update; if (this.emptyString != null) @@ -137,23 +250,6 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar } } - /// - /// Remove nodes marked as "should be removed" from the bar. - /// - internal void HandleRemovedNodes() - { - foreach (var data in this.entries) - { - if (data.ShouldBeRemoved) - { - this.RemoveEntry(data); - this.DtrEntryRemoved?.Invoke(data.Title); - } - } - - this.entries.RemoveAll(d => d.ShouldBeRemoved); - } - /// /// Remove native resources for the specified entry. /// @@ -174,7 +270,17 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar /// /// The title to check for. /// Whether or not an entry with that title is registered. - internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title); + internal bool HasEntry(string title) + { + var found = false; + + this.entriesLock.EnterReadLock(); + for (var i = 0; i < this.entries.Count && !found; i++) + found = this.entries[i].Title == title; + this.entriesLock.ExitReadLock(); + + return found; + } /// /// Dirty the DTR bar entry with the specified title. @@ -183,24 +289,37 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar /// Whether the entry was found. internal bool MakeDirty(string title) { - var entry = this.entries.FirstOrDefault(x => x.Title == title); - if (entry == null) - return false; + var found = false; - entry.Dirty = true; - return true; + this.entriesLock.EnterReadLock(); + for (var i = 0; i < this.entries.Count && !found; i++) + { + found = this.entries[i].Title == title; + if (found) + this.entries[i].Dirty = true; + } + + this.entriesLock.ExitReadLock(); + + return found; } /// /// Reapply the DTR entry ordering from . /// internal void ApplySort() + { + this.entriesLock.EnterWriteLock(); + this.ApplySortUnsafe(this.configuration.DtrOrder ??= []); + this.entriesLock.ExitWriteLock(); + } + + private void ApplySortUnsafe(List dtrOrder) { // Sort the current entry list, based on the order in the configuration. - var positions = this.configuration - .DtrOrder! - .Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry))) - .ToDictionary(x => x.entry, x => x.index); + var positions = dtrOrder + .Select(entry => (entry, index: dtrOrder.IndexOf(entry))) + .ToDictionary(x => x.entry, x => x.index); this.entries.Sort((x, y) => { @@ -208,15 +327,13 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue; return xPos.CompareTo(yPos); }); + this.entriesReadOnlyCopy = null; } private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer(); private void Update(IFramework unused) { - this.HandleRemovedNodes(); - this.HandleAddedNodes(); - var dtr = this.GetDtr(); if (dtr == null || dtr->RootNode == null || dtr->RootNode->ChildNode == null) return; @@ -236,14 +353,28 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var runningXPos = this.entryStartPos; - foreach (var data in this.entries) + this.entriesLock.EnterUpgradeableReadLock(); + for (var i = 0; i < this.entries.Count; i++) { - if (!data.Added) + var data = this.entries[i]; + if (data.ShouldBeRemoved) { - data.Added = this.AddNode(data.TextNode); - data.Dirty = true; + Log.Debug("Removing entry from Framework.Update: {what}", data.Title); + this.entriesLock.EnterWriteLock(); + this.entries.RemoveAt(i); + this.RemoveEntry(data); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + i--; + continue; } + if (!data.Added) + data.Added = this.AddNode(data); + + if (!data.Added || data.TextNode is null) // TextNode check is unnecessary, but just in case. + continue; + var isHide = !data.Shown || data.UserHidden; var node = data.TextNode; var nodeHidden = !node->AtkResNode.IsVisible(); @@ -290,23 +421,10 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar data.Dirty = false; } + + this.entriesLock.ExitUpgradeableReadLock(); } - private void HandleAddedNodes() - { - if (!this.newEntries.IsEmpty) - { - foreach (var newEntry in this.newEntries) - { - newEntry.TextNode = this.MakeNode(++this.runningNodeIds); - this.entries.Add(newEntry); - } - - this.newEntries.Clear(); - this.ApplySort(); - } - } - private void FixCollision(AddonEvent eventType, AddonArgs addonInfo) { var addon = (AtkUnitBase*)addonInfo.Addon; @@ -316,7 +434,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var additionalWidth = 0; AtkResNode* collisionNode = null; - foreach (var index in Enumerable.Range(0, addon->UldManager.NodeListCount)) + for (var index = 0; index < addon->UldManager.NodeListCount; index++) { var node = addon->UldManager.NodeList[index]; if (node->IsVisible()) @@ -382,22 +500,20 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private void RecreateNodes() { this.runningNodeIds = BaseNodeId; - if (this.entries.Any()) - { - this.eventHandles.Clear(); - } + this.entriesLock.EnterReadLock(); + this.eventHandles.Clear(); foreach (var entry in this.entries) - { - entry.TextNode = this.MakeNode(++this.runningNodeIds); entry.Added = false; - } + this.entriesLock.ExitReadLock(); } - private bool AddNode(AtkTextNode* node) + private bool AddNode(DtrBarEntry data) { var dtr = this.GetDtr(); - if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; + if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null) return false; + + var node = data.TextNode = this.MakeNode(++this.runningNodeIds); this.eventHandles.TryAdd(node->AtkResNode.NodeId, new List()); this.eventHandles[node->AtkResNode.NodeId].AddRange(new List @@ -406,7 +522,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler), this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler), }); - + var lastChild = dtr->RootNode->ChildNode; while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode; Log.Debug($"Found last sibling: {(ulong)lastChild:X}"); @@ -420,6 +536,8 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar dtr->UldManager.UpdateDrawNodeList(); dtr->UpdateCollisionNodeList(false); Log.Debug("Updated node draw list"); + + data.Dirty = true; return true; } @@ -472,7 +590,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar if (this.emptyString == null) this.emptyString = Utf8String.FromString(" "); - + newTextNode->SetText(this.emptyString->StringPtr); newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 }; @@ -491,13 +609,21 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar return newTextNode; } - + private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode) { var addon = (AtkUnitBase*)atkUnitBase; var node = (AtkResNode*)atkResNode; - if (this.entries.FirstOrDefault(entry => entry.TextNode == node) is not { } dtrBarEntry) return; + DtrBarEntry? dtrBarEntry = null; + this.entriesLock.EnterReadLock(); + foreach (var entry in this.entries) + { + if (entry.TextNode == node) + dtrBarEntry = entry; + } + + this.entriesLock.ExitReadLock(); if (dtrBarEntry is { Tooltip: not null }) { @@ -506,7 +632,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar case AddonEventType.MouseOver: AtkStage.Instance()->TooltipManager.ShowTooltip(addon->Id, node, dtrBarEntry.Tooltip.Encode()); break; - + case AddonEventType.MouseOut: AtkStage.Instance()->TooltipManager.HideTooltip(addon->Id); break; @@ -520,11 +646,11 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar case AddonEventType.MouseOver: this.uiEventManager.SetCursor(AddonCursorType.Clickable); break; - + case AddonEventType.MouseOut: this.uiEventManager.ResetCursor(); break; - + case AddonEventType.MouseClick: dtrBarEntry.OnClick.Invoke(); break; @@ -541,58 +667,25 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar +internal sealed class DtrBarPluginScoped : IInternalDisposableService, IDtrBar { + private readonly LocalPlugin plugin; + [ServiceManager.ServiceDependency] private readonly DtrBar dtrBarService = Service.Get(); - private readonly Dictionary pluginEntries = new(); - - /// - /// Initializes a new instance of the class. - /// - internal DtrBarPluginScoped() - { - this.dtrBarService.DtrEntryRemoved += this.OnDtrEntryRemoved; - } + [ServiceManager.ServiceConstructor] + private DtrBarPluginScoped(LocalPlugin plugin) => this.plugin = plugin; /// public IReadOnlyList Entries => this.dtrBarService.Entries; /// - void IInternalDisposableService.DisposeService() - { - this.dtrBarService.DtrEntryRemoved -= this.OnDtrEntryRemoved; - - foreach (var entry in this.pluginEntries) - { - entry.Value.Remove(); - } - - this.pluginEntries.Clear(); - } + void IInternalDisposableService.DisposeService() => this.dtrBarService.Remove(this.plugin, null); /// - public IDtrBarEntry Get(string title, SeString? text = null) - { - // If we already have a known entry for this plugin, return it. - if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry; + public IDtrBarEntry Get(string title, SeString? text = null) => this.dtrBarService.Get(this.plugin, title, text); - return this.pluginEntries[title] = this.dtrBarService.Get(title, text); - } - /// - public void Remove(string title) - { - if (this.pluginEntries.TryGetValue(title, out var existingEntry)) - { - existingEntry.Remove(); - this.pluginEntries.Remove(title); - } - } - - private void OnDtrEntryRemoved(string title) - { - this.pluginEntries.Remove(title); - } + public void Remove(string title) => this.dtrBarService.Remove(this.plugin, title); } diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index fc5210fda..49a2cbb73 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -1,7 +1,6 @@ -using System.Linq; - -using Dalamud.Configuration.Internal; +using Dalamud.Configuration.Internal; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; @@ -27,12 +26,12 @@ public interface IReadOnlyDtrBarEntry /// /// Gets the text of this entry. /// - public SeString Text { get; } + public SeString? Text { get; } /// /// Gets a tooltip to be shown when the user mouses over the dtr entry. /// - public SeString Tooltip { get; } + public SeString? Tooltip { get; } /// /// Gets a value indicating whether this entry should be shown. @@ -86,7 +85,7 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry /// /// Class representing an entry in the server info bar. /// -public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry +internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry { private readonly DalamudConfiguration configuration; @@ -146,7 +145,7 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry } /// - [Api10ToDo("Maybe make this config scoped to internalname?")] + [Api12ToDo("Maybe make this config scoped to internalname?")] public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false; /// @@ -160,9 +159,9 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry internal Utf8String* Storage { get; set; } /// - /// Gets a value indicating whether this entry should be removed. + /// Gets or sets a value indicating whether this entry should be removed. /// - internal bool ShouldBeRemoved { get; private set; } + internal bool ShouldBeRemoved { get; set; } /// /// Gets or sets a value indicating whether this entry is dirty. @@ -174,6 +173,11 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry /// internal bool Added { get; set; } + /// + /// Gets or sets the plugin that owns this entry. + /// + internal LocalPlugin? OwnerPlugin { get; set; } + /// public bool TriggerClickAction() { diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 623bc51b3..cbf166cfc 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -8,6 +9,9 @@ using Dalamud.IoC.Internal; using Dalamud.Memory; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + using Serilog; namespace Dalamud.Game.Gui.FlyText; @@ -19,62 +23,21 @@ namespace Dalamud.Game.Gui.FlyText; internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui { /// - /// The native function responsible for adding fly text to the UI. See . + /// The hook that fires when the game creates a fly text element. /// - private readonly AddFlyTextDelegate addFlyTextNative; - - /// - /// The hook that fires when the game creates a fly text element. See . - /// - private readonly Hook createFlyTextHook; + private readonly Hook createFlyTextHook; [ServiceManager.ServiceConstructor] - private FlyTextGui(TargetSigScanner sigScanner) + private unsafe FlyTextGui(TargetSigScanner sigScanner) { - this.Address = new FlyTextGuiAddressResolver(); - this.Address.Setup(sigScanner); - - this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); - this.createFlyTextHook = Hook.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour); + this.createFlyTextHook = Hook.FromAddress(AddonFlyText.Addresses.CreateFlyText.Value, this.CreateFlyTextDetour); this.createFlyTextHook.Enable(); } - /// - /// Private delegate for the native CreateFlyText function's hook. - /// - private delegate IntPtr CreateFlyTextDelegate( - IntPtr addonFlyText, - FlyTextKind kind, - int val1, - int val2, - IntPtr text2, - uint color, - uint icon, - uint damageTypeIcon, - IntPtr text1, - float yOffset); - - /// - /// Private delegate for the native AddFlyText function pointer. - /// - private delegate void AddFlyTextDelegate( - IntPtr addonFlyText, - uint actorIndex, - uint messageMax, - IntPtr numbers, - uint offsetNum, - uint offsetNumMax, - IntPtr strings, - uint offsetStr, - uint offsetStrMax, - int unknown); - /// public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated; - private FlyTextGuiAddressResolver Address { get; } - /// /// Disposes of managed and unmanaged resources. /// @@ -87,26 +50,16 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon) { // Known valid flytext region within the atk arrays - var numIndex = 30; - var strIndex = 27; var numOffset = 161u; var strOffset = 28u; - // Get the UI module and flytext addon pointers - var gameGui = Service.GetNullable(); - if (gameGui == null) - return; - - var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); - var flytext = gameGui.GetAddonByName("_FlyText"); - - if (ui == null || flytext == IntPtr.Zero) + var flytext = (AddonFlyText*)RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText"); + if (flytext == null) return; // Get the number and string arrays we need - var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; - var numArray = atkArrayDataHolder._NumberArrays[numIndex]; - var strArray = atkArrayDataHolder._StringArrays[strIndex]; + var numArray = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.FlyText); + var strArray = AtkStage.Instance()->GetStringArrayData(StringArrayType.FlyText); // Write the values to the arrays using a known valid flytext region numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section @@ -120,66 +73,47 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui numArray->IntArray[numOffset + 8] = 0; // Unknown numArray->IntArray[numOffset + 9] = 0; // Unknown, has something to do with yOffset - strArray->SetValue((int)strOffset + 0, text1.Encode(), false, true, false); - strArray->SetValue((int)strOffset + 1, text2.Encode(), false, true, false); - - this.addFlyTextNative( - flytext, - actorIndex, - 1, - (IntPtr)numArray, - numOffset, - 10, - (IntPtr)strArray, - strOffset, - 2, - 0); + strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false); + strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false); + + flytext->AddFlyText(actorIndex, 1, numArray, numOffset, 10, strArray, strOffset, 2, 0); } - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private IntPtr CreateFlyTextDetour( - IntPtr addonFlyText, - FlyTextKind kind, + private unsafe nint CreateFlyTextDetour( + AddonFlyText* thisPtr, + int kind, int val1, int val2, - IntPtr text2, + byte* text2, uint color, uint icon, uint damageTypeIcon, - IntPtr text1, + byte* text1, float yOffset) { - var retVal = IntPtr.Zero; + var retVal = nint.Zero; try { Log.Verbose("[FlyText] Enter CreateFlyText detour!"); var handled = false; - var tmpKind = kind; + var tmpKind = (FlyTextKind)kind; var tmpVal1 = val1; var tmpVal2 = val2; - var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); - var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); + var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1); + var tmpText2 = text2 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text2); var tmpColor = color; var tmpIcon = icon; var tmpDamageTypeIcon = damageTypeIcon; var tmpYOffset = yOffset; - var cmpText1 = tmpText1.ToString(); - var cmpText2 = tmpText2.ToString(); + var originalText1 = tmpText1.EncodeWithNullTerminator(); + var originalText2 = tmpText2.EncodeWithNullTerminator(); - Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + + Log.Verbose($"[FlyText] Called with addonFlyText({(nint)thisPtr:X}) " + $"kind({kind}) val1({val1}) val2({val2}) damageTypeIcon({damageTypeIcon}) " + - $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + + $"text1({(nint)text1:X}, \"{tmpText1}\") text2({(nint)text2:X}, \"{tmpText2}\") " + $"color({color:X}) icon({icon}) yOffset({yOffset})"); Log.Verbose("[FlyText] Calling flytext events!"); this.FlyTextCreated?.Invoke( @@ -204,12 +138,15 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui return IntPtr.Zero; } + var maybeModifiedText1 = tmpText1.EncodeWithNullTerminator(); + var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator(); + // Check if any values have changed - var dirty = tmpKind != kind || + var dirty = (int)tmpKind != kind || tmpVal1 != val1 || tmpVal2 != val2 || - tmpText1.ToString() != cmpText1 || - tmpText2.ToString() != cmpText2 || + !maybeModifiedText1.SequenceEqual(originalText1) || + !maybeModifiedText2.SequenceEqual(originalText2) || tmpDamageTypeIcon != damageTypeIcon || tmpColor != color || tmpIcon != icon || @@ -219,28 +156,26 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui if (!dirty) { Log.Verbose("[FlyText] Calling flytext with original args."); - return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, + return this.createFlyTextHook.Original(thisPtr, kind, val1, val2, text2, color, icon, damageTypeIcon, text1, yOffset); } - var terminated1 = Terminate(tmpText1.Encode()); - var terminated2 = Terminate(tmpText2.Encode()); - var pText1 = Marshal.AllocHGlobal(terminated1.Length); - var pText2 = Marshal.AllocHGlobal(terminated2.Length); - Marshal.Copy(terminated1, 0, pText1, terminated1.Length); - Marshal.Copy(terminated2, 0, pText2, terminated2.Length); + var pText1 = Marshal.AllocHGlobal(maybeModifiedText1.Length); + var pText2 = Marshal.AllocHGlobal(maybeModifiedText2.Length); + Marshal.Copy(maybeModifiedText1, 0, pText1, maybeModifiedText1.Length); + Marshal.Copy(maybeModifiedText2, 0, pText2, maybeModifiedText2.Length); Log.Verbose("[FlyText] Allocated and set strings."); retVal = this.createFlyTextHook.Original( - addonFlyText, - tmpKind, + thisPtr, + (int)tmpKind, tmpVal1, tmpVal2, - pText2, + (byte*)pText2, tmpColor, tmpIcon, tmpDamageTypeIcon, - pText1, + (byte*)pText1, tmpYOffset); Log.Verbose("[FlyText] Returned from original. Delaying free task."); diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs deleted file mode 100644 index aa7c8026f..000000000 --- a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Dalamud.Game.Gui.FlyText; - -/// -/// An address resolver for the class. -/// -internal class FlyTextGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native AddFlyText method, which occurs - /// when the game adds fly text elements to the UI. Multiple fly text - /// elements can be added in a single AddFlyText call. - /// - public IntPtr AddFlyText { get; private set; } - - /// - /// Gets the address of the native CreateFlyText method, which occurs - /// when the game creates a new fly text element. This method is called - /// once per fly text element, and can be called multiple times per - /// AddFlyText call. - /// - public IntPtr CreateFlyText { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); - this.CreateFlyText = sig.ScanText("E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 18"); - } -} diff --git a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs index 3727fd0f8..0edbd09ee 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs @@ -92,214 +92,232 @@ public enum FlyTextKind : int /// IslandExp = 15, + /// + /// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown16 = 16, + + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown17 = 17, + + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown18 = 18, + /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - MpDrain = 16, + MpDrain = 19, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp = 17, + NamedTp = 20, /// /// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1. /// - Healing = 18, + Healing = 21, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - MpRegen = 19, + MpRegen = 22, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp2 = 20, + NamedTp2 = 23, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. /// - EpRegen = 21, + EpRegen = 24, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle. /// - CpRegen = 22, + CpRegen = 25, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle. /// - GpRegen = 23, + GpRegen = 26, /// /// Displays nothing. /// - None = 24, + None = 27, /// /// All caps serif INVULNERABLE. /// - Invulnerable = 25, + Invulnerable = 28, /// /// All caps sans-serif condensed font INTERRUPTED! /// Does a large bounce effect on appearance. /// Does not scroll up or down the screen. /// - Interrupted = 26, + Interrupted = 29, /// /// Val1 in serif font. /// - CraftingProgress = 27, + CraftingProgress = 30, /// /// Val1 in serif font. /// - CraftingQuality = 28, + CraftingQuality = 31, /// /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance. /// - CraftingQualityCrit = 29, + CraftingQualityCrit = 32, /// /// Currently not used by the game. /// Val1 in serif font. /// - AutoAttackNoText3 = 30, + AutoAttackNoText3 = 33, /// /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). /// - HealingCrit = 31, + HealingCrit = 34, /// /// Currently not used by the game. /// Same as DamageCrit with a MP in condensed font to the right of Val1. /// Does a jiggle effect to the right on appearance. /// - NamedCriticalHitWithMp = 32, + NamedCriticalHitWithMp = 35, /// /// Currently not used by the game. /// Same as DamageCrit with a TP in condensed font to the right of Val1. /// Does a jiggle effect to the right on appearance. /// - NamedCriticalHitWithTp = 33, + NamedCriticalHitWithTp = 36, /// /// Icon next to sans-serif Text1 with sans-serif "has no effect!" to the right. /// - DebuffNoEffect = 34, + DebuffNoEffect = 37, /// /// Icon next to sans-serif slightly faded Text1. /// - BuffFading = 35, + BuffFading = 38, /// /// Icon next to sans-serif slightly faded Text1. /// - DebuffFading = 36, + DebuffFading = 39, /// /// Text1 in sans-serif font. /// - Named = 37, + Named = 40, /// /// Icon next to sans-serif Text1 with sans-serif "(fully resisted)" to the right. /// - DebuffResisted = 38, + DebuffResisted = 41, /// /// All caps serif 'INCAPACITATED!'. /// - Incapacitated = 39, + Incapacitated = 42, /// /// Text1 with sans-serif "(fully resisted)" to the right. /// - FullyResisted = 40, + FullyResisted = 43, /// /// Text1 with sans-serif "has no effect!" to the right. /// - HasNoEffect = 41, + HasNoEffect = 44, /// /// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1. /// - HpDrain = 42, + HpDrain = 45, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - NamedMp3 = 43, + NamedMp3 = 46, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp3 = 44, + NamedTp3 = 47, /// /// Icon next to sans-serif Text1 with serif "INVULNERABLE!" beneath the Text1. /// - DebuffInvulnerable = 45, + DebuffInvulnerable = 48, /// /// All caps serif RESIST. /// - Resist = 46, + Resist = 49, /// /// Icon with an item icon outline next to sans-serif Text1. /// - LootedItem = 47, + LootedItem = 50, /// /// Val1 in serif font. /// - Collectability = 48, + Collectability = 51, /// /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. /// Does a bigger bounce effect on appearance. /// - CollectabilityCrit = 49, + CollectabilityCrit = 52, /// /// All caps serif REFLECT. /// - Reflect = 50, + Reflect = 53, /// /// All caps serif REFLECTED. /// - Reflected = 51, + Reflected = 54, /// /// Val1 in serif font, Text2 in sans-serif as subtitle. /// Does a bounce effect on appearance. /// - CraftingQualityDh = 52, + CraftingQualityDh = 55, /// /// Currently not used by the game. /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. /// Does a bigger bounce effect on appearance. /// - CriticalHit4 = 53, + CriticalHit4 = 56, /// /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle. /// Does a large bounce effect on appearance. Does not scroll up or down the screen. /// - CraftingQualityCritDh = 54, + CraftingQualityCritDh = 57, } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 80463a119..d3fe444ea 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -1,4 +1,3 @@ -using System.Numerics; using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling.Payloads; @@ -10,6 +9,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.String; @@ -17,8 +17,8 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Common.Component.BGCollision; using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; -using SharpDX; using Vector2 = System.Numerics.Vector2; using Vector3 = System.Numerics.Vector3; @@ -33,20 +33,16 @@ namespace Dalamud.Game.Gui; internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui { private static readonly ModuleLog Log = new("GameGui"); - + private readonly GameGuiAddressResolver address; - private readonly Hook setGlobalBgmHook; - private readonly Hook handleItemHoverHook; - private readonly Hook handleItemOutHook; - private readonly Hook handleActionHoverHook; - private readonly Hook handleActionOutHook; + private readonly Hook handleItemHoverHook; + private readonly Hook handleItemOutHook; + private readonly Hook handleActionHoverHook; + private readonly Hook handleActionOutHook; private readonly Hook handleImmHook; - private readonly Hook toggleUiHideHook; - private readonly Hook utf8StringFromSequenceHook; - - private GetUIMapObjectDelegate? getUIMapObject; - private OpenMapWithFlagDelegate? openMapWithFlag; + private readonly Hook setUiVisibilityHook; + private readonly Hook utf8StringFromSequenceHook; [ServiceManager.ServiceConstructor] private GameGui(TargetSigScanner sigScanner) @@ -55,68 +51,34 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.address.Setup(sigScanner); Log.Verbose("===== G A M E G U I ====="); - Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}"); - Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}"); - Log.Verbose($"HandleItemHover address {Util.DescribeAddress(this.address.HandleItemHover)}"); - Log.Verbose($"HandleItemOut address {Util.DescribeAddress(this.address.HandleItemOut)}"); Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}"); - this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); + this.handleItemHoverHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->Update, this.HandleItemHoverDetour); + this.handleItemOutHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleItemOutDetour); - this.handleItemHoverHook = Hook.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = Hook.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour); - - this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); - this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); + this.handleActionHoverHook = Hook.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour); + this.handleActionOutHook = Hook.FromAddress((nint)AgentActionDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleActionOutDetour); this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); - this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour); + this.setUiVisibilityHook = Hook.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour); - this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); + this.utf8StringFromSequenceHook = Hook.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour); - this.setGlobalBgmHook.Enable(); this.handleItemHoverHook.Enable(); this.handleItemOutHook.Enable(); this.handleImmHook.Enable(); - this.toggleUiHideHook.Enable(); + this.setUiVisibilityHook.Enable(); this.handleActionHoverHook.Enable(); this.handleActionOutHook.Enable(); this.utf8StringFromSequenceHook.Enable(); } // Hooked delegates - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] - private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); - + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, bool uiVisible); - /// public event EventHandler? UiHideToggled; @@ -137,33 +99,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// public bool OpenMapWithMapLink(MapLinkPayload mapLink) - { - var uiModule = this.GetUIModule(); - - if (uiModule == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); - return false; - } - - this.getUIMapObject ??= this.address.GetVirtualFunction(uiModule, 0, 8); - - var uiMapObjectPtr = this.getUIMapObject(uiModule); - - if (uiMapObjectPtr == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); - return false; - } - - this.openMapWithFlag ??= this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); - - var mapLinkString = mapLink.DataString; - - Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); - - return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); - } + => RaptureAtkModule.Instance()->OpenMapWithMapLink(mapLink.DataString); /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) @@ -188,7 +124,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui inView = false; return false; } - + pCoords *= MathF.Abs(1.0f / pCoords.W); screenPos = new Vector2(pCoords.X, pCoords.Y); @@ -216,7 +152,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui worldPos = default; return false; } - + var camera = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CameraManager.Instance()->CurrentCamera; if (camera == null) { @@ -271,7 +207,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); - + /// public IntPtr FindAgentInterface(IntPtr addonPtr) { @@ -311,36 +247,33 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// void IInternalDisposableService.DisposeService() { - this.setGlobalBgmHook.Dispose(); this.handleItemHoverHook.Dispose(); this.handleItemOutHook.Dispose(); this.handleImmHook.Dispose(); - this.toggleUiHideHook.Dispose(); + this.setUiVisibilityHook.Dispose(); this.handleActionHoverHook.Dispose(); this.handleActionOutHook.Dispose(); this.utf8StringFromSequenceHook.Dispose(); } /// - /// Indicates if the game is on the title screen. + /// Indicates if the game is in the lobby scene (title screen, chara select, chara make, aesthetician etc.). /// - /// A value indicating whether or not the game is on the title screen. - internal bool IsOnTitleScreen() - { - var charaSelect = this.GetAddonByName("CharaSelect"); - var charaMake = this.GetAddonByName("CharaMake"); - var titleDcWorldMap = this.GetAddonByName("TitleDCWorldMap"); - if (charaMake != nint.Zero || charaSelect != nint.Zero || titleDcWorldMap != nint.Zero) - return false; - - return !Service.Get().IsLoggedIn; - } + /// A value indicating whether or not the game is in the lobby scene. + internal bool IsInLobby() => RaptureAtkModule.Instance()->CurrentUIScene.StartsWith("LobbyMain"u8); /// - /// Set the current background music. + /// Sets the current background music. /// - /// The background music key. - internal void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); + /// The BGM row id. + /// The BGM scene index. Defaults to MiniGame scene to avoid conflicts. + internal void SetBgm(ushort bgmId, uint sceneId = 2) => BGMSystem.SetBGM(bgmId, sceneId); + + /// + /// Resets the current background music. + /// + /// The BGM scene index. + internal void ResetBgm(uint sceneId = 2) => BGMSystem.Instance()->ResetBGM(sceneId); /// /// Reset the stored "UI hide" state. @@ -350,93 +283,63 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.GameUiHidden = false; } - private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) + private void HandleItemHoverDetour(AgentItemDetail* thisPtr, uint frameCount) { - var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); + this.handleItemHoverHook.Original(thisPtr, frameCount); - Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); + if (!thisPtr->IsAgentActive()) + return; - return retVal; + var itemId = (ulong)thisPtr->ItemId; + if (this.HoveredItem == itemId) + return; + + this.HoveredItem = itemId; + this.HoveredItemChanged?.InvokeSafely(this, itemId); + + Log.Verbose($"HoveredItem changed: {itemId}"); } - private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) + private AtkValue* HandleItemOutDetour(AgentItemDetail* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind) { - var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); + var ret = this.handleItemOutHook.Original(thisPtr, returnValue, values, valueCount, eventKind); - if (retVal.ToInt64() == 22) + if (values != null && valueCount == 1 && values->Int == -1) { - var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); - this.HoveredItem = itemId; + this.HoveredItem = 0; + this.HoveredItemChanged?.InvokeSafely(this, 0ul); - this.HoveredItemChanged?.InvokeSafely(this, itemId); - - Log.Verbose($"HoverItemId:{itemId} this:{hoverState.ToInt64()}"); + Log.Verbose("HoveredItem changed: 0"); } - return retVal; + return ret; } - private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); - - if (a3 != IntPtr.Zero && a4 == 1) - { - var a3Val = Marshal.ReadByte(a3, 0x8); - - if (a3Val == 255) - { - this.HoveredItem = 0ul; - - try - { - this.HoveredItemChanged?.Invoke(this, 0ul); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } - - Log.Verbose("HoverItemId: 0"); - } - } - - return retVal; - } - - private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) + private void HandleActionHoverDetour(AgentActionDetail* hoverState, FFXIVClientStructs.FFXIV.Client.UI.Agent.ActionKind actionKind, uint actionId, int a4, byte a5) { this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); - this.HoveredAction.ActionKind = actionKind; + this.HoveredAction.ActionKind = (HoverActionKind)actionKind; this.HoveredAction.BaseActionID = actionId; - this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); + this.HoveredAction.ActionID = hoverState->ActionId; this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); - Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{hoverState.ToInt64():X}"); + Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{(nint)hoverState:X}"); } - private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) + private AtkValue* HandleActionOutDetour(AgentActionDetail* agentActionDetail, AtkValue* a2, AtkValue* a3, uint a4, ulong a5) { - var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); + var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4, a5); - if (a3 != IntPtr.Zero && a4 == 1) + if (a3 != null && a4 == 1) { - var a3Val = Marshal.ReadByte(a3, 0x8); + var a3Val = a3->Int; if (a3Val == 255) { this.HoveredAction.ActionKind = HoverActionKind.None; this.HoveredAction.BaseActionID = 0; this.HoveredAction.ActionID = 0; - - try - { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredActionChanged event."); - } + this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); Log.Verbose("HoverActionId: 0"); } @@ -445,16 +348,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return retVal; } - private IntPtr ToggleUiHideDetour(IntPtr thisPtr, bool unknownByte) + private unsafe void SetUiVisibilityDetour(RaptureAtkModule* thisPtr, bool uiVisible) { - var result = this.toggleUiHideHook.Original(thisPtr, unknownByte); + this.setUiVisibilityHook.Original(thisPtr, uiVisible); this.GameUiHidden = !RaptureAtkModule.Instance()->IsUiVisible; this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden); - Log.Debug("UiHide toggled: {0}", this.GameUiHidden); - - return result; + Log.Debug("GameUiHidden: {0}", this.GameUiHidden); } private char HandleImmDetour(IntPtr framework, char a2, byte a3) @@ -501,13 +402,13 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.HoveredItemChanged += this.HoveredItemForward; this.gameGuiService.HoveredActionChanged += this.HoveredActionForward; } - + /// public event EventHandler? UiHideToggled; - + /// public event EventHandler? HoveredItemChanged; - + /// public event EventHandler? HoveredActionChanged; @@ -523,7 +424,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public HoveredAction HoveredAction => this.gameGuiService.HoveredAction; - + /// void IInternalDisposableService.DisposeService() { @@ -535,7 +436,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.HoveredItemChanged = null; this.HoveredActionChanged = null; } - + /// public bool OpenMapWithMapLink(MapLinkPayload mapLink) => this.gameGuiService.OpenMapWithMapLink(mapLink); @@ -543,7 +444,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) => this.gameGuiService.WorldToScreen(worldPos, out screenPos); - + /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView) => this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView); @@ -555,26 +456,26 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public IntPtr GetUIModule() => this.gameGuiService.GetUIModule(); - + /// public IntPtr GetAddonByName(string name, int index = 1) => this.gameGuiService.GetAddonByName(name, index); - + /// public IntPtr FindAgentInterface(string addonName) => this.gameGuiService.FindAgentInterface(addonName); - + /// - public unsafe IntPtr FindAgentInterface(void* addon) + public unsafe IntPtr FindAgentInterface(void* addon) => this.gameGuiService.FindAgentInterface(addon); /// - public IntPtr FindAgentInterface(IntPtr addonPtr) + public IntPtr FindAgentInterface(IntPtr addonPtr) => this.gameGuiService.FindAgentInterface(addonPtr); private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled); - + private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId); - + private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction); } diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 5b02a2d09..92b89c5a9 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -5,62 +5,14 @@ namespace Dalamud.Game.Gui; /// internal sealed class GameGuiAddressResolver : BaseAddressResolver { - /// - /// Gets the base address of the native GuiManager class. - /// - public IntPtr BaseAddress { get; private set; } - - /// - /// Gets the address of the native SetGlobalBgm method. - /// - public IntPtr SetGlobalBgm { get; private set; } - - /// - /// Gets the address of the native HandleItemHover method. - /// - public IntPtr HandleItemHover { get; private set; } - - /// - /// Gets the address of the native HandleItemOut method. - /// - public IntPtr HandleItemOut { get; private set; } - - /// - /// Gets the address of the native HandleActionHover method. - /// - public IntPtr HandleActionHover { get; private set; } - - /// - /// Gets the address of the native HandleActionOut method. - /// - public IntPtr HandleActionOut { get; private set; } - /// /// Gets the address of the native HandleImm method. /// public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } - - /// - /// Gets the address of the native Utf8StringFromSequence method. - /// - public IntPtr Utf8StringFromSequence { get; private set; } - /// protected override void Setup64Bit(ISigScanner sig) { - this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); - this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 6C 24 48 48 8B 74 24 50 4C 89 B7 08 01 00 00"); - this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); - this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); - this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); - this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); - - this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 44 0F B6 81"); - this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8"); + this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); // unnamed in CS } } diff --git a/Dalamud/Game/Gui/HoverActionKind.cs b/Dalamud/Game/Gui/HoverActionKind.cs index 90ff9d46c..ef8fe6400 100644 --- a/Dalamud/Game/Gui/HoverActionKind.cs +++ b/Dalamud/Game/Gui/HoverActionKind.cs @@ -14,35 +14,140 @@ public enum HoverActionKind /// /// A regular action is hovered. /// - Action = 21, + Action = 28, + + /// + /// A crafting action is hovered. + /// + CraftingAction = 29, /// /// A general action is hovered. /// - GeneralAction = 23, + GeneralAction = 30, /// /// A companion order type of action is hovered. /// - CompanionOrder = 24, + CompanionOrder = 31, // Game Term: BuddyOrder /// /// A main command type of action is hovered. /// - MainCommand = 25, + MainCommand = 32, /// /// An extras command type of action is hovered. /// - ExtraCommand = 26, + ExtraCommand = 33, + + /// + /// A companion action is hovered. + /// + Companion = 34, /// /// A pet order type of action is hovered. /// - PetOrder = 28, + PetOrder = 35, /// /// A trait is hovered. /// - Trait = 29, + Trait = 36, + + /// + /// A buddy action is hovered. + /// + BuddyAction = 37, + + /// + /// A company action is hovered. + /// + CompanyAction = 38, + + /// + /// A mount is hovered. + /// + Mount = 39, + + /// + /// A chocobo race action is hovered. + /// + ChocoboRaceAction = 40, + + /// + /// A chocobo race item is hovered. + /// + ChocoboRaceItem = 41, + + /// + /// A deep dungeon equipment is hovered. + /// + DeepDungeonEquipment = 42, + + /// + /// A deep dungeon equipment 2 is hovered. + /// + DeepDungeonEquipment2 = 43, + + /// + /// A deep dungeon item is hovered. + /// + DeepDungeonItem = 44, + + /// + /// A quick chat is hovered. + /// + QuickChat = 45, + + /// + /// An action combo route is hovered. + /// + ActionComboRoute = 46, + + /// + /// A pvp trait is hovered. + /// + PvPSelectTrait = 47, + + /// + /// A squadron action is hovered. + /// + BgcArmyAction = 48, + + /// + /// A perform action is hovered. + /// + Perform = 49, + + /// + /// A deep dungeon magic stone is hovered. + /// + DeepDungeonMagicStone = 50, + + /// + /// A deep dungeon demiclone is hovered. + /// + DeepDungeonDemiclone = 51, + + /// + /// An eureka magia action is hovered. + /// + EurekaMagiaAction = 52, + + /// + /// An island sanctuary temporary item is hovered. + /// + MYCTemporaryItem = 53, + + /// + /// An ornament is hovered. + /// + Ornament = 54, + + /// + /// Glasses are hovered. + /// + Glasses = 55, } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs index 28e2c36eb..32192ad21 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs @@ -1,14 +1,16 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.InteropServices; -using Dalamud.Game.Addon.Lifecycle; -using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState.Objects; +using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using Serilog; namespace Dalamud.Game.Gui.NamePlate; @@ -18,16 +20,6 @@ namespace Dalamud.Game.Gui.NamePlate; [ServiceManager.EarlyLoadedService] internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui { - /// - /// The index for the number array used by the NamePlate addon. - /// - public const int NumberArrayIndex = 5; - - /// - /// The index for the string array used by the NamePlate addon. - /// - public const int StringArrayIndex = 4; - /// /// The index for of the FullUpdate entry in the NamePlate number array. /// @@ -38,60 +30,59 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer(); - [ServiceManager.ServiceDependency] - private readonly AddonLifecycle addonLifecycle = Service.Get(); - [ServiceManager.ServiceDependency] private readonly GameGui gameGui = Service.Get(); [ServiceManager.ServiceDependency] private readonly ObjectTable objectTable = Service.Get(); - private readonly AddonLifecycleEventListener preRequestedUpdateListener; + private readonly NamePlateGuiAddressResolver address; + + private readonly Hook onRequestedUpdateHook; private NamePlateUpdateContext? context; private NamePlateUpdateHandler[] updateHandlers = []; [ServiceManager.ServiceConstructor] - private NamePlateGui() + private unsafe NamePlateGui(TargetSigScanner sigScanner) { - this.preRequestedUpdateListener = new AddonLifecycleEventListener( - AddonEvent.PreRequestedUpdate, - "NamePlate", - this.OnPreRequestedUpdate); + this.address = new NamePlateGuiAddressResolver(); + this.address.Setup(sigScanner); - this.addonLifecycle.RegisterListener(this.preRequestedUpdateListener); + this.onRequestedUpdateHook = Hook.FromAddress( + this.address.OnRequestedUpdate, + this.OnRequestedUpdateDetour); + this.onRequestedUpdateHook.Enable(); } /// public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate; + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate; + /// public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate; + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate; + /// public unsafe void RequestRedraw() { - var addon = this.gameGui.GetAddonByName("NamePlate"); - if (addon != 0) + var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate"); + if (addon != null) { - var raptureAtkModule = RaptureAtkModule.Instance(); - if (raptureAtkModule == null) - { - return; - } - - ((AddonNamePlate*)addon)->DoFullUpdate = 1; - var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex]; - namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1); + addon->DoFullUpdate = 1; + AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate)->SetValue(NumberArrayFullUpdateIndex, 1); } } /// void IInternalDisposableService.DisposeService() { - this.addonLifecycle.UnregisterListener(this.preRequestedUpdateListener); + this.onRequestedUpdateHook.Dispose(); } /// @@ -144,65 +135,124 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui this.updateHandlers = handlers.ToArray(); } - private void OnPreRequestedUpdate(AddonEvent type, AddonArgs args) + private unsafe void OnRequestedUpdateDetour( + AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { - if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null) - { - return; - } + var calledOriginal = false; - var reqArgs = (AddonRequestedUpdateArgs)args; - if (this.context == null) + try { - this.context = new NamePlateUpdateContext(this.objectTable, reqArgs); - this.CreateHandlers(this.context); - } - else - { - this.context.ResetState(reqArgs); - } - - var activeNamePlateCount = this.context.ActiveNamePlateCount; - if (activeNamePlateCount == 0) - return; - - var activeHandlers = this.updateHandlers[..activeNamePlateCount]; - - if (this.context.IsFullUpdate) - { - foreach (var handler in activeHandlers) + if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null && this.OnPostDataUpdate == null && + this.OnPostNamePlateUpdate == null) { - handler.ResetState(); + return; } - this.OnDataUpdate?.Invoke(this.context, activeHandlers); - this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers); - if (this.context.HasParts) - this.ApplyBuilders(activeHandlers); - } - else - { - var udpatedHandlers = new List(activeNamePlateCount); - foreach (var handler in activeHandlers) + if (this.context == null) { - handler.ResetState(); - if (handler.IsUpdating) - udpatedHandlers.Add(handler); + this.context = new NamePlateUpdateContext(this.objectTable); + this.CreateHandlers(this.context); } - if (this.OnDataUpdate is not null) + this.context.ResetState(addon, numberArrayData, stringArrayData); + + var activeNamePlateCount = this.context!.ActiveNamePlateCount; + if (activeNamePlateCount == 0) + return; + + var activeHandlers = this.updateHandlers[..activeNamePlateCount]; + + if (this.context.IsFullUpdate) { + foreach (var handler in activeHandlers) + { + handler.ResetState(); + } + this.OnDataUpdate?.Invoke(this.context, activeHandlers); - this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers); + if (this.context.HasParts) this.ApplyBuilders(activeHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, activeHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); } - else if (udpatedHandlers.Count != 0) + else { - var changedHandlersSpan = udpatedHandlers.ToArray().AsSpan(); - this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); - if (this.context.HasParts) - this.ApplyBuilders(changedHandlersSpan); + var updatedHandlers = new List(activeNamePlateCount); + foreach (var handler in activeHandlers) + { + handler.ResetState(); + if (handler.IsUpdating) + updatedHandlers.Add(handler); + } + + if (this.OnDataUpdate is not null) + { + this.OnDataUpdate?.Invoke(this.context, activeHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers); + + if (this.context.HasParts) + this.ApplyBuilders(activeHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); + } + else if (updatedHandlers.Count != 0) + { + this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers); + + if (this.context.HasParts) + this.ApplyBuilders(updatedHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); + } + } + } + finally + { + if (!calledOriginal) + { + try + { + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } } } } @@ -217,6 +267,17 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui } } } + + private void ApplyBuilders(List handlers) + { + foreach (var handler in handlers) + { + if (handler.PartsContainer is { } container) + { + container.ApplyBuilders(handler); + } + } + } } /// @@ -239,6 +300,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate { if (this.OnNamePlateUpdateScoped == null) this.parentService.OnNamePlateUpdate += this.OnNamePlateUpdateForward; + this.OnNamePlateUpdateScoped += value; } @@ -250,6 +312,25 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate } } + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate + { + add + { + if (this.OnPostNamePlateUpdateScoped == null) + this.parentService.OnPostNamePlateUpdate += this.OnPostNamePlateUpdateForward; + + this.OnPostNamePlateUpdateScoped += value; + } + + remove + { + this.OnPostNamePlateUpdateScoped -= value; + if (this.OnPostNamePlateUpdateScoped == null) + this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward; + } + } + /// public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate { @@ -257,6 +338,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate { if (this.OnDataUpdateScoped == null) this.parentService.OnDataUpdate += this.OnDataUpdateForward; + this.OnDataUpdateScoped += value; } @@ -268,10 +350,33 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate } } + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate + { + add + { + if (this.OnPostDataUpdateScoped == null) + this.parentService.OnPostDataUpdate += this.OnPostDataUpdateForward; + + this.OnPostDataUpdateScoped += value; + } + + remove + { + this.OnPostDataUpdateScoped -= value; + if (this.OnPostDataUpdateScoped == null) + this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward; + } + } + private event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdateScoped; + /// public void RequestRedraw() { @@ -284,8 +389,14 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate this.parentService.OnNamePlateUpdate -= this.OnNamePlateUpdateForward; this.OnNamePlateUpdateScoped = null; + this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward; + this.OnPostNamePlateUpdateScoped = null; + this.parentService.OnDataUpdate -= this.OnDataUpdateForward; this.OnDataUpdateScoped = null; + + this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward; + this.OnPostDataUpdateScoped = null; } private void OnNamePlateUpdateForward( @@ -294,9 +405,21 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate this.OnNamePlateUpdateScoped?.Invoke(context, handlers); } + private void OnPostNamePlateUpdateForward( + INamePlateUpdateContext context, IReadOnlyList handlers) + { + this.OnPostNamePlateUpdateScoped?.Invoke(context, handlers); + } + private void OnDataUpdateForward( INamePlateUpdateContext context, IReadOnlyList handlers) { this.OnDataUpdateScoped?.Invoke(context, handlers); } + + private void OnPostDataUpdateForward( + INamePlateUpdateContext context, IReadOnlyList handlers) + { + this.OnPostDataUpdateScoped?.Invoke(context, handlers); + } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs new file mode 100644 index 000000000..450e1fa9f --- /dev/null +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs @@ -0,0 +1,20 @@ +namespace Dalamud.Game.Gui.NamePlate; + +/// +/// An address resolver for the class. +/// +internal class NamePlateGuiAddressResolver : BaseAddressResolver +{ + /// + /// Gets the address of the AddonNamePlate OnRequestedUpdate method. We need to use a hook for this because + /// AddonNamePlate.Show calls OnRequestedUpdate directly, bypassing the AddonLifecycle callsite hook. + /// + public IntPtr OnRequestedUpdate { get; private set; } + + /// + protected override void Setup64Bit(ISigScanner sig) + { + this.OnRequestedUpdate = sig.ScanText( + "4C 8B DC 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B 40 20"); + } +} diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index b8a4a9bd8..0a6fe801f 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -1,7 +1,7 @@ -using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState.Objects; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Arrays; using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Gui.NamePlate; @@ -54,13 +54,11 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// Initializes a new instance of the class. /// /// An object table. - /// The addon lifecycle arguments for the update request. - internal NamePlateUpdateContext(ObjectTable objectTable, AddonRequestedUpdateArgs args) + internal NamePlateUpdateContext(ObjectTable objectTable) { this.ObjectTable = objectTable; this.RaptureAtkModule = FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule.Instance(); this.Ui3DModule = UIModule.Instance()->GetUI3DModule(); - this.ResetState(args); } /// @@ -127,7 +125,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Gets a pointer to the NamePlate addon's number array entries as a struct. /// - internal AddonNamePlate.NamePlateIntArrayData* NumberStruct { get; private set; } + internal NamePlateNumberArray* NumberStruct { get; private set; } /// /// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder. @@ -137,13 +135,15 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Resets the state of the context based on the provided addon lifecycle arguments. /// - /// The addon lifecycle arguments for the update request. - internal void ResetState(AddonRequestedUpdateArgs args) + /// A pointer to the addon. + /// A pointer to the global number array data struct. + /// A pointer to the global string array data struct. + public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { - this.Addon = (AddonNamePlate*)args.Addon; - this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex]; - this.NumberStruct = (AddonNamePlate.NamePlateIntArrayData*)this.NumberData->IntArray; - this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex]; + this.Addon = (AddonNamePlate*)addon; + this.NumberData = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate); + this.NumberStruct = (NamePlateNumberArray*)this.NumberData->IntArray; + this.StringData = AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate); this.HasParts = false; this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 99429d932..185be9d24 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -7,6 +7,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Text.SeStringHandling; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Arrays; using FFXIVClientStructs.Interop; namespace Dalamud.Game.Gui.NamePlate; @@ -328,9 +329,22 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId; /// - public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.CreateObjectReference( - (nint)this.context.Ui3DModule->NamePlateObjectInfoPointers[ - this.ArrayIndex].Value->GameObject); + public IGameObject? GameObject + { + get + { + if (this.GameObjectId == 0xE0000000) + { + // Skipping Ui3DModule lookup for invalid nameplate (NamePlateInfo->ObjectId is 0xE0000000). This + // prevents crashes around certain Doman Reconstruction cutscenes. + return null; + } + + return this.gameObject ??= this.context.ObjectTable[ + this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex] + .Value->GameObject->ObjectIndex]; + } + } /// public IBattleChara? BattleChara => this.GameObject as IBattleChara; @@ -490,7 +504,7 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler private AddonNamePlate.NamePlateObject* NamePlateObject => &this.context.Addon->NamePlateObjectArray[this.NamePlateIndex]; - private AddonNamePlate.NamePlateIntArrayData.NamePlateObjectIntArrayData* ObjectData => + private NamePlateNumberArray.NamePlateObjectIntArrayData* ObjectData => this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex); /// diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs deleted file mode 100644 index 0b267694c..000000000 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Dalamud.Game.Gui.PartyFinder; - -/// -/// The address resolver for the class. -/// -internal class PartyFinderAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native ReceiveListing method. - /// - public IntPtr ReceiveListing { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC ?? 48 8B D9 4C 8B FA"); - } -} diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index ef4055b29..0b25a87be 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -7,6 +7,8 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Info; + using Serilog; namespace Dalamud.Game.Gui.PartyFinder; @@ -15,12 +17,11 @@ namespace Dalamud.Game.Gui.PartyFinder; /// This class handles interacting with the native PartyFinder window. /// [ServiceManager.EarlyLoadedService] -internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui +internal sealed unsafe class PartyFinderGui : IInternalDisposableService, IPartyFinderGui { - private readonly PartyFinderAddressResolver address; private readonly nint memory; - private readonly Hook receiveListingHook; + private readonly Hook receiveListingHook; /// /// Initializes a new instance of the class. @@ -29,18 +30,12 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG [ServiceManager.ServiceConstructor] private PartyFinderGui(TargetSigScanner sigScanner) { - this.address = new PartyFinderAddressResolver(); - this.address.Setup(sigScanner); - this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); - this.receiveListingHook = Hook.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour); + this.receiveListingHook = Hook.FromAddress(InfoProxyCrossRealm.Addresses.ReceiveListing.Value, this.HandleReceiveListingDetour); this.receiveListingHook.Enable(); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ReceiveListingDelegate(nint managerPtr, nint data); - /// public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing; @@ -61,18 +56,18 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG } } - private void HandleReceiveListingDetour(nint managerPtr, nint data) + private void HandleReceiveListingDetour(InfoProxyCrossRealm* infoProxy, nint packet) { try { - this.HandleListingEvents(data); + this.HandleListingEvents(packet); } catch (Exception ex) { Log.Error(ex, "Exception on ReceiveListing hook."); } - this.receiveListingHook.Original(managerPtr, data); + this.receiveListingHook.Original(infoProxy, packet); } private void HandleListingEvents(nint data) diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs index ff2a3ce2a..475892205 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -4,160 +4,160 @@ namespace Dalamud.Game.Gui.PartyFinder.Types; /// Job flags for the class. /// [Flags] -public enum JobFlags +public enum JobFlags : ulong { /// /// Gladiator (GLD). /// - Gladiator = 1 << 1, + Gladiator = 1ul << 1, /// /// Pugilist (PGL). /// - Pugilist = 1 << 2, + Pugilist = 1ul << 2, /// /// Marauder (MRD). /// - Marauder = 1 << 3, + Marauder = 1ul << 3, /// /// Lancer (LNC). /// - Lancer = 1 << 4, + Lancer = 1ul << 4, /// /// Archer (ARC). /// - Archer = 1 << 5, + Archer = 1ul << 5, /// /// Conjurer (CNJ). /// - Conjurer = 1 << 6, + Conjurer = 1ul << 6, /// /// Thaumaturge (THM). /// - Thaumaturge = 1 << 7, + Thaumaturge = 1ul << 7, /// /// Paladin (PLD). /// - Paladin = 1 << 8, + Paladin = 1ul << 8, /// /// Monk (MNK). /// - Monk = 1 << 9, + Monk = 1ul << 9, /// /// Warrior (WAR). /// - Warrior = 1 << 10, + Warrior = 1ul << 10, /// /// Dragoon (DRG). /// - Dragoon = 1 << 11, + Dragoon = 1ul << 11, /// /// Bard (BRD). /// - Bard = 1 << 12, + Bard = 1ul << 12, /// /// White mage (WHM). /// - WhiteMage = 1 << 13, + WhiteMage = 1ul << 13, /// /// Black mage (BLM). /// - BlackMage = 1 << 14, + BlackMage = 1ul << 14, /// /// Arcanist (ACN). /// - Arcanist = 1 << 15, + Arcanist = 1ul << 15, /// /// Summoner (SMN). /// - Summoner = 1 << 16, + Summoner = 1ul << 16, /// /// Scholar (SCH). /// - Scholar = 1 << 17, + Scholar = 1ul << 17, /// /// Rogue (ROG). /// - Rogue = 1 << 18, + Rogue = 1ul << 18, /// /// Ninja (NIN). /// - Ninja = 1 << 19, + Ninja = 1ul << 19, /// /// Machinist (MCH). /// - Machinist = 1 << 20, + Machinist = 1ul << 20, /// /// Dark Knight (DRK). /// - DarkKnight = 1 << 21, + DarkKnight = 1ul << 21, /// /// Astrologian (AST). /// - Astrologian = 1 << 22, + Astrologian = 1ul << 22, /// /// Samurai (SAM). /// - Samurai = 1 << 23, + Samurai = 1ul << 23, /// /// Red mage (RDM). /// - RedMage = 1 << 24, + RedMage = 1ul << 24, /// /// Blue mage (BLM). /// - BlueMage = 1 << 25, + BlueMage = 1ul << 25, /// /// Gunbreaker (GNB). /// - Gunbreaker = 1 << 26, + Gunbreaker = 1ul << 26, /// /// Dancer (DNC). /// - Dancer = 1 << 27, + Dancer = 1ul << 27, /// /// Reaper (RPR). /// - Reaper = 1 << 28, + Reaper = 1ul << 28, /// /// Sage (SGE). /// - Sage = 1 << 29, + Sage = 1ul << 29, /// /// Viper (VPR). /// - Viper = 1 << 30, + Viper = 1ul << 30, /// /// Pictomancer (PCT). /// - Pictomancer = 1 << 31, + Pictomancer = 1ul << 31, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs index ba72021ba..1c78c871b 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin.Services; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.PartyFinder.Types; diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs index 3461841ab..09de07e0d 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs @@ -5,7 +5,8 @@ using Dalamud.Data; using Dalamud.Game.Gui.PartyFinder.Internal; using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.PartyFinder.Types; @@ -48,7 +49,7 @@ public interface IPartyFinderListing /// /// Gets a list of the classes/jobs that are currently present in the party. /// - IReadOnlyCollection> JobsPresent { get; } + IReadOnlyCollection> JobsPresent { get; } /// /// Gets the ID assigned to this listing by the game's server. @@ -73,17 +74,17 @@ public interface IPartyFinderListing /// /// Gets the world that this listing was created on. /// - Lazy World { get; } + RowRef World { get; } /// /// Gets the home world of the listing's host. /// - Lazy HomeWorld { get; } + RowRef HomeWorld { get; } /// /// Gets the current world of the listing's host. /// - Lazy CurrentWorld { get; } + RowRef CurrentWorld { get; } /// /// Gets the Party Finder category this listing is listed under. @@ -98,7 +99,7 @@ public interface IPartyFinderListing /// /// Gets the duty this listing is for. May be null for non-duty listings. /// - Lazy Duty { get; } + RowRef Duty { get; } /// /// Gets the type of duty this listing is for. @@ -216,12 +217,12 @@ internal class PartyFinderListing : IPartyFinderListing this.ContentId = listing.ContentId; this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); - this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); - this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); - this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); + this.World = LuminaUtils.CreateRef(listing.World); + this.HomeWorld = LuminaUtils.CreateRef(listing.HomeWorld); + this.CurrentWorld = LuminaUtils.CreateRef(listing.CurrentWorld); this.Category = (DutyCategory)listing.Category; this.RawDuty = listing.Duty; - this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); + this.Duty = LuminaUtils.CreateRef(listing.Duty); this.DutyType = (DutyType)listing.DutyType; this.BeginnersWelcome = listing.BeginnersWelcome == 1; this.SecondsRemaining = listing.SecondsRemaining; @@ -231,10 +232,7 @@ internal class PartyFinderListing : IPartyFinderListing this.SlotsFilled = listing.NumSlotsFilled; this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; this.JobsPresent = listing.JobsPresent - .Select(id => new Lazy( - () => id == 0 - ? null - : dataManager.GetExcelSheet().GetRow(id))) + .Select(id => LuminaUtils.CreateRef(id)) .ToArray(); } @@ -251,13 +249,13 @@ internal class PartyFinderListing : IPartyFinderListing public SeString Description { get; } /// - public Lazy World { get; } + public RowRef World { get; } /// - public Lazy HomeWorld { get; } + public RowRef HomeWorld { get; } /// - public Lazy CurrentWorld { get; } + public RowRef CurrentWorld { get; } /// public DutyCategory Category { get; } @@ -266,7 +264,7 @@ internal class PartyFinderListing : IPartyFinderListing public ushort RawDuty { get; } /// - public Lazy Duty { get; } + public RowRef Duty { get; } /// public DutyType DutyType { get; } @@ -314,7 +312,7 @@ internal class PartyFinderListing : IPartyFinderListing public IReadOnlyCollection RawJobsPresent => this.jobsPresent; /// - public IReadOnlyCollection> JobsPresent { get; } + public IReadOnlyCollection> JobsPresent { get; } #region Indexers diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 0895d1b6b..6b55b3408 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -5,8 +5,11 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; + namespace Dalamud.Game.Gui.Toast; /// @@ -17,8 +20,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui { private const uint QuestToastCheckmarkMagic = 60081; - private readonly ToastGuiAddressResolver address; - private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); private readonly Queue errorQueue = new(); @@ -30,16 +31,12 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui /// /// Initializes a new instance of the class. /// - /// Sig scanner to use. [ServiceManager.ServiceConstructor] - private ToastGui(TargetSigScanner sigScanner) + private unsafe ToastGui() { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour); - this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour); - this.showErrorToastHook = Hook.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour); + this.showNormalToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowWideText, this.HandleNormalToastDetour); + this.showQuestToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowText, this.HandleQuestToastDetour); + this.showErrorToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowErrorText, this.HandleErrorToastDetour); this.showNormalToastHook.Enable(); this.showQuestToastHook.Enable(); @@ -48,16 +45,16 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui #region Marshal delegates - private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); + private unsafe delegate void ShowNormalToastDelegate(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId); - private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); + private unsafe delegate void ShowQuestToastDelegate(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); - private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); + private unsafe delegate void ShowErrorToastDelegate(UIModule* thisPtr, byte* text, byte respectsHidingMaybe); #endregion #region Events - + /// public event IToastGui.OnNormalToastDelegate? Toast; @@ -102,32 +99,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui this.ShowError(message); } } - - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private SeString ParseString(IntPtr text) - { - var bytes = new List(); - unsafe - { - var ptr = (byte*)text; - while (*ptr != 0) - { - bytes.Add(*ptr); - ptr += 1; - } - } - - // call events - return SeString.Parse(bytes.ToArray()); - } } /// @@ -149,36 +120,30 @@ internal sealed partial class ToastGui this.normalQueue.Enqueue((message.Encode(), options)); } - private void ShowNormal(byte[] bytes, ToastOptions? options = null) + private unsafe void ShowNormal(byte[] bytes, ToastOptions? options = null) { options ??= new ToastOptions(); - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); - } + this.HandleNormalToastDetour( + UIModule.Instance(), + ptr, + 5, + (byte)options.Position, + (byte)options.Speed, + 0); } } - private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) + private unsafe void HandleNormalToastDetour(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId) { - if (text == IntPtr.Zero) - { - return IntPtr.Zero; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = MemoryHelper.ReadSeStringNullTerminated((nint)text); var options = new ToastOptions { Position = (ToastPosition)isTop, @@ -189,18 +154,17 @@ internal sealed partial class ToastGui // do nothing if handled if (isHandled) - { - return IntPtr.Zero; - } + return; - var terminated = Terminate(str.Encode()); - - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); - } + this.showNormalToastHook.Original( + thisPtr, + ptr, + layer, + (byte)(options.Position == ToastPosition.Top ? 1 : 0), + (byte)(options.Speed == ToastSpeed.Fast ? 1 : 0), + logMessageId); } } } @@ -224,45 +188,33 @@ internal sealed partial class ToastGui this.questQueue.Enqueue((message.Encode(), options)); } - private void ShowQuest(byte[] bytes, QuestToastOptions? options = null) + private unsafe void ShowQuest(byte[] bytes, QuestToastOptions? options = null) { options ??= new QuestToastOptions(); - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleQuestToastDetour( - manager!.Value, - (int)options.Position, - (IntPtr)ptr, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } + this.HandleQuestToastDetour( + UIModule.Instance(), + (int)options.Position, + ptr, + ioc1, + (byte)(options.PlaySound ? 1 : 0), + ioc2, + 0); } } - private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) + private unsafe void HandleQuestToastDetour(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) { - if (text == IntPtr.Zero) - { - return 0; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = SeString.Parse(text); var options = new QuestToastOptions { Position = (QuestToastPosition)position, @@ -275,27 +227,20 @@ internal sealed partial class ToastGui // do nothing if handled if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); + return; var (ioc1, ioc2) = this.DetermineParameterOrder(options); - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showQuestToastHook.Original( - manager, - (int)options.Position, - (IntPtr)message, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } + this.showQuestToastHook.Original( + UIModule.Instance(), + (int)options.Position, + ptr, + ioc1, + (byte)(options.PlaySound ? 1 : 0), + ioc2, + 0); } } @@ -324,51 +269,32 @@ internal sealed partial class ToastGui this.errorQueue.Enqueue(message.Encode()); } - private void ShowError(byte[] bytes) + private unsafe void ShowError(byte[] bytes) { - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0); - } + this.HandleErrorToastDetour(UIModule.Instance(), ptr, 0); } } - private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) + private unsafe void HandleErrorToastDetour(UIModule* thisPtr, byte* text, byte respectsHidingMaybe) { - if (text == IntPtr.Zero) - { - return 0; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = SeString.Parse(text); this.ErrorToast?.Invoke(ref str, ref isHandled); // do nothing if handled if (isHandled) - { - return 0; - } + return; - var terminated = Terminate(str.Encode()); - - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); - } + this.showErrorToastHook.Original(thisPtr, ptr, respectsHidingMaybe); } } } diff --git a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs deleted file mode 100644 index 0a8775540..000000000 --- a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// An address resolver for the class. -/// -internal class ToastGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native ShowNormalToast method. - /// - public IntPtr ShowNormalToast { get; private set; } - - /// - /// Gets the address of the native ShowQuestToast method. - /// - public IntPtr ShowQuestToast { get; private set; } - - /// - /// Gets the address of the ShowErrorToast method. - /// - public IntPtr ShowErrorToast { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); - this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); - this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); - } -} diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 39ff7c241..83a2f3525 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -6,9 +6,11 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; using Dalamud.Interface.Internal; using Dalamud.Interface.Windowing; +using Dalamud.Logging.Internal; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; -using Serilog; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; @@ -20,12 +22,14 @@ namespace Dalamud.Game.Internal; [ServiceManager.EarlyLoadedService] internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService { - private readonly Hook hookAgentHudOpenSystemMenu; + private static readonly ModuleLog Log = new("DalamudAtkTweaks"); + + private readonly Hook hookAgentHudOpenSystemMenu; // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + private readonly Hook hookUiModuleExecuteMainCommand; - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -41,34 +45,24 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService [ServiceManager.ServiceConstructor] private DalamudAtkTweaks(TargetSigScanner sigScanner) { - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00"); - - this.hookAgentHudOpenSystemMenu = Hook.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - - var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); - this.hookUiModuleRequestMainCommand = Hook.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - - var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 54 41 57"); - this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); + this.hookAgentHudOpenSystemMenu = Hook.FromAddress(AgentHUD.Addresses.OpenSystemMenu.Value, this.AgentHudOpenSystemMenuDetour); + this.hookUiModuleExecuteMainCommand = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour); + this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour); this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); // this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; - + this.hookAgentHudOpenSystemMenu.Enable(); - this.hookUiModuleRequestMainCommand.Enable(); + this.hookUiModuleExecuteMainCommand.Enable(); this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); } /// Finalizes an instance of the class. ~DalamudAtkTweaks() => this.Dispose(false); - private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); - - private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); - - private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); + private delegate void AgentHudOpenSystemMenuPrototype(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize); /// void IInternalDisposableService.DisposeService() => this.Dispose(true); @@ -81,7 +75,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService if (disposing) { this.hookAgentHudOpenSystemMenu.Dispose(); - this.hookUiModuleRequestMainCommand.Dispose(); + this.hookUiModuleExecuteMainCommand.Dispose(); this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); // this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; @@ -116,22 +110,19 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService } */ - private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) + private void AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", MemoryHelper.ReadSeStringAsString(out _, new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); - - // "SendHotkey" // 3 == Close - if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && this.configuration.IsFocusManagementEnabled) + if (eventType == AtkEventType.InputReceived && WindowSystem.HasAnyWindowSystemFocus && atkEventData != null && *(int*)atkEventData == 3 && this.configuration.IsFocusManagementEnabled) { Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return IntPtr.Zero; + return; } - return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); + this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, eventType, eventParam, atkEvent, atkEventData); } - private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) + private void AgentHudOpenSystemMenuDetour(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize) { if (WindowSystem.HasAnyWindowSystemFocus && this.configuration.IsFocusManagementEnabled) { @@ -189,7 +180,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService // about hooking the exd reader, thank god var firstStringEntry = &atkValueArgs[5 + 18]; firstStringEntry->ChangeType(ValueType.String); - + var secondStringEntry = &atkValueArgs[6 + 18]; secondStringEntry->ChangeType(ValueType.String); @@ -202,7 +193,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") .Append(new UIForegroundPayload(0)) .Append(this.locDalamudSettings).Encode(); - + firstStringEntry->SetManagedString(strPlugins); secondStringEntry->SetManagedString(strSettings); @@ -213,7 +204,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); } - private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) + private unsafe void UiModuleExecuteMainCommandDetour(UIModule* thisPtr, uint commandId) { var dalamudInterface = Service.GetNullable(); @@ -226,7 +217,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService dalamudInterface?.OpenSettings(); break; default: - this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); + this.hookUiModuleExecuteMainCommand.Original(thisPtr, commandId); break; } } diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 0ddf0dcaf..5c8ed27b0 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -120,7 +120,7 @@ internal class GameInventory : IInternalDisposableService continue; // Assumption: newItems is sorted by slots, and the last item has the highest slot number. - var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1]; + var oldItems = this.inventoryItems[i] ??= this.CreateItemsArray(newItems[^1].InternalItem.Slot + 1); foreach (ref readonly var newItem in newItems) { @@ -312,6 +312,13 @@ internal class GameInventory : IInternalDisposableService this.mergedEvents.Clear(); } + private GameInventoryItem[] CreateItemsArray(int length) + { + var items = new GameInventoryItem[length]; + items.Initialize(); + return items; + } + private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1) { this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0; @@ -403,6 +410,9 @@ internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInve /// public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit; + /// + public ReadOnlySpan GetInventoryItems(GameInventoryType type) => GameInventoryItem.GetReadOnlySpanOfInventory(type); + /// void IInternalDisposableService.DisposeService() { diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 3844da15d..085200742 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -1,15 +1,19 @@ -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Dalamud.Data; +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Client.Game; +using Lumina.Excel.Sheets; + namespace Dalamud.Game.Inventory; /// /// Dalamud wrapper around a ClientStructs InventoryItem. /// -[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)] +[StructLayout(LayoutKind.Explicit, Size = InventoryItem.StructSize)] public unsafe struct GameInventoryItem : IEquatable { /// @@ -18,20 +22,18 @@ public unsafe struct GameInventoryItem : IEquatable [FieldOffset(0)] internal readonly InventoryItem InternalItem; - private const int StructSizeInBytes = 0x40; - /// /// The view of the backing data, in . /// [FieldOffset(0)] - private fixed ulong dataUInt64[StructSizeInBytes / 0x8]; + private fixed ulong dataUInt64[InventoryItem.StructSize / 0x8]; - static GameInventoryItem() + /// + /// Initializes a new instance of the struct. + /// + public GameInventoryItem() { - Debug.Assert( - sizeof(InventoryItem) == StructSizeInBytes, - $"Definition of {nameof(InventoryItem)} has been changed. " + - $"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change."); + this.InternalItem.Ctor(); } /// @@ -43,67 +45,120 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets a value indicating whether the this is empty. /// - public bool IsEmpty => this.InternalItem.ItemId == 0; + public bool IsEmpty => this.InternalItem.IsEmpty(); /// /// Gets the container inventory type. /// - public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.Container; + public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.GetInventoryType(); /// /// Gets the inventory slot index this item is in. /// - public uint InventorySlot => (uint)this.InternalItem.Slot; + public uint InventorySlot => this.InternalItem.GetSlot(); /// /// Gets the item id. /// - public uint ItemId => this.InternalItem.ItemId; + public uint ItemId => this.InternalItem.GetItemId(); + + /// + /// Gets the base item id (without HQ or Collectible offset applied). + /// + public uint BaseItemId => ItemUtil.GetBaseId(this.ItemId).ItemId; /// /// Gets the quantity of items in this item stack. /// - public uint Quantity => this.InternalItem.Quantity; + public int Quantity => (int)this.InternalItem.GetQuantity(); + + /// + /// Gets the spiritbond or collectability of this item. + /// + public uint SpiritbondOrCollectability => this.InternalItem.GetSpiritbondOrCollectability(); /// /// Gets the spiritbond of this item. /// - public uint Spiritbond => this.InternalItem.Spiritbond; + [Obsolete($"Renamed to {nameof(SpiritbondOrCollectability)}", true)] + public uint Spiritbond => this.SpiritbondOrCollectability; /// /// Gets the repair condition of this item. /// - public uint Condition => this.InternalItem.Condition; + public uint Condition => this.InternalItem.GetCondition(); // Note: This will be the Breeding Capacity of Race Chocobos /// /// Gets a value indicating whether the item is High Quality. /// - public bool IsHq => (this.InternalItem.Flags & InventoryItem.ItemFlags.HighQuality) != 0; + public bool IsHq => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.HighQuality); /// /// Gets a value indicating whether the item has a company crest applied. /// - public bool IsCompanyCrestApplied => (this.InternalItem.Flags & InventoryItem.ItemFlags.CompanyCrestApplied) != 0; + public bool IsCompanyCrestApplied => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.CompanyCrestApplied); /// /// Gets a value indicating whether the item is a relic. /// - public bool IsRelic => (this.InternalItem.Flags & InventoryItem.ItemFlags.Relic) != 0; + public bool IsRelic => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.Relic); /// /// Gets a value indicating whether the is a collectable. /// - public bool IsCollectable => (this.InternalItem.Flags & InventoryItem.ItemFlags.Collectable) != 0; + public bool IsCollectable => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.Collectable); /// /// Gets the array of materia types. /// - public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref this.InternalItem.Materia[0]), 5); + public ReadOnlySpan Materia + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) + return []; + + Span materiaIds = new ushort[this.InternalItem.Materia.Length]; + var materiaRowCount = Service.Get().GetExcelSheet().Count; + + for (byte i = 0; i < this.InternalItem.Materia.Length; i++) + { + var materiaId = this.InternalItem.GetMateriaId(i); + if (materiaId < materiaRowCount) + materiaIds[i] = materiaId; + } + + return materiaIds; + } + } /// /// Gets the array of materia grades. /// - public ReadOnlySpan MateriaGrade => new(Unsafe.AsPointer(ref this.InternalItem.MateriaGrades[0]), 5); + public ReadOnlySpan MateriaGrade + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) + return []; + + Span materiaGrades = new byte[this.InternalItem.MateriaGrades.Length]; + var materiaGradeRowCount = Service.Get().GetExcelSheet().Count; + + for (byte i = 0; i < this.InternalItem.MateriaGrades.Length; i++) + { + var materiaGrade = this.InternalItem.GetMateriaGrade(i); + if (materiaGrade < materiaGradeRowCount) + materiaGrades[i] = materiaGrade; + } + + return materiaGrades; + } + } /// /// Gets the address of native inventory item in the game.
@@ -132,18 +187,60 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets the color used for this item. /// - public ReadOnlySpan Stains => new(Unsafe.AsPointer(ref this.InternalItem.Stains[0]), 2); + public ReadOnlySpan Stains + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId)) + return []; + + var dataManager = Service.Get(); + + if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.DyeCount == 0) + return []; + + Span stainIds = new byte[item.DyeCount]; + var stainRowCount = dataManager.GetExcelSheet().Count; + + for (byte i = 0; i < item.DyeCount; i++) + { + var stainId = this.InternalItem.GetStain(i); + if (stainId < stainRowCount) + stainIds[i] = stainId; + } + + return stainIds; + } + } /// /// Gets the glamour id for this item. /// - public uint GlamourId => this.InternalItem.GlamourId; + public uint GlamourId => this.InternalItem.GetGlamourId(); /// /// Gets the items crafter's content id. /// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now. /// - internal ulong CrafterContentId => this.InternalItem.CrafterContentId; + internal ulong CrafterContentId => this.InternalItem.GetCrafterContentId(); + + /// + /// Gets a value indicating whether the Materia fields are used to store a date. + /// + private bool IsMateriaUsedForDate => this.BaseItemId + // Race Chocobo related items + is 9560 // Proof of Covering + + // Wedding related items + or 8575 // Eternity Ring + or 8693 // Promise of Innocence + or 8694 // Promise of Passion + or 8695 // Promise of Devotion + or 8696 // (Unknown/unused) + or 8698 // Blank Invitation + or 8699; // Ceremony Invitation public static bool operator ==(in GameInventoryItem l, in GameInventoryItem r) => l.Equals(r); @@ -157,7 +254,7 @@ public unsafe struct GameInventoryItem : IEquatable /// true if the current object is equal to the parameter; otherwise, false. public readonly bool Equals(in GameInventoryItem other) { - for (var i = 0; i < StructSizeInBytes / 8; i++) + for (var i = 0; i < InventoryItem.StructSize / 8; i++) { if (this.dataUInt64[i] != other.dataUInt64[i]) return false; @@ -173,7 +270,7 @@ public unsafe struct GameInventoryItem : IEquatable public override int GetHashCode() { var k = 0x5a8447b91aff51b4UL; - for (var i = 0; i < StructSizeInBytes / 8; i++) + for (var i = 0; i < InventoryItem.StructSize / 8; i++) k ^= this.dataUInt64[i]; return unchecked((int)(k ^ (k >> 32))); } diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs index 00c65046f..af124de92 100644 --- a/Dalamud/Game/Inventory/GameInventoryType.cs +++ b/Dalamud/Game/Inventory/GameInventoryType.cs @@ -42,7 +42,12 @@ public enum GameInventoryType : ushort Crystals = 2001, /// - /// Mail container. + /// Item attachments of a letter the player is currently composing. + /// + MailEdit = 2002, + + /// + /// Item attachments of a letter the player is currently reading. /// Mail = 2003, @@ -57,7 +62,7 @@ public enum GameInventoryType : ushort HandIn = 2005, /// - /// DamagedGear container. + /// Repair window container. /// DamagedGear = 2007, @@ -66,6 +71,21 @@ public enum GameInventoryType : ushort ///
Examine = 2009, + /// + /// Estate Possession Retrieval container. + /// + Reclaim = 2010, + + /// + /// Container for items when changing the exterior housing appearance. + /// + HousingExteriorAppearanceEdit = 2011, + + /// + /// Container for items when changing the interior housing appearance. + /// + HousingInteriorAppearanceEdit = 2012, + /// /// Doman Enclave Reconstruction Reclamation Box. /// @@ -93,10 +113,10 @@ public enum GameInventoryType : ushort /// /// Armory waist container. + /// /// /// This container should be unused as belt items were removed from the game in Shadowbringers. /// - ///
ArmoryWaist = 3204, /// diff --git a/Dalamud/Game/Marketboard/MarketBoard.cs b/Dalamud/Game/Marketboard/MarketBoard.cs index 3642b86b8..79de09dea 100644 --- a/Dalamud/Game/Marketboard/MarketBoard.cs +++ b/Dalamud/Game/Marketboard/MarketBoard.cs @@ -1,9 +1,15 @@ -using Dalamud.Game.Network.Internal; +using System.Linq; + +using Dalamud.Game.Network.Internal; using Dalamud.Game.Network.Structures; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using static Dalamud.Plugin.Services.IMarketBoard; + namespace Dalamud.Game.MarketBoard; /// @@ -29,19 +35,19 @@ internal class MarketBoard : IInternalDisposableService, IMarketBoard } /// - public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived; + public event HistoryReceivedDelegate? HistoryReceived; /// - public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased; + public event ItemPurchasedDelegate? ItemPurchased; /// - public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived; + public event OfferingsReceivedDelegate? OfferingsReceived; /// - public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested; + public event PurchaseRequestedDelegate? PurchaseRequested; /// - public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived; + public event TaxRatesReceivedDelegate? TaxRatesReceived; /// public void DisposeService() @@ -89,35 +95,42 @@ internal class MarketBoard : IInternalDisposableService, IMarketBoard #pragma warning restore SA1015 internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoard { + private static readonly ModuleLog Log = new(nameof(MarketBoardPluginScoped)); + [ServiceManager.ServiceDependency] private readonly MarketBoard marketBoardService = Service.Get(); + private readonly string owningPluginName; + /// /// Initializes a new instance of the class. /// - internal MarketBoardPluginScoped() + /// The plugin owning this service. + internal MarketBoardPluginScoped(LocalPlugin? plugin) { this.marketBoardService.HistoryReceived += this.OnHistoryReceived; this.marketBoardService.ItemPurchased += this.OnItemPurchased; this.marketBoardService.OfferingsReceived += this.OnOfferingsReceived; this.marketBoardService.PurchaseRequested += this.OnPurchaseRequested; this.marketBoardService.TaxRatesReceived += this.OnTaxRatesReceived; + + this.owningPluginName = plugin?.InternalName ?? "DalamudInternal"; } /// - public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived; + public event HistoryReceivedDelegate? HistoryReceived; /// - public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased; + public event ItemPurchasedDelegate? ItemPurchased; /// - public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived; + public event OfferingsReceivedDelegate? OfferingsReceived; /// - public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested; + public event PurchaseRequestedDelegate? PurchaseRequested; /// - public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived; + public event TaxRatesReceivedDelegate? TaxRatesReceived; /// void IInternalDisposableService.DisposeService() @@ -137,26 +150,96 @@ internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoar private void OnHistoryReceived(IMarketBoardHistory history) { - this.HistoryReceived?.Invoke(history); + if (this.HistoryReceived == null) return; + + foreach (var action in this.HistoryReceived.GetInvocationList().Cast()) + { + try + { + action.Invoke(history); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.HistoryReceived)); + } + } } private void OnItemPurchased(IMarketBoardPurchase purchase) { - this.ItemPurchased?.Invoke(purchase); + if (this.ItemPurchased == null) return; + + foreach (var action in this.ItemPurchased.GetInvocationList().Cast()) + { + try + { + action.Invoke(purchase); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.ItemPurchased)); + } + } } private void OnOfferingsReceived(IMarketBoardCurrentOfferings currentOfferings) { - this.OfferingsReceived?.Invoke(currentOfferings); + if (this.OfferingsReceived == null) return; + + foreach (var action in this.OfferingsReceived.GetInvocationList() + .Cast()) + { + try + { + action.Invoke(currentOfferings); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.OfferingsReceived)); + } + } } private void OnPurchaseRequested(IMarketBoardPurchaseHandler purchaseHandler) { - this.PurchaseRequested?.Invoke(purchaseHandler); + if (this.PurchaseRequested == null) return; + + foreach (var action in this.PurchaseRequested.GetInvocationList().Cast()) + { + try + { + action.Invoke(purchaseHandler); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.PurchaseRequested)); + } + } } private void OnTaxRatesReceived(IMarketTaxRates taxRates) { - this.TaxRatesReceived?.Invoke(taxRates); + if (this.TaxRatesReceived == null) return; + + foreach (var action in this.TaxRatesReceived.GetInvocationList().Cast()) + { + try + { + action.Invoke(taxRates); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.TaxRatesReceived)); + } + } + } + + private void LogInvocationError(Exception ex, string delegateName) + { + Log.Error( + ex, + "An error occured while invoking event `{evName}` for {plugin}", + delegateName, + this.owningPluginName); } } diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index b51232ece..5022eb93d 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -6,6 +6,9 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Network; + using Serilog; namespace Dalamud.Game.Network; @@ -14,10 +17,10 @@ namespace Dalamud.Game.Network; /// This class handles interacting with game network events. /// [ServiceManager.EarlyLoadedService] -internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork +internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork { private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; + private readonly Hook processZonePacketDownHook; private readonly Hook processZonePacketUpHook; private readonly HitchDetector hitchDetectorUp; @@ -25,11 +28,9 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); - - private IntPtr baseAddress; - + [ServiceManager.ServiceConstructor] - private GameNetwork(TargetSigScanner sigScanner) + private unsafe GameNetwork(TargetSigScanner sigScanner) { this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch); this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch); @@ -37,20 +38,19 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork this.address = new GameNetworkAddressResolver(); this.address.Setup(sigScanner); + var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket; + Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"ProcessZonePacketDown address {Util.DescribeAddress(this.address.ProcessZonePacketDown)}"); + Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}"); Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}"); - this.processZonePacketDownHook = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); + this.processZonePacketDownHook = Hook.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour); this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); this.processZonePacketDownHook.Enable(); this.processZonePacketUpHook.Enable(); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); @@ -64,10 +64,8 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork this.processZonePacketUpHook.Dispose(); } - private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) + private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr) { - this.baseAddress = a; - this.hitchDetectorDown.Start(); // Go back 0x10 to get back to the start of the packet header @@ -78,7 +76,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork // Call events this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10); } catch (Exception ex) { @@ -96,7 +94,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10); } this.hitchDetectorDown.Stop(); diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index fc95bae52..de92f7c10 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -5,11 +5,6 @@ namespace Dalamud.Game.Network; /// internal sealed class GameNetworkAddressResolver : BaseAddressResolver { - /// - /// Gets the address of the ProcessZonePacketDown method. - /// - public IntPtr ProcessZonePacketDown { get; private set; } - /// /// Gets the address of the ProcessZonePacketUp method. /// @@ -18,9 +13,6 @@ internal sealed class GameNetworkAddressResolver : BaseAddressResolver /// protected override void Setup64Bit(ISigScanner sig) { - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); - this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2"); - this.ProcessZonePacketUp = sig.ScanText("E8 ?? ?? ?? ?? 48 83 C4 28 C3 32 C0 48 83 C4 28 C3 CC"); + this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs index dadfef604..adf5f0228 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs @@ -13,20 +13,26 @@ internal interface IMarketBoardUploader /// Upload data about an item. ///
/// The item request data being uploaded. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task Upload(MarketBoardItemRequest item); + Task Upload(MarketBoardItemRequest item, ulong uploaderId, uint worldId); /// /// Upload tax rate data. /// /// The tax rate data being uploaded. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task UploadTax(MarketTaxRates taxRates); + Task UploadTax(MarketTaxRates taxRates, ulong uploaderId, uint worldId); /// /// Upload information about a purchase this client has made. /// /// The purchase handler data associated with the sale. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); + Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler, ulong uploaderId, uint worldId); } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs index 7af647867..a32a92b13 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs @@ -30,6 +30,11 @@ internal class MarketBoardItemRequest ///
public uint AmountToArrive { get; private set; } + /// + /// Gets or sets the offered catalog id used in this listing. + /// + public uint CatalogId { get; internal set; } + /// /// Gets the offered item listings. /// diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs index 72f54773d..2277920a7 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs @@ -48,4 +48,10 @@ internal class UniversalisTaxData ///
[JsonProperty("sharlayan")] public uint Sharlayan { get; set; } + + /// + /// Gets or sets Tuliyollal's current tax rate. + /// + [JsonProperty("tuliyollal")] + public uint Tuliyollal { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 7892e8a04..7ecb9c397 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -33,22 +32,17 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader this.httpClient = happyHttpClient.SharedHttpClient; /// - public async Task Upload(MarketBoardItemRequest request) + public async Task Upload(MarketBoardItemRequest request, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - Log.Verbose("Starting Universalis upload"); - var uploader = clientState.LocalContentId; // ==================================================================================== var uploadObject = new UniversalisItemUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = uploader.ToString(), - ItemId = request.Listings.FirstOrDefault()?.CatalogId ?? 0, + WorldId = worldId, + UploaderId = uploaderId.ToString(), + ItemId = request.CatalogId, Listings = [], Sales = [], }; @@ -102,26 +96,27 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader var uploadPath = "/upload"; var uploadData = JsonConvert.SerializeObject(uploadObject); Log.Verbose("{ListingPath}: {ListingUpload}", uploadPath, uploadData); - await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); + var response = await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); - // ==================================================================================== - - Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.Listings.FirstOrDefault()?.CatalogId ?? 0); + if (response.IsSuccessStatusCode) + { + Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); + } + else + { + var body = await response.Content.ReadAsStringAsync(); + Log.Warning("Universalis data upload for item#{CatalogId} returned status code {StatusCode}.\n" + + " Response Body: {Body}", request.CatalogId, response.StatusCode, body); + } } /// - public async Task UploadTax(MarketTaxRates taxRates) + public async Task UploadTax(MarketTaxRates taxRates, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - - // ==================================================================================== - var taxUploadObject = new UniversalisTaxUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = clientState.LocalContentId.ToString(), + WorldId = worldId, + UploaderId = uploaderId.ToString(), TaxData = new UniversalisTaxData { LimsaLominsa = taxRates.LimsaLominsaTax, @@ -131,6 +126,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader Kugane = taxRates.KuganeTax, Crystarium = taxRates.CrystariumTax, Sharlayan = taxRates.SharlayanTax, + Tuliyollal = taxRates.TuliyollalTax, }, }; @@ -151,14 +147,9 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader /// to track the available listings, that is done via the listings packet. All this does is remove /// a listing, or delete it, when a purchase has been made. /// - public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) + public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - var itemId = purchaseHandler.CatalogId; - var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; // ==================================================================================== @@ -168,7 +159,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader Quantity = purchaseHandler.ItemQuantity, ListingId = purchaseHandler.ListingId.ToString(), RetainerId = purchaseHandler.RetainerId.ToString(), - UploaderId = clientState.LocalContentId.ToString(), + UploaderId = uploaderId.ToString(), }; var deletePath = $"/api/{worldId}/{itemId}/delete"; diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 0e87880ec..c0929fa84 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -15,8 +15,14 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; using Dalamud.Networking.Http; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Network; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; namespace Dalamud.Game.Network.Internal; @@ -35,13 +41,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private readonly NetworkHandlersAddressResolver addressResolver; - private readonly Hook cfPopHook; - private readonly Hook mbPurchaseHook; - private readonly Hook mbHistoryHook; + private readonly Hook cfPopHook; + private readonly Hook mbPurchaseHook; + private readonly Hook mbHistoryHook; private readonly Hook customTalkHook; // used for marketboard taxes - private readonly Hook mbItemRequestStartHook; - private readonly Hook mbOfferingsHook; - private readonly Hook mbSendPurchaseRequestHook; + private readonly Hook mbItemRequestStartHook; + private readonly Hook mbOfferingsHook; + private readonly Hook mbSendPurchaseRequestHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -134,14 +140,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler(); this.mbPurchaseHook = - Hook.FromAddress( - this.addressResolver.MarketBoardPurchasePacketHandler, + Hook.FromAddress( + PacketDispatcher.Addresses.HandleMarketBoardPurchasePacket.Value, this.MarketPurchasePacketDetour); this.mbPurchaseHook.Enable(); this.mbHistoryHook = - Hook.FromAddress( - this.addressResolver.MarketBoardHistoryPacketHandler, + Hook.FromAddress( + InfoProxyItemSearch.Addresses.ProcessItemHistory.Value, this.MarketHistoryPacketDetour); this.mbHistoryHook.Enable(); @@ -151,22 +157,22 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.CustomTalkReceiveResponseDetour); this.customTalkHook.Enable(); - this.mbItemRequestStartHook = Hook.FromAddress( - this.addressResolver.MarketBoardItemRequestStartPacketHandler, + this.mbItemRequestStartHook = Hook.FromAddress( + PacketDispatcher.Addresses.HandleMarketBoardItemRequestStartPacket.Value, this.MarketItemRequestStartDetour); this.mbItemRequestStartHook.Enable(); - this.mbOfferingsHook = Hook.FromAddress( - this.addressResolver.InfoProxyItemSearchAddPage, + this.mbOfferingsHook = Hook.FromAddress( + (nint)InfoProxyItemSearch.StaticVirtualTablePointer->AddPage, this.MarketBoardOfferingsDetour); this.mbOfferingsHook.Enable(); - this.mbSendPurchaseRequestHook = Hook.FromAddress( - this.addressResolver.BuildMarketBoardPurchaseHandlerPacket, + this.mbSendPurchaseRequestHook = Hook.FromAddress( + InfoProxyItemSearch.Addresses.SendPurchaseRequestPacket.Value, this.MarketBoardSendPurchaseRequestDetour); this.mbSendPurchaseRequestHook.Enable(); - this.cfPopHook = Hook.FromAddress(this.addressResolver.CfPopPacketHandler, this.CfPopDetour); + this.cfPopHook = Hook.FromAddress(PublicContentDirector.Addresses.HandleEnterContentInfoPacket.Value, this.CfPopDetour); this.cfPopHook.Enable(); } @@ -183,8 +189,6 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy); - private delegate nint CfPopDelegate(nint packetData); - /// /// Event which gets fired when a duty is ready. /// @@ -263,7 +267,34 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.cfPopHook.Dispose(); } - private unsafe nint CfPopDetour(nint packetData) + private static (ulong UploaderId, uint WorldId) GetUploaderInfo() + { + var agentLobby = AgentLobby.Instance(); + + var uploaderId = agentLobby->LobbyData.ContentId; + if (uploaderId == 0) + { + var playerState = PlayerState.Instance(); + if (playerState->IsLoaded == 1) + { + uploaderId = playerState->ContentId; + } + } + + var worldId = agentLobby->LobbyData.CurrentWorldId; + if (worldId == 0) + { + var localPlayer = Control.GetLocalPlayer(); + if (localPlayer != null) + { + worldId = localPlayer->CurrentWorld; + } + } + + return (uploaderId, worldId); + } + + private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData) { var result = this.cfPopHook.OriginalDisposeSafe(packetData); @@ -282,21 +313,17 @@ internal unsafe class NetworkHandlers : IInternalDisposableService if (this.configuration.DutyFinderTaskbarFlash) Util.FlashWindow(); - var cfConditionSheet = Service.Get().GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); + var cfCondition = LuminaUtils.CreateRef(conditionId); - if (cfCondition == null) + if (!cfCondition.IsValid) { Log.Error("CFC key {ConditionId} not in Lumina data", conditionId); return result; } - var cfcName = cfCondition.Name.ToDalamudString(); + var cfcName = cfCondition.Value.Name.ToDalamudString(); if (cfcName.Payloads.Count == 0) - { cfcName = "Duty Roulette"; - cfCondition.Image = 112324; - } Task.Run(() => { @@ -308,7 +335,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Service.GetNullable()?.Print(b.Build()); } - this.CfPop.InvokeSafely(cfCondition); + this.CfPop.InvokeSafely(cfCondition.Value); }).ContinueWith( task => Log.Error(task.Exception, "CfPop.Invoke failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -371,7 +398,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService })); } - private IObservable> OnMarketBoardSalesBatch( + private IObservable<(uint CatalogId, List Sales)> OnMarketBoardSalesBatch( IObservable start) { var historyObservable = this.MbHistoryObservable.Publish().RefCount(); @@ -394,6 +421,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService // When a start packet is observed, begin observing a window of history packets. // We should only get one packet, which the window closing function ensures. // This packet is flattened to its sale entries and emitted. + uint catalogId = 0; return historyObservable .Do(LogHistoryObserved) .Window(start, UntilBatchEnd) @@ -402,9 +430,12 @@ internal unsafe class NetworkHandlers : IInternalDisposableService new List(), (agg, next) => { + catalogId = next.CatalogId; + agg.AddRange(next.InternalHistoryListings); return agg; - })); + })) + .Select(o => (catalogId, o)); } private IDisposable HandleMarketBoardItemRequest() @@ -415,31 +446,34 @@ internal unsafe class NetworkHandlers : IInternalDisposableService } var startObservable = this.MbItemRequestObservable - .Where(request => request.Ok).Do(LogStartObserved) + .Where(request => request.Ok) + .Do(LogStartObserved) .Publish() .RefCount(); return Observable.When( startObservable .And(this.OnMarketBoardSalesBatch(startObservable)) .And(this.OnMarketBoardListingsBatch(startObservable)) - .Then((request, sales, listings) => (request, sales, listings))) + .Then((request, sales, listings) => (request, sales, listings, GetUploaderInfo()))) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( data => { - var (request, sales, listings) = data; - this.UploadMarketBoardData(request, sales, listings); + var (request, sales, listings, uploaderInfo) = data; + this.UploadMarketBoardData(request, sales, listings, uploaderInfo.UploaderId, uploaderInfo.WorldId); }, ex => Log.Error(ex, "Failed to handle Market Board item request event")); } private void UploadMarketBoardData( MarketBoardItemRequest request, - ICollection sales, - ICollection listings) + (uint CatalogId, ICollection Sales) sales, + ICollection listings, + ulong uploaderId, + uint worldId) { - var catalogId = listings.FirstOrDefault()?.CatalogId ?? 0; + var catalogId = sales.CatalogId; if (listings.Count != request.AmountToArrive) { Log.Error( @@ -452,12 +486,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService "Market Board request resolved, starting upload: item#{CatalogId} listings#{ListingsObserved} sales#{SalesObserved}", catalogId, listings.Count, - sales.Count); + sales.Sales.Count); + request.CatalogId = catalogId; request.Listings.AddRange(listings); - request.History.AddRange(sales); + request.History.AddRange(sales.Sales); - Task.Run(() => this.uploader.Upload(request)) + Task.Run(() => this.uploader.Upload(request, uploaderId, worldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board offerings data upload failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -466,11 +501,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private IDisposable HandleMarketTaxRates() { return this.MbTaxesObservable + .Select((taxes) => (taxes, GetUploaderInfo())) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( - taxes => + data => { + var (taxes, uploaderInfo) = data; + Log.Verbose( "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5} sh#{6}", taxes.LimsaLominsaTax, @@ -481,7 +519,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService taxes.CrystariumTax, taxes.SharlayanTax); - Task.Run(() => this.uploader.UploadTax(taxes)) + Task.Run(() => this.uploader.UploadTax(taxes, uploaderInfo.UploaderId, uploaderInfo.WorldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board tax data upload failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -492,13 +530,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private IDisposable HandleMarketBoardPurchaseHandler() { return this.MbPurchaseSentObservable - .Zip(this.MbPurchaseObservable) + .Zip(this.MbPurchaseObservable, (handler, purchase) => (handler, purchase, GetUploaderInfo())) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( data => { - var (handler, purchase) = data; + var (handler, purchase, uploaderInfo) = data; var sameQty = purchase.ItemQuantity == handler.ItemQuantity; var itemMatch = purchase.CatalogId == handler.CatalogId; @@ -513,7 +551,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService handler.CatalogId, handler.PricePerUnit * purchase.ItemQuantity, handler.ListingId); - Task.Run(() => this.uploader.UploadPurchase(handler)) + Task.Run(() => this.uploader.UploadPurchase(handler, uploaderInfo.UploaderId, uploaderInfo.WorldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board purchase data upload failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -527,7 +565,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService return this.configuration.IsMbCollect; } - private nint MarketPurchasePacketDetour(nint a1, nint packetData) + private void MarketPurchasePacketDetour(uint targetId, nint packetData) { try { @@ -538,10 +576,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketPurchasePacketHandler threw an exception"); } - return this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); + this.mbPurchaseHook.OriginalDisposeSafe(targetId, packetData); } - private nint MarketHistoryPacketDetour(nint a1, nint packetData, uint a3, char a4) + private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData) { try { @@ -552,7 +590,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketHistoryPacketDetour threw an exception"); } - return this.mbHistoryHook.OriginalDisposeSafe(a1, packetData, a3, a4); + this.mbHistoryHook.OriginalDisposeSafe(a1, packetData); } private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount) @@ -571,7 +609,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount); } - private nint MarketItemRequestStartDetour(nint a1, nint packetRef) + private void MarketItemRequestStartDetour(uint targetId, nint packetRef) { try { @@ -582,10 +620,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketItemRequestStartDetour threw an exception"); } - return this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); + this.mbItemRequestStartHook.OriginalDisposeSafe(targetId, packetRef); } - private byte MarketBoardOfferingsDetour(nint a1, nint packetRef) + private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef) { try { @@ -596,10 +634,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketBoardOfferingsDetour threw an exception"); } - return this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); + this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); } - private byte MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) + private bool MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) { try { diff --git a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs index 166b4d67a..18c48b67d 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs @@ -5,62 +5,17 @@ ///
internal class NetworkHandlersAddressResolver : BaseAddressResolver { - /// - /// Gets or sets the pointer to the method responsible for handling CfPop packets. - /// - public nint CfPopPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for handling market board history. In this case, we are - /// sigging the packet handler method directly. - /// - public nint MarketBoardHistoryPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for processing the market board purchase packet. In this - /// case, we are sigging the packet handler method directly. - /// - public nint MarketBoardPurchasePacketHandler { get; set; } - /// /// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data, /// as this isn't really exposed anywhere else. /// public nint CustomTalkEventResponsePacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for the marketboard ItemRequestStart packet. - /// - public nint MarketBoardItemRequestStartPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the InfoProxyItemSearch.AddPage method, used to load market data. - /// - public nint InfoProxyItemSearchAddPage { get; set; } - - /// - /// Gets or sets the pointer to the method inside InfoProxyItemSearch that is responsible for building and sending - /// a purchase request packet. - /// - public nint BuildMarketBoardPurchaseHandlerPacket { get; set; } /// protected override void Setup64Bit(ISigScanner scanner) { - this.CfPopPacketHandler = scanner.ScanText("40 53 57 48 83 EC 78 48 8B D9 48 8D 0D"); - - // TODO: I know this is a CC. I want things working for now. (KW) - this.MarketBoardHistoryPacketHandler = scanner.ScanText( - "40 53 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 85 C0 74 2F 4C 8B 00 48 8B C8 41 FF 90 18 01 00 00 48 8B C8 BA 0B 00 00 00 E8 ?? ?? ?? ?? 48 85 C0 74 10 48 8B D3 48 8B C8 48 83 C4 20 5B E9 ?? ?? ?? ?? 48 83 C4 20 5B C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 40 53"); - this.MarketBoardPurchasePacketHandler = - scanner.ScanText("40 55 56 41 56 48 8B EC 48 83 EC ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 ?? 48 8B 0D ?? ?? ?? ?? 4C 8B F2"); this.CustomTalkEventResponsePacketHandler = - scanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); - this.MarketBoardItemRequestStartPacketHandler = - scanner.ScanText("48 89 5C 24 08 57 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B FA E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 74 4A"); - this.InfoProxyItemSearchAddPage = - scanner.ScanText("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54"); - this.BuildMarketBoardPurchaseHandlerPacket = - scanner.ScanText("40 53 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B D0 48 85 C0 0F 84 ?? ?? ?? ?? 8B 8B"); + scanner.ScanText( + "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); // unnamed in CS } } diff --git a/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs b/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs index 96de1c3b2..fcb46b0c3 100644 --- a/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs +++ b/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; -using Dalamud.Game.ClientState.Resolvers; -using Dalamud.Memory; +using Dalamud.Data; using FFXIVClientStructs.FFXIV.Client.UI.Info; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Network.Structures.InfoProxy; @@ -92,15 +92,15 @@ public unsafe class CharacterData /// /// Gets the applicable statues of the character. /// - public IReadOnlyList> Statuses + public IReadOnlyList> Statuses { get { - var statuses = new List>(); + var statuses = new List>(); for (var i = 0; i < 64; i++) { if ((this.StatusMask & (1UL << i)) != 0) - statuses.Add(new((uint)i)); + statuses.Add(LuminaUtils.CreateRef((uint)i)); } return statuses; @@ -125,22 +125,22 @@ public unsafe class CharacterData /// /// Gets the current world of the character. /// - public ExcelResolver CurrentWorld => new(this.Struct->CurrentWorld); + public RowRef CurrentWorld => LuminaUtils.CreateRef(this.Struct->CurrentWorld); /// /// Gets the home world of the character. /// - public ExcelResolver HomeWorld => new(this.Struct->HomeWorld); + public RowRef HomeWorld => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the location of the character. /// - public ExcelResolver Location => new(this.Struct->Location); + public RowRef Location => LuminaUtils.CreateRef(this.Struct->Location); /// /// Gets the grand company of the character. /// - public ExcelResolver GrandCompany => new((uint)this.Struct->GrandCompany); + public RowRef GrandCompany => LuminaUtils.CreateRef((uint)this.Struct->GrandCompany); /// /// Gets the primary client language of the character. @@ -178,7 +178,7 @@ public unsafe class CharacterData /// /// Gets the job of the character. /// - public ExcelResolver ClassJob => new(this.Struct->Job); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->Job); /// /// Gets the name of the character. diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index ff6a9e327..3422848f3 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using Iced.Intel; using Newtonsoft.Json; @@ -21,8 +23,8 @@ public class SigScanner : IDisposable, ISigScanner { private readonly FileInfo? cacheFile; - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; + private nint moduleCopyPtr; + private nint moduleCopyOffset; private ConcurrentDictionary? textCache; @@ -116,8 +118,8 @@ public class SigScanner : IDisposable, ISigScanner /// The found offset. public static IntPtr Scan(IntPtr baseAddress, int size, string signature) { - var (needle, mask) = ParseSignature(signature); - var index = IndexOf(baseAddress, size, needle, mask); + var (needle, mask, badShift) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask, badShift); if (index < 0) throw new KeyNotFoundException($"Can't find a signature of {signature}"); return baseAddress + index; @@ -274,8 +276,7 @@ public class SigScanner : IDisposable, ISigScanner } } - var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - var scanRet = Scan(mBase, this.TextSectionSize, signature); + var scanRet = Scan(this.TextSectionBase, this.TextSectionSize, signature); if (this.IsCopy) scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); @@ -283,7 +284,15 @@ public class SigScanner : IDisposable, ISigScanner var insnByte = Marshal.ReadByte(scanRet); if (insnByte == 0xE8 || insnByte == 0xE9) + { scanRet = ReadJmpCallSig(scanRet); + var rel = scanRet - this.Module.BaseAddress; + if (rel < 0 || rel >= this.TextSectionSize) + { + throw new KeyNotFoundException( + $"Signature \"{signature}\" resolved to 0x{rel:X} which is outside .text section. Possible signature conflicts?"); + } + } // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it // TODO: THIS IS A HACK! FIX THE ROOT CAUSE! @@ -310,6 +319,32 @@ public class SigScanner : IDisposable, ISigScanner } } + /// + public nint[] ScanAllText(string signature) => this.ScanAllText(signature, default).ToArray(); + + /// + public IEnumerable ScanAllText(string signature, CancellationToken cancellationToken) + { + var (needle, mask, badShift) = ParseSignature(signature); + var mBase = this.TextSectionBase; + var mTo = this.TextSectionBase + this.TextSectionSize; + while (mBase < mTo) + { + cancellationToken.ThrowIfCancellationRequested(); + + var index = IndexOf(mBase, this.TextSectionSize, needle, mask, badShift); + if (index < 0) + break; + + var scanRet = mBase + index; + if (this.IsCopy) + scanRet -= this.moduleCopyOffset; + + yield return scanRet; + mBase = scanRet + 1; + } + } + /// public void Dispose() { @@ -356,7 +391,7 @@ public class SigScanner : IDisposable, ISigScanner return IntPtr.Add(sigLocation, 5 + jumpOffset); } - private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) + private static (byte[] Needle, bool[] Mask, int[] BadShift) ParseSignature(string signature) { signature = signature.Replace(" ", string.Empty); if (signature.Length % 2 != 0) @@ -379,14 +414,13 @@ public class SigScanner : IDisposable, ISigScanner mask[i] = false; } - return (needle, mask); + return (needle, mask, BuildBadCharTable(needle, mask)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) + private static unsafe int IndexOf(nint bufferPtr, int bufferLength, byte[] needle, bool[] mask, int[] badShift) { if (needle.Length > bufferLength) return -1; - var badShift = BuildBadCharTable(needle, mask); var last = needle.Length - 1; var offset = 0; var maxoffset = bufferLength - needle.Length; @@ -485,7 +519,7 @@ public class SigScanner : IDisposable, ISigScanner this.Module.ModuleMemorySize, this.Module.ModuleMemorySize); - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); + this.moduleCopyOffset = this.moduleCopyPtr - this.Module.BaseAddress; } private void Load() diff --git a/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs b/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs new file mode 100644 index 000000000..65567d240 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs @@ -0,0 +1,30 @@ +using Lumina.Text; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// Wraps payloads in an open and close icon, for example the Auto Translation open/close brackets. +/// +internal readonly struct SeStringBuilderIconWrap : IDisposable +{ + private readonly SeStringBuilder builder; + private readonly uint iconClose; + + /// + /// Initializes a new instance of the struct.
+ /// Appends an icon macro with on creation, and an icon macro with + /// on disposal. + ///
+ /// The builder to use. + /// The open icon id. + /// The close icon id. + public SeStringBuilderIconWrap(SeStringBuilder builder, uint iconOpen, uint iconClose) + { + this.builder = builder; + this.iconClose = iconClose; + this.builder.AppendIcon(iconOpen); + } + + /// + public void Dispose() => this.builder.AppendIcon(this.iconClose); +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs b/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs new file mode 100644 index 000000000..a32702f6c --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs @@ -0,0 +1,83 @@ +using System.Globalization; + +using Dalamud.Utility; + +using Lumina.Text; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// A context wrapper used in . +/// +internal readonly ref struct SeStringContext +{ + /// + /// The to append text and macros to. + /// + internal readonly SeStringBuilder Builder; + + /// + /// A list of local parameters. + /// + internal readonly Span LocalParameters; + + /// + /// The target language, used for sheet lookups. + /// + internal readonly ClientLanguage Language; + + /// + /// Initializes a new instance of the struct. + /// + /// The to append text and macros to. + /// A list of local parameters. + /// The target language, used for sheet lookups. + internal SeStringContext(SeStringBuilder builder, Span localParameters, ClientLanguage language) + { + this.Builder = builder; + this.LocalParameters = localParameters; + this.Language = language; + } + + /// + /// Gets the of the current target . + /// + internal CultureInfo CultureInfo => Localization.GetCultureInfoFromLangCode(this.Language.ToCode()); + + /// + /// Tries to get a number from the local parameters at the specified index. + /// + /// The index in the list. + /// The local parameter number. + /// true if the local parameters list contained a parameter at given index, false otherwise. + internal bool TryGetLNum(int index, out uint value) + { + if (index >= 0 && this.LocalParameters.Length > index) + { + value = this.LocalParameters[index].UIntValue; + return true; + } + + value = 0; + return false; + } + + /// + /// Tries to get a string from the local parameters at the specified index. + /// + /// The index in the list. + /// The local parameter string. + /// true if the local parameters list contained a parameter at given index, false otherwise. + internal bool TryGetLStr(int index, out ReadOnlySeString value) + { + if (index >= 0 && this.LocalParameters.Length > index) + { + value = this.LocalParameters[index].StringValue; + return true; + } + + value = default; + return false; + } +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs new file mode 100644 index 000000000..1c1171873 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs @@ -0,0 +1,49 @@ +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// An enum providing additional information about the sheet redirect. +/// +[Flags] +internal enum SheetRedirectFlags +{ + /// + /// No flags. + /// + None = 0, + + /// + /// Resolved to a sheet related with items. + /// + Item = 1, + + /// + /// Resolved to the EventItem sheet. + /// + EventItem = 2, + + /// + /// Resolved to a high quality item. + /// + /// + /// Append Addon#9. + /// + HighQuality = 4, + + /// + /// Resolved to a collectible item. + /// + /// + /// Append Addon#150. + /// + Collectible = 8, + + /// + /// Resolved to a sheet related with actions. + /// + Action = 16, + + /// + /// Resolved to the Action sheet. + /// + ActionSheet = 32, +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs new file mode 100644 index 000000000..f851e7686 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -0,0 +1,233 @@ +using Dalamud.Data; +using Dalamud.Utility; + +using Lumina.Extensions; + +using ItemKind = Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload.ItemKind; +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// A service to resolve sheet redirects in expressions. +/// +[ServiceManager.EarlyLoadedService] +internal class SheetRedirectResolver : IServiceType +{ + private static readonly (string SheetName, uint ColumnIndex, bool ReturnActionSheetFlag)[] ActStrSheets = + [ + (nameof(LSheets.Trait), 0, false), + (nameof(LSheets.Action), 0, true), + (nameof(LSheets.Item), 0, false), + (nameof(LSheets.EventItem), 0, false), + (nameof(LSheets.EventAction), 0, false), + (nameof(LSheets.GeneralAction), 0, false), + (nameof(LSheets.BuddyAction), 0, false), + (nameof(LSheets.MainCommand), 5, false), + (nameof(LSheets.Companion), 0, false), + (nameof(LSheets.CraftAction), 0, false), + (nameof(LSheets.Action), 0, true), + (nameof(LSheets.PetAction), 0, false), + (nameof(LSheets.CompanyAction), 0, false), + (nameof(LSheets.Mount), 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (nameof(LSheets.BgcArmyAction), 1, false), + (nameof(LSheets.Ornament), 8, false), + ]; + + private static readonly string[] ObjStrSheetNames = + [ + nameof(LSheets.BNpcName), + nameof(LSheets.ENpcResident), + nameof(LSheets.Treasure), + nameof(LSheets.Aetheryte), + nameof(LSheets.GatheringPointName), + nameof(LSheets.EObjName), + nameof(LSheets.Mount), + nameof(LSheets.Companion), + string.Empty, + string.Empty, + nameof(LSheets.Item), + ]; + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceConstructor] + private SheetRedirectResolver() + { + } + + /// + /// Resolves the sheet redirect, if any is present. + /// + /// The sheet name. + /// The row id. + /// The column index. Use ushort.MaxValue as default. + /// Flags giving additional information about the redirect. + internal SheetRedirectFlags Resolve(ref string sheetName, ref uint rowId, ref uint colIndex) + { + var flags = SheetRedirectFlags.None; + + switch (sheetName) + { + case nameof(LSheets.Item) or "ItemHQ" or "ItemMP": + { + flags |= SheetRedirectFlags.Item; + + var (itemId, kind) = ItemUtil.GetBaseId(rowId); + + if (kind == ItemKind.Hq || sheetName == "ItemHQ") + { + flags |= SheetRedirectFlags.HighQuality; + } + else if (kind == ItemKind.Collectible || sheetName == "ItemMP") + { + // MP for Masterpiece?! + flags |= SheetRedirectFlags.Collectible; + } + + if (kind == ItemKind.EventItem && + rowId - 2_000_000 <= this.dataManager.GetExcelSheet().Count) + { + flags |= SheetRedirectFlags.EventItem; + sheetName = nameof(LSheets.EventItem); + } + else + { + sheetName = nameof(LSheets.Item); + rowId = itemId; + } + + if (colIndex is >= 4 and <= 7) + return SheetRedirectFlags.None; + + break; + } + + case "ActStr": + { + var returnActionSheetFlag = false; + (var index, rowId) = uint.DivRem(rowId, 1000000); + if (index < ActStrSheets.Length) + (sheetName, colIndex, returnActionSheetFlag) = ActStrSheets[index]; + + if (sheetName != nameof(LSheets.Companion) && colIndex != 13) + flags |= SheetRedirectFlags.Action; + + if (returnActionSheetFlag) + flags |= SheetRedirectFlags.ActionSheet; + + break; + } + + case "ObjStr": + { + (var index, rowId) = uint.DivRem(rowId, 1000000); + if (index < ObjStrSheetNames.Length) + sheetName = ObjStrSheetNames[index]; + + colIndex = 0; + + switch (index) + { + case 0: // BNpcName + if (rowId >= 100000) + rowId += 900000; + break; + + case 1: // ENpcResident + rowId += 1000000; + break; + + case 2: // Treasure + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var treasureRow) && + treasureRow.Unknown0.IsEmpty) + rowId = 0; // defaulting to "Treasure Coffer" + break; + + case 3: // Aetheryte + rowId = this.dataManager.GetExcelSheet() + .TryGetRow(rowId, out var aetheryteRow) && aetheryteRow.IsAetheryte + ? 0u // "Aetheryte" + : 1; // "Aethernet Shard" + break; + + case 5: // EObjName + rowId += 2000000; + break; + } + + break; + } + + case nameof(LSheets.EObj) when colIndex is <= 7 or ushort.MaxValue: + sheetName = nameof(LSheets.EObjName); + break; + + case nameof(LSheets.Treasure) + when this.dataManager.GetExcelSheet().TryGetRow(rowId, out var treasureRow) && + treasureRow.Unknown0.IsEmpty: + rowId = 0; // defaulting to "Treasure Coffer" + break; + + case "WeatherPlaceName": + { + sheetName = nameof(LSheets.PlaceName); + + var placeNameSubId = rowId; + if (this.dataManager.GetExcelSheet().TryGetFirst( + r => r.PlaceNameSub.RowId == placeNameSubId, + out var row)) + rowId = row.PlaceNameParent.RowId; + break; + } + + case nameof(LSheets.InstanceContent) when colIndex == 3: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.ContentFinderCondition.RowId; + break; + } + + case nameof(LSheets.PartyContent) when colIndex == 2: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.ContentFinderCondition.RowId; + break; + } + + case nameof(LSheets.PublicContent) when colIndex == 3: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.ContentFinderCondition.RowId; + break; + } + + case nameof(LSheets.AkatsukiNote): + { + sheetName = nameof(LSheets.AkatsukiNoteString); + colIndex = 0; + + if (this.dataManager.Excel.GetSubrowSheet().TryGetRow(rowId, out var row)) + rowId = (uint)row[0].Unknown2; + break; + } + } + + return flags; + } +} diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs new file mode 100644 index 000000000..83f8e241a --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -0,0 +1,1995 @@ +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.Config; +using Dalamud.Game.Text.Evaluator.Internal; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.IoC; +using Dalamud.IoC.Internal; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Info; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using Lumina.Data.Structs.Excel; +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Extensions; +using Lumina.Text; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using AddonSheet = Lumina.Excel.Sheets.Addon; + +namespace Dalamud.Game.Text.Evaluator; + +#pragma warning disable SeStringEvaluator + +/// +/// Evaluator for SeStrings. +/// +[PluginInterface] +[ServiceManager.EarlyLoadedService] +[ResolveVia] +internal class SeStringEvaluator : IServiceType, ISeStringEvaluator +{ + private static readonly ModuleLog Log = new("SeStringEvaluator"); + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly GameConfig gameConfig = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly NounProcessor nounProcessor = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly SheetRedirectResolver sheetRedirectResolver = Service.Get(); + + private readonly ConcurrentDictionary, string> actStrCache = []; + private readonly ConcurrentDictionary, string> objStrCache = []; + + [ServiceManager.ServiceConstructor] + private SeStringEvaluator() + { + } + + /// + public ReadOnlySeString Evaluate( + ReadOnlySeString str, + Span localParameters = default, + ClientLanguage? language = null) + { + return this.Evaluate(str.AsSpan(), localParameters, language); + } + + /// + public ReadOnlySeString Evaluate( + ReadOnlySeStringSpan str, + Span localParameters = default, + ClientLanguage? language = null) + { + if (str.IsTextOnly()) + return new(str); + + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + // TODO: remove culture info toggling after supporting CultureInfo for SeStringBuilder.Append, + // and then remove try...finally block (discard builder from the pool on exception) + var previousCulture = CultureInfo.CurrentCulture; + var builder = SeStringBuilder.SharedPool.Get(); + try + { + CultureInfo.CurrentCulture = Localization.GetCultureInfoFromLangCode(lang.ToCode()); + return this.EvaluateAndAppendTo(builder, str, localParameters, lang).ToReadOnlySeString(); + } + finally + { + CultureInfo.CurrentCulture = previousCulture; + SeStringBuilder.SharedPool.Return(builder); + } + } + + /// + public ReadOnlySeString EvaluateFromAddon( + uint addonId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(addonId, out var addonRow)) + return default; + + return this.Evaluate(addonRow.Text.AsSpan(), localParameters, lang); + } + + /// + public ReadOnlySeString EvaluateFromLobby( + uint lobbyId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(lobbyId, out var lobbyRow)) + return default; + + return this.Evaluate(lobbyRow.Text.AsSpan(), localParameters, lang); + } + + /// + public ReadOnlySeString EvaluateFromLogMessage( + uint logMessageId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(logMessageId, out var logMessageRow)) + return default; + + return this.Evaluate(logMessageRow.Text.AsSpan(), localParameters, lang); + } + + /// + public string EvaluateActStr(ActionKind actionKind, uint id, ClientLanguage? language = null) => + this.actStrCache.GetOrAdd( + new(actionKind, id, language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage()), + static (key, t) => t.EvaluateFromAddon(2026, [key.Kind.GetActStrId(key.Id)], key.Language) + .ExtractText() + .StripSoftHyphen(), + this); + + /// + public string EvaluateObjStr(ObjectKind objectKind, uint id, ClientLanguage? language = null) => + this.objStrCache.GetOrAdd( + new(objectKind, id, language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage()), + static (key, t) => t.EvaluateFromAddon(2025, [key.Kind.GetObjStrId(key.Id)], key.Language) + .ExtractText() + .StripSoftHyphen(), + this); + + // TODO: move this to MapUtil? + private static uint ConvertRawToMapPos(Lumina.Excel.Sheets.Map map, short offset, float value) + { + var scale = map.SizeFactor / 100.0f; + return (uint)(10 - (int)(((((value + offset) * scale) + 1024f) * -0.2f) / scale)); + } + + private static uint ConvertRawToMapPosX(Lumina.Excel.Sheets.Map map, float x) + => ConvertRawToMapPos(map, map.OffsetX, x); + + private static uint ConvertRawToMapPosY(Lumina.Excel.Sheets.Map map, float y) + => ConvertRawToMapPos(map, map.OffsetY, y); + + private SeStringBuilder EvaluateAndAppendTo( + SeStringBuilder builder, + ReadOnlySeStringSpan str, + Span localParameters, + ClientLanguage language) + { + var context = new SeStringContext(builder, localParameters, language); + + foreach (var payload in str) + { + if (!this.ResolvePayload(in context, payload)) + { + context.Builder.Append(payload); + } + } + + return builder; + } + + private bool ResolvePayload(in SeStringContext context, ReadOnlySePayloadSpan payload) + { + if (payload.Type != ReadOnlySePayloadType.Macro) + return false; + + // if (context.HandlePayload(payload, in context)) + // return true; + + switch (payload.MacroCode) + { + case MacroCode.SetResetTime: + return this.TryResolveSetResetTime(in context, payload); + + case MacroCode.SetTime: + return this.TryResolveSetTime(in context, payload); + + case MacroCode.If: + return this.TryResolveIf(in context, payload); + + case MacroCode.Switch: + return this.TryResolveSwitch(in context, payload); + + case MacroCode.PcName: + return this.TryResolvePcName(in context, payload); + + case MacroCode.IfPcGender: + return this.TryResolveIfPcGender(in context, payload); + + case MacroCode.IfPcName: + return this.TryResolveIfPcName(in context, payload); + + // case MacroCode.Josa: + // case MacroCode.Josaro: + + case MacroCode.IfSelf: + return this.TryResolveIfSelf(in context, payload); + + // case MacroCode.NewLine: // pass through + // case MacroCode.Wait: // pass through + // case MacroCode.Icon: // pass through + + case MacroCode.Color: + return this.TryResolveColor(in context, payload); + + case MacroCode.EdgeColor: + return this.TryResolveEdgeColor(in context, payload); + + case MacroCode.ShadowColor: + return this.TryResolveShadowColor(in context, payload); + + // case MacroCode.SoftHyphen: // pass through + // case MacroCode.Key: + // case MacroCode.Scale: + + case MacroCode.Bold: + return this.TryResolveBold(in context, payload); + + case MacroCode.Italic: + return this.TryResolveItalic(in context, payload); + + // case MacroCode.Edge: + // case MacroCode.Shadow: + // case MacroCode.NonBreakingSpace: // pass through + // case MacroCode.Icon2: // pass through + // case MacroCode.Hyphen: // pass through + + case MacroCode.Num: + return this.TryResolveNum(in context, payload); + + case MacroCode.Hex: + return this.TryResolveHex(in context, payload); + + case MacroCode.Kilo: + return this.TryResolveKilo(in context, payload); + + // case MacroCode.Byte: + + case MacroCode.Sec: + return this.TryResolveSec(in context, payload); + + // case MacroCode.Time: + + case MacroCode.Float: + return this.TryResolveFloat(in context, payload); + + // case MacroCode.Link: // pass through + + case MacroCode.Sheet: + return this.TryResolveSheet(in context, payload); + + case MacroCode.String: + return this.TryResolveString(in context, payload); + + case MacroCode.Caps: + return this.TryResolveCaps(in context, payload); + + case MacroCode.Head: + return this.TryResolveHead(in context, payload); + + case MacroCode.Split: + return this.TryResolveSplit(in context, payload); + + case MacroCode.HeadAll: + return this.TryResolveHeadAll(in context, payload); + + case MacroCode.Fixed: + return this.TryResolveFixed(in context, payload); + + case MacroCode.Lower: + return this.TryResolveLower(in context, payload); + + case MacroCode.JaNoun: + return this.TryResolveNoun(ClientLanguage.Japanese, in context, payload); + + case MacroCode.EnNoun: + return this.TryResolveNoun(ClientLanguage.English, in context, payload); + + case MacroCode.DeNoun: + return this.TryResolveNoun(ClientLanguage.German, in context, payload); + + case MacroCode.FrNoun: + return this.TryResolveNoun(ClientLanguage.French, in context, payload); + + // case MacroCode.ChNoun: + + case MacroCode.LowerHead: + return this.TryResolveLowerHead(in context, payload); + + case MacroCode.ColorType: + return this.TryResolveColorType(in context, payload); + + case MacroCode.EdgeColorType: + return this.TryResolveEdgeColorType(in context, payload); + + // case MacroCode.Ruby: + + case MacroCode.Digit: + return this.TryResolveDigit(in context, payload); + + case MacroCode.Ordinal: + return this.TryResolveOrdinal(in context, payload); + + // case MacroCode.Sound: // pass through + + case MacroCode.LevelPos: + return this.TryResolveLevelPos(in context, payload); + + default: + return false; + } + } + + private unsafe bool TryResolveSetResetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + DateTime date; + + if (payload.TryGetExpression(out var eHour, out var eWeekday) + && this.TryResolveInt(in context, eHour, out var eHourVal) + && this.TryResolveInt(in context, eWeekday, out var eWeekdayVal)) + { + var t = DateTime.UtcNow.AddDays(((eWeekdayVal - (int)DateTime.UtcNow.DayOfWeek) + 7) % 7); + date = new DateTime(t.Year, t.Month, t.Day, eHourVal, 0, 0, DateTimeKind.Utc).ToLocalTime(); + } + else if (payload.TryGetExpression(out eHour) + && this.TryResolveInt(in context, eHour, out eHourVal)) + { + var t = DateTime.UtcNow; + date = new DateTime(t.Year, t.Month, t.Day, eHourVal, 0, 0, DateTimeKind.Utc).ToLocalTime(); + } + else + { + return false; + } + + MacroDecoder.GetMacroTime()->SetTime(date); + + return true; + } + + private unsafe bool TryResolveSetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eTime) || !this.TryResolveUInt(in context, eTime, out var eTimeVal)) + return false; + + var date = DateTimeOffset.FromUnixTimeSeconds(eTimeVal).LocalDateTime; + MacroDecoder.GetMacroTime()->SetTime(date); + + return true; + } + + private bool TryResolveIf(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + return + payload.TryGetExpression(out var eCond, out var eTrue, out var eFalse) + && this.ResolveStringExpression( + context, + this.TryResolveBool(in context, eCond, out var eCondVal) && eCondVal + ? eTrue + : eFalse); + } + + private bool TryResolveSwitch(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var cond = -1; + foreach (var e in payload) + { + switch (cond) + { + case -1: + cond = this.TryResolveUInt(in context, e, out var eVal) ? (int)eVal : 0; + break; + case > 1: + cond--; + break; + default: + return this.ResolveStringExpression(in context, e); + } + } + + return false; + } + + private unsafe bool TryResolvePcName(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + // TODO: handle LogNameType + + NameCache.CharacterInfo characterInfo = default; + if (NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo)) + { + context.Builder.Append((ReadOnlySeStringSpan)characterInfo.Name.AsSpan()); + + if (characterInfo.HomeWorldId != AgentLobby.Instance()->LobbyData.HomeWorldId && + WorldHelper.Instance()->AllWorlds.TryGetValue((ushort)characterInfo.HomeWorldId, out var world, false)) + { + context.Builder.AppendIcon(88); + + if (this.gameConfig.UiConfig.TryGetUInt("LogCrossWorldName", out var logCrossWorldName) && + logCrossWorldName == 1) + context.Builder.Append((ReadOnlySeStringSpan)world.Name); + } + + return true; + } + + // TODO: lookup via InstanceContentCrystallineConflictDirector + // TODO: lookup via MJIManager + + return false; + } + + private unsafe bool TryResolveIfPcGender(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eMale, out var eFemale)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + NameCache.CharacterInfo characterInfo = default; + if (NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo)) + return this.ResolveStringExpression(in context, characterInfo.Sex == 0 ? eMale : eFemale); + + // TODO: lookup via InstanceContentCrystallineConflictDirector + + return false; + } + + private unsafe bool TryResolveIfPcName(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eName, out var eTrue, out var eFalse)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId) || !eName.TryGetString(out var name)) + return false; + + name = this.Evaluate(name, context.LocalParameters, context.Language).AsSpan(); + + NameCache.CharacterInfo characterInfo = default; + return NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo) && + this.ResolveStringExpression( + context, + name.Equals(characterInfo.Name.AsSpan()) + ? eTrue + : eFalse); + } + + private unsafe bool TryResolveIfSelf(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eTrue, out var eFalse)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + // the game uses LocalPlayer here, but using PlayerState seems more safe. + return this.ResolveStringExpression(in context, PlayerState.Instance()->EntityId == entityId ? eTrue : eFalse); + } + + private bool TryResolveColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushColorBgra(eColorVal); + + return true; + } + + private bool TryResolveEdgeColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopEdgeColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushEdgeColorBgra(eColorVal); + + return true; + } + + private bool TryResolveShadowColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopShadowColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushShadowColorBgra(eColorVal); + + return true; + } + + private bool TryResolveBold(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEnable) || + !this.TryResolveBool(in context, eEnable, out var eEnableVal)) + return false; + + context.Builder.AppendSetBold(eEnableVal); + + return true; + } + + private bool TryResolveItalic(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEnable) || + !this.TryResolveBool(in context, eEnable, out var eEnableVal)) + return false; + + context.Builder.AppendSetItalic(eEnableVal); + + return true; + } + + private bool TryResolveNum(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt) || !this.TryResolveInt(in context, eInt, out var eIntVal)) + { + context.Builder.Append('0'); + return true; + } + + context.Builder.Append(eIntVal.ToString()); + + return true; + } + + private bool TryResolveHex(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eUInt) || !this.TryResolveUInt(in context, eUInt, out var eUIntVal)) + { + // TODO: throw? + // ERROR: mismatch parameter type ('' is not numeric) + return false; + } + + context.Builder.Append("0x{0:X08}".Format(eUIntVal)); + + return true; + } + + private bool TryResolveKilo(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt, out var eSep) || + !this.TryResolveInt(in context, eInt, out var eIntVal)) + { + context.Builder.Append('0'); + return true; + } + + if (eIntVal == int.MinValue) + { + // -2147483648 + context.Builder.Append("-2"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("147"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("483"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("648"u8); + return true; + } + + if (eIntVal < 0) + { + context.Builder.Append('-'); + eIntVal = -eIntVal; + } + + if (eIntVal == 0) + { + context.Builder.Append('0'); + return true; + } + + var anyDigitPrinted = false; + for (var i = 1_000_000_000; i > 0; i /= 10) + { + var digit = (eIntVal / i) % 10; + switch (anyDigitPrinted) + { + case false when digit == 0: + continue; + case true when i % 3 == 0: + this.ResolveStringExpression(in context, eSep); + break; + } + + anyDigitPrinted = true; + context.Builder.Append((char)('0' + digit)); + } + + return true; + } + + private bool TryResolveSec(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt) || !this.TryResolveUInt(in context, eInt, out var eIntVal)) + { + // TODO: throw? + // ERROR: mismatch parameter type ('' is not numeric) + return false; + } + + context.Builder.Append("{0:00}".Format(eIntVal)); + return true; + } + + private bool TryResolveFloat(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue, out var eRadix, out var eSeparator) + || !this.TryResolveInt(in context, eValue, out var eValueVal) + || !this.TryResolveInt(in context, eRadix, out var eRadixVal)) + { + return false; + } + + var (integerPart, fractionalPart) = int.DivRem(eValueVal, eRadixVal); + if (fractionalPart < 0) + { + integerPart--; + fractionalPart += eRadixVal; + } + + context.Builder.Append(integerPart.ToString()); + this.ResolveStringExpression(in context, eSeparator); + + // brain fried code + Span fractionalDigits = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + var pos = fractionalDigits.Length - 1; + for (var r = eRadixVal; r > 1; r /= 10) + { + fractionalDigits[pos--] = (byte)('0' + (fractionalPart % 10)); + fractionalPart /= 10; + } + + context.Builder.Append(fractionalDigits[(pos + 1)..]); + + return true; + } + + private bool TryResolveSheet(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSheetNameStr)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eRowIdValue)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eColIndexValue)) + return false; + + var eColParamValue = 0u; + if (enu.MoveNext()) + this.TryResolveUInt(in context, enu.Current, out eColParamValue); + + var resolvedSheetName = this.Evaluate(eSheetNameStr, context.LocalParameters, context.Language).ExtractText(); + + this.sheetRedirectResolver.Resolve(ref resolvedSheetName, ref eRowIdValue, ref eColIndexValue); + + if (string.IsNullOrEmpty(resolvedSheetName)) + return false; + + if (!this.dataManager.Excel.SheetNames.Contains(resolvedSheetName)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language, resolvedSheetName) + .TryGetRow(eRowIdValue, out var row)) + return false; + + if (eColIndexValue >= row.Columns.Count) + return false; + + var column = row.Columns[(int)eColIndexValue]; + switch (column.Type) + { + case ExcelColumnDataType.String: + context.Builder.Append(this.Evaluate(row.ReadString(column.Offset), [eColParamValue], context.Language)); + return true; + case ExcelColumnDataType.Bool: + context.Builder.Append((row.ReadBool(column.Offset) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int8: + context.Builder.Append(row.ReadInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt8: + context.Builder.Append(row.ReadUInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int16: + context.Builder.Append(row.ReadInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt16: + context.Builder.Append(row.ReadUInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int32: + context.Builder.Append(row.ReadInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt32: + context.Builder.Append(row.ReadUInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Float32: + context.Builder.Append(row.ReadFloat32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int64: + context.Builder.Append(row.ReadInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt64: + context.Builder.Append(row.ReadUInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool0: + context.Builder.Append((row.ReadPackedBool(column.Offset, 0) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool1: + context.Builder.Append((row.ReadPackedBool(column.Offset, 1) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool2: + context.Builder.Append((row.ReadPackedBool(column.Offset, 2) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool3: + context.Builder.Append((row.ReadPackedBool(column.Offset, 3) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool4: + context.Builder.Append((row.ReadPackedBool(column.Offset, 4) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool5: + context.Builder.Append((row.ReadPackedBool(column.Offset, 5) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool6: + context.Builder.Append((row.ReadPackedBool(column.Offset, 6) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool7: + context.Builder.Append((row.ReadPackedBool(column.Offset, 7) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + default: + return false; + } + } + + private bool TryResolveString(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + return payload.TryGetExpression(out var eStr) && this.ResolveStringExpression(in context, eStr); + } + + private bool TryResolveCaps(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToUpper(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveHead(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToUpper(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveSplit(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eText, out var eSeparator, out var eIndex)) + return false; + + if (!eSeparator.TryGetString(out var eSeparatorVal) || !eIndex.TryGetUInt(out var eIndexVal) || eIndexVal <= 0) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eText)) + return false; + + var separator = eSeparatorVal.ExtractText(); + if (separator.Length < 1) + return false; + + var splitted = builder.ToReadOnlySeString().ExtractText().Split(separator[0]); + if (eIndexVal <= splitted.Length) + { + context.Builder.Append(splitted[eIndexVal - 1]); + return true; + } + + return false; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveHeadAll(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + + foreach (var p in str) + { + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append( + context.CultureInfo.TextInfo.ToTitleCase(Encoding.UTF8.GetString(p.Body.Span))); + + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveFixed(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + // This is handled by the second function in Client::UI::Misc::PronounModule_ProcessString + + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var e0Val)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var e1Val)) + return false; + + return e0Val switch + { + 100 or 200 => e1Val switch + { + 1 => this.TryResolveFixedPlayerLink(in context, ref enu), + 2 => this.TryResolveFixedClassJobLevel(in context, ref enu), + 3 => this.TryResolveFixedMapLink(in context, ref enu), + 4 => this.TryResolveFixedItemLink(in context, ref enu), + 5 => this.TryResolveFixedChatSoundEffect(in context, ref enu), + 6 => this.TryResolveFixedObjStr(in context, ref enu), + 7 => this.TryResolveFixedString(in context, ref enu), + 8 => this.TryResolveFixedTimeRemaining(in context, ref enu), + // Reads a uint and saves it to PronounModule+0x3AC + // TODO: handle this? looks like it's for the mentor/beginner icon of the player link in novice network + // see "FF 50 50 8B B0" + 9 => true, + 10 => this.TryResolveFixedStatusLink(in context, ref enu), + 11 => this.TryResolveFixedPartyFinderLink(in context, ref enu), + 12 => this.TryResolveFixedQuestLink(in context, ref enu), + _ => false, + }, + _ => this.TryResolveFixedAutoTranslation(in context, payload, e0Val, e1Val), + }; + } + + private unsafe bool TryResolveFixedPlayerLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var worldId)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var playerName)) + return false; + + if (UIGlobals.IsValidPlayerCharacterName(playerName.ExtractText())) + { + var flags = 0u; + if (InfoModule.Instance()->IsInCrossWorldDuty()) + flags |= 0x10; + + context.Builder.PushLink(LinkMacroPayloadType.Character, flags, worldId, 0u, playerName); + context.Builder.Append(playerName); + context.Builder.PopLink(); + } + else + { + context.Builder.Append(playerName); + } + + if (worldId == AgentLobby.Instance()->LobbyData.HomeWorldId) + return true; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow(worldId, out var worldRow)) + return false; + + context.Builder.AppendIcon(88); + context.Builder.Append(worldRow.Name); + + return true; + } + + private bool TryResolveFixedClassJobLevel(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var classJobId) || classJobId <= 0) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var level)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow((uint)classJobId, out var classJobRow)) + return false; + + context.Builder.Append(classJobRow.Name); + + if (level != 0) + context.Builder.Append(context.CultureInfo, $"({level:D})"); + + return true; + } + + private bool TryResolveFixedMapLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var territoryTypeId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var packedIds)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawX)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawY)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawZ)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var placeNameIdInt)) + return false; + + var instance = packedIds >> 0x10; + var mapId = packedIds & 0xFF; + + if (this.dataManager.GetExcelSheet(context.Language) + .TryGetRow(territoryTypeId, out var territoryTypeRow)) + { + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow( + placeNameIdInt == 0 ? territoryTypeRow.PlaceName.RowId : placeNameIdInt, + out var placeNameRow)) + return false; + + if (!this.dataManager.GetExcelSheet().TryGetRow(mapId, out var mapRow)) + return false; + + var sb = SeStringBuilder.SharedPool.Get(); + + sb.Append(placeNameRow.Name); + if (instance is > 0 and <= 9) + sb.Append((char)((char)0xE0B0 + (char)instance)); + + var placeNameWithInstance = sb.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(sb); + + var mapPosX = ConvertRawToMapPosX(mapRow, rawX / 1000f); + var mapPosY = ConvertRawToMapPosY(mapRow, rawY / 1000f); + + var linkText = rawZ == -30000 + ? this.EvaluateFromAddon( + 1635, + [placeNameWithInstance, mapPosX, mapPosY], + context.Language) + : this.EvaluateFromAddon( + 1636, + [placeNameWithInstance, mapPosX, mapPosY, rawZ / (rawZ >= 0 ? 10 : -10), rawZ], + context.Language); + + context.Builder.PushLinkMapPosition(territoryTypeId, mapId, rawX, rawY); + context.Builder.Append(this.EvaluateFromAddon(371, [linkText], context.Language)); + context.Builder.PopLink(); + + return true; + } + + var rowId = mapId switch + { + 0 => 875u, // "(No location set for map link)" + 1 => 874u, // "(Map link unavailable in this area)" + 2 => 13743u, // "(Unable to set map link)" + _ => 0u, + }; + if (rowId == 0u) + return false; + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(rowId, out var addonRow)) + context.Builder.Append(addonRow.Text); + return true; + } + + private bool TryResolveFixedItemLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var itemId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var rarity)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var unk2)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var unk3)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var itemName)) // TODO: unescape?? + return false; + + // rarity color start + context.Builder.Append(this.EvaluateFromAddon(6, [rarity], context.Language)); + + var v2 = (ushort)((unk2 & 0xFF) + (unk3 << 0x10)); // TODO: find out what this does + + context.Builder.PushLink(LinkMacroPayloadType.Item, itemId, rarity, v2); + + // arrow and item name + context.Builder.Append(this.EvaluateFromAddon(371, [itemName], context.Language)); + + context.Builder.PopLink(); + context.Builder.PopColor(); + + return true; + } + + private bool TryResolveFixedChatSoundEffect(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var soundEffectId)) + return false; + + context.Builder.Append($""); + + // the game would play it here + + return true; + } + + private bool TryResolveFixedObjStr(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var objStrId)) + return false; + + context.Builder.Append(this.EvaluateFromAddon(2025, [objStrId], context.Language)); + + return true; + } + + private bool TryResolveFixedString(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !enu.Current.TryGetString(out var text)) + return false; + + // formats it through vsprintf using "%s"?? + context.Builder.Append(text.ExtractText()); + + return true; + } + + private bool TryResolveFixedTimeRemaining(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var seconds)) + return false; + + if (seconds != 0) + { + context.Builder.Append(this.EvaluateFromAddon(33, [seconds / 60, seconds % 60], context.Language)); + } + else + { + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(48, out var addonRow)) + context.Builder.Append(addonRow.Text); + } + + return true; + } + + private bool TryResolveFixedStatusLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var statusId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveBool(in context, enu.Current, out var hasOverride)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow(statusId, out var statusRow)) + return false; + + ReadOnlySeStringSpan statusName; + ReadOnlySeStringSpan statusDescription; + + if (hasOverride) + { + if (!enu.MoveNext() || !enu.Current.TryGetString(out statusName)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out statusDescription)) + return false; + } + else + { + statusName = statusRow.Name.AsSpan(); + statusDescription = statusRow.Description.AsSpan(); + } + + var sb = SeStringBuilder.SharedPool.Get(); + + switch (statusRow.StatusCategory) + { + case 1: + sb.Append(this.EvaluateFromAddon(376, default, context.Language)); + break; + + case 2: + sb.Append(this.EvaluateFromAddon(377, default, context.Language)); + break; + } + + sb.Append(statusName); + + var linkText = sb.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(sb); + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.Status) + .AppendUIntExpression(statusId) + .AppendUIntExpression(0) + .AppendUIntExpression(0) + .AppendStringExpression(statusName) + .AppendStringExpression(statusDescription) + .EndMacro(); + + context.Builder.Append(this.EvaluateFromAddon(371, [linkText], context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedPartyFinderLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var listingId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var unk1)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var worldId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt( + context, + enu.Current, + out var crossWorldFlag)) // 0 = cross world, 1 = not cross world + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var playerName)) + return false; + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.PartyFinder) + .AppendUIntExpression(listingId) + .AppendUIntExpression(unk1) + .AppendUIntExpression((uint)(crossWorldFlag << 0x10) + worldId) + .EndMacro(); + + context.Builder.Append( + this.EvaluateFromAddon( + 371, + [this.EvaluateFromAddon(2265, [playerName, crossWorldFlag], context.Language)], + context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedQuestLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var questId)) + return false; + + if (!enu.MoveNext() || !enu.MoveNext() || !enu.MoveNext()) // unused + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var questName)) + return false; + + /* TODO: hide incomplete, repeatable special event quest names + if (!QuestManager.IsQuestComplete(questId) && !QuestManager.Instance()->IsQuestAccepted(questId)) + { + var questRecompleteManager = QuestRecompleteManager.Instance(); + if (questRecompleteManager == null || !questRecompleteManager->"E8 ?? ?? ?? ?? 0F B6 57 FF"(questId)) { + if (_excelService.TryGetRow(5497, context.Language, out var addonRow)) + questName = addonRow.Text.AsSpan(); + } + } + */ + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.Quest) + .AppendUIntExpression(questId) + .AppendUIntExpression(0) + .AppendUIntExpression(0) + .EndMacro(); + + context.Builder.Append(this.EvaluateFromAddon(371, [questName], context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedAutoTranslation( + in SeStringContext context, in ReadOnlySePayloadSpan payload, int e0Val, int e1Val) + { + // Auto-Translation / Completion + var group = (uint)(e0Val + 1); + var rowId = (uint)e1Val; + + using var icons = new SeStringBuilderIconWrap(context.Builder, 54, 55); + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetFirst( + row => row.Group == group && !row.LookupTable.IsEmpty, + out var groupRow)) + return false; + + var lookupTable = ( + groupRow.LookupTable.IsTextOnly() + ? groupRow.LookupTable + : this.Evaluate( + groupRow.LookupTable.AsSpan(), + context.LocalParameters, + context.Language)).ExtractText(); + + // Completion sheet + if (lookupTable.Equals("@")) + { + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(rowId, out var completionRow)) + { + context.Builder.Append(completionRow.Text); + } + + return true; + } + + // CategoryDataCache + if (lookupTable.Equals("#")) + { + // couldn't find any, so we don't handle them :p + context.Builder.Append(payload); + return false; + } + + // All other sheets + var rangesStart = lookupTable.IndexOf('['); + // Sheet without ranges + if (rangesStart == -1) + { + if (this.dataManager.GetExcelSheet(context.Language, lookupTable).TryGetRow(rowId, out var row)) + { + context.Builder.Append(row.ReadStringColumn(0)); + return true; + } + } + + var sheetName = lookupTable[..rangesStart]; + var ranges = lookupTable[(rangesStart + 1)..^1]; + if (ranges.Length == 0) + return true; + + var isNoun = false; + var col = 0; + + if (ranges.StartsWith("noun")) + { + isNoun = true; + } + else if (ranges.StartsWith("col")) + { + var colRangeEnd = ranges.IndexOf(','); + if (colRangeEnd == -1) + colRangeEnd = ranges.Length; + + col = int.Parse(ranges[4..colRangeEnd]); + } + else if (ranges.StartsWith("tail")) + { + // couldn't find any, so we don't handle them :p + context.Builder.Append(payload); + return false; + } + + if (isNoun && context.Language == ClientLanguage.German && sheetName == "Companion") + { + context.Builder.Append(this.nounProcessor.ProcessNoun(new NounParams() + { + Language = ClientLanguage.German, + SheetName = sheetName, + RowId = rowId, + Quantity = 1, + ArticleType = (int)GermanArticleType.ZeroArticle, + })); + } + else if (this.dataManager.GetExcelSheet(context.Language, sheetName).TryGetRow(rowId, out var row)) + { + context.Builder.Append(row.ReadStringColumn(col)); + } + + return true; + } + + private bool TryResolveLower(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + + foreach (var p in str) + { + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToLower(context.CultureInfo)); + + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveNoun(ClientLanguage language, in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var eAmountVal = 1; + var eCaseVal = 1; + + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSheetNameStr)) + return false; + + var sheetName = this.Evaluate(eSheetNameStr, context.LocalParameters, context.Language).ExtractText(); + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var eArticleTypeVal)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eRowIdVal)) + return false; + + uint colIndex = ushort.MaxValue; + var flags = this.sheetRedirectResolver.Resolve(ref sheetName, ref eRowIdVal, ref colIndex); + + if (string.IsNullOrEmpty(sheetName)) + return false; + + // optional arguments + if (enu.MoveNext()) + { + if (!this.TryResolveInt(in context, enu.Current, out eAmountVal)) + return false; + + if (enu.MoveNext()) + { + if (!this.TryResolveInt(in context, enu.Current, out eCaseVal)) + return false; + + // For Chinese texts? + /* + if (enu.MoveNext()) + { + var eUnkInt5 = enu.Current; + if (!TryResolveInt(context,eUnkInt5, out eUnkInt5Val)) + return false; + } + */ + } + } + + context.Builder.Append( + this.nounProcessor.ProcessNoun(new NounParams() + { + Language = language, + SheetName = sheetName, + RowId = eRowIdVal, + Quantity = eAmountVal, + ArticleType = eArticleTypeVal, + GrammaticalCase = eCaseVal - 1, + IsActionSheet = flags.HasFlag(SheetRedirectFlags.Action), + })); + + return true; + } + + private bool TryResolveLowerHead(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToLower(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColorType) || + !this.TryResolveUInt(in context, eColorType, out var eColorTypeVal)) + return false; + + if (eColorTypeVal == 0) + context.Builder.PopColor(); + else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) + context.Builder.PushColorBgra((row.Dark >> 8) | (row.Dark << 24)); + + return true; + } + + private bool TryResolveEdgeColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColorType) || + !this.TryResolveUInt(in context, eColorType, out var eColorTypeVal)) + return false; + + if (eColorTypeVal == 0) + context.Builder.PopEdgeColor(); + else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) + context.Builder.PushEdgeColorBgra((row.Dark >> 8) | (row.Dark << 24)); + + return true; + } + + private bool TryResolveDigit(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue, out var eTargetLength)) + return false; + + if (!this.TryResolveInt(in context, eValue, out var eValueVal)) + return false; + + if (!this.TryResolveInt(in context, eTargetLength, out var eTargetLengthVal)) + return false; + + context.Builder.Append(eValueVal.ToString(new string('0', eTargetLengthVal))); + + return true; + } + + private bool TryResolveOrdinal(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue) || !this.TryResolveUInt(in context, eValue, out var eValueVal)) + return false; + + // TODO: Culture support? + context.Builder.Append( + $"{eValueVal}{(eValueVal % 10) switch + { + _ when eValueVal is >= 10 and <= 19 => "th", + 1 => "st", + 2 => "nd", + 3 => "rd", + _ => "th", + }}"); + return true; + } + + private bool TryResolveLevelPos(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eLevel) || !this.TryResolveUInt(in context, eLevel, out var eLevelVal)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow(eLevelVal, out var level) || + !level.Map.IsValid) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow( + level.Map.Value.PlaceName.RowId, + out var placeName)) + return false; + + var mapPosX = ConvertRawToMapPosX(level.Map.Value, level.X); + var mapPosY = ConvertRawToMapPosY(level.Map.Value, level.Z); // Z is [sic] + + context.Builder.Append( + this.EvaluateFromAddon( + 1637, + [placeName.Name, mapPosX, mapPosY], + context.Language)); + + return true; + } + + private unsafe bool TryGetGNumDefault(uint parameterIndex, out uint value) + { + value = 0u; + + var rtm = RaptureTextModule.Instance(); + if (rtm is null) + return false; + + ThreadSafety.AssertMainThread("Global parameters may only be used from the main thread."); + + ref var gp = ref rtm->TextModule.MacroDecoder.GlobalParameters; + if (parameterIndex >= gp.MySize) + return false; + + var p = rtm->TextModule.MacroDecoder.GlobalParameters[parameterIndex]; + switch (p.Type) + { + case TextParameterType.Integer: + value = (uint)p.IntValue; + return true; + + case TextParameterType.ReferencedUtf8String: + Log.Error("Requested a number; Utf8String global parameter at {parameterIndex}.", parameterIndex); + return false; + + case TextParameterType.String: + Log.Error("Requested a number; string global parameter at {parameterIndex}.", parameterIndex); + return false; + + case TextParameterType.Uninitialized: + Log.Error("Requested a number; uninitialized global parameter at {parameterIndex}.", parameterIndex); + return false; + + default: + return false; + } + } + + private unsafe bool TryProduceGStrDefault(SeStringBuilder builder, ClientLanguage language, uint parameterIndex) + { + var rtm = RaptureTextModule.Instance(); + if (rtm is null) + return false; + + ref var gp = ref rtm->TextModule.MacroDecoder.GlobalParameters; + if (parameterIndex >= gp.MySize) + return false; + + if (!ThreadSafety.IsMainThread) + { + Log.Error("Global parameters may only be used from the main thread."); + return false; + } + + var p = rtm->TextModule.MacroDecoder.GlobalParameters[parameterIndex]; + switch (p.Type) + { + case TextParameterType.Integer: + builder.Append($"{p.IntValue:D}"); + return true; + + case TextParameterType.ReferencedUtf8String: + this.EvaluateAndAppendTo( + builder, + p.ReferencedUtf8StringValue->Utf8String.AsSpan(), + null, + language); + return false; + + case TextParameterType.String: + this.EvaluateAndAppendTo(builder, p.StringValue.AsSpan(), null, language); + return false; + + case TextParameterType.Uninitialized: + default: + return false; + } + } + + private unsafe bool TryResolveUInt( + in SeStringContext context, in ReadOnlySeExpressionSpan expression, out uint value) + { + if (expression.TryGetUInt(out value)) + return true; + + if (expression.TryGetPlaceholderExpression(out var exprType)) + { + // if (context.TryGetPlaceholderNum(exprType, out value)) + // return true; + + switch ((ExpressionType)exprType) + { + case ExpressionType.Millisecond: + value = (uint)DateTime.Now.Millisecond; + return true; + case ExpressionType.Second: + value = (uint)MacroDecoder.GetMacroTime()->tm_sec; + return true; + case ExpressionType.Minute: + value = (uint)MacroDecoder.GetMacroTime()->tm_min; + return true; + case ExpressionType.Hour: + value = (uint)MacroDecoder.GetMacroTime()->tm_hour; + return true; + case ExpressionType.Day: + value = (uint)MacroDecoder.GetMacroTime()->tm_mday; + return true; + case ExpressionType.Weekday: + value = (uint)MacroDecoder.GetMacroTime()->tm_wday; + return true; + case ExpressionType.Month: + value = (uint)MacroDecoder.GetMacroTime()->tm_mon + 1; + return true; + case ExpressionType.Year: + value = (uint)MacroDecoder.GetMacroTime()->tm_year + 1900; + return true; + default: + return false; + } + } + + if (expression.TryGetParameterExpression(out exprType, out var operand1)) + { + if (!this.TryResolveUInt(in context, operand1, out var paramIndex)) + return false; + if (paramIndex == 0) + return false; + paramIndex--; + return (ExpressionType)exprType switch + { + ExpressionType.LocalNumber => context.TryGetLNum((int)paramIndex, out value), // lnum + ExpressionType.GlobalNumber => this.TryGetGNumDefault(paramIndex, out value), // gnum + _ => false, // gstr, lstr + }; + } + + if (expression.TryGetBinaryExpression(out exprType, out operand1, out var operand2)) + { + switch ((ExpressionType)exprType) + { + case ExpressionType.GreaterThanOrEqualTo: + case ExpressionType.GreaterThan: + case ExpressionType.LessThanOrEqualTo: + case ExpressionType.LessThan: + if (!this.TryResolveInt(in context, operand1, out var value1) + || !this.TryResolveInt(in context, operand2, out var value2)) + { + return false; + } + + value = (ExpressionType)exprType switch + { + ExpressionType.GreaterThanOrEqualTo => value1 >= value2 ? 1u : 0u, + ExpressionType.GreaterThan => value1 > value2 ? 1u : 0u, + ExpressionType.LessThanOrEqualTo => value1 <= value2 ? 1u : 0u, + ExpressionType.LessThan => value1 < value2 ? 1u : 0u, + _ => 0u, + }; + return true; + + case ExpressionType.Equal: + case ExpressionType.NotEqual: + if (this.TryResolveInt(in context, operand1, out value1) && + this.TryResolveInt(in context, operand2, out value2)) + { + if ((ExpressionType)exprType == ExpressionType.Equal) + value = value1 == value2 ? 1u : 0u; + else + value = value1 == value2 ? 0u : 1u; + return true; + } + + if (operand1.TryGetString(out var strval1) && operand2.TryGetString(out var strval2)) + { + var resolvedStr1 = this.EvaluateAndAppendTo( + SeStringBuilder.SharedPool.Get(), + strval1, + context.LocalParameters, + context.Language); + var resolvedStr2 = this.EvaluateAndAppendTo( + SeStringBuilder.SharedPool.Get(), + strval2, + context.LocalParameters, + context.Language); + var equals = resolvedStr1.GetViewAsSpan().SequenceEqual(resolvedStr2.GetViewAsSpan()); + SeStringBuilder.SharedPool.Return(resolvedStr1); + SeStringBuilder.SharedPool.Return(resolvedStr2); + + if ((ExpressionType)exprType == ExpressionType.Equal) + value = equals ? 1u : 0u; + else + value = equals ? 0u : 1u; + return true; + } + + // compare int with string, string with int?? + + return true; + + default: + return false; + } + } + + if (expression.TryGetString(out var str)) + { + var evaluatedStr = this.Evaluate(str, context.LocalParameters, context.Language); + + foreach (var payload in evaluatedStr) + { + if (!payload.TryGetExpression(out var expr)) + return false; + + return this.TryResolveUInt(in context, expr, out value); + } + + return false; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryResolveInt(in SeStringContext context, in ReadOnlySeExpressionSpan expression, out int value) + { + if (this.TryResolveUInt(in context, expression, out var u32)) + { + value = (int)u32; + return true; + } + + value = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryResolveBool(in SeStringContext context, in ReadOnlySeExpressionSpan expression, out bool value) + { + if (this.TryResolveUInt(in context, expression, out var u32)) + { + value = u32 != 0; + return true; + } + + value = false; + return false; + } + + private bool ResolveStringExpression(in SeStringContext context, in ReadOnlySeExpressionSpan expression) + { + uint u32; + + if (expression.TryGetString(out var innerString)) + { + context.Builder.Append(this.Evaluate(innerString, context.LocalParameters, context.Language)); + return true; + } + + /* + if (expression.TryGetPlaceholderExpression(out var exprType)) + { + if (context.TryProducePlaceholder(context,exprType)) + return true; + } + */ + + if (expression.TryGetParameterExpression(out var exprType, out var operand1)) + { + if (!this.TryResolveUInt(in context, operand1, out var paramIndex)) + return false; + if (paramIndex == 0) + return false; + paramIndex--; + switch ((ExpressionType)exprType) + { + case ExpressionType.LocalNumber: // lnum + if (!context.TryGetLNum((int)paramIndex, out u32)) + return false; + + context.Builder.Append(unchecked((int)u32).ToString()); + return true; + + case ExpressionType.LocalString: // lstr + if (!context.TryGetLStr((int)paramIndex, out var str)) + return false; + + context.Builder.Append(str); + return true; + + case ExpressionType.GlobalNumber: // gnum + if (!this.TryGetGNumDefault(paramIndex, out u32)) + return false; + + context.Builder.Append(unchecked((int)u32).ToString()); + return true; + + case ExpressionType.GlobalString: // gstr + return this.TryProduceGStrDefault(context.Builder, context.Language, paramIndex); + + default: + return false; + } + } + + // Handles UInt and Binary expressions + if (!this.TryResolveUInt(in context, expression, out u32)) + return false; + + context.Builder.Append(((int)u32).ToString()); + return true; + } + + private readonly record struct StringCacheKey(TK Kind, uint Id, ClientLanguage Language) + where TK : struct, Enum; +} diff --git a/Dalamud/Game/Text/Evaluator/SeStringParameter.cs b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs new file mode 100644 index 000000000..c1f238f56 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs @@ -0,0 +1,79 @@ +using System.Globalization; + +using Lumina.Text.ReadOnly; + +using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; +using LSeString = Lumina.Text.SeString; + +namespace Dalamud.Game.Text.Evaluator; + +/// +/// A wrapper for a local parameter, holding either a number or a string. +/// +public readonly struct SeStringParameter +{ + private readonly uint num; + private readonly ReadOnlySeString str; + + /// + /// Initializes a new instance of the struct for a number parameter. + /// + /// The number value. + public SeStringParameter(uint value) + { + this.num = value; + } + + /// + /// Initializes a new instance of the struct for a string parameter. + /// + /// The string value. + public SeStringParameter(ReadOnlySeString value) + { + this.str = value; + this.IsString = true; + } + + /// + /// Initializes a new instance of the struct for a string parameter. + /// + /// The string value. + public SeStringParameter(string value) + { + this.str = new ReadOnlySeString(value); + this.IsString = true; + } + + /// + /// Gets a value indicating whether the backing type of this parameter is a string. + /// + public bool IsString { get; } + + /// + /// Gets a numeric value. + /// + public uint UIntValue => + !this.IsString + ? this.num + : uint.TryParse(this.str.ExtractText(), out var value) ? value : 0; + + /// + /// Gets a string value. + /// + public ReadOnlySeString StringValue => + this.IsString ? this.str : new(this.num.ToString("D", CultureInfo.InvariantCulture)); + + public static implicit operator SeStringParameter(int value) => new((uint)value); + + public static implicit operator SeStringParameter(uint value) => new(value); + + public static implicit operator SeStringParameter(ReadOnlySeString value) => new(value); + + public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value)); + + public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData)); + + public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode())); + + public static implicit operator SeStringParameter(string value) => new(value); +} diff --git a/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs b/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs new file mode 100644 index 000000000..9214bea0b --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs @@ -0,0 +1,17 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum EnglishArticleType +{ + /// + /// Indefinite article (a, an). + /// + Indefinite = 1, + + /// + /// Definite article (the). + /// + Definite = 2, +} diff --git a/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs b/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs new file mode 100644 index 000000000..3b6d6a63e --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs @@ -0,0 +1,32 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum FrenchArticleType +{ + /// + /// Indefinite article (une, des). + /// + Indefinite = 1, + + /// + /// Definite article (le, la, les). + /// + Definite = 2, + + /// + /// Possessive article (mon, mes). + /// + PossessiveFirstPerson = 3, + + /// + /// Possessive article (ton, tes). + /// + PossessiveSecondPerson = 4, + + /// + /// Possessive article (son, ses). + /// + PossessiveThirdPerson = 5, +} diff --git a/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs b/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs new file mode 100644 index 000000000..29124e172 --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs @@ -0,0 +1,37 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum GermanArticleType +{ + /// + /// Unbestimmter Artikel (ein, eine, etc.). + /// + Indefinite = 1, + + /// + /// Bestimmter Artikel (der, die, das, etc.). + /// + Definite = 2, + + /// + /// Possessivartikel "dein" (dein, deine, etc.). + /// + Possessive = 3, + + /// + /// Negativartikel "kein" (kein, keine, etc.). + /// + Negative = 4, + + /// + /// Nullartikel. + /// + ZeroArticle = 5, + + /// + /// Demonstrativpronomen "dieser" (dieser, diese, etc.). + /// + Demonstrative = 6, +} diff --git a/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs b/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs new file mode 100644 index 000000000..14a29c4ff --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs @@ -0,0 +1,17 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum JapaneseArticleType +{ + /// + /// Near listener (それら). + /// + NearListener = 1, + + /// + /// Distant from both speaker and listener (あれら). + /// + Distant = 2, +} diff --git a/Dalamud/Game/Text/Noun/NounParams.cs b/Dalamud/Game/Text/Noun/NounParams.cs new file mode 100644 index 000000000..3d5c424be --- /dev/null +++ b/Dalamud/Game/Text/Noun/NounParams.cs @@ -0,0 +1,73 @@ +using Dalamud.Game.Text.Noun.Enums; + +using Lumina.Text.ReadOnly; + +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Noun; + +/// +/// Parameters for noun processing. +/// +internal record struct NounParams() +{ + /// + /// The language of the sheet to be processed. + /// + public required ClientLanguage Language; + + /// + /// The name of the sheet containing the row to process. + /// + public required string SheetName = string.Empty; + + /// + /// The row id within the sheet to process. + /// + public required uint RowId; + + /// + /// The quantity of the entity (default is 1). Used to determine grammatical number (e.g., singular or plural). + /// + public int Quantity = 1; + + /// + /// The article type. + /// + /// + /// Depending on the , this has different meanings.
+ /// See , , , . + ///
+ public int ArticleType = 1; + + /// + /// The grammatical case (e.g., Nominative, Genitive, Dative, Accusative) used for German texts (default is 0). + /// + public int GrammaticalCase = 0; + + /// + /// An optional string that is placed in front of the text that should be linked, such as item names (default is an empty string; the game uses "//"). + /// + public ReadOnlySeString LinkMarker = default; + + /// + /// An indicator that this noun will be processed from an Action sheet. Only used for German texts. + /// + public bool IsActionSheet; + + /// + /// Gets the column offset. + /// + public readonly int ColumnOffset => this.SheetName switch + { + // See "E8 ?? ?? ?? ?? 44 8B 6B 08" + nameof(LSheets.BeastTribe) => 10, + nameof(LSheets.DeepDungeonItem) => 1, + nameof(LSheets.DeepDungeonEquipment) => 1, + nameof(LSheets.DeepDungeonMagicStone) => 1, + nameof(LSheets.DeepDungeonDemiclone) => 1, + nameof(LSheets.Glasses) => 4, + nameof(LSheets.GlassesStyle) => 15, + _ => 0, + }; +} diff --git a/Dalamud/Game/Text/Noun/NounProcessor.cs b/Dalamud/Game/Text/Noun/NounProcessor.cs new file mode 100644 index 000000000..18f8cd4a9 --- /dev/null +++ b/Dalamud/Game/Text/Noun/NounProcessor.cs @@ -0,0 +1,461 @@ +using System.Collections.Concurrent; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Logging.Internal; +using Dalamud.Utility; + +using Lumina.Excel; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Noun; + +/* +Attributive sheet: + Japanese: + Unknown0 = Singular Demonstrative + Unknown1 = Plural Demonstrative + English: + Unknown2 = Article before a singular noun beginning with a consonant sound + Unknown3 = Article before a generic noun beginning with a consonant sound + Unknown4 = N/A + Unknown5 = Article before a singular noun beginning with a vowel sound + Unknown6 = Article before a generic noun beginning with a vowel sound + Unknown7 = N/A + German: + Unknown8 = Nominative Masculine + Unknown9 = Nominative Feminine + Unknown10 = Nominative Neutral + Unknown11 = Nominative Plural + Unknown12 = Genitive Masculine + Unknown13 = Genitive Feminine + Unknown14 = Genitive Neutral + Unknown15 = Genitive Plural + Unknown16 = Dative Masculine + Unknown17 = Dative Feminine + Unknown18 = Dative Neutral + Unknown19 = Dative Plural + Unknown20 = Accusative Masculine + Unknown21 = Accusative Feminine + Unknown22 = Accusative Neutral + Unknown23 = Accusative Plural + French (unsure): + Unknown24 = Singular Article + Unknown25 = Singular Masculine Article + Unknown26 = Plural Masculine Article + Unknown27 = ? + Unknown28 = ? + Unknown29 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown30 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown31 = ? + Unknown32 = ? + Unknown33 = Singular Feminine Article + Unknown34 = Plural Feminine Article + Unknown35 = ? + Unknown36 = ? + Unknown37 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown38 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown39 = ? + Unknown40 = ? + +Placeholders: + [t] = article or grammatical gender (EN: the, DE: der, die, das) + [n] = amount (number) + [a] = declension + [p] = plural + [pa] = ? +*/ + +/// +/// Provides functionality to process texts from sheets containing grammatical placeholders. +/// +[ServiceManager.EarlyLoadedService] +internal class NounProcessor : IServiceType +{ + // column names from ExdSchema, most likely incorrect + private const int SingularColumnIdx = 0; + private const int AdjectiveColumnIdx = 1; + private const int PluralColumnIdx = 2; + private const int PossessivePronounColumnIdx = 3; + private const int StartsWithVowelColumnIdx = 4; + private const int Unknown5ColumnIdx = 5; // probably used in Chinese texts + private const int PronounColumnIdx = 6; + private const int ArticleColumnIdx = 7; + + private static readonly ModuleLog Log = new("NounProcessor"); + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); + + private readonly ConcurrentDictionary cache = []; + + [ServiceManager.ServiceConstructor] + private NounProcessor() + { + } + + /// + /// Processes a specific row from a sheet and generates a formatted string based on grammatical and language-specific rules. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + public ReadOnlySeString ProcessNoun(NounParams nounParams) + { + if (nounParams.GrammaticalCase < 0 || nounParams.GrammaticalCase > 5) + return default; + + if (this.cache.TryGetValue(nounParams, out var value)) + return value; + + var output = nounParams.Language switch + { + ClientLanguage.Japanese => this.ResolveNounJa(nounParams), + ClientLanguage.English => this.ResolveNounEn(nounParams), + ClientLanguage.German => this.ResolveNounDe(nounParams), + ClientLanguage.French => this.ResolveNounFr(nounParams), + _ => default, + }; + + this.cache.TryAdd(nounParams, output); + + return output; + } + + /// + /// Resolves noun placeholders in Japanese text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounJa.Resolve. + /// + private ReadOnlySeString ResolveNounJa(NounParams nounParams) + { + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + + // Ko-So-A-Do + var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0); + if (!ksad.IsEmpty) + { + builder.Append(ksad); + + if (nounParams.Quantity > 1) + { + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + } + } + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset); + if (!text.IsEmpty) + builder.Append(text); + + var ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in English text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounEn.Resolve. + /// + private ReadOnlySeString ResolveNounEn(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = StartsWithVowelColumnIdx + a1->Offsets[3] = PossessivePronounColumnIdx + a1->Offsets[4] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + + var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn; + if (isProperNoun == 0) + { + var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx; + var startsWithVowel = startsWithVowelColumn >= 0 + ? row.ReadInt8Column(startsWithVowelColumn) + : ~startsWithVowelColumn; + + var articleColumn = startsWithVowel + (2 * (startsWithVowel + 1)); + var grammaticalNumberColumnOffset = nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx; + var article = attributiveSheet.GetRow((uint)nounParams.ArticleType) + .ReadStringColumn(articleColumn + grammaticalNumberColumnOffset); + if (!article.IsEmpty) + builder.Append(article); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + } + + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + builder.Append(text); + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + var ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in German text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounDe.Resolve. + /// + private ReadOnlySeString ResolveNounDe(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = PronounColumnIdx + a1->Offsets[3] = AdjectiveColumnIdx + a1->Offsets[4] = PossessivePronounColumnIdx + a1->Offsets[5] = Unknown5ColumnIdx + a1->Offsets[6] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + ReadOnlySeString ross; + + if (nounParams.IsActionSheet) + { + builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase)); + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx; + var genderIndex = genderIndexColumn >= 0 ? row.ReadInt8Column(genderIndexColumn) : ~genderIndexColumn; + + var articleIndexColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var articleIndex = articleIndexColumn >= 0 ? row.ReadInt8Column(articleIndexColumn) : ~articleIndexColumn; + + var caseColumnOffset = (4 * nounParams.GrammaticalCase) + 8; + + var caseRowOffsetColumn = nounParams.ColumnOffset + (nounParams.Quantity == 1 ? AdjectiveColumnIdx : PossessivePronounColumnIdx); + var caseRowOffset = caseRowOffsetColumn >= 0 + ? row.ReadInt8Column(caseRowOffsetColumn) + : (sbyte)~caseRowOffsetColumn; + + if (nounParams.Quantity != 1) + genderIndex = 3; + + var hasT = false; + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + { + hasT = text.ContainsText("[t]"u8); + + if (articleIndex == 0 && !hasT) + { + var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType) + .ReadStringColumn(caseColumnOffset + genderIndex); // Genus + if (!grammaticalGender.IsEmpty) + builder.Append(grammaticalGender); + } + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + builder.Append(text); + + var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26)) + .ReadStringColumn(caseColumnOffset + genderIndex); + if (builder.ContainsText("[p]"u8)) + builder.ReplaceText("[p]"u8, plural); + else + builder.Append(plural); + + if (hasT) + { + var article = + attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel + builder.ReplaceText("[t]"u8, article); + } + } + + var pa = attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex); + builder.ReplaceText("[pa]"u8, pa); + + RawRow declensionRow; + + declensionRow = (GermanArticleType)nounParams.ArticleType switch + { + // Schwache Flexion eines Adjektivs?! + GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25), + _ when hasT => attributiveSheet.GetRow(25), + + // Starke Deklination + GermanArticleType.ZeroArticle => attributiveSheet.GetRow(38), + + // Gemischte Deklination + GermanArticleType.Definite => attributiveSheet.GetRow(37), + + // Starke Flexion eines Artikels?! + GermanArticleType.Indefinite or GermanArticleType.Negative => attributiveSheet.GetRow(26), + _ => attributiveSheet.GetRow(26), + }; + + var declension = declensionRow.ReadStringColumn(caseColumnOffset + genderIndex); + builder.ReplaceText("[a]"u8, declension); + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in French text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounFr.Resolve. + /// + private ReadOnlySeString ResolveNounFr(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = StartsWithVowelColumnIdx + a1->Offsets[3] = PronounColumnIdx + a1->Offsets[4] = Unknown5ColumnIdx + a1->Offsets[5] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + ReadOnlySeString ross; + + var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx; + var startsWithVowel = startsWithVowelColumn >= 0 + ? row.ReadInt8Column(startsWithVowelColumn) + : ~startsWithVowelColumn; + + var pronounColumn = nounParams.ColumnOffset + PronounColumnIdx; + var pronoun = pronounColumn >= 0 ? row.ReadInt8Column(pronounColumn) : ~pronounColumn; + + var articleColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var article = articleColumn >= 0 ? row.ReadInt8Column(articleColumn) : ~articleColumn; + + var v20 = 4 * (startsWithVowel + 6 + (2 * pronoun)); + + if (article != 0) + { + var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20); + if (!v21.IsEmpty) + builder.Append(v21); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + builder.Append(text); + + if (nounParams.Quantity <= 1) + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx); + if (v17 != 0 && (nounParams.Quantity > 1 || v17 == 2)) + { + var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2); + if (!v29.IsEmpty) + { + builder.Append(v29); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx); + if (!text.IsEmpty) + builder.Append(text); + } + } + else + { + var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3)); + if (!v27.IsEmpty) + builder.Append(v27); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx); + if (!text.IsEmpty) + builder.Append(text); + } + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } +} diff --git a/Dalamud/Game/Text/SeIconChar.cs b/Dalamud/Game/Text/SeIconChar.cs index 17924c671..00ffb7342 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -766,4 +766,54 @@ public enum SeIconChar /// The Japanese Eorzea time icon unicode character. ///
EorzeaTimeJa = 0xE0DB, + + /// + /// The boxed, outlined number 0 icon unicode character. + /// + BoxedOutlinedNumber0 = 0xE0E0, + + /// + /// The boxed, outlined number 1 icon unicode character. + /// + BoxedOutlinedNumber1 = 0xE0E1, + + /// + /// The boxed, outlined number 2 icon unicode character. + /// + BoxedOutlinedNumber2 = 0xE0E2, + + /// + /// The boxed, outlined number 3 icon unicode character. + /// + BoxedOutlinedNumber3 = 0xE0E3, + + /// + /// The boxed, outlined number 4 icon unicode character. + /// + BoxedOutlinedNumber4 = 0xE0E4, + + /// + /// The boxed, outlined number 5 icon unicode character. + /// + BoxedOutlinedNumber5 = 0xE0E5, + + /// + /// The boxed, outlined number 6 icon unicode character. + /// + BoxedOutlinedNumber6 = 0xE0E6, + + /// + /// The boxed, outlined number 7 icon unicode character. + /// + BoxedOutlinedNumber7 = 0xE0E7, + + /// + /// The boxed, outlined number 8 icon unicode character. + /// + BoxedOutlinedNumber8 = 0xE0E8, + + /// + /// The boxed, outlined number 9 icon unicode character. + /// + BoxedOutlinedNumber9 = 0xE0E9, } diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index cb87fc343..427e7902a 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -488,7 +488,12 @@ public enum BitmapFontIcon : uint /// /// A blue star icon with an orange exclamation mark. /// - BlueStarProblem = 121, + BlueStarProblem = 122, + + /// + /// The PlayStation Plus icon. + /// + PlayStationPlus = 123, /// /// The Disconnecting icon. @@ -578,12 +583,12 @@ public enum BitmapFontIcon : uint /// /// The Alchemist icon. /// - Alchemist = 131, + Alchemist = 141, /// /// The Culinarian icon. /// - Culinarian = 132, + Culinarian = 142, /// /// The Miner icon. @@ -714,4 +719,69 @@ public enum BitmapFontIcon : uint /// The Waiting For Duty Finder icon. /// WaitingForDutyFinder = 168, + + /// + /// The Tural flag icon. + /// + Tural = 169, + + /// + /// The Viper icon. + /// + Viper = 170, + + /// + /// The Pictomancer icon. + /// + Pictomancer = 171, + + /// + /// The Venture Delivery Moogle icon. + /// + VentureDeliveryMoogle = 172, + + /// + /// The Watching Cutscene icon. + /// + WatchingCutscene = 173, + + /// + /// The Away from Keyboard icon. + /// + Away = 174, + + /// + /// The Camera Mode icon. + /// + CameraMode = 175, + + /// + /// The Looking For Party icon. + /// + LookingForParty = 176, + + /// + /// The Group Finder icon. + /// + GroupFinder = 177, + + /// + /// The Party Leader icon. + /// + PartyLeader = 178, + + /// + /// The Party Member icon. + /// + PartyMember = 179, + + /// + /// The Cross-World Party Leader icon. + /// + CrossWorldPartyLeader = 180, + + /// + /// The Cross-World Party Member icon. + /// + CrossWorldPartyMember = 181, } diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index b876598de..7131a88a7 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -38,13 +38,6 @@ public abstract partial class Payload /// public bool Dirty { get; protected set; } = true; - /// - /// Gets the Lumina instance to use for any necessary data lookups. - /// - [JsonIgnore] - // TODO: We should refactor this. It should not be possible to get IDataManager through here. - protected IDataManager DataResolver => Service.Get(); - /// /// Decodes a binary representation of a payload into its corresponding nice object payload. /// @@ -208,7 +201,7 @@ public abstract partial class Payload case SeStringChunkType.Icon: payload = new IconPayload(); break; - + default: // Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); break; @@ -313,7 +306,7 @@ public abstract partial class Payload /// See the . ///
NewLine = 0x10, - + /// /// See the class. /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index afc1cdc01..b038deb6f 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -2,7 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel.Sheets; +using Lumina.Text.ReadOnly; + using Newtonsoft.Json; using Serilog; @@ -106,28 +110,28 @@ public class AutoTranslatePayload : Payload, ITextProvider this.Key = GetInteger(reader); } + private static ReadOnlySeString ResolveTextCommand(TextCommand command) + { + // TextCommands prioritize the `Alias` field, if it not empty + // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat + return !command.Alias.IsEmpty ? command.Alias : command.Command; + } + private string Resolve() { string value = null; - var sheet = this.DataResolver.GetExcelSheet(); + var excelModule = Service.Get().Excel; + var completionSheet = excelModule.GetSheet(); - Completion row = null; - try - { - // try to get the row in the Completion table itself, because this is 'easiest' - // The row may not exist at all (if the Key is for another table), or it could be the wrong row - // (again, if it's meant for another table) - row = sheet.GetRow(this.Key); - } - catch - { - } // don't care, row will be null + // try to get the row in the Completion table itself, because this is 'easiest' + // The row may not exist at all (if the Key is for another table), or it could be the wrong row + // (again, if it's meant for another table) - if (row?.Group == this.Group) + if (completionSheet.GetRowOrDefault(this.Key) is { } completion && completion.Group == this.Group) { // if the row exists in this table and the group matches, this is actually the correct data - value = row.Text; + value = completion.Text.ExtractText(); } else { @@ -135,34 +139,34 @@ public class AutoTranslatePayload : Payload, ITextProvider { // we need to get the linked table and do the lookup there instead // in this case, there will only be one entry for this group id - row = sheet.First(r => r.Group == this.Group); + var row = completionSheet.First(r => r.Group == this.Group); // many of the names contain valid id ranges after the table name, but we don't need those - var actualTableName = row.LookupTable.RawString.Split('[')[0]; + var actualTableName = row.LookupTable.ExtractText().Split('[')[0]; var name = actualTableName switch { - "Action" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, - "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, - "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Race" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, - "TextCommand" => this.ResolveTextCommand(), - "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, - "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "Action" => excelModule.GetSheet().GetRow(this.Key).Name, + "ActionComboRoute" => excelModule.GetSheet().GetRow(this.Key).Name, + "BuddyAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "ClassJob" => excelModule.GetSheet().GetRow(this.Key).Name, + "Companion" => excelModule.GetSheet().GetRow(this.Key).Singular, + "CraftAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "GeneralAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "GuardianDeity" => excelModule.GetSheet().GetRow(this.Key).Name, + "MainCommand" => excelModule.GetSheet().GetRow(this.Key).Name, + "Mount" => excelModule.GetSheet().GetRow(this.Key).Singular, + "Pet" => excelModule.GetSheet().GetRow(this.Key).Name, + "PetAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "PetMirage" => excelModule.GetSheet().GetRow(this.Key).Name, + "PlaceName" => excelModule.GetSheet().GetRow(this.Key).Name, + "Race" => excelModule.GetSheet().GetRow(this.Key).Masculine, + "TextCommand" => AutoTranslatePayload.ResolveTextCommand(excelModule.GetSheet().GetRow(this.Key)), + "Tribe" => excelModule.GetSheet().GetRow(this.Key).Masculine, + "Weather" => excelModule.GetSheet().GetRow(this.Key).Name, _ => throw new Exception(actualTableName), }; - value = name; + value = name.ExtractText(); } catch (Exception e) { @@ -172,12 +176,4 @@ public class AutoTranslatePayload : Payload, ITextProvider return value; } - - private Lumina.Text.SeString ResolveTextCommand() - { - // TextCommands prioritize the `Alias` field, if it not empty - // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat - var result = this.DataResolver.GetExcelSheet().GetRow(this.Key); - return result.Alias.Payloads.Count > 0 ? result.Alias : result.Command; - } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index d069be30d..8b020b111 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -3,6 +3,8 @@ using System.IO; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; +using Newtonsoft.Json; + namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// @@ -13,32 +15,41 @@ public class DalamudLinkPayload : Payload /// public override PayloadType Type => PayloadType.DalamudLink; - /// - /// Gets the plugin command ID to be linked. - /// - public uint CommandId { get; internal set; } = 0; + /// Gets the plugin command ID to be linked. + public uint CommandId { get; internal set; } - /// - /// Gets the plugin name to be linked. - /// + /// Gets an optional extra integer value 1. + public int Extra1 { get; internal set; } + + /// Gets an optional extra integer value 2. + public int Extra2 { get; internal set; } + + /// Gets the plugin name to be linked. public string Plugin { get; internal set; } = string.Empty; + /// Gets an optional extra string. + public string ExtraString { get; internal set; } = string.Empty; + /// - public override string ToString() - { - return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; - } + public override string ToString() => + $"{this.Type} - {this.Plugin} ({this.CommandId}/{this.Extra1}/{this.Extra2}/{this.ExtraString})"; /// protected override byte[] EncodeImpl() { - return new Lumina.Text.SeStringBuilder() - .BeginMacro(MacroCode.Link) - .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) - .AppendStringExpression(this.Plugin) - .AppendUIntExpression(this.CommandId) - .EndMacro() - .ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb.BeginMacro(MacroCode.Link) + .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) + .AppendUIntExpression(this.CommandId) + .AppendIntExpression(this.Extra1) + .AppendIntExpression(this.Extra2) + .BeginStringExpression() + .Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString })) + .EndExpression() + .EndMacro() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// @@ -49,16 +60,53 @@ public class DalamudLinkPayload : Payload var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan()); - if (!rosps.TryGetExpression(out var pluginExpression, out var commandIdExpression)) - return; + if (!rosps.TryGetExpression( + out var commandIdExpression, + out var extra1Expression, + out var extra2Expression, + out var compositeExpression)) + { + if (!rosps.TryGetExpression(out var pluginExpression, out commandIdExpression)) + return; - if (!pluginExpression.TryGetString(out var pluginString)) - return; + if (!pluginExpression.TryGetString(out var pluginString)) + return; - if (!commandIdExpression.TryGetUInt(out var commandId)) - return; + if (!commandIdExpression.TryGetUInt(out var commandId)) + return; - this.Plugin = pluginString.ExtractText(); - this.CommandId = commandId; + this.Plugin = pluginString.ExtractText(); + this.CommandId = commandId; + } + else + { + if (!commandIdExpression.TryGetUInt(out var commandId)) + return; + + if (!extra1Expression.TryGetInt(out var extra1)) + return; + + if (!extra2Expression.TryGetInt(out var extra2)) + return; + + if (!compositeExpression.TryGetString(out var compositeString)) + return; + + string[] extraData; + try + { + extraData = JsonConvert.DeserializeObject(compositeString.ExtractText()); + } + catch + { + return; + } + + this.CommandId = commandId; + this.Extra1 = extra1; + this.Extra2 = extra2; + this.Plugin = extraData[0]; + this.ExtraString = extraData[1]; + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index e3415b2dc..d6fd897b8 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -3,9 +3,12 @@ using System.IO; using System.Linq; using System.Text; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; +using Dalamud.Utility; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; -using Serilog; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -14,8 +17,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class ItemPayload : Payload { - private Item? item; - // mainly to allow overriding the name (for things like owo) // TODO: even though this is present in some item links, it may not really have a use at all // For things like owo, changing the text payload is probably correct, whereas changing the @@ -74,6 +75,7 @@ public class ItemPayload : Payload /// /// Kinds of items that can be fetched from this payload. /// + [Api12ToDo("Move this out of ItemPayload. It's used in other classes too.")] public enum ItemKind : uint { /// @@ -122,7 +124,7 @@ public class ItemPayload : Payload /// Gets the actual item ID of this payload. /// [JsonIgnore] - public uint ItemId => GetAdjustedId(this.rawItemId).ItemId; + public uint ItemId => ItemUtil.GetBaseId(this.rawItemId).ItemId; /// /// Gets the raw, unadjusted item ID of this payload. @@ -131,27 +133,13 @@ public class ItemPayload : Payload public uint RawItemId => this.rawItemId; /// - /// Gets the underlying Lumina Item represented by this payload. + /// Gets the underlying Lumina data represented by this payload. This is either a Item or EventItem . /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Item? Item - { - get - { - // TODO(goat): This should be revamped/removed on an API level change. - if (this.Kind == ItemKind.EventItem) - { - Log.Warning("Event items cannot be fetched from the ItemPayload"); - return null; - } - - this.item ??= this.DataResolver.GetExcelSheet()!.GetRow(this.ItemId); - return this.item; - } - } + public RowRef Item => + this.Kind == ItemKind.EventItem + ? (RowRef)LuminaUtils.CreateRef(this.ItemId) + : (RowRef)LuminaUtils.CreateRef(this.ItemId); /// /// Gets a value indicating whether or not this item link is for a high-quality version of the item. @@ -176,14 +164,15 @@ public class ItemPayload : Payload /// The created item payload. public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null) { - var (id, kind) = GetAdjustedId(rawItemId); + var (id, kind) = ItemUtil.GetBaseId(rawItemId); return new ItemPayload(id, kind, displayNameOverride); } /// public override string ToString() { - return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}"; + var name = this.displayName ?? (this.Item.GetValueOrDefault()?.Name ?? this.Item.GetValueOrDefault()?.Name)?.ExtractText(); + return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {name}"; } /// @@ -244,7 +233,7 @@ public class ItemPayload : Payload protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.rawItemId = GetInteger(reader); - this.Kind = GetAdjustedId(this.rawItemId).Kind; + this.Kind = ItemUtil.GetBaseId(this.rawItemId).Kind; if (reader.BaseStream.Position + 3 < endOfStream) { @@ -269,15 +258,4 @@ public class ItemPayload : Payload this.displayName = Encoding.UTF8.GetString(itemNameBytes); } } - - private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId) - { - return rawItemId switch - { - > 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible), - > 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq), - > 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted - _ => (rawItemId, ItemKind.Normal), - }; - } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index 7d975b347..7b672d07a 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,11 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class MapLinkPayload : Payload { - private Map map; - private TerritoryType territoryType; - private string placeNameRegion; - private string placeName; - [JsonProperty] private uint territoryTypeId; @@ -38,8 +36,8 @@ public class MapLinkPayload : Payload // this fudge is necessary basically to ensure we don't shift down a full tenth // because essentially values are truncated instead of rounded, so 3.09999f will become // 3.0f and not 3.1f - this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetX); - this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetY); + this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetX); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetY); } /// @@ -72,20 +70,14 @@ public class MapLinkPayload : Payload /// /// Gets the Map specified for this map link. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); + public RowRef Map => LuminaUtils.CreateRef(this.mapId); /// /// Gets the TerritoryType specified for this map link. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); + public RowRef TerritoryType => LuminaUtils.CreateRef(this.territoryTypeId); /// /// Gets the internal x-coordinate for this map position. @@ -102,13 +94,13 @@ public class MapLinkPayload : Payload /// /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. /// - public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor, this.Map.OffsetX); + public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.Value.SizeFactor, this.Map.Value.OffsetX); /// /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. /// [JsonIgnore] - public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor, this.Map.OffsetY); + public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.Value.SizeFactor, this.Map.Value.OffsetY); // there is no Z; it's purely in the text payload where applicable @@ -143,18 +135,18 @@ public class MapLinkPayload : Payload /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". /// [JsonIgnore] - public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; + public string PlaceNameRegion => this.TerritoryType.Value.PlaceNameRegion.Value.Name.ExtractText(); /// /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". /// [JsonIgnore] - public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; + public string PlaceName => this.TerritoryType.Value.PlaceName.Value.Name.ExtractText(); /// /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. /// - public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; + public string DataString => $"m:{this.territoryTypeId},{this.mapId},{this.RawX},{this.RawY}"; /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index b9d6bc896..55697782e 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -2,7 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Text; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -12,8 +18,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class PlayerPayload : Payload { - private World world; - [JsonProperty] private uint serverId; @@ -43,11 +47,8 @@ public class PlayerPayload : Payload /// /// Gets the Lumina object representing the player's home server. /// - /// - /// Value is evaluated lazily and cached. - /// [JsonIgnore] - public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); + public RowRef World => LuminaUtils.CreateRef(this.serverId); /// /// Gets or sets the player's displayed name. This does not contain the server name. @@ -72,7 +73,7 @@ public class PlayerPayload : Payload /// The world name will always be present. /// [JsonIgnore] - public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; + public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.ValueNullable?.Name}"; /// public override PayloadType Type => PayloadType.Player; @@ -80,56 +81,38 @@ public class PlayerPayload : Payload /// public override string ToString() { - return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; + return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.ValueNullable?.Name}"; } /// protected override byte[] EncodeImpl() { - var chunkLen = this.playerName.Length + 7; - var bytes = new List() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, - /* unk */ 0x01, - (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually - /* unk */ 0x01, - /* unk */ 0xFF, // these sometimes vary but are frequently this - (byte)(this.playerName.Length + 1), - }; - - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); - - // TODO: should these really be here? additional payloads should come in separately already... - - // encoded names are followed by the name in plain text again - // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(this.playerName).Encode()); - - // unsure about this entire packet, but it seems to always follow a name - bytes.AddRange(new byte[] - { - START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, - 0x01, 0x01, 0x01, 0xFF, 0x01, - END_BYTE, - }); - - return bytes.ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb + .PushLinkCharacter(this.playerName, this.serverId) + .Append(this.playerName) + .PopLink() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - // unk - reader.ReadByte(); + var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan()); - this.serverId = GetInteger(reader); + if (!rosps.TryGetExpression(out _, out var worldIdExpression, out _, out var characterNameExpression)) + return; - // unk - reader.ReadBytes(2); + if (!worldIdExpression.TryGetUInt(out var worldId)) + return; - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + if (!characterNameExpression.TryGetString(out var characterName)) + return; + + this.serverId = worldId; + this.playerName = characterName.ExtractText(); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs index e5b9e635e..19d494d8a 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class QuestPayload : Payload { - private Quest quest; - [JsonProperty] private uint questId; @@ -40,16 +41,13 @@ public class QuestPayload : Payload /// /// Gets the underlying Lumina Quest represented by this payload. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); + public RowRef Quest => LuminaUtils.CreateRef(this.questId); /// public override string ToString() { - return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; + return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest.ValueNullable?.Name.ExtractText() ?? "QUEST NOT FOUND"}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs index 3e10f7659..d102dfab6 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; ///
public class StatusPayload : Payload { - private Status status; - [JsonProperty] private uint statusId; @@ -40,16 +41,13 @@ public class StatusPayload : Payload /// /// Gets the Lumina Status object represented by this payload. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); + public RowRef Status => LuminaUtils.CreateRef(this.statusId); /// public override string ToString() { - return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; + return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.ValueNullable?.Name}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 1cd96a81a..8443e06ce 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -1,18 +1,20 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// -/// An SeString Payload representing a UI foreground color applied to following text payloads. +/// An SeString Payload that allows text to have a specific color. The color selected will be determined by the +/// theme's coloring, regardless of the active theme. /// public class UIForegroundPayload : Payload { - private UIColor color; - [JsonProperty] private ushort colorKey; @@ -51,11 +53,8 @@ public class UIForegroundPayload : Payload /// /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + public RowRef UIColor => LuminaUtils.CreateRef(this.colorKey); /// /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. @@ -63,15 +62,11 @@ public class UIForegroundPayload : Payload [JsonIgnore] public ushort ColorKey { - get - { - return this.colorKey; - } + get => this.colorKey; set { this.colorKey = value; - this.color = null; this.Dirty = true; } } @@ -80,13 +75,13 @@ public class UIForegroundPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this foreground color, encoded as a typical hex color. /// [JsonIgnore] - public uint RGBA => this.UIColor.UIForeground; + public uint RGBA => this.UIColor.Value.Dark; /// /// Gets the ABGR value for this foreground color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIForeground); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Dark); /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 68ed983ff..d22318378 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -1,18 +1,20 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// -/// An SeString Payload representing a UI glow color applied to following text payloads. +/// An SeString Payload that allows text to have a specific edge glow. The color selected will be determined by the +/// theme's coloring, regardless of the active theme. /// public class UIGlowPayload : Payload { - private UIColor color; - [JsonProperty] private ushort colorKey; @@ -57,7 +59,6 @@ public class UIGlowPayload : Payload set { this.colorKey = value; - this.color = null; this.Dirty = true; } } @@ -71,22 +72,19 @@ public class UIGlowPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this glow color, encoded as a typical hex color. ///
[JsonIgnore] - public uint RGBA => this.UIColor.UIGlow; + public uint RGBA => this.UIColor.Value.Light; /// /// Gets the ABGR value for this glow color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIGlow); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Light); /// /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + public RowRef UIColor => LuminaUtils.CreateRef(this.colorKey); /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index 3b83aed0c..b7618305a 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -5,11 +5,16 @@ using System.Runtime.InteropServices; using System.Text; using Dalamud.Data; +using Dalamud.Game.Text.Evaluator; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Utility; -using Lumina.Excel.GeneratedSheets; + +using Lumina.Excel.Sheets; + using Newtonsoft.Json; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + namespace Dalamud.Game.Text.SeStringHandling; /// @@ -133,9 +138,7 @@ public class SeString { while (stream.Position < len) { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); + payloads.Add(Payload.Decode(reader)); } } @@ -189,57 +192,32 @@ public class SeString /// An SeString containing all the payloads necessary to display an item link in the chat log. public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null) { - var data = Service.Get(); + var clientState = Service.Get(); + var seStringEvaluator = Service.Get(); - var displayName = displayNameOverride; - var rarity = 1; // default: white - if (displayName == null) - { - switch (kind) - { - case ItemPayload.ItemKind.Normal: - case ItemPayload.ItemKind.Collectible: - case ItemPayload.ItemKind.Hq: - var item = data.GetExcelSheet()?.GetRow(itemId); - displayName = item?.Name; - rarity = item?.Rarity ?? 1; - break; - case ItemPayload.ItemKind.EventItem: - displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; - break; - default: - throw new ArgumentOutOfRangeException(nameof(kind), kind, null); - } - } + var rawId = ItemUtil.GetRawId(itemId, kind); - if (displayName == null) - { + var displayName = displayNameOverride ?? ItemUtil.GetItemName(rawId); + if (displayName.IsEmpty) throw new Exception("Invalid item ID specified, could not determine item name."); - } - if (kind == ItemPayload.ItemKind.Hq) - { - displayName += $" {(char)SeIconChar.HighQuality}"; - } - else if (kind == ItemPayload.ItemKind.Collectible) - { - displayName += $" {(char)SeIconChar.Collectible}"; - } + var copyName = ItemUtil.GetItemName(rawId, false).ExtractText(); + var textColor = ItemUtil.GetItemRarityColorType(rawId); + var textEdgeColor = textColor + 1u; - var textColor = (ushort)(549 + ((rarity - 1) * 2)); - var textGlowColor = (ushort)(textColor + 1); + var sb = LSeStringBuilder.SharedPool.Get(); + var itemLink = sb + .PushColorType(textColor) + .PushEdgeColorType(textEdgeColor) + .PushLinkItem(rawId, copyName) + .Append(displayName) + .PopLink() + .PopEdgeColorType() + .PopColorType() + .ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(sb); - // Note: `SeStringBuilder.AddItemLink` uses this function, so don't call it here! - return new SeStringBuilder() - .AddUiForeground(textColor) - .AddUiGlow(textGlowColor) - .Add(new ItemPayload(itemId, kind)) - .Append(TextArrowPayloads) - .AddText(displayName) - .AddUiGlowOff() - .AddUiForegroundOff() - .Add(RawPayload.LinkTerminator) - .Build(); + return SeString.Parse(seStringEvaluator.EvaluateFromAddon(371, [itemLink], clientState.ClientLanguage)); } /// @@ -251,7 +229,7 @@ public class SeString /// An SeString containing all the payloads necessary to display an item link in the chat log. public static SeString CreateItemLink(Item item, bool isHq, string? displayNameOverride = null) { - return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); + return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name.ExtractText()); } /// @@ -303,7 +281,7 @@ public class SeString public static SeString CreateMapLink( uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => CreateMapLinkWithInstance(territoryId, mapId, null, xCoord, yCoord, fudgeFactor); - + /// /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. /// @@ -342,7 +320,7 @@ public class SeString /// An SeString containing all of the payloads necessary to display a map link in the chat log. public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => CreateMapLinkWithInstance(placeName, null, xCoord, yCoord, fudgeFactor); - + /// /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. /// Returns null if no corresponding PlaceName was found. @@ -360,15 +338,14 @@ public class SeString var mapSheet = data.GetExcelSheet(); var matches = data.GetExcelSheet() - .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) - .ToArray(); + .Where(row => row.Name.ExtractText().Equals(placeName, StringComparison.InvariantCultureIgnoreCase)); foreach (var place in matches) { - var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); - if (map != null && map.TerritoryType.Row != 0) + var map = mapSheet.Cast().FirstOrDefault(row => row!.Value.PlaceName.RowId == place.RowId); + if (map.HasValue && map.Value.TerritoryType.RowId != 0) { - return CreateMapLinkWithInstance(map.TerritoryType.Row, map.RowId, instance, xCoord, yCoord, fudgeFactor); + return CreateMapLinkWithInstance(map.Value.TerritoryType.RowId, map.Value.RowId, instance, xCoord, yCoord, fudgeFactor); } } @@ -514,7 +491,7 @@ public class SeString { messageBytes.AddRange(p.Encode()); } - + // Add Null Terminator messageBytes.Add(0); @@ -529,7 +506,7 @@ public class SeString { return this.TextValue; } - + private static string GetMapLinkNameString(string placeName, int? instance, string coordinateString) { var instanceString = string.Empty; @@ -537,7 +514,7 @@ public class SeString { instanceString = (SeIconChar.Instance1 + instance.Value - 1).ToIconString(); } - + return $"{placeName}{instanceString} {coordinateString}"; } } diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index 25f752054..d4ec751f3 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -10,7 +10,7 @@ public sealed class XivChatEntry /// /// Gets or sets the type of entry. /// - public XivChatType Type { get; set; } = XivChatType.Debug; + public XivChatType? Type { get; set; } /// /// Gets or sets the message timestamp. @@ -20,12 +20,30 @@ public sealed class XivChatEntry /// /// Gets or sets the sender name. /// - public SeString Name { get; set; } = string.Empty; + public SeString Name + { + get => SeString.Parse(this.NameBytes); + set => this.NameBytes = value.Encode(); + } /// /// Gets or sets the message. /// - public SeString Message { get; set; } = string.Empty; + public SeString Message + { + get => SeString.Parse(this.MessageBytes); + set => this.MessageBytes = value.Encode(); + } + + /// + /// Gets or sets the name payloads. + /// + public byte[] NameBytes { get; set; } = []; + + /// + /// Gets or sets the message payloads. + /// + public byte[] MessageBytes { get; set; } = []; /// /// Gets or sets a value indicating whether new message sounds should be silenced or not. diff --git a/Dalamud/Game/Text/XivChatTypeExtensions.cs b/Dalamud/Game/Text/XivChatTypeExtensions.cs index 3bdeb1525..457d3b145 100644 --- a/Dalamud/Game/Text/XivChatTypeExtensions.cs +++ b/Dalamud/Game/Text/XivChatTypeExtensions.cs @@ -12,7 +12,7 @@ public static class XivChatTypeExtensions /// /// The chat type. /// The info attribute. - public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) + public static XivChatTypeInfoAttribute? GetDetails(this XivChatType chatType) { return chatType.GetAttribute(); } diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 4ee96bb15..f1ed7fd11 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -20,6 +20,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook private bool isEnabled = false; private DynamicMethod statsMethod; + + private Guid hookId = Guid.NewGuid(); /// /// Initializes a new instance of the class. @@ -44,7 +46,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -70,7 +72,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -116,6 +118,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.IsDisposed = true; + HookManager.TrackedHooks.TryRemove(this.hookId, out _); + if (this.isEnabled) { this.isEnabled = false; diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index da65fedc7..20796bbf0 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -71,6 +71,11 @@ public abstract class Hook : IDalamudHook where T : Delegate /// public virtual string BackendName => throw new NotImplementedException(); + /// + /// Gets the unique GUID for this hook. + /// + protected Guid HookId { get; } = Guid.NewGuid(); + /// /// Remove a hook from the current process. /// diff --git a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs index 7a177206f..40a33fc1b 100644 --- a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs +++ b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs @@ -100,7 +100,7 @@ internal class FunctionPointerVariableHook : Hook unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -137,6 +137,8 @@ internal class FunctionPointerVariableHook : Hook this.Disable(); + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); HookManager.MultiHookTracker[this.Address][index] = null; diff --git a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs index f7c4db00a..52d266335 100644 --- a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs +++ b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Diagnostics; using System.Linq; using Dalamud.Game; @@ -7,6 +6,7 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Dalamud.Utility.Signatures; using Serilog; @@ -25,7 +25,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal private readonly LocalPlugin plugin; private readonly SigScanner scanner; - private readonly ConcurrentBag trackedHooks = new(); + private readonly WeakConcurrentCollection trackedHooks = new(); /// /// Initializes a new instance of the class. diff --git a/Dalamud/Hooking/Internal/MinHookHook.cs b/Dalamud/Hooking/Internal/MinHookHook.cs index 4cebca0d0..0305f3c84 100644 --- a/Dalamud/Hooking/Internal/MinHookHook.cs +++ b/Dalamud/Hooking/Internal/MinHookHook.cs @@ -35,7 +35,7 @@ internal class MinHookHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -76,6 +76,8 @@ internal class MinHookHook : Hook where T : Delegate HookManager.MultiHookTracker[this.Address][index] = null; } + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + base.Dispose(); } diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index bf441a86d..2b0a4e9ce 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -30,7 +30,7 @@ internal class ReloadedHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -63,6 +63,8 @@ internal class ReloadedHook : Hook where T : Delegate if (this.IsDisposed) return; + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + this.Disable(); base.Dispose(); diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index edab25149..8191487f4 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,6 +1,8 @@ using System.Diagnostics; using System.Numerics; +using Dalamud.Utility; + namespace Dalamud.Interface.Animation; /// @@ -43,9 +45,22 @@ public abstract class Easing public bool IsInverse { get; set; } /// - /// Gets or sets the current value of the animation, from 0 to 1. + /// Gets the current value of the animation, following unclamped logic. /// - public double Value + [Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)] + [Api13ToDo("Map this field to ValueClamped, probably.")] + public double Value => this.ValueUnclamped; + + /// + /// Gets the current value of the animation, from 0 to 1. + /// + public double ValueClamped => Math.Clamp(this.ValueUnclamped, 0, 1); + + /// + /// Gets or sets the current value of the animation, not limited to a range of 0 to 1. + /// Will return numbers outside of this range if accessed beyond animation time. + /// + public double ValueUnclamped { get { diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index c467104c5..d94e9fc9f 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -19,6 +19,6 @@ public class InCirc : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + this.ValueUnclamped = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index 78f6774ac..64ebc5ba3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -19,6 +19,6 @@ public class InCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p; + this.ValueUnclamped = p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index c53c3d587..2e834e41c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -21,10 +21,10 @@ public class InElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index 71a598dfb..a63ab648a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -19,8 +19,8 @@ public class InOutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + this.ValueUnclamped = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index 07bcfa28d..4083265b7 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -19,6 +19,6 @@ public class InOutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + this.ValueUnclamped = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index f78f9f336..f27726038 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -21,12 +21,12 @@ public class InOutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index 64ab98b16..e08129b25 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -19,6 +19,6 @@ public class InOutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + this.ValueUnclamped = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index 2f347ff80..cb940d87d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -19,6 +19,6 @@ public class InOutSine : Easing public override void Update() { var p = this.Progress; - this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; + this.ValueUnclamped = -(Math.Cos(Math.PI * p) - 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index a5ab5a22c..827e0e21b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -19,6 +19,6 @@ public class InQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p * p * p; + this.ValueUnclamped = p * p * p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index fa079baad..61affa10a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -19,6 +19,6 @@ public class InSine : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Cos((p * Math.PI) / 2); + this.ValueUnclamped = 1 - Math.Cos((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index b0d3b895a..980e29a81 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -19,6 +19,6 @@ public class OutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + this.ValueUnclamped = Math.Sqrt(1 - Math.Pow(p - 1, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index 9c1bb57dc..e1a79c35b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -19,6 +19,6 @@ public class OutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 3); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 3); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 6a4fcd6dc..1f525b404 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -21,10 +21,10 @@ public class OutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index a3174e762..24a2255d3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -19,6 +19,6 @@ public class OutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 5); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 5); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index ba82232b3..a376d7f57 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -19,6 +19,6 @@ public class OutSine : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sin((p * Math.PI) / 2); + this.ValueUnclamped = Math.Sin((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/ColorHelpers.cs b/Dalamud/Interface/ColorHelpers.cs index 318805529..a3ae6799e 100644 --- a/Dalamud/Interface/ColorHelpers.cs +++ b/Dalamud/Interface/ColorHelpers.cs @@ -2,6 +2,7 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Numerics; +using System.Runtime.CompilerServices; namespace Dalamud.Interface; @@ -247,6 +248,20 @@ public static class ColorHelpers public static uint Desaturate(uint color, float amount) => RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount)); + /// Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value. + /// RGBA value to transform. + /// Opacity to apply. + /// Transformed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ApplyOpacity(uint rgba, float opacity) => + ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu); + + /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). + /// Color to process. + /// Swapped color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); + /// /// Fade a color. /// diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs index aa707aecb..e2f68eab2 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -41,7 +43,9 @@ public static partial class ImGuiComponents ImGui.OpenPopup($"###ColorPickerPopup{id}"); } - if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) + using var popup = ImRaii.Popup($"###ColorPickerPopup{id}"); + + if (popup) { if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) { @@ -61,8 +65,6 @@ public static partial class ImGuiComponents ImGui.SameLine(); } } - - ImGui.EndPopup(); } return selectedColor; diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs index 907ad0aeb..ab2ed4724 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs @@ -1,5 +1,7 @@ using System.Numerics; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -21,17 +23,16 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { - ImGui.PushFont(UiBuilder.IconFont); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var text = icon.ToIconString(); + if (id.HasValue) + { + text = $"{text}##{id}"; + } - var text = icon.ToIconString(); - if (id.HasValue) - text = $"{text}##{id}"; - - var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); - - ImGui.PopFont(); - - return button; + return DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); + } } /// @@ -45,31 +46,28 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { + using var col = new ImRaii.Color(); + if (defaultColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + { + col.Push(ImGuiCol.Button, defaultColor.Value); + } if (activeColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + { + col.Push(ImGuiCol.ButtonActive, activeColor.Value); + } if (hoveredColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + { + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } var style = ImGui.GetStyle(); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); - var button = ImGui.Button(labelWithId); - - ImGui.PopStyleVar(); - - if (defaultColor.HasValue) - ImGui.PopStyleColor(); - - if (activeColor.HasValue) - ImGui.PopStyleColor(); - - if (hoveredColor.HasValue) - ImGui.PopStyleColor(); - - return button; + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult)) + { + return ImGui.Button(labelWithId); + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index f0ecf2f2f..3392136d1 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,3 +1,7 @@ +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Common.Math; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -18,17 +22,32 @@ public static partial class ImGuiComponents /// /// The text to display on hover. /// The icon to use. - public static void HelpMarker(string helpText, FontAwesomeIcon icon) + /// The color of the icon. + public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) { + using var col = new ImRaii.Color(); + + if (color.HasValue) + { + col.Push(ImGuiCol.TextDisabled, color.Value); + } + ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.TextDisabled(icon.ToIconString()); - ImGui.PopFont(); - if (!ImGui.IsItemHovered()) return; - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextDisabled(icon.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f)) + { + ImGui.TextUnformatted(helpText); + } + } + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index dc2c99608..3e61e16bb 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -15,8 +17,26 @@ public static partial class ImGuiComponents /// /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon) - => IconButton(icon, null, null, null); + public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector2 size) => IconButton(icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -24,8 +44,28 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(int id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector2 size) => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -33,51 +73,45 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(string id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector2 size) + => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) + => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. /// /// Text already containing the icon string. /// Indicator if button is clicked. - public static bool IconButton(string iconText) - => IconButton(iconText, null, null, null); + public static bool IconButton(string iconText) => IconButton(iconText, null); /// /// IconButton component to use an icon as a button. /// - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. + /// Text already containing the icon string. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); + public static bool IconButton(string iconText, Vector2 size) => IconButton(iconText, null, null, null, size); /// /// IconButton component to use an icon as a button with color options. @@ -86,62 +120,75 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } + + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; } var icon = iconText; - if (icon.Contains("#")) - icon = icon[..icon.IndexOf("#", StringComparison.Ordinal)]; + if (icon.Contains('#')) + { + icon = icon[..icon.IndexOf('#', StringComparison.Ordinal)]; + } - ImGui.PushID(iconText); + bool button; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon); - ImGui.PopFont(); - - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); - ImGui.PopFont(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var iconSize = ImGui.CalcTextSize(icon); + var cursor = ImGui.GetCursorScreenPos(); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + var buttonSize = new Vector2(width, height); + + using (ImRaii.PushId(iconText)) + { + button = ImGui.Button(string.Empty, buttonSize); + } + + var iconPos = cursor + ((buttonSize - iconSize) / 2f); + + ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); + } return button; } + /// + /// IconButton component to use an icon as a button with color options. + /// + /// Icon to show. + /// Text to show. + /// + /// Sets the size of the button. If either dimension is set to 0, + /// that dimension will conform to the size of the icon and text. + /// + /// Indicator if button is clicked. + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size); + /// /// IconButton component to use an icon as a button with color options. /// @@ -150,61 +197,75 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// + /// Sets the size of the button. If either dimension is set to 0, + /// that dimension will conform to the size of the icon and text. + /// /// Indicator if button is clicked. - public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); } - ImGui.PushID(text); + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; + } + + bool button; + + Vector2 iconSize; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + + var textStr = text; + if (textStr.Contains('#')) + { + textStr = textStr[..textStr.IndexOf('#', StringComparison.Ordinal)]; + } + + var framePadding = ImGui.GetStyle().FramePadding; + var iconPadding = 3 * ImGuiHelpers.GlobalScale; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); var cursor = ImGui.GetCursorScreenPos(); - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); - ImGui.PopFont(); - - // Draw the text on the window drawlist - var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y); - dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text); + using (ImRaii.PushId(text)) + { + var textSize = ImGui.CalcTextSize(textStr); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + textSize.X + (framePadding.X * 2) + iconPadding; + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + button = ImGui.Button(string.Empty, new Vector2(width, height)); + } + + var iconPos = cursor + framePadding; + var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + framePadding.Y); + + var dl = ImGui.GetWindowDrawList(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + } + + dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), textStr); return button; } @@ -215,18 +276,16 @@ public static partial class ImGuiComponents /// Icon to use. /// Text to use. /// Width. - internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) + public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); + Vector2 iconSize; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + var textSize = ImGui.CalcTextSize(text); var iconPadding = 3 * ImGuiHelpers.GlobalScale; - return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs new file mode 100644 index 000000000..99050473f --- /dev/null +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Components; + +/// +/// ImGui component used to create a radio-like input that uses icon buttons. +/// +public static partial class ImGuiComponents +{ + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// The icons that will be displayed on each button. + /// The options that each button will apply. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + public static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair(icon, value)); + return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor); + } + + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// A list of all icon/option pairs. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + public static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button); + activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive); + hoveredColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonHovered); + + var result = false; + + var innerSpacing = ImGui.GetStyle().ItemInnerSpacing; + var y = ImGui.GetCursorPosY(); + + var optArr = options.ToArray(); + for (var i = 0; i < optArr.Length; i++) + { + if (i > 0) + { + if (columns == 0 || i % columns != 0) + { + ImGui.SameLine(0, innerSpacing.X); + } + else + { + y += (buttonSize is { Y: not 0 } ? buttonSize.Value.Y * ImGuiHelpers.GlobalScale : ImGui.GetFrameHeight()) + innerSpacing.Y; + + ImGui.SetCursorPosY(y); + } + } + + optArr[i].Deconstruct(out var icon, out var option); + + var selected = val is not null && val.Equals(option); + + if (IconButton($"{label}{option}{i}", icon, selected ? activeColor : defaultColor, activeColor, hoveredColor, buttonSize)) + { + val = option; + result = true; + } + } + + return result; + } +} diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs index 597b472c6..43b54fc93 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs @@ -1,3 +1,5 @@ +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -24,7 +26,13 @@ public static partial class ImGuiComponents else { ImGui.Text(value + "*"); - if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + ImGui.TextUnformatted(hint); + } + } } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs index 64f3d01eb..6d6e0f6c3 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -36,9 +36,14 @@ public static partial class ImGuiComponents } if (ImGui.IsItemHovered()) + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + } else + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); + } + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); return changed; @@ -62,7 +67,7 @@ public static partial class ImGuiComponents // TODO: animate ImGui.InvisibleButton(id, new Vector2(width, height)); - var dimFactor = 0.5f; + const float dimFactor = 0.5f; drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs index 121ec8890..705c0f100 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -36,6 +36,20 @@ public partial class FileDialog private static string ComposeNewPath(List decomp) { + // Handle UNC paths (network paths) + if (decomp.Count >= 2 && string.IsNullOrEmpty(decomp[0]) && string.IsNullOrEmpty(decomp[1])) + { + var pathParts = new List(decomp); + pathParts.RemoveRange(0, 2); + // Can not access server level or UNC root + if (pathParts.Count <= 1) + { + return string.Empty; + } + + return $"\\\\{string.Join('\\', pathParts)}"; + } + if (decomp.Count == 1) { var drivePath = decomp[0]; diff --git a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs index 332516315..d39fe6bee 100644 --- a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs @@ -1,9 +1,6 @@ using System.Threading; -using System.Threading.Tasks; using Dalamud.Interface.ImGuiNotification.EventArgs; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; namespace Dalamud.Interface.ImGuiNotification; @@ -52,64 +49,6 @@ public interface IActiveNotification : INotification /// This does not override . void ExtendBy(TimeSpan extension); - /// Sets the icon from , overriding the icon. - /// The new texture wrap to use, or null to clear and revert back to the icon specified - /// from . - /// - /// The texture passed will be disposed when the notification is dismissed or a new different texture is set - /// via another call to this function or overwriting the property. You do not have to dispose it yourself. - /// If is not null, then calling this function will simply dispose the - /// passed without actually updating the icon. - /// - void SetIconTexture(IDalamudTextureWrap? textureWrap); - - /// Sets the icon from , overriding the icon, once the given task - /// completes. - /// The task that will result in a new texture wrap to use, or null to clear and - /// revert back to the icon specified from . - /// - /// The texture resulted from the passed will be disposed when the notification - /// is dismissed or a new different texture is set via another call to this function over overwriting the property. - /// You do not have to dispose the resulted instance of yourself. - /// If the task fails for any reason, the exception will be silently ignored and the icon specified from - /// will be used instead. - /// If is not null, then calling this function will simply dispose the - /// result of the passed without actually updating the icon. - /// - void SetIconTexture(Task? textureWrapTask); - - /// Sets the icon from , overriding the icon. - /// The new texture wrap to use, or null to clear and revert back to the icon specified - /// from . - /// Whether to keep the passed not disposed. - /// - /// If is false, the texture passed will be disposed when the - /// notification is dismissed or a new different texture is set via another call to this function. You do not have - /// to dispose it yourself. - /// If is not null and is false, then - /// calling this function will simply dispose the passed without actually updating - /// the icon. - /// - void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen); - - /// Sets the icon from , overriding the icon, once the given task - /// completes. - /// The task that will result in a new texture wrap to use, or null to clear and - /// revert back to the icon specified from . - /// Whether to keep the result from the passed not - /// disposed. - /// - /// If is false, the texture resulted from the passed - /// will be disposed when the notification is dismissed or a new different texture is - /// set via another call to this function. You do not have to dispose the resulted instance of - /// yourself. - /// If the task fails for any reason, the exception will be silently ignored and the icon specified from - /// will be used instead. - /// If is not null, then calling this function will simply dispose the - /// result of the passed without actually updating the icon. - /// - void SetIconTexture(Task? textureWrapTask, bool leaveOpen); - /// Generates a new value to use for . /// The new value. internal static long CreateNewId() => Interlocked.Increment(ref idCounter); diff --git a/Dalamud/Interface/ImGuiNotification/INotification.cs b/Dalamud/Interface/ImGuiNotification/INotification.cs index eab0fd131..0b15d2398 100644 --- a/Dalamud/Interface/ImGuiNotification/INotification.cs +++ b/Dalamud/Interface/ImGuiNotification/INotification.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; - -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; -using Dalamud.Plugin.Services; +using Dalamud.Interface.Textures; namespace Dalamud.Interface.ImGuiNotification; @@ -22,35 +18,12 @@ public interface INotification /// Gets or sets the type of the notification. NotificationType Type { get; set; } - /// Gets or sets the icon source, in case is not set or the task has faulted. + /// Gets or sets the icon source, in case is not set. /// INotificationIcon? Icon { get; set; } - /// Gets or sets a texture wrap that will be used in place of if set. - /// - /// A texture wrap set via this property will NOT be disposed when the notification is dismissed. - /// Use or - /// to use a texture, after calling - /// . Call either of those functions with null to revert - /// the effective icon back to this property. - /// This property and are bound together. If the task is not null but - /// is false (because the task is still in progress or faulted,) - /// the property will return null. Setting this property will set to a new - /// completed with the new value as its result. - /// - public IDalamudTextureWrap? IconTexture { get; set; } - - /// Gets or sets a task that results in a texture wrap that will be used in place of if - /// available. - /// - /// A texture wrap set via this property will NOT be disposed when the notification is dismissed. - /// Use or - /// to use a texture, after calling - /// . Call either of those functions with null to revert - /// the effective icon back to this property. - /// This property and are bound together. - /// - Task? IconTextureTask { get; set; } + /// Gets or sets a texture that will be used in place of if set. + public ISharedImmediateTexture? IconTexture { get; set; } /// Gets or sets the hard expiry. /// diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs index 16d58bea5..c672dd3b3 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs @@ -21,8 +21,8 @@ internal sealed partial class ActiveNotification var opacity = Math.Clamp( (float)(this.hideEasing.IsRunning - ? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.Value) - : (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.Value)), + ? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.ValueClamped) + : (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.ValueClamped)), 0f, 1f); if (opacity <= 0) @@ -106,7 +106,7 @@ internal sealed partial class ActiveNotification } else if (this.expandoEasing.IsRunning) { - var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.Value; + var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.ValueClamped; if (this.underlyingNotification.Minimized) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity * (1f - easedValue)); else @@ -295,8 +295,8 @@ internal sealed partial class ActiveNotification { relativeOpacity = this.underlyingNotification.Minimized - ? 1f - (float)this.expandoEasing.Value - : (float)this.expandoEasing.Value; + ? 1f - (float)this.expandoEasing.ValueClamped + : (float)this.expandoEasing.ValueClamped; } else { @@ -404,7 +404,7 @@ internal sealed partial class ActiveNotification var maxCoord = minCoord + size; var iconColor = this.Type.ToColor(); - if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.IconTextureTask)) + if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.IconTexture)) return; if (this.Icon?.DrawIcon(minCoord, maxCoord, iconColor) is true) @@ -499,7 +499,7 @@ internal sealed partial class ActiveNotification if (fillStartCw == 0 && fillEndCw == 0) return; - + var radius = Math.Min(size.X, size.Y) / 3f; var ifrom = fillStartCw * MathF.PI * 2; var ito = fillEndCw * MathF.PI * 2; @@ -543,7 +543,7 @@ internal sealed partial class ActiveNotification float barL, barR; if (this.DismissReason is not null) { - var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.Value; + var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.ValueClamped; var midpoint = (this.prevProgressL + this.prevProgressR) / 2f; var length = (this.prevProgressR - this.prevProgressL) / 2f; barL = midpoint - (length * v); diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs index 607c7c49d..c3135853d 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs @@ -1,11 +1,9 @@ using System.Runtime.Loader; -using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Interface.Animation; using Dalamud.Interface.Animation.EasingFunctions; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; @@ -23,9 +21,6 @@ internal sealed partial class ActiveNotification : IActiveNotification private readonly Easing progressEasing; private readonly Easing expandoEasing; - /// Whether to call on . - private bool hasIconTextureOwnership; - /// Gets the time of starting to count the timer for the expiration. private DateTime lastInterestTime; @@ -119,31 +114,10 @@ internal sealed partial class ActiveNotification : IActiveNotification } /// - public IDalamudTextureWrap? IconTexture + public ISharedImmediateTexture? IconTexture { get => this.underlyingNotification.IconTexture; - set => this.IconTextureTask = value is null ? null : Task.FromResult(value); - } - - /// - public Task? IconTextureTask - { - get => this.underlyingNotification.IconTextureTask; - set - { - // Do nothing if the value did not change. - if (this.underlyingNotification.IconTextureTask == value) - return; - - if (this.hasIconTextureOwnership) - { - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - this.underlyingNotification.IconTextureTask = null; - this.hasIconTextureOwnership = false; - } - - this.underlyingNotification.IconTextureTask = value; - } + set => this.underlyingNotification.IconTexture = value; } /// @@ -226,7 +200,7 @@ internal sealed partial class ActiveNotification : IActiveNotification if (Math.Abs(underlyingProgress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone || ReducedMotions) return underlyingProgress; - var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.Value, 0f, 1f); + var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.ValueClamped, 0f, 1f); return this.progressBefore + (state * (underlyingProgress - this.progressBefore)); } } @@ -265,39 +239,6 @@ internal sealed partial class ActiveNotification : IActiveNotification this.extendedExpiry = newExpiry; } - /// - public void SetIconTexture(IDalamudTextureWrap? textureWrap) => - this.SetIconTexture(textureWrap, false); - - /// - public void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen) => - this.SetIconTexture(textureWrap is null ? null : Task.FromResult(textureWrap), leaveOpen); - - /// - public void SetIconTexture(Task? textureWrapTask) => - this.SetIconTexture(textureWrapTask, false); - - /// - public void SetIconTexture(Task? textureWrapTask, bool leaveOpen) - { - // If we're requested to replace the texture with the same texture, do nothing. - if (this.underlyingNotification.IconTextureTask == textureWrapTask) - return; - - if (this.DismissReason is not null) - { - if (!leaveOpen) - textureWrapTask?.ToContentDisposedTask(true); - return; - } - - if (this.hasIconTextureOwnership) - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - - this.hasIconTextureOwnership = !leaveOpen; - this.underlyingNotification.IconTextureTask = textureWrapTask; - } - /// Removes non-Dalamud invocation targets from events. /// /// This is done to prevent references of plugins being unloaded from outliving the plugin itself. @@ -317,10 +258,8 @@ internal sealed partial class ActiveNotification : IActiveNotification if (this.Icon is { } previousIcon && !IsOwnedByDalamud(previousIcon.GetType())) this.Icon = null; - // Clear the texture if we don't have the ownership. - // The texture probably was owned by the plugin being unloaded in such case. - if (!this.hasIconTextureOwnership) - this.IconTextureTask = null; + if (this.IconTexture is { } previousTexture && !IsOwnedByDalamud(previousTexture.GetType())) + this.IconTexture = null; this.isInitiatorUnloaded = true; this.UserDismissable = true; @@ -400,13 +339,6 @@ internal sealed partial class ActiveNotification : IActiveNotification /// Clears the resources associated with this instance of . internal void DisposeInternal() { - if (this.hasIconTextureOwnership) - { - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - this.underlyingNotification.IconTextureTask = null; - this.hasIconTextureOwnership = false; - } - this.Dismiss = null; this.Click = null; this.DrawActions = null; diff --git a/Dalamud/Interface/ImGuiNotification/Notification.cs b/Dalamud/Interface/ImGuiNotification/Notification.cs index 927dd5ba9..4dcb10c17 100644 --- a/Dalamud/Interface/ImGuiNotification/Notification.cs +++ b/Dalamud/Interface/ImGuiNotification/Notification.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using Dalamud.Interface.ImGuiNotification.Internal; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; +using Serilog; namespace Dalamud.Interface.ImGuiNotification; - /// Represents a blueprint for a notification. public sealed record Notification : INotification { @@ -30,14 +30,7 @@ public sealed record Notification : INotification public INotificationIcon? Icon { get; set; } /// - public IDalamudTextureWrap? IconTexture - { - get => this.IconTextureTask?.IsCompletedSuccessfully is true ? this.IconTextureTask.Result : null; - set => this.IconTextureTask = value is null ? null : Task.FromResult(value); - } - - /// - public Task? IconTextureTask { get; set; } + public ISharedImmediateTexture? IconTexture { get; set; } /// public DateTime HardExpiry { get; set; } = DateTime.MaxValue; diff --git a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs index 2172663e8..b31321d8b 100644 --- a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs +++ b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs @@ -7,6 +7,7 @@ using Dalamud.Game.Text; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.ManagedFontAtlas; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Plugin.Internal.Types; @@ -78,6 +79,19 @@ public static class NotificationUtilities return true; } + /// Draws an icon from an instance of . + /// The coordinates of the top left of the icon area. + /// The coordinates of the bottom right of the icon area. + /// The texture. + /// true if anything has been drawn. + internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, ISharedImmediateTexture? texture) + { + if (texture is null) + return false; + + return DrawIconFrom(minCoord, maxCoord, texture.GetWrapOrEmpty()); + } + /// Draws an icon from an instance of . /// The coordinates of the top left of the icon area. /// The coordinates of the bottom right of the icon area. @@ -105,16 +119,6 @@ public static class NotificationUtilities } } - /// Draws an icon from an instance of that results in an - /// . - /// The coordinates of the top left of the icon area. - /// The coordinates of the bottom right of the icon area. - /// The task that results in a texture. - /// true if anything has been drawn. - /// Exceptions from the task will be treated as if no texture is provided. - internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, Task? textureTask) => - textureTask?.IsCompletedSuccessfully is true && DrawIconFrom(minCoord, maxCoord, textureTask.Result); - /// Draws an icon from an instance of . /// The coordinates of the top left of the icon area. /// The coordinates of the bottom right of the icon area. diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs new file mode 100644 index 000000000..8559cabdf --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs @@ -0,0 +1,160 @@ +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices; + +using Lumina.Data; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +/// Game font data file. +internal sealed unsafe class GfdFile : FileResource +{ + /// Gets or sets the file header. + public GfdHeader Header { get; set; } + + /// Gets or sets the entries. + public GfdEntry[] Entries { get; set; } = []; + + /// + public override void LoadFile() + { + if (this.DataSpan.Length < sizeof(GfdHeader)) + throw new InvalidDataException($"Not enough space for a {nameof(GfdHeader)}"); + if (this.DataSpan.Length < sizeof(GfdHeader) + (this.Header.Count * sizeof(GfdEntry))) + throw new InvalidDataException($"Not enough space for all the {nameof(GfdEntry)}"); + + this.Header = MemoryMarshal.AsRef(this.DataSpan); + this.Entries = MemoryMarshal.Cast(this.DataSpan[sizeof(GfdHeader)..]).ToArray(); + } + + /// Attempts to get an entry. + /// The icon ID. + /// The entry. + /// Whether to follow redirects. + /// true if found. + public bool TryGetEntry(uint iconId, out GfdEntry entry, bool followRedirect = true) + { + if (iconId == 0) + { + entry = default; + return false; + } + + var entries = this.Entries; + if (iconId <= this.Entries.Length && entries[(int)(iconId - 1)].Id == iconId) + { + if (iconId <= entries.Length) + { + entry = entries[(int)(iconId - 1)]; + return !entry.IsEmpty; + } + + entry = default; + return false; + } + + var lo = 0; + var hi = entries.Length; + while (lo <= hi) + { + var i = lo + ((hi - lo) >> 1); + if (entries[i].Id == iconId) + { + if (followRedirect && entries[i].Redirect != 0) + { + iconId = entries[i].Redirect; + lo = 0; + hi = entries.Length; + continue; + } + + entry = entries[i]; + return !entry.IsEmpty; + } + + if (entries[i].Id < iconId) + lo = i + 1; + else + hi = i - 1; + } + + entry = default; + return false; + } + + /// Header of a .gfd file. + [StructLayout(LayoutKind.Sequential)] + public struct GfdHeader + { + /// Signature: "gftd0100". + public fixed byte Signature[8]; + + /// Number of entries. + public int Count; + + /// Unused/unknown. + public fixed byte Padding[4]; + } + + /// An entry of a .gfd file. + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct GfdEntry + { + /// ID of the entry. + public ushort Id; + + /// The left offset of the entry. + public ushort Left; + + /// The top offset of the entry. + public ushort Top; + + /// The width of the entry. + public ushort Width; + + /// The height of the entry. + public ushort Height; + + /// Unknown/unused. + public ushort Unk0A; + + /// The redirected entry, maybe. + public ushort Redirect; + + /// Unknown/unused. + public ushort Unk0E; + + /// Gets a value indicating whether this entry is effectively empty. + public bool IsEmpty => this.Width == 0 || this.Height == 0; + + /// Gets or sets the size of this entry. + public Vector2 Size + { + get => new(this.Width, this.Height); + set => (this.Width, this.Height) = (checked((ushort)value.X), checked((ushort)value.Y)); + } + + /// Gets the UV0 of this entry. + public Vector2 Uv0 => new(this.Left / 512f, this.Top / 1024f); + + /// Gets the UV1 of this entry. + public Vector2 Uv1 => new((this.Left + this.Width) / 512f, (this.Top + this.Height) / 1024f); + + /// Gets the UV0 of the HQ version of this entry. + public Vector2 HqUv0 => new(this.Left / 256f, (this.Top + 170.5f) / 512f); + + /// Gets the UV1 of the HQ version of this entry. + public Vector2 HqUv1 => new((this.Left + this.Width) / 256f, (this.Top + this.Height + 170.5f) / 512f); + + /// Calculates the size in pixels of a GFD entry when drawn along with a text. + /// Font size in pixels. + /// Whether to draw the HQ texture. + /// Determined size of the GFD entry when drawn. + public readonly Vector2 CalculateScaledSize(float fontSize, out bool useHq) + { + useHq = fontSize > 19; + var targetHeight = useHq ? fontSize : 20; + return new(this.Width * (targetHeight / this.Height), targetHeight); + } + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs new file mode 100644 index 000000000..ad60d405e --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -0,0 +1,202 @@ +using System.Buffers.Binary; +using System.Collections.Generic; + +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +/// Color stacks to use while evaluating a SeString. +internal sealed class SeStringColorStackSet +{ + /// Parsed , containing colors to use with and + /// . + private readonly uint[,] colorTypes; + + /// Foreground color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List colorStack = []; + + /// Edge/border color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List edgeColorStack = []; + + /// Shadow color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List shadowColorStack = []; + + /// Initializes a new instance of the class. + /// UIColor sheet. + public unsafe SeStringColorStackSet(ExcelSheet uiColor) + { + var maxId = 0; + foreach (var row in uiColor) + maxId = (int)Math.Max(row.RowId, maxId); + + this.colorTypes = new uint[maxId + 1, 4]; + foreach (var row in uiColor) + { + // Contains ABGR. + this.colorTypes[row.RowId, 0] = row.Dark; + this.colorTypes[row.RowId, 1] = row.Light; + this.colorTypes[row.RowId, 2] = row.ClassicFF; + this.colorTypes[row.RowId, 3] = row.ClearBlue; + } + + if (BitConverter.IsLittleEndian) + { + // ImGui wants RGBA in LE. + fixed (uint* p = this.colorTypes) + { + foreach (ref var r in new Span(p, this.colorTypes.GetLength(0) * this.colorTypes.GetLength(1))) + r = BinaryPrimitives.ReverseEndianness(r); + } + } + } + + /// Gets a value indicating whether at least one color has been pushed to the edge color stack. + public bool HasAdditionalEdgeColor { get; private set; } + + /// Resets the colors in the stack. + /// Draw state. + internal void Initialize(scoped ref SeStringDrawState drawState) + { + this.colorStack.Clear(); + this.edgeColorStack.Clear(); + this.shadowColorStack.Clear(); + this.colorStack.Add(drawState.Color); + this.edgeColorStack.Add(drawState.EdgeColor); + this.shadowColorStack.Add(drawState.ShadowColor); + drawState.Color = ColorHelpers.ApplyOpacity(drawState.Color, drawState.Opacity); + drawState.EdgeColor = ColorHelpers.ApplyOpacity(drawState.EdgeColor, drawState.EdgeOpacity); + drawState.ShadowColor = ColorHelpers.ApplyOpacity(drawState.ShadowColor, drawState.Opacity); + } + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleColorPayload(scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) => + drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, payload), drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleEdgeColorPayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) + { + var newColor = AdjustStack(this.edgeColorStack, payload); + if (!drawState.ForceEdgeColor) + drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity); + + this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1; + } + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleShadowColorPayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) => + drawState.ShadowColor = + ColorHelpers.ApplyOpacity(AdjustStack(this.shadowColorStack, payload), drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleColorTypePayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) => + drawState.Color = ColorHelpers.ApplyOpacity( + this.AdjustStackByType(this.colorStack, payload, drawState.ThemeIndex), + drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleEdgeColorTypePayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) + { + var newColor = this.AdjustStackByType(this.edgeColorStack, payload, drawState.ThemeIndex); + if (!drawState.ForceEdgeColor) + drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity); + + this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1; + } + + private static unsafe uint AdjustStack(List rgbaStack, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + + // Color payloads have BGRA values as its parameter. ImGui expects RGBA values. + // Opacity component is ignored. + if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + if (expr.TryGetUInt(out var bgra)) + { + // adds the color on the top of the stack. This makes usages like effectively + // become a no-op if no value is provided. + if (bgra == 0) + rgbaStack.Add(rgbaStack[^1]); + else + rgbaStack.Add(ColorHelpers.SwapRedBlue(bgra) | 0xFF000000u); + return rgbaStack[^1]; + } + + if (expr.TryGetParameterExpression(out var et, out var op) && + et == (int)ExpressionType.GlobalNumber && + op.TryGetInt(out var i) && + RaptureTextModule.Instance() is var rtm && + rtm is not null && + i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count && + rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp) + { + rgbaStack.Add(ColorHelpers.SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); + return rgbaStack[^1]; + } + + // Fallback value. + rgbaStack.Add(0xFF000000u); + return rgbaStack[^1]; + } + + private uint AdjustStackByType(List rgbaStack, ReadOnlySePayloadSpan payload, int themeIndex) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + if (!expr.TryGetUInt(out var colorTypeIndex)) + return rgbaStack[^1]; + + // Component::GUI::AtkFontAnalyzerBase.vf4: passing 0 will pop the color off the stack. + if (colorTypeIndex == 0) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + // Opacity component is ignored. + var color = themeIndex >= 0 && themeIndex < this.colorTypes.GetLength(1) && + colorTypeIndex < this.colorTypes.GetLength(0) + ? this.colorTypes[colorTypeIndex, themeIndex] + : 0u; + + rgbaStack.Add(color | 0xFF000000u); + return rgbaStack[^1]; + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs new file mode 100644 index 000000000..9221c2dc5 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -0,0 +1,743 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +using BitFaster.Caching.Lru; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; +using Dalamud.Interface.Utility; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; + +using ImGuiNET; + +using Lumina.Excel.Sheets; +using Lumina.Text; +using Lumina.Text.Parse; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +/// Draws SeString. +[ServiceManager.EarlyLoadedService] +internal unsafe class SeStringRenderer : IInternalDisposableService +{ + private const int ImGuiContextCurrentWindowOffset = 0x3FF0; + private const int ImGuiWindowDcOffset = 0x118; + private const int ImGuiWindowTempDataCurrLineTextBaseOffset = 0x38; + + /// Soft hyphen character, which signifies that a word can be broken here, and will display a standard + /// hyphen when it is broken there. + private const int SoftHyphen = '\u00AD'; + + /// Object replacement character, which signifies that there should be something else displayed in place + /// of this placeholder. On its own, usually displayed like [OBJ]. + private const int ObjectReplacementCharacter = '\uFFFC'; + + /// Cache of compiled SeStrings from . + private readonly ConcurrentLru cache = new(1024); + + /// Parsed gfdata.gfd file, containing bitmap font icon lookup table. + private readonly GfdFile gfd; + + /// Parsed text fragments from a SeString. + /// Touched only from the main thread. + private readonly List fragments = []; + + /// Color stacks to use while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly SeStringColorStackSet colorStackSet; + + /// Splits a draw list so that different layers of a single glyph can be drawn out of order. + private ImDrawListSplitter* splitter = ImGuiNative.ImDrawListSplitter_ImDrawListSplitter(); + + [ServiceManager.ServiceConstructor] + private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) + { + this.colorStackSet = new(dm.Excel.GetSheet()); + this.gfd = dm.GetFile("common/font/gfdata.gfd")!; + } + + /// Finalizes an instance of the class. + ~SeStringRenderer() => this.ReleaseUnmanagedResources(); + + /// + void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); + + /// Compiles and caches a SeString from a text macro representation. + /// SeString text macro representation. + /// Newline characters will be normalized to newline payloads. + /// Compiled SeString. + public ReadOnlySeString CompileAndCache(string text) => + this.cache.GetOrAdd( + text, + static text => ReadOnlySeString.FromMacroString( + text.ReplaceLineEndings("
"), + new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError })); + + /// Compiles and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Newline characters will be normalized to newline payloads. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult CompileAndDrawWrapped( + string text, + scoped in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + this.Draw(this.CompileAndCache(text).AsSpan(), drawParams, imGuiId, buttonFlags); + + /// Draws a SeString. + /// SeString to draw. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult Draw( + scoped in Utf8String utf8String, + scoped in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + this.Draw(utf8String.AsSpan(), drawParams, imGuiId, buttonFlags); + + /// Draws a SeString. + /// SeString to draw. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult Draw( + ReadOnlySeStringSpan sss, + scoped in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) + { + // Drawing is only valid if done from the main thread anyway, especially with interactivity. + ThreadSafety.AssertMainThread(); + + if (drawParams.TargetDrawList is not null && imGuiId) + throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId)); + + // This also does argument validation for drawParams. Do it here. + var state = new SeStringDrawState(sss, drawParams, this.colorStackSet, this.splitter); + + // Reset and initialize the state. + this.fragments.Clear(); + this.colorStackSet.Initialize(ref state); + + // Analyze the provided SeString and break it up to text fragments. + this.CreateTextFragments(ref state); + var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments); + + // Calculate size. + var size = Vector2.Zero; + foreach (ref var fragment in fragmentSpan) + size = Vector2.Max(size, fragment.Offset + new Vector2(fragment.VisibleWidth, state.LineHeight)); + + // If we're not drawing at all, stop further processing. + if (state.DrawList.NativePtr is null) + return new() { Size = size }; + + state.SplitDrawList(); + + // Handle cases where ImGui.AlignTextToFramePadding has been called. + var pCurrentWindow = *(nint*)(ImGui.GetCurrentContext() + ImGuiContextCurrentWindowOffset); + var pWindowDc = pCurrentWindow + ImGuiWindowDcOffset; + var currLineTextBaseOffset = *(float*)(pWindowDc + ImGuiWindowTempDataCurrLineTextBaseOffset); + var itemSize = size; + if (currLineTextBaseOffset != 0f) + { + itemSize.Y += 2 * currLineTextBaseOffset; + foreach (ref var f in fragmentSpan) + f.Offset += new Vector2(0, currLineTextBaseOffset); + } + + // Draw all text fragments. + var lastRune = default(Rune); + foreach (ref var f in fragmentSpan) + { + var data = state.Span[f.From..f.To]; + if (f.Entity) + f.Entity.Draw(state, f.From, f.Offset); + else + this.DrawTextFragment(ref state, f.Offset, f.IsSoftHyphenVisible, data, lastRune, f.Link); + lastRune = f.LastRune; + } + + // Create an ImGui item, if a target draw list is not manually set. + if (drawParams.TargetDrawList is null) + ImGui.Dummy(itemSize); + + // Handle link interactions. + var clicked = false; + var hoveredLinkOffset = -1; + var activeLinkOffset = -1; + if (imGuiId.PushId()) + { + var invisibleButtonDrawn = false; + foreach (ref readonly var f in fragmentSpan) + { + if (f.Link == -1) + continue; + + var pos = ImGui.GetMousePos() - state.ScreenOffset - f.Offset; + var sz = new Vector2(f.AdvanceWidth, state.LineHeight); + if (pos is { X: >= 0, Y: >= 0 } && pos.X <= sz.X && pos.Y <= sz.Y) + { + invisibleButtonDrawn = true; + + var cursorPosBackup = ImGui.GetCursorScreenPos(); + ImGui.SetCursorScreenPos(state.ScreenOffset + f.Offset); + clicked = ImGui.InvisibleButton("##link", sz, buttonFlags); + if (ImGui.IsItemHovered()) + hoveredLinkOffset = f.Link; + if (ImGui.IsItemActive()) + activeLinkOffset = f.Link; + ImGui.SetCursorScreenPos(cursorPosBackup); + + break; + } + } + + // If no link was hovered and thus no invisible button is put, treat the whole area as the button. + if (!invisibleButtonDrawn) + { + ImGui.SetCursorScreenPos(state.ScreenOffset); + clicked = ImGui.InvisibleButton("##text", itemSize, buttonFlags); + } + + ImGui.PopID(); + } + + // If any link is being interacted, draw rectangles behind the relevant text fragments. + if (hoveredLinkOffset != -1 || activeLinkOffset != -1) + { + state.SetCurrentChannel(SeStringDrawChannel.Background); + var color = activeLinkOffset == -1 ? state.LinkHoverBackColor : state.LinkActiveBackColor; + color = ColorHelpers.ApplyOpacity(color, state.Opacity); + foreach (ref readonly var fragment in fragmentSpan) + { + if (fragment.Link != hoveredLinkOffset && hoveredLinkOffset != -1) + continue; + if (fragment.Link != activeLinkOffset && activeLinkOffset != -1) + continue; + var offset = state.ScreenOffset + fragment.Offset; + state.DrawList.AddRectFilled( + offset, + offset + new Vector2(fragment.AdvanceWidth, state.LineHeight), + color); + } + } + + state.MergeDrawList(); + + var payloadEnumerator = new ReadOnlySeStringSpan( + hoveredLinkOffset == -1 ? ReadOnlySpan.Empty : sss.Data[hoveredLinkOffset..]).GetEnumerator(); + if (!payloadEnumerator.MoveNext()) + return new() { Size = size, Clicked = clicked, InteractedPayloadOffset = -1 }; + return new() + { + Size = size, + Clicked = clicked, + InteractedPayloadOffset = hoveredLinkOffset, + InteractedPayloadEnvelope = sss.Data.Slice(hoveredLinkOffset, payloadEnumerator.Current.EnvelopeByteLength), + }; + } + + /// Gets the effective char for the given char, or null(\0) if it should not be handled at all. + /// Character to determine. + /// Corresponding rune. + /// Whether to display soft hyphens. + /// Rune corresponding to the unicode codepoint to process, or null(\0) if none. + private static bool TryGetDisplayRune(Rune rune, out Rune displayRune, bool displaySoftHyphen = true) + { + displayRune = rune.Value switch + { + 0 or char.MaxValue => default, + SoftHyphen => displaySoftHyphen ? new('-') : default, + _ when UnicodeData.LineBreak[rune.Value] + is UnicodeLineBreakClass.BK + or UnicodeLineBreakClass.CR + or UnicodeLineBreakClass.LF + or UnicodeLineBreakClass.NL => new(0), + _ => rune, + }; + return displayRune.Value != 0; + } + + private void ReleaseUnmanagedResources() + { + if (this.splitter is not null) + { + ImGuiNative.ImDrawListSplitter_destroy(this.splitter); + this.splitter = null; + } + } + + /// Creates text fragment, taking line and word breaking into account. + /// Draw state. + private void CreateTextFragments(ref SeStringDrawState state) + { + var prev = 0; + var xy = Vector2.Zero; + var w = 0f; + var link = -1; + foreach (var (breakAt, mandatory) in new LineBreakEnumerator(state.Span, UtfEnumeratorFlags.Utf8SeString)) + { + // Might have happened if custom entity was longer than the previous break unit. + if (prev > breakAt) + continue; + + var nextLink = link; + for (var first = true; prev < breakAt; first = false) + { + var curr = breakAt; + var entity = default(SeStringReplacementEntity); + + // Try to split by link payloads and custom entities. + foreach (var p in new ReadOnlySeStringSpan(state.Span[prev..breakAt]).GetOffsetEnumerator()) + { + var break2 = false; + switch (p.Payload.Type) + { + case ReadOnlySePayloadType.Text when state.GetEntity is { } getEntity: + foreach (var oe in UtfEnumerator.From(p.Payload.Body, UtfEnumeratorFlags.Utf8)) + { + var entityOffset = prev + p.Offset + oe.ByteOffset; + entity = getEntity(state, entityOffset); + if (!entity) + continue; + + if (prev == entityOffset) + { + curr = entityOffset + entity.ByteLength; + } + else + { + entity = default; + curr = entityOffset; + } + + break2 = true; + break; + } + + break; + + case ReadOnlySePayloadType.Macro when + state.GetEntity is { } getEntity && + getEntity(state, prev + p.Offset) is { ByteLength: > 0 } entity1: + entity = entity1; + if (p.Offset == 0) + { + curr = prev + p.Offset + entity.ByteLength; + } + else + { + entity = default; + curr = prev + p.Offset; + } + + break2 = true; + break; + + case ReadOnlySePayloadType.Macro when p.Payload.MacroCode == MacroCode.Link: + { + nextLink = + p.Payload.TryGetExpression(out var e) && + e.TryGetUInt(out var u) && + u == (uint)LinkMacroPayloadType.Terminator + ? -1 + : prev + p.Offset; + + // Split only if we're not splitting at the beginning. + if (p.Offset != 0) + { + curr = prev + p.Offset; + break2 = true; + break; + } + + link = nextLink; + + break; + } + + case ReadOnlySePayloadType.Invalid: + default: + break; + } + + if (break2) break; + } + + // Create a text fragment without applying wrap width limits for testing. + var fragment = this.CreateFragment(state, prev, curr, curr == breakAt && mandatory, xy, link, entity); + var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth; + + // Test if the fragment does not fit into the current line and the current line is not empty. + if (xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows) + { + // Introduce break if this is the first time testing the current break unit or the current fragment + // is an entity. + if (first || entity) + { + // The break unit as a whole does not fit into the current line. Advance to the next line. + xy.X = 0; + xy.Y += state.LineHeight; + w = 0; + CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true; + fragment.Offset = xy; + + // Now that the fragment is given its own line, test if it overflows again. + overflows = fragment.VisibleWidth > state.WrapWidth; + } + } + + if (overflows) + { + // A replacement entity may not be broken down further. + if (!entity) + { + // Create a fragment again that fits into the given width limit. + var remainingWidth = state.WrapWidth - xy.X; + fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth); + } + } + else if (this.fragments.Count > 0 && xy.X != 0) + { + // New fragment fits into the current line, and it has a previous fragment in the same line. + // If the previous fragment ends with a soft hyphen, adjust its width so that the width of its + // trailing soft hyphens are not considered. + if (this.fragments[^1].EndsWithSoftHyphen) + xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth; + + // Adjust this fragment's offset from kerning distance. + xy.X += state.CalculateScaledDistance(this.fragments[^1].LastRune, fragment.FirstRune); + fragment.Offset = xy; + } + + // If the fragment was not broken by wrap width, update the link payload offset. + if (fragment.To == curr) + link = nextLink; + + w = Math.Max(w, xy.X + fragment.VisibleWidth); + xy.X += fragment.AdvanceWidth; + prev = fragment.To; + this.fragments.Add(fragment); + + if (fragment.BreakAfter) + { + xy.X = w = 0; + xy.Y += state.LineHeight; + } + } + } + } + + /// Draws a text fragment. + /// Draw state. + /// Offset of left top corner of this text fragment in pixels w.r.t. + /// . + /// Whether to display soft hyphens in this text fragment. + /// Byte span of the SeString fragment to draw. + /// Rune that preceded this text fragment in the same line, or 0 if none. + /// Byte offset of the link payload that decorates this text fragment in + /// , or -1 if none. + private void DrawTextFragment( + ref SeStringDrawState state, + Vector2 offset, + bool displaySoftHyphen, + ReadOnlySpan span, + Rune lastRune, + int link) + { + // This might temporarily return 0 while logging in. + var gfdTextureSrv = GetGfdTextureSrv(); + var x = 0f; + var width = 0f; + foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString)) + { + if (c is { IsSeStringPayload: true, EffectiveInt: char.MaxValue or ObjectReplacementCharacter }) + { + var enu = new ReadOnlySeStringSpan(span[c.ByteOffset..]).GetOffsetEnumerator(); + if (!enu.MoveNext()) + continue; + + if (state.HandleStyleAdjustingPayloads(enu.Current.Payload)) + continue; + + if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is var icon and not None && + this.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) + { + var size = gfdEntry.CalculateScaledSize(state.FontSize, out var useHq); + state.SetCurrentChannel(SeStringDrawChannel.Foreground); + if (gfdTextureSrv != 0) + { + state.Draw( + gfdTextureSrv, + offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)), + size, + useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, + useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, + ColorHelpers.ApplyOpacity(uint.MaxValue, state.Opacity)); + } + + if (link != -1) + state.DrawLinkUnderline(offset + new Vector2(x, 0), size.X); + + width = Math.Max(width, x + size.X); + x += MathF.Round(size.X); + lastRune = default; + } + + continue; + } + + if (!TryGetDisplayRune(c.EffectiveRune, out var rune, displaySoftHyphen)) + continue; + + ref var g = ref state.FindGlyph(ref rune); + var dist = state.CalculateScaledDistance(lastRune, rune); + var advanceWidth = MathF.Round(g.AdvanceX * state.FontSizeScale); + lastRune = rune; + + state.DrawGlyph(g, offset + new Vector2(x + dist, 0)); + if (link != -1) + state.DrawLinkUnderline(offset + new Vector2(x + dist, 0), advanceWidth); + + width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); + x += dist + advanceWidth; + } + + return; + + static nint GetGfdTextureSrv() + { + var uim = UIModule.Instance(); + if (uim is null) + return 0; + + var ram = uim->GetRaptureAtkModule(); + if (ram is null) + return 0; + + var gfd = ram->AtkModule.AtkFontManager.Gfd; + if (gfd is null) + return 0; + + var tex = gfd->Texture; + if (tex is null) + return 0; + + return (nint)tex->D3D11ShaderResourceView; + } + } + + /// Determines a bitmap icon to display for the given SeString payload. + /// Byte span that should include a SeString payload. + /// Icon to display, or if it should not be displayed as an icon. + private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) + { + var e = new ReadOnlySeStringSpan(sss).GetEnumerator(); + if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2) + return None; + + var payload = e.Current; + switch (payload.MacroCode) + { + // Show the specified icon as-is. + case MacroCode.Icon + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + return (BitmapFontIcon)iconId; + + // Apply gamepad key mapping to icons. + case MacroCode.Icon2 + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + ref var iconMapping = ref RaptureAtkModule.Instance()->AtkFontManager.Icon2RemapTable; + for (var i = 0; i < 30; i++) + { + if (iconMapping[i].IconId == iconId) + { + return (BitmapFontIcon)iconMapping[i].RemappedIconId; + } + } + + return (BitmapFontIcon)iconId; + } + + return None; + } + + /// Creates a text fragment. + /// Draw state. + /// Starting byte offset (inclusive) in that this fragment + /// deals with. + /// Ending byte offset (exclusive) in that this fragment deals + /// with. + /// Whether to break line after this fragment. + /// Offset in pixels w.r.t. . + /// Byte offset of the link payload in that + /// decorates this text fragment. + /// Entity to display in place of this fragment. + /// Optional wrap width to stop at while creating this text fragment. Note that at least + /// one visible character needs to be there in a single text fragment, in which case it is allowed to exceed + /// the wrap width. + /// Newly created text fragment. + private TextFragment CreateFragment( + scoped in SeStringDrawState state, + int from, + int to, + bool breakAfter, + Vector2 offset, + int link, + SeStringReplacementEntity entity, + float wrapWidth = float.MaxValue) + { + if (entity) + { + return new( + from, + to, + link, + offset, + entity, + entity.Size.X, + entity.Size.X, + entity.Size.X, + false, + false, + default, + default); + } + + var x = 0f; + var w = 0f; + var visibleWidth = 0f; + var advanceWidth = 0f; + var advanceWidthWithoutSoftHyphen = 0f; + var firstDisplayRune = default(Rune?); + var lastDisplayRune = default(Rune); + var lastNonSoftHyphenRune = default(Rune); + var endsWithSoftHyphen = false; + foreach (var c in UtfEnumerator.From(state.Span[from..to], UtfEnumeratorFlags.Utf8SeString)) + { + var byteOffset = from + c.ByteOffset; + var isBreakableWhitespace = false; + var effectiveRune = c.EffectiveRune; + Rune displayRune; + if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && + this.GetBitmapFontIconFor(state.Span[byteOffset..]) is var icon and not None && + this.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) + { + // This is an icon payload. + var size = gfdEntry.CalculateScaledSize(state.FontSize, out _); + w = Math.Max(w, x + size.X); + x += MathF.Round(size.X); + displayRune = default; + } + else if (TryGetDisplayRune(effectiveRune, out displayRune)) + { + // This is a printable character, or a standard whitespace character. + ref var g = ref state.FindGlyph(ref displayRune); + var dist = state.CalculateScaledDistance(lastDisplayRune, displayRune); + w = Math.Max(w, x + dist + MathF.Round(g.X1 * state.FontSizeScale)); + x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); + + isBreakableWhitespace = + Rune.IsWhiteSpace(displayRune) && + UnicodeData.LineBreak[displayRune.Value] is not UnicodeLineBreakClass.GL; + } + else + { + continue; + } + + if (isBreakableWhitespace) + { + advanceWidth = x; + } + else + { + if (firstDisplayRune is not null && w > wrapWidth && effectiveRune.Value != SoftHyphen) + { + to = byteOffset; + break; + } + + advanceWidth = x; + visibleWidth = w; + } + + firstDisplayRune ??= displayRune; + lastDisplayRune = displayRune; + endsWithSoftHyphen = effectiveRune.Value == SoftHyphen; + if (!endsWithSoftHyphen) + { + advanceWidthWithoutSoftHyphen = x; + lastNonSoftHyphenRune = displayRune; + } + } + + return new( + from, + to, + link, + offset, + entity, + visibleWidth, + advanceWidth, + advanceWidthWithoutSoftHyphen, + breakAfter, + endsWithSoftHyphen, + firstDisplayRune ?? default, + lastNonSoftHyphenRune); + } + + /// Represents a text fragment in a SeString span. + /// Starting byte offset (inclusive) in a SeString. + /// Ending byte offset (exclusive) in a SeString. + /// Byte offset of the link that decorates this text fragment, or -1 if none. + /// Offset in pixels w.r.t. . + /// Replacement entity, if any. + /// Visible width of this text fragment. This is the width required to draw everything + /// without clipping. + /// Advance width of this text fragment. This is the width required to add to the cursor + /// to position the next fragment correctly. + /// Same with , but trimming all the + /// trailing soft hyphens. + /// Whether to insert a line break after this text fragment. + /// Whether this text fragment ends with one or more soft hyphens. + /// First rune in this text fragment. + /// Last rune in this text fragment, for the purpose of calculating kerning distance with + /// the following text fragment in the same line, if any. + private record struct TextFragment( + int From, + int To, + int Link, + Vector2 Offset, + SeStringReplacementEntity Entity, + float VisibleWidth, + float AdvanceWidth, + float AdvanceWidthWithoutSoftHyphen, + bool BreakAfter, + bool EndsWithSoftHyphen, + Rune FirstRune, + Rune LastRune) + { + public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter; + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/DerivedGeneralCategory.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/DerivedGeneralCategory.txt new file mode 100644 index 000000000..285ffa8fb --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/DerivedGeneralCategory.txt @@ -0,0 +1,4233 @@ +# DerivedGeneralCategory-15.1.0.txt +# Date: 2023-07-28, 23:34:02 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ + +# ================================================ + +# Property: General_Category + +# ================================================ + +# General_Category=Unassigned + +0378..0379 ; Cn # [2] .. +0380..0383 ; Cn # [4] .. +038B ; Cn # +038D ; Cn # +03A2 ; Cn # +0530 ; Cn # +0557..0558 ; Cn # [2] .. +058B..058C ; Cn # [2] .. +0590 ; Cn # +05C8..05CF ; Cn # [8] .. +05EB..05EE ; Cn # [4] .. +05F5..05FF ; Cn # [11] .. +070E ; Cn # +074B..074C ; Cn # [2] .. +07B2..07BF ; Cn # [14] .. +07FB..07FC ; Cn # [2] .. +082E..082F ; Cn # [2] .. +083F ; Cn # +085C..085D ; Cn # [2] .. +085F ; Cn # +086B..086F ; Cn # [5] .. +088F ; Cn # +0892..0897 ; Cn # [6] .. +0984 ; Cn # +098D..098E ; Cn # [2] .. +0991..0992 ; Cn # [2] .. +09A9 ; Cn # +09B1 ; Cn # +09B3..09B5 ; Cn # [3] .. +09BA..09BB ; Cn # [2] .. +09C5..09C6 ; Cn # [2] .. +09C9..09CA ; Cn # [2] .. +09CF..09D6 ; Cn # [8] .. +09D8..09DB ; Cn # [4] .. +09DE ; Cn # +09E4..09E5 ; Cn # [2] .. +09FF..0A00 ; Cn # [2] .. +0A04 ; Cn # +0A0B..0A0E ; Cn # [4] .. +0A11..0A12 ; Cn # [2] .. +0A29 ; Cn # +0A31 ; Cn # +0A34 ; Cn # +0A37 ; Cn # +0A3A..0A3B ; Cn # [2] .. +0A3D ; Cn # +0A43..0A46 ; Cn # [4] .. +0A49..0A4A ; Cn # [2] .. +0A4E..0A50 ; Cn # [3] .. +0A52..0A58 ; Cn # [7] .. +0A5D ; Cn # +0A5F..0A65 ; Cn # [7] .. +0A77..0A80 ; Cn # [10] .. +0A84 ; Cn # +0A8E ; Cn # +0A92 ; Cn # +0AA9 ; Cn # +0AB1 ; Cn # +0AB4 ; Cn # +0ABA..0ABB ; Cn # [2] .. +0AC6 ; Cn # +0ACA ; Cn # +0ACE..0ACF ; Cn # [2] .. +0AD1..0ADF ; Cn # [15] .. +0AE4..0AE5 ; Cn # [2] .. +0AF2..0AF8 ; Cn # [7] .. +0B00 ; Cn # +0B04 ; Cn # +0B0D..0B0E ; Cn # [2] .. +0B11..0B12 ; Cn # [2] .. +0B29 ; Cn # +0B31 ; Cn # +0B34 ; Cn # +0B3A..0B3B ; Cn # [2] .. +0B45..0B46 ; Cn # [2] .. +0B49..0B4A ; Cn # [2] .. +0B4E..0B54 ; Cn # [7] .. +0B58..0B5B ; Cn # [4] .. +0B5E ; Cn # +0B64..0B65 ; Cn # [2] .. +0B78..0B81 ; Cn # [10] .. +0B84 ; Cn # +0B8B..0B8D ; Cn # [3] .. +0B91 ; Cn # +0B96..0B98 ; Cn # [3] .. +0B9B ; Cn # +0B9D ; Cn # +0BA0..0BA2 ; Cn # [3] .. +0BA5..0BA7 ; Cn # [3] .. +0BAB..0BAD ; Cn # [3] .. +0BBA..0BBD ; Cn # [4] .. +0BC3..0BC5 ; Cn # [3] .. +0BC9 ; Cn # +0BCE..0BCF ; Cn # [2] .. +0BD1..0BD6 ; Cn # [6] .. +0BD8..0BE5 ; Cn # [14] .. +0BFB..0BFF ; Cn # [5] .. +0C0D ; Cn # +0C11 ; Cn # +0C29 ; Cn # +0C3A..0C3B ; Cn # [2] .. +0C45 ; Cn # +0C49 ; Cn # +0C4E..0C54 ; Cn # [7] .. +0C57 ; Cn # +0C5B..0C5C ; Cn # [2] .. +0C5E..0C5F ; Cn # [2] .. +0C64..0C65 ; Cn # [2] .. +0C70..0C76 ; Cn # [7] .. +0C8D ; Cn # +0C91 ; Cn # +0CA9 ; Cn # +0CB4 ; Cn # +0CBA..0CBB ; Cn # [2] .. +0CC5 ; Cn # +0CC9 ; Cn # +0CCE..0CD4 ; Cn # [7] .. +0CD7..0CDC ; Cn # [6] .. +0CDF ; Cn # +0CE4..0CE5 ; Cn # [2] .. +0CF0 ; Cn # +0CF4..0CFF ; Cn # [12] .. +0D0D ; Cn # +0D11 ; Cn # +0D45 ; Cn # +0D49 ; Cn # +0D50..0D53 ; Cn # [4] .. +0D64..0D65 ; Cn # [2] .. +0D80 ; Cn # +0D84 ; Cn # +0D97..0D99 ; Cn # [3] .. +0DB2 ; Cn # +0DBC ; Cn # +0DBE..0DBF ; Cn # [2] .. +0DC7..0DC9 ; Cn # [3] .. +0DCB..0DCE ; Cn # [4] .. +0DD5 ; Cn # +0DD7 ; Cn # +0DE0..0DE5 ; Cn # [6] .. +0DF0..0DF1 ; Cn # [2] .. +0DF5..0E00 ; Cn # [12] .. +0E3B..0E3E ; Cn # [4] .. +0E5C..0E80 ; Cn # [37] .. +0E83 ; Cn # +0E85 ; Cn # +0E8B ; Cn # +0EA4 ; Cn # +0EA6 ; Cn # +0EBE..0EBF ; Cn # [2] .. +0EC5 ; Cn # +0EC7 ; Cn # +0ECF ; Cn # +0EDA..0EDB ; Cn # [2] .. +0EE0..0EFF ; Cn # [32] .. +0F48 ; Cn # +0F6D..0F70 ; Cn # [4] .. +0F98 ; Cn # +0FBD ; Cn # +0FCD ; Cn # +0FDB..0FFF ; Cn # [37] .. +10C6 ; Cn # +10C8..10CC ; Cn # [5] .. +10CE..10CF ; Cn # [2] .. +1249 ; Cn # +124E..124F ; Cn # [2] .. +1257 ; Cn # +1259 ; Cn # +125E..125F ; Cn # [2] .. +1289 ; Cn # +128E..128F ; Cn # [2] .. +12B1 ; Cn # +12B6..12B7 ; Cn # [2] .. +12BF ; Cn # +12C1 ; Cn # +12C6..12C7 ; Cn # [2] .. +12D7 ; Cn # +1311 ; Cn # +1316..1317 ; Cn # [2] .. +135B..135C ; Cn # [2] .. +137D..137F ; Cn # [3] .. +139A..139F ; Cn # [6] .. +13F6..13F7 ; Cn # [2] .. +13FE..13FF ; Cn # [2] .. +169D..169F ; Cn # [3] .. +16F9..16FF ; Cn # [7] .. +1716..171E ; Cn # [9] .. +1737..173F ; Cn # [9] .. +1754..175F ; Cn # [12] .. +176D ; Cn # +1771 ; Cn # +1774..177F ; Cn # [12] .. +17DE..17DF ; Cn # [2] .. +17EA..17EF ; Cn # [6] .. +17FA..17FF ; Cn # [6] .. +181A..181F ; Cn # [6] .. +1879..187F ; Cn # [7] .. +18AB..18AF ; Cn # [5] .. +18F6..18FF ; Cn # [10] .. +191F ; Cn # +192C..192F ; Cn # [4] .. +193C..193F ; Cn # [4] .. +1941..1943 ; Cn # [3] .. +196E..196F ; Cn # [2] .. +1975..197F ; Cn # [11] .. +19AC..19AF ; Cn # [4] .. +19CA..19CF ; Cn # [6] .. +19DB..19DD ; Cn # [3] .. +1A1C..1A1D ; Cn # [2] .. +1A5F ; Cn # +1A7D..1A7E ; Cn # [2] .. +1A8A..1A8F ; Cn # [6] .. +1A9A..1A9F ; Cn # [6] .. +1AAE..1AAF ; Cn # [2] .. +1ACF..1AFF ; Cn # [49] .. +1B4D..1B4F ; Cn # [3] .. +1B7F ; Cn # +1BF4..1BFB ; Cn # [8] .. +1C38..1C3A ; Cn # [3] .. +1C4A..1C4C ; Cn # [3] .. +1C89..1C8F ; Cn # [7] .. +1CBB..1CBC ; Cn # [2] .. +1CC8..1CCF ; Cn # [8] .. +1CFB..1CFF ; Cn # [5] .. +1F16..1F17 ; Cn # [2] .. +1F1E..1F1F ; Cn # [2] .. +1F46..1F47 ; Cn # [2] .. +1F4E..1F4F ; Cn # [2] .. +1F58 ; Cn # +1F5A ; Cn # +1F5C ; Cn # +1F5E ; Cn # +1F7E..1F7F ; Cn # [2] .. +1FB5 ; Cn # +1FC5 ; Cn # +1FD4..1FD5 ; Cn # [2] .. +1FDC ; Cn # +1FF0..1FF1 ; Cn # [2] .. +1FF5 ; Cn # +1FFF ; Cn # +2065 ; Cn # +2072..2073 ; Cn # [2] .. +208F ; Cn # +209D..209F ; Cn # [3] .. +20C1..20CF ; Cn # [15] .. +20F1..20FF ; Cn # [15] .. +218C..218F ; Cn # [4] .. +2427..243F ; Cn # [25] .. +244B..245F ; Cn # [21] .. +2B74..2B75 ; Cn # [2] .. +2B96 ; Cn # +2CF4..2CF8 ; Cn # [5] .. +2D26 ; Cn # +2D28..2D2C ; Cn # [5] .. +2D2E..2D2F ; Cn # [2] .. +2D68..2D6E ; Cn # [7] .. +2D71..2D7E ; Cn # [14] .. +2D97..2D9F ; Cn # [9] .. +2DA7 ; Cn # +2DAF ; Cn # +2DB7 ; Cn # +2DBF ; Cn # +2DC7 ; Cn # +2DCF ; Cn # +2DD7 ; Cn # +2DDF ; Cn # +2E5E..2E7F ; Cn # [34] .. +2E9A ; Cn # +2EF4..2EFF ; Cn # [12] .. +2FD6..2FEF ; Cn # [26] .. +3040 ; Cn # +3097..3098 ; Cn # [2] .. +3100..3104 ; Cn # [5] .. +3130 ; Cn # +318F ; Cn # +31E4..31EE ; Cn # [11] .. +321F ; Cn # +A48D..A48F ; Cn # [3] .. +A4C7..A4CF ; Cn # [9] .. +A62C..A63F ; Cn # [20] .. +A6F8..A6FF ; Cn # [8] .. +A7CB..A7CF ; Cn # [5] .. +A7D2 ; Cn # +A7D4 ; Cn # +A7DA..A7F1 ; Cn # [24] .. +A82D..A82F ; Cn # [3] .. +A83A..A83F ; Cn # [6] .. +A878..A87F ; Cn # [8] .. +A8C6..A8CD ; Cn # [8] .. +A8DA..A8DF ; Cn # [6] .. +A954..A95E ; Cn # [11] .. +A97D..A97F ; Cn # [3] .. +A9CE ; Cn # +A9DA..A9DD ; Cn # [4] .. +A9FF ; Cn # +AA37..AA3F ; Cn # [9] .. +AA4E..AA4F ; Cn # [2] .. +AA5A..AA5B ; Cn # [2] .. +AAC3..AADA ; Cn # [24] .. +AAF7..AB00 ; Cn # [10] .. +AB07..AB08 ; Cn # [2] .. +AB0F..AB10 ; Cn # [2] .. +AB17..AB1F ; Cn # [9] .. +AB27 ; Cn # +AB2F ; Cn # +AB6C..AB6F ; Cn # [4] .. +ABEE..ABEF ; Cn # [2] .. +ABFA..ABFF ; Cn # [6] .. +D7A4..D7AF ; Cn # [12] .. +D7C7..D7CA ; Cn # [4] .. +D7FC..D7FF ; Cn # [4] .. +FA6E..FA6F ; Cn # [2] .. +FADA..FAFF ; Cn # [38] .. +FB07..FB12 ; Cn # [12] .. +FB18..FB1C ; Cn # [5] .. +FB37 ; Cn # +FB3D ; Cn # +FB3F ; Cn # +FB42 ; Cn # +FB45 ; Cn # +FBC3..FBD2 ; Cn # [16] .. +FD90..FD91 ; Cn # [2] .. +FDC8..FDCE ; Cn # [7] .. +FDD0..FDEF ; Cn # [32] .. +FE1A..FE1F ; Cn # [6] .. +FE53 ; Cn # +FE67 ; Cn # +FE6C..FE6F ; Cn # [4] .. +FE75 ; Cn # +FEFD..FEFE ; Cn # [2] .. +FF00 ; Cn # +FFBF..FFC1 ; Cn # [3] .. +FFC8..FFC9 ; Cn # [2] .. +FFD0..FFD1 ; Cn # [2] .. +FFD8..FFD9 ; Cn # [2] .. +FFDD..FFDF ; Cn # [3] .. +FFE7 ; Cn # +FFEF..FFF8 ; Cn # [10] .. +FFFE..FFFF ; Cn # [2] .. +1000C ; Cn # +10027 ; Cn # +1003B ; Cn # +1003E ; Cn # +1004E..1004F ; Cn # [2] .. +1005E..1007F ; Cn # [34] .. +100FB..100FF ; Cn # [5] .. +10103..10106 ; Cn # [4] .. +10134..10136 ; Cn # [3] .. +1018F ; Cn # +1019D..1019F ; Cn # [3] .. +101A1..101CF ; Cn # [47] .. +101FE..1027F ; Cn # [130] .. +1029D..1029F ; Cn # [3] .. +102D1..102DF ; Cn # [15] .. +102FC..102FF ; Cn # [4] .. +10324..1032C ; Cn # [9] .. +1034B..1034F ; Cn # [5] .. +1037B..1037F ; Cn # [5] .. +1039E ; Cn # +103C4..103C7 ; Cn # [4] .. +103D6..103FF ; Cn # [42] .. +1049E..1049F ; Cn # [2] .. +104AA..104AF ; Cn # [6] .. +104D4..104D7 ; Cn # [4] .. +104FC..104FF ; Cn # [4] .. +10528..1052F ; Cn # [8] .. +10564..1056E ; Cn # [11] .. +1057B ; Cn # +1058B ; Cn # +10593 ; Cn # +10596 ; Cn # +105A2 ; Cn # +105B2 ; Cn # +105BA ; Cn # +105BD..105FF ; Cn # [67] .. +10737..1073F ; Cn # [9] .. +10756..1075F ; Cn # [10] .. +10768..1077F ; Cn # [24] .. +10786 ; Cn # +107B1 ; Cn # +107BB..107FF ; Cn # [69] .. +10806..10807 ; Cn # [2] .. +10809 ; Cn # +10836 ; Cn # +10839..1083B ; Cn # [3] .. +1083D..1083E ; Cn # [2] .. +10856 ; Cn # +1089F..108A6 ; Cn # [8] .. +108B0..108DF ; Cn # [48] .. +108F3 ; Cn # +108F6..108FA ; Cn # [5] .. +1091C..1091E ; Cn # [3] .. +1093A..1093E ; Cn # [5] .. +10940..1097F ; Cn # [64] .. +109B8..109BB ; Cn # [4] .. +109D0..109D1 ; Cn # [2] .. +10A04 ; Cn # +10A07..10A0B ; Cn # [5] .. +10A14 ; Cn # +10A18 ; Cn # +10A36..10A37 ; Cn # [2] .. +10A3B..10A3E ; Cn # [4] .. +10A49..10A4F ; Cn # [7] .. +10A59..10A5F ; Cn # [7] .. +10AA0..10ABF ; Cn # [32] .. +10AE7..10AEA ; Cn # [4] .. +10AF7..10AFF ; Cn # [9] .. +10B36..10B38 ; Cn # [3] .. +10B56..10B57 ; Cn # [2] .. +10B73..10B77 ; Cn # [5] .. +10B92..10B98 ; Cn # [7] .. +10B9D..10BA8 ; Cn # [12] .. +10BB0..10BFF ; Cn # [80] .. +10C49..10C7F ; Cn # [55] .. +10CB3..10CBF ; Cn # [13] .. +10CF3..10CF9 ; Cn # [7] .. +10D28..10D2F ; Cn # [8] .. +10D3A..10E5F ; Cn # [294] .. +10E7F ; Cn # +10EAA ; Cn # +10EAE..10EAF ; Cn # [2] .. +10EB2..10EFC ; Cn # [75] .. +10F28..10F2F ; Cn # [8] .. +10F5A..10F6F ; Cn # [22] .. +10F8A..10FAF ; Cn # [38] .. +10FCC..10FDF ; Cn # [20] .. +10FF7..10FFF ; Cn # [9] .. +1104E..11051 ; Cn # [4] .. +11076..1107E ; Cn # [9] .. +110C3..110CC ; Cn # [10] .. +110CE..110CF ; Cn # [2] .. +110E9..110EF ; Cn # [7] .. +110FA..110FF ; Cn # [6] .. +11135 ; Cn # +11148..1114F ; Cn # [8] .. +11177..1117F ; Cn # [9] .. +111E0 ; Cn # +111F5..111FF ; Cn # [11] .. +11212 ; Cn # +11242..1127F ; Cn # [62] .. +11287 ; Cn # +11289 ; Cn # +1128E ; Cn # +1129E ; Cn # +112AA..112AF ; Cn # [6] .. +112EB..112EF ; Cn # [5] .. +112FA..112FF ; Cn # [6] .. +11304 ; Cn # +1130D..1130E ; Cn # [2] .. +11311..11312 ; Cn # [2] .. +11329 ; Cn # +11331 ; Cn # +11334 ; Cn # +1133A ; Cn # +11345..11346 ; Cn # [2] .. +11349..1134A ; Cn # [2] .. +1134E..1134F ; Cn # [2] .. +11351..11356 ; Cn # [6] .. +11358..1135C ; Cn # [5] .. +11364..11365 ; Cn # [2] .. +1136D..1136F ; Cn # [3] .. +11375..113FF ; Cn # [139] .. +1145C ; Cn # +11462..1147F ; Cn # [30] .. +114C8..114CF ; Cn # [8] .. +114DA..1157F ; Cn # [166] .. +115B6..115B7 ; Cn # [2] .. +115DE..115FF ; Cn # [34] .. +11645..1164F ; Cn # [11] .. +1165A..1165F ; Cn # [6] .. +1166D..1167F ; Cn # [19] .. +116BA..116BF ; Cn # [6] .. +116CA..116FF ; Cn # [54] .. +1171B..1171C ; Cn # [2] .. +1172C..1172F ; Cn # [4] .. +11747..117FF ; Cn # [185] .. +1183C..1189F ; Cn # [100] .. +118F3..118FE ; Cn # [12] .. +11907..11908 ; Cn # [2] .. +1190A..1190B ; Cn # [2] .. +11914 ; Cn # +11917 ; Cn # +11936 ; Cn # +11939..1193A ; Cn # [2] .. +11947..1194F ; Cn # [9] .. +1195A..1199F ; Cn # [70] .. +119A8..119A9 ; Cn # [2] .. +119D8..119D9 ; Cn # [2] .. +119E5..119FF ; Cn # [27] .. +11A48..11A4F ; Cn # [8] .. +11AA3..11AAF ; Cn # [13] .. +11AF9..11AFF ; Cn # [7] .. +11B0A..11BFF ; Cn # [246] .. +11C09 ; Cn # +11C37 ; Cn # +11C46..11C4F ; Cn # [10] .. +11C6D..11C6F ; Cn # [3] .. +11C90..11C91 ; Cn # [2] .. +11CA8 ; Cn # +11CB7..11CFF ; Cn # [73] .. +11D07 ; Cn # +11D0A ; Cn # +11D37..11D39 ; Cn # [3] .. +11D3B ; Cn # +11D3E ; Cn # +11D48..11D4F ; Cn # [8] .. +11D5A..11D5F ; Cn # [6] .. +11D66 ; Cn # +11D69 ; Cn # +11D8F ; Cn # +11D92 ; Cn # +11D99..11D9F ; Cn # [7] .. +11DAA..11EDF ; Cn # [310] .. +11EF9..11EFF ; Cn # [7] .. +11F11 ; Cn # +11F3B..11F3D ; Cn # [3] .. +11F5A..11FAF ; Cn # [86] .. +11FB1..11FBF ; Cn # [15] .. +11FF2..11FFE ; Cn # [13] .. +1239A..123FF ; Cn # [102] .. +1246F ; Cn # +12475..1247F ; Cn # [11] .. +12544..12F8F ; Cn # [2636] .. +12FF3..12FFF ; Cn # [13] .. +13456..143FF ; Cn # [4010] .. +14647..167FF ; Cn # [8633] .. +16A39..16A3F ; Cn # [7] .. +16A5F ; Cn # +16A6A..16A6D ; Cn # [4] .. +16ABF ; Cn # +16ACA..16ACF ; Cn # [6] .. +16AEE..16AEF ; Cn # [2] .. +16AF6..16AFF ; Cn # [10] .. +16B46..16B4F ; Cn # [10] .. +16B5A ; Cn # +16B62 ; Cn # +16B78..16B7C ; Cn # [5] .. +16B90..16E3F ; Cn # [688] .. +16E9B..16EFF ; Cn # [101] .. +16F4B..16F4E ; Cn # [4] .. +16F88..16F8E ; Cn # [7] .. +16FA0..16FDF ; Cn # [64] .. +16FE5..16FEF ; Cn # [11] .. +16FF2..16FFF ; Cn # [14] .. +187F8..187FF ; Cn # [8] .. +18CD6..18CFF ; Cn # [42] .. +18D09..1AFEF ; Cn # [8935] .. +1AFF4 ; Cn # +1AFFC ; Cn # +1AFFF ; Cn # +1B123..1B131 ; Cn # [15] .. +1B133..1B14F ; Cn # [29] .. +1B153..1B154 ; Cn # [2] .. +1B156..1B163 ; Cn # [14] .. +1B168..1B16F ; Cn # [8] .. +1B2FC..1BBFF ; Cn # [2308] .. +1BC6B..1BC6F ; Cn # [5] .. +1BC7D..1BC7F ; Cn # [3] .. +1BC89..1BC8F ; Cn # [7] .. +1BC9A..1BC9B ; Cn # [2] .. +1BCA4..1CEFF ; Cn # [4700] .. +1CF2E..1CF2F ; Cn # [2] .. +1CF47..1CF4F ; Cn # [9] .. +1CFC4..1CFFF ; Cn # [60] .. +1D0F6..1D0FF ; Cn # [10] .. +1D127..1D128 ; Cn # [2] .. +1D1EB..1D1FF ; Cn # [21] .. +1D246..1D2BF ; Cn # [122] .. +1D2D4..1D2DF ; Cn # [12] .. +1D2F4..1D2FF ; Cn # [12] .. +1D357..1D35F ; Cn # [9] .. +1D379..1D3FF ; Cn # [135] .. +1D455 ; Cn # +1D49D ; Cn # +1D4A0..1D4A1 ; Cn # [2] .. +1D4A3..1D4A4 ; Cn # [2] .. +1D4A7..1D4A8 ; Cn # [2] .. +1D4AD ; Cn # +1D4BA ; Cn # +1D4BC ; Cn # +1D4C4 ; Cn # +1D506 ; Cn # +1D50B..1D50C ; Cn # [2] .. +1D515 ; Cn # +1D51D ; Cn # +1D53A ; Cn # +1D53F ; Cn # +1D545 ; Cn # +1D547..1D549 ; Cn # [3] .. +1D551 ; Cn # +1D6A6..1D6A7 ; Cn # [2] .. +1D7CC..1D7CD ; Cn # [2] .. +1DA8C..1DA9A ; Cn # [15] .. +1DAA0 ; Cn # +1DAB0..1DEFF ; Cn # [1104] .. +1DF1F..1DF24 ; Cn # [6] .. +1DF2B..1DFFF ; Cn # [213] .. +1E007 ; Cn # +1E019..1E01A ; Cn # [2] .. +1E022 ; Cn # +1E025 ; Cn # +1E02B..1E02F ; Cn # [5] .. +1E06E..1E08E ; Cn # [33] .. +1E090..1E0FF ; Cn # [112] .. +1E12D..1E12F ; Cn # [3] .. +1E13E..1E13F ; Cn # [2] .. +1E14A..1E14D ; Cn # [4] .. +1E150..1E28F ; Cn # [320] .. +1E2AF..1E2BF ; Cn # [17] .. +1E2FA..1E2FE ; Cn # [5] .. +1E300..1E4CF ; Cn # [464] .. +1E4FA..1E7DF ; Cn # [742] .. +1E7E7 ; Cn # +1E7EC ; Cn # +1E7EF ; Cn # +1E7FF ; Cn # +1E8C5..1E8C6 ; Cn # [2] .. +1E8D7..1E8FF ; Cn # [41] .. +1E94C..1E94F ; Cn # [4] .. +1E95A..1E95D ; Cn # [4] .. +1E960..1EC70 ; Cn # [785] .. +1ECB5..1ED00 ; Cn # [76] .. +1ED3E..1EDFF ; Cn # [194] .. +1EE04 ; Cn # +1EE20 ; Cn # +1EE23 ; Cn # +1EE25..1EE26 ; Cn # [2] .. +1EE28 ; Cn # +1EE33 ; Cn # +1EE38 ; Cn # +1EE3A ; Cn # +1EE3C..1EE41 ; Cn # [6] .. +1EE43..1EE46 ; Cn # [4] .. +1EE48 ; Cn # +1EE4A ; Cn # +1EE4C ; Cn # +1EE50 ; Cn # +1EE53 ; Cn # +1EE55..1EE56 ; Cn # [2] .. +1EE58 ; Cn # +1EE5A ; Cn # +1EE5C ; Cn # +1EE5E ; Cn # +1EE60 ; Cn # +1EE63 ; Cn # +1EE65..1EE66 ; Cn # [2] .. +1EE6B ; Cn # +1EE73 ; Cn # +1EE78 ; Cn # +1EE7D ; Cn # +1EE7F ; Cn # +1EE8A ; Cn # +1EE9C..1EEA0 ; Cn # [5] .. +1EEA4 ; Cn # +1EEAA ; Cn # +1EEBC..1EEEF ; Cn # [52] .. +1EEF2..1EFFF ; Cn # [270] .. +1F02C..1F02F ; Cn # [4] .. +1F094..1F09F ; Cn # [12] .. +1F0AF..1F0B0 ; Cn # [2] .. +1F0C0 ; Cn # +1F0D0 ; Cn # +1F0F6..1F0FF ; Cn # [10] .. +1F1AE..1F1E5 ; Cn # [56] .. +1F203..1F20F ; Cn # [13] .. +1F23C..1F23F ; Cn # [4] .. +1F249..1F24F ; Cn # [7] .. +1F252..1F25F ; Cn # [14] .. +1F266..1F2FF ; Cn # [154] .. +1F6D8..1F6DB ; Cn # [4] .. +1F6ED..1F6EF ; Cn # [3] .. +1F6FD..1F6FF ; Cn # [3] .. +1F777..1F77A ; Cn # [4] .. +1F7DA..1F7DF ; Cn # [6] .. +1F7EC..1F7EF ; Cn # [4] .. +1F7F1..1F7FF ; Cn # [15] .. +1F80C..1F80F ; Cn # [4] .. +1F848..1F84F ; Cn # [8] .. +1F85A..1F85F ; Cn # [6] .. +1F888..1F88F ; Cn # [8] .. +1F8AE..1F8AF ; Cn # [2] .. +1F8B2..1F8FF ; Cn # [78] .. +1FA54..1FA5F ; Cn # [12] .. +1FA6E..1FA6F ; Cn # [2] .. +1FA7D..1FA7F ; Cn # [3] .. +1FA89..1FA8F ; Cn # [7] .. +1FABE ; Cn # +1FAC6..1FACD ; Cn # [8] .. +1FADC..1FADF ; Cn # [4] .. +1FAE9..1FAEF ; Cn # [7] .. +1FAF9..1FAFF ; Cn # [7] .. +1FB93 ; Cn # +1FBCB..1FBEF ; Cn # [37] .. +1FBFA..1FFFF ; Cn # [1030] .. +2A6E0..2A6FF ; Cn # [32] .. +2B73A..2B73F ; Cn # [6] .. +2B81E..2B81F ; Cn # [2] .. +2CEA2..2CEAF ; Cn # [14] .. +2EBE1..2EBEF ; Cn # [15] .. +2EE5E..2F7FF ; Cn # [2466] .. +2FA1E..2FFFF ; Cn # [1506] .. +3134B..3134F ; Cn # [5] .. +323B0..E0000 ; Cn # [711761] .. +E0002..E001F ; Cn # [30] .. +E0080..E00FF ; Cn # [128] .. +E01F0..EFFFF ; Cn # [65040] .. +FFFFE..FFFFF ; Cn # [2] .. +10FFFE..10FFFF; Cn # [2] .. + +# Total code points: 824718 + +# ================================================ + +# General_Category=Uppercase_Letter + +0041..005A ; Lu # [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +00C0..00D6 ; Lu # [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D8..00DE ; Lu # [7] LATIN CAPITAL LETTER O WITH STROKE..LATIN CAPITAL LETTER THORN +0100 ; Lu # LATIN CAPITAL LETTER A WITH MACRON +0102 ; Lu # LATIN CAPITAL LETTER A WITH BREVE +0104 ; Lu # LATIN CAPITAL LETTER A WITH OGONEK +0106 ; Lu # LATIN CAPITAL LETTER C WITH ACUTE +0108 ; Lu # LATIN CAPITAL LETTER C WITH CIRCUMFLEX +010A ; Lu # LATIN CAPITAL LETTER C WITH DOT ABOVE +010C ; Lu # LATIN CAPITAL LETTER C WITH CARON +010E ; Lu # LATIN CAPITAL LETTER D WITH CARON +0110 ; Lu # LATIN CAPITAL LETTER D WITH STROKE +0112 ; Lu # LATIN CAPITAL LETTER E WITH MACRON +0114 ; Lu # LATIN CAPITAL LETTER E WITH BREVE +0116 ; Lu # LATIN CAPITAL LETTER E WITH DOT ABOVE +0118 ; Lu # LATIN CAPITAL LETTER E WITH OGONEK +011A ; Lu # LATIN CAPITAL LETTER E WITH CARON +011C ; Lu # LATIN CAPITAL LETTER G WITH CIRCUMFLEX +011E ; Lu # LATIN CAPITAL LETTER G WITH BREVE +0120 ; Lu # LATIN CAPITAL LETTER G WITH DOT ABOVE +0122 ; Lu # LATIN CAPITAL LETTER G WITH CEDILLA +0124 ; Lu # LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0126 ; Lu # LATIN CAPITAL LETTER H WITH STROKE +0128 ; Lu # LATIN CAPITAL LETTER I WITH TILDE +012A ; Lu # LATIN CAPITAL LETTER I WITH MACRON +012C ; Lu # LATIN CAPITAL LETTER I WITH BREVE +012E ; Lu # LATIN CAPITAL LETTER I WITH OGONEK +0130 ; Lu # LATIN CAPITAL LETTER I WITH DOT ABOVE +0132 ; Lu # LATIN CAPITAL LIGATURE IJ +0134 ; Lu # LATIN CAPITAL LETTER J WITH CIRCUMFLEX +0136 ; Lu # LATIN CAPITAL LETTER K WITH CEDILLA +0139 ; Lu # LATIN CAPITAL LETTER L WITH ACUTE +013B ; Lu # LATIN CAPITAL LETTER L WITH CEDILLA +013D ; Lu # LATIN CAPITAL LETTER L WITH CARON +013F ; Lu # LATIN CAPITAL LETTER L WITH MIDDLE DOT +0141 ; Lu # LATIN CAPITAL LETTER L WITH STROKE +0143 ; Lu # LATIN CAPITAL LETTER N WITH ACUTE +0145 ; Lu # LATIN CAPITAL LETTER N WITH CEDILLA +0147 ; Lu # LATIN CAPITAL LETTER N WITH CARON +014A ; Lu # LATIN CAPITAL LETTER ENG +014C ; Lu # LATIN CAPITAL LETTER O WITH MACRON +014E ; Lu # LATIN CAPITAL LETTER O WITH BREVE +0150 ; Lu # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0152 ; Lu # LATIN CAPITAL LIGATURE OE +0154 ; Lu # LATIN CAPITAL LETTER R WITH ACUTE +0156 ; Lu # LATIN CAPITAL LETTER R WITH CEDILLA +0158 ; Lu # LATIN CAPITAL LETTER R WITH CARON +015A ; Lu # LATIN CAPITAL LETTER S WITH ACUTE +015C ; Lu # LATIN CAPITAL LETTER S WITH CIRCUMFLEX +015E ; Lu # LATIN CAPITAL LETTER S WITH CEDILLA +0160 ; Lu # LATIN CAPITAL LETTER S WITH CARON +0162 ; Lu # LATIN CAPITAL LETTER T WITH CEDILLA +0164 ; Lu # LATIN CAPITAL LETTER T WITH CARON +0166 ; Lu # LATIN CAPITAL LETTER T WITH STROKE +0168 ; Lu # LATIN CAPITAL LETTER U WITH TILDE +016A ; Lu # LATIN CAPITAL LETTER U WITH MACRON +016C ; Lu # LATIN CAPITAL LETTER U WITH BREVE +016E ; Lu # LATIN CAPITAL LETTER U WITH RING ABOVE +0170 ; Lu # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0172 ; Lu # LATIN CAPITAL LETTER U WITH OGONEK +0174 ; Lu # LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0176 ; Lu # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0178..0179 ; Lu # [2] LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE +017B ; Lu # LATIN CAPITAL LETTER Z WITH DOT ABOVE +017D ; Lu # LATIN CAPITAL LETTER Z WITH CARON +0181..0182 ; Lu # [2] LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR +0184 ; Lu # LATIN CAPITAL LETTER TONE SIX +0186..0187 ; Lu # [2] LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK +0189..018B ; Lu # [3] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR +018E..0191 ; Lu # [4] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK +0193..0194 ; Lu # [2] LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA +0196..0198 ; Lu # [3] LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK +019C..019D ; Lu # [2] LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK +019F..01A0 ; Lu # [2] LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN +01A2 ; Lu # LATIN CAPITAL LETTER OI +01A4 ; Lu # LATIN CAPITAL LETTER P WITH HOOK +01A6..01A7 ; Lu # [2] LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO +01A9 ; Lu # LATIN CAPITAL LETTER ESH +01AC ; Lu # LATIN CAPITAL LETTER T WITH HOOK +01AE..01AF ; Lu # [2] LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN +01B1..01B3 ; Lu # [3] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK +01B5 ; Lu # LATIN CAPITAL LETTER Z WITH STROKE +01B7..01B8 ; Lu # [2] LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED +01BC ; Lu # LATIN CAPITAL LETTER TONE FIVE +01C4 ; Lu # LATIN CAPITAL LETTER DZ WITH CARON +01C7 ; Lu # LATIN CAPITAL LETTER LJ +01CA ; Lu # LATIN CAPITAL LETTER NJ +01CD ; Lu # LATIN CAPITAL LETTER A WITH CARON +01CF ; Lu # LATIN CAPITAL LETTER I WITH CARON +01D1 ; Lu # LATIN CAPITAL LETTER O WITH CARON +01D3 ; Lu # LATIN CAPITAL LETTER U WITH CARON +01D5 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D7 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D9 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DB ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DE ; Lu # LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON +01E0 ; Lu # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON +01E2 ; Lu # LATIN CAPITAL LETTER AE WITH MACRON +01E4 ; Lu # LATIN CAPITAL LETTER G WITH STROKE +01E6 ; Lu # LATIN CAPITAL LETTER G WITH CARON +01E8 ; Lu # LATIN CAPITAL LETTER K WITH CARON +01EA ; Lu # LATIN CAPITAL LETTER O WITH OGONEK +01EC ; Lu # LATIN CAPITAL LETTER O WITH OGONEK AND MACRON +01EE ; Lu # LATIN CAPITAL LETTER EZH WITH CARON +01F1 ; Lu # LATIN CAPITAL LETTER DZ +01F4 ; Lu # LATIN CAPITAL LETTER G WITH ACUTE +01F6..01F8 ; Lu # [3] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE +01FA ; Lu # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +01FC ; Lu # LATIN CAPITAL LETTER AE WITH ACUTE +01FE ; Lu # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +0200 ; Lu # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +0202 ; Lu # LATIN CAPITAL LETTER A WITH INVERTED BREVE +0204 ; Lu # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +0206 ; Lu # LATIN CAPITAL LETTER E WITH INVERTED BREVE +0208 ; Lu # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +020A ; Lu # LATIN CAPITAL LETTER I WITH INVERTED BREVE +020C ; Lu # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +020E ; Lu # LATIN CAPITAL LETTER O WITH INVERTED BREVE +0210 ; Lu # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +0212 ; Lu # LATIN CAPITAL LETTER R WITH INVERTED BREVE +0214 ; Lu # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +0216 ; Lu # LATIN CAPITAL LETTER U WITH INVERTED BREVE +0218 ; Lu # LATIN CAPITAL LETTER S WITH COMMA BELOW +021A ; Lu # LATIN CAPITAL LETTER T WITH COMMA BELOW +021C ; Lu # LATIN CAPITAL LETTER YOGH +021E ; Lu # LATIN CAPITAL LETTER H WITH CARON +0220 ; Lu # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG +0222 ; Lu # LATIN CAPITAL LETTER OU +0224 ; Lu # LATIN CAPITAL LETTER Z WITH HOOK +0226 ; Lu # LATIN CAPITAL LETTER A WITH DOT ABOVE +0228 ; Lu # LATIN CAPITAL LETTER E WITH CEDILLA +022A ; Lu # LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON +022C ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND MACRON +022E ; Lu # LATIN CAPITAL LETTER O WITH DOT ABOVE +0230 ; Lu # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON +0232 ; Lu # LATIN CAPITAL LETTER Y WITH MACRON +023A..023B ; Lu # [2] LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE +023D..023E ; Lu # [2] LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE +0241 ; Lu # LATIN CAPITAL LETTER GLOTTAL STOP +0243..0246 ; Lu # [4] LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE +0248 ; Lu # LATIN CAPITAL LETTER J WITH STROKE +024A ; Lu # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +024C ; Lu # LATIN CAPITAL LETTER R WITH STROKE +024E ; Lu # LATIN CAPITAL LETTER Y WITH STROKE +0370 ; Lu # GREEK CAPITAL LETTER HETA +0372 ; Lu # GREEK CAPITAL LETTER ARCHAIC SAMPI +0376 ; Lu # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA +037F ; Lu # GREEK CAPITAL LETTER YOT +0386 ; Lu # GREEK CAPITAL LETTER ALPHA WITH TONOS +0388..038A ; Lu # [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; Lu # GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..038F ; Lu # [2] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS +0391..03A1 ; Lu # [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO +03A3..03AB ; Lu # [9] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +03CF ; Lu # GREEK CAPITAL KAI SYMBOL +03D2..03D4 ; Lu # [3] GREEK UPSILON WITH HOOK SYMBOL..GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL +03D8 ; Lu # GREEK LETTER ARCHAIC KOPPA +03DA ; Lu # GREEK LETTER STIGMA +03DC ; Lu # GREEK LETTER DIGAMMA +03DE ; Lu # GREEK LETTER KOPPA +03E0 ; Lu # GREEK LETTER SAMPI +03E2 ; Lu # COPTIC CAPITAL LETTER SHEI +03E4 ; Lu # COPTIC CAPITAL LETTER FEI +03E6 ; Lu # COPTIC CAPITAL LETTER KHEI +03E8 ; Lu # COPTIC CAPITAL LETTER HORI +03EA ; Lu # COPTIC CAPITAL LETTER GANGIA +03EC ; Lu # COPTIC CAPITAL LETTER SHIMA +03EE ; Lu # COPTIC CAPITAL LETTER DEI +03F4 ; Lu # GREEK CAPITAL THETA SYMBOL +03F7 ; Lu # GREEK CAPITAL LETTER SHO +03F9..03FA ; Lu # [2] GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN +03FD..042F ; Lu # [51] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA +0460 ; Lu # CYRILLIC CAPITAL LETTER OMEGA +0462 ; Lu # CYRILLIC CAPITAL LETTER YAT +0464 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED E +0466 ; Lu # CYRILLIC CAPITAL LETTER LITTLE YUS +0468 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS +046A ; Lu # CYRILLIC CAPITAL LETTER BIG YUS +046C ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS +046E ; Lu # CYRILLIC CAPITAL LETTER KSI +0470 ; Lu # CYRILLIC CAPITAL LETTER PSI +0472 ; Lu # CYRILLIC CAPITAL LETTER FITA +0474 ; Lu # CYRILLIC CAPITAL LETTER IZHITSA +0476 ; Lu # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0478 ; Lu # CYRILLIC CAPITAL LETTER UK +047A ; Lu # CYRILLIC CAPITAL LETTER ROUND OMEGA +047C ; Lu # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO +047E ; Lu # CYRILLIC CAPITAL LETTER OT +0480 ; Lu # CYRILLIC CAPITAL LETTER KOPPA +048A ; Lu # CYRILLIC CAPITAL LETTER SHORT I WITH TAIL +048C ; Lu # CYRILLIC CAPITAL LETTER SEMISOFT SIGN +048E ; Lu # CYRILLIC CAPITAL LETTER ER WITH TICK +0490 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH UPTURN +0492 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH STROKE +0494 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK +0496 ; Lu # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER +0498 ; Lu # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER +049A ; Lu # CYRILLIC CAPITAL LETTER KA WITH DESCENDER +049C ; Lu # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE +049E ; Lu # CYRILLIC CAPITAL LETTER KA WITH STROKE +04A0 ; Lu # CYRILLIC CAPITAL LETTER BASHKIR KA +04A2 ; Lu # CYRILLIC CAPITAL LETTER EN WITH DESCENDER +04A4 ; Lu # CYRILLIC CAPITAL LIGATURE EN GHE +04A6 ; Lu # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK +04A8 ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN HA +04AA ; Lu # CYRILLIC CAPITAL LETTER ES WITH DESCENDER +04AC ; Lu # CYRILLIC CAPITAL LETTER TE WITH DESCENDER +04AE ; Lu # CYRILLIC CAPITAL LETTER STRAIGHT U +04B0 ; Lu # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE +04B2 ; Lu # CYRILLIC CAPITAL LETTER HA WITH DESCENDER +04B4 ; Lu # CYRILLIC CAPITAL LIGATURE TE TSE +04B6 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER +04B8 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE +04BA ; Lu # CYRILLIC CAPITAL LETTER SHHA +04BC ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN CHE +04BE ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER +04C0..04C1 ; Lu # [2] CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE +04C3 ; Lu # CYRILLIC CAPITAL LETTER KA WITH HOOK +04C5 ; Lu # CYRILLIC CAPITAL LETTER EL WITH TAIL +04C7 ; Lu # CYRILLIC CAPITAL LETTER EN WITH HOOK +04C9 ; Lu # CYRILLIC CAPITAL LETTER EN WITH TAIL +04CB ; Lu # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE +04CD ; Lu # CYRILLIC CAPITAL LETTER EM WITH TAIL +04D0 ; Lu # CYRILLIC CAPITAL LETTER A WITH BREVE +04D2 ; Lu # CYRILLIC CAPITAL LETTER A WITH DIAERESIS +04D4 ; Lu # CYRILLIC CAPITAL LIGATURE A IE +04D6 ; Lu # CYRILLIC CAPITAL LETTER IE WITH BREVE +04D8 ; Lu # CYRILLIC CAPITAL LETTER SCHWA +04DA ; Lu # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS +04DC ; Lu # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS +04DE ; Lu # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS +04E0 ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN DZE +04E2 ; Lu # CYRILLIC CAPITAL LETTER I WITH MACRON +04E4 ; Lu # CYRILLIC CAPITAL LETTER I WITH DIAERESIS +04E6 ; Lu # CYRILLIC CAPITAL LETTER O WITH DIAERESIS +04E8 ; Lu # CYRILLIC CAPITAL LETTER BARRED O +04EA ; Lu # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS +04EC ; Lu # CYRILLIC CAPITAL LETTER E WITH DIAERESIS +04EE ; Lu # CYRILLIC CAPITAL LETTER U WITH MACRON +04F0 ; Lu # CYRILLIC CAPITAL LETTER U WITH DIAERESIS +04F2 ; Lu # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE +04F4 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS +04F6 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER +04F8 ; Lu # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS +04FA ; Lu # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK +04FC ; Lu # CYRILLIC CAPITAL LETTER HA WITH HOOK +04FE ; Lu # CYRILLIC CAPITAL LETTER HA WITH STROKE +0500 ; Lu # CYRILLIC CAPITAL LETTER KOMI DE +0502 ; Lu # CYRILLIC CAPITAL LETTER KOMI DJE +0504 ; Lu # CYRILLIC CAPITAL LETTER KOMI ZJE +0506 ; Lu # CYRILLIC CAPITAL LETTER KOMI DZJE +0508 ; Lu # CYRILLIC CAPITAL LETTER KOMI LJE +050A ; Lu # CYRILLIC CAPITAL LETTER KOMI NJE +050C ; Lu # CYRILLIC CAPITAL LETTER KOMI SJE +050E ; Lu # CYRILLIC CAPITAL LETTER KOMI TJE +0510 ; Lu # CYRILLIC CAPITAL LETTER REVERSED ZE +0512 ; Lu # CYRILLIC CAPITAL LETTER EL WITH HOOK +0514 ; Lu # CYRILLIC CAPITAL LETTER LHA +0516 ; Lu # CYRILLIC CAPITAL LETTER RHA +0518 ; Lu # CYRILLIC CAPITAL LETTER YAE +051A ; Lu # CYRILLIC CAPITAL LETTER QA +051C ; Lu # CYRILLIC CAPITAL LETTER WE +051E ; Lu # CYRILLIC CAPITAL LETTER ALEUT KA +0520 ; Lu # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK +0522 ; Lu # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK +0524 ; Lu # CYRILLIC CAPITAL LETTER PE WITH DESCENDER +0526 ; Lu # CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER +0528 ; Lu # CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK +052A ; Lu # CYRILLIC CAPITAL LETTER DZZHE +052C ; Lu # CYRILLIC CAPITAL LETTER DCHE +052E ; Lu # CYRILLIC CAPITAL LETTER EL WITH DESCENDER +0531..0556 ; Lu # [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +10A0..10C5 ; Lu # [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; Lu # GEORGIAN CAPITAL LETTER YN +10CD ; Lu # GEORGIAN CAPITAL LETTER AEN +13A0..13F5 ; Lu # [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +1C90..1CBA ; Lu # [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; Lu # [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1E00 ; Lu # LATIN CAPITAL LETTER A WITH RING BELOW +1E02 ; Lu # LATIN CAPITAL LETTER B WITH DOT ABOVE +1E04 ; Lu # LATIN CAPITAL LETTER B WITH DOT BELOW +1E06 ; Lu # LATIN CAPITAL LETTER B WITH LINE BELOW +1E08 ; Lu # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE +1E0A ; Lu # LATIN CAPITAL LETTER D WITH DOT ABOVE +1E0C ; Lu # LATIN CAPITAL LETTER D WITH DOT BELOW +1E0E ; Lu # LATIN CAPITAL LETTER D WITH LINE BELOW +1E10 ; Lu # LATIN CAPITAL LETTER D WITH CEDILLA +1E12 ; Lu # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +1E14 ; Lu # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +1E16 ; Lu # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +1E18 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +1E1A ; Lu # LATIN CAPITAL LETTER E WITH TILDE BELOW +1E1C ; Lu # LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE +1E1E ; Lu # LATIN CAPITAL LETTER F WITH DOT ABOVE +1E20 ; Lu # LATIN CAPITAL LETTER G WITH MACRON +1E22 ; Lu # LATIN CAPITAL LETTER H WITH DOT ABOVE +1E24 ; Lu # LATIN CAPITAL LETTER H WITH DOT BELOW +1E26 ; Lu # LATIN CAPITAL LETTER H WITH DIAERESIS +1E28 ; Lu # LATIN CAPITAL LETTER H WITH CEDILLA +1E2A ; Lu # LATIN CAPITAL LETTER H WITH BREVE BELOW +1E2C ; Lu # LATIN CAPITAL LETTER I WITH TILDE BELOW +1E2E ; Lu # LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE +1E30 ; Lu # LATIN CAPITAL LETTER K WITH ACUTE +1E32 ; Lu # LATIN CAPITAL LETTER K WITH DOT BELOW +1E34 ; Lu # LATIN CAPITAL LETTER K WITH LINE BELOW +1E36 ; Lu # LATIN CAPITAL LETTER L WITH DOT BELOW +1E38 ; Lu # LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON +1E3A ; Lu # LATIN CAPITAL LETTER L WITH LINE BELOW +1E3C ; Lu # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +1E3E ; Lu # LATIN CAPITAL LETTER M WITH ACUTE +1E40 ; Lu # LATIN CAPITAL LETTER M WITH DOT ABOVE +1E42 ; Lu # LATIN CAPITAL LETTER M WITH DOT BELOW +1E44 ; Lu # LATIN CAPITAL LETTER N WITH DOT ABOVE +1E46 ; Lu # LATIN CAPITAL LETTER N WITH DOT BELOW +1E48 ; Lu # LATIN CAPITAL LETTER N WITH LINE BELOW +1E4A ; Lu # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +1E4C ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +1E4E ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS +1E50 ; Lu # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +1E52 ; Lu # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +1E54 ; Lu # LATIN CAPITAL LETTER P WITH ACUTE +1E56 ; Lu # LATIN CAPITAL LETTER P WITH DOT ABOVE +1E58 ; Lu # LATIN CAPITAL LETTER R WITH DOT ABOVE +1E5A ; Lu # LATIN CAPITAL LETTER R WITH DOT BELOW +1E5C ; Lu # LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON +1E5E ; Lu # LATIN CAPITAL LETTER R WITH LINE BELOW +1E60 ; Lu # LATIN CAPITAL LETTER S WITH DOT ABOVE +1E62 ; Lu # LATIN CAPITAL LETTER S WITH DOT BELOW +1E64 ; Lu # LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE +1E66 ; Lu # LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE +1E68 ; Lu # LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6A ; Lu # LATIN CAPITAL LETTER T WITH DOT ABOVE +1E6C ; Lu # LATIN CAPITAL LETTER T WITH DOT BELOW +1E6E ; Lu # LATIN CAPITAL LETTER T WITH LINE BELOW +1E70 ; Lu # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +1E72 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +1E74 ; Lu # LATIN CAPITAL LETTER U WITH TILDE BELOW +1E76 ; Lu # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +1E78 ; Lu # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +1E7A ; Lu # LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS +1E7C ; Lu # LATIN CAPITAL LETTER V WITH TILDE +1E7E ; Lu # LATIN CAPITAL LETTER V WITH DOT BELOW +1E80 ; Lu # LATIN CAPITAL LETTER W WITH GRAVE +1E82 ; Lu # LATIN CAPITAL LETTER W WITH ACUTE +1E84 ; Lu # LATIN CAPITAL LETTER W WITH DIAERESIS +1E86 ; Lu # LATIN CAPITAL LETTER W WITH DOT ABOVE +1E88 ; Lu # LATIN CAPITAL LETTER W WITH DOT BELOW +1E8A ; Lu # LATIN CAPITAL LETTER X WITH DOT ABOVE +1E8C ; Lu # LATIN CAPITAL LETTER X WITH DIAERESIS +1E8E ; Lu # LATIN CAPITAL LETTER Y WITH DOT ABOVE +1E90 ; Lu # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +1E92 ; Lu # LATIN CAPITAL LETTER Z WITH DOT BELOW +1E94 ; Lu # LATIN CAPITAL LETTER Z WITH LINE BELOW +1E9E ; Lu # LATIN CAPITAL LETTER SHARP S +1EA0 ; Lu # LATIN CAPITAL LETTER A WITH DOT BELOW +1EA2 ; Lu # LATIN CAPITAL LETTER A WITH HOOK ABOVE +1EA4 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA6 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA8 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAA ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE +1EAC ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAE ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +1EB0 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +1EB2 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE +1EB4 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND TILDE +1EB6 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW +1EB8 ; Lu # LATIN CAPITAL LETTER E WITH DOT BELOW +1EBA ; Lu # LATIN CAPITAL LETTER E WITH HOOK ABOVE +1EBC ; Lu # LATIN CAPITAL LETTER E WITH TILDE +1EBE ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC0 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC2 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC4 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE +1EC6 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC8 ; Lu # LATIN CAPITAL LETTER I WITH HOOK ABOVE +1ECA ; Lu # LATIN CAPITAL LETTER I WITH DOT BELOW +1ECC ; Lu # LATIN CAPITAL LETTER O WITH DOT BELOW +1ECE ; Lu # LATIN CAPITAL LETTER O WITH HOOK ABOVE +1ED0 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED2 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED4 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED6 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE +1ED8 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDA ; Lu # LATIN CAPITAL LETTER O WITH HORN AND ACUTE +1EDC ; Lu # LATIN CAPITAL LETTER O WITH HORN AND GRAVE +1EDE ; Lu # LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE +1EE0 ; Lu # LATIN CAPITAL LETTER O WITH HORN AND TILDE +1EE2 ; Lu # LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW +1EE4 ; Lu # LATIN CAPITAL LETTER U WITH DOT BELOW +1EE6 ; Lu # LATIN CAPITAL LETTER U WITH HOOK ABOVE +1EE8 ; Lu # LATIN CAPITAL LETTER U WITH HORN AND ACUTE +1EEA ; Lu # LATIN CAPITAL LETTER U WITH HORN AND GRAVE +1EEC ; Lu # LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE +1EEE ; Lu # LATIN CAPITAL LETTER U WITH HORN AND TILDE +1EF0 ; Lu # LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW +1EF2 ; Lu # LATIN CAPITAL LETTER Y WITH GRAVE +1EF4 ; Lu # LATIN CAPITAL LETTER Y WITH DOT BELOW +1EF6 ; Lu # LATIN CAPITAL LETTER Y WITH HOOK ABOVE +1EF8 ; Lu # LATIN CAPITAL LETTER Y WITH TILDE +1EFA ; Lu # LATIN CAPITAL LETTER MIDDLE-WELSH LL +1EFC ; Lu # LATIN CAPITAL LETTER MIDDLE-WELSH V +1EFE ; Lu # LATIN CAPITAL LETTER Y WITH LOOP +1F08..1F0F ; Lu # [8] GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F18..1F1D ; Lu # [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F28..1F2F ; Lu # [8] GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI +1F38..1F3F ; Lu # [8] GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI +1F48..1F4D ; Lu # [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F59 ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F68..1F6F ; Lu # [8] GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI +1FB8..1FBB ; Lu # [4] GREEK CAPITAL LETTER ALPHA WITH VRACHY..GREEK CAPITAL LETTER ALPHA WITH OXIA +1FC8..1FCB ; Lu # [4] GREEK CAPITAL LETTER EPSILON WITH VARIA..GREEK CAPITAL LETTER ETA WITH OXIA +1FD8..1FDB ; Lu # [4] GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA +1FE8..1FEC ; Lu # [5] GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FF8..1FFB ; Lu # [4] GREEK CAPITAL LETTER OMICRON WITH VARIA..GREEK CAPITAL LETTER OMEGA WITH OXIA +2102 ; Lu # DOUBLE-STRUCK CAPITAL C +2107 ; Lu # EULER CONSTANT +210B..210D ; Lu # [3] SCRIPT CAPITAL H..DOUBLE-STRUCK CAPITAL H +2110..2112 ; Lu # [3] SCRIPT CAPITAL I..SCRIPT CAPITAL L +2115 ; Lu # DOUBLE-STRUCK CAPITAL N +2119..211D ; Lu # [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +2124 ; Lu # DOUBLE-STRUCK CAPITAL Z +2126 ; Lu # OHM SIGN +2128 ; Lu # BLACK-LETTER CAPITAL Z +212A..212D ; Lu # [4] KELVIN SIGN..BLACK-LETTER CAPITAL C +2130..2133 ; Lu # [4] SCRIPT CAPITAL E..SCRIPT CAPITAL M +213E..213F ; Lu # [2] DOUBLE-STRUCK CAPITAL GAMMA..DOUBLE-STRUCK CAPITAL PI +2145 ; Lu # DOUBLE-STRUCK ITALIC CAPITAL D +2183 ; Lu # ROMAN NUMERAL REVERSED ONE HUNDRED +2C00..2C2F ; Lu # [48] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI +2C60 ; Lu # LATIN CAPITAL LETTER L WITH DOUBLE BAR +2C62..2C64 ; Lu # [3] LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL +2C67 ; Lu # LATIN CAPITAL LETTER H WITH DESCENDER +2C69 ; Lu # LATIN CAPITAL LETTER K WITH DESCENDER +2C6B ; Lu # LATIN CAPITAL LETTER Z WITH DESCENDER +2C6D..2C70 ; Lu # [4] LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA +2C72 ; Lu # LATIN CAPITAL LETTER W WITH HOOK +2C75 ; Lu # LATIN CAPITAL LETTER HALF H +2C7E..2C80 ; Lu # [3] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC CAPITAL LETTER ALFA +2C82 ; Lu # COPTIC CAPITAL LETTER VIDA +2C84 ; Lu # COPTIC CAPITAL LETTER GAMMA +2C86 ; Lu # COPTIC CAPITAL LETTER DALDA +2C88 ; Lu # COPTIC CAPITAL LETTER EIE +2C8A ; Lu # COPTIC CAPITAL LETTER SOU +2C8C ; Lu # COPTIC CAPITAL LETTER ZATA +2C8E ; Lu # COPTIC CAPITAL LETTER HATE +2C90 ; Lu # COPTIC CAPITAL LETTER THETHE +2C92 ; Lu # COPTIC CAPITAL LETTER IAUDA +2C94 ; Lu # COPTIC CAPITAL LETTER KAPA +2C96 ; Lu # COPTIC CAPITAL LETTER LAULA +2C98 ; Lu # COPTIC CAPITAL LETTER MI +2C9A ; Lu # COPTIC CAPITAL LETTER NI +2C9C ; Lu # COPTIC CAPITAL LETTER KSI +2C9E ; Lu # COPTIC CAPITAL LETTER O +2CA0 ; Lu # COPTIC CAPITAL LETTER PI +2CA2 ; Lu # COPTIC CAPITAL LETTER RO +2CA4 ; Lu # COPTIC CAPITAL LETTER SIMA +2CA6 ; Lu # COPTIC CAPITAL LETTER TAU +2CA8 ; Lu # COPTIC CAPITAL LETTER UA +2CAA ; Lu # COPTIC CAPITAL LETTER FI +2CAC ; Lu # COPTIC CAPITAL LETTER KHI +2CAE ; Lu # COPTIC CAPITAL LETTER PSI +2CB0 ; Lu # COPTIC CAPITAL LETTER OOU +2CB2 ; Lu # COPTIC CAPITAL LETTER DIALECT-P ALEF +2CB4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC AIN +2CB6 ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE +2CB8 ; Lu # COPTIC CAPITAL LETTER DIALECT-P KAPA +2CBA ; Lu # COPTIC CAPITAL LETTER DIALECT-P NI +2CBC ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI +2CBE ; Lu # COPTIC CAPITAL LETTER OLD COPTIC OOU +2CC0 ; Lu # COPTIC CAPITAL LETTER SAMPI +2CC2 ; Lu # COPTIC CAPITAL LETTER CROSSED SHEI +2CC4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC SHEI +2CC6 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC ESH +2CC8 ; Lu # COPTIC CAPITAL LETTER AKHMIMIC KHEI +2CCA ; Lu # COPTIC CAPITAL LETTER DIALECT-P HORI +2CCC ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HORI +2CCE ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HA +2CD0 ; Lu # COPTIC CAPITAL LETTER L-SHAPED HA +2CD2 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HEI +2CD4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HAT +2CD6 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC GANGIA +2CD8 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC DJA +2CDA ; Lu # COPTIC CAPITAL LETTER OLD COPTIC SHIMA +2CDC ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA +2CDE ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN NGI +2CE0 ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN NYI +2CE2 ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN WAU +2CEB ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI +2CED ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA +2CF2 ; Lu # COPTIC CAPITAL LETTER BOHAIRIC KHEI +A640 ; Lu # CYRILLIC CAPITAL LETTER ZEMLYA +A642 ; Lu # CYRILLIC CAPITAL LETTER DZELO +A644 ; Lu # CYRILLIC CAPITAL LETTER REVERSED DZE +A646 ; Lu # CYRILLIC CAPITAL LETTER IOTA +A648 ; Lu # CYRILLIC CAPITAL LETTER DJERV +A64A ; Lu # CYRILLIC CAPITAL LETTER MONOGRAPH UK +A64C ; Lu # CYRILLIC CAPITAL LETTER BROAD OMEGA +A64E ; Lu # CYRILLIC CAPITAL LETTER NEUTRAL YER +A650 ; Lu # CYRILLIC CAPITAL LETTER YERU WITH BACK YER +A652 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED YAT +A654 ; Lu # CYRILLIC CAPITAL LETTER REVERSED YU +A656 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED A +A658 ; Lu # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS +A65A ; Lu # CYRILLIC CAPITAL LETTER BLENDED YUS +A65C ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS +A65E ; Lu # CYRILLIC CAPITAL LETTER YN +A660 ; Lu # CYRILLIC CAPITAL LETTER REVERSED TSE +A662 ; Lu # CYRILLIC CAPITAL LETTER SOFT DE +A664 ; Lu # CYRILLIC CAPITAL LETTER SOFT EL +A666 ; Lu # CYRILLIC CAPITAL LETTER SOFT EM +A668 ; Lu # CYRILLIC CAPITAL LETTER MONOCULAR O +A66A ; Lu # CYRILLIC CAPITAL LETTER BINOCULAR O +A66C ; Lu # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O +A680 ; Lu # CYRILLIC CAPITAL LETTER DWE +A682 ; Lu # CYRILLIC CAPITAL LETTER DZWE +A684 ; Lu # CYRILLIC CAPITAL LETTER ZHWE +A686 ; Lu # CYRILLIC CAPITAL LETTER CCHE +A688 ; Lu # CYRILLIC CAPITAL LETTER DZZE +A68A ; Lu # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK +A68C ; Lu # CYRILLIC CAPITAL LETTER TWE +A68E ; Lu # CYRILLIC CAPITAL LETTER TSWE +A690 ; Lu # CYRILLIC CAPITAL LETTER TSSE +A692 ; Lu # CYRILLIC CAPITAL LETTER TCHE +A694 ; Lu # CYRILLIC CAPITAL LETTER HWE +A696 ; Lu # CYRILLIC CAPITAL LETTER SHWE +A698 ; Lu # CYRILLIC CAPITAL LETTER DOUBLE O +A69A ; Lu # CYRILLIC CAPITAL LETTER CROSSED O +A722 ; Lu # LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF +A724 ; Lu # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN +A726 ; Lu # LATIN CAPITAL LETTER HENG +A728 ; Lu # LATIN CAPITAL LETTER TZ +A72A ; Lu # LATIN CAPITAL LETTER TRESILLO +A72C ; Lu # LATIN CAPITAL LETTER CUATRILLO +A72E ; Lu # LATIN CAPITAL LETTER CUATRILLO WITH COMMA +A732 ; Lu # LATIN CAPITAL LETTER AA +A734 ; Lu # LATIN CAPITAL LETTER AO +A736 ; Lu # LATIN CAPITAL LETTER AU +A738 ; Lu # LATIN CAPITAL LETTER AV +A73A ; Lu # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR +A73C ; Lu # LATIN CAPITAL LETTER AY +A73E ; Lu # LATIN CAPITAL LETTER REVERSED C WITH DOT +A740 ; Lu # LATIN CAPITAL LETTER K WITH STROKE +A742 ; Lu # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE +A744 ; Lu # LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE +A746 ; Lu # LATIN CAPITAL LETTER BROKEN L +A748 ; Lu # LATIN CAPITAL LETTER L WITH HIGH STROKE +A74A ; Lu # LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY +A74C ; Lu # LATIN CAPITAL LETTER O WITH LOOP +A74E ; Lu # LATIN CAPITAL LETTER OO +A750 ; Lu # LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER +A752 ; Lu # LATIN CAPITAL LETTER P WITH FLOURISH +A754 ; Lu # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL +A756 ; Lu # LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER +A758 ; Lu # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE +A75A ; Lu # LATIN CAPITAL LETTER R ROTUNDA +A75C ; Lu # LATIN CAPITAL LETTER RUM ROTUNDA +A75E ; Lu # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE +A760 ; Lu # LATIN CAPITAL LETTER VY +A762 ; Lu # LATIN CAPITAL LETTER VISIGOTHIC Z +A764 ; Lu # LATIN CAPITAL LETTER THORN WITH STROKE +A766 ; Lu # LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER +A768 ; Lu # LATIN CAPITAL LETTER VEND +A76A ; Lu # LATIN CAPITAL LETTER ET +A76C ; Lu # LATIN CAPITAL LETTER IS +A76E ; Lu # LATIN CAPITAL LETTER CON +A779 ; Lu # LATIN CAPITAL LETTER INSULAR D +A77B ; Lu # LATIN CAPITAL LETTER INSULAR F +A77D..A77E ; Lu # [2] LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G +A780 ; Lu # LATIN CAPITAL LETTER TURNED L +A782 ; Lu # LATIN CAPITAL LETTER INSULAR R +A784 ; Lu # LATIN CAPITAL LETTER INSULAR S +A786 ; Lu # LATIN CAPITAL LETTER INSULAR T +A78B ; Lu # LATIN CAPITAL LETTER SALTILLO +A78D ; Lu # LATIN CAPITAL LETTER TURNED H +A790 ; Lu # LATIN CAPITAL LETTER N WITH DESCENDER +A792 ; Lu # LATIN CAPITAL LETTER C WITH BAR +A796 ; Lu # LATIN CAPITAL LETTER B WITH FLOURISH +A798 ; Lu # LATIN CAPITAL LETTER F WITH STROKE +A79A ; Lu # LATIN CAPITAL LETTER VOLAPUK AE +A79C ; Lu # LATIN CAPITAL LETTER VOLAPUK OE +A79E ; Lu # LATIN CAPITAL LETTER VOLAPUK UE +A7A0 ; Lu # LATIN CAPITAL LETTER G WITH OBLIQUE STROKE +A7A2 ; Lu # LATIN CAPITAL LETTER K WITH OBLIQUE STROKE +A7A4 ; Lu # LATIN CAPITAL LETTER N WITH OBLIQUE STROKE +A7A6 ; Lu # LATIN CAPITAL LETTER R WITH OBLIQUE STROKE +A7A8 ; Lu # LATIN CAPITAL LETTER S WITH OBLIQUE STROKE +A7AA..A7AE ; Lu # [5] LATIN CAPITAL LETTER H WITH HOOK..LATIN CAPITAL LETTER SMALL CAPITAL I +A7B0..A7B4 ; Lu # [5] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER BETA +A7B6 ; Lu # LATIN CAPITAL LETTER OMEGA +A7B8 ; Lu # LATIN CAPITAL LETTER U WITH STROKE +A7BA ; Lu # LATIN CAPITAL LETTER GLOTTAL A +A7BC ; Lu # LATIN CAPITAL LETTER GLOTTAL I +A7BE ; Lu # LATIN CAPITAL LETTER GLOTTAL U +A7C0 ; Lu # LATIN CAPITAL LETTER OLD POLISH O +A7C2 ; Lu # LATIN CAPITAL LETTER ANGLICANA W +A7C4..A7C7 ; Lu # [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY +A7C9 ; Lu # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY +A7D0 ; Lu # LATIN CAPITAL LETTER CLOSED INSULAR G +A7D6 ; Lu # LATIN CAPITAL LETTER MIDDLE SCOTS S +A7D8 ; Lu # LATIN CAPITAL LETTER SIGMOID S +A7F5 ; Lu # LATIN CAPITAL LETTER REVERSED HALF H +FF21..FF3A ; Lu # [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +10400..10427 ; Lu # [40] DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW +104B0..104D3 ; Lu # [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +10570..1057A ; Lu # [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; Lu # [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; Lu # [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; Lu # [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10C80..10CB2 ; Lu # [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +118A0..118BF ; Lu # [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO +16E40..16E5F ; Lu # [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +1D400..1D419 ; Lu # [26] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL BOLD CAPITAL Z +1D434..1D44D ; Lu # [26] MATHEMATICAL ITALIC CAPITAL A..MATHEMATICAL ITALIC CAPITAL Z +1D468..1D481 ; Lu # [26] MATHEMATICAL BOLD ITALIC CAPITAL A..MATHEMATICAL BOLD ITALIC CAPITAL Z +1D49C ; Lu # MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; Lu # [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; Lu # MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; Lu # [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; Lu # [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B5 ; Lu # [8] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT CAPITAL Z +1D4D0..1D4E9 ; Lu # [26] MATHEMATICAL BOLD SCRIPT CAPITAL A..MATHEMATICAL BOLD SCRIPT CAPITAL Z +1D504..1D505 ; Lu # [2] MATHEMATICAL FRAKTUR CAPITAL A..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; Lu # [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; Lu # [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; Lu # [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D538..1D539 ; Lu # [2] MATHEMATICAL DOUBLE-STRUCK CAPITAL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; Lu # [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; Lu # [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; Lu # MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; Lu # [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D56C..1D585 ; Lu # [26] MATHEMATICAL BOLD FRAKTUR CAPITAL A..MATHEMATICAL BOLD FRAKTUR CAPITAL Z +1D5A0..1D5B9 ; Lu # [26] MATHEMATICAL SANS-SERIF CAPITAL A..MATHEMATICAL SANS-SERIF CAPITAL Z +1D5D4..1D5ED ; Lu # [26] MATHEMATICAL SANS-SERIF BOLD CAPITAL A..MATHEMATICAL SANS-SERIF BOLD CAPITAL Z +1D608..1D621 ; Lu # [26] MATHEMATICAL SANS-SERIF ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z +1D63C..1D655 ; Lu # [26] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z +1D670..1D689 ; Lu # [26] MATHEMATICAL MONOSPACE CAPITAL A..MATHEMATICAL MONOSPACE CAPITAL Z +1D6A8..1D6C0 ; Lu # [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6E2..1D6FA ; Lu # [25] MATHEMATICAL ITALIC CAPITAL ALPHA..MATHEMATICAL ITALIC CAPITAL OMEGA +1D71C..1D734 ; Lu # [25] MATHEMATICAL BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D756..1D76E ; Lu # [25] MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D790..1D7A8 ; Lu # [25] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7CA ; Lu # MATHEMATICAL BOLD CAPITAL DIGAMMA +1E900..1E921 ; Lu # [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA + +# Total code points: 1831 + +# ================================================ + +# General_Category=Lowercase_Letter + +0061..007A ; Ll # [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +00B5 ; Ll # MICRO SIGN +00DF..00F6 ; Ll # [24] LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS +00F8..00FF ; Ll # [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS +0101 ; Ll # LATIN SMALL LETTER A WITH MACRON +0103 ; Ll # LATIN SMALL LETTER A WITH BREVE +0105 ; Ll # LATIN SMALL LETTER A WITH OGONEK +0107 ; Ll # LATIN SMALL LETTER C WITH ACUTE +0109 ; Ll # LATIN SMALL LETTER C WITH CIRCUMFLEX +010B ; Ll # LATIN SMALL LETTER C WITH DOT ABOVE +010D ; Ll # LATIN SMALL LETTER C WITH CARON +010F ; Ll # LATIN SMALL LETTER D WITH CARON +0111 ; Ll # LATIN SMALL LETTER D WITH STROKE +0113 ; Ll # LATIN SMALL LETTER E WITH MACRON +0115 ; Ll # LATIN SMALL LETTER E WITH BREVE +0117 ; Ll # LATIN SMALL LETTER E WITH DOT ABOVE +0119 ; Ll # LATIN SMALL LETTER E WITH OGONEK +011B ; Ll # LATIN SMALL LETTER E WITH CARON +011D ; Ll # LATIN SMALL LETTER G WITH CIRCUMFLEX +011F ; Ll # LATIN SMALL LETTER G WITH BREVE +0121 ; Ll # LATIN SMALL LETTER G WITH DOT ABOVE +0123 ; Ll # LATIN SMALL LETTER G WITH CEDILLA +0125 ; Ll # LATIN SMALL LETTER H WITH CIRCUMFLEX +0127 ; Ll # LATIN SMALL LETTER H WITH STROKE +0129 ; Ll # LATIN SMALL LETTER I WITH TILDE +012B ; Ll # LATIN SMALL LETTER I WITH MACRON +012D ; Ll # LATIN SMALL LETTER I WITH BREVE +012F ; Ll # LATIN SMALL LETTER I WITH OGONEK +0131 ; Ll # LATIN SMALL LETTER DOTLESS I +0133 ; Ll # LATIN SMALL LIGATURE IJ +0135 ; Ll # LATIN SMALL LETTER J WITH CIRCUMFLEX +0137..0138 ; Ll # [2] LATIN SMALL LETTER K WITH CEDILLA..LATIN SMALL LETTER KRA +013A ; Ll # LATIN SMALL LETTER L WITH ACUTE +013C ; Ll # LATIN SMALL LETTER L WITH CEDILLA +013E ; Ll # LATIN SMALL LETTER L WITH CARON +0140 ; Ll # LATIN SMALL LETTER L WITH MIDDLE DOT +0142 ; Ll # LATIN SMALL LETTER L WITH STROKE +0144 ; Ll # LATIN SMALL LETTER N WITH ACUTE +0146 ; Ll # LATIN SMALL LETTER N WITH CEDILLA +0148..0149 ; Ll # [2] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +014B ; Ll # LATIN SMALL LETTER ENG +014D ; Ll # LATIN SMALL LETTER O WITH MACRON +014F ; Ll # LATIN SMALL LETTER O WITH BREVE +0151 ; Ll # LATIN SMALL LETTER O WITH DOUBLE ACUTE +0153 ; Ll # LATIN SMALL LIGATURE OE +0155 ; Ll # LATIN SMALL LETTER R WITH ACUTE +0157 ; Ll # LATIN SMALL LETTER R WITH CEDILLA +0159 ; Ll # LATIN SMALL LETTER R WITH CARON +015B ; Ll # LATIN SMALL LETTER S WITH ACUTE +015D ; Ll # LATIN SMALL LETTER S WITH CIRCUMFLEX +015F ; Ll # LATIN SMALL LETTER S WITH CEDILLA +0161 ; Ll # LATIN SMALL LETTER S WITH CARON +0163 ; Ll # LATIN SMALL LETTER T WITH CEDILLA +0165 ; Ll # LATIN SMALL LETTER T WITH CARON +0167 ; Ll # LATIN SMALL LETTER T WITH STROKE +0169 ; Ll # LATIN SMALL LETTER U WITH TILDE +016B ; Ll # LATIN SMALL LETTER U WITH MACRON +016D ; Ll # LATIN SMALL LETTER U WITH BREVE +016F ; Ll # LATIN SMALL LETTER U WITH RING ABOVE +0171 ; Ll # LATIN SMALL LETTER U WITH DOUBLE ACUTE +0173 ; Ll # LATIN SMALL LETTER U WITH OGONEK +0175 ; Ll # LATIN SMALL LETTER W WITH CIRCUMFLEX +0177 ; Ll # LATIN SMALL LETTER Y WITH CIRCUMFLEX +017A ; Ll # LATIN SMALL LETTER Z WITH ACUTE +017C ; Ll # LATIN SMALL LETTER Z WITH DOT ABOVE +017E..0180 ; Ll # [3] LATIN SMALL LETTER Z WITH CARON..LATIN SMALL LETTER B WITH STROKE +0183 ; Ll # LATIN SMALL LETTER B WITH TOPBAR +0185 ; Ll # LATIN SMALL LETTER TONE SIX +0188 ; Ll # LATIN SMALL LETTER C WITH HOOK +018C..018D ; Ll # [2] LATIN SMALL LETTER D WITH TOPBAR..LATIN SMALL LETTER TURNED DELTA +0192 ; Ll # LATIN SMALL LETTER F WITH HOOK +0195 ; Ll # LATIN SMALL LETTER HV +0199..019B ; Ll # [3] LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE +019E ; Ll # LATIN SMALL LETTER N WITH LONG RIGHT LEG +01A1 ; Ll # LATIN SMALL LETTER O WITH HORN +01A3 ; Ll # LATIN SMALL LETTER OI +01A5 ; Ll # LATIN SMALL LETTER P WITH HOOK +01A8 ; Ll # LATIN SMALL LETTER TONE TWO +01AA..01AB ; Ll # [2] LATIN LETTER REVERSED ESH LOOP..LATIN SMALL LETTER T WITH PALATAL HOOK +01AD ; Ll # LATIN SMALL LETTER T WITH HOOK +01B0 ; Ll # LATIN SMALL LETTER U WITH HORN +01B4 ; Ll # LATIN SMALL LETTER Y WITH HOOK +01B6 ; Ll # LATIN SMALL LETTER Z WITH STROKE +01B9..01BA ; Ll # [2] LATIN SMALL LETTER EZH REVERSED..LATIN SMALL LETTER EZH WITH TAIL +01BD..01BF ; Ll # [3] LATIN SMALL LETTER TONE FIVE..LATIN LETTER WYNN +01C6 ; Ll # LATIN SMALL LETTER DZ WITH CARON +01C9 ; Ll # LATIN SMALL LETTER LJ +01CC ; Ll # LATIN SMALL LETTER NJ +01CE ; Ll # LATIN SMALL LETTER A WITH CARON +01D0 ; Ll # LATIN SMALL LETTER I WITH CARON +01D2 ; Ll # LATIN SMALL LETTER O WITH CARON +01D4 ; Ll # LATIN SMALL LETTER U WITH CARON +01D6 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D8 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01DA ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DC..01DD ; Ll # [2] LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E +01DF ; Ll # LATIN SMALL LETTER A WITH DIAERESIS AND MACRON +01E1 ; Ll # LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON +01E3 ; Ll # LATIN SMALL LETTER AE WITH MACRON +01E5 ; Ll # LATIN SMALL LETTER G WITH STROKE +01E7 ; Ll # LATIN SMALL LETTER G WITH CARON +01E9 ; Ll # LATIN SMALL LETTER K WITH CARON +01EB ; Ll # LATIN SMALL LETTER O WITH OGONEK +01ED ; Ll # LATIN SMALL LETTER O WITH OGONEK AND MACRON +01EF..01F0 ; Ll # [2] LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON +01F3 ; Ll # LATIN SMALL LETTER DZ +01F5 ; Ll # LATIN SMALL LETTER G WITH ACUTE +01F9 ; Ll # LATIN SMALL LETTER N WITH GRAVE +01FB ; Ll # LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +01FD ; Ll # LATIN SMALL LETTER AE WITH ACUTE +01FF ; Ll # LATIN SMALL LETTER O WITH STROKE AND ACUTE +0201 ; Ll # LATIN SMALL LETTER A WITH DOUBLE GRAVE +0203 ; Ll # LATIN SMALL LETTER A WITH INVERTED BREVE +0205 ; Ll # LATIN SMALL LETTER E WITH DOUBLE GRAVE +0207 ; Ll # LATIN SMALL LETTER E WITH INVERTED BREVE +0209 ; Ll # LATIN SMALL LETTER I WITH DOUBLE GRAVE +020B ; Ll # LATIN SMALL LETTER I WITH INVERTED BREVE +020D ; Ll # LATIN SMALL LETTER O WITH DOUBLE GRAVE +020F ; Ll # LATIN SMALL LETTER O WITH INVERTED BREVE +0211 ; Ll # LATIN SMALL LETTER R WITH DOUBLE GRAVE +0213 ; Ll # LATIN SMALL LETTER R WITH INVERTED BREVE +0215 ; Ll # LATIN SMALL LETTER U WITH DOUBLE GRAVE +0217 ; Ll # LATIN SMALL LETTER U WITH INVERTED BREVE +0219 ; Ll # LATIN SMALL LETTER S WITH COMMA BELOW +021B ; Ll # LATIN SMALL LETTER T WITH COMMA BELOW +021D ; Ll # LATIN SMALL LETTER YOGH +021F ; Ll # LATIN SMALL LETTER H WITH CARON +0221 ; Ll # LATIN SMALL LETTER D WITH CURL +0223 ; Ll # LATIN SMALL LETTER OU +0225 ; Ll # LATIN SMALL LETTER Z WITH HOOK +0227 ; Ll # LATIN SMALL LETTER A WITH DOT ABOVE +0229 ; Ll # LATIN SMALL LETTER E WITH CEDILLA +022B ; Ll # LATIN SMALL LETTER O WITH DIAERESIS AND MACRON +022D ; Ll # LATIN SMALL LETTER O WITH TILDE AND MACRON +022F ; Ll # LATIN SMALL LETTER O WITH DOT ABOVE +0231 ; Ll # LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON +0233..0239 ; Ll # [7] LATIN SMALL LETTER Y WITH MACRON..LATIN SMALL LETTER QP DIGRAPH +023C ; Ll # LATIN SMALL LETTER C WITH STROKE +023F..0240 ; Ll # [2] LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL +0242 ; Ll # LATIN SMALL LETTER GLOTTAL STOP +0247 ; Ll # LATIN SMALL LETTER E WITH STROKE +0249 ; Ll # LATIN SMALL LETTER J WITH STROKE +024B ; Ll # LATIN SMALL LETTER Q WITH HOOK TAIL +024D ; Ll # LATIN SMALL LETTER R WITH STROKE +024F..0293 ; Ll # [69] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER EZH WITH CURL +0295..02AF ; Ll # [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0371 ; Ll # GREEK SMALL LETTER HETA +0373 ; Ll # GREEK SMALL LETTER ARCHAIC SAMPI +0377 ; Ll # GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037B..037D ; Ll # [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +0390 ; Ll # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +03AC..03CE ; Ll # [35] GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS +03D0..03D1 ; Ll # [2] GREEK BETA SYMBOL..GREEK THETA SYMBOL +03D5..03D7 ; Ll # [3] GREEK PHI SYMBOL..GREEK KAI SYMBOL +03D9 ; Ll # GREEK SMALL LETTER ARCHAIC KOPPA +03DB ; Ll # GREEK SMALL LETTER STIGMA +03DD ; Ll # GREEK SMALL LETTER DIGAMMA +03DF ; Ll # GREEK SMALL LETTER KOPPA +03E1 ; Ll # GREEK SMALL LETTER SAMPI +03E3 ; Ll # COPTIC SMALL LETTER SHEI +03E5 ; Ll # COPTIC SMALL LETTER FEI +03E7 ; Ll # COPTIC SMALL LETTER KHEI +03E9 ; Ll # COPTIC SMALL LETTER HORI +03EB ; Ll # COPTIC SMALL LETTER GANGIA +03ED ; Ll # COPTIC SMALL LETTER SHIMA +03EF..03F3 ; Ll # [5] COPTIC SMALL LETTER DEI..GREEK LETTER YOT +03F5 ; Ll # GREEK LUNATE EPSILON SYMBOL +03F8 ; Ll # GREEK SMALL LETTER SHO +03FB..03FC ; Ll # [2] GREEK SMALL LETTER SAN..GREEK RHO WITH STROKE SYMBOL +0430..045F ; Ll # [48] CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE +0461 ; Ll # CYRILLIC SMALL LETTER OMEGA +0463 ; Ll # CYRILLIC SMALL LETTER YAT +0465 ; Ll # CYRILLIC SMALL LETTER IOTIFIED E +0467 ; Ll # CYRILLIC SMALL LETTER LITTLE YUS +0469 ; Ll # CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS +046B ; Ll # CYRILLIC SMALL LETTER BIG YUS +046D ; Ll # CYRILLIC SMALL LETTER IOTIFIED BIG YUS +046F ; Ll # CYRILLIC SMALL LETTER KSI +0471 ; Ll # CYRILLIC SMALL LETTER PSI +0473 ; Ll # CYRILLIC SMALL LETTER FITA +0475 ; Ll # CYRILLIC SMALL LETTER IZHITSA +0477 ; Ll # CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0479 ; Ll # CYRILLIC SMALL LETTER UK +047B ; Ll # CYRILLIC SMALL LETTER ROUND OMEGA +047D ; Ll # CYRILLIC SMALL LETTER OMEGA WITH TITLO +047F ; Ll # CYRILLIC SMALL LETTER OT +0481 ; Ll # CYRILLIC SMALL LETTER KOPPA +048B ; Ll # CYRILLIC SMALL LETTER SHORT I WITH TAIL +048D ; Ll # CYRILLIC SMALL LETTER SEMISOFT SIGN +048F ; Ll # CYRILLIC SMALL LETTER ER WITH TICK +0491 ; Ll # CYRILLIC SMALL LETTER GHE WITH UPTURN +0493 ; Ll # CYRILLIC SMALL LETTER GHE WITH STROKE +0495 ; Ll # CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK +0497 ; Ll # CYRILLIC SMALL LETTER ZHE WITH DESCENDER +0499 ; Ll # CYRILLIC SMALL LETTER ZE WITH DESCENDER +049B ; Ll # CYRILLIC SMALL LETTER KA WITH DESCENDER +049D ; Ll # CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE +049F ; Ll # CYRILLIC SMALL LETTER KA WITH STROKE +04A1 ; Ll # CYRILLIC SMALL LETTER BASHKIR KA +04A3 ; Ll # CYRILLIC SMALL LETTER EN WITH DESCENDER +04A5 ; Ll # CYRILLIC SMALL LIGATURE EN GHE +04A7 ; Ll # CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK +04A9 ; Ll # CYRILLIC SMALL LETTER ABKHASIAN HA +04AB ; Ll # CYRILLIC SMALL LETTER ES WITH DESCENDER +04AD ; Ll # CYRILLIC SMALL LETTER TE WITH DESCENDER +04AF ; Ll # CYRILLIC SMALL LETTER STRAIGHT U +04B1 ; Ll # CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE +04B3 ; Ll # CYRILLIC SMALL LETTER HA WITH DESCENDER +04B5 ; Ll # CYRILLIC SMALL LIGATURE TE TSE +04B7 ; Ll # CYRILLIC SMALL LETTER CHE WITH DESCENDER +04B9 ; Ll # CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE +04BB ; Ll # CYRILLIC SMALL LETTER SHHA +04BD ; Ll # CYRILLIC SMALL LETTER ABKHASIAN CHE +04BF ; Ll # CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER +04C2 ; Ll # CYRILLIC SMALL LETTER ZHE WITH BREVE +04C4 ; Ll # CYRILLIC SMALL LETTER KA WITH HOOK +04C6 ; Ll # CYRILLIC SMALL LETTER EL WITH TAIL +04C8 ; Ll # CYRILLIC SMALL LETTER EN WITH HOOK +04CA ; Ll # CYRILLIC SMALL LETTER EN WITH TAIL +04CC ; Ll # CYRILLIC SMALL LETTER KHAKASSIAN CHE +04CE..04CF ; Ll # [2] CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA +04D1 ; Ll # CYRILLIC SMALL LETTER A WITH BREVE +04D3 ; Ll # CYRILLIC SMALL LETTER A WITH DIAERESIS +04D5 ; Ll # CYRILLIC SMALL LIGATURE A IE +04D7 ; Ll # CYRILLIC SMALL LETTER IE WITH BREVE +04D9 ; Ll # CYRILLIC SMALL LETTER SCHWA +04DB ; Ll # CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS +04DD ; Ll # CYRILLIC SMALL LETTER ZHE WITH DIAERESIS +04DF ; Ll # CYRILLIC SMALL LETTER ZE WITH DIAERESIS +04E1 ; Ll # CYRILLIC SMALL LETTER ABKHASIAN DZE +04E3 ; Ll # CYRILLIC SMALL LETTER I WITH MACRON +04E5 ; Ll # CYRILLIC SMALL LETTER I WITH DIAERESIS +04E7 ; Ll # CYRILLIC SMALL LETTER O WITH DIAERESIS +04E9 ; Ll # CYRILLIC SMALL LETTER BARRED O +04EB ; Ll # CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS +04ED ; Ll # CYRILLIC SMALL LETTER E WITH DIAERESIS +04EF ; Ll # CYRILLIC SMALL LETTER U WITH MACRON +04F1 ; Ll # CYRILLIC SMALL LETTER U WITH DIAERESIS +04F3 ; Ll # CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE +04F5 ; Ll # CYRILLIC SMALL LETTER CHE WITH DIAERESIS +04F7 ; Ll # CYRILLIC SMALL LETTER GHE WITH DESCENDER +04F9 ; Ll # CYRILLIC SMALL LETTER YERU WITH DIAERESIS +04FB ; Ll # CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK +04FD ; Ll # CYRILLIC SMALL LETTER HA WITH HOOK +04FF ; Ll # CYRILLIC SMALL LETTER HA WITH STROKE +0501 ; Ll # CYRILLIC SMALL LETTER KOMI DE +0503 ; Ll # CYRILLIC SMALL LETTER KOMI DJE +0505 ; Ll # CYRILLIC SMALL LETTER KOMI ZJE +0507 ; Ll # CYRILLIC SMALL LETTER KOMI DZJE +0509 ; Ll # CYRILLIC SMALL LETTER KOMI LJE +050B ; Ll # CYRILLIC SMALL LETTER KOMI NJE +050D ; Ll # CYRILLIC SMALL LETTER KOMI SJE +050F ; Ll # CYRILLIC SMALL LETTER KOMI TJE +0511 ; Ll # CYRILLIC SMALL LETTER REVERSED ZE +0513 ; Ll # CYRILLIC SMALL LETTER EL WITH HOOK +0515 ; Ll # CYRILLIC SMALL LETTER LHA +0517 ; Ll # CYRILLIC SMALL LETTER RHA +0519 ; Ll # CYRILLIC SMALL LETTER YAE +051B ; Ll # CYRILLIC SMALL LETTER QA +051D ; Ll # CYRILLIC SMALL LETTER WE +051F ; Ll # CYRILLIC SMALL LETTER ALEUT KA +0521 ; Ll # CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK +0523 ; Ll # CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK +0525 ; Ll # CYRILLIC SMALL LETTER PE WITH DESCENDER +0527 ; Ll # CYRILLIC SMALL LETTER SHHA WITH DESCENDER +0529 ; Ll # CYRILLIC SMALL LETTER EN WITH LEFT HOOK +052B ; Ll # CYRILLIC SMALL LETTER DZZHE +052D ; Ll # CYRILLIC SMALL LETTER DCHE +052F ; Ll # CYRILLIC SMALL LETTER EL WITH DESCENDER +0560..0588 ; Ll # [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +10D0..10FA ; Ll # [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FD..10FF ; Ll # [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +13F8..13FD ; Ll # [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1C80..1C88 ; Ll # [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1D00..1D2B ; Ll # [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D6B..1D77 ; Ll # [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D79..1D9A ; Ll # [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1E01 ; Ll # LATIN SMALL LETTER A WITH RING BELOW +1E03 ; Ll # LATIN SMALL LETTER B WITH DOT ABOVE +1E05 ; Ll # LATIN SMALL LETTER B WITH DOT BELOW +1E07 ; Ll # LATIN SMALL LETTER B WITH LINE BELOW +1E09 ; Ll # LATIN SMALL LETTER C WITH CEDILLA AND ACUTE +1E0B ; Ll # LATIN SMALL LETTER D WITH DOT ABOVE +1E0D ; Ll # LATIN SMALL LETTER D WITH DOT BELOW +1E0F ; Ll # LATIN SMALL LETTER D WITH LINE BELOW +1E11 ; Ll # LATIN SMALL LETTER D WITH CEDILLA +1E13 ; Ll # LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E15 ; Ll # LATIN SMALL LETTER E WITH MACRON AND GRAVE +1E17 ; Ll # LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E19 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW +1E1B ; Ll # LATIN SMALL LETTER E WITH TILDE BELOW +1E1D ; Ll # LATIN SMALL LETTER E WITH CEDILLA AND BREVE +1E1F ; Ll # LATIN SMALL LETTER F WITH DOT ABOVE +1E21 ; Ll # LATIN SMALL LETTER G WITH MACRON +1E23 ; Ll # LATIN SMALL LETTER H WITH DOT ABOVE +1E25 ; Ll # LATIN SMALL LETTER H WITH DOT BELOW +1E27 ; Ll # LATIN SMALL LETTER H WITH DIAERESIS +1E29 ; Ll # LATIN SMALL LETTER H WITH CEDILLA +1E2B ; Ll # LATIN SMALL LETTER H WITH BREVE BELOW +1E2D ; Ll # LATIN SMALL LETTER I WITH TILDE BELOW +1E2F ; Ll # LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE +1E31 ; Ll # LATIN SMALL LETTER K WITH ACUTE +1E33 ; Ll # LATIN SMALL LETTER K WITH DOT BELOW +1E35 ; Ll # LATIN SMALL LETTER K WITH LINE BELOW +1E37 ; Ll # LATIN SMALL LETTER L WITH DOT BELOW +1E39 ; Ll # LATIN SMALL LETTER L WITH DOT BELOW AND MACRON +1E3B ; Ll # LATIN SMALL LETTER L WITH LINE BELOW +1E3D ; Ll # LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW +1E3F ; Ll # LATIN SMALL LETTER M WITH ACUTE +1E41 ; Ll # LATIN SMALL LETTER M WITH DOT ABOVE +1E43 ; Ll # LATIN SMALL LETTER M WITH DOT BELOW +1E45 ; Ll # LATIN SMALL LETTER N WITH DOT ABOVE +1E47 ; Ll # LATIN SMALL LETTER N WITH DOT BELOW +1E49 ; Ll # LATIN SMALL LETTER N WITH LINE BELOW +1E4B ; Ll # LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E4D ; Ll # LATIN SMALL LETTER O WITH TILDE AND ACUTE +1E4F ; Ll # LATIN SMALL LETTER O WITH TILDE AND DIAERESIS +1E51 ; Ll # LATIN SMALL LETTER O WITH MACRON AND GRAVE +1E53 ; Ll # LATIN SMALL LETTER O WITH MACRON AND ACUTE +1E55 ; Ll # LATIN SMALL LETTER P WITH ACUTE +1E57 ; Ll # LATIN SMALL LETTER P WITH DOT ABOVE +1E59 ; Ll # LATIN SMALL LETTER R WITH DOT ABOVE +1E5B ; Ll # LATIN SMALL LETTER R WITH DOT BELOW +1E5D ; Ll # LATIN SMALL LETTER R WITH DOT BELOW AND MACRON +1E5F ; Ll # LATIN SMALL LETTER R WITH LINE BELOW +1E61 ; Ll # LATIN SMALL LETTER S WITH DOT ABOVE +1E63 ; Ll # LATIN SMALL LETTER S WITH DOT BELOW +1E65 ; Ll # LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE +1E67 ; Ll # LATIN SMALL LETTER S WITH CARON AND DOT ABOVE +1E69 ; Ll # LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6B ; Ll # LATIN SMALL LETTER T WITH DOT ABOVE +1E6D ; Ll # LATIN SMALL LETTER T WITH DOT BELOW +1E6F ; Ll # LATIN SMALL LETTER T WITH LINE BELOW +1E71 ; Ll # LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E73 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS BELOW +1E75 ; Ll # LATIN SMALL LETTER U WITH TILDE BELOW +1E77 ; Ll # LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW +1E79 ; Ll # LATIN SMALL LETTER U WITH TILDE AND ACUTE +1E7B ; Ll # LATIN SMALL LETTER U WITH MACRON AND DIAERESIS +1E7D ; Ll # LATIN SMALL LETTER V WITH TILDE +1E7F ; Ll # LATIN SMALL LETTER V WITH DOT BELOW +1E81 ; Ll # LATIN SMALL LETTER W WITH GRAVE +1E83 ; Ll # LATIN SMALL LETTER W WITH ACUTE +1E85 ; Ll # LATIN SMALL LETTER W WITH DIAERESIS +1E87 ; Ll # LATIN SMALL LETTER W WITH DOT ABOVE +1E89 ; Ll # LATIN SMALL LETTER W WITH DOT BELOW +1E8B ; Ll # LATIN SMALL LETTER X WITH DOT ABOVE +1E8D ; Ll # LATIN SMALL LETTER X WITH DIAERESIS +1E8F ; Ll # LATIN SMALL LETTER Y WITH DOT ABOVE +1E91 ; Ll # LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E93 ; Ll # LATIN SMALL LETTER Z WITH DOT BELOW +1E95..1E9D ; Ll # [9] LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER LONG S WITH HIGH STROKE +1E9F ; Ll # LATIN SMALL LETTER DELTA +1EA1 ; Ll # LATIN SMALL LETTER A WITH DOT BELOW +1EA3 ; Ll # LATIN SMALL LETTER A WITH HOOK ABOVE +1EA5 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA7 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA9 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAB ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE +1EAD ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAF ; Ll # LATIN SMALL LETTER A WITH BREVE AND ACUTE +1EB1 ; Ll # LATIN SMALL LETTER A WITH BREVE AND GRAVE +1EB3 ; Ll # LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE +1EB5 ; Ll # LATIN SMALL LETTER A WITH BREVE AND TILDE +1EB7 ; Ll # LATIN SMALL LETTER A WITH BREVE AND DOT BELOW +1EB9 ; Ll # LATIN SMALL LETTER E WITH DOT BELOW +1EBB ; Ll # LATIN SMALL LETTER E WITH HOOK ABOVE +1EBD ; Ll # LATIN SMALL LETTER E WITH TILDE +1EBF ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC1 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC3 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC5 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE +1EC7 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC9 ; Ll # LATIN SMALL LETTER I WITH HOOK ABOVE +1ECB ; Ll # LATIN SMALL LETTER I WITH DOT BELOW +1ECD ; Ll # LATIN SMALL LETTER O WITH DOT BELOW +1ECF ; Ll # LATIN SMALL LETTER O WITH HOOK ABOVE +1ED1 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED3 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED5 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED7 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE +1ED9 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDB ; Ll # LATIN SMALL LETTER O WITH HORN AND ACUTE +1EDD ; Ll # LATIN SMALL LETTER O WITH HORN AND GRAVE +1EDF ; Ll # LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE +1EE1 ; Ll # LATIN SMALL LETTER O WITH HORN AND TILDE +1EE3 ; Ll # LATIN SMALL LETTER O WITH HORN AND DOT BELOW +1EE5 ; Ll # LATIN SMALL LETTER U WITH DOT BELOW +1EE7 ; Ll # LATIN SMALL LETTER U WITH HOOK ABOVE +1EE9 ; Ll # LATIN SMALL LETTER U WITH HORN AND ACUTE +1EEB ; Ll # LATIN SMALL LETTER U WITH HORN AND GRAVE +1EED ; Ll # LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE +1EEF ; Ll # LATIN SMALL LETTER U WITH HORN AND TILDE +1EF1 ; Ll # LATIN SMALL LETTER U WITH HORN AND DOT BELOW +1EF3 ; Ll # LATIN SMALL LETTER Y WITH GRAVE +1EF5 ; Ll # LATIN SMALL LETTER Y WITH DOT BELOW +1EF7 ; Ll # LATIN SMALL LETTER Y WITH HOOK ABOVE +1EF9 ; Ll # LATIN SMALL LETTER Y WITH TILDE +1EFB ; Ll # LATIN SMALL LETTER MIDDLE-WELSH LL +1EFD ; Ll # LATIN SMALL LETTER MIDDLE-WELSH V +1EFF..1F07 ; Ll # [9] LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F10..1F15 ; Ll # [6] GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F27 ; Ll # [8] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI +1F30..1F37 ; Ll # [8] GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI +1F40..1F45 ; Ll # [6] GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; Ll # [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F60..1F67 ; Ll # [8] GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +1F70..1F7D ; Ll # [14] GREEK SMALL LETTER ALPHA WITH VARIA..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1F87 ; Ll # [8] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1F90..1F97 ; Ll # [8] GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1FA0..1FA7 ; Ll # [8] GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1FB0..1FB4 ; Ll # [5] GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FB7 ; Ll # [2] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI +1FBE ; Ll # GREEK PROSGEGRAMMENI +1FC2..1FC4 ; Ll # [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FC7 ; Ll # [2] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI +1FD0..1FD3 ; Ll # [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FD7 ; Ll # [2] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI +1FE0..1FE7 ; Ll # [8] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI +1FF2..1FF4 ; Ll # [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FF7 ; Ll # [2] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI +210A ; Ll # SCRIPT SMALL G +210E..210F ; Ll # [2] PLANCK CONSTANT..PLANCK CONSTANT OVER TWO PI +2113 ; Ll # SCRIPT SMALL L +212F ; Ll # SCRIPT SMALL E +2134 ; Ll # SCRIPT SMALL O +2139 ; Ll # INFORMATION SOURCE +213C..213D ; Ll # [2] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK SMALL GAMMA +2146..2149 ; Ll # [4] DOUBLE-STRUCK ITALIC SMALL D..DOUBLE-STRUCK ITALIC SMALL J +214E ; Ll # TURNED SMALL F +2184 ; Ll # LATIN SMALL LETTER REVERSED C +2C30..2C5F ; Ll # [48] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C61 ; Ll # LATIN SMALL LETTER L WITH DOUBLE BAR +2C65..2C66 ; Ll # [2] LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE +2C68 ; Ll # LATIN SMALL LETTER H WITH DESCENDER +2C6A ; Ll # LATIN SMALL LETTER K WITH DESCENDER +2C6C ; Ll # LATIN SMALL LETTER Z WITH DESCENDER +2C71 ; Ll # LATIN SMALL LETTER V WITH RIGHT HOOK +2C73..2C74 ; Ll # [2] LATIN SMALL LETTER W WITH HOOK..LATIN SMALL LETTER V WITH CURL +2C76..2C7B ; Ll # [6] LATIN SMALL LETTER HALF H..LATIN LETTER SMALL CAPITAL TURNED E +2C81 ; Ll # COPTIC SMALL LETTER ALFA +2C83 ; Ll # COPTIC SMALL LETTER VIDA +2C85 ; Ll # COPTIC SMALL LETTER GAMMA +2C87 ; Ll # COPTIC SMALL LETTER DALDA +2C89 ; Ll # COPTIC SMALL LETTER EIE +2C8B ; Ll # COPTIC SMALL LETTER SOU +2C8D ; Ll # COPTIC SMALL LETTER ZATA +2C8F ; Ll # COPTIC SMALL LETTER HATE +2C91 ; Ll # COPTIC SMALL LETTER THETHE +2C93 ; Ll # COPTIC SMALL LETTER IAUDA +2C95 ; Ll # COPTIC SMALL LETTER KAPA +2C97 ; Ll # COPTIC SMALL LETTER LAULA +2C99 ; Ll # COPTIC SMALL LETTER MI +2C9B ; Ll # COPTIC SMALL LETTER NI +2C9D ; Ll # COPTIC SMALL LETTER KSI +2C9F ; Ll # COPTIC SMALL LETTER O +2CA1 ; Ll # COPTIC SMALL LETTER PI +2CA3 ; Ll # COPTIC SMALL LETTER RO +2CA5 ; Ll # COPTIC SMALL LETTER SIMA +2CA7 ; Ll # COPTIC SMALL LETTER TAU +2CA9 ; Ll # COPTIC SMALL LETTER UA +2CAB ; Ll # COPTIC SMALL LETTER FI +2CAD ; Ll # COPTIC SMALL LETTER KHI +2CAF ; Ll # COPTIC SMALL LETTER PSI +2CB1 ; Ll # COPTIC SMALL LETTER OOU +2CB3 ; Ll # COPTIC SMALL LETTER DIALECT-P ALEF +2CB5 ; Ll # COPTIC SMALL LETTER OLD COPTIC AIN +2CB7 ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC EIE +2CB9 ; Ll # COPTIC SMALL LETTER DIALECT-P KAPA +2CBB ; Ll # COPTIC SMALL LETTER DIALECT-P NI +2CBD ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC NI +2CBF ; Ll # COPTIC SMALL LETTER OLD COPTIC OOU +2CC1 ; Ll # COPTIC SMALL LETTER SAMPI +2CC3 ; Ll # COPTIC SMALL LETTER CROSSED SHEI +2CC5 ; Ll # COPTIC SMALL LETTER OLD COPTIC SHEI +2CC7 ; Ll # COPTIC SMALL LETTER OLD COPTIC ESH +2CC9 ; Ll # COPTIC SMALL LETTER AKHMIMIC KHEI +2CCB ; Ll # COPTIC SMALL LETTER DIALECT-P HORI +2CCD ; Ll # COPTIC SMALL LETTER OLD COPTIC HORI +2CCF ; Ll # COPTIC SMALL LETTER OLD COPTIC HA +2CD1 ; Ll # COPTIC SMALL LETTER L-SHAPED HA +2CD3 ; Ll # COPTIC SMALL LETTER OLD COPTIC HEI +2CD5 ; Ll # COPTIC SMALL LETTER OLD COPTIC HAT +2CD7 ; Ll # COPTIC SMALL LETTER OLD COPTIC GANGIA +2CD9 ; Ll # COPTIC SMALL LETTER OLD COPTIC DJA +2CDB ; Ll # COPTIC SMALL LETTER OLD COPTIC SHIMA +2CDD ; Ll # COPTIC SMALL LETTER OLD NUBIAN SHIMA +2CDF ; Ll # COPTIC SMALL LETTER OLD NUBIAN NGI +2CE1 ; Ll # COPTIC SMALL LETTER OLD NUBIAN NYI +2CE3..2CE4 ; Ll # [2] COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC SYMBOL KAI +2CEC ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI +2CEE ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CF3 ; Ll # COPTIC SMALL LETTER BOHAIRIC KHEI +2D00..2D25 ; Ll # [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; Ll # GEORGIAN SMALL LETTER YN +2D2D ; Ll # GEORGIAN SMALL LETTER AEN +A641 ; Ll # CYRILLIC SMALL LETTER ZEMLYA +A643 ; Ll # CYRILLIC SMALL LETTER DZELO +A645 ; Ll # CYRILLIC SMALL LETTER REVERSED DZE +A647 ; Ll # CYRILLIC SMALL LETTER IOTA +A649 ; Ll # CYRILLIC SMALL LETTER DJERV +A64B ; Ll # CYRILLIC SMALL LETTER MONOGRAPH UK +A64D ; Ll # CYRILLIC SMALL LETTER BROAD OMEGA +A64F ; Ll # CYRILLIC SMALL LETTER NEUTRAL YER +A651 ; Ll # CYRILLIC SMALL LETTER YERU WITH BACK YER +A653 ; Ll # CYRILLIC SMALL LETTER IOTIFIED YAT +A655 ; Ll # CYRILLIC SMALL LETTER REVERSED YU +A657 ; Ll # CYRILLIC SMALL LETTER IOTIFIED A +A659 ; Ll # CYRILLIC SMALL LETTER CLOSED LITTLE YUS +A65B ; Ll # CYRILLIC SMALL LETTER BLENDED YUS +A65D ; Ll # CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS +A65F ; Ll # CYRILLIC SMALL LETTER YN +A661 ; Ll # CYRILLIC SMALL LETTER REVERSED TSE +A663 ; Ll # CYRILLIC SMALL LETTER SOFT DE +A665 ; Ll # CYRILLIC SMALL LETTER SOFT EL +A667 ; Ll # CYRILLIC SMALL LETTER SOFT EM +A669 ; Ll # CYRILLIC SMALL LETTER MONOCULAR O +A66B ; Ll # CYRILLIC SMALL LETTER BINOCULAR O +A66D ; Ll # CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A681 ; Ll # CYRILLIC SMALL LETTER DWE +A683 ; Ll # CYRILLIC SMALL LETTER DZWE +A685 ; Ll # CYRILLIC SMALL LETTER ZHWE +A687 ; Ll # CYRILLIC SMALL LETTER CCHE +A689 ; Ll # CYRILLIC SMALL LETTER DZZE +A68B ; Ll # CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK +A68D ; Ll # CYRILLIC SMALL LETTER TWE +A68F ; Ll # CYRILLIC SMALL LETTER TSWE +A691 ; Ll # CYRILLIC SMALL LETTER TSSE +A693 ; Ll # CYRILLIC SMALL LETTER TCHE +A695 ; Ll # CYRILLIC SMALL LETTER HWE +A697 ; Ll # CYRILLIC SMALL LETTER SHWE +A699 ; Ll # CYRILLIC SMALL LETTER DOUBLE O +A69B ; Ll # CYRILLIC SMALL LETTER CROSSED O +A723 ; Ll # LATIN SMALL LETTER EGYPTOLOGICAL ALEF +A725 ; Ll # LATIN SMALL LETTER EGYPTOLOGICAL AIN +A727 ; Ll # LATIN SMALL LETTER HENG +A729 ; Ll # LATIN SMALL LETTER TZ +A72B ; Ll # LATIN SMALL LETTER TRESILLO +A72D ; Ll # LATIN SMALL LETTER CUATRILLO +A72F..A731 ; Ll # [3] LATIN SMALL LETTER CUATRILLO WITH COMMA..LATIN LETTER SMALL CAPITAL S +A733 ; Ll # LATIN SMALL LETTER AA +A735 ; Ll # LATIN SMALL LETTER AO +A737 ; Ll # LATIN SMALL LETTER AU +A739 ; Ll # LATIN SMALL LETTER AV +A73B ; Ll # LATIN SMALL LETTER AV WITH HORIZONTAL BAR +A73D ; Ll # LATIN SMALL LETTER AY +A73F ; Ll # LATIN SMALL LETTER REVERSED C WITH DOT +A741 ; Ll # LATIN SMALL LETTER K WITH STROKE +A743 ; Ll # LATIN SMALL LETTER K WITH DIAGONAL STROKE +A745 ; Ll # LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE +A747 ; Ll # LATIN SMALL LETTER BROKEN L +A749 ; Ll # LATIN SMALL LETTER L WITH HIGH STROKE +A74B ; Ll # LATIN SMALL LETTER O WITH LONG STROKE OVERLAY +A74D ; Ll # LATIN SMALL LETTER O WITH LOOP +A74F ; Ll # LATIN SMALL LETTER OO +A751 ; Ll # LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER +A753 ; Ll # LATIN SMALL LETTER P WITH FLOURISH +A755 ; Ll # LATIN SMALL LETTER P WITH SQUIRREL TAIL +A757 ; Ll # LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER +A759 ; Ll # LATIN SMALL LETTER Q WITH DIAGONAL STROKE +A75B ; Ll # LATIN SMALL LETTER R ROTUNDA +A75D ; Ll # LATIN SMALL LETTER RUM ROTUNDA +A75F ; Ll # LATIN SMALL LETTER V WITH DIAGONAL STROKE +A761 ; Ll # LATIN SMALL LETTER VY +A763 ; Ll # LATIN SMALL LETTER VISIGOTHIC Z +A765 ; Ll # LATIN SMALL LETTER THORN WITH STROKE +A767 ; Ll # LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER +A769 ; Ll # LATIN SMALL LETTER VEND +A76B ; Ll # LATIN SMALL LETTER ET +A76D ; Ll # LATIN SMALL LETTER IS +A76F ; Ll # LATIN SMALL LETTER CON +A771..A778 ; Ll # [8] LATIN SMALL LETTER DUM..LATIN SMALL LETTER UM +A77A ; Ll # LATIN SMALL LETTER INSULAR D +A77C ; Ll # LATIN SMALL LETTER INSULAR F +A77F ; Ll # LATIN SMALL LETTER TURNED INSULAR G +A781 ; Ll # LATIN SMALL LETTER TURNED L +A783 ; Ll # LATIN SMALL LETTER INSULAR R +A785 ; Ll # LATIN SMALL LETTER INSULAR S +A787 ; Ll # LATIN SMALL LETTER INSULAR T +A78C ; Ll # LATIN SMALL LETTER SALTILLO +A78E ; Ll # LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A791 ; Ll # LATIN SMALL LETTER N WITH DESCENDER +A793..A795 ; Ll # [3] LATIN SMALL LETTER C WITH BAR..LATIN SMALL LETTER H WITH PALATAL HOOK +A797 ; Ll # LATIN SMALL LETTER B WITH FLOURISH +A799 ; Ll # LATIN SMALL LETTER F WITH STROKE +A79B ; Ll # LATIN SMALL LETTER VOLAPUK AE +A79D ; Ll # LATIN SMALL LETTER VOLAPUK OE +A79F ; Ll # LATIN SMALL LETTER VOLAPUK UE +A7A1 ; Ll # LATIN SMALL LETTER G WITH OBLIQUE STROKE +A7A3 ; Ll # LATIN SMALL LETTER K WITH OBLIQUE STROKE +A7A5 ; Ll # LATIN SMALL LETTER N WITH OBLIQUE STROKE +A7A7 ; Ll # LATIN SMALL LETTER R WITH OBLIQUE STROKE +A7A9 ; Ll # LATIN SMALL LETTER S WITH OBLIQUE STROKE +A7AF ; Ll # LATIN LETTER SMALL CAPITAL Q +A7B5 ; Ll # LATIN SMALL LETTER BETA +A7B7 ; Ll # LATIN SMALL LETTER OMEGA +A7B9 ; Ll # LATIN SMALL LETTER U WITH STROKE +A7BB ; Ll # LATIN SMALL LETTER GLOTTAL A +A7BD ; Ll # LATIN SMALL LETTER GLOTTAL I +A7BF ; Ll # LATIN SMALL LETTER GLOTTAL U +A7C1 ; Ll # LATIN SMALL LETTER OLD POLISH O +A7C3 ; Ll # LATIN SMALL LETTER ANGLICANA W +A7C8 ; Ll # LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY +A7CA ; Ll # LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D1 ; Ll # LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Ll # LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Ll # LATIN SMALL LETTER DOUBLE WYNN +A7D7 ; Ll # LATIN SMALL LETTER MIDDLE SCOTS S +A7D9 ; Ll # LATIN SMALL LETTER SIGMOID S +A7F6 ; Ll # LATIN SMALL LETTER REVERSED HALF H +A7FA ; Ll # LATIN LETTER SMALL CAPITAL TURNED M +AB30..AB5A ; Ll # [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB60..AB68 ; Ll # [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB70..ABBF ; Ll # [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +FB00..FB06 ; Ll # [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; Ll # [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FF41..FF5A ; Ll # [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +10428..1044F ; Ll # [40] DESERET SMALL LETTER LONG I..DESERET SMALL LETTER EW +104D8..104FB ; Ll # [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10597..105A1 ; Ll # [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; Ll # [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; Ll # [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; Ll # [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10CC0..10CF2 ; Ll # [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +118C0..118DF ; Ll # [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +16E60..16E7F ; Ll # [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +1D41A..1D433 ; Ll # [26] MATHEMATICAL BOLD SMALL A..MATHEMATICAL BOLD SMALL Z +1D44E..1D454 ; Ll # [7] MATHEMATICAL ITALIC SMALL A..MATHEMATICAL ITALIC SMALL G +1D456..1D467 ; Ll # [18] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL Z +1D482..1D49B ; Ll # [26] MATHEMATICAL BOLD ITALIC SMALL A..MATHEMATICAL BOLD ITALIC SMALL Z +1D4B6..1D4B9 ; Ll # [4] MATHEMATICAL SCRIPT SMALL A..MATHEMATICAL SCRIPT SMALL D +1D4BB ; Ll # MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; Ll # [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D4CF ; Ll # [11] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL SCRIPT SMALL Z +1D4EA..1D503 ; Ll # [26] MATHEMATICAL BOLD SCRIPT SMALL A..MATHEMATICAL BOLD SCRIPT SMALL Z +1D51E..1D537 ; Ll # [26] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL FRAKTUR SMALL Z +1D552..1D56B ; Ll # [26] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL DOUBLE-STRUCK SMALL Z +1D586..1D59F ; Ll # [26] MATHEMATICAL BOLD FRAKTUR SMALL A..MATHEMATICAL BOLD FRAKTUR SMALL Z +1D5BA..1D5D3 ; Ll # [26] MATHEMATICAL SANS-SERIF SMALL A..MATHEMATICAL SANS-SERIF SMALL Z +1D5EE..1D607 ; Ll # [26] MATHEMATICAL SANS-SERIF BOLD SMALL A..MATHEMATICAL SANS-SERIF BOLD SMALL Z +1D622..1D63B ; Ll # [26] MATHEMATICAL SANS-SERIF ITALIC SMALL A..MATHEMATICAL SANS-SERIF ITALIC SMALL Z +1D656..1D66F ; Ll # [26] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z +1D68A..1D6A5 ; Ll # [28] MATHEMATICAL MONOSPACE SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6C2..1D6DA ; Ll # [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DC..1D6E1 ; Ll # [6] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL BOLD PI SYMBOL +1D6FC..1D714 ; Ll # [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D716..1D71B ; Ll # [6] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL ITALIC PI SYMBOL +1D736..1D74E ; Ll # [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D750..1D755 ; Ll # [6] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC PI SYMBOL +1D770..1D788 ; Ll # [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D78A..1D78F ; Ll # [6] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD PI SYMBOL +1D7AA..1D7C2 ; Ll # [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C4..1D7C9 ; Ll # [6] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL +1D7CB ; Ll # MATHEMATICAL BOLD SMALL DIGAMMA +1DF00..1DF09 ; Ll # [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0B..1DF1E ; Ll # [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; Ll # [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E922..1E943 ; Ll # [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA + +# Total code points: 2233 + +# ================================================ + +# General_Category=Titlecase_Letter + +01C5 ; Lt # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON +01C8 ; Lt # LATIN CAPITAL LETTER L WITH SMALL LETTER J +01CB ; Lt # LATIN CAPITAL LETTER N WITH SMALL LETTER J +01F2 ; Lt # LATIN CAPITAL LETTER D WITH SMALL LETTER Z +1F88..1F8F ; Lt # [8] GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1F98..1F9F ; Lt # [8] GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FA8..1FAF ; Lt # [8] GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FBC ; Lt # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FCC ; Lt # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FFC ; Lt # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI + +# Total code points: 31 + +# ================================================ + +# General_Category=Modifier_Letter + +02B0..02C1 ; Lm # [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C6..02D1 ; Lm # [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON +02E0..02E4 ; Lm # [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02EC ; Lm # MODIFIER LETTER VOICING +02EE ; Lm # MODIFIER LETTER DOUBLE APOSTROPHE +0374 ; Lm # GREEK NUMERAL SIGN +037A ; Lm # GREEK YPOGEGRAMMENI +0559 ; Lm # ARMENIAN MODIFIER LETTER LEFT HALF RING +0640 ; Lm # ARABIC TATWEEL +06E5..06E6 ; Lm # [2] ARABIC SMALL WAW..ARABIC SMALL YEH +07F4..07F5 ; Lm # [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07FA ; Lm # NKO LAJANYALAN +081A ; Lm # SAMARITAN MODIFIER LETTER EPENTHETIC YUT +0824 ; Lm # SAMARITAN MODIFIER LETTER SHORT A +0828 ; Lm # SAMARITAN MODIFIER LETTER I +08C9 ; Lm # ARABIC SMALL FARSI YEH +0971 ; Lm # DEVANAGARI SIGN HIGH SPACING DOT +0E46 ; Lm # THAI CHARACTER MAIYAMOK +0EC6 ; Lm # LAO KO LA +10FC ; Lm # MODIFIER LETTER GEORGIAN NAR +17D7 ; Lm # KHMER SIGN LEK TOO +1843 ; Lm # MONGOLIAN LETTER TODO LONG VOWEL SIGN +1AA7 ; Lm # TAI THAM SIGN MAI YAMOK +1C78..1C7D ; Lm # [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1D2C..1D6A ; Lm # [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D78 ; Lm # MODIFIER LETTER CYRILLIC EN +1D9B..1DBF ; Lm # [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +2071 ; Lm # SUPERSCRIPT LATIN SMALL LETTER I +207F ; Lm # SUPERSCRIPT LATIN SMALL LETTER N +2090..209C ; Lm # [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +2C7C..2C7D ; Lm # [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2D6F ; Lm # TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2E2F ; Lm # VERTICAL TILDE +3005 ; Lm # IDEOGRAPHIC ITERATION MARK +3031..3035 ; Lm # [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF +303B ; Lm # VERTICAL IDEOGRAPHIC ITERATION MARK +309D..309E ; Lm # [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +30FC..30FE ; Lm # [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +A015 ; Lm # YI SYLLABLE WU +A4F8..A4FD ; Lm # [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A60C ; Lm # VAI SYLLABLE LENGTHENER +A67F ; Lm # CYRILLIC PAYEROK +A69C..A69D ; Lm # [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A717..A71F ; Lm # [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A770 ; Lm # MODIFIER LETTER US +A788 ; Lm # MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A7F2..A7F4 ; Lm # [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F8..A7F9 ; Lm # [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A9CF ; Lm # JAVANESE PANGRANGKEP +A9E6 ; Lm # MYANMAR MODIFIER LETTER SHAN REDUPLICATION +AA70 ; Lm # MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AADD ; Lm # TAI VIET SYMBOL SAM +AAF3..AAF4 ; Lm # [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AB5C..AB5F ; Lm # [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB69 ; Lm # MODIFIER LETTER SMALL TURNED W +FF70 ; Lm # HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF9E..FF9F ; Lm # [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +10780..10785 ; Lm # [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; Lm # [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; Lm # [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +16B40..16B43 ; Lm # [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16F93..16F9F ; Lm # [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; Lm # [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE3 ; Lm # OLD CHINESE ITERATION MARK +1AFF0..1AFF3 ; Lm # [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; Lm # [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; Lm # [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1E030..1E06D ; Lm # [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E137..1E13D ; Lm # [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E4EB ; Lm # NAG MUNDARI SIGN OJOD +1E94B ; Lm # ADLAM NASALIZATION MARK + +# Total code points: 397 + +# ================================================ + +# General_Category=Other_Letter + +00AA ; Lo # FEMININE ORDINAL INDICATOR +00BA ; Lo # MASCULINE ORDINAL INDICATOR +01BB ; Lo # LATIN LETTER TWO WITH STROKE +01C0..01C3 ; Lo # [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +0294 ; Lo # LATIN LETTER GLOTTAL STOP +05D0..05EA ; Lo # [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; Lo # [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +0620..063F ; Lo # [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0641..064A ; Lo # [10] ARABIC LETTER FEH..ARABIC LETTER YEH +066E..066F ; Lo # [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0671..06D3 ; Lo # [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D5 ; Lo # ARABIC LETTER AE +06EE..06EF ; Lo # [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06FA..06FC ; Lo # [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FF ; Lo # ARABIC LETTER HEH WITH INVERTED V +0710 ; Lo # SYRIAC LETTER ALAPH +0712..072F ; Lo # [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +074D..07A5 ; Lo # [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU +07B1 ; Lo # THAANA LETTER NAA +07CA..07EA ; Lo # [33] NKO LETTER A..NKO LETTER JONA RA +0800..0815 ; Lo # [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0840..0858 ; Lo # [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0860..086A ; Lo # [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; Lo # [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0889..088E ; Lo # [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +08A0..08C8 ; Lo # [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +0904..0939 ; Lo # [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093D ; Lo # DEVANAGARI SIGN AVAGRAHA +0950 ; Lo # DEVANAGARI OM +0958..0961 ; Lo # [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0972..0980 ; Lo # [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI +0985..098C ; Lo # [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; Lo # [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; Lo # [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; Lo # [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; Lo # BENGALI LETTER LA +09B6..09B9 ; Lo # [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BD ; Lo # BENGALI SIGN AVAGRAHA +09CE ; Lo # BENGALI LETTER KHANDA TA +09DC..09DD ; Lo # [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; Lo # [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09F0..09F1 ; Lo # [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09FC ; Lo # BENGALI LETTER VEDIC ANUSVARA +0A05..0A0A ; Lo # [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; Lo # [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; Lo # [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; Lo # [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; Lo # [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; Lo # [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; Lo # [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A59..0A5C ; Lo # [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; Lo # GURMUKHI LETTER FA +0A72..0A74 ; Lo # [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A85..0A8D ; Lo # [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; Lo # [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; Lo # [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; Lo # [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; Lo # [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; Lo # [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABD ; Lo # GUJARATI SIGN AVAGRAHA +0AD0 ; Lo # GUJARATI OM +0AE0..0AE1 ; Lo # [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AF9 ; Lo # GUJARATI LETTER ZHA +0B05..0B0C ; Lo # [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; Lo # [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; Lo # [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; Lo # [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; Lo # [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; Lo # [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3D ; Lo # ORIYA SIGN AVAGRAHA +0B5C..0B5D ; Lo # [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; Lo # [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B71 ; Lo # ORIYA LETTER WA +0B83 ; Lo # TAMIL SIGN VISARGA +0B85..0B8A ; Lo # [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; Lo # [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; Lo # [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; Lo # [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; Lo # TAMIL LETTER JA +0B9E..0B9F ; Lo # [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; Lo # [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; Lo # [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; Lo # [12] TAMIL LETTER MA..TAMIL LETTER HA +0BD0 ; Lo # TAMIL OM +0C05..0C0C ; Lo # [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; Lo # [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; Lo # [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; Lo # [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3D ; Lo # TELUGU SIGN AVAGRAHA +0C58..0C5A ; Lo # [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; Lo # TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; Lo # [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C80 ; Lo # KANNADA SIGN SPACING CANDRABINDU +0C85..0C8C ; Lo # [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; Lo # [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; Lo # [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; Lo # [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; Lo # [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBD ; Lo # KANNADA SIGN AVAGRAHA +0CDD..0CDE ; Lo # [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; Lo # [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CF1..0CF2 ; Lo # [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0D04..0D0C ; Lo # [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; Lo # [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; Lo # [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3D ; Lo # MALAYALAM SIGN AVAGRAHA +0D4E ; Lo # MALAYALAM LETTER DOT REPH +0D54..0D56 ; Lo # [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D5F..0D61 ; Lo # [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D7A..0D7F ; Lo # [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D85..0D96 ; Lo # [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; Lo # [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; Lo # [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; Lo # SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; Lo # [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0E01..0E30 ; Lo # [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E32..0E33 ; Lo # [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E40..0E45 ; Lo # [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E81..0E82 ; Lo # [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; Lo # LAO LETTER KHO TAM +0E86..0E8A ; Lo # [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; Lo # [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; Lo # LAO LETTER LO LOOT +0EA7..0EB0 ; Lo # [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB2..0EB3 ; Lo # [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EBD ; Lo # LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; Lo # [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EDC..0EDF ; Lo # [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; Lo # TIBETAN SYLLABLE OM +0F40..0F47 ; Lo # [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; Lo # [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F88..0F8C ; Lo # [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +1000..102A ; Lo # [43] MYANMAR LETTER KA..MYANMAR LETTER AU +103F ; Lo # MYANMAR LETTER GREAT SA +1050..1055 ; Lo # [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +105A..105D ; Lo # [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +1061 ; Lo # MYANMAR LETTER SGAW KAREN SHA +1065..1066 ; Lo # [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +106E..1070 ; Lo # [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1075..1081 ; Lo # [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +108E ; Lo # MYANMAR LETTER RUMAI PALAUNG FA +1100..1248 ; Lo # [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA +124A..124D ; Lo # [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; Lo # [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; Lo # ETHIOPIC SYLLABLE QHWA +125A..125D ; Lo # [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; Lo # [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; Lo # [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; Lo # [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; Lo # [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; Lo # [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; Lo # ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; Lo # [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; Lo # [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; Lo # [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; Lo # [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; Lo # [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +1380..138F ; Lo # [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1401..166C ; Lo # [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166F..167F ; Lo # [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1681..169A ; Lo # [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +16A0..16EA ; Lo # [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16F1..16F8 ; Lo # [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; Lo # [18] TAGALOG LETTER A..TAGALOG LETTER HA +171F..1731 ; Lo # [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA +1740..1751 ; Lo # [18] BUHID LETTER A..BUHID LETTER HA +1760..176C ; Lo # [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; Lo # [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1780..17B3 ; Lo # [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17DC ; Lo # KHMER SIGN AVAKRAHASANYA +1820..1842 ; Lo # [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1844..1878 ; Lo # [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; Lo # [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1887..18A8 ; Lo # [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18AA ; Lo # MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; Lo # [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; Lo # [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1950..196D ; Lo # [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; Lo # [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; Lo # [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; Lo # [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +1A00..1A16 ; Lo # [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A20..1A54 ; Lo # [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1B05..1B33 ; Lo # [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B45..1B4C ; Lo # [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B83..1BA0 ; Lo # [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BAE..1BAF ; Lo # [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BBA..1BE5 ; Lo # [44] SUNDANESE AVAGRAHA..BATAK LETTER U +1C00..1C23 ; Lo # [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C4D..1C4F ; Lo # [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C5A..1C77 ; Lo # [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1CE9..1CEC ; Lo # [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CEE..1CF3 ; Lo # [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF5..1CF6 ; Lo # [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CFA ; Lo # VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +2135..2138 ; Lo # [4] ALEF SYMBOL..DALET SYMBOL +2D30..2D67 ; Lo # [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D80..2D96 ; Lo # [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; Lo # [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; Lo # [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; Lo # [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; Lo # [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; Lo # [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; Lo # [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; Lo # [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; Lo # [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +3006 ; Lo # IDEOGRAPHIC CLOSING MARK +303C ; Lo # MASU MARK +3041..3096 ; Lo # [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE +309F ; Lo # HIRAGANA DIGRAPH YORI +30A1..30FA ; Lo # [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FF ; Lo # KATAKANA DIGRAPH KOTO +3105..312F ; Lo # [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; Lo # [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +31A0..31BF ; Lo # [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31F0..31FF ; Lo # [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3400..4DBF ; Lo # [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4E00..A014 ; Lo # [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E +A016..A48C ; Lo # [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A4D0..A4F7 ; Lo # [40] LISU LETTER BA..LISU LETTER OE +A500..A60B ; Lo # [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A610..A61F ; Lo # [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A62A..A62B ; Lo # [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A66E ; Lo # CYRILLIC LETTER MULTIOCULAR O +A6A0..A6E5 ; Lo # [70] BAMUM LETTER A..BAMUM LETTER KI +A78F ; Lo # LATIN LETTER SINOLOGICAL DOT +A7F7 ; Lo # LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7FB..A801 ; Lo # [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I +A803..A805 ; Lo # [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A807..A80A ; Lo # [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80C..A822 ; Lo # [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A840..A873 ; Lo # [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A882..A8B3 ; Lo # [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8F2..A8F7 ; Lo # [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8FB ; Lo # DEVANAGARI HEADSTROKE +A8FD..A8FE ; Lo # [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A90A..A925 ; Lo # [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A930..A946 ; Lo # [23] REJANG LETTER KA..REJANG LETTER A +A960..A97C ; Lo # [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A984..A9B2 ; Lo # [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9E0..A9E4 ; Lo # [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E7..A9EF ; Lo # [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9FA..A9FE ; Lo # [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; Lo # [41] CHAM LETTER A..CHAM LETTER HA +AA40..AA42 ; Lo # [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA44..AA4B ; Lo # [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA60..AA6F ; Lo # [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA71..AA76 ; Lo # [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA7A ; Lo # MYANMAR LETTER AITON RA +AA7E..AAAF ; Lo # [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O +AAB1 ; Lo # TAI VIET VOWEL AA +AAB5..AAB6 ; Lo # [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB9..AABD ; Lo # [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AAC0 ; Lo # TAI VIET TONE MAI NUENG +AAC2 ; Lo # TAI VIET TONE MAI SONG +AADB..AADC ; Lo # [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AAE0..AAEA ; Lo # [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAF2 ; Lo # MEETEI MAYEK ANJI +AB01..AB06 ; Lo # [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; Lo # [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; Lo # [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; Lo # [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; Lo # [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +ABC0..ABE2 ; Lo # [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +AC00..D7A3 ; Lo # [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH +D7B0..D7C6 ; Lo # [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; Lo # [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +F900..FA6D ; Lo # [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA70..FAD9 ; Lo # [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FB1D ; Lo # HEBREW LETTER YOD WITH HIRIQ +FB1F..FB28 ; Lo # [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB2A..FB36 ; Lo # [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; Lo # [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; Lo # HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; Lo # [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; Lo # [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FBB1 ; Lo # [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBD3..FD3D ; Lo # [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD50..FD8F ; Lo # [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; Lo # [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDF0..FDFB ; Lo # [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FE70..FE74 ; Lo # [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; Lo # [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FF66..FF6F ; Lo # [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU +FF71..FF9D ; Lo # [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FFA0..FFBE ; Lo # [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; Lo # [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; Lo # [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; Lo # [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; Lo # [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +10000..1000B ; Lo # [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; Lo # [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; Lo # [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; Lo # [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; Lo # [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; Lo # [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; Lo # [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10280..1029C ; Lo # [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; Lo # [49] CARIAN LETTER A..CARIAN LETTER UUU3 +10300..1031F ; Lo # [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +1032D..10340 ; Lo # [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA +10342..10349 ; Lo # [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +10350..10375 ; Lo # [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10380..1039D ; Lo # [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +103A0..103C3 ; Lo # [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; Lo # [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +10450..1049D ; Lo # [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO +10500..10527 ; Lo # [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; Lo # [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +10600..10736 ; Lo # [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; Lo # [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; Lo # [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10800..10805 ; Lo # [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; Lo # CYPRIOT SYLLABLE JO +1080A..10835 ; Lo # [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; Lo # [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; Lo # CYPRIOT SYLLABLE ZA +1083F..10855 ; Lo # [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW +10860..10876 ; Lo # [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10880..1089E ; Lo # [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108E0..108F2 ; Lo # [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; Lo # [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +10900..10915 ; Lo # [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10920..10939 ; Lo # [26] LYDIAN LETTER A..LYDIAN LETTER C +10980..109B7 ; Lo # [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA +109BE..109BF ; Lo # [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +10A00 ; Lo # KHAROSHTHI LETTER A +10A10..10A13 ; Lo # [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; Lo # [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; Lo # [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A60..10A7C ; Lo # [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A80..10A9C ; Lo # [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10AC0..10AC7 ; Lo # [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC9..10AE4 ; Lo # [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10B00..10B35 ; Lo # [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B40..10B55 ; Lo # [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B60..10B72 ; Lo # [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B80..10B91 ; Lo # [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10C00..10C48 ; Lo # [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10D00..10D23 ; Lo # [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10E80..10EA9 ; Lo # [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EB0..10EB1 ; Lo # [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10F00..10F1C ; Lo # [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F27 ; Lo # OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; Lo # [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F70..10F81 ; Lo # [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10FB0..10FC4 ; Lo # [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FE0..10FF6 ; Lo # [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11003..11037 ; Lo # [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA +11071..11072 ; Lo # [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11075 ; Lo # BRAHMI LETTER OLD TAMIL LLA +11083..110AF ; Lo # [45] KAITHI LETTER A..KAITHI LETTER HA +110D0..110E8 ; Lo # [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +11103..11126 ; Lo # [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11144 ; Lo # CHAKMA LETTER LHAA +11147 ; Lo # CHAKMA LETTER VAA +11150..11172 ; Lo # [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11176 ; Lo # MAHAJANI LIGATURE SHRI +11183..111B2 ; Lo # [48] SHARADA LETTER A..SHARADA LETTER HA +111C1..111C4 ; Lo # [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111DA ; Lo # SHARADA EKAM +111DC ; Lo # SHARADA HEADSTROKE +11200..11211 ; Lo # [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; Lo # [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1123F..11240 ; Lo # [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11280..11286 ; Lo # [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; Lo # MULTANI LETTER GHA +1128A..1128D ; Lo # [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; Lo # [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; Lo # [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112B0..112DE ; Lo # [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +11305..1130C ; Lo # [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; Lo # [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; Lo # [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; Lo # [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; Lo # [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; Lo # [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133D ; Lo # GRANTHA SIGN AVAGRAHA +11350 ; Lo # GRANTHA OM +1135D..11361 ; Lo # [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL +11400..11434 ; Lo # [53] NEWA LETTER A..NEWA LETTER HA +11447..1144A ; Lo # [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1145F..11461 ; Lo # [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; Lo # [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114C4..114C5 ; Lo # [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C7 ; Lo # TIRHUTA OM +11580..115AE ; Lo # [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115D8..115DB ; Lo # [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +11600..1162F ; Lo # [48] MODI LETTER A..MODI LETTER LLA +11644 ; Lo # MODI SIGN HUVA +11680..116AA ; Lo # [43] TAKRI LETTER A..TAKRI LETTER RRA +116B8 ; Lo # TAKRI LETTER ARCHAIC KHA +11700..1171A ; Lo # [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +11740..11746 ; Lo # [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; Lo # [44] DOGRA LETTER A..DOGRA LETTER RRA +118FF..11906 ; Lo # [8] WARANG CITI OM..DIVES AKURU LETTER E +11909 ; Lo # DIVES AKURU LETTER O +1190C..11913 ; Lo # [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; Lo # [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; Lo # [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +1193F ; Lo # DIVES AKURU PREFIXED NASAL SIGN +11941 ; Lo # DIVES AKURU INITIAL RA +119A0..119A7 ; Lo # [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; Lo # [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119E1 ; Lo # NANDINAGARI SIGN AVAGRAHA +119E3 ; Lo # NANDINAGARI HEADSTROKE +11A00 ; Lo # ZANABAZAR SQUARE LETTER A +11A0B..11A32 ; Lo # [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A3A ; Lo # ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A50 ; Lo # SOYOMBO LETTER A +11A5C..11A89 ; Lo # [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A9D ; Lo # SOYOMBO MARK PLUTA +11AB0..11AF8 ; Lo # [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11C00..11C08 ; Lo # [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; Lo # [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C40 ; Lo # BHAIKSUKI SIGN AVAGRAHA +11C72..11C8F ; Lo # [30] MARCHEN LETTER KA..MARCHEN LETTER A +11D00..11D06 ; Lo # [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; Lo # [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; Lo # [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D46 ; Lo # MASARAM GONDI REPHA +11D60..11D65 ; Lo # [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; Lo # [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; Lo # [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D98 ; Lo # GUNJALA GONDI OM +11EE0..11EF2 ; Lo # [19] MAKASAR LETTER KA..MAKASAR ANGKA +11F02 ; Lo # KAWI SIGN REPHA +11F04..11F10 ; Lo # [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; Lo # [34] KAWI LETTER KA..KAWI LETTER JNYA +11FB0 ; Lo # LISU LETTER YHA +12000..12399 ; Lo # [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12480..12543 ; Lo # [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; Lo # [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +13000..1342F ; Lo # [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13441..13446 ; Lo # [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +14400..14646 ; Lo # [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; Lo # [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; Lo # [31] MRO LETTER TA..MRO LETTER TEK +16A70..16ABE ; Lo # [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AD0..16AED ; Lo # [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16B00..16B2F ; Lo # [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B63..16B77 ; Lo # [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; Lo # [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16F00..16F4A ; Lo # [75] MIAO LETTER PA..MIAO LETTER RTE +16F50 ; Lo # MIAO LETTER NASALIZATION +17000..187F7 ; Lo # [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18CD5 ; Lo # [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; Lo # [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1B000..1B122 ; Lo # [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU +1B132 ; Lo # HIRAGANA LETTER SMALL KO +1B150..1B152 ; Lo # [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; Lo # KATAKANA LETTER SMALL KO +1B164..1B167 ; Lo # [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; Lo # [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; Lo # [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; Lo # [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; Lo # [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; Lo # [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1DF0A ; Lo # LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1E100..1E12C ; Lo # [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E14E ; Lo # NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E290..1E2AD ; Lo # [30] TOTO LETTER PA..TOTO LETTER A +1E2C0..1E2EB ; Lo # [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E4D0..1E4EA ; Lo # [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E7E0..1E7E6 ; Lo # [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; Lo # [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; Lo # [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; Lo # [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; Lo # [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1EE00..1EE03 ; Lo # [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; Lo # [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; Lo # [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; Lo # ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; Lo # ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; Lo # [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; Lo # [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; Lo # ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; Lo # ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; Lo # ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; Lo # ARABIC MATHEMATICAL TAILED HAH +1EE49 ; Lo # ARABIC MATHEMATICAL TAILED YEH +1EE4B ; Lo # ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; Lo # [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; Lo # [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; Lo # ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; Lo # ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; Lo # ARABIC MATHEMATICAL TAILED DAD +1EE5B ; Lo # ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; Lo # ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; Lo # ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; Lo # [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; Lo # ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; Lo # [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; Lo # [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; Lo # [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; Lo # [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; Lo # ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; Lo # [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; Lo # [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; Lo # [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; Lo # [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; Lo # [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +20000..2A6DF ; Lo # [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A700..2B739 ; Lo # [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B740..2B81D ; Lo # [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEA1 ; Lo # [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEB0..2EBE0 ; Lo # [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBF0..2EE5D ; Lo # [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2F800..2FA1D ; Lo # [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +30000..3134A ; Lo # [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +31350..323AF ; Lo # [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF + +# Total code points: 132234 + +# ================================================ + +# General_Category=Nonspacing_Mark + +0300..036F ; Mn # [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X +0483..0487 ; Mn # [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0591..05BD ; Mn # [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BF ; Mn # HEBREW POINT RAFE +05C1..05C2 ; Mn # [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C4..05C5 ; Mn # [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C7 ; Mn # HEBREW POINT QAMATS QATAN +0610..061A ; Mn # [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +064B..065F ; Mn # [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0670 ; Mn # ARABIC LETTER SUPERSCRIPT ALEF +06D6..06DC ; Mn # [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DF..06E4 ; Mn # [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E7..06E8 ; Mn # [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06EA..06ED ; Mn # [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +0711 ; Mn # SYRIAC LETTER SUPERSCRIPT ALAPH +0730..074A ; Mn # [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +07A6..07B0 ; Mn # [11] THAANA ABAFILI..THAANA SUKUN +07EB..07F3 ; Mn # [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07FD ; Mn # NKO DANTAYALAN +0816..0819 ; Mn # [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081B..0823 ; Mn # [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0825..0827 ; Mn # [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0829..082D ; Mn # [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0859..085B ; Mn # [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +0898..089F ; Mn # [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08CA..08E1 ; Mn # [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E3..0902 ; Mn # [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA +093A ; Mn # DEVANAGARI VOWEL SIGN OE +093C ; Mn # DEVANAGARI SIGN NUKTA +0941..0948 ; Mn # [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +094D ; Mn # DEVANAGARI SIGN VIRAMA +0951..0957 ; Mn # [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0962..0963 ; Mn # [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0981 ; Mn # BENGALI SIGN CANDRABINDU +09BC ; Mn # BENGALI SIGN NUKTA +09C1..09C4 ; Mn # [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09CD ; Mn # BENGALI SIGN VIRAMA +09E2..09E3 ; Mn # [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09FE ; Mn # BENGALI SANDHI MARK +0A01..0A02 ; Mn # [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A3C ; Mn # GURMUKHI SIGN NUKTA +0A41..0A42 ; Mn # [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; Mn # [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; Mn # [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; Mn # GURMUKHI SIGN UDAAT +0A70..0A71 ; Mn # [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A75 ; Mn # GURMUKHI SIGN YAKASH +0A81..0A82 ; Mn # [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0ABC ; Mn # GUJARATI SIGN NUKTA +0AC1..0AC5 ; Mn # [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; Mn # [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0ACD ; Mn # GUJARATI SIGN VIRAMA +0AE2..0AE3 ; Mn # [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AFA..0AFF ; Mn # [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; Mn # ORIYA SIGN CANDRABINDU +0B3C ; Mn # ORIYA SIGN NUKTA +0B3F ; Mn # ORIYA VOWEL SIGN I +0B41..0B44 ; Mn # [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B4D ; Mn # ORIYA SIGN VIRAMA +0B55..0B56 ; Mn # [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B62..0B63 ; Mn # [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B82 ; Mn # TAMIL SIGN ANUSVARA +0BC0 ; Mn # TAMIL VOWEL SIGN II +0BCD ; Mn # TAMIL SIGN VIRAMA +0C00 ; Mn # TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C04 ; Mn # TELUGU SIGN COMBINING ANUSVARA ABOVE +0C3C ; Mn # TELUGU SIGN NUKTA +0C3E..0C40 ; Mn # [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C46..0C48 ; Mn # [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; Mn # [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; Mn # [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C62..0C63 ; Mn # [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C81 ; Mn # KANNADA SIGN CANDRABINDU +0CBC ; Mn # KANNADA SIGN NUKTA +0CBF ; Mn # KANNADA VOWEL SIGN I +0CC6 ; Mn # KANNADA VOWEL SIGN E +0CCC..0CCD ; Mn # [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CE2..0CE3 ; Mn # [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0D00..0D01 ; Mn # [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D3B..0D3C ; Mn # [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D41..0D44 ; Mn # [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D4D ; Mn # MALAYALAM SIGN VIRAMA +0D62..0D63 ; Mn # [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D81 ; Mn # SINHALA SIGN CANDRABINDU +0DCA ; Mn # SINHALA SIGN AL-LAKUNA +0DD2..0DD4 ; Mn # [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; Mn # SINHALA VOWEL SIGN DIGA PAA-PILLA +0E31 ; Mn # THAI CHARACTER MAI HAN-AKAT +0E34..0E3A ; Mn # [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E47..0E4E ; Mn # [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0EB1 ; Mn # LAO VOWEL SIGN MAI KAN +0EB4..0EBC ; Mn # [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EC8..0ECE ; Mn # [7] LAO TONE MAI EK..LAO YAMAKKAN +0F18..0F19 ; Mn # [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F35 ; Mn # TIBETAN MARK NGAS BZUNG NYI ZLA +0F37 ; Mn # TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F39 ; Mn # TIBETAN MARK TSA -PHRU +0F71..0F7E ; Mn # [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F80..0F84 ; Mn # [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F86..0F87 ; Mn # [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F8D..0F97 ; Mn # [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; Mn # [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FC6 ; Mn # TIBETAN SYMBOL PADMA GDAN +102D..1030 ; Mn # [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1032..1037 ; Mn # [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1039..103A ; Mn # [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103D..103E ; Mn # [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +1058..1059 ; Mn # [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105E..1060 ; Mn # [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1071..1074 ; Mn # [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1082 ; Mn # MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1085..1086 ; Mn # [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +108D ; Mn # MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +109D ; Mn # MYANMAR VOWEL SIGN AITON AI +135D..135F ; Mn # [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1712..1714 ; Mn # [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1732..1733 ; Mn # [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1752..1753 ; Mn # [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1772..1773 ; Mn # [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +17B4..17B5 ; Mn # [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B7..17BD ; Mn # [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17C6 ; Mn # KHMER SIGN NIKAHIT +17C9..17D3 ; Mn # [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17DD ; Mn # KHMER SIGN ATTHACAN +180B..180D ; Mn # [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180F ; Mn # MONGOLIAN FREE VARIATION SELECTOR FOUR +1885..1886 ; Mn # [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +18A9 ; Mn # MONGOLIAN LETTER ALI GALI DAGALGA +1920..1922 ; Mn # [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1927..1928 ; Mn # [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1932 ; Mn # LIMBU SMALL LETTER ANUSVARA +1939..193B ; Mn # [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1A17..1A18 ; Mn # [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A1B ; Mn # BUGINESE VOWEL SIGN AE +1A56 ; Mn # TAI THAM CONSONANT SIGN MEDIAL LA +1A58..1A5E ; Mn # [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; Mn # TAI THAM SIGN SAKOT +1A62 ; Mn # TAI THAM VOWEL SIGN MAI SAT +1A65..1A6C ; Mn # [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A73..1A7C ; Mn # [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; Mn # TAI THAM COMBINING CRYPTOGRAMMIC DOT +1AB0..1ABD ; Mn # [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABF..1ACE ; Mn # [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; Mn # [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B34 ; Mn # BALINESE SIGN REREKAN +1B36..1B3A ; Mn # [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3C ; Mn # BALINESE VOWEL SIGN LA LENGA +1B42 ; Mn # BALINESE VOWEL SIGN PEPET +1B6B..1B73 ; Mn # [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B80..1B81 ; Mn # [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1BA2..1BA5 ; Mn # [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA8..1BA9 ; Mn # [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAB..1BAD ; Mn # [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BE6 ; Mn # BATAK SIGN TOMPI +1BE8..1BE9 ; Mn # [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BED ; Mn # BATAK VOWEL SIGN KARO O +1BEF..1BF1 ; Mn # [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1C2C..1C33 ; Mn # [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C36..1C37 ; Mn # [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1CD0..1CD2 ; Mn # [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD4..1CE0 ; Mn # [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE2..1CE8 ; Mn # [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CED ; Mn # VEDIC SIGN TIRYAK +1CF4 ; Mn # VEDIC TONE CANDRA ABOVE +1CF8..1CF9 ; Mn # [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1DC0..1DFF ; Mn # [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +20D0..20DC ; Mn # [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20E1 ; Mn # COMBINING LEFT RIGHT ARROW ABOVE +20E5..20F0 ; Mn # [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2CEF..2CF1 ; Mn # [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2D7F ; Mn # TIFINAGH CONSONANT JOINER +2DE0..2DFF ; Mn # [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +302A..302D ; Mn # [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +3099..309A ; Mn # [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +A66F ; Mn # COMBINING CYRILLIC VZMET +A674..A67D ; Mn # [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A69E..A69F ; Mn # [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6F0..A6F1 ; Mn # [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A802 ; Mn # SYLOTI NAGRI SIGN DVISVARA +A806 ; Mn # SYLOTI NAGRI SIGN HASANTA +A80B ; Mn # SYLOTI NAGRI SIGN ANUSVARA +A825..A826 ; Mn # [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A82C ; Mn # SYLOTI NAGRI SIGN ALTERNATE HASANTA +A8C4..A8C5 ; Mn # [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8E0..A8F1 ; Mn # [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8FF ; Mn # DEVANAGARI VOWEL SIGN AY +A926..A92D ; Mn # [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A947..A951 ; Mn # [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A980..A982 ; Mn # [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A9B3 ; Mn # JAVANESE SIGN CECAK TELU +A9B6..A9B9 ; Mn # [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BC..A9BD ; Mn # [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9E5 ; Mn # MYANMAR SIGN SHAN SAW +AA29..AA2E ; Mn # [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA31..AA32 ; Mn # [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA35..AA36 ; Mn # [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA43 ; Mn # CHAM CONSONANT SIGN FINAL NG +AA4C ; Mn # CHAM CONSONANT SIGN FINAL M +AA7C ; Mn # MYANMAR SIGN TAI LAING TONE-2 +AAB0 ; Mn # TAI VIET MAI KANG +AAB2..AAB4 ; Mn # [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB7..AAB8 ; Mn # [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AABE..AABF ; Mn # [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC1 ; Mn # TAI VIET TONE MAI THO +AAEC..AAED ; Mn # [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAF6 ; Mn # MEETEI MAYEK VIRAMA +ABE5 ; Mn # MEETEI MAYEK VOWEL SIGN ANAP +ABE8 ; Mn # MEETEI MAYEK VOWEL SIGN UNAP +ABED ; Mn # MEETEI MAYEK APUN IYEK +FB1E ; Mn # HEBREW POINT JUDEO-SPANISH VARIKA +FE00..FE0F ; Mn # [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE20..FE2F ; Mn # [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +101FD ; Mn # PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +102E0 ; Mn # COPTIC EPACT THOUSANDS MARK +10376..1037A ; Mn # [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10A01..10A03 ; Mn # [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; Mn # [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; Mn # [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A38..10A3A ; Mn # [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; Mn # KHAROSHTHI VIRAMA +10AE5..10AE6 ; Mn # [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10D24..10D27 ; Mn # [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10EAB..10EAC ; Mn # [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EFD..10EFF ; Mn # [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F46..10F50 ; Mn # [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F82..10F85 ; Mn # [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +11001 ; Mn # BRAHMI SIGN ANUSVARA +11038..11046 ; Mn # [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA +11070 ; Mn # BRAHMI SIGN OLD TAMIL VIRAMA +11073..11074 ; Mn # [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +1107F..11081 ; Mn # [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA +110B3..110B6 ; Mn # [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B9..110BA ; Mn # [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110C2 ; Mn # KAITHI VOWEL SIGN VOCALIC R +11100..11102 ; Mn # [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11127..1112B ; Mn # [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112D..11134 ; Mn # [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11173 ; Mn # MAHAJANI SIGN NUKTA +11180..11181 ; Mn # [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +111B6..111BE ; Mn # [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111C9..111CC ; Mn # [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CF ; Mn # SHARADA SIGN INVERTED CANDRABINDU +1122F..11231 ; Mn # [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11234 ; Mn # KHOJKI SIGN ANUSVARA +11236..11237 ; Mn # [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +1123E ; Mn # KHOJKI SIGN SUKUN +11241 ; Mn # KHOJKI VOWEL SIGN VOCALIC R +112DF ; Mn # KHUDAWADI SIGN ANUSVARA +112E3..112EA ; Mn # [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +11300..11301 ; Mn # [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +1133B..1133C ; Mn # [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +11340 ; Mn # GRANTHA VOWEL SIGN II +11366..1136C ; Mn # [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; Mn # [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11438..1143F ; Mn # [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11442..11444 ; Mn # [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11446 ; Mn # NEWA SIGN NUKTA +1145E ; Mn # NEWA SANDHI MARK +114B3..114B8 ; Mn # [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114BA ; Mn # TIRHUTA VOWEL SIGN SHORT E +114BF..114C0 ; Mn # [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C2..114C3 ; Mn # [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +115B2..115B5 ; Mn # [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115BC..115BD ; Mn # [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BF..115C0 ; Mn # [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115DC..115DD ; Mn # [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11633..1163A ; Mn # [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163D ; Mn # MODI SIGN ANUSVARA +1163F..11640 ; Mn # [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +116AB ; Mn # TAKRI SIGN ANUSVARA +116AD ; Mn # TAKRI VOWEL SIGN AA +116B0..116B5 ; Mn # [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B7 ; Mn # TAKRI SIGN NUKTA +1171D..1171F ; Mn # [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11722..11725 ; Mn # [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11727..1172B ; Mn # [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +1182F..11837 ; Mn # [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11839..1183A ; Mn # [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1193B..1193C ; Mn # [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193E ; Mn # DIVES AKURU VIRAMA +11943 ; Mn # DIVES AKURU SIGN NUKTA +119D4..119D7 ; Mn # [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; Mn # [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119E0 ; Mn # NANDINAGARI SIGN VIRAMA +11A01..11A0A ; Mn # [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A33..11A38 ; Mn # [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A3B..11A3E ; Mn # [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A47 ; Mn # ZANABAZAR SQUARE SUBJOINER +11A51..11A56 ; Mn # [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A59..11A5B ; Mn # [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A8A..11A96 ; Mn # [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A98..11A99 ; Mn # [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11C30..11C36 ; Mn # [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; Mn # [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3F ; Mn # BHAIKSUKI SIGN VIRAMA +11C92..11CA7 ; Mn # [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CAA..11CB0 ; Mn # [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB2..11CB3 ; Mn # [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB5..11CB6 ; Mn # [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D31..11D36 ; Mn # [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; Mn # MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; Mn # [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; Mn # [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D47 ; Mn # MASARAM GONDI RA-KARA +11D90..11D91 ; Mn # [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D95 ; Mn # GUNJALA GONDI SIGN ANUSVARA +11D97 ; Mn # GUNJALA GONDI VIRAMA +11EF3..11EF4 ; Mn # [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11F00..11F01 ; Mn # [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F36..11F3A ; Mn # [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F40 ; Mn # KAWI VOWEL SIGN EU +11F42 ; Mn # KAWI CONJOINER +13440 ; Mn # EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13447..13455 ; Mn # [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +16AF0..16AF4 ; Mn # [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16B30..16B36 ; Mn # [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16F4F ; Mn # MIAO SIGN CONSONANT MODIFIER BAR +16F8F..16F92 ; Mn # [4] MIAO TONE RIGHT..MIAO TONE BELOW +16FE4 ; Mn # KHITAN SMALL SCRIPT FILLER +1BC9D..1BC9E ; Mn # [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1CF00..1CF2D ; Mn # [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; Mn # [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1D167..1D169 ; Mn # [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D17B..1D182 ; Mn # [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D185..1D18B ; Mn # [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D1AA..1D1AD ; Mn # [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D242..1D244 ; Mn # [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1DA00..1DA36 ; Mn # [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA3B..1DA6C ; Mn # [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA75 ; Mn # SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA84 ; Mn # SIGNWRITING LOCATION HEAD NECK +1DA9B..1DA9F ; Mn # [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; Mn # [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1E000..1E006 ; Mn # [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; Mn # [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; Mn # [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; Mn # [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; Mn # [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E08F ; Mn # COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E130..1E136 ; Mn # [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E2AE ; Mn # TOTO SIGN RISING TONE +1E2EC..1E2EF ; Mn # [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E4EC..1E4EF ; Mn # [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E8D0..1E8D6 ; Mn # [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E944..1E94A ; Mn # [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +E0100..E01EF ; Mn # [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 + +# Total code points: 1985 + +# ================================================ + +# General_Category=Enclosing_Mark + +0488..0489 ; Me # [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +1ABE ; Me # COMBINING PARENTHESES OVERLAY +20DD..20E0 ; Me # [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E2..20E4 ; Me # [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +A670..A672 ; Me # [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN + +# Total code points: 13 + +# ================================================ + +# General_Category=Spacing_Mark + +0903 ; Mc # DEVANAGARI SIGN VISARGA +093B ; Mc # DEVANAGARI VOWEL SIGN OOE +093E..0940 ; Mc # [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0949..094C ; Mc # [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094E..094F ; Mc # [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0982..0983 ; Mc # [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +09BE..09C0 ; Mc # [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C7..09C8 ; Mc # [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; Mc # [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09D7 ; Mc # BENGALI AU LENGTH MARK +0A03 ; Mc # GURMUKHI SIGN VISARGA +0A3E..0A40 ; Mc # [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A83 ; Mc # GUJARATI SIGN VISARGA +0ABE..0AC0 ; Mc # [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC9 ; Mc # GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; Mc # [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0B02..0B03 ; Mc # [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B3E ; Mc # ORIYA VOWEL SIGN AA +0B40 ; Mc # ORIYA VOWEL SIGN II +0B47..0B48 ; Mc # [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; Mc # [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B57 ; Mc # ORIYA AU LENGTH MARK +0BBE..0BBF ; Mc # [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC1..0BC2 ; Mc # [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; Mc # [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; Mc # [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BD7 ; Mc # TAMIL AU LENGTH MARK +0C01..0C03 ; Mc # [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C41..0C44 ; Mc # [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C82..0C83 ; Mc # [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0CBE ; Mc # KANNADA VOWEL SIGN AA +0CC0..0CC4 ; Mc # [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC7..0CC8 ; Mc # [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; Mc # [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CD5..0CD6 ; Mc # [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CF3 ; Mc # KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D02..0D03 ; Mc # [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D3E..0D40 ; Mc # [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D46..0D48 ; Mc # [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; Mc # [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D57 ; Mc # MALAYALAM AU LENGTH MARK +0D82..0D83 ; Mc # [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0DCF..0DD1 ; Mc # [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD8..0DDF ; Mc # [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DF2..0DF3 ; Mc # [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0F3E..0F3F ; Mc # [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F7F ; Mc # TIBETAN SIGN RNAM BCAD +102B..102C ; Mc # [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +1031 ; Mc # MYANMAR VOWEL SIGN E +1038 ; Mc # MYANMAR SIGN VISARGA +103B..103C ; Mc # [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +1056..1057 ; Mc # [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1062..1064 ; Mc # [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1067..106D ; Mc # [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +1083..1084 ; Mc # [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1087..108C ; Mc # [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108F ; Mc # MYANMAR SIGN RUMAI PALAUNG TONE-5 +109A..109C ; Mc # [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +1715 ; Mc # TAGALOG SIGN PAMUDPOD +1734 ; Mc # HANUNOO SIGN PAMUDPOD +17B6 ; Mc # KHMER VOWEL SIGN AA +17BE..17C5 ; Mc # [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C7..17C8 ; Mc # [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +1923..1926 ; Mc # [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1929..192B ; Mc # [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; Mc # [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1933..1938 ; Mc # [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1A19..1A1A ; Mc # [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A55 ; Mc # TAI THAM CONSONANT SIGN MEDIAL RA +1A57 ; Mc # TAI THAM CONSONANT SIGN LA TANG LAI +1A61 ; Mc # TAI THAM VOWEL SIGN A +1A63..1A64 ; Mc # [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A6D..1A72 ; Mc # [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1B04 ; Mc # BALINESE SIGN BISAH +1B35 ; Mc # BALINESE VOWEL SIGN TEDUNG +1B3B ; Mc # BALINESE VOWEL SIGN RA REPA TEDUNG +1B3D..1B41 ; Mc # [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B43..1B44 ; Mc # [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG +1B82 ; Mc # SUNDANESE SIGN PANGWISAD +1BA1 ; Mc # SUNDANESE CONSONANT SIGN PAMINGKAL +1BA6..1BA7 ; Mc # [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BAA ; Mc # SUNDANESE SIGN PAMAAEH +1BE7 ; Mc # BATAK VOWEL SIGN E +1BEA..1BEC ; Mc # [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BEE ; Mc # BATAK VOWEL SIGN U +1BF2..1BF3 ; Mc # [2] BATAK PANGOLAT..BATAK PANONGONAN +1C24..1C2B ; Mc # [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C34..1C35 ; Mc # [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1CE1 ; Mc # VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CF7 ; Mc # VEDIC SIGN ATIKRAMA +302E..302F ; Mc # [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +A823..A824 ; Mc # [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A827 ; Mc # SYLOTI NAGRI VOWEL SIGN OO +A880..A881 ; Mc # [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A8B4..A8C3 ; Mc # [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A952..A953 ; Mc # [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A983 ; Mc # JAVANESE SIGN WIGNYAN +A9B4..A9B5 ; Mc # [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9BA..A9BB ; Mc # [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BE..A9C0 ; Mc # [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON +AA2F..AA30 ; Mc # [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA33..AA34 ; Mc # [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA4D ; Mc # CHAM CONSONANT SIGN FINAL H +AA7B ; Mc # MYANMAR SIGN PAO KAREN TONE +AA7D ; Mc # MYANMAR SIGN TAI LAING TONE-5 +AAEB ; Mc # MEETEI MAYEK VOWEL SIGN II +AAEE..AAEF ; Mc # [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF5 ; Mc # MEETEI MAYEK VOWEL SIGN VISARGA +ABE3..ABE4 ; Mc # [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE6..ABE7 ; Mc # [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE9..ABEA ; Mc # [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEC ; Mc # MEETEI MAYEK LUM IYEK +11000 ; Mc # BRAHMI SIGN CANDRABINDU +11002 ; Mc # BRAHMI SIGN VISARGA +11082 ; Mc # KAITHI SIGN VISARGA +110B0..110B2 ; Mc # [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B7..110B8 ; Mc # [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +1112C ; Mc # CHAKMA VOWEL SIGN E +11145..11146 ; Mc # [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11182 ; Mc # SHARADA SIGN VISARGA +111B3..111B5 ; Mc # [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111BF..111C0 ; Mc # [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111CE ; Mc # SHARADA VOWEL SIGN PRISHTHAMATRA E +1122C..1122E ; Mc # [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +11232..11233 ; Mc # [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11235 ; Mc # KHOJKI SIGN VIRAMA +112E0..112E2 ; Mc # [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +11302..11303 ; Mc # [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +1133E..1133F ; Mc # [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11341..11344 ; Mc # [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; Mc # [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134D ; Mc # [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA +11357 ; Mc # GRANTHA AU LENGTH MARK +11362..11363 ; Mc # [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11435..11437 ; Mc # [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11440..11441 ; Mc # [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11445 ; Mc # NEWA SIGN VISARGA +114B0..114B2 ; Mc # [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B9 ; Mc # TIRHUTA VOWEL SIGN E +114BB..114BE ; Mc # [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114C1 ; Mc # TIRHUTA SIGN VISARGA +115AF..115B1 ; Mc # [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B8..115BB ; Mc # [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BE ; Mc # SIDDHAM SIGN VISARGA +11630..11632 ; Mc # [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +1163B..1163C ; Mc # [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163E ; Mc # MODI SIGN VISARGA +116AC ; Mc # TAKRI SIGN VISARGA +116AE..116AF ; Mc # [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B6 ; Mc # TAKRI SIGN VIRAMA +11720..11721 ; Mc # [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11726 ; Mc # AHOM VOWEL SIGN E +1182C..1182E ; Mc # [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +11838 ; Mc # DOGRA SIGN VISARGA +11930..11935 ; Mc # [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; Mc # [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193D ; Mc # DIVES AKURU SIGN HALANTA +11940 ; Mc # DIVES AKURU MEDIAL YA +11942 ; Mc # DIVES AKURU MEDIAL RA +119D1..119D3 ; Mc # [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119DC..119DF ; Mc # [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E4 ; Mc # NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A39 ; Mc # ZANABAZAR SQUARE SIGN VISARGA +11A57..11A58 ; Mc # [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A97 ; Mc # SOYOMBO SIGN VISARGA +11C2F ; Mc # BHAIKSUKI VOWEL SIGN AA +11C3E ; Mc # BHAIKSUKI SIGN VISARGA +11CA9 ; Mc # MARCHEN SUBJOINED LETTER YA +11CB1 ; Mc # MARCHEN VOWEL SIGN I +11CB4 ; Mc # MARCHEN VOWEL SIGN O +11D8A..11D8E ; Mc # [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D93..11D94 ; Mc # [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D96 ; Mc # GUNJALA GONDI SIGN VISARGA +11EF5..11EF6 ; Mc # [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11F03 ; Mc # KAWI SIGN VISARGA +11F34..11F35 ; Mc # [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F3E..11F3F ; Mc # [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F41 ; Mc # KAWI SIGN KILLER +16F51..16F87 ; Mc # [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16FF0..16FF1 ; Mc # [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +1D165..1D166 ; Mc # [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D16D..1D172 ; Mc # [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 + +# Total code points: 452 + +# ================================================ + +# General_Category=Decimal_Number + +0030..0039 ; Nd # [10] DIGIT ZERO..DIGIT NINE +0660..0669 ; Nd # [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +06F0..06F9 ; Nd # [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +07C0..07C9 ; Nd # [10] NKO DIGIT ZERO..NKO DIGIT NINE +0966..096F ; Nd # [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +09E6..09EF ; Nd # [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +0A66..0A6F ; Nd # [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0AE6..0AEF ; Nd # [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0B66..0B6F ; Nd # [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0BE6..0BEF ; Nd # [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0C66..0C6F ; Nd # [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0CE6..0CEF ; Nd # [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0D66..0D6F ; Nd # [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0DE6..0DEF ; Nd # [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0E50..0E59 ; Nd # [10] THAI DIGIT ZERO..THAI DIGIT NINE +0ED0..0ED9 ; Nd # [10] LAO DIGIT ZERO..LAO DIGIT NINE +0F20..0F29 ; Nd # [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +1040..1049 ; Nd # [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +1090..1099 ; Nd # [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +17E0..17E9 ; Nd # [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +1810..1819 ; Nd # [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1946..194F ; Nd # [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +19D0..19D9 ; Nd # [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +1A80..1A89 ; Nd # [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; Nd # [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1B50..1B59 ; Nd # [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1BB0..1BB9 ; Nd # [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1C40..1C49 ; Nd # [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C50..1C59 ; Nd # [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +A620..A629 ; Nd # [10] VAI DIGIT ZERO..VAI DIGIT NINE +A8D0..A8D9 ; Nd # [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A900..A909 ; Nd # [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A9D0..A9D9 ; Nd # [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9F0..A9F9 ; Nd # [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +AA50..AA59 ; Nd # [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +ABF0..ABF9 ; Nd # [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +FF10..FF19 ; Nd # [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +104A0..104A9 ; Nd # [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +10D30..10D39 ; Nd # [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +11066..1106F ; Nd # [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +110F0..110F9 ; Nd # [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11136..1113F ; Nd # [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +111D0..111D9 ; Nd # [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +112F0..112F9 ; Nd # [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11450..11459 ; Nd # [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +114D0..114D9 ; Nd # [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11650..11659 ; Nd # [10] MODI DIGIT ZERO..MODI DIGIT NINE +116C0..116C9 ; Nd # [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11730..11739 ; Nd # [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +118E0..118E9 ; Nd # [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +11950..11959 ; Nd # [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +11C50..11C59 ; Nd # [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11D50..11D59 ; Nd # [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11DA0..11DA9 ; Nd # [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11F50..11F59 ; Nd # [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +16A60..16A69 ; Nd # [10] MRO DIGIT ZERO..MRO DIGIT NINE +16AC0..16AC9 ; Nd # [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16B50..16B59 ; Nd # [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +1D7CE..1D7FF ; Nd # [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1E140..1E149 ; Nd # [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E2F0..1E2F9 ; Nd # [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E4F0..1E4F9 ; Nd # [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E950..1E959 ; Nd # [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1FBF0..1FBF9 ; Nd # [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE + +# Total code points: 680 + +# ================================================ + +# General_Category=Letter_Number + +16EE..16F0 ; Nl # [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +2160..2182 ; Nl # [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND +2185..2188 ; Nl # [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +3007 ; Nl # IDEOGRAPHIC NUMBER ZERO +3021..3029 ; Nl # [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +3038..303A ; Nl # [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +A6E6..A6EF ; Nl # [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +10140..10174 ; Nl # [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10341 ; Nl # GOTHIC LETTER NINETY +1034A ; Nl # GOTHIC LETTER NINE HUNDRED +103D1..103D5 ; Nl # [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +12400..1246E ; Nl # [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM + +# Total code points: 236 + +# ================================================ + +# General_Category=Other_Number + +00B2..00B3 ; No # [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B9 ; No # SUPERSCRIPT ONE +00BC..00BE ; No # [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +09F4..09F9 ; No # [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN +0B72..0B77 ; No # [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0BF0..0BF2 ; No # [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0C78..0C7E ; No # [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0D58..0D5E ; No # [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D70..0D78 ; No # [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0F2A..0F33 ; No # [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +1369..137C ; No # [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +17F0..17F9 ; No # [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +19DA ; No # NEW TAI LUE THAM DIGIT ONE +2070 ; No # SUPERSCRIPT ZERO +2074..2079 ; No # [6] SUPERSCRIPT FOUR..SUPERSCRIPT NINE +2080..2089 ; No # [10] SUBSCRIPT ZERO..SUBSCRIPT NINE +2150..215F ; No # [16] VULGAR FRACTION ONE SEVENTH..FRACTION NUMERATOR ONE +2189 ; No # VULGAR FRACTION ZERO THIRDS +2460..249B ; No # [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +24EA..24FF ; No # [22] CIRCLED DIGIT ZERO..NEGATIVE CIRCLED DIGIT ZERO +2776..2793 ; No # [30] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2CFD ; No # COPTIC FRACTION ONE HALF +3192..3195 ; No # [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3220..3229 ; No # [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +3248..324F ; No # [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3251..325F ; No # [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3280..3289 ; No # [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +32B1..32BF ; No # [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +A830..A835 ; No # [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +10107..10133 ; No # [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10175..10178 ; No # [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +1018A..1018B ; No # [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +102E1..102FB ; No # [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10320..10323 ; No # [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +10858..1085F ; No # [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10879..1087F ; No # [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +108A7..108AF ; No # [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108FB..108FF ; No # [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10916..1091B ; No # [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +109BC..109BD ; No # [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109C0..109CF ; No # [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; No # [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A40..10A48 ; No # [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A7D..10A7E ; No # [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A9D..10A9F ; No # [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AEB..10AEF ; No # [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10B58..10B5F ; No # [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B78..10B7F ; No # [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10BA9..10BAF ; No # [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10CFA..10CFF ; No # [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10E60..10E7E ; No # [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10F1D..10F26 ; No # [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F51..10F54 ; No # [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10FC5..10FCB ; No # [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +11052..11065 ; No # [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +111E1..111F4 ; No # [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +1173A..1173B ; No # [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +118EA..118F2 ; No # [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +11C5A..11C6C ; No # [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11FC0..11FD4 ; No # [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +16B5B..16B61 ; No # [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16E80..16E96 ; No # [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +1D2C0..1D2D3 ; No # [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; No # [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D360..1D378 ; No # [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1E8C7..1E8CF ; No # [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1EC71..1ECAB ; No # [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAD..1ECAF ; No # [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB1..1ECB4 ; No # [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; No # [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2F..1ED3D ; No # [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1F100..1F10C ; No # [13] DIGIT ZERO FULL STOP..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO + +# Total code points: 915 + +# ================================================ + +# General_Category=Space_Separator + +0020 ; Zs # SPACE +00A0 ; Zs # NO-BREAK SPACE +1680 ; Zs # OGHAM SPACE MARK +2000..200A ; Zs # [11] EN QUAD..HAIR SPACE +202F ; Zs # NARROW NO-BREAK SPACE +205F ; Zs # MEDIUM MATHEMATICAL SPACE +3000 ; Zs # IDEOGRAPHIC SPACE + +# Total code points: 17 + +# ================================================ + +# General_Category=Line_Separator + +2028 ; Zl # LINE SEPARATOR + +# Total code points: 1 + +# ================================================ + +# General_Category=Paragraph_Separator + +2029 ; Zp # PARAGRAPH SEPARATOR + +# Total code points: 1 + +# ================================================ + +# General_Category=Control + +0000..001F ; Cc # [32] .. +007F..009F ; Cc # [33] .. + +# Total code points: 65 + +# ================================================ + +# General_Category=Format + +00AD ; Cf # SOFT HYPHEN +0600..0605 ; Cf # [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +061C ; Cf # ARABIC LETTER MARK +06DD ; Cf # ARABIC END OF AYAH +070F ; Cf # SYRIAC ABBREVIATION MARK +0890..0891 ; Cf # [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +08E2 ; Cf # ARABIC DISPUTED END OF AYAH +180E ; Cf # MONGOLIAN VOWEL SEPARATOR +200B..200F ; Cf # [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK +202A..202E ; Cf # [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +2060..2064 ; Cf # [5] WORD JOINER..INVISIBLE PLUS +2066..206F ; Cf # [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +FEFF ; Cf # ZERO WIDTH NO-BREAK SPACE +FFF9..FFFB ; Cf # [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +110BD ; Cf # KAITHI NUMBER SIGN +110CD ; Cf # KAITHI NUMBER SIGN ABOVE +13430..1343F ; Cf # [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +1BCA0..1BCA3 ; Cf # [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1D173..1D17A ; Cf # [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +E0001 ; Cf # LANGUAGE TAG +E0020..E007F ; Cf # [96] TAG SPACE..CANCEL TAG + +# Total code points: 170 + +# ================================================ + +# General_Category=Private_Use + +E000..F8FF ; Co # [6400] .. +F0000..FFFFD ; Co # [65534] .. +100000..10FFFD; Co # [65534] .. + +# Total code points: 137468 + +# ================================================ + +# General_Category=Surrogate + +D800..DFFF ; Cs # [2048] .. + +# Total code points: 2048 + +# ================================================ + +# General_Category=Dash_Punctuation + +002D ; Pd # HYPHEN-MINUS +058A ; Pd # ARMENIAN HYPHEN +05BE ; Pd # HEBREW PUNCTUATION MAQAF +1400 ; Pd # CANADIAN SYLLABICS HYPHEN +1806 ; Pd # MONGOLIAN TODO SOFT HYPHEN +2010..2015 ; Pd # [6] HYPHEN..HORIZONTAL BAR +2E17 ; Pd # DOUBLE OBLIQUE HYPHEN +2E1A ; Pd # HYPHEN WITH DIAERESIS +2E3A..2E3B ; Pd # [2] TWO-EM DASH..THREE-EM DASH +2E40 ; Pd # DOUBLE HYPHEN +2E5D ; Pd # OBLIQUE HYPHEN +301C ; Pd # WAVE DASH +3030 ; Pd # WAVY DASH +30A0 ; Pd # KATAKANA-HIRAGANA DOUBLE HYPHEN +FE31..FE32 ; Pd # [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE58 ; Pd # SMALL EM DASH +FE63 ; Pd # SMALL HYPHEN-MINUS +FF0D ; Pd # FULLWIDTH HYPHEN-MINUS +10EAD ; Pd # YEZIDI HYPHENATION MARK + +# Total code points: 26 + +# ================================================ + +# General_Category=Open_Punctuation + +0028 ; Ps # LEFT PARENTHESIS +005B ; Ps # LEFT SQUARE BRACKET +007B ; Ps # LEFT CURLY BRACKET +0F3A ; Ps # TIBETAN MARK GUG RTAGS GYON +0F3C ; Ps # TIBETAN MARK ANG KHANG GYON +169B ; Ps # OGHAM FEATHER MARK +201A ; Ps # SINGLE LOW-9 QUOTATION MARK +201E ; Ps # DOUBLE LOW-9 QUOTATION MARK +2045 ; Ps # LEFT SQUARE BRACKET WITH QUILL +207D ; Ps # SUPERSCRIPT LEFT PARENTHESIS +208D ; Ps # SUBSCRIPT LEFT PARENTHESIS +2308 ; Ps # LEFT CEILING +230A ; Ps # LEFT FLOOR +2329 ; Ps # LEFT-POINTING ANGLE BRACKET +2768 ; Ps # MEDIUM LEFT PARENTHESIS ORNAMENT +276A ; Ps # MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276C ; Ps # MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276E ; Ps # HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; Ps # HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2772 ; Ps # LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2774 ; Ps # MEDIUM LEFT CURLY BRACKET ORNAMENT +27C5 ; Ps # LEFT S-SHAPED BAG DELIMITER +27E6 ; Ps # MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E8 ; Ps # MATHEMATICAL LEFT ANGLE BRACKET +27EA ; Ps # MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EC ; Ps # MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27EE ; Ps # MATHEMATICAL LEFT FLATTENED PARENTHESIS +2983 ; Ps # LEFT WHITE CURLY BRACKET +2985 ; Ps # LEFT WHITE PARENTHESIS +2987 ; Ps # Z NOTATION LEFT IMAGE BRACKET +2989 ; Ps # Z NOTATION LEFT BINDING BRACKET +298B ; Ps # LEFT SQUARE BRACKET WITH UNDERBAR +298D ; Ps # LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298F ; Ps # LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2991 ; Ps # LEFT ANGLE BRACKET WITH DOT +2993 ; Ps # LEFT ARC LESS-THAN BRACKET +2995 ; Ps # DOUBLE LEFT ARC GREATER-THAN BRACKET +2997 ; Ps # LEFT BLACK TORTOISE SHELL BRACKET +29D8 ; Ps # LEFT WIGGLY FENCE +29DA ; Ps # LEFT DOUBLE WIGGLY FENCE +29FC ; Ps # LEFT-POINTING CURVED ANGLE BRACKET +2E22 ; Ps # TOP LEFT HALF BRACKET +2E24 ; Ps # BOTTOM LEFT HALF BRACKET +2E26 ; Ps # LEFT SIDEWAYS U BRACKET +2E28 ; Ps # LEFT DOUBLE PARENTHESIS +2E42 ; Ps # DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E55 ; Ps # LEFT SQUARE BRACKET WITH STROKE +2E57 ; Ps # LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; Ps # TOP HALF LEFT PARENTHESIS +2E5B ; Ps # BOTTOM HALF LEFT PARENTHESIS +3008 ; Ps # LEFT ANGLE BRACKET +300A ; Ps # LEFT DOUBLE ANGLE BRACKET +300C ; Ps # LEFT CORNER BRACKET +300E ; Ps # LEFT WHITE CORNER BRACKET +3010 ; Ps # LEFT BLACK LENTICULAR BRACKET +3014 ; Ps # LEFT TORTOISE SHELL BRACKET +3016 ; Ps # LEFT WHITE LENTICULAR BRACKET +3018 ; Ps # LEFT WHITE TORTOISE SHELL BRACKET +301A ; Ps # LEFT WHITE SQUARE BRACKET +301D ; Ps # REVERSED DOUBLE PRIME QUOTATION MARK +FD3F ; Ps # ORNATE RIGHT PARENTHESIS +FE17 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE35 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE37 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE39 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3B ; Ps # PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3D ; Ps # PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3F ; Ps # PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE41 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE43 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE47 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE59 ; Ps # SMALL LEFT PARENTHESIS +FE5B ; Ps # SMALL LEFT CURLY BRACKET +FE5D ; Ps # SMALL LEFT TORTOISE SHELL BRACKET +FF08 ; Ps # FULLWIDTH LEFT PARENTHESIS +FF3B ; Ps # FULLWIDTH LEFT SQUARE BRACKET +FF5B ; Ps # FULLWIDTH LEFT CURLY BRACKET +FF5F ; Ps # FULLWIDTH LEFT WHITE PARENTHESIS +FF62 ; Ps # HALFWIDTH LEFT CORNER BRACKET + +# Total code points: 79 + +# ================================================ + +# General_Category=Close_Punctuation + +0029 ; Pe # RIGHT PARENTHESIS +005D ; Pe # RIGHT SQUARE BRACKET +007D ; Pe # RIGHT CURLY BRACKET +0F3B ; Pe # TIBETAN MARK GUG RTAGS GYAS +0F3D ; Pe # TIBETAN MARK ANG KHANG GYAS +169C ; Pe # OGHAM REVERSED FEATHER MARK +2046 ; Pe # RIGHT SQUARE BRACKET WITH QUILL +207E ; Pe # SUPERSCRIPT RIGHT PARENTHESIS +208E ; Pe # SUBSCRIPT RIGHT PARENTHESIS +2309 ; Pe # RIGHT CEILING +230B ; Pe # RIGHT FLOOR +232A ; Pe # RIGHT-POINTING ANGLE BRACKET +2769 ; Pe # MEDIUM RIGHT PARENTHESIS ORNAMENT +276B ; Pe # MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276D ; Pe # MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276F ; Pe # HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2771 ; Pe # HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2773 ; Pe # LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2775 ; Pe # MEDIUM RIGHT CURLY BRACKET ORNAMENT +27C6 ; Pe # RIGHT S-SHAPED BAG DELIMITER +27E7 ; Pe # MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E9 ; Pe # MATHEMATICAL RIGHT ANGLE BRACKET +27EB ; Pe # MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27ED ; Pe # MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EF ; Pe # MATHEMATICAL RIGHT FLATTENED PARENTHESIS +2984 ; Pe # RIGHT WHITE CURLY BRACKET +2986 ; Pe # RIGHT WHITE PARENTHESIS +2988 ; Pe # Z NOTATION RIGHT IMAGE BRACKET +298A ; Pe # Z NOTATION RIGHT BINDING BRACKET +298C ; Pe # RIGHT SQUARE BRACKET WITH UNDERBAR +298E ; Pe # RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; Pe # RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2992 ; Pe # RIGHT ANGLE BRACKET WITH DOT +2994 ; Pe # RIGHT ARC GREATER-THAN BRACKET +2996 ; Pe # DOUBLE RIGHT ARC LESS-THAN BRACKET +2998 ; Pe # RIGHT BLACK TORTOISE SHELL BRACKET +29D9 ; Pe # RIGHT WIGGLY FENCE +29DB ; Pe # RIGHT DOUBLE WIGGLY FENCE +29FD ; Pe # RIGHT-POINTING CURVED ANGLE BRACKET +2E23 ; Pe # TOP RIGHT HALF BRACKET +2E25 ; Pe # BOTTOM RIGHT HALF BRACKET +2E27 ; Pe # RIGHT SIDEWAYS U BRACKET +2E29 ; Pe # RIGHT DOUBLE PARENTHESIS +2E56 ; Pe # RIGHT SQUARE BRACKET WITH STROKE +2E58 ; Pe # RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E5A ; Pe # TOP HALF RIGHT PARENTHESIS +2E5C ; Pe # BOTTOM HALF RIGHT PARENTHESIS +3009 ; Pe # RIGHT ANGLE BRACKET +300B ; Pe # RIGHT DOUBLE ANGLE BRACKET +300D ; Pe # RIGHT CORNER BRACKET +300F ; Pe # RIGHT WHITE CORNER BRACKET +3011 ; Pe # RIGHT BLACK LENTICULAR BRACKET +3015 ; Pe # RIGHT TORTOISE SHELL BRACKET +3017 ; Pe # RIGHT WHITE LENTICULAR BRACKET +3019 ; Pe # RIGHT WHITE TORTOISE SHELL BRACKET +301B ; Pe # RIGHT WHITE SQUARE BRACKET +301E..301F ; Pe # [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +FD3E ; Pe # ORNATE LEFT PARENTHESIS +FE18 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE36 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE38 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE3A ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3C ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3E ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE40 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE42 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE44 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE48 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE5A ; Pe # SMALL RIGHT PARENTHESIS +FE5C ; Pe # SMALL RIGHT CURLY BRACKET +FE5E ; Pe # SMALL RIGHT TORTOISE SHELL BRACKET +FF09 ; Pe # FULLWIDTH RIGHT PARENTHESIS +FF3D ; Pe # FULLWIDTH RIGHT SQUARE BRACKET +FF5D ; Pe # FULLWIDTH RIGHT CURLY BRACKET +FF60 ; Pe # FULLWIDTH RIGHT WHITE PARENTHESIS +FF63 ; Pe # HALFWIDTH RIGHT CORNER BRACKET + +# Total code points: 77 + +# ================================================ + +# General_Category=Connector_Punctuation + +005F ; Pc # LOW LINE +203F..2040 ; Pc # [2] UNDERTIE..CHARACTER TIE +2054 ; Pc # INVERTED UNDERTIE +FE33..FE34 ; Pc # [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE4D..FE4F ; Pc # [3] DASHED LOW LINE..WAVY LOW LINE +FF3F ; Pc # FULLWIDTH LOW LINE + +# Total code points: 10 + +# ================================================ + +# General_Category=Other_Punctuation + +0021..0023 ; Po # [3] EXCLAMATION MARK..NUMBER SIGN +0025..0027 ; Po # [3] PERCENT SIGN..APOSTROPHE +002A ; Po # ASTERISK +002C ; Po # COMMA +002E..002F ; Po # [2] FULL STOP..SOLIDUS +003A..003B ; Po # [2] COLON..SEMICOLON +003F..0040 ; Po # [2] QUESTION MARK..COMMERCIAL AT +005C ; Po # REVERSE SOLIDUS +00A1 ; Po # INVERTED EXCLAMATION MARK +00A7 ; Po # SECTION SIGN +00B6..00B7 ; Po # [2] PILCROW SIGN..MIDDLE DOT +00BF ; Po # INVERTED QUESTION MARK +037E ; Po # GREEK QUESTION MARK +0387 ; Po # GREEK ANO TELEIA +055A..055F ; Po # [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0589 ; Po # ARMENIAN FULL STOP +05C0 ; Po # HEBREW PUNCTUATION PASEQ +05C3 ; Po # HEBREW PUNCTUATION SOF PASUQ +05C6 ; Po # HEBREW PUNCTUATION NUN HAFUKHA +05F3..05F4 ; Po # [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0609..060A ; Po # [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060C..060D ; Po # [2] ARABIC COMMA..ARABIC DATE SEPARATOR +061B ; Po # ARABIC SEMICOLON +061D..061F ; Po # [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +066A..066D ; Po # [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR +06D4 ; Po # ARABIC FULL STOP +0700..070D ; Po # [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +07F7..07F9 ; Po # [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK +0830..083E ; Po # [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +085E ; Po # MANDAIC PUNCTUATION +0964..0965 ; Po # [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0970 ; Po # DEVANAGARI ABBREVIATION SIGN +09FD ; Po # BENGALI ABBREVIATION SIGN +0A76 ; Po # GURMUKHI ABBREVIATION SIGN +0AF0 ; Po # GUJARATI ABBREVIATION SIGN +0C77 ; Po # TELUGU SIGN SIDDHAM +0C84 ; Po # KANNADA SIGN SIDDHAM +0DF4 ; Po # SINHALA PUNCTUATION KUNDDALIYA +0E4F ; Po # THAI CHARACTER FONGMAN +0E5A..0E5B ; Po # [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0F04..0F12 ; Po # [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD +0F14 ; Po # TIBETAN MARK GTER TSHEG +0F85 ; Po # TIBETAN MARK PALUTA +0FD0..0FD4 ; Po # [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD9..0FDA ; Po # [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +104A..104F ; Po # [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE +10FB ; Po # GEORGIAN PARAGRAPH SEPARATOR +1360..1368 ; Po # [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR +166E ; Po # CANADIAN SYLLABICS FULL STOP +16EB..16ED ; Po # [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +1735..1736 ; Po # [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +17D4..17D6 ; Po # [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D8..17DA ; Po # [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT +1800..1805 ; Po # [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS +1807..180A ; Po # [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU +1944..1945 ; Po # [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1A1E..1A1F ; Po # [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1AA0..1AA6 ; Po # [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA8..1AAD ; Po # [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1B5A..1B60 ; Po # [7] BALINESE PANTI..BALINESE PAMENENG +1B7D..1B7E ; Po # [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1BFC..1BFF ; Po # [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C3B..1C3F ; Po # [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C7E..1C7F ; Po # [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1CC0..1CC7 ; Po # [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD3 ; Po # VEDIC SIGN NIHSHVASA +2016..2017 ; Po # [2] DOUBLE VERTICAL LINE..DOUBLE LOW LINE +2020..2027 ; Po # [8] DAGGER..HYPHENATION POINT +2030..2038 ; Po # [9] PER MILLE SIGN..CARET +203B..203E ; Po # [4] REFERENCE MARK..OVERLINE +2041..2043 ; Po # [3] CARET INSERTION POINT..HYPHEN BULLET +2047..2051 ; Po # [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY +2053 ; Po # SWUNG DASH +2055..205E ; Po # [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS +2CF9..2CFC ; Po # [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER +2CFE..2CFF ; Po # [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER +2D70 ; Po # TIFINAGH SEPARATOR MARK +2E00..2E01 ; Po # [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E06..2E08 ; Po # [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E0B ; Po # RAISED SQUARE +2E0E..2E16 ; Po # [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE +2E18..2E19 ; Po # [2] INVERTED INTERROBANG..PALM BRANCH +2E1B ; Po # TILDE WITH RING ABOVE +2E1E..2E1F ; Po # [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E2A..2E2E ; Po # [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK +2E30..2E39 ; Po # [10] RING POINT..TOP HALF SECTION SIGN +2E3C..2E3F ; Po # [4] STENOGRAPHIC FULL STOP..CAPITULUM +2E41 ; Po # REVERSED COMMA +2E43..2E4F ; Po # [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER +2E52..2E54 ; Po # [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK +3001..3003 ; Po # [3] IDEOGRAPHIC COMMA..DITTO MARK +303D ; Po # PART ALTERNATION MARK +30FB ; Po # KATAKANA MIDDLE DOT +A4FE..A4FF ; Po # [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A60D..A60F ; Po # [3] VAI COMMA..VAI QUESTION MARK +A673 ; Po # SLAVONIC ASTERISK +A67E ; Po # CYRILLIC KAVYKA +A6F2..A6F7 ; Po # [6] BAMUM NJAEMLI..BAMUM QUESTION MARK +A874..A877 ; Po # [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD +A8CE..A8CF ; Po # [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8F8..A8FA ; Po # [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FC ; Po # DEVANAGARI SIGN SIDDHAM +A92E..A92F ; Po # [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A95F ; Po # REJANG SECTION MARK +A9C1..A9CD ; Po # [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH +A9DE..A9DF ; Po # [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +AA5C..AA5F ; Po # [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA +AADE..AADF ; Po # [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAF0..AAF1 ; Po # [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +ABEB ; Po # MEETEI MAYEK CHEIKHEI +FE10..FE16 ; Po # [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE19 ; Po # PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE30 ; Po # PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE45..FE46 ; Po # [2] SESAME DOT..WHITE SESAME DOT +FE49..FE4C ; Po # [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE50..FE52 ; Po # [3] SMALL COMMA..SMALL FULL STOP +FE54..FE57 ; Po # [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK +FE5F..FE61 ; Po # [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE68 ; Po # SMALL REVERSE SOLIDUS +FE6A..FE6B ; Po # [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT +FF01..FF03 ; Po # [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN +FF05..FF07 ; Po # [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE +FF0A ; Po # FULLWIDTH ASTERISK +FF0C ; Po # FULLWIDTH COMMA +FF0E..FF0F ; Po # [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS +FF1A..FF1B ; Po # [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1F..FF20 ; Po # [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT +FF3C ; Po # FULLWIDTH REVERSE SOLIDUS +FF61 ; Po # HALFWIDTH IDEOGRAPHIC FULL STOP +FF64..FF65 ; Po # [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT +10100..10102 ; Po # [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +1039F ; Po # UGARITIC WORD DIVIDER +103D0 ; Po # OLD PERSIAN WORD DIVIDER +1056F ; Po # CAUCASIAN ALBANIAN CITATION MARK +10857 ; Po # IMPERIAL ARAMAIC SECTION SIGN +1091F ; Po # PHOENICIAN WORD SEPARATOR +1093F ; Po # LYDIAN TRIANGULAR MARK +10A50..10A58 ; Po # [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES +10A7F ; Po # OLD SOUTH ARABIAN NUMERIC INDICATOR +10AF0..10AF6 ; Po # [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER +10B39..10B3F ; Po # [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B99..10B9C ; Po # [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10F55..10F59 ; Po # [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F86..10F89 ; Po # [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +11047..1104D ; Po # [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS +110BB..110BC ; Po # [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BE..110C1 ; Po # [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +11140..11143 ; Po # [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11174..11175 ; Po # [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK +111C5..111C8 ; Po # [4] SHARADA DANDA..SHARADA SEPARATOR +111CD ; Po # SHARADA SUTRA MARK +111DB ; Po # SHARADA SIGN SIDDHAM +111DD..111DF ; Po # [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +11238..1123D ; Po # [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN +112A9 ; Po # MULTANI SECTION MARK +1144B..1144F ; Po # [5] NEWA DANDA..NEWA ABBREVIATION SIGN +1145A..1145B ; Po # [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; Po # NEWA INSERTION SIGN +114C6 ; Po # TIRHUTA ABBREVIATION SIGN +115C1..115D7 ; Po # [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +11641..11643 ; Po # [3] MODI DANDA..MODI ABBREVIATION SIGN +11660..1166C ; Po # [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +116B9 ; Po # TAKRI ABBREVIATION SIGN +1173C..1173E ; Po # [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1183B ; Po # DOGRA ABBREVIATION SIGN +11944..11946 ; Po # [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +119E2 ; Po # NANDINAGARI SIGN SIDDHAM +11A3F..11A46 ; Po # [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A9A..11A9C ; Po # [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9E..11AA2 ; Po # [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2 +11B00..11B09 ; Po # [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C41..11C45 ; Po # [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C70..11C71 ; Po # [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD +11EF7..11EF8 ; Po # [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F43..11F4F ; Po # [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL +11FFF ; Po # TAMIL PUNCTUATION END OF TEXT +12470..12474 ; Po # [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12FF1..12FF2 ; Po # [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +16A6E..16A6F ; Po # [2] MRO DANDA..MRO DOUBLE DANDA +16AF5 ; Po # BASSA VAH FULL STOP +16B37..16B3B ; Po # [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM +16B44 ; Po # PAHAWH HMONG SIGN XAUS +16E97..16E9A ; Po # [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH +16FE2 ; Po # OLD CHINESE HOOK MARK +1BC9F ; Po # DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1DA87..1DA8B ; Po # [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS +1E95E..1E95F ; Po # [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK + +# Total code points: 628 + +# ================================================ + +# General_Category=Math_Symbol + +002B ; Sm # PLUS SIGN +003C..003E ; Sm # [3] LESS-THAN SIGN..GREATER-THAN SIGN +007C ; Sm # VERTICAL LINE +007E ; Sm # TILDE +00AC ; Sm # NOT SIGN +00B1 ; Sm # PLUS-MINUS SIGN +00D7 ; Sm # MULTIPLICATION SIGN +00F7 ; Sm # DIVISION SIGN +03F6 ; Sm # GREEK REVERSED LUNATE EPSILON SYMBOL +0606..0608 ; Sm # [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +2044 ; Sm # FRACTION SLASH +2052 ; Sm # COMMERCIAL MINUS SIGN +207A..207C ; Sm # [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +208A..208C ; Sm # [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +2118 ; Sm # SCRIPT CAPITAL P +2140..2144 ; Sm # [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +214B ; Sm # TURNED AMPERSAND +2190..2194 ; Sm # [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +219A..219B ; Sm # [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +21A0 ; Sm # RIGHTWARDS TWO HEADED ARROW +21A3 ; Sm # RIGHTWARDS ARROW WITH TAIL +21A6 ; Sm # RIGHTWARDS ARROW FROM BAR +21AE ; Sm # LEFT RIGHT ARROW WITH STROKE +21CE..21CF ; Sm # [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D2 ; Sm # RIGHTWARDS DOUBLE ARROW +21D4 ; Sm # LEFT RIGHT DOUBLE ARROW +21F4..22FF ; Sm # [268] RIGHT ARROW WITH SMALL CIRCLE..Z NOTATION BAG MEMBERSHIP +2320..2321 ; Sm # [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +237C ; Sm # RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +239B..23B3 ; Sm # [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23DC..23E1 ; Sm # [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +25B7 ; Sm # WHITE RIGHT-POINTING TRIANGLE +25C1 ; Sm # WHITE LEFT-POINTING TRIANGLE +25F8..25FF ; Sm # [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE +266F ; Sm # MUSIC SHARP SIGN +27C0..27C4 ; Sm # [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C7..27E5 ; Sm # [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27F0..27FF ; Sm # [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2900..2982 ; Sm # [131] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..Z NOTATION TYPE COLON +2999..29D7 ; Sm # [63] DOTTED FENCE..BLACK HOURGLASS +29DC..29FB ; Sm # [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FE..2AFF ; Sm # [258] TINY..N-ARY WHITE VERTICAL BAR +2B30..2B44 ; Sm # [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B47..2B4C ; Sm # [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +FB29 ; Sm # HEBREW LETTER ALTERNATIVE PLUS SIGN +FE62 ; Sm # SMALL PLUS SIGN +FE64..FE66 ; Sm # [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FF0B ; Sm # FULLWIDTH PLUS SIGN +FF1C..FF1E ; Sm # [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF5C ; Sm # FULLWIDTH VERTICAL LINE +FF5E ; Sm # FULLWIDTH TILDE +FFE2 ; Sm # FULLWIDTH NOT SIGN +FFE9..FFEC ; Sm # [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +1D6C1 ; Sm # MATHEMATICAL BOLD NABLA +1D6DB ; Sm # MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6FB ; Sm # MATHEMATICAL ITALIC NABLA +1D715 ; Sm # MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D735 ; Sm # MATHEMATICAL BOLD ITALIC NABLA +1D74F ; Sm # MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D76F ; Sm # MATHEMATICAL SANS-SERIF BOLD NABLA +1D789 ; Sm # MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D7A9 ; Sm # MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7C3 ; Sm # MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1EEF0..1EEF1 ; Sm # [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL + +# Total code points: 948 + +# ================================================ + +# General_Category=Currency_Symbol + +0024 ; Sc # DOLLAR SIGN +00A2..00A5 ; Sc # [4] CENT SIGN..YEN SIGN +058F ; Sc # ARMENIAN DRAM SIGN +060B ; Sc # AFGHANI SIGN +07FE..07FF ; Sc # [2] NKO DOROME SIGN..NKO TAMAN SIGN +09F2..09F3 ; Sc # [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09FB ; Sc # BENGALI GANDA MARK +0AF1 ; Sc # GUJARATI RUPEE SIGN +0BF9 ; Sc # TAMIL RUPEE SIGN +0E3F ; Sc # THAI CURRENCY SYMBOL BAHT +17DB ; Sc # KHMER CURRENCY SYMBOL RIEL +20A0..20C0 ; Sc # [33] EURO-CURRENCY SIGN..SOM SIGN +A838 ; Sc # NORTH INDIC RUPEE MARK +FDFC ; Sc # RIAL SIGN +FE69 ; Sc # SMALL DOLLAR SIGN +FF04 ; Sc # FULLWIDTH DOLLAR SIGN +FFE0..FFE1 ; Sc # [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN +FFE5..FFE6 ; Sc # [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +11FDD..11FE0 ; Sc # [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +1E2FF ; Sc # WANCHO NGUN SIGN +1ECB0 ; Sc # INDIC SIYAQ RUPEE MARK + +# Total code points: 63 + +# ================================================ + +# General_Category=Modifier_Symbol + +005E ; Sk # CIRCUMFLEX ACCENT +0060 ; Sk # GRAVE ACCENT +00A8 ; Sk # DIAERESIS +00AF ; Sk # MACRON +00B4 ; Sk # ACUTE ACCENT +00B8 ; Sk # CEDILLA +02C2..02C5 ; Sk # [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02D2..02DF ; Sk # [14] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER CROSS ACCENT +02E5..02EB ; Sk # [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02ED ; Sk # MODIFIER LETTER UNASPIRATED +02EF..02FF ; Sk # [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0375 ; Sk # GREEK LOWER NUMERAL SIGN +0384..0385 ; Sk # [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0888 ; Sk # ARABIC RAISED ROUND DOT +1FBD ; Sk # GREEK KORONIS +1FBF..1FC1 ; Sk # [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FCD..1FCF ; Sk # [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FDD..1FDF ; Sk # [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FED..1FEF ; Sk # [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FFD..1FFE ; Sk # [2] GREEK OXIA..GREEK DASIA +309B..309C ; Sk # [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +A700..A716 ; Sk # [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A720..A721 ; Sk # [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A789..A78A ; Sk # [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +AB5B ; Sk # MODIFIER BREVE WITH INVERTED BREVE +AB6A..AB6B ; Sk # [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +FBB2..FBC2 ; Sk # [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FF3E ; Sk # FULLWIDTH CIRCUMFLEX ACCENT +FF40 ; Sk # FULLWIDTH GRAVE ACCENT +FFE3 ; Sk # FULLWIDTH MACRON +1F3FB..1F3FF ; Sk # [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 + +# Total code points: 125 + +# ================================================ + +# General_Category=Other_Symbol + +00A6 ; So # BROKEN BAR +00A9 ; So # COPYRIGHT SIGN +00AE ; So # REGISTERED SIGN +00B0 ; So # DEGREE SIGN +0482 ; So # CYRILLIC THOUSANDS SIGN +058D..058E ; So # [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +060E..060F ; So # [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +06DE ; So # ARABIC START OF RUB EL HIZB +06E9 ; So # ARABIC PLACE OF SAJDAH +06FD..06FE ; So # [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +07F6 ; So # NKO SYMBOL OO DENNEN +09FA ; So # BENGALI ISSHAR +0B70 ; So # ORIYA ISSHAR +0BF3..0BF8 ; So # [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BFA ; So # TAMIL NUMBER SIGN +0C7F ; So # TELUGU SIGN TUUMU +0D4F ; So # MALAYALAM SIGN PARA +0D79 ; So # MALAYALAM DATE MARK +0F01..0F03 ; So # [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F13 ; So # TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F15..0F17 ; So # [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F1A..0F1F ; So # [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F34 ; So # TIBETAN MARK BSDUS RTAGS +0F36 ; So # TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F38 ; So # TIBETAN MARK CHE MGO +0FBE..0FC5 ; So # [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC7..0FCC ; So # [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; So # [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD5..0FD8 ; So # [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +109E..109F ; So # [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +1390..1399 ; So # [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +166D ; So # CANADIAN SYLLABICS CHI SIGN +1940 ; So # LIMBU SIGN LOO +19DE..19FF ; So # [34] NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM ROC +1B61..1B6A ; So # [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B74..1B7C ; So # [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +2100..2101 ; So # [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2103..2106 ; So # [4] DEGREE CELSIUS..CADA UNA +2108..2109 ; So # [2] SCRUPLE..DEGREE FAHRENHEIT +2114 ; So # L B BAR SYMBOL +2116..2117 ; So # [2] NUMERO SIGN..SOUND RECORDING COPYRIGHT +211E..2123 ; So # [6] PRESCRIPTION TAKE..VERSICLE +2125 ; So # OUNCE SIGN +2127 ; So # INVERTED OHM SIGN +2129 ; So # TURNED GREEK SMALL LETTER IOTA +212E ; So # ESTIMATED SYMBOL +213A..213B ; So # [2] ROTATED CAPITAL Q..FACSIMILE SIGN +214A ; So # PROPERTY LINE +214C..214D ; So # [2] PER SIGN..AKTIESELSKAB +214F ; So # SYMBOL FOR SAMARITAN SOURCE +218A..218B ; So # [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2195..2199 ; So # [5] UP DOWN ARROW..SOUTH WEST ARROW +219C..219F ; So # [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A1..21A2 ; So # [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A4..21A5 ; So # [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A7..21AD ; So # [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AF..21CD ; So # [31] DOWNWARDS ZIGZAG ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; So # [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D3 ; So # DOWNWARDS DOUBLE ARROW +21D5..21F3 ; So # [31] UP DOWN DOUBLE ARROW..UP DOWN WHITE ARROW +2300..2307 ; So # [8] DIAMETER SIGN..WAVY LINE +230C..231F ; So # [20] BOTTOM RIGHT CROP..BOTTOM RIGHT CORNER +2322..2328 ; So # [7] FROWN..KEYBOARD +232B..237B ; So # [81] ERASE TO THE LEFT..NOT CHECK MARK +237D..239A ; So # [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +23B4..23DB ; So # [40] TOP SQUARE BRACKET..FUSE +23E2..2426 ; So # [69] WHITE TRAPEZIUM..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; So # [11] OCR HOOK..OCR DOUBLE BACKSLASH +249C..24E9 ; So # [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +2500..25B6 ; So # [183] BOX DRAWINGS LIGHT HORIZONTAL..BLACK RIGHT-POINTING TRIANGLE +25B8..25C0 ; So # [9] BLACK RIGHT-POINTING SMALL TRIANGLE..BLACK LEFT-POINTING TRIANGLE +25C2..25F7 ; So # [54] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +2600..266E ; So # [111] BLACK SUN WITH RAYS..MUSIC NATURAL SIGN +2670..2767 ; So # [248] WEST SYRIAC CROSS..ROTATED FLORAL HEART BULLET +2794..27BF ; So # [44] HEAVY WIDE-HEADED RIGHTWARDS ARROW..DOUBLE CURLY LOOP +2800..28FF ; So # [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2B00..2B2F ; So # [48] NORTH EAST WHITE ARROW..WHITE VERTICAL ELLIPSE +2B45..2B46 ; So # [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B4D..2B73 ; So # [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; So # [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; So # [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2CE5..2CEA ; So # [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2E50..2E51 ; So # [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E80..2E99 ; So # [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; So # [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; So # [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; So # [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3004 ; So # JAPANESE INDUSTRIAL STANDARD SYMBOL +3012..3013 ; So # [2] POSTAL MARK..GETA MARK +3020 ; So # POSTAL MARK FACE +3036..3037 ; So # [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +303E..303F ; So # [2] IDEOGRAPHIC VARIATION INDICATOR..IDEOGRAPHIC HALF FILL SPACE +3190..3191 ; So # [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3196..319F ; So # [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31C0..31E3 ; So # [36] CJK STROKE T..CJK STROKE Q +31EF ; So # IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +3200..321E ; So # [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +322A..3247 ; So # [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3250 ; So # PARTNERSHIP SIGN +3260..327F ; So # [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +328A..32B0 ; So # [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32C0..33FF ; So # [320] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE GAL +4DC0..4DFF ; So # [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +A490..A4C6 ; So # [55] YI RADICAL QOT..YI RADICAL KE +A828..A82B ; So # [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A836..A837 ; So # [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A839 ; So # NORTH INDIC QUANTITY MARK +AA77..AA79 ; So # [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +FD40..FD4F ; So # [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FDCF ; So # ARABIC LIGATURE SALAAMUHU ALAYNAA +FDFD..FDFF ; So # [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FFE4 ; So # FULLWIDTH BROKEN BAR +FFE8 ; So # HALFWIDTH FORMS LIGHT VERTICAL +FFED..FFEE ; So # [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFFC..FFFD ; So # [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHARACTER +10137..1013F ; So # [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10179..10189 ; So # [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018C..1018E ; So # [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; So # [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; So # GREEK SYMBOL TAU RHO +101D0..101FC ; So # [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +10877..10878 ; So # [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10AC8 ; So # MANICHAEAN SIGN UD +1173F ; So # AHOM SYMBOL VI +11FD5..11FDC ; So # [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FE1..11FF1 ; So # [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +16B3C..16B3F ; So # [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B45 ; So # PAHAWH HMONG SIGN CIM TSOV ROG +1BC9C ; So # DUPLOYAN SIGN O WITH CROSS +1CF50..1CFC3 ; So # [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; So # [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; So # [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; So # [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D16A..1D16C ; So # [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D183..1D184 ; So # [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D18C..1D1A9 ; So # [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AE..1D1EA ; So # [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; So # [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D245 ; So # GREEK MUSICAL LEIMMA +1D300..1D356 ; So # [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D800..1D9FF ; So # [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA37..1DA3A ; So # [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA6D..1DA74 ; So # [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA76..1DA83 ; So # [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA85..1DA86 ; So # [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1E14F ; So # NYIAKENG PUACHUE HMONG CIRCLED CA +1ECAC ; So # INDIC SIYAQ PLACEHOLDER +1ED2E ; So # OTTOMAN SIYAQ MARRATAN +1F000..1F02B ; So # [44] MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F030..1F093 ; So # [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F0A0..1F0AE ; So # [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0B1..1F0BF ; So # [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C1..1F0CF ; So # [15] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER +1F0D1..1F0F5 ; So # [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F10D..1F1AD ; So # [161] CIRCLED ZERO WITH SLASH..MASK WORK SYMBOL +1F1E6..1F202 ; So # [29] REGIONAL INDICATOR SYMBOL LETTER A..SQUARED KATAKANA SA +1F210..1F23B ; So # [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F240..1F248 ; So # [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F250..1F251 ; So # [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F260..1F265 ; So # [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F300..1F3FA ; So # [251] CYCLONE..AMPHORA +1F400..1F6D7 ; So # [728] RAT..ELEVATOR +1F6DC..1F6EC ; So # [17] WIRELESS..AIRPLANE ARRIVING +1F6F0..1F6FC ; So # [13] SATELLITE..ROLLER SKATE +1F700..1F776 ; So # [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE +1F77B..1F7D9 ; So # [95] HAUMEA..NINE POINTED WHITE STAR +1F7E0..1F7EB ; So # [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7F0 ; So # HEAVY EQUALS SIGN +1F800..1F80B ; So # [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F810..1F847 ; So # [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F850..1F859 ; So # [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F860..1F887 ; So # [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F890..1F8AD ; So # [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8B0..1F8B1 ; So # [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F900..1FA53 ; So # [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP +1FA60..1FA6D ; So # [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA70..1FA7C ; So # [13] BALLET SHOES..CRUTCH +1FA80..1FA88 ; So # [9] YO-YO..FLUTE +1FA90..1FABD ; So # [46] RINGED PLANET..WING +1FABF..1FAC5 ; So # [7] GOOSE..PERSON WITH CROWN +1FACE..1FADB ; So # [14] MOOSE..PEA POD +1FAE0..1FAE8 ; So # [9] MELTING FACE..SHAKING FACE +1FAF0..1FAF8 ; So # [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FB00..1FB92 ; So # [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; So # [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON + +# Total code points: 6639 + +# ================================================ + +# General_Category=Initial_Punctuation + +00AB ; Pi # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +2018 ; Pi # LEFT SINGLE QUOTATION MARK +201B..201C ; Pi # [2] SINGLE HIGH-REVERSED-9 QUOTATION MARK..LEFT DOUBLE QUOTATION MARK +201F ; Pi # DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2039 ; Pi # SINGLE LEFT-POINTING ANGLE QUOTATION MARK +2E02 ; Pi # LEFT SUBSTITUTION BRACKET +2E04 ; Pi # LEFT DOTTED SUBSTITUTION BRACKET +2E09 ; Pi # LEFT TRANSPOSITION BRACKET +2E0C ; Pi # LEFT RAISED OMISSION BRACKET +2E1C ; Pi # LEFT LOW PARAPHRASE BRACKET +2E20 ; Pi # LEFT VERTICAL BAR WITH QUILL + +# Total code points: 12 + +# ================================================ + +# General_Category=Final_Punctuation + +00BB ; Pf # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +2019 ; Pf # RIGHT SINGLE QUOTATION MARK +201D ; Pf # RIGHT DOUBLE QUOTATION MARK +203A ; Pf # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +2E03 ; Pf # RIGHT SUBSTITUTION BRACKET +2E05 ; Pf # RIGHT DOTTED SUBSTITUTION BRACKET +2E0A ; Pf # RIGHT TRANSPOSITION BRACKET +2E0D ; Pf # RIGHT RAISED OMISSION BRACKET +2E1D ; Pf # RIGHT LOW PARAPHRASE BRACKET +2E21 ; Pf # RIGHT VERTICAL BAR WITH QUILL + +# Total code points: 10 + +# EOF diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/EastAsianWidth.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/EastAsianWidth.txt new file mode 100644 index 000000000..02df4df47 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/EastAsianWidth.txt @@ -0,0 +1,2621 @@ +# EastAsianWidth-15.1.0.txt +# Date: 2023-07-28, 23:34:08 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# East_Asian_Width Property +# +# This file is a normative contributory data file in the +# Unicode Character Database. +# +# The format is two fields separated by a semicolon. +# Field 0: Unicode code point value or range of code point values +# Field 1: East_Asian_Width property, consisting of one of the following values: +# "A", "F", "H", "N", "Na", "W" +# - All code points, assigned or unassigned, that are not listed +# explicitly are given the value "N". +# - The unassigned code points in the following blocks default to "W": +# CJK Unified Ideographs Extension A: U+3400..U+4DBF +# CJK Unified Ideographs: U+4E00..U+9FFF +# CJK Compatibility Ideographs: U+F900..U+FAFF +# - All undesignated code points in Planes 2 and 3, whether inside or +# outside of allocated blocks, default to "W": +# Plane 2: U+20000..U+2FFFD +# Plane 3: U+30000..U+3FFFD +# +# Character ranges are specified as for other property files in the +# Unicode Character Database. +# +# The comments following the number sign "#" list the General_Category +# property value or the L& alias of the derived value LC, the Unicode +# character name or names, and, in lines with ranges of code points, +# the code point count in square brackets. +# +# For more information, see UAX #11: East Asian Width, +# at https://www.unicode.org/reports/tr11/ +# +# @missing: 0000..10FFFF; N +0000..001F ; N # Cc [32] .. +0020 ; Na # Zs SPACE +0021..0023 ; Na # Po [3] EXCLAMATION MARK..NUMBER SIGN +0024 ; Na # Sc DOLLAR SIGN +0025..0027 ; Na # Po [3] PERCENT SIGN..APOSTROPHE +0028 ; Na # Ps LEFT PARENTHESIS +0029 ; Na # Pe RIGHT PARENTHESIS +002A ; Na # Po ASTERISK +002B ; Na # Sm PLUS SIGN +002C ; Na # Po COMMA +002D ; Na # Pd HYPHEN-MINUS +002E..002F ; Na # Po [2] FULL STOP..SOLIDUS +0030..0039 ; Na # Nd [10] DIGIT ZERO..DIGIT NINE +003A..003B ; Na # Po [2] COLON..SEMICOLON +003C..003E ; Na # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN +003F..0040 ; Na # Po [2] QUESTION MARK..COMMERCIAL AT +0041..005A ; Na # Lu [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +005B ; Na # Ps LEFT SQUARE BRACKET +005C ; Na # Po REVERSE SOLIDUS +005D ; Na # Pe RIGHT SQUARE BRACKET +005E ; Na # Sk CIRCUMFLEX ACCENT +005F ; Na # Pc LOW LINE +0060 ; Na # Sk GRAVE ACCENT +0061..007A ; Na # Ll [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B ; Na # Ps LEFT CURLY BRACKET +007C ; Na # Sm VERTICAL LINE +007D ; Na # Pe RIGHT CURLY BRACKET +007E ; Na # Sm TILDE +007F ; N # Cc +0080..009F ; N # Cc [32] .. +00A0 ; N # Zs NO-BREAK SPACE +00A1 ; A # Po INVERTED EXCLAMATION MARK +00A2..00A3 ; Na # Sc [2] CENT SIGN..POUND SIGN +00A4 ; A # Sc CURRENCY SIGN +00A5 ; Na # Sc YEN SIGN +00A6 ; Na # So BROKEN BAR +00A7 ; A # Po SECTION SIGN +00A8 ; A # Sk DIAERESIS +00A9 ; N # So COPYRIGHT SIGN +00AA ; A # Lo FEMININE ORDINAL INDICATOR +00AB ; N # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00AC ; Na # Sm NOT SIGN +00AD ; A # Cf SOFT HYPHEN +00AE ; A # So REGISTERED SIGN +00AF ; Na # Sk MACRON +00B0 ; A # So DEGREE SIGN +00B1 ; A # Sm PLUS-MINUS SIGN +00B2..00B3 ; A # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B4 ; A # Sk ACUTE ACCENT +00B5 ; N # Ll MICRO SIGN +00B6..00B7 ; A # Po [2] PILCROW SIGN..MIDDLE DOT +00B8 ; A # Sk CEDILLA +00B9 ; A # No SUPERSCRIPT ONE +00BA ; A # Lo MASCULINE ORDINAL INDICATOR +00BB ; N # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +00BC..00BE ; A # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +00BF ; A # Po INVERTED QUESTION MARK +00C0..00C5 ; N # Lu [6] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER A WITH RING ABOVE +00C6 ; A # Lu LATIN CAPITAL LETTER AE +00C7..00CF ; N # Lu [9] LATIN CAPITAL LETTER C WITH CEDILLA..LATIN CAPITAL LETTER I WITH DIAERESIS +00D0 ; A # Lu LATIN CAPITAL LETTER ETH +00D1..00D6 ; N # Lu [6] LATIN CAPITAL LETTER N WITH TILDE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D7 ; A # Sm MULTIPLICATION SIGN +00D8 ; A # Lu LATIN CAPITAL LETTER O WITH STROKE +00D9..00DD ; N # Lu [5] LATIN CAPITAL LETTER U WITH GRAVE..LATIN CAPITAL LETTER Y WITH ACUTE +00DE..00E1 ; A # L& [4] LATIN CAPITAL LETTER THORN..LATIN SMALL LETTER A WITH ACUTE +00E2..00E5 ; N # Ll [4] LATIN SMALL LETTER A WITH CIRCUMFLEX..LATIN SMALL LETTER A WITH RING ABOVE +00E6 ; A # Ll LATIN SMALL LETTER AE +00E7 ; N # Ll LATIN SMALL LETTER C WITH CEDILLA +00E8..00EA ; A # Ll [3] LATIN SMALL LETTER E WITH GRAVE..LATIN SMALL LETTER E WITH CIRCUMFLEX +00EB ; N # Ll LATIN SMALL LETTER E WITH DIAERESIS +00EC..00ED ; A # Ll [2] LATIN SMALL LETTER I WITH GRAVE..LATIN SMALL LETTER I WITH ACUTE +00EE..00EF ; N # Ll [2] LATIN SMALL LETTER I WITH CIRCUMFLEX..LATIN SMALL LETTER I WITH DIAERESIS +00F0 ; A # Ll LATIN SMALL LETTER ETH +00F1 ; N # Ll LATIN SMALL LETTER N WITH TILDE +00F2..00F3 ; A # Ll [2] LATIN SMALL LETTER O WITH GRAVE..LATIN SMALL LETTER O WITH ACUTE +00F4..00F6 ; N # Ll [3] LATIN SMALL LETTER O WITH CIRCUMFLEX..LATIN SMALL LETTER O WITH DIAERESIS +00F7 ; A # Sm DIVISION SIGN +00F8..00FA ; A # Ll [3] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER U WITH ACUTE +00FB ; N # Ll LATIN SMALL LETTER U WITH CIRCUMFLEX +00FC ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS +00FD ; N # Ll LATIN SMALL LETTER Y WITH ACUTE +00FE ; A # Ll LATIN SMALL LETTER THORN +00FF ; N # Ll LATIN SMALL LETTER Y WITH DIAERESIS +0100 ; N # Lu LATIN CAPITAL LETTER A WITH MACRON +0101 ; A # Ll LATIN SMALL LETTER A WITH MACRON +0102..0110 ; N # L& [15] LATIN CAPITAL LETTER A WITH BREVE..LATIN CAPITAL LETTER D WITH STROKE +0111 ; A # Ll LATIN SMALL LETTER D WITH STROKE +0112 ; N # Lu LATIN CAPITAL LETTER E WITH MACRON +0113 ; A # Ll LATIN SMALL LETTER E WITH MACRON +0114..011A ; N # L& [7] LATIN CAPITAL LETTER E WITH BREVE..LATIN CAPITAL LETTER E WITH CARON +011B ; A # Ll LATIN SMALL LETTER E WITH CARON +011C..0125 ; N # L& [10] LATIN CAPITAL LETTER G WITH CIRCUMFLEX..LATIN SMALL LETTER H WITH CIRCUMFLEX +0126..0127 ; A # L& [2] LATIN CAPITAL LETTER H WITH STROKE..LATIN SMALL LETTER H WITH STROKE +0128..012A ; N # L& [3] LATIN CAPITAL LETTER I WITH TILDE..LATIN CAPITAL LETTER I WITH MACRON +012B ; A # Ll LATIN SMALL LETTER I WITH MACRON +012C..0130 ; N # L& [5] LATIN CAPITAL LETTER I WITH BREVE..LATIN CAPITAL LETTER I WITH DOT ABOVE +0131..0133 ; A # L& [3] LATIN SMALL LETTER DOTLESS I..LATIN SMALL LIGATURE IJ +0134..0137 ; N # L& [4] LATIN CAPITAL LETTER J WITH CIRCUMFLEX..LATIN SMALL LETTER K WITH CEDILLA +0138 ; A # Ll LATIN SMALL LETTER KRA +0139..013E ; N # L& [6] LATIN CAPITAL LETTER L WITH ACUTE..LATIN SMALL LETTER L WITH CARON +013F..0142 ; A # L& [4] LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATIN SMALL LETTER L WITH STROKE +0143 ; N # Lu LATIN CAPITAL LETTER N WITH ACUTE +0144 ; A # Ll LATIN SMALL LETTER N WITH ACUTE +0145..0147 ; N # L& [3] LATIN CAPITAL LETTER N WITH CEDILLA..LATIN CAPITAL LETTER N WITH CARON +0148..014B ; A # L& [4] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER ENG +014C ; N # Lu LATIN CAPITAL LETTER O WITH MACRON +014D ; A # Ll LATIN SMALL LETTER O WITH MACRON +014E..0151 ; N # L& [4] LATIN CAPITAL LETTER O WITH BREVE..LATIN SMALL LETTER O WITH DOUBLE ACUTE +0152..0153 ; A # L& [2] LATIN CAPITAL LIGATURE OE..LATIN SMALL LIGATURE OE +0154..0165 ; N # L& [18] LATIN CAPITAL LETTER R WITH ACUTE..LATIN SMALL LETTER T WITH CARON +0166..0167 ; A # L& [2] LATIN CAPITAL LETTER T WITH STROKE..LATIN SMALL LETTER T WITH STROKE +0168..016A ; N # L& [3] LATIN CAPITAL LETTER U WITH TILDE..LATIN CAPITAL LETTER U WITH MACRON +016B ; A # Ll LATIN SMALL LETTER U WITH MACRON +016C..017F ; N # L& [20] LATIN CAPITAL LETTER U WITH BREVE..LATIN SMALL LETTER LONG S +0180..01BA ; N # L& [59] LATIN SMALL LETTER B WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL +01BB ; N # Lo LATIN LETTER TWO WITH STROKE +01BC..01BF ; N # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN +01C0..01C3 ; N # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +01C4..01CD ; N # L& [10] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER A WITH CARON +01CE ; A # Ll LATIN SMALL LETTER A WITH CARON +01CF ; N # Lu LATIN CAPITAL LETTER I WITH CARON +01D0 ; A # Ll LATIN SMALL LETTER I WITH CARON +01D1 ; N # Lu LATIN CAPITAL LETTER O WITH CARON +01D2 ; A # Ll LATIN SMALL LETTER O WITH CARON +01D3 ; N # Lu LATIN CAPITAL LETTER U WITH CARON +01D4 ; A # Ll LATIN SMALL LETTER U WITH CARON +01D5 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D6 ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D7 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D8 ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01D9 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DA ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DB ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DC ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE +01DD..024F ; N # L& [115] LATIN SMALL LETTER TURNED E..LATIN SMALL LETTER Y WITH STROKE +0250 ; N # Ll LATIN SMALL LETTER TURNED A +0251 ; A # Ll LATIN SMALL LETTER ALPHA +0252..0260 ; N # Ll [15] LATIN SMALL LETTER TURNED ALPHA..LATIN SMALL LETTER G WITH HOOK +0261 ; A # Ll LATIN SMALL LETTER SCRIPT G +0262..0293 ; N # Ll [50] LATIN LETTER SMALL CAPITAL G..LATIN SMALL LETTER EZH WITH CURL +0294 ; N # Lo LATIN LETTER GLOTTAL STOP +0295..02AF ; N # Ll [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0..02C1 ; N # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2..02C3 ; N # Sk [2] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER RIGHT ARROWHEAD +02C4 ; A # Sk MODIFIER LETTER UP ARROWHEAD +02C5 ; N # Sk MODIFIER LETTER DOWN ARROWHEAD +02C6 ; N # Lm MODIFIER LETTER CIRCUMFLEX ACCENT +02C7 ; A # Lm CARON +02C8 ; N # Lm MODIFIER LETTER VERTICAL LINE +02C9..02CB ; A # Lm [3] MODIFIER LETTER MACRON..MODIFIER LETTER GRAVE ACCENT +02CC ; N # Lm MODIFIER LETTER LOW VERTICAL LINE +02CD ; A # Lm MODIFIER LETTER LOW MACRON +02CE..02CF ; N # Lm [2] MODIFIER LETTER LOW GRAVE ACCENT..MODIFIER LETTER LOW ACUTE ACCENT +02D0 ; A # Lm MODIFIER LETTER TRIANGULAR COLON +02D1 ; N # Lm MODIFIER LETTER HALF TRIANGULAR COLON +02D2..02D7 ; N # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN +02D8..02DB ; A # Sk [4] BREVE..OGONEK +02DC ; N # Sk SMALL TILDE +02DD ; A # Sk DOUBLE ACUTE ACCENT +02DE ; N # Sk MODIFIER LETTER RHOTIC HOOK +02DF ; A # Sk MODIFIER LETTER CROSS ACCENT +02E0..02E4 ; N # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5..02EB ; N # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC ; N # Lm MODIFIER LETTER VOICING +02ED ; N # Sk MODIFIER LETTER UNASPIRATED +02EE ; N # Lm MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; N # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0300..036F ; A # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X +0370..0373 ; N # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; N # Lm GREEK NUMERAL SIGN +0375 ; N # Sk GREEK LOWER NUMERAL SIGN +0376..0377 ; N # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037A ; N # Lm GREEK YPOGEGRAMMENI +037B..037D ; N # Ll [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037E ; N # Po GREEK QUESTION MARK +037F ; N # Lu GREEK CAPITAL LETTER YOT +0384..0385 ; N # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0386 ; N # Lu GREEK CAPITAL LETTER ALPHA WITH TONOS +0387 ; N # Po GREEK ANO TELEIA +0388..038A ; N # Lu [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; N # Lu GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..0390 ; N # L& [3] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +0391..03A1 ; A # Lu [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO +03A3..03A9 ; A # Lu [7] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER OMEGA +03AA..03B0 ; N # L& [7] GREEK CAPITAL LETTER IOTA WITH DIALYTIKA..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +03B1..03C1 ; A # Ll [17] GREEK SMALL LETTER ALPHA..GREEK SMALL LETTER RHO +03C2 ; N # Ll GREEK SMALL LETTER FINAL SIGMA +03C3..03C9 ; A # Ll [7] GREEK SMALL LETTER SIGMA..GREEK SMALL LETTER OMEGA +03CA..03F5 ; N # L& [44] GREEK SMALL LETTER IOTA WITH DIALYTIKA..GREEK LUNATE EPSILON SYMBOL +03F6 ; N # Sm GREEK REVERSED LUNATE EPSILON SYMBOL +03F7..03FF ; N # L& [9] GREEK CAPITAL LETTER SHO..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL +0400 ; N # Lu CYRILLIC CAPITAL LETTER IE WITH GRAVE +0401 ; A # Lu CYRILLIC CAPITAL LETTER IO +0402..040F ; N # Lu [14] CYRILLIC CAPITAL LETTER DJE..CYRILLIC CAPITAL LETTER DZHE +0410..044F ; A # L& [64] CYRILLIC CAPITAL LETTER A..CYRILLIC SMALL LETTER YA +0450 ; N # Ll CYRILLIC SMALL LETTER IE WITH GRAVE +0451 ; A # Ll CYRILLIC SMALL LETTER IO +0452..0481 ; N # L& [48] CYRILLIC SMALL LETTER DJE..CYRILLIC SMALL LETTER KOPPA +0482 ; N # So CYRILLIC THOUSANDS SIGN +0483..0487 ; N # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488..0489 ; N # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +048A..04FF ; N # L& [118] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER HA WITH STROKE +0500..052F ; N # L& [48] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER EL WITH DESCENDER +0531..0556 ; N # Lu [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0559 ; N # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055F ; N # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0560..0588 ; N # Ll [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +0589 ; N # Po ARMENIAN FULL STOP +058A ; N # Pd ARMENIAN HYPHEN +058D..058E ; N # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +058F ; N # Sc ARMENIAN DRAM SIGN +0591..05BD ; N # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE ; N # Pd HEBREW PUNCTUATION MAQAF +05BF ; N # Mn HEBREW POINT RAFE +05C0 ; N # Po HEBREW PUNCTUATION PASEQ +05C1..05C2 ; N # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3 ; N # Po HEBREW PUNCTUATION SOF PASUQ +05C4..05C5 ; N # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6 ; N # Po HEBREW PUNCTUATION NUN HAFUKHA +05C7 ; N # Mn HEBREW POINT QAMATS QATAN +05D0..05EA ; N # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; N # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +05F3..05F4 ; N # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0600..0605 ; N # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +0606..0608 ; N # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +0609..060A ; N # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060B ; N # Sc AFGHANI SIGN +060C..060D ; N # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR +060E..060F ; N # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +0610..061A ; N # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +061B ; N # Po ARABIC SEMICOLON +061C ; N # Cf ARABIC LETTER MARK +061D..061F ; N # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +0620..063F ; N # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640 ; N # Lm ARABIC TATWEEL +0641..064A ; N # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH +064B..065F ; N # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0660..0669 ; N # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066A..066D ; N # Po [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR +066E..066F ; N # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0670 ; N # Mn ARABIC LETTER SUPERSCRIPT ALEF +0671..06D3 ; N # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D4 ; N # Po ARABIC FULL STOP +06D5 ; N # Lo ARABIC LETTER AE +06D6..06DC ; N # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DD ; N # Cf ARABIC END OF AYAH +06DE ; N # So ARABIC START OF RUB EL HIZB +06DF..06E4 ; N # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E5..06E6 ; N # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH +06E7..06E8 ; N # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06E9 ; N # So ARABIC PLACE OF SAJDAH +06EA..06ED ; N # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +06EE..06EF ; N # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06F0..06F9 ; N # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +06FA..06FC ; N # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FD..06FE ; N # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +06FF ; N # Lo ARABIC LETTER HEH WITH INVERTED V +0700..070D ; N # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +070F ; N # Cf SYRIAC ABBREVIATION MARK +0710 ; N # Lo SYRIAC LETTER ALAPH +0711 ; N # Mn SYRIAC LETTER SUPERSCRIPT ALAPH +0712..072F ; N # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +0730..074A ; N # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +074D..074F ; N # Lo [3] SYRIAC LETTER SOGDIAN ZHAIN..SYRIAC LETTER SOGDIAN FE +0750..077F ; N # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE +0780..07A5 ; N # Lo [38] THAANA LETTER HAA..THAANA LETTER WAAVU +07A6..07B0 ; N # Mn [11] THAANA ABAFILI..THAANA SUKUN +07B1 ; N # Lo THAANA LETTER NAA +07C0..07C9 ; N # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE +07CA..07EA ; N # Lo [33] NKO LETTER A..NKO LETTER JONA RA +07EB..07F3 ; N # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07F4..07F5 ; N # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07F6 ; N # So NKO SYMBOL OO DENNEN +07F7..07F9 ; N # Po [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK +07FA ; N # Lm NKO LAJANYALAN +07FD ; N # Mn NKO DANTAYALAN +07FE..07FF ; N # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN +0800..0815 ; N # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0816..0819 ; N # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081A ; N # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT +081B..0823 ; N # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0824 ; N # Lm SAMARITAN MODIFIER LETTER SHORT A +0825..0827 ; N # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0828 ; N # Lm SAMARITAN MODIFIER LETTER I +0829..082D ; N # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0830..083E ; N # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +0840..0858 ; N # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0859..085B ; N # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +085E ; N # Po MANDAIC PUNCTUATION +0860..086A ; N # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; N # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0888 ; N # Sk ARABIC RAISED ROUND DOT +0889..088E ; N # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0890..0891 ; N # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +0898..089F ; N # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08A0..08C8 ; N # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +08C9 ; N # Lm ARABIC SMALL FARSI YEH +08CA..08E1 ; N # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E2 ; N # Cf ARABIC DISPUTED END OF AYAH +08E3..08FF ; N # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA +0900..0902 ; N # Mn [3] DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI SIGN ANUSVARA +0903 ; N # Mc DEVANAGARI SIGN VISARGA +0904..0939 ; N # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093A ; N # Mn DEVANAGARI VOWEL SIGN OE +093B ; N # Mc DEVANAGARI VOWEL SIGN OOE +093C ; N # Mn DEVANAGARI SIGN NUKTA +093D ; N # Lo DEVANAGARI SIGN AVAGRAHA +093E..0940 ; N # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0941..0948 ; N # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +0949..094C ; N # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094D ; N # Mn DEVANAGARI SIGN VIRAMA +094E..094F ; N # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0950 ; N # Lo DEVANAGARI OM +0951..0957 ; N # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0958..0961 ; N # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0962..0963 ; N # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0964..0965 ; N # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966..096F ; N # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970 ; N # Po DEVANAGARI ABBREVIATION SIGN +0971 ; N # Lm DEVANAGARI SIGN HIGH SPACING DOT +0972..097F ; N # Lo [14] DEVANAGARI LETTER CANDRA A..DEVANAGARI LETTER BBA +0980 ; N # Lo BENGALI ANJI +0981 ; N # Mn BENGALI SIGN CANDRABINDU +0982..0983 ; N # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +0985..098C ; N # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; N # Lo [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; N # Lo [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; N # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; N # Lo BENGALI LETTER LA +09B6..09B9 ; N # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BC ; N # Mn BENGALI SIGN NUKTA +09BD ; N # Lo BENGALI SIGN AVAGRAHA +09BE..09C0 ; N # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C1..09C4 ; N # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09C7..09C8 ; N # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; N # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09CD ; N # Mn BENGALI SIGN VIRAMA +09CE ; N # Lo BENGALI LETTER KHANDA TA +09D7 ; N # Mc BENGALI AU LENGTH MARK +09DC..09DD ; N # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; N # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09E2..09E3 ; N # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09E6..09EF ; N # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +09F0..09F1 ; N # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09F2..09F3 ; N # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09F4..09F9 ; N # No [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN +09FA ; N # So BENGALI ISSHAR +09FB ; N # Sc BENGALI GANDA MARK +09FC ; N # Lo BENGALI LETTER VEDIC ANUSVARA +09FD ; N # Po BENGALI ABBREVIATION SIGN +09FE ; N # Mn BENGALI SANDHI MARK +0A01..0A02 ; N # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A03 ; N # Mc GURMUKHI SIGN VISARGA +0A05..0A0A ; N # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; N # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; N # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; N # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; N # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; N # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; N # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3C ; N # Mn GURMUKHI SIGN NUKTA +0A3E..0A40 ; N # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A41..0A42 ; N # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; N # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; N # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; N # Mn GURMUKHI SIGN UDAAT +0A59..0A5C ; N # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; N # Lo GURMUKHI LETTER FA +0A66..0A6F ; N # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0A70..0A71 ; N # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A72..0A74 ; N # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A75 ; N # Mn GURMUKHI SIGN YAKASH +0A76 ; N # Po GURMUKHI ABBREVIATION SIGN +0A81..0A82 ; N # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0A83 ; N # Mc GUJARATI SIGN VISARGA +0A85..0A8D ; N # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; N # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; N # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; N # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; N # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; N # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABC ; N # Mn GUJARATI SIGN NUKTA +0ABD ; N # Lo GUJARATI SIGN AVAGRAHA +0ABE..0AC0 ; N # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC1..0AC5 ; N # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; N # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0AC9 ; N # Mc GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; N # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0ACD ; N # Mn GUJARATI SIGN VIRAMA +0AD0 ; N # Lo GUJARATI OM +0AE0..0AE1 ; N # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AE2..0AE3 ; N # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AE6..0AEF ; N # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0 ; N # Po GUJARATI ABBREVIATION SIGN +0AF1 ; N # Sc GUJARATI RUPEE SIGN +0AF9 ; N # Lo GUJARATI LETTER ZHA +0AFA..0AFF ; N # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; N # Mn ORIYA SIGN CANDRABINDU +0B02..0B03 ; N # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B05..0B0C ; N # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; N # Lo [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; N # Lo [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; N # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; N # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; N # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3C ; N # Mn ORIYA SIGN NUKTA +0B3D ; N # Lo ORIYA SIGN AVAGRAHA +0B3E ; N # Mc ORIYA VOWEL SIGN AA +0B3F ; N # Mn ORIYA VOWEL SIGN I +0B40 ; N # Mc ORIYA VOWEL SIGN II +0B41..0B44 ; N # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B47..0B48 ; N # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; N # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B4D ; N # Mn ORIYA SIGN VIRAMA +0B55..0B56 ; N # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B57 ; N # Mc ORIYA AU LENGTH MARK +0B5C..0B5D ; N # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; N # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B62..0B63 ; N # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B66..0B6F ; N # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70 ; N # So ORIYA ISSHAR +0B71 ; N # Lo ORIYA LETTER WA +0B72..0B77 ; N # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0B82 ; N # Mn TAMIL SIGN ANUSVARA +0B83 ; N # Lo TAMIL SIGN VISARGA +0B85..0B8A ; N # Lo [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; N # Lo [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; N # Lo [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; N # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; N # Lo TAMIL LETTER JA +0B9E..0B9F ; N # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; N # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; N # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; N # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA +0BBE..0BBF ; N # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC0 ; N # Mn TAMIL VOWEL SIGN II +0BC1..0BC2 ; N # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; N # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; N # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BCD ; N # Mn TAMIL SIGN VIRAMA +0BD0 ; N # Lo TAMIL OM +0BD7 ; N # Mc TAMIL AU LENGTH MARK +0BE6..0BEF ; N # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0..0BF2 ; N # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0BF3..0BF8 ; N # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BF9 ; N # Sc TAMIL RUPEE SIGN +0BFA ; N # So TAMIL NUMBER SIGN +0C00 ; N # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C01..0C03 ; N # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; N # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE +0C05..0C0C ; N # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; N # Lo [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; N # Lo [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; N # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3C ; N # Mn TELUGU SIGN NUKTA +0C3D ; N # Lo TELUGU SIGN AVAGRAHA +0C3E..0C40 ; N # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C41..0C44 ; N # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C46..0C48 ; N # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; N # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; N # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C58..0C5A ; N # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; N # Lo TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; N # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C62..0C63 ; N # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C66..0C6F ; N # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C77 ; N # Po TELUGU SIGN SIDDHAM +0C78..0C7E ; N # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0C7F ; N # So TELUGU SIGN TUUMU +0C80 ; N # Lo KANNADA SIGN SPACING CANDRABINDU +0C81 ; N # Mn KANNADA SIGN CANDRABINDU +0C82..0C83 ; N # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84 ; N # Po KANNADA SIGN SIDDHAM +0C85..0C8C ; N # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; N # Lo [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; N # Lo [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; N # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; N # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBC ; N # Mn KANNADA SIGN NUKTA +0CBD ; N # Lo KANNADA SIGN AVAGRAHA +0CBE ; N # Mc KANNADA VOWEL SIGN AA +0CBF ; N # Mn KANNADA VOWEL SIGN I +0CC0..0CC4 ; N # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC6 ; N # Mn KANNADA VOWEL SIGN E +0CC7..0CC8 ; N # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; N # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CCC..0CCD ; N # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CD5..0CD6 ; N # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CDD..0CDE ; N # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; N # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CE2..0CE3 ; N # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CE6..0CEF ; N # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF1..0CF2 ; N # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3 ; N # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00..0D01 ; N # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D02..0D03 ; N # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D04..0D0C ; N # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; N # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; N # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3B..0D3C ; N # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3D ; N # Lo MALAYALAM SIGN AVAGRAHA +0D3E..0D40 ; N # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D41..0D44 ; N # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D46..0D48 ; N # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; N # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D4D ; N # Mn MALAYALAM SIGN VIRAMA +0D4E ; N # Lo MALAYALAM LETTER DOT REPH +0D4F ; N # So MALAYALAM SIGN PARA +0D54..0D56 ; N # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D57 ; N # Mc MALAYALAM AU LENGTH MARK +0D58..0D5E ; N # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D5F..0D61 ; N # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D62..0D63 ; N # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D66..0D6F ; N # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70..0D78 ; N # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0D79 ; N # So MALAYALAM DATE MARK +0D7A..0D7F ; N # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D81 ; N # Mn SINHALA SIGN CANDRABINDU +0D82..0D83 ; N # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0D85..0D96 ; N # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; N # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; N # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; N # Lo SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; N # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0DCA ; N # Mn SINHALA SIGN AL-LAKUNA +0DCF..0DD1 ; N # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD2..0DD4 ; N # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; N # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD8..0DDF ; N # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DE6..0DEF ; N # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0DF2..0DF3 ; N # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0DF4 ; N # Po SINHALA PUNCTUATION KUNDDALIYA +0E01..0E30 ; N # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E31 ; N # Mn THAI CHARACTER MAI HAN-AKAT +0E32..0E33 ; N # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E34..0E3A ; N # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E3F ; N # Sc THAI CURRENCY SYMBOL BAHT +0E40..0E45 ; N # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E46 ; N # Lm THAI CHARACTER MAIYAMOK +0E47..0E4E ; N # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0E4F ; N # Po THAI CHARACTER FONGMAN +0E50..0E59 ; N # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE +0E5A..0E5B ; N # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0E81..0E82 ; N # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; N # Lo LAO LETTER KHO TAM +0E86..0E8A ; N # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; N # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; N # Lo LAO LETTER LO LOOT +0EA7..0EB0 ; N # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB1 ; N # Mn LAO VOWEL SIGN MAI KAN +0EB2..0EB3 ; N # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EB4..0EBC ; N # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EBD ; N # Lo LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; N # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC6 ; N # Lm LAO KO LA +0EC8..0ECE ; N # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN +0ED0..0ED9 ; N # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE +0EDC..0EDF ; N # Lo [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; N # Lo TIBETAN SYLLABLE OM +0F01..0F03 ; N # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F04..0F12 ; N # Po [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD +0F13 ; N # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F14 ; N # Po TIBETAN MARK GTER TSHEG +0F15..0F17 ; N # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F18..0F19 ; N # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F1A..0F1F ; N # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F20..0F29 ; N # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A..0F33 ; N # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +0F34 ; N # So TIBETAN MARK BSDUS RTAGS +0F35 ; N # Mn TIBETAN MARK NGAS BZUNG NYI ZLA +0F36 ; N # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37 ; N # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38 ; N # So TIBETAN MARK CHE MGO +0F39 ; N # Mn TIBETAN MARK TSA -PHRU +0F3A ; N # Ps TIBETAN MARK GUG RTAGS GYON +0F3B ; N # Pe TIBETAN MARK GUG RTAGS GYAS +0F3C ; N # Ps TIBETAN MARK ANG KHANG GYON +0F3D ; N # Pe TIBETAN MARK ANG KHANG GYAS +0F3E..0F3F ; N # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F40..0F47 ; N # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; N # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F71..0F7E ; N # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F7F ; N # Mc TIBETAN SIGN RNAM BCAD +0F80..0F84 ; N # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F85 ; N # Po TIBETAN MARK PALUTA +0F86..0F87 ; N # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F88..0F8C ; N # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +0F8D..0F97 ; N # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; N # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FBE..0FC5 ; N # So [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC6 ; N # Mn TIBETAN SYMBOL PADMA GDAN +0FC7..0FCC ; N # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; N # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD0..0FD4 ; N # Po [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD5..0FD8 ; N # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +0FD9..0FDA ; N # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +1000..102A ; N # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU +102B..102C ; N # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +102D..1030 ; N # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1031 ; N # Mc MYANMAR VOWEL SIGN E +1032..1037 ; N # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1038 ; N # Mc MYANMAR SIGN VISARGA +1039..103A ; N # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103B..103C ; N # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +103D..103E ; N # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +103F ; N # Lo MYANMAR LETTER GREAT SA +1040..1049 ; N # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +104A..104F ; N # Po [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE +1050..1055 ; N # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +1056..1057 ; N # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1058..1059 ; N # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105A..105D ; N # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +105E..1060 ; N # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1061 ; N # Lo MYANMAR LETTER SGAW KAREN SHA +1062..1064 ; N # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1065..1066 ; N # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +1067..106D ; N # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +106E..1070 ; N # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1071..1074 ; N # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1075..1081 ; N # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +1082 ; N # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1083..1084 ; N # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1085..1086 ; N # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +1087..108C ; N # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108D ; N # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +108E ; N # Lo MYANMAR LETTER RUMAI PALAUNG FA +108F ; N # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5 +1090..1099 ; N # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +109A..109C ; N # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +109D ; N # Mn MYANMAR VOWEL SIGN AITON AI +109E..109F ; N # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +10A0..10C5 ; N # Lu [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; N # Lu GEORGIAN CAPITAL LETTER YN +10CD ; N # Lu GEORGIAN CAPITAL LETTER AEN +10D0..10FA ; N # Ll [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FB ; N # Po GEORGIAN PARAGRAPH SEPARATOR +10FC ; N # Lm MODIFIER LETTER GEORGIAN NAR +10FD..10FF ; N # Ll [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100..115F ; W # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER +1160..11FF ; N # Lo [160] HANGUL JUNGSEONG FILLER..HANGUL JONGSEONG SSANGNIEUN +1200..1248 ; N # Lo [73] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +124A..124D ; N # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; N # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; N # Lo ETHIOPIC SYLLABLE QHWA +125A..125D ; N # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; N # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; N # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; N # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; N # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; N # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; N # Lo ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; N # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; N # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; N # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; N # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; N # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +135D..135F ; N # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1360..1368 ; N # Po [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR +1369..137C ; N # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +1380..138F ; N # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1390..1399 ; N # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +13A0..13F5 ; N # Lu [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +13F8..13FD ; N # Ll [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1400 ; N # Pd CANADIAN SYLLABICS HYPHEN +1401..166C ; N # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166D ; N # So CANADIAN SYLLABICS CHI SIGN +166E ; N # Po CANADIAN SYLLABICS FULL STOP +166F..167F ; N # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1680 ; N # Zs OGHAM SPACE MARK +1681..169A ; N # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B ; N # Ps OGHAM FEATHER MARK +169C ; N # Pe OGHAM REVERSED FEATHER MARK +16A0..16EA ; N # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB..16ED ; N # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +16EE..16F0 ; N # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +16F1..16F8 ; N # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; N # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA +1712..1714 ; N # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1715 ; N # Mc TAGALOG SIGN PAMUDPOD +171F ; N # Lo TAGALOG LETTER ARCHAIC RA +1720..1731 ; N # Lo [18] HANUNOO LETTER A..HANUNOO LETTER HA +1732..1733 ; N # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1734 ; N # Mc HANUNOO SIGN PAMUDPOD +1735..1736 ; N # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +1740..1751 ; N # Lo [18] BUHID LETTER A..BUHID LETTER HA +1752..1753 ; N # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1760..176C ; N # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; N # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1772..1773 ; N # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1780..17B3 ; N # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4..17B5 ; N # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6 ; N # Mc KHMER VOWEL SIGN AA +17B7..17BD ; N # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17BE..17C5 ; N # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C6 ; N # Mn KHMER SIGN NIKAHIT +17C7..17C8 ; N # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +17C9..17D3 ; N # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17D4..17D6 ; N # Po [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D7 ; N # Lm KHMER SIGN LEK TOO +17D8..17DA ; N # Po [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT +17DB ; N # Sc KHMER CURRENCY SYMBOL RIEL +17DC ; N # Lo KHMER SIGN AVAKRAHASANYA +17DD ; N # Mn KHMER SIGN ATTHACAN +17E0..17E9 ; N # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +17F0..17F9 ; N # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +1800..1805 ; N # Po [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS +1806 ; N # Pd MONGOLIAN TODO SOFT HYPHEN +1807..180A ; N # Po [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU +180B..180D ; N # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180E ; N # Cf MONGOLIAN VOWEL SEPARATOR +180F ; N # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR +1810..1819 ; N # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1820..1842 ; N # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1843 ; N # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN +1844..1878 ; N # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; N # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1885..1886 ; N # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +1887..18A8 ; N # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18A9 ; N # Mn MONGOLIAN LETTER ALI GALI DAGALGA +18AA ; N # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; N # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; N # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1920..1922 ; N # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1923..1926 ; N # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1927..1928 ; N # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1929..192B ; N # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; N # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1932 ; N # Mn LIMBU SMALL LETTER ANUSVARA +1933..1938 ; N # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1939..193B ; N # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1940 ; N # So LIMBU SIGN LOO +1944..1945 ; N # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946..194F ; N # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +1950..196D ; N # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; N # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; N # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; N # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +19D0..19D9 ; N # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +19DA ; N # No NEW TAI LUE THAM DIGIT ONE +19DE..19DF ; N # So [2] NEW TAI LUE SIGN LAE..NEW TAI LUE SIGN LAEV +19E0..19FF ; N # So [32] KHMER SYMBOL PATHAMASAT..KHMER SYMBOL DAP-PRAM ROC +1A00..1A16 ; N # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A17..1A18 ; N # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A19..1A1A ; N # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A1B ; N # Mn BUGINESE VOWEL SIGN AE +1A1E..1A1F ; N # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20..1A54 ; N # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1A55 ; N # Mc TAI THAM CONSONANT SIGN MEDIAL RA +1A56 ; N # Mn TAI THAM CONSONANT SIGN MEDIAL LA +1A57 ; N # Mc TAI THAM CONSONANT SIGN LA TANG LAI +1A58..1A5E ; N # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; N # Mn TAI THAM SIGN SAKOT +1A61 ; N # Mc TAI THAM VOWEL SIGN A +1A62 ; N # Mn TAI THAM VOWEL SIGN MAI SAT +1A63..1A64 ; N # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A65..1A6C ; N # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A6D..1A72 ; N # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1A73..1A7C ; N # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; N # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT +1A80..1A89 ; N # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; N # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1AA0..1AA6 ; N # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA7 ; N # Lm TAI THAM SIGN MAI YAMOK +1AA8..1AAD ; N # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AB0..1ABD ; N # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABE ; N # Me COMBINING PARENTHESES OVERLAY +1ABF..1ACE ; N # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; N # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B04 ; N # Mc BALINESE SIGN BISAH +1B05..1B33 ; N # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B34 ; N # Mn BALINESE SIGN REREKAN +1B35 ; N # Mc BALINESE VOWEL SIGN TEDUNG +1B36..1B3A ; N # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3B ; N # Mc BALINESE VOWEL SIGN RA REPA TEDUNG +1B3C ; N # Mn BALINESE VOWEL SIGN LA LENGA +1B3D..1B41 ; N # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B42 ; N # Mn BALINESE VOWEL SIGN PEPET +1B43..1B44 ; N # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG +1B45..1B4C ; N # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B50..1B59 ; N # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A..1B60 ; N # Po [7] BALINESE PANTI..BALINESE PAMENENG +1B61..1B6A ; N # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B6B..1B73 ; N # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B74..1B7C ; N # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +1B7D..1B7E ; N # Po [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1B80..1B81 ; N # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1B82 ; N # Mc SUNDANESE SIGN PANGWISAD +1B83..1BA0 ; N # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BA1 ; N # Mc SUNDANESE CONSONANT SIGN PAMINGKAL +1BA2..1BA5 ; N # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA6..1BA7 ; N # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BA8..1BA9 ; N # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAA ; N # Mc SUNDANESE SIGN PAMAAEH +1BAB..1BAD ; N # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BAE..1BAF ; N # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BB0..1BB9 ; N # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1BBA..1BBF ; N # Lo [6] SUNDANESE AVAGRAHA..SUNDANESE LETTER FINAL M +1BC0..1BE5 ; N # Lo [38] BATAK LETTER A..BATAK LETTER U +1BE6 ; N # Mn BATAK SIGN TOMPI +1BE7 ; N # Mc BATAK VOWEL SIGN E +1BE8..1BE9 ; N # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BEA..1BEC ; N # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BED ; N # Mn BATAK VOWEL SIGN KARO O +1BEE ; N # Mc BATAK VOWEL SIGN U +1BEF..1BF1 ; N # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1BF2..1BF3 ; N # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN +1BFC..1BFF ; N # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C00..1C23 ; N # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C24..1C2B ; N # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C2C..1C33 ; N # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C34..1C35 ; N # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1C36..1C37 ; N # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1C3B..1C3F ; N # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C40..1C49 ; N # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4D..1C4F ; N # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C50..1C59 ; N # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +1C5A..1C77 ; N # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1C78..1C7D ; N # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1C7E..1C7F ; N # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1C80..1C88 ; N # Ll [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1C90..1CBA ; N # Lu [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; N # Lu [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1CC0..1CC7 ; N # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD0..1CD2 ; N # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3 ; N # Po VEDIC SIGN NIHSHVASA +1CD4..1CE0 ; N # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE1 ; N # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CE2..1CE8 ; N # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CE9..1CEC ; N # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CED ; N # Mn VEDIC SIGN TIRYAK +1CEE..1CF3 ; N # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF4 ; N # Mn VEDIC TONE CANDRA ABOVE +1CF5..1CF6 ; N # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CF7 ; N # Mc VEDIC SIGN ATIKRAMA +1CF8..1CF9 ; N # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1CFA ; N # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +1D00..1D2B ; N # Ll [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C..1D6A ; N # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B..1D77 ; N # Ll [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78 ; N # Lm MODIFIER LETTER CYRILLIC EN +1D79..1D7F ; N # Ll [7] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER UPSILON WITH STROKE +1D80..1D9A ; N # Ll [27] LATIN SMALL LETTER B WITH PALATAL HOOK..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B..1DBF ; N # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1DC0..1DFF ; N # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00..1EFF ; N # L& [256] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH LOOP +1F00..1F15 ; N # L& [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; N # Lu [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; N # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; N # Lu [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; N # Ll [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F7D ; N # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1FB4 ; N # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FBC ; N # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FBD ; N # Sk GREEK KORONIS +1FBE ; N # Ll GREEK PROSGEGRAMMENI +1FBF..1FC1 ; N # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FC2..1FC4 ; N # Ll [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FCC ; N # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FCD..1FCF ; N # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FD0..1FD3 ; N # Ll [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FDB ; N # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA +1FDD..1FDF ; N # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FE0..1FEC ; N # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FED..1FEF ; N # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FF2..1FF4 ; N # Ll [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FFC ; N # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI +1FFD..1FFE ; N # Sk [2] GREEK OXIA..GREEK DASIA +2000..200A ; N # Zs [11] EN QUAD..HAIR SPACE +200B..200F ; N # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK +2010 ; A # Pd HYPHEN +2011..2012 ; N # Pd [2] NON-BREAKING HYPHEN..FIGURE DASH +2013..2015 ; A # Pd [3] EN DASH..HORIZONTAL BAR +2016 ; A # Po DOUBLE VERTICAL LINE +2017 ; N # Po DOUBLE LOW LINE +2018 ; A # Pi LEFT SINGLE QUOTATION MARK +2019 ; A # Pf RIGHT SINGLE QUOTATION MARK +201A ; N # Ps SINGLE LOW-9 QUOTATION MARK +201B ; N # Pi SINGLE HIGH-REVERSED-9 QUOTATION MARK +201C ; A # Pi LEFT DOUBLE QUOTATION MARK +201D ; A # Pf RIGHT DOUBLE QUOTATION MARK +201E ; N # Ps DOUBLE LOW-9 QUOTATION MARK +201F ; N # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2020..2022 ; A # Po [3] DAGGER..BULLET +2023 ; N # Po TRIANGULAR BULLET +2024..2027 ; A # Po [4] ONE DOT LEADER..HYPHENATION POINT +2028 ; N # Zl LINE SEPARATOR +2029 ; N # Zp PARAGRAPH SEPARATOR +202A..202E ; N # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +202F ; N # Zs NARROW NO-BREAK SPACE +2030 ; A # Po PER MILLE SIGN +2031 ; N # Po PER TEN THOUSAND SIGN +2032..2033 ; A # Po [2] PRIME..DOUBLE PRIME +2034 ; N # Po TRIPLE PRIME +2035 ; A # Po REVERSED PRIME +2036..2038 ; N # Po [3] REVERSED DOUBLE PRIME..CARET +2039 ; N # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A ; N # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +203B ; A # Po REFERENCE MARK +203C..203D ; N # Po [2] DOUBLE EXCLAMATION MARK..INTERROBANG +203E ; A # Po OVERLINE +203F..2040 ; N # Pc [2] UNDERTIE..CHARACTER TIE +2041..2043 ; N # Po [3] CARET INSERTION POINT..HYPHEN BULLET +2044 ; N # Sm FRACTION SLASH +2045 ; N # Ps LEFT SQUARE BRACKET WITH QUILL +2046 ; N # Pe RIGHT SQUARE BRACKET WITH QUILL +2047..2051 ; N # Po [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY +2052 ; N # Sm COMMERCIAL MINUS SIGN +2053 ; N # Po SWUNG DASH +2054 ; N # Pc INVERTED UNDERTIE +2055..205E ; N # Po [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS +205F ; N # Zs MEDIUM MATHEMATICAL SPACE +2060..2064 ; N # Cf [5] WORD JOINER..INVISIBLE PLUS +2066..206F ; N # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +2070 ; N # No SUPERSCRIPT ZERO +2071 ; N # Lm SUPERSCRIPT LATIN SMALL LETTER I +2074 ; A # No SUPERSCRIPT FOUR +2075..2079 ; N # No [5] SUPERSCRIPT FIVE..SUPERSCRIPT NINE +207A..207C ; N # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +207D ; N # Ps SUPERSCRIPT LEFT PARENTHESIS +207E ; N # Pe SUPERSCRIPT RIGHT PARENTHESIS +207F ; A # Lm SUPERSCRIPT LATIN SMALL LETTER N +2080 ; N # No SUBSCRIPT ZERO +2081..2084 ; A # No [4] SUBSCRIPT ONE..SUBSCRIPT FOUR +2085..2089 ; N # No [5] SUBSCRIPT FIVE..SUBSCRIPT NINE +208A..208C ; N # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +208D ; N # Ps SUBSCRIPT LEFT PARENTHESIS +208E ; N # Pe SUBSCRIPT RIGHT PARENTHESIS +2090..209C ; N # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +20A0..20A8 ; N # Sc [9] EURO-CURRENCY SIGN..RUPEE SIGN +20A9 ; H # Sc WON SIGN +20AA..20AB ; N # Sc [2] NEW SHEQEL SIGN..DONG SIGN +20AC ; A # Sc EURO SIGN +20AD..20C0 ; N # Sc [20] KIP SIGN..SOM SIGN +20D0..20DC ; N # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20DD..20E0 ; N # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E1 ; N # Mn COMBINING LEFT RIGHT ARROW ABOVE +20E2..20E4 ; N # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +20E5..20F0 ; N # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2100..2101 ; N # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2102 ; N # Lu DOUBLE-STRUCK CAPITAL C +2103 ; A # So DEGREE CELSIUS +2104 ; N # So CENTRE LINE SYMBOL +2105 ; A # So CARE OF +2106 ; N # So CADA UNA +2107 ; N # Lu EULER CONSTANT +2108 ; N # So SCRUPLE +2109 ; A # So DEGREE FAHRENHEIT +210A..2112 ; N # L& [9] SCRIPT SMALL G..SCRIPT CAPITAL L +2113 ; A # Ll SCRIPT SMALL L +2114 ; N # So L B BAR SYMBOL +2115 ; N # Lu DOUBLE-STRUCK CAPITAL N +2116 ; A # So NUMERO SIGN +2117 ; N # So SOUND RECORDING COPYRIGHT +2118 ; N # Sm SCRIPT CAPITAL P +2119..211D ; N # Lu [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +211E..2120 ; N # So [3] PRESCRIPTION TAKE..SERVICE MARK +2121..2122 ; A # So [2] TELEPHONE SIGN..TRADE MARK SIGN +2123 ; N # So VERSICLE +2124 ; N # Lu DOUBLE-STRUCK CAPITAL Z +2125 ; N # So OUNCE SIGN +2126 ; A # Lu OHM SIGN +2127 ; N # So INVERTED OHM SIGN +2128 ; N # Lu BLACK-LETTER CAPITAL Z +2129 ; N # So TURNED GREEK SMALL LETTER IOTA +212A ; N # Lu KELVIN SIGN +212B ; A # Lu ANGSTROM SIGN +212C..212D ; N # Lu [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C +212E ; N # So ESTIMATED SYMBOL +212F..2134 ; N # L& [6] SCRIPT SMALL E..SCRIPT SMALL O +2135..2138 ; N # Lo [4] ALEF SYMBOL..DALET SYMBOL +2139 ; N # Ll INFORMATION SOURCE +213A..213B ; N # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN +213C..213F ; N # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI +2140..2144 ; N # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +2145..2149 ; N # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J +214A ; N # So PROPERTY LINE +214B ; N # Sm TURNED AMPERSAND +214C..214D ; N # So [2] PER SIGN..AKTIESELSKAB +214E ; N # Ll TURNED SMALL F +214F ; N # So SYMBOL FOR SAMARITAN SOURCE +2150..2152 ; N # No [3] VULGAR FRACTION ONE SEVENTH..VULGAR FRACTION ONE TENTH +2153..2154 ; A # No [2] VULGAR FRACTION ONE THIRD..VULGAR FRACTION TWO THIRDS +2155..215A ; N # No [6] VULGAR FRACTION ONE FIFTH..VULGAR FRACTION FIVE SIXTHS +215B..215E ; A # No [4] VULGAR FRACTION ONE EIGHTH..VULGAR FRACTION SEVEN EIGHTHS +215F ; N # No FRACTION NUMERATOR ONE +2160..216B ; A # Nl [12] ROMAN NUMERAL ONE..ROMAN NUMERAL TWELVE +216C..216F ; N # Nl [4] ROMAN NUMERAL FIFTY..ROMAN NUMERAL ONE THOUSAND +2170..2179 ; A # Nl [10] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL TEN +217A..2182 ; N # Nl [9] SMALL ROMAN NUMERAL ELEVEN..ROMAN NUMERAL TEN THOUSAND +2183..2184 ; N # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C +2185..2188 ; N # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +2189 ; A # No VULGAR FRACTION ZERO THIRDS +218A..218B ; N # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2190..2194 ; A # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +2195..2199 ; A # So [5] UP DOWN ARROW..SOUTH WEST ARROW +219A..219B ; N # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +219C..219F ; N # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A0 ; N # Sm RIGHTWARDS TWO HEADED ARROW +21A1..21A2 ; N # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A3 ; N # Sm RIGHTWARDS ARROW WITH TAIL +21A4..21A5 ; N # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A6 ; N # Sm RIGHTWARDS ARROW FROM BAR +21A7..21AD ; N # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AE ; N # Sm LEFT RIGHT ARROW WITH STROKE +21AF..21B7 ; N # So [9] DOWNWARDS ZIGZAG ARROW..CLOCKWISE TOP SEMICIRCLE ARROW +21B8..21B9 ; A # So [2] NORTH WEST ARROW TO LONG BAR..LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR +21BA..21CD ; N # So [20] ANTICLOCKWISE OPEN CIRCLE ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21CE..21CF ; N # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; N # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D2 ; A # Sm RIGHTWARDS DOUBLE ARROW +21D3 ; N # So DOWNWARDS DOUBLE ARROW +21D4 ; A # Sm LEFT RIGHT DOUBLE ARROW +21D5..21E6 ; N # So [18] UP DOWN DOUBLE ARROW..LEFTWARDS WHITE ARROW +21E7 ; A # So UPWARDS WHITE ARROW +21E8..21F3 ; N # So [12] RIGHTWARDS WHITE ARROW..UP DOWN WHITE ARROW +21F4..21FF ; N # Sm [12] RIGHT ARROW WITH SMALL CIRCLE..LEFT RIGHT OPEN-HEADED ARROW +2200 ; A # Sm FOR ALL +2201 ; N # Sm COMPLEMENT +2202..2203 ; A # Sm [2] PARTIAL DIFFERENTIAL..THERE EXISTS +2204..2206 ; N # Sm [3] THERE DOES NOT EXIST..INCREMENT +2207..2208 ; A # Sm [2] NABLA..ELEMENT OF +2209..220A ; N # Sm [2] NOT AN ELEMENT OF..SMALL ELEMENT OF +220B ; A # Sm CONTAINS AS MEMBER +220C..220E ; N # Sm [3] DOES NOT CONTAIN AS MEMBER..END OF PROOF +220F ; A # Sm N-ARY PRODUCT +2210 ; N # Sm N-ARY COPRODUCT +2211 ; A # Sm N-ARY SUMMATION +2212..2214 ; N # Sm [3] MINUS SIGN..DOT PLUS +2215 ; A # Sm DIVISION SLASH +2216..2219 ; N # Sm [4] SET MINUS..BULLET OPERATOR +221A ; A # Sm SQUARE ROOT +221B..221C ; N # Sm [2] CUBE ROOT..FOURTH ROOT +221D..2220 ; A # Sm [4] PROPORTIONAL TO..ANGLE +2221..2222 ; N # Sm [2] MEASURED ANGLE..SPHERICAL ANGLE +2223 ; A # Sm DIVIDES +2224 ; N # Sm DOES NOT DIVIDE +2225 ; A # Sm PARALLEL TO +2226 ; N # Sm NOT PARALLEL TO +2227..222C ; A # Sm [6] LOGICAL AND..DOUBLE INTEGRAL +222D ; N # Sm TRIPLE INTEGRAL +222E ; A # Sm CONTOUR INTEGRAL +222F..2233 ; N # Sm [5] SURFACE INTEGRAL..ANTICLOCKWISE CONTOUR INTEGRAL +2234..2237 ; A # Sm [4] THEREFORE..PROPORTION +2238..223B ; N # Sm [4] DOT MINUS..HOMOTHETIC +223C..223D ; A # Sm [2] TILDE OPERATOR..REVERSED TILDE +223E..2247 ; N # Sm [10] INVERTED LAZY S..NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +2248 ; A # Sm ALMOST EQUAL TO +2249..224B ; N # Sm [3] NOT ALMOST EQUAL TO..TRIPLE TILDE +224C ; A # Sm ALL EQUAL TO +224D..2251 ; N # Sm [5] EQUIVALENT TO..GEOMETRICALLY EQUAL TO +2252 ; A # Sm APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253..225F ; N # Sm [13] IMAGE OF OR APPROXIMATELY EQUAL TO..QUESTIONED EQUAL TO +2260..2261 ; A # Sm [2] NOT EQUAL TO..IDENTICAL TO +2262..2263 ; N # Sm [2] NOT IDENTICAL TO..STRICTLY EQUIVALENT TO +2264..2267 ; A # Sm [4] LESS-THAN OR EQUAL TO..GREATER-THAN OVER EQUAL TO +2268..2269 ; N # Sm [2] LESS-THAN BUT NOT EQUAL TO..GREATER-THAN BUT NOT EQUAL TO +226A..226B ; A # Sm [2] MUCH LESS-THAN..MUCH GREATER-THAN +226C..226D ; N # Sm [2] BETWEEN..NOT EQUIVALENT TO +226E..226F ; A # Sm [2] NOT LESS-THAN..NOT GREATER-THAN +2270..2281 ; N # Sm [18] NEITHER LESS-THAN NOR EQUAL TO..DOES NOT SUCCEED +2282..2283 ; A # Sm [2] SUBSET OF..SUPERSET OF +2284..2285 ; N # Sm [2] NOT A SUBSET OF..NOT A SUPERSET OF +2286..2287 ; A # Sm [2] SUBSET OF OR EQUAL TO..SUPERSET OF OR EQUAL TO +2288..2294 ; N # Sm [13] NEITHER A SUBSET OF NOR EQUAL TO..SQUARE CUP +2295 ; A # Sm CIRCLED PLUS +2296..2298 ; N # Sm [3] CIRCLED MINUS..CIRCLED DIVISION SLASH +2299 ; A # Sm CIRCLED DOT OPERATOR +229A..22A4 ; N # Sm [11] CIRCLED RING OPERATOR..DOWN TACK +22A5 ; A # Sm UP TACK +22A6..22BE ; N # Sm [25] ASSERTION..RIGHT ANGLE WITH ARC +22BF ; A # Sm RIGHT TRIANGLE +22C0..22FF ; N # Sm [64] N-ARY LOGICAL AND..Z NOTATION BAG MEMBERSHIP +2300..2307 ; N # So [8] DIAMETER SIGN..WAVY LINE +2308 ; N # Ps LEFT CEILING +2309 ; N # Pe RIGHT CEILING +230A ; N # Ps LEFT FLOOR +230B ; N # Pe RIGHT FLOOR +230C..2311 ; N # So [6] BOTTOM RIGHT CROP..SQUARE LOZENGE +2312 ; A # So ARC +2313..2319 ; N # So [7] SEGMENT..TURNED NOT SIGN +231A..231B ; W # So [2] WATCH..HOURGLASS +231C..231F ; N # So [4] TOP LEFT CORNER..BOTTOM RIGHT CORNER +2320..2321 ; N # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +2322..2328 ; N # So [7] FROWN..KEYBOARD +2329 ; W # Ps LEFT-POINTING ANGLE BRACKET +232A ; W # Pe RIGHT-POINTING ANGLE BRACKET +232B..237B ; N # So [81] ERASE TO THE LEFT..NOT CHECK MARK +237C ; N # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +237D..239A ; N # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +239B..23B3 ; N # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23B4..23DB ; N # So [40] TOP SQUARE BRACKET..FUSE +23DC..23E1 ; N # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +23E2..23E8 ; N # So [7] WHITE TRAPEZIUM..DECIMAL EXPONENT SYMBOL +23E9..23EC ; W # So [4] BLACK RIGHT-POINTING DOUBLE TRIANGLE..BLACK DOWN-POINTING DOUBLE TRIANGLE +23ED..23EF ; N # So [3] BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR..BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23F0 ; W # So ALARM CLOCK +23F1..23F2 ; N # So [2] STOPWATCH..TIMER CLOCK +23F3 ; W # So HOURGLASS WITH FLOWING SAND +23F4..23FF ; N # So [12] BLACK MEDIUM LEFT-POINTING TRIANGLE..OBSERVER EYE SYMBOL +2400..2426 ; N # So [39] SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; N # So [11] OCR HOOK..OCR DOUBLE BACKSLASH +2460..249B ; A # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +249C..24E9 ; A # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +24EA ; N # No CIRCLED DIGIT ZERO +24EB..24FF ; A # No [21] NEGATIVE CIRCLED NUMBER ELEVEN..NEGATIVE CIRCLED DIGIT ZERO +2500..254B ; A # So [76] BOX DRAWINGS LIGHT HORIZONTAL..BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL +254C..254F ; N # So [4] BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL..BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL +2550..2573 ; A # So [36] BOX DRAWINGS DOUBLE HORIZONTAL..BOX DRAWINGS LIGHT DIAGONAL CROSS +2574..257F ; N # So [12] BOX DRAWINGS LIGHT LEFT..BOX DRAWINGS HEAVY UP AND LIGHT DOWN +2580..258F ; A # So [16] UPPER HALF BLOCK..LEFT ONE EIGHTH BLOCK +2590..2591 ; N # So [2] RIGHT HALF BLOCK..LIGHT SHADE +2592..2595 ; A # So [4] MEDIUM SHADE..RIGHT ONE EIGHTH BLOCK +2596..259F ; N # So [10] QUADRANT LOWER LEFT..QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT +25A0..25A1 ; A # So [2] BLACK SQUARE..WHITE SQUARE +25A2 ; N # So WHITE SQUARE WITH ROUNDED CORNERS +25A3..25A9 ; A # So [7] WHITE SQUARE CONTAINING BLACK SMALL SQUARE..SQUARE WITH DIAGONAL CROSSHATCH FILL +25AA..25B1 ; N # So [8] BLACK SMALL SQUARE..WHITE PARALLELOGRAM +25B2..25B3 ; A # So [2] BLACK UP-POINTING TRIANGLE..WHITE UP-POINTING TRIANGLE +25B4..25B5 ; N # So [2] BLACK UP-POINTING SMALL TRIANGLE..WHITE UP-POINTING SMALL TRIANGLE +25B6 ; A # So BLACK RIGHT-POINTING TRIANGLE +25B7 ; A # Sm WHITE RIGHT-POINTING TRIANGLE +25B8..25BB ; N # So [4] BLACK RIGHT-POINTING SMALL TRIANGLE..WHITE RIGHT-POINTING POINTER +25BC..25BD ; A # So [2] BLACK DOWN-POINTING TRIANGLE..WHITE DOWN-POINTING TRIANGLE +25BE..25BF ; N # So [2] BLACK DOWN-POINTING SMALL TRIANGLE..WHITE DOWN-POINTING SMALL TRIANGLE +25C0 ; A # So BLACK LEFT-POINTING TRIANGLE +25C1 ; A # Sm WHITE LEFT-POINTING TRIANGLE +25C2..25C5 ; N # So [4] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE LEFT-POINTING POINTER +25C6..25C8 ; A # So [3] BLACK DIAMOND..WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND +25C9..25CA ; N # So [2] FISHEYE..LOZENGE +25CB ; A # So WHITE CIRCLE +25CC..25CD ; N # So [2] DOTTED CIRCLE..CIRCLE WITH VERTICAL FILL +25CE..25D1 ; A # So [4] BULLSEYE..CIRCLE WITH RIGHT HALF BLACK +25D2..25E1 ; N # So [16] CIRCLE WITH LOWER HALF BLACK..LOWER HALF CIRCLE +25E2..25E5 ; A # So [4] BLACK LOWER RIGHT TRIANGLE..BLACK UPPER RIGHT TRIANGLE +25E6..25EE ; N # So [9] WHITE BULLET..UP-POINTING TRIANGLE WITH RIGHT HALF BLACK +25EF ; A # So LARGE CIRCLE +25F0..25F7 ; N # So [8] WHITE SQUARE WITH UPPER LEFT QUADRANT..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +25F8..25FC ; N # Sm [5] UPPER LEFT TRIANGLE..BLACK MEDIUM SQUARE +25FD..25FE ; W # Sm [2] WHITE MEDIUM SMALL SQUARE..BLACK MEDIUM SMALL SQUARE +25FF ; N # Sm LOWER RIGHT TRIANGLE +2600..2604 ; N # So [5] BLACK SUN WITH RAYS..COMET +2605..2606 ; A # So [2] BLACK STAR..WHITE STAR +2607..2608 ; N # So [2] LIGHTNING..THUNDERSTORM +2609 ; A # So SUN +260A..260D ; N # So [4] ASCENDING NODE..OPPOSITION +260E..260F ; A # So [2] BLACK TELEPHONE..WHITE TELEPHONE +2610..2613 ; N # So [4] BALLOT BOX..SALTIRE +2614..2615 ; W # So [2] UMBRELLA WITH RAIN DROPS..HOT BEVERAGE +2616..261B ; N # So [6] WHITE SHOGI PIECE..BLACK RIGHT POINTING INDEX +261C ; A # So WHITE LEFT POINTING INDEX +261D ; N # So WHITE UP POINTING INDEX +261E ; A # So WHITE RIGHT POINTING INDEX +261F..263F ; N # So [33] WHITE DOWN POINTING INDEX..MERCURY +2640 ; A # So FEMALE SIGN +2641 ; N # So EARTH +2642 ; A # So MALE SIGN +2643..2647 ; N # So [5] JUPITER..PLUTO +2648..2653 ; W # So [12] ARIES..PISCES +2654..265F ; N # So [12] WHITE CHESS KING..BLACK CHESS PAWN +2660..2661 ; A # So [2] BLACK SPADE SUIT..WHITE HEART SUIT +2662 ; N # So WHITE DIAMOND SUIT +2663..2665 ; A # So [3] BLACK CLUB SUIT..BLACK HEART SUIT +2666 ; N # So BLACK DIAMOND SUIT +2667..266A ; A # So [4] WHITE CLUB SUIT..EIGHTH NOTE +266B ; N # So BEAMED EIGHTH NOTES +266C..266D ; A # So [2] BEAMED SIXTEENTH NOTES..MUSIC FLAT SIGN +266E ; N # So MUSIC NATURAL SIGN +266F ; A # Sm MUSIC SHARP SIGN +2670..267E ; N # So [15] WEST SYRIAC CROSS..PERMANENT PAPER SIGN +267F ; W # So WHEELCHAIR SYMBOL +2680..2692 ; N # So [19] DIE FACE-1..HAMMER AND PICK +2693 ; W # So ANCHOR +2694..269D ; N # So [10] CROSSED SWORDS..OUTLINED WHITE STAR +269E..269F ; A # So [2] THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT +26A0 ; N # So WARNING SIGN +26A1 ; W # So HIGH VOLTAGE SIGN +26A2..26A9 ; N # So [8] DOUBLED FEMALE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; W # So [2] MEDIUM WHITE CIRCLE..MEDIUM BLACK CIRCLE +26AC..26BC ; N # So [17] MEDIUM SMALL WHITE CIRCLE..SESQUIQUADRATE +26BD..26BE ; W # So [2] SOCCER BALL..BASEBALL +26BF ; A # So SQUARED KEY +26C0..26C3 ; N # So [4] WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING +26C4..26C5 ; W # So [2] SNOWMAN WITHOUT SNOW..SUN BEHIND CLOUD +26C6..26CD ; A # So [8] RAIN..DISABLED CAR +26CE ; W # So OPHIUCHUS +26CF..26D3 ; A # So [5] PICK..CHAINS +26D4 ; W # So NO ENTRY +26D5..26E1 ; A # So [13] ALTERNATE ONE-WAY LEFT WAY TRAFFIC..RESTRICTED LEFT ENTRY-2 +26E2 ; N # So ASTRONOMICAL SYMBOL FOR URANUS +26E3 ; A # So HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; N # So [4] PENTAGRAM..INVERTED PENTAGRAM +26E8..26E9 ; A # So [2] BLACK CROSS ON SHIELD..SHINTO SHRINE +26EA ; W # So CHURCH +26EB..26F1 ; A # So [7] CASTLE..UMBRELLA ON GROUND +26F2..26F3 ; W # So [2] FOUNTAIN..FLAG IN HOLE +26F4 ; A # So FERRY +26F5 ; W # So SAILBOAT +26F6..26F9 ; A # So [4] SQUARE FOUR CORNERS..PERSON WITH BALL +26FA ; W # So TENT +26FB..26FC ; A # So [2] JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; W # So FUEL PUMP +26FE..26FF ; A # So [2] CUP ON BLACK SQUARE..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700..2704 ; N # So [5] BLACK SAFETY SCISSORS..WHITE SCISSORS +2705 ; W # So WHITE HEAVY CHECK MARK +2706..2709 ; N # So [4] TELEPHONE LOCATION SIGN..ENVELOPE +270A..270B ; W # So [2] RAISED FIST..RAISED HAND +270C..2727 ; N # So [28] VICTORY HAND..WHITE FOUR POINTED STAR +2728 ; W # So SPARKLES +2729..273C ; N # So [20] STRESS OUTLINED WHITE STAR..OPEN CENTRE TEARDROP-SPOKED ASTERISK +273D ; A # So HEAVY TEARDROP-SPOKED ASTERISK +273E..274B ; N # So [14] SIX PETALLED BLACK AND WHITE FLORETTE..HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK +274C ; W # So CROSS MARK +274D ; N # So SHADOWED WHITE CIRCLE +274E ; W # So NEGATIVE SQUARED CROSS MARK +274F..2752 ; N # So [4] LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPER RIGHT SHADOWED WHITE SQUARE +2753..2755 ; W # So [3] BLACK QUESTION MARK ORNAMENT..WHITE EXCLAMATION MARK ORNAMENT +2756 ; N # So BLACK DIAMOND MINUS WHITE X +2757 ; W # So HEAVY EXCLAMATION MARK SYMBOL +2758..2767 ; N # So [16] LIGHT VERTICAL BAR..ROTATED FLORAL HEART BULLET +2768 ; N # Ps MEDIUM LEFT PARENTHESIS ORNAMENT +2769 ; N # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT +276A ; N # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B ; N # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C ; N # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D ; N # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E ; N # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F ; N # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; N # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771 ; N # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772 ; N # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2773 ; N # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2774 ; N # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT +2775 ; N # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT +2776..277F ; A # No [10] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED NUMBER TEN +2780..2793 ; N # No [20] DINGBAT CIRCLED SANS-SERIF DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2794 ; N # So HEAVY WIDE-HEADED RIGHTWARDS ARROW +2795..2797 ; W # So [3] HEAVY PLUS SIGN..HEAVY DIVISION SIGN +2798..27AF ; N # So [24] HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW +27B0 ; W # So CURLY LOOP +27B1..27BE ; N # So [14] NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW..OPEN-OUTLINED RIGHTWARDS ARROW +27BF ; W # So DOUBLE CURLY LOOP +27C0..27C4 ; N # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C5 ; N # Ps LEFT S-SHAPED BAG DELIMITER +27C6 ; N # Pe RIGHT S-SHAPED BAG DELIMITER +27C7..27E5 ; N # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27E6 ; Na # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7 ; Na # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8 ; Na # Ps MATHEMATICAL LEFT ANGLE BRACKET +27E9 ; Na # Pe MATHEMATICAL RIGHT ANGLE BRACKET +27EA ; Na # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB ; Na # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC ; Na # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED ; Na # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE ; N # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF ; N # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS +27F0..27FF ; N # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2800..28FF ; N # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2900..297F ; N # Sm [128] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..DOWN FISH TAIL +2980..2982 ; N # Sm [3] TRIPLE VERTICAL BAR DELIMITER..Z NOTATION TYPE COLON +2983 ; N # Ps LEFT WHITE CURLY BRACKET +2984 ; N # Pe RIGHT WHITE CURLY BRACKET +2985 ; Na # Ps LEFT WHITE PARENTHESIS +2986 ; Na # Pe RIGHT WHITE PARENTHESIS +2987 ; N # Ps Z NOTATION LEFT IMAGE BRACKET +2988 ; N # Pe Z NOTATION RIGHT IMAGE BRACKET +2989 ; N # Ps Z NOTATION LEFT BINDING BRACKET +298A ; N # Pe Z NOTATION RIGHT BINDING BRACKET +298B ; N # Ps LEFT SQUARE BRACKET WITH UNDERBAR +298C ; N # Pe RIGHT SQUARE BRACKET WITH UNDERBAR +298D ; N # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E ; N # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F ; N # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; N # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991 ; N # Ps LEFT ANGLE BRACKET WITH DOT +2992 ; N # Pe RIGHT ANGLE BRACKET WITH DOT +2993 ; N # Ps LEFT ARC LESS-THAN BRACKET +2994 ; N # Pe RIGHT ARC GREATER-THAN BRACKET +2995 ; N # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET +2996 ; N # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET +2997 ; N # Ps LEFT BLACK TORTOISE SHELL BRACKET +2998 ; N # Pe RIGHT BLACK TORTOISE SHELL BRACKET +2999..29D7 ; N # Sm [63] DOTTED FENCE..BLACK HOURGLASS +29D8 ; N # Ps LEFT WIGGLY FENCE +29D9 ; N # Pe RIGHT WIGGLY FENCE +29DA ; N # Ps LEFT DOUBLE WIGGLY FENCE +29DB ; N # Pe RIGHT DOUBLE WIGGLY FENCE +29DC..29FB ; N # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FC ; N # Ps LEFT-POINTING CURVED ANGLE BRACKET +29FD ; N # Pe RIGHT-POINTING CURVED ANGLE BRACKET +29FE..29FF ; N # Sm [2] TINY..MINY +2A00..2AFF ; N # Sm [256] N-ARY CIRCLED DOT OPERATOR..N-ARY WHITE VERTICAL BAR +2B00..2B1A ; N # So [27] NORTH EAST WHITE ARROW..DOTTED SQUARE +2B1B..2B1C ; W # So [2] BLACK LARGE SQUARE..WHITE LARGE SQUARE +2B1D..2B2F ; N # So [19] BLACK VERY SMALL SQUARE..WHITE VERTICAL ELLIPSE +2B30..2B44 ; N # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B45..2B46 ; N # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B47..2B4C ; N # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +2B4D..2B4F ; N # So [3] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..SHORT BACKSLANTED SOUTH ARROW +2B50 ; W # So WHITE MEDIUM STAR +2B51..2B54 ; N # So [4] BLACK SMALL STAR..WHITE RIGHT-POINTING PENTAGON +2B55 ; W # So HEAVY LARGE CIRCLE +2B56..2B59 ; A # So [4] HEAVY OVAL WITH OVAL INSIDE..HEAVY CIRCLED SALTIRE +2B5A..2B73 ; N # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; N # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; N # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2C00..2C5F ; N # L& [96] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C60..2C7B ; N # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E +2C7C..2C7D ; N # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2C7E..2C7F ; N # Lu [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL +2C80..2CE4 ; N # L& [101] COPTIC CAPITAL LETTER ALFA..COPTIC SYMBOL KAI +2CE5..2CEA ; N # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2CEB..2CEE ; N # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CEF..2CF1 ; N # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2CF2..2CF3 ; N # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI +2CF9..2CFC ; N # Po [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER +2CFD ; N # No COPTIC FRACTION ONE HALF +2CFE..2CFF ; N # Po [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER +2D00..2D25 ; N # Ll [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; N # Ll GEORGIAN SMALL LETTER YN +2D2D ; N # Ll GEORGIAN SMALL LETTER AEN +2D30..2D67 ; N # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D6F ; N # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D70 ; N # Po TIFINAGH SEPARATOR MARK +2D7F ; N # Mn TIFINAGH CONSONANT JOINER +2D80..2D96 ; N # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; N # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; N # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; N # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; N # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; N # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; N # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; N # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; N # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DE0..2DFF ; N # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +2E00..2E01 ; N # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E02 ; N # Pi LEFT SUBSTITUTION BRACKET +2E03 ; N # Pf RIGHT SUBSTITUTION BRACKET +2E04 ; N # Pi LEFT DOTTED SUBSTITUTION BRACKET +2E05 ; N # Pf RIGHT DOTTED SUBSTITUTION BRACKET +2E06..2E08 ; N # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E09 ; N # Pi LEFT TRANSPOSITION BRACKET +2E0A ; N # Pf RIGHT TRANSPOSITION BRACKET +2E0B ; N # Po RAISED SQUARE +2E0C ; N # Pi LEFT RAISED OMISSION BRACKET +2E0D ; N # Pf RIGHT RAISED OMISSION BRACKET +2E0E..2E16 ; N # Po [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE +2E17 ; N # Pd DOUBLE OBLIQUE HYPHEN +2E18..2E19 ; N # Po [2] INVERTED INTERROBANG..PALM BRANCH +2E1A ; N # Pd HYPHEN WITH DIAERESIS +2E1B ; N # Po TILDE WITH RING ABOVE +2E1C ; N # Pi LEFT LOW PARAPHRASE BRACKET +2E1D ; N # Pf RIGHT LOW PARAPHRASE BRACKET +2E1E..2E1F ; N # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E20 ; N # Pi LEFT VERTICAL BAR WITH QUILL +2E21 ; N # Pf RIGHT VERTICAL BAR WITH QUILL +2E22 ; N # Ps TOP LEFT HALF BRACKET +2E23 ; N # Pe TOP RIGHT HALF BRACKET +2E24 ; N # Ps BOTTOM LEFT HALF BRACKET +2E25 ; N # Pe BOTTOM RIGHT HALF BRACKET +2E26 ; N # Ps LEFT SIDEWAYS U BRACKET +2E27 ; N # Pe RIGHT SIDEWAYS U BRACKET +2E28 ; N # Ps LEFT DOUBLE PARENTHESIS +2E29 ; N # Pe RIGHT DOUBLE PARENTHESIS +2E2A..2E2E ; N # Po [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK +2E2F ; N # Lm VERTICAL TILDE +2E30..2E39 ; N # Po [10] RING POINT..TOP HALF SECTION SIGN +2E3A..2E3B ; N # Pd [2] TWO-EM DASH..THREE-EM DASH +2E3C..2E3F ; N # Po [4] STENOGRAPHIC FULL STOP..CAPITULUM +2E40 ; N # Pd DOUBLE HYPHEN +2E41 ; N # Po REVERSED COMMA +2E42 ; N # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E43..2E4F ; N # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER +2E50..2E51 ; N # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E52..2E54 ; N # Po [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK +2E55 ; N # Ps LEFT SQUARE BRACKET WITH STROKE +2E56 ; N # Pe RIGHT SQUARE BRACKET WITH STROKE +2E57 ; N # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E58 ; N # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; N # Ps TOP HALF LEFT PARENTHESIS +2E5A ; N # Pe TOP HALF RIGHT PARENTHESIS +2E5B ; N # Ps BOTTOM HALF LEFT PARENTHESIS +2E5C ; N # Pe BOTTOM HALF RIGHT PARENTHESIS +2E5D ; N # Pd OBLIQUE HYPHEN +2E80..2E99 ; W # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; W # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; W # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; W # So [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3000 ; F # Zs IDEOGRAPHIC SPACE +3001..3003 ; W # Po [3] IDEOGRAPHIC COMMA..DITTO MARK +3004 ; W # So JAPANESE INDUSTRIAL STANDARD SYMBOL +3005 ; W # Lm IDEOGRAPHIC ITERATION MARK +3006 ; W # Lo IDEOGRAPHIC CLOSING MARK +3007 ; W # Nl IDEOGRAPHIC NUMBER ZERO +3008 ; W # Ps LEFT ANGLE BRACKET +3009 ; W # Pe RIGHT ANGLE BRACKET +300A ; W # Ps LEFT DOUBLE ANGLE BRACKET +300B ; W # Pe RIGHT DOUBLE ANGLE BRACKET +300C ; W # Ps LEFT CORNER BRACKET +300D ; W # Pe RIGHT CORNER BRACKET +300E ; W # Ps LEFT WHITE CORNER BRACKET +300F ; W # Pe RIGHT WHITE CORNER BRACKET +3010 ; W # Ps LEFT BLACK LENTICULAR BRACKET +3011 ; W # Pe RIGHT BLACK LENTICULAR BRACKET +3012..3013 ; W # So [2] POSTAL MARK..GETA MARK +3014 ; W # Ps LEFT TORTOISE SHELL BRACKET +3015 ; W # Pe RIGHT TORTOISE SHELL BRACKET +3016 ; W # Ps LEFT WHITE LENTICULAR BRACKET +3017 ; W # Pe RIGHT WHITE LENTICULAR BRACKET +3018 ; W # Ps LEFT WHITE TORTOISE SHELL BRACKET +3019 ; W # Pe RIGHT WHITE TORTOISE SHELL BRACKET +301A ; W # Ps LEFT WHITE SQUARE BRACKET +301B ; W # Pe RIGHT WHITE SQUARE BRACKET +301C ; W # Pd WAVE DASH +301D ; W # Ps REVERSED DOUBLE PRIME QUOTATION MARK +301E..301F ; W # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +3020 ; W # So POSTAL MARK FACE +3021..3029 ; W # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +302A..302D ; W # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E..302F ; W # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +3030 ; W # Pd WAVY DASH +3031..3035 ; W # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF +3036..3037 ; W # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +3038..303A ; W # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +303B ; W # Lm VERTICAL IDEOGRAPHIC ITERATION MARK +303C ; W # Lo MASU MARK +303D ; W # Po PART ALTERNATION MARK +303E ; W # So IDEOGRAPHIC VARIATION INDICATOR +303F ; N # So IDEOGRAPHIC HALF FILL SPACE +3041..3096 ; W # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE +3099..309A ; W # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309B..309C ; W # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309D..309E ; W # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +309F ; W # Lo HIRAGANA DIGRAPH YORI +30A0 ; W # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1..30FA ; W # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FB ; W # Po KATAKANA MIDDLE DOT +30FC..30FE ; W # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +30FF ; W # Lo KATAKANA DIGRAPH KOTO +3105..312F ; W # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; W # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +3190..3191 ; W # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3192..3195 ; W # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3196..319F ; W # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31A0..31BF ; W # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31C0..31E3 ; W # So [36] CJK STROKE T..CJK STROKE Q +31EF ; W # So IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +31F0..31FF ; W # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3200..321E ; W # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +3220..3229 ; W # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +322A..3247 ; W # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3248..324F ; A # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3250 ; W # So PARTNERSHIP SIGN +3251..325F ; W # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3260..327F ; W # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +3280..3289 ; W # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +328A..32B0 ; W # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32B1..32BF ; W # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +32C0..32FF ; W # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA +3300..33FF ; W # So [256] SQUARE APAATO..SQUARE GAL +3400..4DBF ; W # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4DC0..4DFF ; N # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +4E00..9FFF ; W # Lo [20992] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFF +A000..A014 ; W # Lo [21] YI SYLLABLE IT..YI SYLLABLE E +A015 ; W # Lm YI SYLLABLE WU +A016..A48C ; W # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A490..A4C6 ; W # So [55] YI RADICAL QOT..YI RADICAL KE +A4D0..A4F7 ; N # Lo [40] LISU LETTER BA..LISU LETTER OE +A4F8..A4FD ; N # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A4FE..A4FF ; N # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A500..A60B ; N # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A60C ; N # Lm VAI SYLLABLE LENGTHENER +A60D..A60F ; N # Po [3] VAI COMMA..VAI QUESTION MARK +A610..A61F ; N # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A620..A629 ; N # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE +A62A..A62B ; N # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A640..A66D ; N # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A66E ; N # Lo CYRILLIC LETTER MULTIOCULAR O +A66F ; N # Mn COMBINING CYRILLIC VZMET +A670..A672 ; N # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN +A673 ; N # Po SLAVONIC ASTERISK +A674..A67D ; N # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A67E ; N # Po CYRILLIC KAVYKA +A67F ; N # Lm CYRILLIC PAYEROK +A680..A69B ; N # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O +A69C..A69D ; N # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A69E..A69F ; N # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6A0..A6E5 ; N # Lo [70] BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; N # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0..A6F1 ; N # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A6F2..A6F7 ; N # Po [6] BAMUM NJAEMLI..BAMUM QUESTION MARK +A700..A716 ; N # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717..A71F ; N # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720..A721 ; N # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A722..A76F ; N # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON +A770 ; N # Lm MODIFIER LETTER US +A771..A787 ; N # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T +A788 ; N # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789..A78A ; N # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A78B..A78E ; N # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F ; N # Lo LATIN LETTER SINOLOGICAL DOT +A790..A7CA ; N # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; N # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; N # Ll LATIN SMALL LETTER DOUBLE THORN +A7D5..A7D9 ; N # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S +A7F2..A7F4 ; N # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F5..A7F6 ; N # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H +A7F7 ; N # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7F8..A7F9 ; N # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA ; N # Ll LATIN LETTER SMALL CAPITAL TURNED M +A7FB..A7FF ; N # Lo [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M +A800..A801 ; N # Lo [2] SYLOTI NAGRI LETTER A..SYLOTI NAGRI LETTER I +A802 ; N # Mn SYLOTI NAGRI SIGN DVISVARA +A803..A805 ; N # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A806 ; N # Mn SYLOTI NAGRI SIGN HASANTA +A807..A80A ; N # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80B ; N # Mn SYLOTI NAGRI SIGN ANUSVARA +A80C..A822 ; N # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A823..A824 ; N # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A825..A826 ; N # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A827 ; N # Mc SYLOTI NAGRI VOWEL SIGN OO +A828..A82B ; N # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C ; N # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA +A830..A835 ; N # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +A836..A837 ; N # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A838 ; N # Sc NORTH INDIC RUPEE MARK +A839 ; N # So NORTH INDIC QUANTITY MARK +A840..A873 ; N # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A874..A877 ; N # Po [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD +A880..A881 ; N # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A882..A8B3 ; N # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8B4..A8C3 ; N # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A8C4..A8C5 ; N # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8CE..A8CF ; N # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0..A8D9 ; N # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8E0..A8F1 ; N # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8F2..A8F7 ; N # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8F8..A8FA ; N # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB ; N # Lo DEVANAGARI HEADSTROKE +A8FC ; N # Po DEVANAGARI SIGN SIDDHAM +A8FD..A8FE ; N # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A8FF ; N # Mn DEVANAGARI VOWEL SIGN AY +A900..A909 ; N # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A90A..A925 ; N # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A926..A92D ; N # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A92E..A92F ; N # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930..A946 ; N # Lo [23] REJANG LETTER KA..REJANG LETTER A +A947..A951 ; N # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A952..A953 ; N # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A95F ; N # Po REJANG SECTION MARK +A960..A97C ; W # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A980..A982 ; N # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A983 ; N # Mc JAVANESE SIGN WIGNYAN +A984..A9B2 ; N # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9B3 ; N # Mn JAVANESE SIGN CECAK TELU +A9B4..A9B5 ; N # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9B6..A9B9 ; N # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BA..A9BB ; N # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BC..A9BD ; N # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9BE..A9C0 ; N # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON +A9C1..A9CD ; N # Po [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH +A9CF ; N # Lm JAVANESE PANGRANGKEP +A9D0..A9D9 ; N # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9DE..A9DF ; N # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +A9E0..A9E4 ; N # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E5 ; N # Mn MYANMAR SIGN SHAN SAW +A9E6 ; N # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION +A9E7..A9EF ; N # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9F0..A9F9 ; N # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +A9FA..A9FE ; N # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; N # Lo [41] CHAM LETTER A..CHAM LETTER HA +AA29..AA2E ; N # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA2F..AA30 ; N # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA31..AA32 ; N # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA33..AA34 ; N # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA35..AA36 ; N # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA40..AA42 ; N # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA43 ; N # Mn CHAM CONSONANT SIGN FINAL NG +AA44..AA4B ; N # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA4C ; N # Mn CHAM CONSONANT SIGN FINAL M +AA4D ; N # Mc CHAM CONSONANT SIGN FINAL H +AA50..AA59 ; N # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5C..AA5F ; N # Po [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA +AA60..AA6F ; N # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA70 ; N # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AA71..AA76 ; N # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA77..AA79 ; N # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +AA7A ; N # Lo MYANMAR LETTER AITON RA +AA7B ; N # Mc MYANMAR SIGN PAO KAREN TONE +AA7C ; N # Mn MYANMAR SIGN TAI LAING TONE-2 +AA7D ; N # Mc MYANMAR SIGN TAI LAING TONE-5 +AA7E..AA7F ; N # Lo [2] MYANMAR LETTER SHWE PALAUNG CHA..MYANMAR LETTER SHWE PALAUNG SHA +AA80..AAAF ; N # Lo [48] TAI VIET LETTER LOW KO..TAI VIET LETTER HIGH O +AAB0 ; N # Mn TAI VIET MAI KANG +AAB1 ; N # Lo TAI VIET VOWEL AA +AAB2..AAB4 ; N # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB5..AAB6 ; N # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB7..AAB8 ; N # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AAB9..AABD ; N # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AABE..AABF ; N # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC0 ; N # Lo TAI VIET TONE MAI NUENG +AAC1 ; N # Mn TAI VIET TONE MAI THO +AAC2 ; N # Lo TAI VIET TONE MAI SONG +AADB..AADC ; N # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AADD ; N # Lm TAI VIET SYMBOL SAM +AADE..AADF ; N # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAE0..AAEA ; N # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAEB ; N # Mc MEETEI MAYEK VOWEL SIGN II +AAEC..AAED ; N # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAEE..AAEF ; N # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF0..AAF1 ; N # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +AAF2 ; N # Lo MEETEI MAYEK ANJI +AAF3..AAF4 ; N # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AAF5 ; N # Mc MEETEI MAYEK VOWEL SIGN VISARGA +AAF6 ; N # Mn MEETEI MAYEK VIRAMA +AB01..AB06 ; N # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; N # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; N # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; N # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; N # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB30..AB5A ; N # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB5B ; N # Sk MODIFIER BREVE WITH INVERTED BREVE +AB5C..AB5F ; N # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB60..AB68 ; N # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69 ; N # Lm MODIFIER LETTER SMALL TURNED W +AB6A..AB6B ; N # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +AB70..ABBF ; N # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +ABC0..ABE2 ; N # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +ABE3..ABE4 ; N # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE5 ; N # Mn MEETEI MAYEK VOWEL SIGN ANAP +ABE6..ABE7 ; N # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE8 ; N # Mn MEETEI MAYEK VOWEL SIGN UNAP +ABE9..ABEA ; N # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEB ; N # Po MEETEI MAYEK CHEIKHEI +ABEC ; N # Mc MEETEI MAYEK LUM IYEK +ABED ; N # Mn MEETEI MAYEK APUN IYEK +ABF0..ABF9 ; N # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +AC00..D7A3 ; W # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH +D7B0..D7C6 ; N # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; N # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +D800..DB7F ; N # Cs [896] .. +DB80..DBFF ; N # Cs [128] .. +DC00..DFFF ; N # Cs [1024] .. +E000..F8FF ; A # Co [6400] .. +F900..FA6D ; W # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA6E..FA6F ; W # Cn [2] .. +FA70..FAD9 ; W # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FADA..FAFF ; W # Cn [38] .. +FB00..FB06 ; N # Ll [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; N # Ll [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB1D ; N # Lo HEBREW LETTER YOD WITH HIRIQ +FB1E ; N # Mn HEBREW POINT JUDEO-SPANISH VARIKA +FB1F..FB28 ; N # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB29 ; N # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN +FB2A..FB36 ; N # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; N # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; N # Lo HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; N # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; N # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FB4F ; N # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED +FB50..FBB1 ; N # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBB2..FBC2 ; N # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBD3..FD3D ; N # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD3E ; N # Pe ORNATE LEFT PARENTHESIS +FD3F ; N # Ps ORNATE RIGHT PARENTHESIS +FD40..FD4F ; N # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FD50..FD8F ; N # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; N # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDCF ; N # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDF0..FDFB ; N # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FDFC ; N # Sc RIAL SIGN +FDFD..FDFF ; N # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FE00..FE0F ; A # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE10..FE16 ; W # Po [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE17 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE18 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE19 ; W # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE20..FE2F ; N # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +FE30 ; W # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE31..FE32 ; W # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE33..FE34 ; W # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE35 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE36 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE37 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE38 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE39 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3A ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3B ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3C ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3D ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3E ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE3F ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE40 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE41 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE42 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE43 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE44 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE45..FE46 ; W # Po [2] SESAME DOT..WHITE SESAME DOT +FE47 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE48 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE49..FE4C ; W # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE4D..FE4F ; W # Pc [3] DASHED LOW LINE..WAVY LOW LINE +FE50..FE52 ; W # Po [3] SMALL COMMA..SMALL FULL STOP +FE54..FE57 ; W # Po [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK +FE58 ; W # Pd SMALL EM DASH +FE59 ; W # Ps SMALL LEFT PARENTHESIS +FE5A ; W # Pe SMALL RIGHT PARENTHESIS +FE5B ; W # Ps SMALL LEFT CURLY BRACKET +FE5C ; W # Pe SMALL RIGHT CURLY BRACKET +FE5D ; W # Ps SMALL LEFT TORTOISE SHELL BRACKET +FE5E ; W # Pe SMALL RIGHT TORTOISE SHELL BRACKET +FE5F..FE61 ; W # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE62 ; W # Sm SMALL PLUS SIGN +FE63 ; W # Pd SMALL HYPHEN-MINUS +FE64..FE66 ; W # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FE68 ; W # Po SMALL REVERSE SOLIDUS +FE69 ; W # Sc SMALL DOLLAR SIGN +FE6A..FE6B ; W # Po [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT +FE70..FE74 ; N # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; N # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FEFF ; N # Cf ZERO WIDTH NO-BREAK SPACE +FF01..FF03 ; F # Po [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN +FF04 ; F # Sc FULLWIDTH DOLLAR SIGN +FF05..FF07 ; F # Po [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE +FF08 ; F # Ps FULLWIDTH LEFT PARENTHESIS +FF09 ; F # Pe FULLWIDTH RIGHT PARENTHESIS +FF0A ; F # Po FULLWIDTH ASTERISK +FF0B ; F # Sm FULLWIDTH PLUS SIGN +FF0C ; F # Po FULLWIDTH COMMA +FF0D ; F # Pd FULLWIDTH HYPHEN-MINUS +FF0E..FF0F ; F # Po [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS +FF10..FF19 ; F # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +FF1A..FF1B ; F # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1C..FF1E ; F # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF1F..FF20 ; F # Po [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT +FF21..FF3A ; F # Lu [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +FF3B ; F # Ps FULLWIDTH LEFT SQUARE BRACKET +FF3C ; F # Po FULLWIDTH REVERSE SOLIDUS +FF3D ; F # Pe FULLWIDTH RIGHT SQUARE BRACKET +FF3E ; F # Sk FULLWIDTH CIRCUMFLEX ACCENT +FF3F ; F # Pc FULLWIDTH LOW LINE +FF40 ; F # Sk FULLWIDTH GRAVE ACCENT +FF41..FF5A ; F # Ll [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +FF5B ; F # Ps FULLWIDTH LEFT CURLY BRACKET +FF5C ; F # Sm FULLWIDTH VERTICAL LINE +FF5D ; F # Pe FULLWIDTH RIGHT CURLY BRACKET +FF5E ; F # Sm FULLWIDTH TILDE +FF5F ; F # Ps FULLWIDTH LEFT WHITE PARENTHESIS +FF60 ; F # Pe FULLWIDTH RIGHT WHITE PARENTHESIS +FF61 ; H # Po HALFWIDTH IDEOGRAPHIC FULL STOP +FF62 ; H # Ps HALFWIDTH LEFT CORNER BRACKET +FF63 ; H # Pe HALFWIDTH RIGHT CORNER BRACKET +FF64..FF65 ; H # Po [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT +FF66..FF6F ; H # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU +FF70 ; H # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF71..FF9D ; H # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FF9E..FF9F ; H # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +FFA0..FFBE ; H # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; H # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; H # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; H # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; H # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +FFE0..FFE1 ; F # Sc [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN +FFE2 ; F # Sm FULLWIDTH NOT SIGN +FFE3 ; F # Sk FULLWIDTH MACRON +FFE4 ; F # So FULLWIDTH BROKEN BAR +FFE5..FFE6 ; F # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +FFE8 ; H # So HALFWIDTH FORMS LIGHT VERTICAL +FFE9..FFEC ; H # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +FFED..FFEE ; H # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFF9..FFFB ; N # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +FFFC ; N # So OBJECT REPLACEMENT CHARACTER +FFFD ; A # So REPLACEMENT CHARACTER +10000..1000B ; N # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; N # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; N # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; N # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; N # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; N # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; N # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10100..10102 ; N # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +10107..10133 ; N # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10137..1013F ; N # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10140..10174 ; N # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10175..10178 ; N # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +10179..10189 ; N # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018A..1018B ; N # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +1018C..1018E ; N # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; N # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; N # So GREEK SYMBOL TAU RHO +101D0..101FC ; N # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +101FD ; N # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +10280..1029C ; N # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; N # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3 +102E0 ; N # Mn COPTIC EPACT THOUSANDS MARK +102E1..102FB ; N # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10300..1031F ; N # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +10320..10323 ; N # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +1032D..1032F ; N # Lo [3] OLD ITALIC LETTER YE..OLD ITALIC LETTER SOUTHERN TSE +10330..10340 ; N # Lo [17] GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341 ; N # Nl GOTHIC LETTER NINETY +10342..10349 ; N # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; N # Nl GOTHIC LETTER NINE HUNDRED +10350..10375 ; N # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10376..1037A ; N # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10380..1039D ; N # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039F ; N # Po UGARITIC WORD DIVIDER +103A0..103C3 ; N # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; N # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D0 ; N # Po OLD PERSIAN WORD DIVIDER +103D1..103D5 ; N # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +10400..1044F ; N # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW +10450..1047F ; N # Lo [48] SHAVIAN LETTER PEEP..SHAVIAN LETTER YEW +10480..1049D ; N # Lo [30] OSMANYA LETTER ALEF..OSMANYA LETTER OO +104A0..104A9 ; N # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104B0..104D3 ; N # Lu [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +104D8..104FB ; N # Ll [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10500..10527 ; N # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; N # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +1056F ; N # Po CAUCASIAN ALBANIAN CITATION MARK +10570..1057A ; N # Lu [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; N # Lu [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; N # Lu [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; N # Lu [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10597..105A1 ; N # Ll [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; N # Ll [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; N # Ll [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; N # Ll [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10600..10736 ; N # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; N # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; N # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10780..10785 ; N # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; N # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; N # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +10800..10805 ; N # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; N # Lo CYPRIOT SYLLABLE JO +1080A..10835 ; N # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; N # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; N # Lo CYPRIOT SYLLABLE ZA +1083F ; N # Lo CYPRIOT SYLLABLE ZO +10840..10855 ; N # Lo [22] IMPERIAL ARAMAIC LETTER ALEPH..IMPERIAL ARAMAIC LETTER TAW +10857 ; N # Po IMPERIAL ARAMAIC SECTION SIGN +10858..1085F ; N # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10860..10876 ; N # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10877..10878 ; N # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10879..1087F ; N # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +10880..1089E ; N # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108A7..108AF ; N # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108E0..108F2 ; N # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; N # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +108FB..108FF ; N # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10900..10915 ; N # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916..1091B ; N # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +1091F ; N # Po PHOENICIAN WORD SEPARATOR +10920..10939 ; N # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +1093F ; N # Po LYDIAN TRIANGULAR MARK +10980..1099F ; N # Lo [32] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2 +109A0..109B7 ; N # Lo [24] MEROITIC CURSIVE LETTER A..MEROITIC CURSIVE LETTER DA +109BC..109BD ; N # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109BE..109BF ; N # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +109C0..109CF ; N # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; N # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A00 ; N # Lo KHAROSHTHI LETTER A +10A01..10A03 ; N # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; N # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; N # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A10..10A13 ; N # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; N # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; N # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A38..10A3A ; N # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; N # Mn KHAROSHTHI VIRAMA +10A40..10A48 ; N # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A50..10A58 ; N # Po [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES +10A60..10A7C ; N # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A7D..10A7E ; N # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A7F ; N # Po OLD SOUTH ARABIAN NUMERIC INDICATOR +10A80..10A9C ; N # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10A9D..10A9F ; N # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AC0..10AC7 ; N # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC8 ; N # So MANICHAEAN SIGN UD +10AC9..10AE4 ; N # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10AE5..10AE6 ; N # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10AEB..10AEF ; N # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10AF0..10AF6 ; N # Po [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER +10B00..10B35 ; N # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B39..10B3F ; N # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B40..10B55 ; N # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B58..10B5F ; N # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B60..10B72 ; N # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B78..10B7F ; N # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10B80..10B91 ; N # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10B99..10B9C ; N # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10BA9..10BAF ; N # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10C00..10C48 ; N # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C80..10CB2 ; N # Lu [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +10CC0..10CF2 ; N # Ll [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +10CFA..10CFF ; N # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10D00..10D23 ; N # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10D24..10D27 ; N # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10D30..10D39 ; N # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +10E60..10E7E ; N # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E80..10EA9 ; N # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EAB..10EAC ; N # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EAD ; N # Pd YEZIDI HYPHENATION MARK +10EB0..10EB1 ; N # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10EFD..10EFF ; N # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F00..10F1C ; N # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F1D..10F26 ; N # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F27 ; N # Lo OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; N # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F46..10F50 ; N # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F51..10F54 ; N # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10F55..10F59 ; N # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F70..10F81 ; N # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10F82..10F85 ; N # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +10F86..10F89 ; N # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +10FB0..10FC4 ; N # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FC5..10FCB ; N # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +10FE0..10FF6 ; N # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11000 ; N # Mc BRAHMI SIGN CANDRABINDU +11001 ; N # Mn BRAHMI SIGN ANUSVARA +11002 ; N # Mc BRAHMI SIGN VISARGA +11003..11037 ; N # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA +11038..11046 ; N # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA +11047..1104D ; N # Po [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS +11052..11065 ; N # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +11066..1106F ; N # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +11070 ; N # Mn BRAHMI SIGN OLD TAMIL VIRAMA +11071..11072 ; N # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11073..11074 ; N # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +11075 ; N # Lo BRAHMI LETTER OLD TAMIL LLA +1107F ; N # Mn BRAHMI NUMBER JOINER +11080..11081 ; N # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA +11082 ; N # Mc KAITHI SIGN VISARGA +11083..110AF ; N # Lo [45] KAITHI LETTER A..KAITHI LETTER HA +110B0..110B2 ; N # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B3..110B6 ; N # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B7..110B8 ; N # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +110B9..110BA ; N # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110BB..110BC ; N # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BD ; N # Cf KAITHI NUMBER SIGN +110BE..110C1 ; N # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +110C2 ; N # Mn KAITHI VOWEL SIGN VOCALIC R +110CD ; N # Cf KAITHI NUMBER SIGN ABOVE +110D0..110E8 ; N # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +110F0..110F9 ; N # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11100..11102 ; N # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11103..11126 ; N # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11127..1112B ; N # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112C ; N # Mc CHAKMA VOWEL SIGN E +1112D..11134 ; N # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11136..1113F ; N # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +11140..11143 ; N # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11144 ; N # Lo CHAKMA LETTER LHAA +11145..11146 ; N # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11147 ; N # Lo CHAKMA LETTER VAA +11150..11172 ; N # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11173 ; N # Mn MAHAJANI SIGN NUKTA +11174..11175 ; N # Po [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK +11176 ; N # Lo MAHAJANI LIGATURE SHRI +11180..11181 ; N # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +11182 ; N # Mc SHARADA SIGN VISARGA +11183..111B2 ; N # Lo [48] SHARADA LETTER A..SHARADA LETTER HA +111B3..111B5 ; N # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111B6..111BE ; N # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111BF..111C0 ; N # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111C1..111C4 ; N # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111C5..111C8 ; N # Po [4] SHARADA DANDA..SHARADA SEPARATOR +111C9..111CC ; N # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CD ; N # Po SHARADA SUTRA MARK +111CE ; N # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF ; N # Mn SHARADA SIGN INVERTED CANDRABINDU +111D0..111D9 ; N # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +111DA ; N # Lo SHARADA EKAM +111DB ; N # Po SHARADA SIGN SIDDHAM +111DC ; N # Lo SHARADA HEADSTROKE +111DD..111DF ; N # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +111E1..111F4 ; N # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +11200..11211 ; N # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; N # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1122C..1122E ; N # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +1122F..11231 ; N # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11232..11233 ; N # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11234 ; N # Mn KHOJKI SIGN ANUSVARA +11235 ; N # Mc KHOJKI SIGN VIRAMA +11236..11237 ; N # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +11238..1123D ; N # Po [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN +1123E ; N # Mn KHOJKI SIGN SUKUN +1123F..11240 ; N # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11241 ; N # Mn KHOJKI VOWEL SIGN VOCALIC R +11280..11286 ; N # Lo [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; N # Lo MULTANI LETTER GHA +1128A..1128D ; N # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; N # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; N # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112A9 ; N # Po MULTANI SECTION MARK +112B0..112DE ; N # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +112DF ; N # Mn KHUDAWADI SIGN ANUSVARA +112E0..112E2 ; N # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +112E3..112EA ; N # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +112F0..112F9 ; N # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11300..11301 ; N # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +11302..11303 ; N # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +11305..1130C ; N # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; N # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; N # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; N # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; N # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; N # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133B..1133C ; N # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +1133D ; N # Lo GRANTHA SIGN AVAGRAHA +1133E..1133F ; N # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11340 ; N # Mn GRANTHA VOWEL SIGN II +11341..11344 ; N # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; N # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134D ; N # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA +11350 ; N # Lo GRANTHA OM +11357 ; N # Mc GRANTHA AU LENGTH MARK +1135D..11361 ; N # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL +11362..11363 ; N # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11366..1136C ; N # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; N # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11400..11434 ; N # Lo [53] NEWA LETTER A..NEWA LETTER HA +11435..11437 ; N # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11438..1143F ; N # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11440..11441 ; N # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11442..11444 ; N # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11445 ; N # Mc NEWA SIGN VISARGA +11446 ; N # Mn NEWA SIGN NUKTA +11447..1144A ; N # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1144B..1144F ; N # Po [5] NEWA DANDA..NEWA ABBREVIATION SIGN +11450..11459 ; N # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +1145A..1145B ; N # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; N # Po NEWA INSERTION SIGN +1145E ; N # Mn NEWA SANDHI MARK +1145F..11461 ; N # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; N # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114B0..114B2 ; N # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B3..114B8 ; N # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114B9 ; N # Mc TIRHUTA VOWEL SIGN E +114BA ; N # Mn TIRHUTA VOWEL SIGN SHORT E +114BB..114BE ; N # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114BF..114C0 ; N # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C1 ; N # Mc TIRHUTA SIGN VISARGA +114C2..114C3 ; N # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +114C4..114C5 ; N # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C6 ; N # Po TIRHUTA ABBREVIATION SIGN +114C7 ; N # Lo TIRHUTA OM +114D0..114D9 ; N # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11580..115AE ; N # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115AF..115B1 ; N # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B2..115B5 ; N # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115B8..115BB ; N # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BC..115BD ; N # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BE ; N # Mc SIDDHAM SIGN VISARGA +115BF..115C0 ; N # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115C1..115D7 ; N # Po [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +115D8..115DB ; N # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +115DC..115DD ; N # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11600..1162F ; N # Lo [48] MODI LETTER A..MODI LETTER LLA +11630..11632 ; N # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +11633..1163A ; N # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163B..1163C ; N # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163D ; N # Mn MODI SIGN ANUSVARA +1163E ; N # Mc MODI SIGN VISARGA +1163F..11640 ; N # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +11641..11643 ; N # Po [3] MODI DANDA..MODI ABBREVIATION SIGN +11644 ; N # Lo MODI SIGN HUVA +11650..11659 ; N # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE +11660..1166C ; N # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +11680..116AA ; N # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA +116AB ; N # Mn TAKRI SIGN ANUSVARA +116AC ; N # Mc TAKRI SIGN VISARGA +116AD ; N # Mn TAKRI VOWEL SIGN AA +116AE..116AF ; N # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B0..116B5 ; N # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B6 ; N # Mc TAKRI SIGN VIRAMA +116B7 ; N # Mn TAKRI SIGN NUKTA +116B8 ; N # Lo TAKRI LETTER ARCHAIC KHA +116B9 ; N # Po TAKRI ABBREVIATION SIGN +116C0..116C9 ; N # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11700..1171A ; N # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +1171D..1171F ; N # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11720..11721 ; N # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11722..11725 ; N # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11726 ; N # Mc AHOM VOWEL SIGN E +11727..1172B ; N # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +11730..11739 ; N # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +1173A..1173B ; N # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +1173C..1173E ; N # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1173F ; N # So AHOM SYMBOL VI +11740..11746 ; N # Lo [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; N # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA +1182C..1182E ; N # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +1182F..11837 ; N # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11838 ; N # Mc DOGRA SIGN VISARGA +11839..1183A ; N # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1183B ; N # Po DOGRA ABBREVIATION SIGN +118A0..118DF ; N # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +118E0..118E9 ; N # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +118EA..118F2 ; N # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +118FF ; N # Lo WARANG CITI OM +11900..11906 ; N # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E +11909 ; N # Lo DIVES AKURU LETTER O +1190C..11913 ; N # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; N # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; N # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +11930..11935 ; N # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; N # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C ; N # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D ; N # Mc DIVES AKURU SIGN HALANTA +1193E ; N # Mn DIVES AKURU VIRAMA +1193F ; N # Lo DIVES AKURU PREFIXED NASAL SIGN +11940 ; N # Mc DIVES AKURU MEDIAL YA +11941 ; N # Lo DIVES AKURU INITIAL RA +11942 ; N # Mc DIVES AKURU MEDIAL RA +11943 ; N # Mn DIVES AKURU SIGN NUKTA +11944..11946 ; N # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +11950..11959 ; N # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +119A0..119A7 ; N # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; N # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119D1..119D3 ; N # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119D4..119D7 ; N # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; N # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119DC..119DF ; N # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E0 ; N # Mn NANDINAGARI SIGN VIRAMA +119E1 ; N # Lo NANDINAGARI SIGN AVAGRAHA +119E2 ; N # Po NANDINAGARI SIGN SIDDHAM +119E3 ; N # Lo NANDINAGARI HEADSTROKE +119E4 ; N # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A00 ; N # Lo ZANABAZAR SQUARE LETTER A +11A01..11A0A ; N # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A0B..11A32 ; N # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A33..11A38 ; N # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A39 ; N # Mc ZANABAZAR SQUARE SIGN VISARGA +11A3A ; N # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A3B..11A3E ; N # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A3F..11A46 ; N # Po [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A47 ; N # Mn ZANABAZAR SQUARE SUBJOINER +11A50 ; N # Lo SOYOMBO LETTER A +11A51..11A56 ; N # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A57..11A58 ; N # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A59..11A5B ; N # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A5C..11A89 ; N # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A8A..11A96 ; N # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A97 ; N # Mc SOYOMBO SIGN VISARGA +11A98..11A99 ; N # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11A9A..11A9C ; N # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9D ; N # Lo SOYOMBO MARK PLUTA +11A9E..11AA2 ; N # Po [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2 +11AB0..11ABF ; N # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA +11AC0..11AF8 ; N # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B00..11B09 ; N # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C00..11C08 ; N # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; N # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C2F ; N # Mc BHAIKSUKI VOWEL SIGN AA +11C30..11C36 ; N # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; N # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3E ; N # Mc BHAIKSUKI SIGN VISARGA +11C3F ; N # Mn BHAIKSUKI SIGN VIRAMA +11C40 ; N # Lo BHAIKSUKI SIGN AVAGRAHA +11C41..11C45 ; N # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C50..11C59 ; N # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11C5A..11C6C ; N # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11C70..11C71 ; N # Po [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD +11C72..11C8F ; N # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A +11C92..11CA7 ; N # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CA9 ; N # Mc MARCHEN SUBJOINED LETTER YA +11CAA..11CB0 ; N # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB1 ; N # Mc MARCHEN VOWEL SIGN I +11CB2..11CB3 ; N # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB4 ; N # Mc MARCHEN VOWEL SIGN O +11CB5..11CB6 ; N # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D00..11D06 ; N # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; N # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; N # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D31..11D36 ; N # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; N # Mn MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; N # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; N # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D46 ; N # Lo MASARAM GONDI REPHA +11D47 ; N # Mn MASARAM GONDI RA-KARA +11D50..11D59 ; N # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11D60..11D65 ; N # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; N # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; N # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D8A..11D8E ; N # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; N # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D94 ; N # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D95 ; N # Mn GUNJALA GONDI SIGN ANUSVARA +11D96 ; N # Mc GUNJALA GONDI SIGN VISARGA +11D97 ; N # Mn GUNJALA GONDI VIRAMA +11D98 ; N # Lo GUNJALA GONDI OM +11DA0..11DA9 ; N # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11EE0..11EF2 ; N # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA +11EF3..11EF4 ; N # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11EF5..11EF6 ; N # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11EF7..11EF8 ; N # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F00..11F01 ; N # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F02 ; N # Lo KAWI SIGN REPHA +11F03 ; N # Mc KAWI SIGN VISARGA +11F04..11F10 ; N # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; N # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11F34..11F35 ; N # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A ; N # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F ; N # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40 ; N # Mn KAWI VOWEL SIGN EU +11F41 ; N # Mc KAWI SIGN KILLER +11F42 ; N # Mn KAWI CONJOINER +11F43..11F4F ; N # Po [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL +11F50..11F59 ; N # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +11FB0 ; N # Lo LISU LETTER YHA +11FC0..11FD4 ; N # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +11FD5..11FDC ; N # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FDD..11FE0 ; N # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +11FE1..11FF1 ; N # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +11FFF ; N # Po TAMIL PUNCTUATION END OF TEXT +12000..12399 ; N # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12400..1246E ; N # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM +12470..12474 ; N # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12480..12543 ; N # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; N # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +12FF1..12FF2 ; N # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +13000..1342F ; N # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13430..1343F ; N # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +13440 ; N # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13441..13446 ; N # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +13447..13455 ; N # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +14400..14646 ; N # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; N # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; N # Lo [31] MRO LETTER TA..MRO LETTER TEK +16A60..16A69 ; N # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE +16A6E..16A6F ; N # Po [2] MRO DANDA..MRO DOUBLE DANDA +16A70..16ABE ; N # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AC0..16AC9 ; N # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16AD0..16AED ; N # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16AF0..16AF4 ; N # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16AF5 ; N # Po BASSA VAH FULL STOP +16B00..16B2F ; N # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B30..16B36 ; N # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16B37..16B3B ; N # Po [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM +16B3C..16B3F ; N # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B40..16B43 ; N # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16B44 ; N # Po PAHAWH HMONG SIGN XAUS +16B45 ; N # So PAHAWH HMONG SIGN CIM TSOV ROG +16B50..16B59 ; N # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +16B5B..16B61 ; N # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16B63..16B77 ; N # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; N # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16E40..16E7F ; N # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16E80..16E96 ; N # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +16E97..16E9A ; N # Po [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH +16F00..16F4A ; N # Lo [75] MIAO LETTER PA..MIAO LETTER RTE +16F4F ; N # Mn MIAO SIGN CONSONANT MODIFIER BAR +16F50 ; N # Lo MIAO LETTER NASALIZATION +16F51..16F87 ; N # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16F8F..16F92 ; N # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW +16F93..16F9F ; N # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; W # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE2 ; W # Po OLD CHINESE HOOK MARK +16FE3 ; W # Lm OLD CHINESE ITERATION MARK +16FE4 ; W # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1 ; W # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +17000..187F7 ; W # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18AFF ; W # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 +18B00..18CD5 ; W # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; W # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1AFF0..1AFF3 ; W # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; W # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; W # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1B000..1B0FF ; W # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 +1B100..1B122 ; W # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU +1B132 ; W # Lo HIRAGANA LETTER SMALL KO +1B150..1B152 ; W # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; W # Lo KATAKANA LETTER SMALL KO +1B164..1B167 ; W # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; W # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; N # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; N # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; N # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; N # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1BC9C ; N # So DUPLOYAN SIGN O WITH CROSS +1BC9D..1BC9E ; N # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1BC9F ; N # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1BCA0..1BCA3 ; N # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1CF00..1CF2D ; N # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; N # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1CF50..1CFC3 ; N # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; N # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; N # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; N # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D165..1D166 ; N # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D167..1D169 ; N # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D16A..1D16C ; N # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D16D..1D172 ; N # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 +1D173..1D17A ; N # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +1D17B..1D182 ; N # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D183..1D184 ; N # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D185..1D18B ; N # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D18C..1D1A9 ; N # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AA..1D1AD ; N # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D1AE..1D1EA ; N # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; N # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D242..1D244 ; N # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1D245 ; N # So GREEK MUSICAL LEIMMA +1D2C0..1D2D3 ; N # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; N # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D300..1D356 ; N # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D360..1D378 ; N # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1D400..1D454 ; N # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D456..1D49C ; N # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; N # Lu [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; N # Lu MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; N # Lu [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; N # Lu [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B9 ; N # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BB ; N # Ll MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; N # Ll [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D505 ; N # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; N # Lu [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; N # Lu [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; N # Lu [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51E..1D539 ; N # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; N # Lu [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; N # Lu [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; N # Lu MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; N # Lu [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D552..1D6A5 ; N # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A8..1D6C0 ; N # Lu [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6C1 ; N # Sm MATHEMATICAL BOLD NABLA +1D6C2..1D6DA ; N # Ll [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DB ; N # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6DC..1D6FA ; N # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA +1D6FB ; N # Sm MATHEMATICAL ITALIC NABLA +1D6FC..1D714 ; N # Ll [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D715 ; N # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D716..1D734 ; N # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D735 ; N # Sm MATHEMATICAL BOLD ITALIC NABLA +1D736..1D74E ; N # Ll [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D74F ; N # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D750..1D76E ; N # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D76F ; N # Sm MATHEMATICAL SANS-SERIF BOLD NABLA +1D770..1D788 ; N # Ll [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D789 ; N # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D78A..1D7A8 ; N # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7A9 ; N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7AA..1D7C2 ; N # Ll [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C3 ; N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1D7C4..1D7CB ; N # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA +1D7CE..1D7FF ; N # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1D800..1D9FF ; N # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA00..1DA36 ; N # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA37..1DA3A ; N # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA3B..1DA6C ; N # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA6D..1DA74 ; N # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA75 ; N # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA76..1DA83 ; N # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA84 ; N # Mn SIGNWRITING LOCATION HEAD NECK +1DA85..1DA86 ; N # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1DA87..1DA8B ; N # Po [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS +1DA9B..1DA9F ; N # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; N # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1DF00..1DF09 ; N # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0A ; N # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1DF0B..1DF1E ; N # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; N # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E000..1E006 ; N # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; N # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; N # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; N # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; N # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E030..1E06D ; N # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E08F ; N # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E100..1E12C ; N # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E130..1E136 ; N # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E137..1E13D ; N # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E140..1E149 ; N # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E14E ; N # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E14F ; N # So NYIAKENG PUACHUE HMONG CIRCLED CA +1E290..1E2AD ; N # Lo [30] TOTO LETTER PA..TOTO LETTER A +1E2AE ; N # Mn TOTO SIGN RISING TONE +1E2C0..1E2EB ; N # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E2EC..1E2EF ; N # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E2F0..1E2F9 ; N # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E2FF ; N # Sc WANCHO NGUN SIGN +1E4D0..1E4EA ; N # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB ; N # Lm NAG MUNDARI SIGN OJOD +1E4EC..1E4EF ; N # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E4F0..1E4F9 ; N # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E7E0..1E7E6 ; N # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; N # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; N # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; N # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; N # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1E8C7..1E8CF ; N # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1E8D0..1E8D6 ; N # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E900..1E943 ; N # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA +1E944..1E94A ; N # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +1E94B ; N # Lm ADLAM NASALIZATION MARK +1E950..1E959 ; N # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1E95E..1E95F ; N # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK +1EC71..1ECAB ; N # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAC ; N # So INDIC SIYAQ PLACEHOLDER +1ECAD..1ECAF ; N # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB0 ; N # Sc INDIC SIYAQ RUPEE MARK +1ECB1..1ECB4 ; N # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; N # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2E ; N # So OTTOMAN SIYAQ MARRATAN +1ED2F..1ED3D ; N # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1EE00..1EE03 ; N # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; N # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; N # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; N # Lo ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; N # Lo ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; N # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; N # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; N # Lo ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; N # Lo ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; N # Lo ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; N # Lo ARABIC MATHEMATICAL TAILED HAH +1EE49 ; N # Lo ARABIC MATHEMATICAL TAILED YEH +1EE4B ; N # Lo ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; N # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; N # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; N # Lo ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; N # Lo ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; N # Lo ARABIC MATHEMATICAL TAILED DAD +1EE5B ; N # Lo ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; N # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; N # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; N # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; N # Lo ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; N # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; N # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; N # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; N # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; N # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; N # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; N # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1EEF0..1EEF1 ; N # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F000..1F003 ; N # So [4] MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; W # So MAHJONG TILE RED DRAGON +1F005..1F02B ; N # So [39] MAHJONG TILE GREEN DRAGON..MAHJONG TILE BACK +1F030..1F093 ; N # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F0A0..1F0AE ; N # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0B1..1F0BF ; N # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C1..1F0CE ; N # So [14] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD KING OF DIAMONDS +1F0CF ; W # So PLAYING CARD BLACK JOKER +1F0D1..1F0F5 ; N # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F100..1F10A ; A # No [11] DIGIT ZERO FULL STOP..DIGIT NINE COMMA +1F10B..1F10C ; N # No [2] DINGBAT CIRCLED SANS-SERIF DIGIT ZERO..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO +1F10D..1F10F ; N # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F110..1F12D ; A # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD +1F12E..1F12F ; N # So [2] CIRCLED WZ..COPYLEFT SYMBOL +1F130..1F169 ; A # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z +1F16A..1F16F ; N # So [6] RAISED MC SIGN..CIRCLED HUMAN FIGURE +1F170..1F18D ; A # So [30] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED SA +1F18E ; W # So NEGATIVE SQUARED AB +1F18F..1F190 ; A # So [2] NEGATIVE SQUARED WC..SQUARE DJ +1F191..1F19A ; W # So [10] SQUARED CL..SQUARED VS +1F19B..1F1AC ; A # So [18] SQUARED THREE D..SQUARED VOD +1F1AD ; N # So MASK WORK SYMBOL +1F1E6..1F1FF ; N # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z +1F200..1F202 ; W # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA +1F210..1F23B ; W # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F240..1F248 ; W # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F250..1F251 ; W # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F260..1F265 ; W # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F300..1F320 ; W # So [33] CYCLONE..SHOOTING STAR +1F321..1F32C ; N # So [12] THERMOMETER..WIND BLOWING FACE +1F32D..1F335 ; W # So [9] HOT DOG..CACTUS +1F336 ; N # So HOT PEPPER +1F337..1F37C ; W # So [70] TULIP..BABY BOTTLE +1F37D ; N # So FORK AND KNIFE WITH PLATE +1F37E..1F393 ; W # So [22] BOTTLE WITH POPPING CORK..GRADUATION CAP +1F394..1F39F ; N # So [12] HEART WITH TIP ON THE LEFT..ADMISSION TICKETS +1F3A0..1F3CA ; W # So [43] CAROUSEL HORSE..SWIMMER +1F3CB..1F3CE ; N # So [4] WEIGHT LIFTER..RACING CAR +1F3CF..1F3D3 ; W # So [5] CRICKET BAT AND BALL..TABLE TENNIS PADDLE AND BALL +1F3D4..1F3DF ; N # So [12] SNOW CAPPED MOUNTAIN..STADIUM +1F3E0..1F3F0 ; W # So [17] HOUSE BUILDING..EUROPEAN CASTLE +1F3F1..1F3F3 ; N # So [3] WHITE PENNANT..WAVING WHITE FLAG +1F3F4 ; W # So WAVING BLACK FLAG +1F3F5..1F3F7 ; N # So [3] ROSETTE..LABEL +1F3F8..1F3FA ; W # So [3] BADMINTON RACQUET AND SHUTTLECOCK..AMPHORA +1F3FB..1F3FF ; W # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 +1F400..1F43E ; W # So [63] RAT..PAW PRINTS +1F43F ; N # So CHIPMUNK +1F440 ; W # So EYES +1F441 ; N # So EYE +1F442..1F4FC ; W # So [187] EAR..VIDEOCASSETTE +1F4FD..1F4FE ; N # So [2] FILM PROJECTOR..PORTABLE STEREO +1F4FF..1F53D ; W # So [63] PRAYER BEADS..DOWN-POINTING SMALL RED TRIANGLE +1F53E..1F54A ; N # So [13] LOWER RIGHT SHADOWED WHITE CIRCLE..DOVE OF PEACE +1F54B..1F54E ; W # So [4] KAABA..MENORAH WITH NINE BRANCHES +1F54F ; N # So BOWL OF HYGIEIA +1F550..1F567 ; W # So [24] CLOCK FACE ONE OCLOCK..CLOCK FACE TWELVE-THIRTY +1F568..1F579 ; N # So [18] RIGHT SPEAKER..JOYSTICK +1F57A ; W # So MAN DANCING +1F57B..1F594 ; N # So [26] LEFT HAND TELEPHONE RECEIVER..REVERSED VICTORY HAND +1F595..1F596 ; W # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS +1F597..1F5A3 ; N # So [13] WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; W # So BLACK HEART +1F5A5..1F5FA ; N # So [86] DESKTOP COMPUTER..WORLD MAP +1F5FB..1F5FF ; W # So [5] MOUNT FUJI..MOYAI +1F600..1F64F ; W # So [80] GRINNING FACE..PERSON WITH FOLDED HANDS +1F650..1F67F ; N # So [48] NORTH WEST POINTING LEAF..REVERSE CHECKER BOARD +1F680..1F6C5 ; W # So [70] ROCKET..LEFT LUGGAGE +1F6C6..1F6CB ; N # So [6] TRIANGLE WITH ROUNDED CORNERS..COUCH AND LAMP +1F6CC ; W # So SLEEPING ACCOMMODATION +1F6CD..1F6CF ; N # So [3] SHOPPING BAGS..BED +1F6D0..1F6D2 ; W # So [3] PLACE OF WORSHIP..SHOPPING TROLLEY +1F6D3..1F6D4 ; N # So [2] STUPA..PAGODA +1F6D5..1F6D7 ; W # So [3] HINDU TEMPLE..ELEVATOR +1F6DC..1F6DF ; W # So [4] WIRELESS..RING BUOY +1F6E0..1F6EA ; N # So [11] HAMMER AND WRENCH..NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; W # So [2] AIRPLANE DEPARTURE..AIRPLANE ARRIVING +1F6F0..1F6F3 ; N # So [4] SATELLITE..PASSENGER SHIP +1F6F4..1F6FC ; W # So [9] SCOOTER..ROLLER SKATE +1F700..1F776 ; N # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE +1F77B..1F77F ; N # So [5] HAUMEA..ORCUS +1F780..1F7D9 ; N # So [90] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NINE POINTED WHITE STAR +1F7E0..1F7EB ; W # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7F0 ; W # So HEAVY EQUALS SIGN +1F800..1F80B ; N # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F810..1F847 ; N # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F850..1F859 ; N # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F860..1F887 ; N # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F890..1F8AD ; N # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8B0..1F8B1 ; N # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F900..1F90B ; N # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT +1F90C..1F93A ; W # So [47] PINCHED FINGERS..FENCER +1F93B ; N # So MODERN PENTATHLON +1F93C..1F945 ; W # So [10] WRESTLERS..GOAL NET +1F946 ; N # So RIFLE +1F947..1F9FF ; W # So [185] FIRST PLACE MEDAL..NAZAR AMULET +1FA00..1FA53 ; N # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP +1FA60..1FA6D ; N # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA70..1FA7C ; W # So [13] BALLET SHOES..CRUTCH +1FA80..1FA88 ; W # So [9] YO-YO..FLUTE +1FA90..1FABD ; W # So [46] RINGED PLANET..WING +1FABF..1FAC5 ; W # So [7] GOOSE..PERSON WITH CROWN +1FACE..1FADB ; W # So [14] MOOSE..PEA POD +1FAE0..1FAE8 ; W # So [9] MELTING FACE..SHAKING FACE +1FAF0..1FAF8 ; W # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FB00..1FB92 ; N # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; N # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON +1FBF0..1FBF9 ; N # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +20000..2A6DF ; W # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A6E0..2A6FF ; W # Cn [32] .. +2A700..2B739 ; W # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F ; W # Cn [6] .. +2B740..2B81D ; W # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B81E..2B81F ; W # Cn [2] .. +2B820..2CEA1 ; W # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEA2..2CEAF ; W # Cn [14] .. +2CEB0..2EBE0 ; W # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBE1..2EBEF ; W # Cn [15] .. +2EBF0..2EE5D ; W # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2EE5E..2F7FF ; W # Cn [2466] .. +2F800..2FA1D ; W # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +2FA1E..2FA1F ; W # Cn [2] .. +2FA20..2FFFD ; W # Cn [1502] .. +30000..3134A ; W # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +3134B..3134F ; W # Cn [5] .. +31350..323AF ; W # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..3FFFD ; W # Cn [56398] .. +E0001 ; N # Cf LANGUAGE TAG +E0020..E007F ; N # Cf [96] TAG SPACE..CANCEL TAG +E0100..E01EF ; A # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 +F0000..FFFFD ; A # Co [65534] .. +100000..10FFFD ; A # Co [65534] .. + +# EOF diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreak.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreak.txt new file mode 100644 index 000000000..b6bc67939 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreak.txt @@ -0,0 +1,3608 @@ +# LineBreak-15.1.0.txt +# Date: 2023-07-28, 13:19:22 GMT [KW] +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# Line_Break Property +# +# This file is a normative contributory data file in the +# Unicode Character Database. +# +# The format is two fields separated by a semicolon. +# Field 0: Unicode code point value or range of code point values +# Field 1: Line_Break property, consisting of one of the following values: +# Non-tailorable: +# "BK", "CM", "CR", "GL", "LF", "NL", "SP", "WJ", "ZW", "ZWJ" +# Tailorable: +# "AI", "AK", "AL", "AP", "AS", "B2", "BA", "BB", "CB", "CJ", +# "CL", "CP", "EB", "EM", "EX", "H2", "H3", "HL", "HY", "ID", +# "IN", "IS", "JL", "JT", "JV", "NS", "NU", "OP", "PO", "PR", +# "QU", "RI", "SA", "SG", "SY", "VF", "VI", "XX" +# - All code points, assigned and unassigned, that are not listed +# explicitly are given the value "XX". +# - The unassigned code points in the following blocks default to "ID": +# CJK Unified Ideographs Extension A: U+3400..U+4DBF +# CJK Unified Ideographs: U+4E00..U+9FFF +# CJK Compatibility Ideographs: U+F900..U+FAFF +# - All undesignated code points in Planes 2 and 3, whether inside or +# outside of allocated blocks, default to "ID": +# Plane 2: U+20000..U+2FFFD +# Plane 3: U+30000..U+3FFFD +# - All unassigned code points in the following Plane 1 ranges, whether +# inside or outside of allocated blocks, also default to "ID": +# Plane 1 range: U+1F000..U+1FAFF +# Plane 1 range: U+1FC00..U+1FFFD +# - The unassigned code points in the following block default to "PR": +# Currency Symbols: U+20A0..U+20CF +# +# Character ranges are specified as for other property files in the +# Unicode Character Database. +# +# The comments following the number sign "#" list the General_Category +# property value or the L& alias of the derived value LC, the Unicode +# character name or names, and, in lines with ranges of code points, +# the code point count in square brackets. +# +# For more information, see UAX #14: Unicode Line Breaking Algorithm, +# at https://www.unicode.org/reports/tr14/ +# +# @missing: 0000..10FFFF; XX +0000..0008 ; CM # Cc [9] .. +0009 ; BA # Cc +000A ; LF # Cc +000B..000C ; BK # Cc [2] .. +000D ; CR # Cc +000E..001F ; CM # Cc [18] .. +0020 ; SP # Zs SPACE +0021 ; EX # Po EXCLAMATION MARK +0022 ; QU # Po QUOTATION MARK +0023 ; AL # Po NUMBER SIGN +0024 ; PR # Sc DOLLAR SIGN +0025 ; PO # Po PERCENT SIGN +0026 ; AL # Po AMPERSAND +0027 ; QU # Po APOSTROPHE +0028 ; OP # Ps LEFT PARENTHESIS +0029 ; CP # Pe RIGHT PARENTHESIS +002A ; AL # Po ASTERISK +002B ; PR # Sm PLUS SIGN +002C ; IS # Po COMMA +002D ; HY # Pd HYPHEN-MINUS +002E ; IS # Po FULL STOP +002F ; SY # Po SOLIDUS +0030..0039 ; NU # Nd [10] DIGIT ZERO..DIGIT NINE +003A..003B ; IS # Po [2] COLON..SEMICOLON +003C..003E ; AL # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN +003F ; EX # Po QUESTION MARK +0040 ; AL # Po COMMERCIAL AT +0041..005A ; AL # Lu [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +005B ; OP # Ps LEFT SQUARE BRACKET +005C ; PR # Po REVERSE SOLIDUS +005D ; CP # Pe RIGHT SQUARE BRACKET +005E ; AL # Sk CIRCUMFLEX ACCENT +005F ; AL # Pc LOW LINE +0060 ; AL # Sk GRAVE ACCENT +0061..007A ; AL # Ll [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B ; OP # Ps LEFT CURLY BRACKET +007C ; BA # Sm VERTICAL LINE +007D ; CL # Pe RIGHT CURLY BRACKET +007E ; AL # Sm TILDE +007F ; CM # Cc +0080..0084 ; CM # Cc [5] .. +0085 ; NL # Cc +0086..009F ; CM # Cc [26] .. +00A0 ; GL # Zs NO-BREAK SPACE +00A1 ; OP # Po INVERTED EXCLAMATION MARK +00A2 ; PO # Sc CENT SIGN +00A3..00A5 ; PR # Sc [3] POUND SIGN..YEN SIGN +00A6 ; AL # So BROKEN BAR +00A7 ; AI # Po SECTION SIGN +00A8 ; AI # Sk DIAERESIS +00A9 ; AL # So COPYRIGHT SIGN +00AA ; AI # Lo FEMININE ORDINAL INDICATOR +00AB ; QU # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00AC ; AL # Sm NOT SIGN +00AD ; BA # Cf SOFT HYPHEN +00AE ; AL # So REGISTERED SIGN +00AF ; AL # Sk MACRON +00B0 ; PO # So DEGREE SIGN +00B1 ; PR # Sm PLUS-MINUS SIGN +00B2..00B3 ; AI # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B4 ; BB # Sk ACUTE ACCENT +00B5 ; AL # Ll MICRO SIGN +00B6..00B7 ; AI # Po [2] PILCROW SIGN..MIDDLE DOT +00B8 ; AI # Sk CEDILLA +00B9 ; AI # No SUPERSCRIPT ONE +00BA ; AI # Lo MASCULINE ORDINAL INDICATOR +00BB ; QU # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +00BC..00BE ; AI # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +00BF ; OP # Po INVERTED QUESTION MARK +00C0..00D6 ; AL # Lu [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D7 ; AI # Sm MULTIPLICATION SIGN +00D8..00F6 ; AL # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS +00F7 ; AI # Sm DIVISION SIGN +00F8..00FF ; AL # Ll [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS +0100..017F ; AL # L& [128] LATIN CAPITAL LETTER A WITH MACRON..LATIN SMALL LETTER LONG S +0180..01BA ; AL # L& [59] LATIN SMALL LETTER B WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL +01BB ; AL # Lo LATIN LETTER TWO WITH STROKE +01BC..01BF ; AL # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN +01C0..01C3 ; AL # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +01C4..024F ; AL # L& [140] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER Y WITH STROKE +0250..0293 ; AL # Ll [68] LATIN SMALL LETTER TURNED A..LATIN SMALL LETTER EZH WITH CURL +0294 ; AL # Lo LATIN LETTER GLOTTAL STOP +0295..02AF ; AL # Ll [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0..02C1 ; AL # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2..02C5 ; AL # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02C6 ; AL # Lm MODIFIER LETTER CIRCUMFLEX ACCENT +02C7 ; AI # Lm CARON +02C8 ; BB # Lm MODIFIER LETTER VERTICAL LINE +02C9..02CB ; AI # Lm [3] MODIFIER LETTER MACRON..MODIFIER LETTER GRAVE ACCENT +02CC ; BB # Lm MODIFIER LETTER LOW VERTICAL LINE +02CD ; AI # Lm MODIFIER LETTER LOW MACRON +02CE..02CF ; AL # Lm [2] MODIFIER LETTER LOW GRAVE ACCENT..MODIFIER LETTER LOW ACUTE ACCENT +02D0 ; AI # Lm MODIFIER LETTER TRIANGULAR COLON +02D1 ; AL # Lm MODIFIER LETTER HALF TRIANGULAR COLON +02D2..02D7 ; AL # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN +02D8..02DB ; AI # Sk [4] BREVE..OGONEK +02DC ; AL # Sk SMALL TILDE +02DD ; AI # Sk DOUBLE ACUTE ACCENT +02DE ; AL # Sk MODIFIER LETTER RHOTIC HOOK +02DF ; BB # Sk MODIFIER LETTER CROSS ACCENT +02E0..02E4 ; AL # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5..02EB ; AL # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC ; AL # Lm MODIFIER LETTER VOICING +02ED ; AL # Sk MODIFIER LETTER UNASPIRATED +02EE ; AL # Lm MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; AL # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0300..034E ; CM # Mn [79] COMBINING GRAVE ACCENT..COMBINING UPWARDS ARROW BELOW +034F ; GL # Mn COMBINING GRAPHEME JOINER +0350..035B ; CM # Mn [12] COMBINING RIGHT ARROWHEAD ABOVE..COMBINING ZIGZAG ABOVE +035C..0362 ; GL # Mn [7] COMBINING DOUBLE BREVE BELOW..COMBINING DOUBLE RIGHTWARDS ARROW BELOW +0363..036F ; CM # Mn [13] COMBINING LATIN SMALL LETTER A..COMBINING LATIN SMALL LETTER X +0370..0373 ; AL # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; AL # Lm GREEK NUMERAL SIGN +0375 ; AL # Sk GREEK LOWER NUMERAL SIGN +0376..0377 ; AL # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037A ; AL # Lm GREEK YPOGEGRAMMENI +037B..037D ; AL # Ll [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037E ; IS # Po GREEK QUESTION MARK +037F ; AL # Lu GREEK CAPITAL LETTER YOT +0384..0385 ; AL # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0386 ; AL # Lu GREEK CAPITAL LETTER ALPHA WITH TONOS +0387 ; AL # Po GREEK ANO TELEIA +0388..038A ; AL # Lu [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; AL # Lu GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..03A1 ; AL # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO +03A3..03F5 ; AL # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL +03F6 ; AL # Sm GREEK REVERSED LUNATE EPSILON SYMBOL +03F7..03FF ; AL # L& [9] GREEK CAPITAL LETTER SHO..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL +0400..0481 ; AL # L& [130] CYRILLIC CAPITAL LETTER IE WITH GRAVE..CYRILLIC SMALL LETTER KOPPA +0482 ; AL # So CYRILLIC THOUSANDS SIGN +0483..0487 ; CM # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488..0489 ; CM # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +048A..04FF ; AL # L& [118] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER HA WITH STROKE +0500..052F ; AL # L& [48] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER EL WITH DESCENDER +0531..0556 ; AL # Lu [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0559 ; AL # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055F ; AL # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0560..0588 ; AL # Ll [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +0589 ; IS # Po ARMENIAN FULL STOP +058A ; BA # Pd ARMENIAN HYPHEN +058D..058E ; AL # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +058F ; PR # Sc ARMENIAN DRAM SIGN +0591..05BD ; CM # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE ; BA # Pd HEBREW PUNCTUATION MAQAF +05BF ; CM # Mn HEBREW POINT RAFE +05C0 ; AL # Po HEBREW PUNCTUATION PASEQ +05C1..05C2 ; CM # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3 ; AL # Po HEBREW PUNCTUATION SOF PASUQ +05C4..05C5 ; CM # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6 ; EX # Po HEBREW PUNCTUATION NUN HAFUKHA +05C7 ; CM # Mn HEBREW POINT QAMATS QATAN +05D0..05EA ; HL # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; HL # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +05F3..05F4 ; AL # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0600..0605 ; NU # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +0606..0608 ; AL # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +0609..060A ; PO # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060B ; PO # Sc AFGHANI SIGN +060C..060D ; IS # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR +060E..060F ; AL # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +0610..061A ; CM # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +061B ; EX # Po ARABIC SEMICOLON +061C ; CM # Cf ARABIC LETTER MARK +061D..061F ; EX # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +0620..063F ; AL # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640 ; AL # Lm ARABIC TATWEEL +0641..064A ; AL # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH +064B..065F ; CM # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0660..0669 ; NU # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066A ; PO # Po ARABIC PERCENT SIGN +066B..066C ; NU # Po [2] ARABIC DECIMAL SEPARATOR..ARABIC THOUSANDS SEPARATOR +066D ; AL # Po ARABIC FIVE POINTED STAR +066E..066F ; AL # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0670 ; CM # Mn ARABIC LETTER SUPERSCRIPT ALEF +0671..06D3 ; AL # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D4 ; EX # Po ARABIC FULL STOP +06D5 ; AL # Lo ARABIC LETTER AE +06D6..06DC ; CM # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DD ; NU # Cf ARABIC END OF AYAH +06DE ; AL # So ARABIC START OF RUB EL HIZB +06DF..06E4 ; CM # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E5..06E6 ; AL # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH +06E7..06E8 ; CM # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06E9 ; AL # So ARABIC PLACE OF SAJDAH +06EA..06ED ; CM # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +06EE..06EF ; AL # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06F0..06F9 ; NU # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +06FA..06FC ; AL # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FD..06FE ; AL # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +06FF ; AL # Lo ARABIC LETTER HEH WITH INVERTED V +0700..070D ; AL # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +070F ; AL # Cf SYRIAC ABBREVIATION MARK +0710 ; AL # Lo SYRIAC LETTER ALAPH +0711 ; CM # Mn SYRIAC LETTER SUPERSCRIPT ALAPH +0712..072F ; AL # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +0730..074A ; CM # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +074D..074F ; AL # Lo [3] SYRIAC LETTER SOGDIAN ZHAIN..SYRIAC LETTER SOGDIAN FE +0750..077F ; AL # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE +0780..07A5 ; AL # Lo [38] THAANA LETTER HAA..THAANA LETTER WAAVU +07A6..07B0 ; CM # Mn [11] THAANA ABAFILI..THAANA SUKUN +07B1 ; AL # Lo THAANA LETTER NAA +07C0..07C9 ; NU # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE +07CA..07EA ; AL # Lo [33] NKO LETTER A..NKO LETTER JONA RA +07EB..07F3 ; CM # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07F4..07F5 ; AL # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07F6 ; AL # So NKO SYMBOL OO DENNEN +07F7 ; AL # Po NKO SYMBOL GBAKURUNEN +07F8 ; IS # Po NKO COMMA +07F9 ; EX # Po NKO EXCLAMATION MARK +07FA ; AL # Lm NKO LAJANYALAN +07FD ; CM # Mn NKO DANTAYALAN +07FE..07FF ; PR # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN +0800..0815 ; AL # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0816..0819 ; CM # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081A ; AL # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT +081B..0823 ; CM # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0824 ; AL # Lm SAMARITAN MODIFIER LETTER SHORT A +0825..0827 ; CM # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0828 ; AL # Lm SAMARITAN MODIFIER LETTER I +0829..082D ; CM # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0830..083E ; AL # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +0840..0858 ; AL # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0859..085B ; CM # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +085E ; AL # Po MANDAIC PUNCTUATION +0860..086A ; AL # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; AL # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0888 ; AL # Sk ARABIC RAISED ROUND DOT +0889..088E ; AL # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0890..0891 ; NU # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +0898..089F ; CM # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08A0..08C8 ; AL # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +08C9 ; AL # Lm ARABIC SMALL FARSI YEH +08CA..08E1 ; CM # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E2 ; NU # Cf ARABIC DISPUTED END OF AYAH +08E3..08FF ; CM # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA +0900..0902 ; CM # Mn [3] DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI SIGN ANUSVARA +0903 ; CM # Mc DEVANAGARI SIGN VISARGA +0904..0939 ; AL # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093A ; CM # Mn DEVANAGARI VOWEL SIGN OE +093B ; CM # Mc DEVANAGARI VOWEL SIGN OOE +093C ; CM # Mn DEVANAGARI SIGN NUKTA +093D ; AL # Lo DEVANAGARI SIGN AVAGRAHA +093E..0940 ; CM # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0941..0948 ; CM # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +0949..094C ; CM # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094D ; CM # Mn DEVANAGARI SIGN VIRAMA +094E..094F ; CM # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0950 ; AL # Lo DEVANAGARI OM +0951..0957 ; CM # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0958..0961 ; AL # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0962..0963 ; CM # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0964..0965 ; BA # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966..096F ; NU # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970 ; AL # Po DEVANAGARI ABBREVIATION SIGN +0971 ; AL # Lm DEVANAGARI SIGN HIGH SPACING DOT +0972..097F ; AL # Lo [14] DEVANAGARI LETTER CANDRA A..DEVANAGARI LETTER BBA +0980 ; AL # Lo BENGALI ANJI +0981 ; CM # Mn BENGALI SIGN CANDRABINDU +0982..0983 ; CM # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +0985..098C ; AL # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; AL # Lo [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; AL # Lo [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; AL # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; AL # Lo BENGALI LETTER LA +09B6..09B9 ; AL # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BC ; CM # Mn BENGALI SIGN NUKTA +09BD ; AL # Lo BENGALI SIGN AVAGRAHA +09BE..09C0 ; CM # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C1..09C4 ; CM # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09C7..09C8 ; CM # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; CM # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09CD ; CM # Mn BENGALI SIGN VIRAMA +09CE ; AL # Lo BENGALI LETTER KHANDA TA +09D7 ; CM # Mc BENGALI AU LENGTH MARK +09DC..09DD ; AL # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; AL # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09E2..09E3 ; CM # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09E6..09EF ; NU # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +09F0..09F1 ; AL # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09F2..09F3 ; PO # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09F4..09F8 ; AL # No [5] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY NUMERATOR ONE LESS THAN THE DENOMINATOR +09F9 ; PO # No BENGALI CURRENCY DENOMINATOR SIXTEEN +09FA ; AL # So BENGALI ISSHAR +09FB ; PR # Sc BENGALI GANDA MARK +09FC ; AL # Lo BENGALI LETTER VEDIC ANUSVARA +09FD ; AL # Po BENGALI ABBREVIATION SIGN +09FE ; CM # Mn BENGALI SANDHI MARK +0A01..0A02 ; CM # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A03 ; CM # Mc GURMUKHI SIGN VISARGA +0A05..0A0A ; AL # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; AL # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; AL # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; AL # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; AL # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; AL # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; AL # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3C ; CM # Mn GURMUKHI SIGN NUKTA +0A3E..0A40 ; CM # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A41..0A42 ; CM # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; CM # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; CM # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; CM # Mn GURMUKHI SIGN UDAAT +0A59..0A5C ; AL # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; AL # Lo GURMUKHI LETTER FA +0A66..0A6F ; NU # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0A70..0A71 ; CM # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A72..0A74 ; AL # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A75 ; CM # Mn GURMUKHI SIGN YAKASH +0A76 ; AL # Po GURMUKHI ABBREVIATION SIGN +0A81..0A82 ; CM # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0A83 ; CM # Mc GUJARATI SIGN VISARGA +0A85..0A8D ; AL # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; AL # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; AL # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; AL # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; AL # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; AL # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABC ; CM # Mn GUJARATI SIGN NUKTA +0ABD ; AL # Lo GUJARATI SIGN AVAGRAHA +0ABE..0AC0 ; CM # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC1..0AC5 ; CM # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; CM # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0AC9 ; CM # Mc GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; CM # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0ACD ; CM # Mn GUJARATI SIGN VIRAMA +0AD0 ; AL # Lo GUJARATI OM +0AE0..0AE1 ; AL # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AE2..0AE3 ; CM # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AE6..0AEF ; NU # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0 ; AL # Po GUJARATI ABBREVIATION SIGN +0AF1 ; PR # Sc GUJARATI RUPEE SIGN +0AF9 ; AL # Lo GUJARATI LETTER ZHA +0AFA..0AFF ; CM # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; CM # Mn ORIYA SIGN CANDRABINDU +0B02..0B03 ; CM # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B05..0B0C ; AL # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; AL # Lo [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; AL # Lo [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; AL # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; AL # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; AL # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3C ; CM # Mn ORIYA SIGN NUKTA +0B3D ; AL # Lo ORIYA SIGN AVAGRAHA +0B3E ; CM # Mc ORIYA VOWEL SIGN AA +0B3F ; CM # Mn ORIYA VOWEL SIGN I +0B40 ; CM # Mc ORIYA VOWEL SIGN II +0B41..0B44 ; CM # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B47..0B48 ; CM # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; CM # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B4D ; CM # Mn ORIYA SIGN VIRAMA +0B55..0B56 ; CM # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B57 ; CM # Mc ORIYA AU LENGTH MARK +0B5C..0B5D ; AL # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; AL # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B62..0B63 ; CM # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B66..0B6F ; NU # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70 ; AL # So ORIYA ISSHAR +0B71 ; AL # Lo ORIYA LETTER WA +0B72..0B77 ; AL # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0B82 ; CM # Mn TAMIL SIGN ANUSVARA +0B83 ; AL # Lo TAMIL SIGN VISARGA +0B85..0B8A ; AL # Lo [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; AL # Lo [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; AL # Lo [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; AL # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; AL # Lo TAMIL LETTER JA +0B9E..0B9F ; AL # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; AL # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; AL # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; AL # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA +0BBE..0BBF ; CM # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC0 ; CM # Mn TAMIL VOWEL SIGN II +0BC1..0BC2 ; CM # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; CM # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; CM # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BCD ; CM # Mn TAMIL SIGN VIRAMA +0BD0 ; AL # Lo TAMIL OM +0BD7 ; CM # Mc TAMIL AU LENGTH MARK +0BE6..0BEF ; NU # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0..0BF2 ; AL # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0BF3..0BF8 ; AL # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BF9 ; PR # Sc TAMIL RUPEE SIGN +0BFA ; AL # So TAMIL NUMBER SIGN +0C00 ; CM # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C01..0C03 ; CM # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; CM # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE +0C05..0C0C ; AL # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; AL # Lo [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; AL # Lo [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; AL # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3C ; CM # Mn TELUGU SIGN NUKTA +0C3D ; AL # Lo TELUGU SIGN AVAGRAHA +0C3E..0C40 ; CM # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C41..0C44 ; CM # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C46..0C48 ; CM # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; CM # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; CM # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C58..0C5A ; AL # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; AL # Lo TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; AL # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C62..0C63 ; CM # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C66..0C6F ; NU # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C77 ; BB # Po TELUGU SIGN SIDDHAM +0C78..0C7E ; AL # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0C7F ; AL # So TELUGU SIGN TUUMU +0C80 ; AL # Lo KANNADA SIGN SPACING CANDRABINDU +0C81 ; CM # Mn KANNADA SIGN CANDRABINDU +0C82..0C83 ; CM # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84 ; BB # Po KANNADA SIGN SIDDHAM +0C85..0C8C ; AL # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; AL # Lo [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; AL # Lo [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; AL # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; AL # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBC ; CM # Mn KANNADA SIGN NUKTA +0CBD ; AL # Lo KANNADA SIGN AVAGRAHA +0CBE ; CM # Mc KANNADA VOWEL SIGN AA +0CBF ; CM # Mn KANNADA VOWEL SIGN I +0CC0..0CC4 ; CM # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC6 ; CM # Mn KANNADA VOWEL SIGN E +0CC7..0CC8 ; CM # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; CM # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CCC..0CCD ; CM # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CD5..0CD6 ; CM # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CDD..0CDE ; AL # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; AL # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CE2..0CE3 ; CM # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CE6..0CEF ; NU # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF1..0CF2 ; AL # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3 ; CM # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00..0D01 ; CM # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D02..0D03 ; CM # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D04..0D0C ; AL # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; AL # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; AL # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3B..0D3C ; CM # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3D ; AL # Lo MALAYALAM SIGN AVAGRAHA +0D3E..0D40 ; CM # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D41..0D44 ; CM # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D46..0D48 ; CM # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; CM # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D4D ; CM # Mn MALAYALAM SIGN VIRAMA +0D4E ; AL # Lo MALAYALAM LETTER DOT REPH +0D4F ; AL # So MALAYALAM SIGN PARA +0D54..0D56 ; AL # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D57 ; CM # Mc MALAYALAM AU LENGTH MARK +0D58..0D5E ; AL # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D5F..0D61 ; AL # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D62..0D63 ; CM # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D66..0D6F ; NU # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70..0D78 ; AL # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0D79 ; PO # So MALAYALAM DATE MARK +0D7A..0D7F ; AL # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D81 ; CM # Mn SINHALA SIGN CANDRABINDU +0D82..0D83 ; CM # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0D85..0D96 ; AL # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; AL # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; AL # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; AL # Lo SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; AL # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0DCA ; CM # Mn SINHALA SIGN AL-LAKUNA +0DCF..0DD1 ; CM # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD2..0DD4 ; CM # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; CM # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD8..0DDF ; CM # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DE6..0DEF ; NU # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0DF2..0DF3 ; CM # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0DF4 ; AL # Po SINHALA PUNCTUATION KUNDDALIYA +0E01..0E30 ; SA # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E31 ; SA # Mn THAI CHARACTER MAI HAN-AKAT +0E32..0E33 ; SA # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E34..0E3A ; SA # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E3F ; PR # Sc THAI CURRENCY SYMBOL BAHT +0E40..0E45 ; SA # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E46 ; SA # Lm THAI CHARACTER MAIYAMOK +0E47..0E4E ; SA # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0E4F ; AL # Po THAI CHARACTER FONGMAN +0E50..0E59 ; NU # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE +0E5A..0E5B ; BA # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0E81..0E82 ; SA # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; SA # Lo LAO LETTER KHO TAM +0E86..0E8A ; SA # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; SA # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; SA # Lo LAO LETTER LO LOOT +0EA7..0EB0 ; SA # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB1 ; SA # Mn LAO VOWEL SIGN MAI KAN +0EB2..0EB3 ; SA # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EB4..0EBC ; SA # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EBD ; SA # Lo LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; SA # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC6 ; SA # Lm LAO KO LA +0EC8..0ECE ; SA # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN +0ED0..0ED9 ; NU # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE +0EDC..0EDF ; SA # Lo [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; AL # Lo TIBETAN SYLLABLE OM +0F01..0F03 ; BB # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F04 ; BB # Po TIBETAN MARK INITIAL YIG MGO MDUN MA +0F05 ; AL # Po TIBETAN MARK CLOSING YIG MGO SGAB MA +0F06..0F07 ; BB # Po [2] TIBETAN MARK CARET YIG MGO PHUR SHAD MA..TIBETAN MARK YIG MGO TSHEG SHAD MA +0F08 ; GL # Po TIBETAN MARK SBRUL SHAD +0F09..0F0A ; BB # Po [2] TIBETAN MARK BSKUR YIG MGO..TIBETAN MARK BKA- SHOG YIG MGO +0F0B ; BA # Po TIBETAN MARK INTERSYLLABIC TSHEG +0F0C ; GL # Po TIBETAN MARK DELIMITER TSHEG BSTAR +0F0D..0F11 ; EX # Po [5] TIBETAN MARK SHAD..TIBETAN MARK RIN CHEN SPUNGS SHAD +0F12 ; GL # Po TIBETAN MARK RGYA GRAM SHAD +0F13 ; AL # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F14 ; EX # Po TIBETAN MARK GTER TSHEG +0F15..0F17 ; AL # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F18..0F19 ; CM # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F1A..0F1F ; AL # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F20..0F29 ; NU # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A..0F33 ; AL # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +0F34 ; BA # So TIBETAN MARK BSDUS RTAGS +0F35 ; CM # Mn TIBETAN MARK NGAS BZUNG NYI ZLA +0F36 ; AL # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37 ; CM # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38 ; AL # So TIBETAN MARK CHE MGO +0F39 ; CM # Mn TIBETAN MARK TSA -PHRU +0F3A ; OP # Ps TIBETAN MARK GUG RTAGS GYON +0F3B ; CL # Pe TIBETAN MARK GUG RTAGS GYAS +0F3C ; OP # Ps TIBETAN MARK ANG KHANG GYON +0F3D ; CL # Pe TIBETAN MARK ANG KHANG GYAS +0F3E..0F3F ; CM # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F40..0F47 ; AL # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; AL # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F71..0F7E ; CM # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F7F ; BA # Mc TIBETAN SIGN RNAM BCAD +0F80..0F84 ; CM # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F85 ; BA # Po TIBETAN MARK PALUTA +0F86..0F87 ; CM # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F88..0F8C ; AL # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +0F8D..0F97 ; CM # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; CM # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FBE..0FBF ; BA # So [2] TIBETAN KU RU KHA..TIBETAN KU RU KHA BZHI MIG CAN +0FC0..0FC5 ; AL # So [6] TIBETAN CANTILLATION SIGN HEAVY BEAT..TIBETAN SYMBOL RDO RJE +0FC6 ; CM # Mn TIBETAN SYMBOL PADMA GDAN +0FC7..0FCC ; AL # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; AL # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD0..0FD1 ; BB # Po [2] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK MNYAM YIG GI MGO RGYAN +0FD2 ; BA # Po TIBETAN MARK NYIS TSHEG +0FD3 ; BB # Po TIBETAN MARK INITIAL BRDA RNYING YIG MGO MDUN MA +0FD4 ; AL # Po TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD5..0FD8 ; AL # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +0FD9..0FDA ; GL # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +1000..102A ; SA # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU +102B..102C ; SA # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +102D..1030 ; SA # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1031 ; SA # Mc MYANMAR VOWEL SIGN E +1032..1037 ; SA # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1038 ; SA # Mc MYANMAR SIGN VISARGA +1039..103A ; SA # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103B..103C ; SA # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +103D..103E ; SA # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +103F ; SA # Lo MYANMAR LETTER GREAT SA +1040..1049 ; NU # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +104A..104B ; BA # Po [2] MYANMAR SIGN LITTLE SECTION..MYANMAR SIGN SECTION +104C..104F ; AL # Po [4] MYANMAR SYMBOL LOCATIVE..MYANMAR SYMBOL GENITIVE +1050..1055 ; SA # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +1056..1057 ; SA # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1058..1059 ; SA # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105A..105D ; SA # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +105E..1060 ; SA # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1061 ; SA # Lo MYANMAR LETTER SGAW KAREN SHA +1062..1064 ; SA # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1065..1066 ; SA # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +1067..106D ; SA # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +106E..1070 ; SA # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1071..1074 ; SA # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1075..1081 ; SA # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +1082 ; SA # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1083..1084 ; SA # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1085..1086 ; SA # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +1087..108C ; SA # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108D ; SA # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +108E ; SA # Lo MYANMAR LETTER RUMAI PALAUNG FA +108F ; SA # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5 +1090..1099 ; NU # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +109A..109C ; SA # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +109D ; SA # Mn MYANMAR VOWEL SIGN AITON AI +109E..109F ; SA # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +10A0..10C5 ; AL # Lu [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; AL # Lu GEORGIAN CAPITAL LETTER YN +10CD ; AL # Lu GEORGIAN CAPITAL LETTER AEN +10D0..10FA ; AL # Ll [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FB ; AL # Po GEORGIAN PARAGRAPH SEPARATOR +10FC ; AL # Lm MODIFIER LETTER GEORGIAN NAR +10FD..10FF ; AL # Ll [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100..115F ; JL # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER +1160..11A7 ; JV # Lo [72] HANGUL JUNGSEONG FILLER..HANGUL JUNGSEONG O-YAE +11A8..11FF ; JT # Lo [88] HANGUL JONGSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN +1200..1248 ; AL # Lo [73] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +124A..124D ; AL # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; AL # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; AL # Lo ETHIOPIC SYLLABLE QHWA +125A..125D ; AL # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; AL # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; AL # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; AL # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; AL # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; AL # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; AL # Lo ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; AL # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; AL # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; AL # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; AL # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; AL # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +135D..135F ; CM # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1360 ; AL # Po ETHIOPIC SECTION MARK +1361 ; BA # Po ETHIOPIC WORDSPACE +1362..1368 ; AL # Po [7] ETHIOPIC FULL STOP..ETHIOPIC PARAGRAPH SEPARATOR +1369..137C ; AL # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +1380..138F ; AL # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1390..1399 ; AL # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +13A0..13F5 ; AL # Lu [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +13F8..13FD ; AL # Ll [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1400 ; BA # Pd CANADIAN SYLLABICS HYPHEN +1401..166C ; AL # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166D ; AL # So CANADIAN SYLLABICS CHI SIGN +166E ; AL # Po CANADIAN SYLLABICS FULL STOP +166F..167F ; AL # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1680 ; BA # Zs OGHAM SPACE MARK +1681..169A ; AL # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B ; OP # Ps OGHAM FEATHER MARK +169C ; CL # Pe OGHAM REVERSED FEATHER MARK +16A0..16EA ; AL # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB..16ED ; BA # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +16EE..16F0 ; AL # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +16F1..16F8 ; AL # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; AL # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA +1712..1714 ; CM # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1715 ; CM # Mc TAGALOG SIGN PAMUDPOD +171F ; AL # Lo TAGALOG LETTER ARCHAIC RA +1720..1731 ; AL # Lo [18] HANUNOO LETTER A..HANUNOO LETTER HA +1732..1733 ; CM # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1734 ; CM # Mc HANUNOO SIGN PAMUDPOD +1735..1736 ; BA # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +1740..1751 ; AL # Lo [18] BUHID LETTER A..BUHID LETTER HA +1752..1753 ; CM # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1760..176C ; AL # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; AL # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1772..1773 ; CM # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1780..17B3 ; SA # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4..17B5 ; SA # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6 ; SA # Mc KHMER VOWEL SIGN AA +17B7..17BD ; SA # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17BE..17C5 ; SA # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C6 ; SA # Mn KHMER SIGN NIKAHIT +17C7..17C8 ; SA # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +17C9..17D3 ; SA # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17D4..17D5 ; BA # Po [2] KHMER SIGN KHAN..KHMER SIGN BARIYOOSAN +17D6 ; NS # Po KHMER SIGN CAMNUC PII KUUH +17D7 ; SA # Lm KHMER SIGN LEK TOO +17D8 ; BA # Po KHMER SIGN BEYYAL +17D9 ; AL # Po KHMER SIGN PHNAEK MUAN +17DA ; BA # Po KHMER SIGN KOOMUUT +17DB ; PR # Sc KHMER CURRENCY SYMBOL RIEL +17DC ; SA # Lo KHMER SIGN AVAKRAHASANYA +17DD ; SA # Mn KHMER SIGN ATTHACAN +17E0..17E9 ; NU # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +17F0..17F9 ; AL # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +1800..1801 ; AL # Po [2] MONGOLIAN BIRGA..MONGOLIAN ELLIPSIS +1802..1803 ; EX # Po [2] MONGOLIAN COMMA..MONGOLIAN FULL STOP +1804..1805 ; BA # Po [2] MONGOLIAN COLON..MONGOLIAN FOUR DOTS +1806 ; BB # Pd MONGOLIAN TODO SOFT HYPHEN +1807 ; AL # Po MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER +1808..1809 ; EX # Po [2] MONGOLIAN MANCHU COMMA..MONGOLIAN MANCHU FULL STOP +180A ; AL # Po MONGOLIAN NIRUGU +180B..180D ; CM # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180E ; GL # Cf MONGOLIAN VOWEL SEPARATOR +180F ; CM # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR +1810..1819 ; NU # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1820..1842 ; AL # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1843 ; AL # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN +1844..1878 ; AL # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; AL # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1885..1886 ; CM # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +1887..18A8 ; AL # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18A9 ; CM # Mn MONGOLIAN LETTER ALI GALI DAGALGA +18AA ; AL # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; AL # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; AL # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1920..1922 ; CM # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1923..1926 ; CM # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1927..1928 ; CM # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1929..192B ; CM # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; CM # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1932 ; CM # Mn LIMBU SMALL LETTER ANUSVARA +1933..1938 ; CM # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1939..193B ; CM # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1940 ; AL # So LIMBU SIGN LOO +1944..1945 ; EX # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946..194F ; NU # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +1950..196D ; SA # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; SA # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; SA # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; SA # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +19D0..19D9 ; NU # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +19DA ; SA # No NEW TAI LUE THAM DIGIT ONE +19DE..19DF ; SA # So [2] NEW TAI LUE SIGN LAE..NEW TAI LUE SIGN LAEV +19E0..19FF ; AL # So [32] KHMER SYMBOL PATHAMASAT..KHMER SYMBOL DAP-PRAM ROC +1A00..1A16 ; AL # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A17..1A18 ; CM # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A19..1A1A ; CM # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A1B ; CM # Mn BUGINESE VOWEL SIGN AE +1A1E..1A1F ; AL # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20..1A54 ; SA # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1A55 ; SA # Mc TAI THAM CONSONANT SIGN MEDIAL RA +1A56 ; SA # Mn TAI THAM CONSONANT SIGN MEDIAL LA +1A57 ; SA # Mc TAI THAM CONSONANT SIGN LA TANG LAI +1A58..1A5E ; SA # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; SA # Mn TAI THAM SIGN SAKOT +1A61 ; SA # Mc TAI THAM VOWEL SIGN A +1A62 ; SA # Mn TAI THAM VOWEL SIGN MAI SAT +1A63..1A64 ; SA # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A65..1A6C ; SA # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A6D..1A72 ; SA # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1A73..1A7C ; SA # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; CM # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT +1A80..1A89 ; NU # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; NU # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1AA0..1AA6 ; SA # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA7 ; SA # Lm TAI THAM SIGN MAI YAMOK +1AA8..1AAD ; SA # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AB0..1ABD ; CM # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABE ; CM # Me COMBINING PARENTHESES OVERLAY +1ABF..1ACE ; CM # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; CM # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B04 ; CM # Mc BALINESE SIGN BISAH +1B05..1B33 ; AK # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B34 ; CM # Mn BALINESE SIGN REREKAN +1B35 ; CM # Mc BALINESE VOWEL SIGN TEDUNG +1B36..1B3A ; CM # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3B ; CM # Mc BALINESE VOWEL SIGN RA REPA TEDUNG +1B3C ; CM # Mn BALINESE VOWEL SIGN LA LENGA +1B3D..1B41 ; CM # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B42 ; CM # Mn BALINESE VOWEL SIGN PEPET +1B43 ; CM # Mc BALINESE VOWEL SIGN PEPET TEDUNG +1B44 ; VI # Mc BALINESE ADEG ADEG +1B45..1B4C ; AK # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B50..1B59 ; ID # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A..1B5B ; BA # Po [2] BALINESE PANTI..BALINESE PAMADA +1B5C ; ID # Po BALINESE WINDU +1B5D..1B60 ; BA # Po [4] BALINESE CARIK PAMUNGKAH..BALINESE PAMENENG +1B61..1B6A ; ID # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B6B..1B73 ; CM # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B74..1B7C ; ID # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +1B7D..1B7E ; BA # Po [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1B80..1B81 ; CM # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1B82 ; CM # Mc SUNDANESE SIGN PANGWISAD +1B83..1BA0 ; AL # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BA1 ; CM # Mc SUNDANESE CONSONANT SIGN PAMINGKAL +1BA2..1BA5 ; CM # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA6..1BA7 ; CM # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BA8..1BA9 ; CM # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAA ; CM # Mc SUNDANESE SIGN PAMAAEH +1BAB..1BAD ; CM # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BAE..1BAF ; AL # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BB0..1BB9 ; NU # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1BBA..1BBF ; AL # Lo [6] SUNDANESE AVAGRAHA..SUNDANESE LETTER FINAL M +1BC0..1BE5 ; AS # Lo [38] BATAK LETTER A..BATAK LETTER U +1BE6 ; CM # Mn BATAK SIGN TOMPI +1BE7 ; CM # Mc BATAK VOWEL SIGN E +1BE8..1BE9 ; CM # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BEA..1BEC ; CM # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BED ; CM # Mn BATAK VOWEL SIGN KARO O +1BEE ; CM # Mc BATAK VOWEL SIGN U +1BEF..1BF1 ; CM # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1BF2..1BF3 ; VF # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN +1BFC..1BFF ; AL # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C00..1C23 ; AL # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C24..1C2B ; CM # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C2C..1C33 ; CM # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C34..1C35 ; CM # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1C36..1C37 ; CM # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1C3B..1C3F ; BA # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C40..1C49 ; NU # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4D..1C4F ; AL # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C50..1C59 ; NU # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +1C5A..1C77 ; AL # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1C78..1C7D ; AL # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1C7E..1C7F ; BA # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1C80..1C88 ; AL # Ll [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1C90..1CBA ; AL # Lu [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; AL # Lu [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1CC0..1CC7 ; AL # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD0..1CD2 ; CM # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3 ; AL # Po VEDIC SIGN NIHSHVASA +1CD4..1CE0 ; CM # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE1 ; CM # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CE2..1CE8 ; CM # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CE9..1CEC ; AL # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CED ; CM # Mn VEDIC SIGN TIRYAK +1CEE..1CF3 ; AL # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF4 ; CM # Mn VEDIC TONE CANDRA ABOVE +1CF5..1CF6 ; AL # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CF7 ; CM # Mc VEDIC SIGN ATIKRAMA +1CF8..1CF9 ; CM # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1CFA ; AL # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +1D00..1D2B ; AL # Ll [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C..1D6A ; AL # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B..1D77 ; AL # Ll [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78 ; AL # Lm MODIFIER LETTER CYRILLIC EN +1D79..1D7F ; AL # Ll [7] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER UPSILON WITH STROKE +1D80..1D9A ; AL # Ll [27] LATIN SMALL LETTER B WITH PALATAL HOOK..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B..1DBF ; AL # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1DC0..1DCC ; CM # Mn [13] COMBINING DOTTED GRAVE ACCENT..COMBINING MACRON-BREVE +1DCD ; GL # Mn COMBINING DOUBLE CIRCUMFLEX ABOVE +1DCE..1DFB ; CM # Mn [46] COMBINING OGONEK ABOVE..COMBINING DELETION MARK +1DFC ; GL # Mn COMBINING DOUBLE INVERTED BREVE BELOW +1DFD..1DFF ; CM # Mn [3] COMBINING ALMOST EQUAL TO BELOW..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00..1EFF ; AL # L& [256] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH LOOP +1F00..1F15 ; AL # L& [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; AL # Lu [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; AL # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; AL # Lu [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; AL # Ll [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F7D ; AL # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1FB4 ; AL # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FBC ; AL # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FBD ; AL # Sk GREEK KORONIS +1FBE ; AL # Ll GREEK PROSGEGRAMMENI +1FBF..1FC1 ; AL # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FC2..1FC4 ; AL # Ll [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FCC ; AL # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FCD..1FCF ; AL # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FD0..1FD3 ; AL # Ll [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FDB ; AL # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA +1FDD..1FDF ; AL # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FE0..1FEC ; AL # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FED..1FEF ; AL # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FF2..1FF4 ; AL # Ll [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FFC ; AL # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI +1FFD ; BB # Sk GREEK OXIA +1FFE ; AL # Sk GREEK DASIA +2000..2006 ; BA # Zs [7] EN QUAD..SIX-PER-EM SPACE +2007 ; GL # Zs FIGURE SPACE +2008..200A ; BA # Zs [3] PUNCTUATION SPACE..HAIR SPACE +200B ; ZW # Cf ZERO WIDTH SPACE +200C ; CM # Cf ZERO WIDTH NON-JOINER +200D ; ZWJ# Cf ZERO WIDTH JOINER +200E..200F ; CM # Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK +2010 ; BA # Pd HYPHEN +2011 ; GL # Pd NON-BREAKING HYPHEN +2012..2013 ; BA # Pd [2] FIGURE DASH..EN DASH +2014 ; B2 # Pd EM DASH +2015 ; AI # Pd HORIZONTAL BAR +2016 ; AI # Po DOUBLE VERTICAL LINE +2017 ; AL # Po DOUBLE LOW LINE +2018 ; QU # Pi LEFT SINGLE QUOTATION MARK +2019 ; QU # Pf RIGHT SINGLE QUOTATION MARK +201A ; OP # Ps SINGLE LOW-9 QUOTATION MARK +201B..201C ; QU # Pi [2] SINGLE HIGH-REVERSED-9 QUOTATION MARK..LEFT DOUBLE QUOTATION MARK +201D ; QU # Pf RIGHT DOUBLE QUOTATION MARK +201E ; OP # Ps DOUBLE LOW-9 QUOTATION MARK +201F ; QU # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2020..2021 ; AI # Po [2] DAGGER..DOUBLE DAGGER +2022..2023 ; AL # Po [2] BULLET..TRIANGULAR BULLET +2024..2026 ; IN # Po [3] ONE DOT LEADER..HORIZONTAL ELLIPSIS +2027 ; BA # Po HYPHENATION POINT +2028 ; BK # Zl LINE SEPARATOR +2029 ; BK # Zp PARAGRAPH SEPARATOR +202A..202E ; CM # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +202F ; GL # Zs NARROW NO-BREAK SPACE +2030..2037 ; PO # Po [8] PER MILLE SIGN..REVERSED TRIPLE PRIME +2038 ; AL # Po CARET +2039 ; QU # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A ; QU # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +203B ; AI # Po REFERENCE MARK +203C..203D ; NS # Po [2] DOUBLE EXCLAMATION MARK..INTERROBANG +203E ; AL # Po OVERLINE +203F..2040 ; AL # Pc [2] UNDERTIE..CHARACTER TIE +2041..2043 ; AL # Po [3] CARET INSERTION POINT..HYPHEN BULLET +2044 ; IS # Sm FRACTION SLASH +2045 ; OP # Ps LEFT SQUARE BRACKET WITH QUILL +2046 ; CL # Pe RIGHT SQUARE BRACKET WITH QUILL +2047..2049 ; NS # Po [3] DOUBLE QUESTION MARK..EXCLAMATION QUESTION MARK +204A..2051 ; AL # Po [8] TIRONIAN SIGN ET..TWO ASTERISKS ALIGNED VERTICALLY +2052 ; AL # Sm COMMERCIAL MINUS SIGN +2053 ; AL # Po SWUNG DASH +2054 ; AL # Pc INVERTED UNDERTIE +2055 ; AL # Po FLOWER PUNCTUATION MARK +2056 ; BA # Po THREE DOT PUNCTUATION +2057 ; PO # Po QUADRUPLE PRIME +2058..205B ; BA # Po [4] FOUR DOT PUNCTUATION..FOUR DOT MARK +205C ; AL # Po DOTTED CROSS +205D..205E ; BA # Po [2] TRICOLON..VERTICAL FOUR DOTS +205F ; BA # Zs MEDIUM MATHEMATICAL SPACE +2060 ; WJ # Cf WORD JOINER +2061..2064 ; AL # Cf [4] FUNCTION APPLICATION..INVISIBLE PLUS +2066..206F ; CM # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +2070 ; AL # No SUPERSCRIPT ZERO +2071 ; AL # Lm SUPERSCRIPT LATIN SMALL LETTER I +2074 ; AI # No SUPERSCRIPT FOUR +2075..2079 ; AL # No [5] SUPERSCRIPT FIVE..SUPERSCRIPT NINE +207A..207C ; AL # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +207D ; OP # Ps SUPERSCRIPT LEFT PARENTHESIS +207E ; CL # Pe SUPERSCRIPT RIGHT PARENTHESIS +207F ; AI # Lm SUPERSCRIPT LATIN SMALL LETTER N +2080 ; AL # No SUBSCRIPT ZERO +2081..2084 ; AI # No [4] SUBSCRIPT ONE..SUBSCRIPT FOUR +2085..2089 ; AL # No [5] SUBSCRIPT FIVE..SUBSCRIPT NINE +208A..208C ; AL # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +208D ; OP # Ps SUBSCRIPT LEFT PARENTHESIS +208E ; CL # Pe SUBSCRIPT RIGHT PARENTHESIS +2090..209C ; AL # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +20A0..20A6 ; PR # Sc [7] EURO-CURRENCY SIGN..NAIRA SIGN +20A7 ; PO # Sc PESETA SIGN +20A8..20B5 ; PR # Sc [14] RUPEE SIGN..CEDI SIGN +20B6 ; PO # Sc LIVRE TOURNOIS SIGN +20B7..20BA ; PR # Sc [4] SPESMILO SIGN..TURKISH LIRA SIGN +20BB ; PO # Sc NORDIC MARK SIGN +20BC..20BD ; PR # Sc [2] MANAT SIGN..RUBLE SIGN +20BE ; PO # Sc LARI SIGN +20BF ; PR # Sc BITCOIN SIGN +20C0 ; PO # Sc SOM SIGN +20C1..20CF ; PR # Cn [15] .. +20D0..20DC ; CM # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20DD..20E0 ; CM # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E1 ; CM # Mn COMBINING LEFT RIGHT ARROW ABOVE +20E2..20E4 ; CM # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +20E5..20F0 ; CM # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2100..2101 ; AL # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2102 ; AL # Lu DOUBLE-STRUCK CAPITAL C +2103 ; PO # So DEGREE CELSIUS +2104 ; AL # So CENTRE LINE SYMBOL +2105 ; AI # So CARE OF +2106 ; AL # So CADA UNA +2107 ; AL # Lu EULER CONSTANT +2108 ; AL # So SCRUPLE +2109 ; PO # So DEGREE FAHRENHEIT +210A..2112 ; AL # L& [9] SCRIPT SMALL G..SCRIPT CAPITAL L +2113 ; AI # Ll SCRIPT SMALL L +2114 ; AL # So L B BAR SYMBOL +2115 ; AL # Lu DOUBLE-STRUCK CAPITAL N +2116 ; PR # So NUMERO SIGN +2117 ; AL # So SOUND RECORDING COPYRIGHT +2118 ; AL # Sm SCRIPT CAPITAL P +2119..211D ; AL # Lu [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +211E..2120 ; AL # So [3] PRESCRIPTION TAKE..SERVICE MARK +2121..2122 ; AI # So [2] TELEPHONE SIGN..TRADE MARK SIGN +2123 ; AL # So VERSICLE +2124 ; AL # Lu DOUBLE-STRUCK CAPITAL Z +2125 ; AL # So OUNCE SIGN +2126 ; AL # Lu OHM SIGN +2127 ; AL # So INVERTED OHM SIGN +2128 ; AL # Lu BLACK-LETTER CAPITAL Z +2129 ; AL # So TURNED GREEK SMALL LETTER IOTA +212A ; AL # Lu KELVIN SIGN +212B ; AI # Lu ANGSTROM SIGN +212C..212D ; AL # Lu [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C +212E ; AL # So ESTIMATED SYMBOL +212F..2134 ; AL # L& [6] SCRIPT SMALL E..SCRIPT SMALL O +2135..2138 ; AL # Lo [4] ALEF SYMBOL..DALET SYMBOL +2139 ; AL # Ll INFORMATION SOURCE +213A..213B ; AL # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN +213C..213F ; AL # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI +2140..2144 ; AL # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +2145..2149 ; AL # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J +214A ; AL # So PROPERTY LINE +214B ; AL # Sm TURNED AMPERSAND +214C..214D ; AL # So [2] PER SIGN..AKTIESELSKAB +214E ; AL # Ll TURNED SMALL F +214F ; AL # So SYMBOL FOR SAMARITAN SOURCE +2150..2153 ; AL # No [4] VULGAR FRACTION ONE SEVENTH..VULGAR FRACTION ONE THIRD +2154..2155 ; AI # No [2] VULGAR FRACTION TWO THIRDS..VULGAR FRACTION ONE FIFTH +2156..215A ; AL # No [5] VULGAR FRACTION TWO FIFTHS..VULGAR FRACTION FIVE SIXTHS +215B ; AI # No VULGAR FRACTION ONE EIGHTH +215C..215D ; AL # No [2] VULGAR FRACTION THREE EIGHTHS..VULGAR FRACTION FIVE EIGHTHS +215E ; AI # No VULGAR FRACTION SEVEN EIGHTHS +215F ; AL # No FRACTION NUMERATOR ONE +2160..216B ; AI # Nl [12] ROMAN NUMERAL ONE..ROMAN NUMERAL TWELVE +216C..216F ; AL # Nl [4] ROMAN NUMERAL FIFTY..ROMAN NUMERAL ONE THOUSAND +2170..2179 ; AI # Nl [10] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL TEN +217A..2182 ; AL # Nl [9] SMALL ROMAN NUMERAL ELEVEN..ROMAN NUMERAL TEN THOUSAND +2183..2184 ; AL # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C +2185..2188 ; AL # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +2189 ; AI # No VULGAR FRACTION ZERO THIRDS +218A..218B ; AL # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2190..2194 ; AI # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +2195..2199 ; AI # So [5] UP DOWN ARROW..SOUTH WEST ARROW +219A..219B ; AL # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +219C..219F ; AL # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A0 ; AL # Sm RIGHTWARDS TWO HEADED ARROW +21A1..21A2 ; AL # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A3 ; AL # Sm RIGHTWARDS ARROW WITH TAIL +21A4..21A5 ; AL # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A6 ; AL # Sm RIGHTWARDS ARROW FROM BAR +21A7..21AD ; AL # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AE ; AL # Sm LEFT RIGHT ARROW WITH STROKE +21AF..21CD ; AL # So [31] DOWNWARDS ZIGZAG ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21CE..21CF ; AL # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; AL # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D2 ; AI # Sm RIGHTWARDS DOUBLE ARROW +21D3 ; AL # So DOWNWARDS DOUBLE ARROW +21D4 ; AI # Sm LEFT RIGHT DOUBLE ARROW +21D5..21F3 ; AL # So [31] UP DOWN DOUBLE ARROW..UP DOWN WHITE ARROW +21F4..21FF ; AL # Sm [12] RIGHT ARROW WITH SMALL CIRCLE..LEFT RIGHT OPEN-HEADED ARROW +2200 ; AI # Sm FOR ALL +2201 ; AL # Sm COMPLEMENT +2202..2203 ; AI # Sm [2] PARTIAL DIFFERENTIAL..THERE EXISTS +2204..2206 ; AL # Sm [3] THERE DOES NOT EXIST..INCREMENT +2207..2208 ; AI # Sm [2] NABLA..ELEMENT OF +2209..220A ; AL # Sm [2] NOT AN ELEMENT OF..SMALL ELEMENT OF +220B ; AI # Sm CONTAINS AS MEMBER +220C..220E ; AL # Sm [3] DOES NOT CONTAIN AS MEMBER..END OF PROOF +220F ; AI # Sm N-ARY PRODUCT +2210 ; AL # Sm N-ARY COPRODUCT +2211 ; AI # Sm N-ARY SUMMATION +2212..2213 ; PR # Sm [2] MINUS SIGN..MINUS-OR-PLUS SIGN +2214 ; AL # Sm DOT PLUS +2215 ; AI # Sm DIVISION SLASH +2216..2219 ; AL # Sm [4] SET MINUS..BULLET OPERATOR +221A ; AI # Sm SQUARE ROOT +221B..221C ; AL # Sm [2] CUBE ROOT..FOURTH ROOT +221D..2220 ; AI # Sm [4] PROPORTIONAL TO..ANGLE +2221..2222 ; AL # Sm [2] MEASURED ANGLE..SPHERICAL ANGLE +2223 ; AI # Sm DIVIDES +2224 ; AL # Sm DOES NOT DIVIDE +2225 ; AI # Sm PARALLEL TO +2226 ; AL # Sm NOT PARALLEL TO +2227..222C ; AI # Sm [6] LOGICAL AND..DOUBLE INTEGRAL +222D ; AL # Sm TRIPLE INTEGRAL +222E ; AI # Sm CONTOUR INTEGRAL +222F..2233 ; AL # Sm [5] SURFACE INTEGRAL..ANTICLOCKWISE CONTOUR INTEGRAL +2234..2237 ; AI # Sm [4] THEREFORE..PROPORTION +2238..223B ; AL # Sm [4] DOT MINUS..HOMOTHETIC +223C..223D ; AI # Sm [2] TILDE OPERATOR..REVERSED TILDE +223E..2247 ; AL # Sm [10] INVERTED LAZY S..NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +2248 ; AI # Sm ALMOST EQUAL TO +2249..224B ; AL # Sm [3] NOT ALMOST EQUAL TO..TRIPLE TILDE +224C ; AI # Sm ALL EQUAL TO +224D..2251 ; AL # Sm [5] EQUIVALENT TO..GEOMETRICALLY EQUAL TO +2252 ; AI # Sm APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253..225F ; AL # Sm [13] IMAGE OF OR APPROXIMATELY EQUAL TO..QUESTIONED EQUAL TO +2260..2261 ; AI # Sm [2] NOT EQUAL TO..IDENTICAL TO +2262..2263 ; AL # Sm [2] NOT IDENTICAL TO..STRICTLY EQUIVALENT TO +2264..2267 ; AI # Sm [4] LESS-THAN OR EQUAL TO..GREATER-THAN OVER EQUAL TO +2268..2269 ; AL # Sm [2] LESS-THAN BUT NOT EQUAL TO..GREATER-THAN BUT NOT EQUAL TO +226A..226B ; AI # Sm [2] MUCH LESS-THAN..MUCH GREATER-THAN +226C..226D ; AL # Sm [2] BETWEEN..NOT EQUIVALENT TO +226E..226F ; AI # Sm [2] NOT LESS-THAN..NOT GREATER-THAN +2270..2281 ; AL # Sm [18] NEITHER LESS-THAN NOR EQUAL TO..DOES NOT SUCCEED +2282..2283 ; AI # Sm [2] SUBSET OF..SUPERSET OF +2284..2285 ; AL # Sm [2] NOT A SUBSET OF..NOT A SUPERSET OF +2286..2287 ; AI # Sm [2] SUBSET OF OR EQUAL TO..SUPERSET OF OR EQUAL TO +2288..2294 ; AL # Sm [13] NEITHER A SUBSET OF NOR EQUAL TO..SQUARE CUP +2295 ; AI # Sm CIRCLED PLUS +2296..2298 ; AL # Sm [3] CIRCLED MINUS..CIRCLED DIVISION SLASH +2299 ; AI # Sm CIRCLED DOT OPERATOR +229A..22A4 ; AL # Sm [11] CIRCLED RING OPERATOR..DOWN TACK +22A5 ; AI # Sm UP TACK +22A6..22BE ; AL # Sm [25] ASSERTION..RIGHT ANGLE WITH ARC +22BF ; AI # Sm RIGHT TRIANGLE +22C0..22EE ; AL # Sm [47] N-ARY LOGICAL AND..VERTICAL ELLIPSIS +22EF ; IN # Sm MIDLINE HORIZONTAL ELLIPSIS +22F0..22FF ; AL # Sm [16] UP RIGHT DIAGONAL ELLIPSIS..Z NOTATION BAG MEMBERSHIP +2300..2307 ; AL # So [8] DIAMETER SIGN..WAVY LINE +2308 ; OP # Ps LEFT CEILING +2309 ; CL # Pe RIGHT CEILING +230A ; OP # Ps LEFT FLOOR +230B ; CL # Pe RIGHT FLOOR +230C..2311 ; AL # So [6] BOTTOM RIGHT CROP..SQUARE LOZENGE +2312 ; AI # So ARC +2313..2319 ; AL # So [7] SEGMENT..TURNED NOT SIGN +231A..231B ; ID # So [2] WATCH..HOURGLASS +231C..231F ; AL # So [4] TOP LEFT CORNER..BOTTOM RIGHT CORNER +2320..2321 ; AL # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +2322..2328 ; AL # So [7] FROWN..KEYBOARD +2329 ; OP # Ps LEFT-POINTING ANGLE BRACKET +232A ; CL # Pe RIGHT-POINTING ANGLE BRACKET +232B..237B ; AL # So [81] ERASE TO THE LEFT..NOT CHECK MARK +237C ; AL # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +237D..239A ; AL # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +239B..23B3 ; AL # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23B4..23DB ; AL # So [40] TOP SQUARE BRACKET..FUSE +23DC..23E1 ; AL # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +23E2..23EF ; AL # So [14] WHITE TRAPEZIUM..BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23F0..23F3 ; ID # So [4] ALARM CLOCK..HOURGLASS WITH FLOWING SAND +23F4..23FF ; AL # So [12] BLACK MEDIUM LEFT-POINTING TRIANGLE..OBSERVER EYE SYMBOL +2400..2426 ; AL # So [39] SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; AL # So [11] OCR HOOK..OCR DOUBLE BACKSLASH +2460..249B ; AI # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +249C..24E9 ; AI # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +24EA..24FE ; AI # No [21] CIRCLED DIGIT ZERO..DOUBLE CIRCLED NUMBER TEN +24FF ; AL # No NEGATIVE CIRCLED DIGIT ZERO +2500..254B ; AI # So [76] BOX DRAWINGS LIGHT HORIZONTAL..BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL +254C..254F ; AL # So [4] BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL..BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL +2550..2574 ; AI # So [37] BOX DRAWINGS DOUBLE HORIZONTAL..BOX DRAWINGS LIGHT LEFT +2575..257F ; AL # So [11] BOX DRAWINGS LIGHT UP..BOX DRAWINGS HEAVY UP AND LIGHT DOWN +2580..258F ; AI # So [16] UPPER HALF BLOCK..LEFT ONE EIGHTH BLOCK +2590..2591 ; AL # So [2] RIGHT HALF BLOCK..LIGHT SHADE +2592..2595 ; AI # So [4] MEDIUM SHADE..RIGHT ONE EIGHTH BLOCK +2596..259F ; AL # So [10] QUADRANT LOWER LEFT..QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT +25A0..25A1 ; AI # So [2] BLACK SQUARE..WHITE SQUARE +25A2 ; AL # So WHITE SQUARE WITH ROUNDED CORNERS +25A3..25A9 ; AI # So [7] WHITE SQUARE CONTAINING BLACK SMALL SQUARE..SQUARE WITH DIAGONAL CROSSHATCH FILL +25AA..25B1 ; AL # So [8] BLACK SMALL SQUARE..WHITE PARALLELOGRAM +25B2..25B3 ; AI # So [2] BLACK UP-POINTING TRIANGLE..WHITE UP-POINTING TRIANGLE +25B4..25B5 ; AL # So [2] BLACK UP-POINTING SMALL TRIANGLE..WHITE UP-POINTING SMALL TRIANGLE +25B6 ; AI # So BLACK RIGHT-POINTING TRIANGLE +25B7 ; AI # Sm WHITE RIGHT-POINTING TRIANGLE +25B8..25BB ; AL # So [4] BLACK RIGHT-POINTING SMALL TRIANGLE..WHITE RIGHT-POINTING POINTER +25BC..25BD ; AI # So [2] BLACK DOWN-POINTING TRIANGLE..WHITE DOWN-POINTING TRIANGLE +25BE..25BF ; AL # So [2] BLACK DOWN-POINTING SMALL TRIANGLE..WHITE DOWN-POINTING SMALL TRIANGLE +25C0 ; AI # So BLACK LEFT-POINTING TRIANGLE +25C1 ; AI # Sm WHITE LEFT-POINTING TRIANGLE +25C2..25C5 ; AL # So [4] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE LEFT-POINTING POINTER +25C6..25C8 ; AI # So [3] BLACK DIAMOND..WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND +25C9..25CA ; AL # So [2] FISHEYE..LOZENGE +25CB ; AI # So WHITE CIRCLE +25CC..25CD ; AL # So [2] DOTTED CIRCLE..CIRCLE WITH VERTICAL FILL +25CE..25D1 ; AI # So [4] BULLSEYE..CIRCLE WITH RIGHT HALF BLACK +25D2..25E1 ; AL # So [16] CIRCLE WITH LOWER HALF BLACK..LOWER HALF CIRCLE +25E2..25E5 ; AI # So [4] BLACK LOWER RIGHT TRIANGLE..BLACK UPPER RIGHT TRIANGLE +25E6..25EE ; AL # So [9] WHITE BULLET..UP-POINTING TRIANGLE WITH RIGHT HALF BLACK +25EF ; AI # So LARGE CIRCLE +25F0..25F7 ; AL # So [8] WHITE SQUARE WITH UPPER LEFT QUADRANT..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +25F8..25FF ; AL # Sm [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE +2600..2603 ; ID # So [4] BLACK SUN WITH RAYS..SNOWMAN +2604 ; AL # So COMET +2605..2606 ; AI # So [2] BLACK STAR..WHITE STAR +2607..2608 ; AL # So [2] LIGHTNING..THUNDERSTORM +2609 ; AI # So SUN +260A..260D ; AL # So [4] ASCENDING NODE..OPPOSITION +260E..260F ; AI # So [2] BLACK TELEPHONE..WHITE TELEPHONE +2610..2613 ; AL # So [4] BALLOT BOX..SALTIRE +2614..2615 ; ID # So [2] UMBRELLA WITH RAIN DROPS..HOT BEVERAGE +2616..2617 ; AI # So [2] WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; ID # So SHAMROCK +2619 ; AL # So REVERSED ROTATED FLORAL HEART BULLET +261A..261C ; ID # So [3] BLACK LEFT POINTING INDEX..WHITE LEFT POINTING INDEX +261D ; EB # So WHITE UP POINTING INDEX +261E..261F ; ID # So [2] WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620..2638 ; AL # So [25] SKULL AND CROSSBONES..WHEEL OF DHARMA +2639..263B ; ID # So [3] WHITE FROWNING FACE..BLACK SMILING FACE +263C..263F ; AL # So [4] WHITE SUN WITH RAYS..MERCURY +2640 ; AI # So FEMALE SIGN +2641 ; AL # So EARTH +2642 ; AI # So MALE SIGN +2643..265F ; AL # So [29] JUPITER..BLACK CHESS PAWN +2660..2661 ; AI # So [2] BLACK SPADE SUIT..WHITE HEART SUIT +2662 ; AL # So WHITE DIAMOND SUIT +2663..2665 ; AI # So [3] BLACK CLUB SUIT..BLACK HEART SUIT +2666 ; AL # So BLACK DIAMOND SUIT +2667 ; AI # So WHITE CLUB SUIT +2668 ; ID # So HOT SPRINGS +2669..266A ; AI # So [2] QUARTER NOTE..EIGHTH NOTE +266B ; AL # So BEAMED EIGHTH NOTES +266C..266D ; AI # So [2] BEAMED SIXTEENTH NOTES..MUSIC FLAT SIGN +266E ; AL # So MUSIC NATURAL SIGN +266F ; AI # Sm MUSIC SHARP SIGN +2670..267E ; AL # So [15] WEST SYRIAC CROSS..PERMANENT PAPER SIGN +267F ; ID # So WHEELCHAIR SYMBOL +2680..269D ; AL # So [30] DIE FACE-1..OUTLINED WHITE STAR +269E..269F ; AI # So [2] THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT +26A0..26BC ; AL # So [29] WARNING SIGN..SESQUIQUADRATE +26BD..26C8 ; ID # So [12] SOCCER BALL..THUNDER CLOUD AND RAIN +26C9..26CC ; AI # So [4] TURNED WHITE SHOGI PIECE..CROSSING LANES +26CD ; ID # So DISABLED CAR +26CE ; AL # So OPHIUCHUS +26CF..26D1 ; ID # So [3] PICK..HELMET WITH WHITE CROSS +26D2 ; AI # So CIRCLED CROSSING LANES +26D3..26D4 ; ID # So [2] CHAINS..NO ENTRY +26D5..26D7 ; AI # So [3] ALTERNATE ONE-WAY LEFT WAY TRAFFIC..WHITE TWO-WAY LEFT WAY TRAFFIC +26D8..26D9 ; ID # So [2] BLACK LEFT LANE MERGE..WHITE LEFT LANE MERGE +26DA..26DB ; AI # So [2] DRIVE SLOW SIGN..HEAVY WHITE DOWN-POINTING TRIANGLE +26DC ; ID # So LEFT CLOSED ENTRY +26DD..26DE ; AI # So [2] SQUARED SALTIRE..FALLING DIAGONAL IN WHITE CIRCLE IN BLACK SQUARE +26DF..26E1 ; ID # So [3] BLACK TRUCK..RESTRICTED LEFT ENTRY-2 +26E2 ; AL # So ASTRONOMICAL SYMBOL FOR URANUS +26E3 ; AI # So HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; AL # So [4] PENTAGRAM..INVERTED PENTAGRAM +26E8..26E9 ; AI # So [2] BLACK CROSS ON SHIELD..SHINTO SHRINE +26EA ; ID # So CHURCH +26EB..26F0 ; AI # So [6] CASTLE..MOUNTAIN +26F1..26F5 ; ID # So [5] UMBRELLA ON GROUND..SAILBOAT +26F6 ; AI # So SQUARE FOUR CORNERS +26F7..26F8 ; ID # So [2] SKIER..ICE SKATE +26F9 ; EB # So PERSON WITH BALL +26FA ; ID # So TENT +26FB..26FC ; AI # So [2] JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD..26FF ; ID # So [3] FUEL PUMP..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700..2704 ; ID # So [5] BLACK SAFETY SCISSORS..WHITE SCISSORS +2705..2707 ; AL # So [3] WHITE HEAVY CHECK MARK..TAPE DRIVE +2708..2709 ; ID # So [2] AIRPLANE..ENVELOPE +270A..270D ; EB # So [4] RAISED FIST..WRITING HAND +270E..2756 ; AL # So [73] LOWER RIGHT PENCIL..BLACK DIAMOND MINUS WHITE X +2757 ; AI # So HEAVY EXCLAMATION MARK SYMBOL +2758..275A ; AL # So [3] LIGHT VERTICAL BAR..HEAVY VERTICAL BAR +275B..2760 ; QU # So [6] HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT..HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT +2761 ; AL # So CURVED STEM PARAGRAPH SIGN ORNAMENT +2762..2763 ; EX # So [2] HEAVY EXCLAMATION MARK ORNAMENT..HEAVY HEART EXCLAMATION MARK ORNAMENT +2764 ; ID # So HEAVY BLACK HEART +2765..2767 ; AL # So [3] ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2768 ; OP # Ps MEDIUM LEFT PARENTHESIS ORNAMENT +2769 ; CL # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT +276A ; OP # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B ; CL # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C ; OP # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D ; CL # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E ; OP # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F ; CL # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; OP # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771 ; CL # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772 ; OP # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2773 ; CL # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2774 ; OP # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT +2775 ; CL # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT +2776..2793 ; AI # No [30] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2794..27BF ; AL # So [44] HEAVY WIDE-HEADED RIGHTWARDS ARROW..DOUBLE CURLY LOOP +27C0..27C4 ; AL # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C5 ; OP # Ps LEFT S-SHAPED BAG DELIMITER +27C6 ; CL # Pe RIGHT S-SHAPED BAG DELIMITER +27C7..27E5 ; AL # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27E6 ; OP # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7 ; CL # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8 ; OP # Ps MATHEMATICAL LEFT ANGLE BRACKET +27E9 ; CL # Pe MATHEMATICAL RIGHT ANGLE BRACKET +27EA ; OP # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB ; CL # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC ; OP # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED ; CL # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE ; OP # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF ; CL # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS +27F0..27FF ; AL # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2800..28FF ; AL # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2900..297F ; AL # Sm [128] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..DOWN FISH TAIL +2980..2982 ; AL # Sm [3] TRIPLE VERTICAL BAR DELIMITER..Z NOTATION TYPE COLON +2983 ; OP # Ps LEFT WHITE CURLY BRACKET +2984 ; CL # Pe RIGHT WHITE CURLY BRACKET +2985 ; OP # Ps LEFT WHITE PARENTHESIS +2986 ; CL # Pe RIGHT WHITE PARENTHESIS +2987 ; OP # Ps Z NOTATION LEFT IMAGE BRACKET +2988 ; CL # Pe Z NOTATION RIGHT IMAGE BRACKET +2989 ; OP # Ps Z NOTATION LEFT BINDING BRACKET +298A ; CL # Pe Z NOTATION RIGHT BINDING BRACKET +298B ; OP # Ps LEFT SQUARE BRACKET WITH UNDERBAR +298C ; CL # Pe RIGHT SQUARE BRACKET WITH UNDERBAR +298D ; OP # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E ; CL # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F ; OP # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; CL # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991 ; OP # Ps LEFT ANGLE BRACKET WITH DOT +2992 ; CL # Pe RIGHT ANGLE BRACKET WITH DOT +2993 ; OP # Ps LEFT ARC LESS-THAN BRACKET +2994 ; CL # Pe RIGHT ARC GREATER-THAN BRACKET +2995 ; OP # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET +2996 ; CL # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET +2997 ; OP # Ps LEFT BLACK TORTOISE SHELL BRACKET +2998 ; CL # Pe RIGHT BLACK TORTOISE SHELL BRACKET +2999..29D7 ; AL # Sm [63] DOTTED FENCE..BLACK HOURGLASS +29D8 ; OP # Ps LEFT WIGGLY FENCE +29D9 ; CL # Pe RIGHT WIGGLY FENCE +29DA ; OP # Ps LEFT DOUBLE WIGGLY FENCE +29DB ; CL # Pe RIGHT DOUBLE WIGGLY FENCE +29DC..29FB ; AL # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FC ; OP # Ps LEFT-POINTING CURVED ANGLE BRACKET +29FD ; CL # Pe RIGHT-POINTING CURVED ANGLE BRACKET +29FE..29FF ; AL # Sm [2] TINY..MINY +2A00..2AFF ; AL # Sm [256] N-ARY CIRCLED DOT OPERATOR..N-ARY WHITE VERTICAL BAR +2B00..2B2F ; AL # So [48] NORTH EAST WHITE ARROW..WHITE VERTICAL ELLIPSE +2B30..2B44 ; AL # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B45..2B46 ; AL # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B47..2B4C ; AL # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +2B4D..2B54 ; AL # So [8] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..WHITE RIGHT-POINTING PENTAGON +2B55..2B59 ; AI # So [5] HEAVY LARGE CIRCLE..HEAVY CIRCLED SALTIRE +2B5A..2B73 ; AL # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; AL # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; AL # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2C00..2C5F ; AL # L& [96] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C60..2C7B ; AL # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E +2C7C..2C7D ; AL # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2C7E..2C7F ; AL # Lu [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL +2C80..2CE4 ; AL # L& [101] COPTIC CAPITAL LETTER ALFA..COPTIC SYMBOL KAI +2CE5..2CEA ; AL # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2CEB..2CEE ; AL # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CEF..2CF1 ; CM # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2CF2..2CF3 ; AL # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI +2CF9 ; EX # Po COPTIC OLD NUBIAN FULL STOP +2CFA..2CFC ; BA # Po [3] COPTIC OLD NUBIAN DIRECT QUESTION MARK..COPTIC OLD NUBIAN VERSE DIVIDER +2CFD ; AL # No COPTIC FRACTION ONE HALF +2CFE ; EX # Po COPTIC FULL STOP +2CFF ; BA # Po COPTIC MORPHOLOGICAL DIVIDER +2D00..2D25 ; AL # Ll [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; AL # Ll GEORGIAN SMALL LETTER YN +2D2D ; AL # Ll GEORGIAN SMALL LETTER AEN +2D30..2D67 ; AL # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D6F ; AL # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D70 ; BA # Po TIFINAGH SEPARATOR MARK +2D7F ; CM # Mn TIFINAGH CONSONANT JOINER +2D80..2D96 ; AL # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; AL # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; AL # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; AL # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; AL # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; AL # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; AL # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; AL # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; AL # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DE0..2DFF ; CM # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +2E00..2E01 ; QU # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E02 ; QU # Pi LEFT SUBSTITUTION BRACKET +2E03 ; QU # Pf RIGHT SUBSTITUTION BRACKET +2E04 ; QU # Pi LEFT DOTTED SUBSTITUTION BRACKET +2E05 ; QU # Pf RIGHT DOTTED SUBSTITUTION BRACKET +2E06..2E08 ; QU # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E09 ; QU # Pi LEFT TRANSPOSITION BRACKET +2E0A ; QU # Pf RIGHT TRANSPOSITION BRACKET +2E0B ; QU # Po RAISED SQUARE +2E0C ; QU # Pi LEFT RAISED OMISSION BRACKET +2E0D ; QU # Pf RIGHT RAISED OMISSION BRACKET +2E0E..2E15 ; BA # Po [8] EDITORIAL CORONIS..UPWARDS ANCORA +2E16 ; AL # Po DOTTED RIGHT-POINTING ANGLE +2E17 ; BA # Pd DOUBLE OBLIQUE HYPHEN +2E18 ; OP # Po INVERTED INTERROBANG +2E19 ; BA # Po PALM BRANCH +2E1A ; AL # Pd HYPHEN WITH DIAERESIS +2E1B ; AL # Po TILDE WITH RING ABOVE +2E1C ; QU # Pi LEFT LOW PARAPHRASE BRACKET +2E1D ; QU # Pf RIGHT LOW PARAPHRASE BRACKET +2E1E..2E1F ; AL # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E20 ; QU # Pi LEFT VERTICAL BAR WITH QUILL +2E21 ; QU # Pf RIGHT VERTICAL BAR WITH QUILL +2E22 ; OP # Ps TOP LEFT HALF BRACKET +2E23 ; CL # Pe TOP RIGHT HALF BRACKET +2E24 ; OP # Ps BOTTOM LEFT HALF BRACKET +2E25 ; CL # Pe BOTTOM RIGHT HALF BRACKET +2E26 ; OP # Ps LEFT SIDEWAYS U BRACKET +2E27 ; CL # Pe RIGHT SIDEWAYS U BRACKET +2E28 ; OP # Ps LEFT DOUBLE PARENTHESIS +2E29 ; CL # Pe RIGHT DOUBLE PARENTHESIS +2E2A..2E2D ; BA # Po [4] TWO DOTS OVER ONE DOT PUNCTUATION..FIVE DOT MARK +2E2E ; EX # Po REVERSED QUESTION MARK +2E2F ; AL # Lm VERTICAL TILDE +2E30..2E31 ; BA # Po [2] RING POINT..WORD SEPARATOR MIDDLE DOT +2E32 ; AL # Po TURNED COMMA +2E33..2E34 ; BA # Po [2] RAISED DOT..RAISED COMMA +2E35..2E39 ; AL # Po [5] TURNED SEMICOLON..TOP HALF SECTION SIGN +2E3A..2E3B ; B2 # Pd [2] TWO-EM DASH..THREE-EM DASH +2E3C..2E3E ; BA # Po [3] STENOGRAPHIC FULL STOP..WIGGLY VERTICAL LINE +2E3F ; AL # Po CAPITULUM +2E40 ; BA # Pd DOUBLE HYPHEN +2E41 ; BA # Po REVERSED COMMA +2E42 ; OP # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E43..2E4A ; BA # Po [8] DASH WITH LEFT UPTURN..DOTTED SOLIDUS +2E4B ; AL # Po TRIPLE DAGGER +2E4C ; BA # Po MEDIEVAL COMMA +2E4D ; AL # Po PARAGRAPHUS MARK +2E4E..2E4F ; BA # Po [2] PUNCTUS ELEVATUS MARK..CORNISH VERSE DIVIDER +2E50..2E51 ; AL # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E52 ; AL # Po TIRONIAN SIGN CAPITAL ET +2E53..2E54 ; EX # Po [2] MEDIEVAL EXCLAMATION MARK..MEDIEVAL QUESTION MARK +2E55 ; OP # Ps LEFT SQUARE BRACKET WITH STROKE +2E56 ; CL # Pe RIGHT SQUARE BRACKET WITH STROKE +2E57 ; OP # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E58 ; CL # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; OP # Ps TOP HALF LEFT PARENTHESIS +2E5A ; CL # Pe TOP HALF RIGHT PARENTHESIS +2E5B ; OP # Ps BOTTOM HALF LEFT PARENTHESIS +2E5C ; CL # Pe BOTTOM HALF RIGHT PARENTHESIS +2E5D ; BA # Pd OBLIQUE HYPHEN +2E80..2E99 ; ID # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; ID # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; ID # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; ID # So [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3000 ; BA # Zs IDEOGRAPHIC SPACE +3001..3002 ; CL # Po [2] IDEOGRAPHIC COMMA..IDEOGRAPHIC FULL STOP +3003 ; ID # Po DITTO MARK +3004 ; ID # So JAPANESE INDUSTRIAL STANDARD SYMBOL +3005 ; NS # Lm IDEOGRAPHIC ITERATION MARK +3006 ; ID # Lo IDEOGRAPHIC CLOSING MARK +3007 ; ID # Nl IDEOGRAPHIC NUMBER ZERO +3008 ; OP # Ps LEFT ANGLE BRACKET +3009 ; CL # Pe RIGHT ANGLE BRACKET +300A ; OP # Ps LEFT DOUBLE ANGLE BRACKET +300B ; CL # Pe RIGHT DOUBLE ANGLE BRACKET +300C ; OP # Ps LEFT CORNER BRACKET +300D ; CL # Pe RIGHT CORNER BRACKET +300E ; OP # Ps LEFT WHITE CORNER BRACKET +300F ; CL # Pe RIGHT WHITE CORNER BRACKET +3010 ; OP # Ps LEFT BLACK LENTICULAR BRACKET +3011 ; CL # Pe RIGHT BLACK LENTICULAR BRACKET +3012..3013 ; ID # So [2] POSTAL MARK..GETA MARK +3014 ; OP # Ps LEFT TORTOISE SHELL BRACKET +3015 ; CL # Pe RIGHT TORTOISE SHELL BRACKET +3016 ; OP # Ps LEFT WHITE LENTICULAR BRACKET +3017 ; CL # Pe RIGHT WHITE LENTICULAR BRACKET +3018 ; OP # Ps LEFT WHITE TORTOISE SHELL BRACKET +3019 ; CL # Pe RIGHT WHITE TORTOISE SHELL BRACKET +301A ; OP # Ps LEFT WHITE SQUARE BRACKET +301B ; CL # Pe RIGHT WHITE SQUARE BRACKET +301C ; NS # Pd WAVE DASH +301D ; OP # Ps REVERSED DOUBLE PRIME QUOTATION MARK +301E..301F ; CL # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +3020 ; ID # So POSTAL MARK FACE +3021..3029 ; ID # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +302A..302D ; CM # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E..302F ; CM # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +3030 ; ID # Pd WAVY DASH +3031..3034 ; ID # Lm [4] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HALF +3035 ; CM # Lm VERTICAL KANA REPEAT MARK LOWER HALF +3036..3037 ; ID # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +3038..303A ; ID # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +303B ; NS # Lm VERTICAL IDEOGRAPHIC ITERATION MARK +303C ; NS # Lo MASU MARK +303D ; ID # Po PART ALTERNATION MARK +303E..303F ; ID # So [2] IDEOGRAPHIC VARIATION INDICATOR..IDEOGRAPHIC HALF FILL SPACE +3041 ; CJ # Lo HIRAGANA LETTER SMALL A +3042 ; ID # Lo HIRAGANA LETTER A +3043 ; CJ # Lo HIRAGANA LETTER SMALL I +3044 ; ID # Lo HIRAGANA LETTER I +3045 ; CJ # Lo HIRAGANA LETTER SMALL U +3046 ; ID # Lo HIRAGANA LETTER U +3047 ; CJ # Lo HIRAGANA LETTER SMALL E +3048 ; ID # Lo HIRAGANA LETTER E +3049 ; CJ # Lo HIRAGANA LETTER SMALL O +304A..3062 ; ID # Lo [25] HIRAGANA LETTER O..HIRAGANA LETTER DI +3063 ; CJ # Lo HIRAGANA LETTER SMALL TU +3064..3082 ; ID # Lo [31] HIRAGANA LETTER TU..HIRAGANA LETTER MO +3083 ; CJ # Lo HIRAGANA LETTER SMALL YA +3084 ; ID # Lo HIRAGANA LETTER YA +3085 ; CJ # Lo HIRAGANA LETTER SMALL YU +3086 ; ID # Lo HIRAGANA LETTER YU +3087 ; CJ # Lo HIRAGANA LETTER SMALL YO +3088..308D ; ID # Lo [6] HIRAGANA LETTER YO..HIRAGANA LETTER RO +308E ; CJ # Lo HIRAGANA LETTER SMALL WA +308F..3094 ; ID # Lo [6] HIRAGANA LETTER WA..HIRAGANA LETTER VU +3095..3096 ; CJ # Lo [2] HIRAGANA LETTER SMALL KA..HIRAGANA LETTER SMALL KE +3099..309A ; CM # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309B..309C ; NS # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309D..309E ; NS # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +309F ; ID # Lo HIRAGANA DIGRAPH YORI +30A0 ; NS # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1 ; CJ # Lo KATAKANA LETTER SMALL A +30A2 ; ID # Lo KATAKANA LETTER A +30A3 ; CJ # Lo KATAKANA LETTER SMALL I +30A4 ; ID # Lo KATAKANA LETTER I +30A5 ; CJ # Lo KATAKANA LETTER SMALL U +30A6 ; ID # Lo KATAKANA LETTER U +30A7 ; CJ # Lo KATAKANA LETTER SMALL E +30A8 ; ID # Lo KATAKANA LETTER E +30A9 ; CJ # Lo KATAKANA LETTER SMALL O +30AA..30C2 ; ID # Lo [25] KATAKANA LETTER O..KATAKANA LETTER DI +30C3 ; CJ # Lo KATAKANA LETTER SMALL TU +30C4..30E2 ; ID # Lo [31] KATAKANA LETTER TU..KATAKANA LETTER MO +30E3 ; CJ # Lo KATAKANA LETTER SMALL YA +30E4 ; ID # Lo KATAKANA LETTER YA +30E5 ; CJ # Lo KATAKANA LETTER SMALL YU +30E6 ; ID # Lo KATAKANA LETTER YU +30E7 ; CJ # Lo KATAKANA LETTER SMALL YO +30E8..30ED ; ID # Lo [6] KATAKANA LETTER YO..KATAKANA LETTER RO +30EE ; CJ # Lo KATAKANA LETTER SMALL WA +30EF..30F4 ; ID # Lo [6] KATAKANA LETTER WA..KATAKANA LETTER VU +30F5..30F6 ; CJ # Lo [2] KATAKANA LETTER SMALL KA..KATAKANA LETTER SMALL KE +30F7..30FA ; ID # Lo [4] KATAKANA LETTER VA..KATAKANA LETTER VO +30FB ; NS # Po KATAKANA MIDDLE DOT +30FC ; CJ # Lm KATAKANA-HIRAGANA PROLONGED SOUND MARK +30FD..30FE ; NS # Lm [2] KATAKANA ITERATION MARK..KATAKANA VOICED ITERATION MARK +30FF ; ID # Lo KATAKANA DIGRAPH KOTO +3105..312F ; ID # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; ID # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +3190..3191 ; ID # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3192..3195 ; ID # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3196..319F ; ID # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31A0..31BF ; ID # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31C0..31E3 ; ID # So [36] CJK STROKE T..CJK STROKE Q +31EF ; ID # So IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +31F0..31FF ; CJ # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3200..321E ; ID # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +3220..3229 ; ID # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +322A..3247 ; ID # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3248..324F ; AI # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3250 ; ID # So PARTNERSHIP SIGN +3251..325F ; ID # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3260..327F ; ID # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +3280..3289 ; ID # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +328A..32B0 ; ID # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32B1..32BF ; ID # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +32C0..32FF ; ID # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA +3300..33FF ; ID # So [256] SQUARE APAATO..SQUARE GAL +3400..4DBF ; ID # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4DC0..4DFF ; AL # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +4E00..9FFF ; ID # Lo [20992] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFF +A000..A014 ; ID # Lo [21] YI SYLLABLE IT..YI SYLLABLE E +A015 ; NS # Lm YI SYLLABLE WU +A016..A48C ; ID # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A490..A4C6 ; ID # So [55] YI RADICAL QOT..YI RADICAL KE +A4D0..A4F7 ; AL # Lo [40] LISU LETTER BA..LISU LETTER OE +A4F8..A4FD ; AL # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A4FE..A4FF ; BA # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A500..A60B ; AL # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A60C ; AL # Lm VAI SYLLABLE LENGTHENER +A60D ; BA # Po VAI COMMA +A60E ; EX # Po VAI FULL STOP +A60F ; BA # Po VAI QUESTION MARK +A610..A61F ; AL # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A620..A629 ; NU # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE +A62A..A62B ; AL # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A640..A66D ; AL # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A66E ; AL # Lo CYRILLIC LETTER MULTIOCULAR O +A66F ; CM # Mn COMBINING CYRILLIC VZMET +A670..A672 ; CM # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN +A673 ; AL # Po SLAVONIC ASTERISK +A674..A67D ; CM # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A67E ; AL # Po CYRILLIC KAVYKA +A67F ; AL # Lm CYRILLIC PAYEROK +A680..A69B ; AL # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O +A69C..A69D ; AL # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A69E..A69F ; CM # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6A0..A6E5 ; AL # Lo [70] BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; AL # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0..A6F1 ; CM # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A6F2 ; AL # Po BAMUM NJAEMLI +A6F3..A6F7 ; BA # Po [5] BAMUM FULL STOP..BAMUM QUESTION MARK +A700..A716 ; AL # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717..A71F ; AL # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720..A721 ; AL # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A722..A76F ; AL # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON +A770 ; AL # Lm MODIFIER LETTER US +A771..A787 ; AL # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T +A788 ; AL # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789..A78A ; AL # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A78B..A78E ; AL # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F ; AL # Lo LATIN LETTER SINOLOGICAL DOT +A790..A7CA ; AL # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; AL # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; AL # Ll LATIN SMALL LETTER DOUBLE THORN +A7D5..A7D9 ; AL # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S +A7F2..A7F4 ; AL # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F5..A7F6 ; AL # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H +A7F7 ; AL # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7F8..A7F9 ; AL # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA ; AL # Ll LATIN LETTER SMALL CAPITAL TURNED M +A7FB..A7FF ; AL # Lo [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M +A800..A801 ; AL # Lo [2] SYLOTI NAGRI LETTER A..SYLOTI NAGRI LETTER I +A802 ; CM # Mn SYLOTI NAGRI SIGN DVISVARA +A803..A805 ; AL # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A806 ; CM # Mn SYLOTI NAGRI SIGN HASANTA +A807..A80A ; AL # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80B ; CM # Mn SYLOTI NAGRI SIGN ANUSVARA +A80C..A822 ; AL # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A823..A824 ; CM # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A825..A826 ; CM # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A827 ; CM # Mc SYLOTI NAGRI VOWEL SIGN OO +A828..A82B ; AL # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C ; CM # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA +A830..A835 ; AL # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +A836..A837 ; AL # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A838 ; PO # Sc NORTH INDIC RUPEE MARK +A839 ; AL # So NORTH INDIC QUANTITY MARK +A840..A873 ; AL # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A874..A875 ; BB # Po [2] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA DOUBLE HEAD MARK +A876..A877 ; EX # Po [2] PHAGS-PA MARK SHAD..PHAGS-PA MARK DOUBLE SHAD +A880..A881 ; CM # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A882..A8B3 ; AL # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8B4..A8C3 ; CM # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A8C4..A8C5 ; CM # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8CE..A8CF ; BA # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0..A8D9 ; NU # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8E0..A8F1 ; CM # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8F2..A8F7 ; AL # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8F8..A8FA ; AL # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB ; AL # Lo DEVANAGARI HEADSTROKE +A8FC ; BB # Po DEVANAGARI SIGN SIDDHAM +A8FD..A8FE ; AL # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A8FF ; CM # Mn DEVANAGARI VOWEL SIGN AY +A900..A909 ; NU # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A90A..A925 ; AL # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A926..A92D ; CM # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A92E..A92F ; BA # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930..A946 ; AL # Lo [23] REJANG LETTER KA..REJANG LETTER A +A947..A951 ; CM # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A952..A953 ; CM # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A95F ; AL # Po REJANG SECTION MARK +A960..A97C ; JL # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A980..A982 ; CM # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A983 ; CM # Mc JAVANESE SIGN WIGNYAN +A984..A9B2 ; AK # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9B3 ; CM # Mn JAVANESE SIGN CECAK TELU +A9B4..A9B5 ; CM # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9B6..A9B9 ; CM # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BA..A9BB ; CM # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BC..A9BD ; CM # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9BE..A9BF ; CM # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA +A9C0 ; VI # Mc JAVANESE PANGKON +A9C1..A9C6 ; ID # Po [6] JAVANESE LEFT RERENGGAN..JAVANESE PADA WINDU +A9C7..A9C9 ; BA # Po [3] JAVANESE PADA PANGKAT..JAVANESE PADA LUNGSI +A9CA..A9CD ; ID # Po [4] JAVANESE PADA ADEG..JAVANESE TURNED PADA PISELEH +A9CF ; BA # Lm JAVANESE PANGRANGKEP +A9D0..A9D9 ; ID # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9DE..A9DF ; ID # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +A9E0..A9E4 ; SA # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E5 ; SA # Mn MYANMAR SIGN SHAN SAW +A9E6 ; SA # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION +A9E7..A9EF ; SA # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9F0..A9F9 ; NU # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +A9FA..A9FE ; SA # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; AS # Lo [41] CHAM LETTER A..CHAM LETTER HA +AA29..AA2E ; CM # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA2F..AA30 ; CM # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA31..AA32 ; CM # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA33..AA34 ; CM # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA35..AA36 ; CM # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA40..AA42 ; BA # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA43 ; CM # Mn CHAM CONSONANT SIGN FINAL NG +AA44..AA4B ; BA # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA4C ; CM # Mn CHAM CONSONANT SIGN FINAL M +AA4D ; CM # Mc CHAM CONSONANT SIGN FINAL H +AA50..AA59 ; ID # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5C ; ID # Po CHAM PUNCTUATION SPIRAL +AA5D..AA5F ; BA # Po [3] CHAM PUNCTUATION DANDA..CHAM PUNCTUATION TRIPLE DANDA +AA60..AA6F ; SA # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA70 ; SA # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AA71..AA76 ; SA # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA77..AA79 ; SA # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +AA7A ; SA # Lo MYANMAR LETTER AITON RA +AA7B ; SA # Mc MYANMAR SIGN PAO KAREN TONE +AA7C ; SA # Mn MYANMAR SIGN TAI LAING TONE-2 +AA7D ; SA # Mc MYANMAR SIGN TAI LAING TONE-5 +AA7E..AA7F ; SA # Lo [2] MYANMAR LETTER SHWE PALAUNG CHA..MYANMAR LETTER SHWE PALAUNG SHA +AA80..AAAF ; SA # Lo [48] TAI VIET LETTER LOW KO..TAI VIET LETTER HIGH O +AAB0 ; SA # Mn TAI VIET MAI KANG +AAB1 ; SA # Lo TAI VIET VOWEL AA +AAB2..AAB4 ; SA # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB5..AAB6 ; SA # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB7..AAB8 ; SA # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AAB9..AABD ; SA # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AABE..AABF ; SA # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC0 ; SA # Lo TAI VIET TONE MAI NUENG +AAC1 ; SA # Mn TAI VIET TONE MAI THO +AAC2 ; SA # Lo TAI VIET TONE MAI SONG +AADB..AADC ; SA # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AADD ; SA # Lm TAI VIET SYMBOL SAM +AADE..AADF ; SA # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAE0..AAEA ; AL # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAEB ; CM # Mc MEETEI MAYEK VOWEL SIGN II +AAEC..AAED ; CM # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAEE..AAEF ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF0..AAF1 ; BA # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +AAF2 ; AL # Lo MEETEI MAYEK ANJI +AAF3..AAF4 ; AL # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AAF5 ; CM # Mc MEETEI MAYEK VOWEL SIGN VISARGA +AAF6 ; CM # Mn MEETEI MAYEK VIRAMA +AB01..AB06 ; AL # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; AL # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; AL # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; AL # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; AL # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB30..AB5A ; AL # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB5B ; AL # Sk MODIFIER BREVE WITH INVERTED BREVE +AB5C..AB5F ; AL # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB60..AB68 ; AL # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69 ; AL # Lm MODIFIER LETTER SMALL TURNED W +AB6A..AB6B ; AL # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +AB70..ABBF ; AL # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +ABC0..ABE2 ; AL # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +ABE3..ABE4 ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE5 ; CM # Mn MEETEI MAYEK VOWEL SIGN ANAP +ABE6..ABE7 ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE8 ; CM # Mn MEETEI MAYEK VOWEL SIGN UNAP +ABE9..ABEA ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEB ; BA # Po MEETEI MAYEK CHEIKHEI +ABEC ; CM # Mc MEETEI MAYEK LUM IYEK +ABED ; CM # Mn MEETEI MAYEK APUN IYEK +ABF0..ABF9 ; NU # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +AC00 ; H2 # Lo HANGUL SYLLABLE GA +AC01..AC1B ; H3 # Lo [27] HANGUL SYLLABLE GAG..HANGUL SYLLABLE GAH +AC1C ; H2 # Lo HANGUL SYLLABLE GAE +AC1D..AC37 ; H3 # Lo [27] HANGUL SYLLABLE GAEG..HANGUL SYLLABLE GAEH +AC38 ; H2 # Lo HANGUL SYLLABLE GYA +AC39..AC53 ; H3 # Lo [27] HANGUL SYLLABLE GYAG..HANGUL SYLLABLE GYAH +AC54 ; H2 # Lo HANGUL SYLLABLE GYAE +AC55..AC6F ; H3 # Lo [27] HANGUL SYLLABLE GYAEG..HANGUL SYLLABLE GYAEH +AC70 ; H2 # Lo HANGUL SYLLABLE GEO +AC71..AC8B ; H3 # Lo [27] HANGUL SYLLABLE GEOG..HANGUL SYLLABLE GEOH +AC8C ; H2 # Lo HANGUL SYLLABLE GE +AC8D..ACA7 ; H3 # Lo [27] HANGUL SYLLABLE GEG..HANGUL SYLLABLE GEH +ACA8 ; H2 # Lo HANGUL SYLLABLE GYEO +ACA9..ACC3 ; H3 # Lo [27] HANGUL SYLLABLE GYEOG..HANGUL SYLLABLE GYEOH +ACC4 ; H2 # Lo HANGUL SYLLABLE GYE +ACC5..ACDF ; H3 # Lo [27] HANGUL SYLLABLE GYEG..HANGUL SYLLABLE GYEH +ACE0 ; H2 # Lo HANGUL SYLLABLE GO +ACE1..ACFB ; H3 # Lo [27] HANGUL SYLLABLE GOG..HANGUL SYLLABLE GOH +ACFC ; H2 # Lo HANGUL SYLLABLE GWA +ACFD..AD17 ; H3 # Lo [27] HANGUL SYLLABLE GWAG..HANGUL SYLLABLE GWAH +AD18 ; H2 # Lo HANGUL SYLLABLE GWAE +AD19..AD33 ; H3 # Lo [27] HANGUL SYLLABLE GWAEG..HANGUL SYLLABLE GWAEH +AD34 ; H2 # Lo HANGUL SYLLABLE GOE +AD35..AD4F ; H3 # Lo [27] HANGUL SYLLABLE GOEG..HANGUL SYLLABLE GOEH +AD50 ; H2 # Lo HANGUL SYLLABLE GYO +AD51..AD6B ; H3 # Lo [27] HANGUL SYLLABLE GYOG..HANGUL SYLLABLE GYOH +AD6C ; H2 # Lo HANGUL SYLLABLE GU +AD6D..AD87 ; H3 # Lo [27] HANGUL SYLLABLE GUG..HANGUL SYLLABLE GUH +AD88 ; H2 # Lo HANGUL SYLLABLE GWEO +AD89..ADA3 ; H3 # Lo [27] HANGUL SYLLABLE GWEOG..HANGUL SYLLABLE GWEOH +ADA4 ; H2 # Lo HANGUL SYLLABLE GWE +ADA5..ADBF ; H3 # Lo [27] HANGUL SYLLABLE GWEG..HANGUL SYLLABLE GWEH +ADC0 ; H2 # Lo HANGUL SYLLABLE GWI +ADC1..ADDB ; H3 # Lo [27] HANGUL SYLLABLE GWIG..HANGUL SYLLABLE GWIH +ADDC ; H2 # Lo HANGUL SYLLABLE GYU +ADDD..ADF7 ; H3 # Lo [27] HANGUL SYLLABLE GYUG..HANGUL SYLLABLE GYUH +ADF8 ; H2 # Lo HANGUL SYLLABLE GEU +ADF9..AE13 ; H3 # Lo [27] HANGUL SYLLABLE GEUG..HANGUL SYLLABLE GEUH +AE14 ; H2 # Lo HANGUL SYLLABLE GYI +AE15..AE2F ; H3 # Lo [27] HANGUL SYLLABLE GYIG..HANGUL SYLLABLE GYIH +AE30 ; H2 # Lo HANGUL SYLLABLE GI +AE31..AE4B ; H3 # Lo [27] HANGUL SYLLABLE GIG..HANGUL SYLLABLE GIH +AE4C ; H2 # Lo HANGUL SYLLABLE GGA +AE4D..AE67 ; H3 # Lo [27] HANGUL SYLLABLE GGAG..HANGUL SYLLABLE GGAH +AE68 ; H2 # Lo HANGUL SYLLABLE GGAE +AE69..AE83 ; H3 # Lo [27] HANGUL SYLLABLE GGAEG..HANGUL SYLLABLE GGAEH +AE84 ; H2 # Lo HANGUL SYLLABLE GGYA +AE85..AE9F ; H3 # Lo [27] HANGUL SYLLABLE GGYAG..HANGUL SYLLABLE GGYAH +AEA0 ; H2 # Lo HANGUL SYLLABLE GGYAE +AEA1..AEBB ; H3 # Lo [27] HANGUL SYLLABLE GGYAEG..HANGUL SYLLABLE GGYAEH +AEBC ; H2 # Lo HANGUL SYLLABLE GGEO +AEBD..AED7 ; H3 # Lo [27] HANGUL SYLLABLE GGEOG..HANGUL SYLLABLE GGEOH +AED8 ; H2 # Lo HANGUL SYLLABLE GGE +AED9..AEF3 ; H3 # Lo [27] HANGUL SYLLABLE GGEG..HANGUL SYLLABLE GGEH +AEF4 ; H2 # Lo HANGUL SYLLABLE GGYEO +AEF5..AF0F ; H3 # Lo [27] HANGUL SYLLABLE GGYEOG..HANGUL SYLLABLE GGYEOH +AF10 ; H2 # Lo HANGUL SYLLABLE GGYE +AF11..AF2B ; H3 # Lo [27] HANGUL SYLLABLE GGYEG..HANGUL SYLLABLE GGYEH +AF2C ; H2 # Lo HANGUL SYLLABLE GGO +AF2D..AF47 ; H3 # Lo [27] HANGUL SYLLABLE GGOG..HANGUL SYLLABLE GGOH +AF48 ; H2 # Lo HANGUL SYLLABLE GGWA +AF49..AF63 ; H3 # Lo [27] HANGUL SYLLABLE GGWAG..HANGUL SYLLABLE GGWAH +AF64 ; H2 # Lo HANGUL SYLLABLE GGWAE +AF65..AF7F ; H3 # Lo [27] HANGUL SYLLABLE GGWAEG..HANGUL SYLLABLE GGWAEH +AF80 ; H2 # Lo HANGUL SYLLABLE GGOE +AF81..AF9B ; H3 # Lo [27] HANGUL SYLLABLE GGOEG..HANGUL SYLLABLE GGOEH +AF9C ; H2 # Lo HANGUL SYLLABLE GGYO +AF9D..AFB7 ; H3 # Lo [27] HANGUL SYLLABLE GGYOG..HANGUL SYLLABLE GGYOH +AFB8 ; H2 # Lo HANGUL SYLLABLE GGU +AFB9..AFD3 ; H3 # Lo [27] HANGUL SYLLABLE GGUG..HANGUL SYLLABLE GGUH +AFD4 ; H2 # Lo HANGUL SYLLABLE GGWEO +AFD5..AFEF ; H3 # Lo [27] HANGUL SYLLABLE GGWEOG..HANGUL SYLLABLE GGWEOH +AFF0 ; H2 # Lo HANGUL SYLLABLE GGWE +AFF1..B00B ; H3 # Lo [27] HANGUL SYLLABLE GGWEG..HANGUL SYLLABLE GGWEH +B00C ; H2 # Lo HANGUL SYLLABLE GGWI +B00D..B027 ; H3 # Lo [27] HANGUL SYLLABLE GGWIG..HANGUL SYLLABLE GGWIH +B028 ; H2 # Lo HANGUL SYLLABLE GGYU +B029..B043 ; H3 # Lo [27] HANGUL SYLLABLE GGYUG..HANGUL SYLLABLE GGYUH +B044 ; H2 # Lo HANGUL SYLLABLE GGEU +B045..B05F ; H3 # Lo [27] HANGUL SYLLABLE GGEUG..HANGUL SYLLABLE GGEUH +B060 ; H2 # Lo HANGUL SYLLABLE GGYI +B061..B07B ; H3 # Lo [27] HANGUL SYLLABLE GGYIG..HANGUL SYLLABLE GGYIH +B07C ; H2 # Lo HANGUL SYLLABLE GGI +B07D..B097 ; H3 # Lo [27] HANGUL SYLLABLE GGIG..HANGUL SYLLABLE GGIH +B098 ; H2 # Lo HANGUL SYLLABLE NA +B099..B0B3 ; H3 # Lo [27] HANGUL SYLLABLE NAG..HANGUL SYLLABLE NAH +B0B4 ; H2 # Lo HANGUL SYLLABLE NAE +B0B5..B0CF ; H3 # Lo [27] HANGUL SYLLABLE NAEG..HANGUL SYLLABLE NAEH +B0D0 ; H2 # Lo HANGUL SYLLABLE NYA +B0D1..B0EB ; H3 # Lo [27] HANGUL SYLLABLE NYAG..HANGUL SYLLABLE NYAH +B0EC ; H2 # Lo HANGUL SYLLABLE NYAE +B0ED..B107 ; H3 # Lo [27] HANGUL SYLLABLE NYAEG..HANGUL SYLLABLE NYAEH +B108 ; H2 # Lo HANGUL SYLLABLE NEO +B109..B123 ; H3 # Lo [27] HANGUL SYLLABLE NEOG..HANGUL SYLLABLE NEOH +B124 ; H2 # Lo HANGUL SYLLABLE NE +B125..B13F ; H3 # Lo [27] HANGUL SYLLABLE NEG..HANGUL SYLLABLE NEH +B140 ; H2 # Lo HANGUL SYLLABLE NYEO +B141..B15B ; H3 # Lo [27] HANGUL SYLLABLE NYEOG..HANGUL SYLLABLE NYEOH +B15C ; H2 # Lo HANGUL SYLLABLE NYE +B15D..B177 ; H3 # Lo [27] HANGUL SYLLABLE NYEG..HANGUL SYLLABLE NYEH +B178 ; H2 # Lo HANGUL SYLLABLE NO +B179..B193 ; H3 # Lo [27] HANGUL SYLLABLE NOG..HANGUL SYLLABLE NOH +B194 ; H2 # Lo HANGUL SYLLABLE NWA +B195..B1AF ; H3 # Lo [27] HANGUL SYLLABLE NWAG..HANGUL SYLLABLE NWAH +B1B0 ; H2 # Lo HANGUL SYLLABLE NWAE +B1B1..B1CB ; H3 # Lo [27] HANGUL SYLLABLE NWAEG..HANGUL SYLLABLE NWAEH +B1CC ; H2 # Lo HANGUL SYLLABLE NOE +B1CD..B1E7 ; H3 # Lo [27] HANGUL SYLLABLE NOEG..HANGUL SYLLABLE NOEH +B1E8 ; H2 # Lo HANGUL SYLLABLE NYO +B1E9..B203 ; H3 # Lo [27] HANGUL SYLLABLE NYOG..HANGUL SYLLABLE NYOH +B204 ; H2 # Lo HANGUL SYLLABLE NU +B205..B21F ; H3 # Lo [27] HANGUL SYLLABLE NUG..HANGUL SYLLABLE NUH +B220 ; H2 # Lo HANGUL SYLLABLE NWEO +B221..B23B ; H3 # Lo [27] HANGUL SYLLABLE NWEOG..HANGUL SYLLABLE NWEOH +B23C ; H2 # Lo HANGUL SYLLABLE NWE +B23D..B257 ; H3 # Lo [27] HANGUL SYLLABLE NWEG..HANGUL SYLLABLE NWEH +B258 ; H2 # Lo HANGUL SYLLABLE NWI +B259..B273 ; H3 # Lo [27] HANGUL SYLLABLE NWIG..HANGUL SYLLABLE NWIH +B274 ; H2 # Lo HANGUL SYLLABLE NYU +B275..B28F ; H3 # Lo [27] HANGUL SYLLABLE NYUG..HANGUL SYLLABLE NYUH +B290 ; H2 # Lo HANGUL SYLLABLE NEU +B291..B2AB ; H3 # Lo [27] HANGUL SYLLABLE NEUG..HANGUL SYLLABLE NEUH +B2AC ; H2 # Lo HANGUL SYLLABLE NYI +B2AD..B2C7 ; H3 # Lo [27] HANGUL SYLLABLE NYIG..HANGUL SYLLABLE NYIH +B2C8 ; H2 # Lo HANGUL SYLLABLE NI +B2C9..B2E3 ; H3 # Lo [27] HANGUL SYLLABLE NIG..HANGUL SYLLABLE NIH +B2E4 ; H2 # Lo HANGUL SYLLABLE DA +B2E5..B2FF ; H3 # Lo [27] HANGUL SYLLABLE DAG..HANGUL SYLLABLE DAH +B300 ; H2 # Lo HANGUL SYLLABLE DAE +B301..B31B ; H3 # Lo [27] HANGUL SYLLABLE DAEG..HANGUL SYLLABLE DAEH +B31C ; H2 # Lo HANGUL SYLLABLE DYA +B31D..B337 ; H3 # Lo [27] HANGUL SYLLABLE DYAG..HANGUL SYLLABLE DYAH +B338 ; H2 # Lo HANGUL SYLLABLE DYAE +B339..B353 ; H3 # Lo [27] HANGUL SYLLABLE DYAEG..HANGUL SYLLABLE DYAEH +B354 ; H2 # Lo HANGUL SYLLABLE DEO +B355..B36F ; H3 # Lo [27] HANGUL SYLLABLE DEOG..HANGUL SYLLABLE DEOH +B370 ; H2 # Lo HANGUL SYLLABLE DE +B371..B38B ; H3 # Lo [27] HANGUL SYLLABLE DEG..HANGUL SYLLABLE DEH +B38C ; H2 # Lo HANGUL SYLLABLE DYEO +B38D..B3A7 ; H3 # Lo [27] HANGUL SYLLABLE DYEOG..HANGUL SYLLABLE DYEOH +B3A8 ; H2 # Lo HANGUL SYLLABLE DYE +B3A9..B3C3 ; H3 # Lo [27] HANGUL SYLLABLE DYEG..HANGUL SYLLABLE DYEH +B3C4 ; H2 # Lo HANGUL SYLLABLE DO +B3C5..B3DF ; H3 # Lo [27] HANGUL SYLLABLE DOG..HANGUL SYLLABLE DOH +B3E0 ; H2 # Lo HANGUL SYLLABLE DWA +B3E1..B3FB ; H3 # Lo [27] HANGUL SYLLABLE DWAG..HANGUL SYLLABLE DWAH +B3FC ; H2 # Lo HANGUL SYLLABLE DWAE +B3FD..B417 ; H3 # Lo [27] HANGUL SYLLABLE DWAEG..HANGUL SYLLABLE DWAEH +B418 ; H2 # Lo HANGUL SYLLABLE DOE +B419..B433 ; H3 # Lo [27] HANGUL SYLLABLE DOEG..HANGUL SYLLABLE DOEH +B434 ; H2 # Lo HANGUL SYLLABLE DYO +B435..B44F ; H3 # Lo [27] HANGUL SYLLABLE DYOG..HANGUL SYLLABLE DYOH +B450 ; H2 # Lo HANGUL SYLLABLE DU +B451..B46B ; H3 # Lo [27] HANGUL SYLLABLE DUG..HANGUL SYLLABLE DUH +B46C ; H2 # Lo HANGUL SYLLABLE DWEO +B46D..B487 ; H3 # Lo [27] HANGUL SYLLABLE DWEOG..HANGUL SYLLABLE DWEOH +B488 ; H2 # Lo HANGUL SYLLABLE DWE +B489..B4A3 ; H3 # Lo [27] HANGUL SYLLABLE DWEG..HANGUL SYLLABLE DWEH +B4A4 ; H2 # Lo HANGUL SYLLABLE DWI +B4A5..B4BF ; H3 # Lo [27] HANGUL SYLLABLE DWIG..HANGUL SYLLABLE DWIH +B4C0 ; H2 # Lo HANGUL SYLLABLE DYU +B4C1..B4DB ; H3 # Lo [27] HANGUL SYLLABLE DYUG..HANGUL SYLLABLE DYUH +B4DC ; H2 # Lo HANGUL SYLLABLE DEU +B4DD..B4F7 ; H3 # Lo [27] HANGUL SYLLABLE DEUG..HANGUL SYLLABLE DEUH +B4F8 ; H2 # Lo HANGUL SYLLABLE DYI +B4F9..B513 ; H3 # Lo [27] HANGUL SYLLABLE DYIG..HANGUL SYLLABLE DYIH +B514 ; H2 # Lo HANGUL SYLLABLE DI +B515..B52F ; H3 # Lo [27] HANGUL SYLLABLE DIG..HANGUL SYLLABLE DIH +B530 ; H2 # Lo HANGUL SYLLABLE DDA +B531..B54B ; H3 # Lo [27] HANGUL SYLLABLE DDAG..HANGUL SYLLABLE DDAH +B54C ; H2 # Lo HANGUL SYLLABLE DDAE +B54D..B567 ; H3 # Lo [27] HANGUL SYLLABLE DDAEG..HANGUL SYLLABLE DDAEH +B568 ; H2 # Lo HANGUL SYLLABLE DDYA +B569..B583 ; H3 # Lo [27] HANGUL SYLLABLE DDYAG..HANGUL SYLLABLE DDYAH +B584 ; H2 # Lo HANGUL SYLLABLE DDYAE +B585..B59F ; H3 # Lo [27] HANGUL SYLLABLE DDYAEG..HANGUL SYLLABLE DDYAEH +B5A0 ; H2 # Lo HANGUL SYLLABLE DDEO +B5A1..B5BB ; H3 # Lo [27] HANGUL SYLLABLE DDEOG..HANGUL SYLLABLE DDEOH +B5BC ; H2 # Lo HANGUL SYLLABLE DDE +B5BD..B5D7 ; H3 # Lo [27] HANGUL SYLLABLE DDEG..HANGUL SYLLABLE DDEH +B5D8 ; H2 # Lo HANGUL SYLLABLE DDYEO +B5D9..B5F3 ; H3 # Lo [27] HANGUL SYLLABLE DDYEOG..HANGUL SYLLABLE DDYEOH +B5F4 ; H2 # Lo HANGUL SYLLABLE DDYE +B5F5..B60F ; H3 # Lo [27] HANGUL SYLLABLE DDYEG..HANGUL SYLLABLE DDYEH +B610 ; H2 # Lo HANGUL SYLLABLE DDO +B611..B62B ; H3 # Lo [27] HANGUL SYLLABLE DDOG..HANGUL SYLLABLE DDOH +B62C ; H2 # Lo HANGUL SYLLABLE DDWA +B62D..B647 ; H3 # Lo [27] HANGUL SYLLABLE DDWAG..HANGUL SYLLABLE DDWAH +B648 ; H2 # Lo HANGUL SYLLABLE DDWAE +B649..B663 ; H3 # Lo [27] HANGUL SYLLABLE DDWAEG..HANGUL SYLLABLE DDWAEH +B664 ; H2 # Lo HANGUL SYLLABLE DDOE +B665..B67F ; H3 # Lo [27] HANGUL SYLLABLE DDOEG..HANGUL SYLLABLE DDOEH +B680 ; H2 # Lo HANGUL SYLLABLE DDYO +B681..B69B ; H3 # Lo [27] HANGUL SYLLABLE DDYOG..HANGUL SYLLABLE DDYOH +B69C ; H2 # Lo HANGUL SYLLABLE DDU +B69D..B6B7 ; H3 # Lo [27] HANGUL SYLLABLE DDUG..HANGUL SYLLABLE DDUH +B6B8 ; H2 # Lo HANGUL SYLLABLE DDWEO +B6B9..B6D3 ; H3 # Lo [27] HANGUL SYLLABLE DDWEOG..HANGUL SYLLABLE DDWEOH +B6D4 ; H2 # Lo HANGUL SYLLABLE DDWE +B6D5..B6EF ; H3 # Lo [27] HANGUL SYLLABLE DDWEG..HANGUL SYLLABLE DDWEH +B6F0 ; H2 # Lo HANGUL SYLLABLE DDWI +B6F1..B70B ; H3 # Lo [27] HANGUL SYLLABLE DDWIG..HANGUL SYLLABLE DDWIH +B70C ; H2 # Lo HANGUL SYLLABLE DDYU +B70D..B727 ; H3 # Lo [27] HANGUL SYLLABLE DDYUG..HANGUL SYLLABLE DDYUH +B728 ; H2 # Lo HANGUL SYLLABLE DDEU +B729..B743 ; H3 # Lo [27] HANGUL SYLLABLE DDEUG..HANGUL SYLLABLE DDEUH +B744 ; H2 # Lo HANGUL SYLLABLE DDYI +B745..B75F ; H3 # Lo [27] HANGUL SYLLABLE DDYIG..HANGUL SYLLABLE DDYIH +B760 ; H2 # Lo HANGUL SYLLABLE DDI +B761..B77B ; H3 # Lo [27] HANGUL SYLLABLE DDIG..HANGUL SYLLABLE DDIH +B77C ; H2 # Lo HANGUL SYLLABLE RA +B77D..B797 ; H3 # Lo [27] HANGUL SYLLABLE RAG..HANGUL SYLLABLE RAH +B798 ; H2 # Lo HANGUL SYLLABLE RAE +B799..B7B3 ; H3 # Lo [27] HANGUL SYLLABLE RAEG..HANGUL SYLLABLE RAEH +B7B4 ; H2 # Lo HANGUL SYLLABLE RYA +B7B5..B7CF ; H3 # Lo [27] HANGUL SYLLABLE RYAG..HANGUL SYLLABLE RYAH +B7D0 ; H2 # Lo HANGUL SYLLABLE RYAE +B7D1..B7EB ; H3 # Lo [27] HANGUL SYLLABLE RYAEG..HANGUL SYLLABLE RYAEH +B7EC ; H2 # Lo HANGUL SYLLABLE REO +B7ED..B807 ; H3 # Lo [27] HANGUL SYLLABLE REOG..HANGUL SYLLABLE REOH +B808 ; H2 # Lo HANGUL SYLLABLE RE +B809..B823 ; H3 # Lo [27] HANGUL SYLLABLE REG..HANGUL SYLLABLE REH +B824 ; H2 # Lo HANGUL SYLLABLE RYEO +B825..B83F ; H3 # Lo [27] HANGUL SYLLABLE RYEOG..HANGUL SYLLABLE RYEOH +B840 ; H2 # Lo HANGUL SYLLABLE RYE +B841..B85B ; H3 # Lo [27] HANGUL SYLLABLE RYEG..HANGUL SYLLABLE RYEH +B85C ; H2 # Lo HANGUL SYLLABLE RO +B85D..B877 ; H3 # Lo [27] HANGUL SYLLABLE ROG..HANGUL SYLLABLE ROH +B878 ; H2 # Lo HANGUL SYLLABLE RWA +B879..B893 ; H3 # Lo [27] HANGUL SYLLABLE RWAG..HANGUL SYLLABLE RWAH +B894 ; H2 # Lo HANGUL SYLLABLE RWAE +B895..B8AF ; H3 # Lo [27] HANGUL SYLLABLE RWAEG..HANGUL SYLLABLE RWAEH +B8B0 ; H2 # Lo HANGUL SYLLABLE ROE +B8B1..B8CB ; H3 # Lo [27] HANGUL SYLLABLE ROEG..HANGUL SYLLABLE ROEH +B8CC ; H2 # Lo HANGUL SYLLABLE RYO +B8CD..B8E7 ; H3 # Lo [27] HANGUL SYLLABLE RYOG..HANGUL SYLLABLE RYOH +B8E8 ; H2 # Lo HANGUL SYLLABLE RU +B8E9..B903 ; H3 # Lo [27] HANGUL SYLLABLE RUG..HANGUL SYLLABLE RUH +B904 ; H2 # Lo HANGUL SYLLABLE RWEO +B905..B91F ; H3 # Lo [27] HANGUL SYLLABLE RWEOG..HANGUL SYLLABLE RWEOH +B920 ; H2 # Lo HANGUL SYLLABLE RWE +B921..B93B ; H3 # Lo [27] HANGUL SYLLABLE RWEG..HANGUL SYLLABLE RWEH +B93C ; H2 # Lo HANGUL SYLLABLE RWI +B93D..B957 ; H3 # Lo [27] HANGUL SYLLABLE RWIG..HANGUL SYLLABLE RWIH +B958 ; H2 # Lo HANGUL SYLLABLE RYU +B959..B973 ; H3 # Lo [27] HANGUL SYLLABLE RYUG..HANGUL SYLLABLE RYUH +B974 ; H2 # Lo HANGUL SYLLABLE REU +B975..B98F ; H3 # Lo [27] HANGUL SYLLABLE REUG..HANGUL SYLLABLE REUH +B990 ; H2 # Lo HANGUL SYLLABLE RYI +B991..B9AB ; H3 # Lo [27] HANGUL SYLLABLE RYIG..HANGUL SYLLABLE RYIH +B9AC ; H2 # Lo HANGUL SYLLABLE RI +B9AD..B9C7 ; H3 # Lo [27] HANGUL SYLLABLE RIG..HANGUL SYLLABLE RIH +B9C8 ; H2 # Lo HANGUL SYLLABLE MA +B9C9..B9E3 ; H3 # Lo [27] HANGUL SYLLABLE MAG..HANGUL SYLLABLE MAH +B9E4 ; H2 # Lo HANGUL SYLLABLE MAE +B9E5..B9FF ; H3 # Lo [27] HANGUL SYLLABLE MAEG..HANGUL SYLLABLE MAEH +BA00 ; H2 # Lo HANGUL SYLLABLE MYA +BA01..BA1B ; H3 # Lo [27] HANGUL SYLLABLE MYAG..HANGUL SYLLABLE MYAH +BA1C ; H2 # Lo HANGUL SYLLABLE MYAE +BA1D..BA37 ; H3 # Lo [27] HANGUL SYLLABLE MYAEG..HANGUL SYLLABLE MYAEH +BA38 ; H2 # Lo HANGUL SYLLABLE MEO +BA39..BA53 ; H3 # Lo [27] HANGUL SYLLABLE MEOG..HANGUL SYLLABLE MEOH +BA54 ; H2 # Lo HANGUL SYLLABLE ME +BA55..BA6F ; H3 # Lo [27] HANGUL SYLLABLE MEG..HANGUL SYLLABLE MEH +BA70 ; H2 # Lo HANGUL SYLLABLE MYEO +BA71..BA8B ; H3 # Lo [27] HANGUL SYLLABLE MYEOG..HANGUL SYLLABLE MYEOH +BA8C ; H2 # Lo HANGUL SYLLABLE MYE +BA8D..BAA7 ; H3 # Lo [27] HANGUL SYLLABLE MYEG..HANGUL SYLLABLE MYEH +BAA8 ; H2 # Lo HANGUL SYLLABLE MO +BAA9..BAC3 ; H3 # Lo [27] HANGUL SYLLABLE MOG..HANGUL SYLLABLE MOH +BAC4 ; H2 # Lo HANGUL SYLLABLE MWA +BAC5..BADF ; H3 # Lo [27] HANGUL SYLLABLE MWAG..HANGUL SYLLABLE MWAH +BAE0 ; H2 # Lo HANGUL SYLLABLE MWAE +BAE1..BAFB ; H3 # Lo [27] HANGUL SYLLABLE MWAEG..HANGUL SYLLABLE MWAEH +BAFC ; H2 # Lo HANGUL SYLLABLE MOE +BAFD..BB17 ; H3 # Lo [27] HANGUL SYLLABLE MOEG..HANGUL SYLLABLE MOEH +BB18 ; H2 # Lo HANGUL SYLLABLE MYO +BB19..BB33 ; H3 # Lo [27] HANGUL SYLLABLE MYOG..HANGUL SYLLABLE MYOH +BB34 ; H2 # Lo HANGUL SYLLABLE MU +BB35..BB4F ; H3 # Lo [27] HANGUL SYLLABLE MUG..HANGUL SYLLABLE MUH +BB50 ; H2 # Lo HANGUL SYLLABLE MWEO +BB51..BB6B ; H3 # Lo [27] HANGUL SYLLABLE MWEOG..HANGUL SYLLABLE MWEOH +BB6C ; H2 # Lo HANGUL SYLLABLE MWE +BB6D..BB87 ; H3 # Lo [27] HANGUL SYLLABLE MWEG..HANGUL SYLLABLE MWEH +BB88 ; H2 # Lo HANGUL SYLLABLE MWI +BB89..BBA3 ; H3 # Lo [27] HANGUL SYLLABLE MWIG..HANGUL SYLLABLE MWIH +BBA4 ; H2 # Lo HANGUL SYLLABLE MYU +BBA5..BBBF ; H3 # Lo [27] HANGUL SYLLABLE MYUG..HANGUL SYLLABLE MYUH +BBC0 ; H2 # Lo HANGUL SYLLABLE MEU +BBC1..BBDB ; H3 # Lo [27] HANGUL SYLLABLE MEUG..HANGUL SYLLABLE MEUH +BBDC ; H2 # Lo HANGUL SYLLABLE MYI +BBDD..BBF7 ; H3 # Lo [27] HANGUL SYLLABLE MYIG..HANGUL SYLLABLE MYIH +BBF8 ; H2 # Lo HANGUL SYLLABLE MI +BBF9..BC13 ; H3 # Lo [27] HANGUL SYLLABLE MIG..HANGUL SYLLABLE MIH +BC14 ; H2 # Lo HANGUL SYLLABLE BA +BC15..BC2F ; H3 # Lo [27] HANGUL SYLLABLE BAG..HANGUL SYLLABLE BAH +BC30 ; H2 # Lo HANGUL SYLLABLE BAE +BC31..BC4B ; H3 # Lo [27] HANGUL SYLLABLE BAEG..HANGUL SYLLABLE BAEH +BC4C ; H2 # Lo HANGUL SYLLABLE BYA +BC4D..BC67 ; H3 # Lo [27] HANGUL SYLLABLE BYAG..HANGUL SYLLABLE BYAH +BC68 ; H2 # Lo HANGUL SYLLABLE BYAE +BC69..BC83 ; H3 # Lo [27] HANGUL SYLLABLE BYAEG..HANGUL SYLLABLE BYAEH +BC84 ; H2 # Lo HANGUL SYLLABLE BEO +BC85..BC9F ; H3 # Lo [27] HANGUL SYLLABLE BEOG..HANGUL SYLLABLE BEOH +BCA0 ; H2 # Lo HANGUL SYLLABLE BE +BCA1..BCBB ; H3 # Lo [27] HANGUL SYLLABLE BEG..HANGUL SYLLABLE BEH +BCBC ; H2 # Lo HANGUL SYLLABLE BYEO +BCBD..BCD7 ; H3 # Lo [27] HANGUL SYLLABLE BYEOG..HANGUL SYLLABLE BYEOH +BCD8 ; H2 # Lo HANGUL SYLLABLE BYE +BCD9..BCF3 ; H3 # Lo [27] HANGUL SYLLABLE BYEG..HANGUL SYLLABLE BYEH +BCF4 ; H2 # Lo HANGUL SYLLABLE BO +BCF5..BD0F ; H3 # Lo [27] HANGUL SYLLABLE BOG..HANGUL SYLLABLE BOH +BD10 ; H2 # Lo HANGUL SYLLABLE BWA +BD11..BD2B ; H3 # Lo [27] HANGUL SYLLABLE BWAG..HANGUL SYLLABLE BWAH +BD2C ; H2 # Lo HANGUL SYLLABLE BWAE +BD2D..BD47 ; H3 # Lo [27] HANGUL SYLLABLE BWAEG..HANGUL SYLLABLE BWAEH +BD48 ; H2 # Lo HANGUL SYLLABLE BOE +BD49..BD63 ; H3 # Lo [27] HANGUL SYLLABLE BOEG..HANGUL SYLLABLE BOEH +BD64 ; H2 # Lo HANGUL SYLLABLE BYO +BD65..BD7F ; H3 # Lo [27] HANGUL SYLLABLE BYOG..HANGUL SYLLABLE BYOH +BD80 ; H2 # Lo HANGUL SYLLABLE BU +BD81..BD9B ; H3 # Lo [27] HANGUL SYLLABLE BUG..HANGUL SYLLABLE BUH +BD9C ; H2 # Lo HANGUL SYLLABLE BWEO +BD9D..BDB7 ; H3 # Lo [27] HANGUL SYLLABLE BWEOG..HANGUL SYLLABLE BWEOH +BDB8 ; H2 # Lo HANGUL SYLLABLE BWE +BDB9..BDD3 ; H3 # Lo [27] HANGUL SYLLABLE BWEG..HANGUL SYLLABLE BWEH +BDD4 ; H2 # Lo HANGUL SYLLABLE BWI +BDD5..BDEF ; H3 # Lo [27] HANGUL SYLLABLE BWIG..HANGUL SYLLABLE BWIH +BDF0 ; H2 # Lo HANGUL SYLLABLE BYU +BDF1..BE0B ; H3 # Lo [27] HANGUL SYLLABLE BYUG..HANGUL SYLLABLE BYUH +BE0C ; H2 # Lo HANGUL SYLLABLE BEU +BE0D..BE27 ; H3 # Lo [27] HANGUL SYLLABLE BEUG..HANGUL SYLLABLE BEUH +BE28 ; H2 # Lo HANGUL SYLLABLE BYI +BE29..BE43 ; H3 # Lo [27] HANGUL SYLLABLE BYIG..HANGUL SYLLABLE BYIH +BE44 ; H2 # Lo HANGUL SYLLABLE BI +BE45..BE5F ; H3 # Lo [27] HANGUL SYLLABLE BIG..HANGUL SYLLABLE BIH +BE60 ; H2 # Lo HANGUL SYLLABLE BBA +BE61..BE7B ; H3 # Lo [27] HANGUL SYLLABLE BBAG..HANGUL SYLLABLE BBAH +BE7C ; H2 # Lo HANGUL SYLLABLE BBAE +BE7D..BE97 ; H3 # Lo [27] HANGUL SYLLABLE BBAEG..HANGUL SYLLABLE BBAEH +BE98 ; H2 # Lo HANGUL SYLLABLE BBYA +BE99..BEB3 ; H3 # Lo [27] HANGUL SYLLABLE BBYAG..HANGUL SYLLABLE BBYAH +BEB4 ; H2 # Lo HANGUL SYLLABLE BBYAE +BEB5..BECF ; H3 # Lo [27] HANGUL SYLLABLE BBYAEG..HANGUL SYLLABLE BBYAEH +BED0 ; H2 # Lo HANGUL SYLLABLE BBEO +BED1..BEEB ; H3 # Lo [27] HANGUL SYLLABLE BBEOG..HANGUL SYLLABLE BBEOH +BEEC ; H2 # Lo HANGUL SYLLABLE BBE +BEED..BF07 ; H3 # Lo [27] HANGUL SYLLABLE BBEG..HANGUL SYLLABLE BBEH +BF08 ; H2 # Lo HANGUL SYLLABLE BBYEO +BF09..BF23 ; H3 # Lo [27] HANGUL SYLLABLE BBYEOG..HANGUL SYLLABLE BBYEOH +BF24 ; H2 # Lo HANGUL SYLLABLE BBYE +BF25..BF3F ; H3 # Lo [27] HANGUL SYLLABLE BBYEG..HANGUL SYLLABLE BBYEH +BF40 ; H2 # Lo HANGUL SYLLABLE BBO +BF41..BF5B ; H3 # Lo [27] HANGUL SYLLABLE BBOG..HANGUL SYLLABLE BBOH +BF5C ; H2 # Lo HANGUL SYLLABLE BBWA +BF5D..BF77 ; H3 # Lo [27] HANGUL SYLLABLE BBWAG..HANGUL SYLLABLE BBWAH +BF78 ; H2 # Lo HANGUL SYLLABLE BBWAE +BF79..BF93 ; H3 # Lo [27] HANGUL SYLLABLE BBWAEG..HANGUL SYLLABLE BBWAEH +BF94 ; H2 # Lo HANGUL SYLLABLE BBOE +BF95..BFAF ; H3 # Lo [27] HANGUL SYLLABLE BBOEG..HANGUL SYLLABLE BBOEH +BFB0 ; H2 # Lo HANGUL SYLLABLE BBYO +BFB1..BFCB ; H3 # Lo [27] HANGUL SYLLABLE BBYOG..HANGUL SYLLABLE BBYOH +BFCC ; H2 # Lo HANGUL SYLLABLE BBU +BFCD..BFE7 ; H3 # Lo [27] HANGUL SYLLABLE BBUG..HANGUL SYLLABLE BBUH +BFE8 ; H2 # Lo HANGUL SYLLABLE BBWEO +BFE9..C003 ; H3 # Lo [27] HANGUL SYLLABLE BBWEOG..HANGUL SYLLABLE BBWEOH +C004 ; H2 # Lo HANGUL SYLLABLE BBWE +C005..C01F ; H3 # Lo [27] HANGUL SYLLABLE BBWEG..HANGUL SYLLABLE BBWEH +C020 ; H2 # Lo HANGUL SYLLABLE BBWI +C021..C03B ; H3 # Lo [27] HANGUL SYLLABLE BBWIG..HANGUL SYLLABLE BBWIH +C03C ; H2 # Lo HANGUL SYLLABLE BBYU +C03D..C057 ; H3 # Lo [27] HANGUL SYLLABLE BBYUG..HANGUL SYLLABLE BBYUH +C058 ; H2 # Lo HANGUL SYLLABLE BBEU +C059..C073 ; H3 # Lo [27] HANGUL SYLLABLE BBEUG..HANGUL SYLLABLE BBEUH +C074 ; H2 # Lo HANGUL SYLLABLE BBYI +C075..C08F ; H3 # Lo [27] HANGUL SYLLABLE BBYIG..HANGUL SYLLABLE BBYIH +C090 ; H2 # Lo HANGUL SYLLABLE BBI +C091..C0AB ; H3 # Lo [27] HANGUL SYLLABLE BBIG..HANGUL SYLLABLE BBIH +C0AC ; H2 # Lo HANGUL SYLLABLE SA +C0AD..C0C7 ; H3 # Lo [27] HANGUL SYLLABLE SAG..HANGUL SYLLABLE SAH +C0C8 ; H2 # Lo HANGUL SYLLABLE SAE +C0C9..C0E3 ; H3 # Lo [27] HANGUL SYLLABLE SAEG..HANGUL SYLLABLE SAEH +C0E4 ; H2 # Lo HANGUL SYLLABLE SYA +C0E5..C0FF ; H3 # Lo [27] HANGUL SYLLABLE SYAG..HANGUL SYLLABLE SYAH +C100 ; H2 # Lo HANGUL SYLLABLE SYAE +C101..C11B ; H3 # Lo [27] HANGUL SYLLABLE SYAEG..HANGUL SYLLABLE SYAEH +C11C ; H2 # Lo HANGUL SYLLABLE SEO +C11D..C137 ; H3 # Lo [27] HANGUL SYLLABLE SEOG..HANGUL SYLLABLE SEOH +C138 ; H2 # Lo HANGUL SYLLABLE SE +C139..C153 ; H3 # Lo [27] HANGUL SYLLABLE SEG..HANGUL SYLLABLE SEH +C154 ; H2 # Lo HANGUL SYLLABLE SYEO +C155..C16F ; H3 # Lo [27] HANGUL SYLLABLE SYEOG..HANGUL SYLLABLE SYEOH +C170 ; H2 # Lo HANGUL SYLLABLE SYE +C171..C18B ; H3 # Lo [27] HANGUL SYLLABLE SYEG..HANGUL SYLLABLE SYEH +C18C ; H2 # Lo HANGUL SYLLABLE SO +C18D..C1A7 ; H3 # Lo [27] HANGUL SYLLABLE SOG..HANGUL SYLLABLE SOH +C1A8 ; H2 # Lo HANGUL SYLLABLE SWA +C1A9..C1C3 ; H3 # Lo [27] HANGUL SYLLABLE SWAG..HANGUL SYLLABLE SWAH +C1C4 ; H2 # Lo HANGUL SYLLABLE SWAE +C1C5..C1DF ; H3 # Lo [27] HANGUL SYLLABLE SWAEG..HANGUL SYLLABLE SWAEH +C1E0 ; H2 # Lo HANGUL SYLLABLE SOE +C1E1..C1FB ; H3 # Lo [27] HANGUL SYLLABLE SOEG..HANGUL SYLLABLE SOEH +C1FC ; H2 # Lo HANGUL SYLLABLE SYO +C1FD..C217 ; H3 # Lo [27] HANGUL SYLLABLE SYOG..HANGUL SYLLABLE SYOH +C218 ; H2 # Lo HANGUL SYLLABLE SU +C219..C233 ; H3 # Lo [27] HANGUL SYLLABLE SUG..HANGUL SYLLABLE SUH +C234 ; H2 # Lo HANGUL SYLLABLE SWEO +C235..C24F ; H3 # Lo [27] HANGUL SYLLABLE SWEOG..HANGUL SYLLABLE SWEOH +C250 ; H2 # Lo HANGUL SYLLABLE SWE +C251..C26B ; H3 # Lo [27] HANGUL SYLLABLE SWEG..HANGUL SYLLABLE SWEH +C26C ; H2 # Lo HANGUL SYLLABLE SWI +C26D..C287 ; H3 # Lo [27] HANGUL SYLLABLE SWIG..HANGUL SYLLABLE SWIH +C288 ; H2 # Lo HANGUL SYLLABLE SYU +C289..C2A3 ; H3 # Lo [27] HANGUL SYLLABLE SYUG..HANGUL SYLLABLE SYUH +C2A4 ; H2 # Lo HANGUL SYLLABLE SEU +C2A5..C2BF ; H3 # Lo [27] HANGUL SYLLABLE SEUG..HANGUL SYLLABLE SEUH +C2C0 ; H2 # Lo HANGUL SYLLABLE SYI +C2C1..C2DB ; H3 # Lo [27] HANGUL SYLLABLE SYIG..HANGUL SYLLABLE SYIH +C2DC ; H2 # Lo HANGUL SYLLABLE SI +C2DD..C2F7 ; H3 # Lo [27] HANGUL SYLLABLE SIG..HANGUL SYLLABLE SIH +C2F8 ; H2 # Lo HANGUL SYLLABLE SSA +C2F9..C313 ; H3 # Lo [27] HANGUL SYLLABLE SSAG..HANGUL SYLLABLE SSAH +C314 ; H2 # Lo HANGUL SYLLABLE SSAE +C315..C32F ; H3 # Lo [27] HANGUL SYLLABLE SSAEG..HANGUL SYLLABLE SSAEH +C330 ; H2 # Lo HANGUL SYLLABLE SSYA +C331..C34B ; H3 # Lo [27] HANGUL SYLLABLE SSYAG..HANGUL SYLLABLE SSYAH +C34C ; H2 # Lo HANGUL SYLLABLE SSYAE +C34D..C367 ; H3 # Lo [27] HANGUL SYLLABLE SSYAEG..HANGUL SYLLABLE SSYAEH +C368 ; H2 # Lo HANGUL SYLLABLE SSEO +C369..C383 ; H3 # Lo [27] HANGUL SYLLABLE SSEOG..HANGUL SYLLABLE SSEOH +C384 ; H2 # Lo HANGUL SYLLABLE SSE +C385..C39F ; H3 # Lo [27] HANGUL SYLLABLE SSEG..HANGUL SYLLABLE SSEH +C3A0 ; H2 # Lo HANGUL SYLLABLE SSYEO +C3A1..C3BB ; H3 # Lo [27] HANGUL SYLLABLE SSYEOG..HANGUL SYLLABLE SSYEOH +C3BC ; H2 # Lo HANGUL SYLLABLE SSYE +C3BD..C3D7 ; H3 # Lo [27] HANGUL SYLLABLE SSYEG..HANGUL SYLLABLE SSYEH +C3D8 ; H2 # Lo HANGUL SYLLABLE SSO +C3D9..C3F3 ; H3 # Lo [27] HANGUL SYLLABLE SSOG..HANGUL SYLLABLE SSOH +C3F4 ; H2 # Lo HANGUL SYLLABLE SSWA +C3F5..C40F ; H3 # Lo [27] HANGUL SYLLABLE SSWAG..HANGUL SYLLABLE SSWAH +C410 ; H2 # Lo HANGUL SYLLABLE SSWAE +C411..C42B ; H3 # Lo [27] HANGUL SYLLABLE SSWAEG..HANGUL SYLLABLE SSWAEH +C42C ; H2 # Lo HANGUL SYLLABLE SSOE +C42D..C447 ; H3 # Lo [27] HANGUL SYLLABLE SSOEG..HANGUL SYLLABLE SSOEH +C448 ; H2 # Lo HANGUL SYLLABLE SSYO +C449..C463 ; H3 # Lo [27] HANGUL SYLLABLE SSYOG..HANGUL SYLLABLE SSYOH +C464 ; H2 # Lo HANGUL SYLLABLE SSU +C465..C47F ; H3 # Lo [27] HANGUL SYLLABLE SSUG..HANGUL SYLLABLE SSUH +C480 ; H2 # Lo HANGUL SYLLABLE SSWEO +C481..C49B ; H3 # Lo [27] HANGUL SYLLABLE SSWEOG..HANGUL SYLLABLE SSWEOH +C49C ; H2 # Lo HANGUL SYLLABLE SSWE +C49D..C4B7 ; H3 # Lo [27] HANGUL SYLLABLE SSWEG..HANGUL SYLLABLE SSWEH +C4B8 ; H2 # Lo HANGUL SYLLABLE SSWI +C4B9..C4D3 ; H3 # Lo [27] HANGUL SYLLABLE SSWIG..HANGUL SYLLABLE SSWIH +C4D4 ; H2 # Lo HANGUL SYLLABLE SSYU +C4D5..C4EF ; H3 # Lo [27] HANGUL SYLLABLE SSYUG..HANGUL SYLLABLE SSYUH +C4F0 ; H2 # Lo HANGUL SYLLABLE SSEU +C4F1..C50B ; H3 # Lo [27] HANGUL SYLLABLE SSEUG..HANGUL SYLLABLE SSEUH +C50C ; H2 # Lo HANGUL SYLLABLE SSYI +C50D..C527 ; H3 # Lo [27] HANGUL SYLLABLE SSYIG..HANGUL SYLLABLE SSYIH +C528 ; H2 # Lo HANGUL SYLLABLE SSI +C529..C543 ; H3 # Lo [27] HANGUL SYLLABLE SSIG..HANGUL SYLLABLE SSIH +C544 ; H2 # Lo HANGUL SYLLABLE A +C545..C55F ; H3 # Lo [27] HANGUL SYLLABLE AG..HANGUL SYLLABLE AH +C560 ; H2 # Lo HANGUL SYLLABLE AE +C561..C57B ; H3 # Lo [27] HANGUL SYLLABLE AEG..HANGUL SYLLABLE AEH +C57C ; H2 # Lo HANGUL SYLLABLE YA +C57D..C597 ; H3 # Lo [27] HANGUL SYLLABLE YAG..HANGUL SYLLABLE YAH +C598 ; H2 # Lo HANGUL SYLLABLE YAE +C599..C5B3 ; H3 # Lo [27] HANGUL SYLLABLE YAEG..HANGUL SYLLABLE YAEH +C5B4 ; H2 # Lo HANGUL SYLLABLE EO +C5B5..C5CF ; H3 # Lo [27] HANGUL SYLLABLE EOG..HANGUL SYLLABLE EOH +C5D0 ; H2 # Lo HANGUL SYLLABLE E +C5D1..C5EB ; H3 # Lo [27] HANGUL SYLLABLE EG..HANGUL SYLLABLE EH +C5EC ; H2 # Lo HANGUL SYLLABLE YEO +C5ED..C607 ; H3 # Lo [27] HANGUL SYLLABLE YEOG..HANGUL SYLLABLE YEOH +C608 ; H2 # Lo HANGUL SYLLABLE YE +C609..C623 ; H3 # Lo [27] HANGUL SYLLABLE YEG..HANGUL SYLLABLE YEH +C624 ; H2 # Lo HANGUL SYLLABLE O +C625..C63F ; H3 # Lo [27] HANGUL SYLLABLE OG..HANGUL SYLLABLE OH +C640 ; H2 # Lo HANGUL SYLLABLE WA +C641..C65B ; H3 # Lo [27] HANGUL SYLLABLE WAG..HANGUL SYLLABLE WAH +C65C ; H2 # Lo HANGUL SYLLABLE WAE +C65D..C677 ; H3 # Lo [27] HANGUL SYLLABLE WAEG..HANGUL SYLLABLE WAEH +C678 ; H2 # Lo HANGUL SYLLABLE OE +C679..C693 ; H3 # Lo [27] HANGUL SYLLABLE OEG..HANGUL SYLLABLE OEH +C694 ; H2 # Lo HANGUL SYLLABLE YO +C695..C6AF ; H3 # Lo [27] HANGUL SYLLABLE YOG..HANGUL SYLLABLE YOH +C6B0 ; H2 # Lo HANGUL SYLLABLE U +C6B1..C6CB ; H3 # Lo [27] HANGUL SYLLABLE UG..HANGUL SYLLABLE UH +C6CC ; H2 # Lo HANGUL SYLLABLE WEO +C6CD..C6E7 ; H3 # Lo [27] HANGUL SYLLABLE WEOG..HANGUL SYLLABLE WEOH +C6E8 ; H2 # Lo HANGUL SYLLABLE WE +C6E9..C703 ; H3 # Lo [27] HANGUL SYLLABLE WEG..HANGUL SYLLABLE WEH +C704 ; H2 # Lo HANGUL SYLLABLE WI +C705..C71F ; H3 # Lo [27] HANGUL SYLLABLE WIG..HANGUL SYLLABLE WIH +C720 ; H2 # Lo HANGUL SYLLABLE YU +C721..C73B ; H3 # Lo [27] HANGUL SYLLABLE YUG..HANGUL SYLLABLE YUH +C73C ; H2 # Lo HANGUL SYLLABLE EU +C73D..C757 ; H3 # Lo [27] HANGUL SYLLABLE EUG..HANGUL SYLLABLE EUH +C758 ; H2 # Lo HANGUL SYLLABLE YI +C759..C773 ; H3 # Lo [27] HANGUL SYLLABLE YIG..HANGUL SYLLABLE YIH +C774 ; H2 # Lo HANGUL SYLLABLE I +C775..C78F ; H3 # Lo [27] HANGUL SYLLABLE IG..HANGUL SYLLABLE IH +C790 ; H2 # Lo HANGUL SYLLABLE JA +C791..C7AB ; H3 # Lo [27] HANGUL SYLLABLE JAG..HANGUL SYLLABLE JAH +C7AC ; H2 # Lo HANGUL SYLLABLE JAE +C7AD..C7C7 ; H3 # Lo [27] HANGUL SYLLABLE JAEG..HANGUL SYLLABLE JAEH +C7C8 ; H2 # Lo HANGUL SYLLABLE JYA +C7C9..C7E3 ; H3 # Lo [27] HANGUL SYLLABLE JYAG..HANGUL SYLLABLE JYAH +C7E4 ; H2 # Lo HANGUL SYLLABLE JYAE +C7E5..C7FF ; H3 # Lo [27] HANGUL SYLLABLE JYAEG..HANGUL SYLLABLE JYAEH +C800 ; H2 # Lo HANGUL SYLLABLE JEO +C801..C81B ; H3 # Lo [27] HANGUL SYLLABLE JEOG..HANGUL SYLLABLE JEOH +C81C ; H2 # Lo HANGUL SYLLABLE JE +C81D..C837 ; H3 # Lo [27] HANGUL SYLLABLE JEG..HANGUL SYLLABLE JEH +C838 ; H2 # Lo HANGUL SYLLABLE JYEO +C839..C853 ; H3 # Lo [27] HANGUL SYLLABLE JYEOG..HANGUL SYLLABLE JYEOH +C854 ; H2 # Lo HANGUL SYLLABLE JYE +C855..C86F ; H3 # Lo [27] HANGUL SYLLABLE JYEG..HANGUL SYLLABLE JYEH +C870 ; H2 # Lo HANGUL SYLLABLE JO +C871..C88B ; H3 # Lo [27] HANGUL SYLLABLE JOG..HANGUL SYLLABLE JOH +C88C ; H2 # Lo HANGUL SYLLABLE JWA +C88D..C8A7 ; H3 # Lo [27] HANGUL SYLLABLE JWAG..HANGUL SYLLABLE JWAH +C8A8 ; H2 # Lo HANGUL SYLLABLE JWAE +C8A9..C8C3 ; H3 # Lo [27] HANGUL SYLLABLE JWAEG..HANGUL SYLLABLE JWAEH +C8C4 ; H2 # Lo HANGUL SYLLABLE JOE +C8C5..C8DF ; H3 # Lo [27] HANGUL SYLLABLE JOEG..HANGUL SYLLABLE JOEH +C8E0 ; H2 # Lo HANGUL SYLLABLE JYO +C8E1..C8FB ; H3 # Lo [27] HANGUL SYLLABLE JYOG..HANGUL SYLLABLE JYOH +C8FC ; H2 # Lo HANGUL SYLLABLE JU +C8FD..C917 ; H3 # Lo [27] HANGUL SYLLABLE JUG..HANGUL SYLLABLE JUH +C918 ; H2 # Lo HANGUL SYLLABLE JWEO +C919..C933 ; H3 # Lo [27] HANGUL SYLLABLE JWEOG..HANGUL SYLLABLE JWEOH +C934 ; H2 # Lo HANGUL SYLLABLE JWE +C935..C94F ; H3 # Lo [27] HANGUL SYLLABLE JWEG..HANGUL SYLLABLE JWEH +C950 ; H2 # Lo HANGUL SYLLABLE JWI +C951..C96B ; H3 # Lo [27] HANGUL SYLLABLE JWIG..HANGUL SYLLABLE JWIH +C96C ; H2 # Lo HANGUL SYLLABLE JYU +C96D..C987 ; H3 # Lo [27] HANGUL SYLLABLE JYUG..HANGUL SYLLABLE JYUH +C988 ; H2 # Lo HANGUL SYLLABLE JEU +C989..C9A3 ; H3 # Lo [27] HANGUL SYLLABLE JEUG..HANGUL SYLLABLE JEUH +C9A4 ; H2 # Lo HANGUL SYLLABLE JYI +C9A5..C9BF ; H3 # Lo [27] HANGUL SYLLABLE JYIG..HANGUL SYLLABLE JYIH +C9C0 ; H2 # Lo HANGUL SYLLABLE JI +C9C1..C9DB ; H3 # Lo [27] HANGUL SYLLABLE JIG..HANGUL SYLLABLE JIH +C9DC ; H2 # Lo HANGUL SYLLABLE JJA +C9DD..C9F7 ; H3 # Lo [27] HANGUL SYLLABLE JJAG..HANGUL SYLLABLE JJAH +C9F8 ; H2 # Lo HANGUL SYLLABLE JJAE +C9F9..CA13 ; H3 # Lo [27] HANGUL SYLLABLE JJAEG..HANGUL SYLLABLE JJAEH +CA14 ; H2 # Lo HANGUL SYLLABLE JJYA +CA15..CA2F ; H3 # Lo [27] HANGUL SYLLABLE JJYAG..HANGUL SYLLABLE JJYAH +CA30 ; H2 # Lo HANGUL SYLLABLE JJYAE +CA31..CA4B ; H3 # Lo [27] HANGUL SYLLABLE JJYAEG..HANGUL SYLLABLE JJYAEH +CA4C ; H2 # Lo HANGUL SYLLABLE JJEO +CA4D..CA67 ; H3 # Lo [27] HANGUL SYLLABLE JJEOG..HANGUL SYLLABLE JJEOH +CA68 ; H2 # Lo HANGUL SYLLABLE JJE +CA69..CA83 ; H3 # Lo [27] HANGUL SYLLABLE JJEG..HANGUL SYLLABLE JJEH +CA84 ; H2 # Lo HANGUL SYLLABLE JJYEO +CA85..CA9F ; H3 # Lo [27] HANGUL SYLLABLE JJYEOG..HANGUL SYLLABLE JJYEOH +CAA0 ; H2 # Lo HANGUL SYLLABLE JJYE +CAA1..CABB ; H3 # Lo [27] HANGUL SYLLABLE JJYEG..HANGUL SYLLABLE JJYEH +CABC ; H2 # Lo HANGUL SYLLABLE JJO +CABD..CAD7 ; H3 # Lo [27] HANGUL SYLLABLE JJOG..HANGUL SYLLABLE JJOH +CAD8 ; H2 # Lo HANGUL SYLLABLE JJWA +CAD9..CAF3 ; H3 # Lo [27] HANGUL SYLLABLE JJWAG..HANGUL SYLLABLE JJWAH +CAF4 ; H2 # Lo HANGUL SYLLABLE JJWAE +CAF5..CB0F ; H3 # Lo [27] HANGUL SYLLABLE JJWAEG..HANGUL SYLLABLE JJWAEH +CB10 ; H2 # Lo HANGUL SYLLABLE JJOE +CB11..CB2B ; H3 # Lo [27] HANGUL SYLLABLE JJOEG..HANGUL SYLLABLE JJOEH +CB2C ; H2 # Lo HANGUL SYLLABLE JJYO +CB2D..CB47 ; H3 # Lo [27] HANGUL SYLLABLE JJYOG..HANGUL SYLLABLE JJYOH +CB48 ; H2 # Lo HANGUL SYLLABLE JJU +CB49..CB63 ; H3 # Lo [27] HANGUL SYLLABLE JJUG..HANGUL SYLLABLE JJUH +CB64 ; H2 # Lo HANGUL SYLLABLE JJWEO +CB65..CB7F ; H3 # Lo [27] HANGUL SYLLABLE JJWEOG..HANGUL SYLLABLE JJWEOH +CB80 ; H2 # Lo HANGUL SYLLABLE JJWE +CB81..CB9B ; H3 # Lo [27] HANGUL SYLLABLE JJWEG..HANGUL SYLLABLE JJWEH +CB9C ; H2 # Lo HANGUL SYLLABLE JJWI +CB9D..CBB7 ; H3 # Lo [27] HANGUL SYLLABLE JJWIG..HANGUL SYLLABLE JJWIH +CBB8 ; H2 # Lo HANGUL SYLLABLE JJYU +CBB9..CBD3 ; H3 # Lo [27] HANGUL SYLLABLE JJYUG..HANGUL SYLLABLE JJYUH +CBD4 ; H2 # Lo HANGUL SYLLABLE JJEU +CBD5..CBEF ; H3 # Lo [27] HANGUL SYLLABLE JJEUG..HANGUL SYLLABLE JJEUH +CBF0 ; H2 # Lo HANGUL SYLLABLE JJYI +CBF1..CC0B ; H3 # Lo [27] HANGUL SYLLABLE JJYIG..HANGUL SYLLABLE JJYIH +CC0C ; H2 # Lo HANGUL SYLLABLE JJI +CC0D..CC27 ; H3 # Lo [27] HANGUL SYLLABLE JJIG..HANGUL SYLLABLE JJIH +CC28 ; H2 # Lo HANGUL SYLLABLE CA +CC29..CC43 ; H3 # Lo [27] HANGUL SYLLABLE CAG..HANGUL SYLLABLE CAH +CC44 ; H2 # Lo HANGUL SYLLABLE CAE +CC45..CC5F ; H3 # Lo [27] HANGUL SYLLABLE CAEG..HANGUL SYLLABLE CAEH +CC60 ; H2 # Lo HANGUL SYLLABLE CYA +CC61..CC7B ; H3 # Lo [27] HANGUL SYLLABLE CYAG..HANGUL SYLLABLE CYAH +CC7C ; H2 # Lo HANGUL SYLLABLE CYAE +CC7D..CC97 ; H3 # Lo [27] HANGUL SYLLABLE CYAEG..HANGUL SYLLABLE CYAEH +CC98 ; H2 # Lo HANGUL SYLLABLE CEO +CC99..CCB3 ; H3 # Lo [27] HANGUL SYLLABLE CEOG..HANGUL SYLLABLE CEOH +CCB4 ; H2 # Lo HANGUL SYLLABLE CE +CCB5..CCCF ; H3 # Lo [27] HANGUL SYLLABLE CEG..HANGUL SYLLABLE CEH +CCD0 ; H2 # Lo HANGUL SYLLABLE CYEO +CCD1..CCEB ; H3 # Lo [27] HANGUL SYLLABLE CYEOG..HANGUL SYLLABLE CYEOH +CCEC ; H2 # Lo HANGUL SYLLABLE CYE +CCED..CD07 ; H3 # Lo [27] HANGUL SYLLABLE CYEG..HANGUL SYLLABLE CYEH +CD08 ; H2 # Lo HANGUL SYLLABLE CO +CD09..CD23 ; H3 # Lo [27] HANGUL SYLLABLE COG..HANGUL SYLLABLE COH +CD24 ; H2 # Lo HANGUL SYLLABLE CWA +CD25..CD3F ; H3 # Lo [27] HANGUL SYLLABLE CWAG..HANGUL SYLLABLE CWAH +CD40 ; H2 # Lo HANGUL SYLLABLE CWAE +CD41..CD5B ; H3 # Lo [27] HANGUL SYLLABLE CWAEG..HANGUL SYLLABLE CWAEH +CD5C ; H2 # Lo HANGUL SYLLABLE COE +CD5D..CD77 ; H3 # Lo [27] HANGUL SYLLABLE COEG..HANGUL SYLLABLE COEH +CD78 ; H2 # Lo HANGUL SYLLABLE CYO +CD79..CD93 ; H3 # Lo [27] HANGUL SYLLABLE CYOG..HANGUL SYLLABLE CYOH +CD94 ; H2 # Lo HANGUL SYLLABLE CU +CD95..CDAF ; H3 # Lo [27] HANGUL SYLLABLE CUG..HANGUL SYLLABLE CUH +CDB0 ; H2 # Lo HANGUL SYLLABLE CWEO +CDB1..CDCB ; H3 # Lo [27] HANGUL SYLLABLE CWEOG..HANGUL SYLLABLE CWEOH +CDCC ; H2 # Lo HANGUL SYLLABLE CWE +CDCD..CDE7 ; H3 # Lo [27] HANGUL SYLLABLE CWEG..HANGUL SYLLABLE CWEH +CDE8 ; H2 # Lo HANGUL SYLLABLE CWI +CDE9..CE03 ; H3 # Lo [27] HANGUL SYLLABLE CWIG..HANGUL SYLLABLE CWIH +CE04 ; H2 # Lo HANGUL SYLLABLE CYU +CE05..CE1F ; H3 # Lo [27] HANGUL SYLLABLE CYUG..HANGUL SYLLABLE CYUH +CE20 ; H2 # Lo HANGUL SYLLABLE CEU +CE21..CE3B ; H3 # Lo [27] HANGUL SYLLABLE CEUG..HANGUL SYLLABLE CEUH +CE3C ; H2 # Lo HANGUL SYLLABLE CYI +CE3D..CE57 ; H3 # Lo [27] HANGUL SYLLABLE CYIG..HANGUL SYLLABLE CYIH +CE58 ; H2 # Lo HANGUL SYLLABLE CI +CE59..CE73 ; H3 # Lo [27] HANGUL SYLLABLE CIG..HANGUL SYLLABLE CIH +CE74 ; H2 # Lo HANGUL SYLLABLE KA +CE75..CE8F ; H3 # Lo [27] HANGUL SYLLABLE KAG..HANGUL SYLLABLE KAH +CE90 ; H2 # Lo HANGUL SYLLABLE KAE +CE91..CEAB ; H3 # Lo [27] HANGUL SYLLABLE KAEG..HANGUL SYLLABLE KAEH +CEAC ; H2 # Lo HANGUL SYLLABLE KYA +CEAD..CEC7 ; H3 # Lo [27] HANGUL SYLLABLE KYAG..HANGUL SYLLABLE KYAH +CEC8 ; H2 # Lo HANGUL SYLLABLE KYAE +CEC9..CEE3 ; H3 # Lo [27] HANGUL SYLLABLE KYAEG..HANGUL SYLLABLE KYAEH +CEE4 ; H2 # Lo HANGUL SYLLABLE KEO +CEE5..CEFF ; H3 # Lo [27] HANGUL SYLLABLE KEOG..HANGUL SYLLABLE KEOH +CF00 ; H2 # Lo HANGUL SYLLABLE KE +CF01..CF1B ; H3 # Lo [27] HANGUL SYLLABLE KEG..HANGUL SYLLABLE KEH +CF1C ; H2 # Lo HANGUL SYLLABLE KYEO +CF1D..CF37 ; H3 # Lo [27] HANGUL SYLLABLE KYEOG..HANGUL SYLLABLE KYEOH +CF38 ; H2 # Lo HANGUL SYLLABLE KYE +CF39..CF53 ; H3 # Lo [27] HANGUL SYLLABLE KYEG..HANGUL SYLLABLE KYEH +CF54 ; H2 # Lo HANGUL SYLLABLE KO +CF55..CF6F ; H3 # Lo [27] HANGUL SYLLABLE KOG..HANGUL SYLLABLE KOH +CF70 ; H2 # Lo HANGUL SYLLABLE KWA +CF71..CF8B ; H3 # Lo [27] HANGUL SYLLABLE KWAG..HANGUL SYLLABLE KWAH +CF8C ; H2 # Lo HANGUL SYLLABLE KWAE +CF8D..CFA7 ; H3 # Lo [27] HANGUL SYLLABLE KWAEG..HANGUL SYLLABLE KWAEH +CFA8 ; H2 # Lo HANGUL SYLLABLE KOE +CFA9..CFC3 ; H3 # Lo [27] HANGUL SYLLABLE KOEG..HANGUL SYLLABLE KOEH +CFC4 ; H2 # Lo HANGUL SYLLABLE KYO +CFC5..CFDF ; H3 # Lo [27] HANGUL SYLLABLE KYOG..HANGUL SYLLABLE KYOH +CFE0 ; H2 # Lo HANGUL SYLLABLE KU +CFE1..CFFB ; H3 # Lo [27] HANGUL SYLLABLE KUG..HANGUL SYLLABLE KUH +CFFC ; H2 # Lo HANGUL SYLLABLE KWEO +CFFD..D017 ; H3 # Lo [27] HANGUL SYLLABLE KWEOG..HANGUL SYLLABLE KWEOH +D018 ; H2 # Lo HANGUL SYLLABLE KWE +D019..D033 ; H3 # Lo [27] HANGUL SYLLABLE KWEG..HANGUL SYLLABLE KWEH +D034 ; H2 # Lo HANGUL SYLLABLE KWI +D035..D04F ; H3 # Lo [27] HANGUL SYLLABLE KWIG..HANGUL SYLLABLE KWIH +D050 ; H2 # Lo HANGUL SYLLABLE KYU +D051..D06B ; H3 # Lo [27] HANGUL SYLLABLE KYUG..HANGUL SYLLABLE KYUH +D06C ; H2 # Lo HANGUL SYLLABLE KEU +D06D..D087 ; H3 # Lo [27] HANGUL SYLLABLE KEUG..HANGUL SYLLABLE KEUH +D088 ; H2 # Lo HANGUL SYLLABLE KYI +D089..D0A3 ; H3 # Lo [27] HANGUL SYLLABLE KYIG..HANGUL SYLLABLE KYIH +D0A4 ; H2 # Lo HANGUL SYLLABLE KI +D0A5..D0BF ; H3 # Lo [27] HANGUL SYLLABLE KIG..HANGUL SYLLABLE KIH +D0C0 ; H2 # Lo HANGUL SYLLABLE TA +D0C1..D0DB ; H3 # Lo [27] HANGUL SYLLABLE TAG..HANGUL SYLLABLE TAH +D0DC ; H2 # Lo HANGUL SYLLABLE TAE +D0DD..D0F7 ; H3 # Lo [27] HANGUL SYLLABLE TAEG..HANGUL SYLLABLE TAEH +D0F8 ; H2 # Lo HANGUL SYLLABLE TYA +D0F9..D113 ; H3 # Lo [27] HANGUL SYLLABLE TYAG..HANGUL SYLLABLE TYAH +D114 ; H2 # Lo HANGUL SYLLABLE TYAE +D115..D12F ; H3 # Lo [27] HANGUL SYLLABLE TYAEG..HANGUL SYLLABLE TYAEH +D130 ; H2 # Lo HANGUL SYLLABLE TEO +D131..D14B ; H3 # Lo [27] HANGUL SYLLABLE TEOG..HANGUL SYLLABLE TEOH +D14C ; H2 # Lo HANGUL SYLLABLE TE +D14D..D167 ; H3 # Lo [27] HANGUL SYLLABLE TEG..HANGUL SYLLABLE TEH +D168 ; H2 # Lo HANGUL SYLLABLE TYEO +D169..D183 ; H3 # Lo [27] HANGUL SYLLABLE TYEOG..HANGUL SYLLABLE TYEOH +D184 ; H2 # Lo HANGUL SYLLABLE TYE +D185..D19F ; H3 # Lo [27] HANGUL SYLLABLE TYEG..HANGUL SYLLABLE TYEH +D1A0 ; H2 # Lo HANGUL SYLLABLE TO +D1A1..D1BB ; H3 # Lo [27] HANGUL SYLLABLE TOG..HANGUL SYLLABLE TOH +D1BC ; H2 # Lo HANGUL SYLLABLE TWA +D1BD..D1D7 ; H3 # Lo [27] HANGUL SYLLABLE TWAG..HANGUL SYLLABLE TWAH +D1D8 ; H2 # Lo HANGUL SYLLABLE TWAE +D1D9..D1F3 ; H3 # Lo [27] HANGUL SYLLABLE TWAEG..HANGUL SYLLABLE TWAEH +D1F4 ; H2 # Lo HANGUL SYLLABLE TOE +D1F5..D20F ; H3 # Lo [27] HANGUL SYLLABLE TOEG..HANGUL SYLLABLE TOEH +D210 ; H2 # Lo HANGUL SYLLABLE TYO +D211..D22B ; H3 # Lo [27] HANGUL SYLLABLE TYOG..HANGUL SYLLABLE TYOH +D22C ; H2 # Lo HANGUL SYLLABLE TU +D22D..D247 ; H3 # Lo [27] HANGUL SYLLABLE TUG..HANGUL SYLLABLE TUH +D248 ; H2 # Lo HANGUL SYLLABLE TWEO +D249..D263 ; H3 # Lo [27] HANGUL SYLLABLE TWEOG..HANGUL SYLLABLE TWEOH +D264 ; H2 # Lo HANGUL SYLLABLE TWE +D265..D27F ; H3 # Lo [27] HANGUL SYLLABLE TWEG..HANGUL SYLLABLE TWEH +D280 ; H2 # Lo HANGUL SYLLABLE TWI +D281..D29B ; H3 # Lo [27] HANGUL SYLLABLE TWIG..HANGUL SYLLABLE TWIH +D29C ; H2 # Lo HANGUL SYLLABLE TYU +D29D..D2B7 ; H3 # Lo [27] HANGUL SYLLABLE TYUG..HANGUL SYLLABLE TYUH +D2B8 ; H2 # Lo HANGUL SYLLABLE TEU +D2B9..D2D3 ; H3 # Lo [27] HANGUL SYLLABLE TEUG..HANGUL SYLLABLE TEUH +D2D4 ; H2 # Lo HANGUL SYLLABLE TYI +D2D5..D2EF ; H3 # Lo [27] HANGUL SYLLABLE TYIG..HANGUL SYLLABLE TYIH +D2F0 ; H2 # Lo HANGUL SYLLABLE TI +D2F1..D30B ; H3 # Lo [27] HANGUL SYLLABLE TIG..HANGUL SYLLABLE TIH +D30C ; H2 # Lo HANGUL SYLLABLE PA +D30D..D327 ; H3 # Lo [27] HANGUL SYLLABLE PAG..HANGUL SYLLABLE PAH +D328 ; H2 # Lo HANGUL SYLLABLE PAE +D329..D343 ; H3 # Lo [27] HANGUL SYLLABLE PAEG..HANGUL SYLLABLE PAEH +D344 ; H2 # Lo HANGUL SYLLABLE PYA +D345..D35F ; H3 # Lo [27] HANGUL SYLLABLE PYAG..HANGUL SYLLABLE PYAH +D360 ; H2 # Lo HANGUL SYLLABLE PYAE +D361..D37B ; H3 # Lo [27] HANGUL SYLLABLE PYAEG..HANGUL SYLLABLE PYAEH +D37C ; H2 # Lo HANGUL SYLLABLE PEO +D37D..D397 ; H3 # Lo [27] HANGUL SYLLABLE PEOG..HANGUL SYLLABLE PEOH +D398 ; H2 # Lo HANGUL SYLLABLE PE +D399..D3B3 ; H3 # Lo [27] HANGUL SYLLABLE PEG..HANGUL SYLLABLE PEH +D3B4 ; H2 # Lo HANGUL SYLLABLE PYEO +D3B5..D3CF ; H3 # Lo [27] HANGUL SYLLABLE PYEOG..HANGUL SYLLABLE PYEOH +D3D0 ; H2 # Lo HANGUL SYLLABLE PYE +D3D1..D3EB ; H3 # Lo [27] HANGUL SYLLABLE PYEG..HANGUL SYLLABLE PYEH +D3EC ; H2 # Lo HANGUL SYLLABLE PO +D3ED..D407 ; H3 # Lo [27] HANGUL SYLLABLE POG..HANGUL SYLLABLE POH +D408 ; H2 # Lo HANGUL SYLLABLE PWA +D409..D423 ; H3 # Lo [27] HANGUL SYLLABLE PWAG..HANGUL SYLLABLE PWAH +D424 ; H2 # Lo HANGUL SYLLABLE PWAE +D425..D43F ; H3 # Lo [27] HANGUL SYLLABLE PWAEG..HANGUL SYLLABLE PWAEH +D440 ; H2 # Lo HANGUL SYLLABLE POE +D441..D45B ; H3 # Lo [27] HANGUL SYLLABLE POEG..HANGUL SYLLABLE POEH +D45C ; H2 # Lo HANGUL SYLLABLE PYO +D45D..D477 ; H3 # Lo [27] HANGUL SYLLABLE PYOG..HANGUL SYLLABLE PYOH +D478 ; H2 # Lo HANGUL SYLLABLE PU +D479..D493 ; H3 # Lo [27] HANGUL SYLLABLE PUG..HANGUL SYLLABLE PUH +D494 ; H2 # Lo HANGUL SYLLABLE PWEO +D495..D4AF ; H3 # Lo [27] HANGUL SYLLABLE PWEOG..HANGUL SYLLABLE PWEOH +D4B0 ; H2 # Lo HANGUL SYLLABLE PWE +D4B1..D4CB ; H3 # Lo [27] HANGUL SYLLABLE PWEG..HANGUL SYLLABLE PWEH +D4CC ; H2 # Lo HANGUL SYLLABLE PWI +D4CD..D4E7 ; H3 # Lo [27] HANGUL SYLLABLE PWIG..HANGUL SYLLABLE PWIH +D4E8 ; H2 # Lo HANGUL SYLLABLE PYU +D4E9..D503 ; H3 # Lo [27] HANGUL SYLLABLE PYUG..HANGUL SYLLABLE PYUH +D504 ; H2 # Lo HANGUL SYLLABLE PEU +D505..D51F ; H3 # Lo [27] HANGUL SYLLABLE PEUG..HANGUL SYLLABLE PEUH +D520 ; H2 # Lo HANGUL SYLLABLE PYI +D521..D53B ; H3 # Lo [27] HANGUL SYLLABLE PYIG..HANGUL SYLLABLE PYIH +D53C ; H2 # Lo HANGUL SYLLABLE PI +D53D..D557 ; H3 # Lo [27] HANGUL SYLLABLE PIG..HANGUL SYLLABLE PIH +D558 ; H2 # Lo HANGUL SYLLABLE HA +D559..D573 ; H3 # Lo [27] HANGUL SYLLABLE HAG..HANGUL SYLLABLE HAH +D574 ; H2 # Lo HANGUL SYLLABLE HAE +D575..D58F ; H3 # Lo [27] HANGUL SYLLABLE HAEG..HANGUL SYLLABLE HAEH +D590 ; H2 # Lo HANGUL SYLLABLE HYA +D591..D5AB ; H3 # Lo [27] HANGUL SYLLABLE HYAG..HANGUL SYLLABLE HYAH +D5AC ; H2 # Lo HANGUL SYLLABLE HYAE +D5AD..D5C7 ; H3 # Lo [27] HANGUL SYLLABLE HYAEG..HANGUL SYLLABLE HYAEH +D5C8 ; H2 # Lo HANGUL SYLLABLE HEO +D5C9..D5E3 ; H3 # Lo [27] HANGUL SYLLABLE HEOG..HANGUL SYLLABLE HEOH +D5E4 ; H2 # Lo HANGUL SYLLABLE HE +D5E5..D5FF ; H3 # Lo [27] HANGUL SYLLABLE HEG..HANGUL SYLLABLE HEH +D600 ; H2 # Lo HANGUL SYLLABLE HYEO +D601..D61B ; H3 # Lo [27] HANGUL SYLLABLE HYEOG..HANGUL SYLLABLE HYEOH +D61C ; H2 # Lo HANGUL SYLLABLE HYE +D61D..D637 ; H3 # Lo [27] HANGUL SYLLABLE HYEG..HANGUL SYLLABLE HYEH +D638 ; H2 # Lo HANGUL SYLLABLE HO +D639..D653 ; H3 # Lo [27] HANGUL SYLLABLE HOG..HANGUL SYLLABLE HOH +D654 ; H2 # Lo HANGUL SYLLABLE HWA +D655..D66F ; H3 # Lo [27] HANGUL SYLLABLE HWAG..HANGUL SYLLABLE HWAH +D670 ; H2 # Lo HANGUL SYLLABLE HWAE +D671..D68B ; H3 # Lo [27] HANGUL SYLLABLE HWAEG..HANGUL SYLLABLE HWAEH +D68C ; H2 # Lo HANGUL SYLLABLE HOE +D68D..D6A7 ; H3 # Lo [27] HANGUL SYLLABLE HOEG..HANGUL SYLLABLE HOEH +D6A8 ; H2 # Lo HANGUL SYLLABLE HYO +D6A9..D6C3 ; H3 # Lo [27] HANGUL SYLLABLE HYOG..HANGUL SYLLABLE HYOH +D6C4 ; H2 # Lo HANGUL SYLLABLE HU +D6C5..D6DF ; H3 # Lo [27] HANGUL SYLLABLE HUG..HANGUL SYLLABLE HUH +D6E0 ; H2 # Lo HANGUL SYLLABLE HWEO +D6E1..D6FB ; H3 # Lo [27] HANGUL SYLLABLE HWEOG..HANGUL SYLLABLE HWEOH +D6FC ; H2 # Lo HANGUL SYLLABLE HWE +D6FD..D717 ; H3 # Lo [27] HANGUL SYLLABLE HWEG..HANGUL SYLLABLE HWEH +D718 ; H2 # Lo HANGUL SYLLABLE HWI +D719..D733 ; H3 # Lo [27] HANGUL SYLLABLE HWIG..HANGUL SYLLABLE HWIH +D734 ; H2 # Lo HANGUL SYLLABLE HYU +D735..D74F ; H3 # Lo [27] HANGUL SYLLABLE HYUG..HANGUL SYLLABLE HYUH +D750 ; H2 # Lo HANGUL SYLLABLE HEU +D751..D76B ; H3 # Lo [27] HANGUL SYLLABLE HEUG..HANGUL SYLLABLE HEUH +D76C ; H2 # Lo HANGUL SYLLABLE HYI +D76D..D787 ; H3 # Lo [27] HANGUL SYLLABLE HYIG..HANGUL SYLLABLE HYIH +D788 ; H2 # Lo HANGUL SYLLABLE HI +D789..D7A3 ; H3 # Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH +D7B0..D7C6 ; JV # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; JT # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +D800..DB7F ; SG # Cs [896] .. +DB80..DBFF ; SG # Cs [128] .. +DC00..DFFF ; SG # Cs [1024] .. +E000..F8FF ; XX # Co [6400] .. +F900..FA6D ; ID # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA6E..FA6F ; ID # Cn [2] .. +FA70..FAD9 ; ID # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FADA..FAFF ; ID # Cn [38] .. +FB00..FB06 ; AL # Ll [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; AL # Ll [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB1D ; HL # Lo HEBREW LETTER YOD WITH HIRIQ +FB1E ; CM # Mn HEBREW POINT JUDEO-SPANISH VARIKA +FB1F..FB28 ; HL # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB29 ; AL # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN +FB2A..FB36 ; HL # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; HL # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; HL # Lo HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; HL # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; HL # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FB4F ; HL # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED +FB50..FBB1 ; AL # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBB2..FBC2 ; AL # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBD3..FD3D ; AL # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD3E ; CL # Pe ORNATE LEFT PARENTHESIS +FD3F ; OP # Ps ORNATE RIGHT PARENTHESIS +FD40..FD4F ; AL # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FD50..FD8F ; AL # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; AL # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDCF ; AL # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDF0..FDFB ; AL # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FDFC ; PO # Sc RIAL SIGN +FDFD..FDFF ; AL # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FE00..FE0F ; CM # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE10 ; IS # Po PRESENTATION FORM FOR VERTICAL COMMA +FE11..FE12 ; CL # Po [2] PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA..PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP +FE13..FE14 ; IS # Po [2] PRESENTATION FORM FOR VERTICAL COLON..PRESENTATION FORM FOR VERTICAL SEMICOLON +FE15..FE16 ; EX # Po [2] PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE17 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE18 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE19 ; IN # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE20..FE2F ; CM # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +FE30 ; ID # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE31..FE32 ; ID # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE33..FE34 ; ID # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE35 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE36 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE37 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE38 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE39 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3A ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3B ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3C ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3D ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3E ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE3F ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE40 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE41 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE42 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE43 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE44 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE45..FE46 ; ID # Po [2] SESAME DOT..WHITE SESAME DOT +FE47 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE48 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE49..FE4C ; ID # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE4D..FE4F ; ID # Pc [3] DASHED LOW LINE..WAVY LOW LINE +FE50 ; CL # Po SMALL COMMA +FE51 ; ID # Po SMALL IDEOGRAPHIC COMMA +FE52 ; CL # Po SMALL FULL STOP +FE54..FE55 ; NS # Po [2] SMALL SEMICOLON..SMALL COLON +FE56..FE57 ; EX # Po [2] SMALL QUESTION MARK..SMALL EXCLAMATION MARK +FE58 ; ID # Pd SMALL EM DASH +FE59 ; OP # Ps SMALL LEFT PARENTHESIS +FE5A ; CL # Pe SMALL RIGHT PARENTHESIS +FE5B ; OP # Ps SMALL LEFT CURLY BRACKET +FE5C ; CL # Pe SMALL RIGHT CURLY BRACKET +FE5D ; OP # Ps SMALL LEFT TORTOISE SHELL BRACKET +FE5E ; CL # Pe SMALL RIGHT TORTOISE SHELL BRACKET +FE5F..FE61 ; ID # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE62 ; ID # Sm SMALL PLUS SIGN +FE63 ; ID # Pd SMALL HYPHEN-MINUS +FE64..FE66 ; ID # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FE68 ; ID # Po SMALL REVERSE SOLIDUS +FE69 ; PR # Sc SMALL DOLLAR SIGN +FE6A ; PO # Po SMALL PERCENT SIGN +FE6B ; ID # Po SMALL COMMERCIAL AT +FE70..FE74 ; AL # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; AL # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FEFF ; WJ # Cf ZERO WIDTH NO-BREAK SPACE +FF01 ; EX # Po FULLWIDTH EXCLAMATION MARK +FF02..FF03 ; ID # Po [2] FULLWIDTH QUOTATION MARK..FULLWIDTH NUMBER SIGN +FF04 ; PR # Sc FULLWIDTH DOLLAR SIGN +FF05 ; PO # Po FULLWIDTH PERCENT SIGN +FF06..FF07 ; ID # Po [2] FULLWIDTH AMPERSAND..FULLWIDTH APOSTROPHE +FF08 ; OP # Ps FULLWIDTH LEFT PARENTHESIS +FF09 ; CL # Pe FULLWIDTH RIGHT PARENTHESIS +FF0A ; ID # Po FULLWIDTH ASTERISK +FF0B ; ID # Sm FULLWIDTH PLUS SIGN +FF0C ; CL # Po FULLWIDTH COMMA +FF0D ; ID # Pd FULLWIDTH HYPHEN-MINUS +FF0E ; CL # Po FULLWIDTH FULL STOP +FF0F ; ID # Po FULLWIDTH SOLIDUS +FF10..FF19 ; ID # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +FF1A..FF1B ; NS # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1C..FF1E ; ID # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF1F ; EX # Po FULLWIDTH QUESTION MARK +FF20 ; ID # Po FULLWIDTH COMMERCIAL AT +FF21..FF3A ; ID # Lu [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +FF3B ; OP # Ps FULLWIDTH LEFT SQUARE BRACKET +FF3C ; ID # Po FULLWIDTH REVERSE SOLIDUS +FF3D ; CL # Pe FULLWIDTH RIGHT SQUARE BRACKET +FF3E ; ID # Sk FULLWIDTH CIRCUMFLEX ACCENT +FF3F ; ID # Pc FULLWIDTH LOW LINE +FF40 ; ID # Sk FULLWIDTH GRAVE ACCENT +FF41..FF5A ; ID # Ll [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +FF5B ; OP # Ps FULLWIDTH LEFT CURLY BRACKET +FF5C ; ID # Sm FULLWIDTH VERTICAL LINE +FF5D ; CL # Pe FULLWIDTH RIGHT CURLY BRACKET +FF5E ; ID # Sm FULLWIDTH TILDE +FF5F ; OP # Ps FULLWIDTH LEFT WHITE PARENTHESIS +FF60 ; CL # Pe FULLWIDTH RIGHT WHITE PARENTHESIS +FF61 ; CL # Po HALFWIDTH IDEOGRAPHIC FULL STOP +FF62 ; OP # Ps HALFWIDTH LEFT CORNER BRACKET +FF63 ; CL # Pe HALFWIDTH RIGHT CORNER BRACKET +FF64 ; CL # Po HALFWIDTH IDEOGRAPHIC COMMA +FF65 ; NS # Po HALFWIDTH KATAKANA MIDDLE DOT +FF66 ; ID # Lo HALFWIDTH KATAKANA LETTER WO +FF67..FF6F ; CJ # Lo [9] HALFWIDTH KATAKANA LETTER SMALL A..HALFWIDTH KATAKANA LETTER SMALL TU +FF70 ; CJ # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF71..FF9D ; ID # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FF9E..FF9F ; NS # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +FFA0..FFBE ; ID # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; ID # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; ID # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; ID # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; ID # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +FFE0 ; PO # Sc FULLWIDTH CENT SIGN +FFE1 ; PR # Sc FULLWIDTH POUND SIGN +FFE2 ; ID # Sm FULLWIDTH NOT SIGN +FFE3 ; ID # Sk FULLWIDTH MACRON +FFE4 ; ID # So FULLWIDTH BROKEN BAR +FFE5..FFE6 ; PR # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +FFE8 ; AL # So HALFWIDTH FORMS LIGHT VERTICAL +FFE9..FFEC ; AL # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +FFED..FFEE ; AL # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFF9..FFFB ; CM # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +FFFC ; CB # So OBJECT REPLACEMENT CHARACTER +FFFD ; AI # So REPLACEMENT CHARACTER +10000..1000B ; AL # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; AL # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; AL # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; AL # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; AL # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; AL # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; AL # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10100..10102 ; BA # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +10107..10133 ; AL # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10137..1013F ; AL # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10140..10174 ; AL # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10175..10178 ; AL # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +10179..10189 ; AL # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018A..1018B ; AL # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +1018C..1018E ; AL # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; AL # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; AL # So GREEK SYMBOL TAU RHO +101D0..101FC ; AL # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +101FD ; CM # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +10280..1029C ; AL # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; AL # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3 +102E0 ; CM # Mn COPTIC EPACT THOUSANDS MARK +102E1..102FB ; AL # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10300..1031F ; AL # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +10320..10323 ; AL # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +1032D..1032F ; AL # Lo [3] OLD ITALIC LETTER YE..OLD ITALIC LETTER SOUTHERN TSE +10330..10340 ; AL # Lo [17] GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341 ; AL # Nl GOTHIC LETTER NINETY +10342..10349 ; AL # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; AL # Nl GOTHIC LETTER NINE HUNDRED +10350..10375 ; AL # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10376..1037A ; CM # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10380..1039D ; AL # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039F ; BA # Po UGARITIC WORD DIVIDER +103A0..103C3 ; AL # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; AL # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D0 ; BA # Po OLD PERSIAN WORD DIVIDER +103D1..103D5 ; AL # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +10400..1044F ; AL # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW +10450..1047F ; AL # Lo [48] SHAVIAN LETTER PEEP..SHAVIAN LETTER YEW +10480..1049D ; AL # Lo [30] OSMANYA LETTER ALEF..OSMANYA LETTER OO +104A0..104A9 ; NU # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104B0..104D3 ; AL # Lu [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +104D8..104FB ; AL # Ll [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10500..10527 ; AL # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; AL # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +1056F ; AL # Po CAUCASIAN ALBANIAN CITATION MARK +10570..1057A ; AL # Lu [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; AL # Lu [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; AL # Lu [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; AL # Lu [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10597..105A1 ; AL # Ll [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; AL # Ll [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; AL # Ll [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; AL # Ll [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10600..10736 ; AL # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; AL # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; AL # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10780..10785 ; AL # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; AL # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; AL # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +10800..10805 ; AL # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; AL # Lo CYPRIOT SYLLABLE JO +1080A..10835 ; AL # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; AL # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; AL # Lo CYPRIOT SYLLABLE ZA +1083F ; AL # Lo CYPRIOT SYLLABLE ZO +10840..10855 ; AL # Lo [22] IMPERIAL ARAMAIC LETTER ALEPH..IMPERIAL ARAMAIC LETTER TAW +10857 ; BA # Po IMPERIAL ARAMAIC SECTION SIGN +10858..1085F ; AL # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10860..10876 ; AL # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10877..10878 ; AL # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10879..1087F ; AL # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +10880..1089E ; AL # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108A7..108AF ; AL # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108E0..108F2 ; AL # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; AL # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +108FB..108FF ; AL # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10900..10915 ; AL # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916..1091B ; AL # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +1091F ; BA # Po PHOENICIAN WORD SEPARATOR +10920..10939 ; AL # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +1093F ; AL # Po LYDIAN TRIANGULAR MARK +10980..1099F ; AL # Lo [32] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2 +109A0..109B7 ; AL # Lo [24] MEROITIC CURSIVE LETTER A..MEROITIC CURSIVE LETTER DA +109BC..109BD ; AL # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109BE..109BF ; AL # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +109C0..109CF ; AL # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; AL # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A00 ; AL # Lo KHAROSHTHI LETTER A +10A01..10A03 ; CM # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; CM # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; CM # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A10..10A13 ; AL # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; AL # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; AL # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A38..10A3A ; CM # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; CM # Mn KHAROSHTHI VIRAMA +10A40..10A48 ; AL # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A50..10A57 ; BA # Po [8] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION DOUBLE DANDA +10A58 ; AL # Po KHAROSHTHI PUNCTUATION LINES +10A60..10A7C ; AL # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A7D..10A7E ; AL # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A7F ; AL # Po OLD SOUTH ARABIAN NUMERIC INDICATOR +10A80..10A9C ; AL # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10A9D..10A9F ; AL # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AC0..10AC7 ; AL # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC8 ; AL # So MANICHAEAN SIGN UD +10AC9..10AE4 ; AL # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10AE5..10AE6 ; CM # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10AEB..10AEF ; AL # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10AF0..10AF5 ; BA # Po [6] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION TWO DOTS +10AF6 ; IN # Po MANICHAEAN PUNCTUATION LINE FILLER +10B00..10B35 ; AL # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B39..10B3F ; BA # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B40..10B55 ; AL # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B58..10B5F ; AL # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B60..10B72 ; AL # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B78..10B7F ; AL # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10B80..10B91 ; AL # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10B99..10B9C ; AL # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10BA9..10BAF ; AL # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10C00..10C48 ; AL # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C80..10CB2 ; AL # Lu [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +10CC0..10CF2 ; AL # Ll [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +10CFA..10CFF ; AL # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10D00..10D23 ; AL # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10D24..10D27 ; CM # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10D30..10D39 ; NU # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +10E60..10E7E ; AL # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E80..10EA9 ; AL # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EAB..10EAC ; CM # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EAD ; BA # Pd YEZIDI HYPHENATION MARK +10EB0..10EB1 ; AL # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10EFD..10EFF ; CM # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F00..10F1C ; AL # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F1D..10F26 ; AL # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F27 ; AL # Lo OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; AL # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F46..10F50 ; CM # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F51..10F54 ; AL # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10F55..10F59 ; AL # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F70..10F81 ; AL # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10F82..10F85 ; CM # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +10F86..10F89 ; AL # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +10FB0..10FC4 ; AL # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FC5..10FCB ; AL # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +10FE0..10FF6 ; AL # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11000 ; CM # Mc BRAHMI SIGN CANDRABINDU +11001 ; CM # Mn BRAHMI SIGN ANUSVARA +11002 ; CM # Mc BRAHMI SIGN VISARGA +11003..11004 ; AP # Lo [2] BRAHMI SIGN JIHVAMULIYA..BRAHMI SIGN UPADHMANIYA +11005..11037 ; AK # Lo [51] BRAHMI LETTER A..BRAHMI LETTER OLD TAMIL NNNA +11038..11045 ; CM # Mn [14] BRAHMI VOWEL SIGN AA..BRAHMI VOWEL SIGN AU +11046 ; VI # Mn BRAHMI VIRAMA +11047..11048 ; BA # Po [2] BRAHMI DANDA..BRAHMI DOUBLE DANDA +11049..1104D ; ID # Po [5] BRAHMI PUNCTUATION DOT..BRAHMI PUNCTUATION LOTUS +11052..11065 ; ID # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +11066..1106F ; AS # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +11070 ; CM # Mn BRAHMI SIGN OLD TAMIL VIRAMA +11071..11072 ; AK # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11073..11074 ; CM # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +11075 ; AK # Lo BRAHMI LETTER OLD TAMIL LLA +1107F ; GL # Mn BRAHMI NUMBER JOINER +11080..11081 ; CM # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA +11082 ; CM # Mc KAITHI SIGN VISARGA +11083..110AF ; AL # Lo [45] KAITHI LETTER A..KAITHI LETTER HA +110B0..110B2 ; CM # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B3..110B6 ; CM # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B7..110B8 ; CM # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +110B9..110BA ; CM # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110BB..110BC ; AL # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BD ; NU # Cf KAITHI NUMBER SIGN +110BE..110C1 ; BA # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +110C2 ; CM # Mn KAITHI VOWEL SIGN VOCALIC R +110CD ; NU # Cf KAITHI NUMBER SIGN ABOVE +110D0..110E8 ; AL # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +110F0..110F9 ; NU # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11100..11102 ; CM # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11103..11126 ; AL # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11127..1112B ; CM # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112C ; CM # Mc CHAKMA VOWEL SIGN E +1112D..11134 ; CM # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11136..1113F ; NU # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +11140..11143 ; BA # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11144 ; AL # Lo CHAKMA LETTER LHAA +11145..11146 ; CM # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11147 ; AL # Lo CHAKMA LETTER VAA +11150..11172 ; AL # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11173 ; CM # Mn MAHAJANI SIGN NUKTA +11174 ; AL # Po MAHAJANI ABBREVIATION SIGN +11175 ; BB # Po MAHAJANI SECTION MARK +11176 ; AL # Lo MAHAJANI LIGATURE SHRI +11180..11181 ; CM # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +11182 ; CM # Mc SHARADA SIGN VISARGA +11183..111B2 ; AL # Lo [48] SHARADA LETTER A..SHARADA LETTER HA +111B3..111B5 ; CM # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111B6..111BE ; CM # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111BF..111C0 ; CM # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111C1..111C4 ; AL # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111C5..111C6 ; BA # Po [2] SHARADA DANDA..SHARADA DOUBLE DANDA +111C7 ; AL # Po SHARADA ABBREVIATION SIGN +111C8 ; BA # Po SHARADA SEPARATOR +111C9..111CC ; CM # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CD ; AL # Po SHARADA SUTRA MARK +111CE ; CM # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF ; CM # Mn SHARADA SIGN INVERTED CANDRABINDU +111D0..111D9 ; NU # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +111DA ; AL # Lo SHARADA EKAM +111DB ; BB # Po SHARADA SIGN SIDDHAM +111DC ; AL # Lo SHARADA HEADSTROKE +111DD..111DF ; BA # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +111E1..111F4 ; AL # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +11200..11211 ; AL # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; AL # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1122C..1122E ; CM # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +1122F..11231 ; CM # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11232..11233 ; CM # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11234 ; CM # Mn KHOJKI SIGN ANUSVARA +11235 ; CM # Mc KHOJKI SIGN VIRAMA +11236..11237 ; CM # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +11238..11239 ; BA # Po [2] KHOJKI DANDA..KHOJKI DOUBLE DANDA +1123A ; AL # Po KHOJKI WORD SEPARATOR +1123B..1123C ; BA # Po [2] KHOJKI SECTION MARK..KHOJKI DOUBLE SECTION MARK +1123D ; AL # Po KHOJKI ABBREVIATION SIGN +1123E ; CM # Mn KHOJKI SIGN SUKUN +1123F..11240 ; AL # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11241 ; CM # Mn KHOJKI VOWEL SIGN VOCALIC R +11280..11286 ; AL # Lo [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; AL # Lo MULTANI LETTER GHA +1128A..1128D ; AL # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; AL # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; AL # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112A9 ; BA # Po MULTANI SECTION MARK +112B0..112DE ; AL # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +112DF ; CM # Mn KHUDAWADI SIGN ANUSVARA +112E0..112E2 ; CM # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +112E3..112EA ; CM # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +112F0..112F9 ; NU # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11300..11301 ; CM # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +11302..11303 ; CM # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +11305..1130C ; AK # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; AK # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; AK # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; AK # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; AK # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; AK # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133B..1133C ; CM # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +1133D ; BA # Lo GRANTHA SIGN AVAGRAHA +1133E..1133F ; CM # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11340 ; CM # Mn GRANTHA VOWEL SIGN II +11341..11344 ; CM # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; CM # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134C ; CM # Mc [2] GRANTHA VOWEL SIGN OO..GRANTHA VOWEL SIGN AU +1134D ; VI # Mc GRANTHA SIGN VIRAMA +11350 ; AS # Lo GRANTHA OM +11357 ; CM # Mc GRANTHA AU LENGTH MARK +1135D ; BA # Lo GRANTHA SIGN PLUTA +1135E..1135F ; AS # Lo [2] GRANTHA LETTER VEDIC ANUSVARA..GRANTHA LETTER VEDIC DOUBLE ANUSVARA +11360..11361 ; AK # Lo [2] GRANTHA LETTER VOCALIC RR..GRANTHA LETTER VOCALIC LL +11362..11363 ; CM # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11366..1136C ; CM # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; CM # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11400..11434 ; AL # Lo [53] NEWA LETTER A..NEWA LETTER HA +11435..11437 ; CM # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11438..1143F ; CM # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11440..11441 ; CM # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11442..11444 ; CM # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11445 ; CM # Mc NEWA SIGN VISARGA +11446 ; CM # Mn NEWA SIGN NUKTA +11447..1144A ; AL # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1144B..1144E ; BA # Po [4] NEWA DANDA..NEWA GAP FILLER +1144F ; AL # Po NEWA ABBREVIATION SIGN +11450..11459 ; NU # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +1145A..1145B ; BA # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; AL # Po NEWA INSERTION SIGN +1145E ; CM # Mn NEWA SANDHI MARK +1145F..11461 ; AL # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; AL # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114B0..114B2 ; CM # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B3..114B8 ; CM # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114B9 ; CM # Mc TIRHUTA VOWEL SIGN E +114BA ; CM # Mn TIRHUTA VOWEL SIGN SHORT E +114BB..114BE ; CM # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114BF..114C0 ; CM # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C1 ; CM # Mc TIRHUTA SIGN VISARGA +114C2..114C3 ; CM # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +114C4..114C5 ; AL # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C6 ; AL # Po TIRHUTA ABBREVIATION SIGN +114C7 ; AL # Lo TIRHUTA OM +114D0..114D9 ; NU # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11580..115AE ; AL # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115AF..115B1 ; CM # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B2..115B5 ; CM # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115B8..115BB ; CM # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BC..115BD ; CM # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BE ; CM # Mc SIDDHAM SIGN VISARGA +115BF..115C0 ; CM # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115C1 ; BB # Po SIDDHAM SIGN SIDDHAM +115C2..115C3 ; BA # Po [2] SIDDHAM DANDA..SIDDHAM DOUBLE DANDA +115C4..115C5 ; EX # Po [2] SIDDHAM SEPARATOR DOT..SIDDHAM SEPARATOR BAR +115C6..115C8 ; AL # Po [3] SIDDHAM REPETITION MARK-1..SIDDHAM REPETITION MARK-3 +115C9..115D7 ; BA # Po [15] SIDDHAM END OF TEXT MARK..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +115D8..115DB ; AL # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +115DC..115DD ; CM # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11600..1162F ; AL # Lo [48] MODI LETTER A..MODI LETTER LLA +11630..11632 ; CM # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +11633..1163A ; CM # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163B..1163C ; CM # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163D ; CM # Mn MODI SIGN ANUSVARA +1163E ; CM # Mc MODI SIGN VISARGA +1163F..11640 ; CM # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +11641..11642 ; BA # Po [2] MODI DANDA..MODI DOUBLE DANDA +11643 ; AL # Po MODI ABBREVIATION SIGN +11644 ; AL # Lo MODI SIGN HUVA +11650..11659 ; NU # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE +11660..1166C ; BB # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +11680..116AA ; AL # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA +116AB ; CM # Mn TAKRI SIGN ANUSVARA +116AC ; CM # Mc TAKRI SIGN VISARGA +116AD ; CM # Mn TAKRI VOWEL SIGN AA +116AE..116AF ; CM # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B0..116B5 ; CM # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B6 ; CM # Mc TAKRI SIGN VIRAMA +116B7 ; CM # Mn TAKRI SIGN NUKTA +116B8 ; AL # Lo TAKRI LETTER ARCHAIC KHA +116B9 ; AL # Po TAKRI ABBREVIATION SIGN +116C0..116C9 ; NU # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11700..1171A ; SA # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +1171D..1171F ; SA # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11720..11721 ; SA # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11722..11725 ; SA # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11726 ; SA # Mc AHOM VOWEL SIGN E +11727..1172B ; SA # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +11730..11739 ; NU # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +1173A..1173B ; SA # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +1173C..1173E ; BA # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1173F ; SA # So AHOM SYMBOL VI +11740..11746 ; SA # Lo [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; AL # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA +1182C..1182E ; CM # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +1182F..11837 ; CM # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11838 ; CM # Mc DOGRA SIGN VISARGA +11839..1183A ; CM # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1183B ; AL # Po DOGRA ABBREVIATION SIGN +118A0..118DF ; AL # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +118E0..118E9 ; NU # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +118EA..118F2 ; AL # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +118FF ; AL # Lo WARANG CITI OM +11900..11906 ; AK # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E +11909 ; AK # Lo DIVES AKURU LETTER O +1190C..11913 ; AK # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; AK # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; AK # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +11930..11935 ; CM # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; CM # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C ; CM # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D ; CM # Mc DIVES AKURU SIGN HALANTA +1193E ; VI # Mn DIVES AKURU VIRAMA +1193F ; AP # Lo DIVES AKURU PREFIXED NASAL SIGN +11940 ; CM # Mc DIVES AKURU MEDIAL YA +11941 ; AP # Lo DIVES AKURU INITIAL RA +11942 ; CM # Mc DIVES AKURU MEDIAL RA +11943 ; CM # Mn DIVES AKURU SIGN NUKTA +11944..11946 ; BA # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +11950..11959 ; ID # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +119A0..119A7 ; AL # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; AL # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119D1..119D3 ; CM # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119D4..119D7 ; CM # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; CM # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119DC..119DF ; CM # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E0 ; CM # Mn NANDINAGARI SIGN VIRAMA +119E1 ; AL # Lo NANDINAGARI SIGN AVAGRAHA +119E2 ; BB # Po NANDINAGARI SIGN SIDDHAM +119E3 ; AL # Lo NANDINAGARI HEADSTROKE +119E4 ; CM # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A00 ; AL # Lo ZANABAZAR SQUARE LETTER A +11A01..11A0A ; CM # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A0B..11A32 ; AL # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A33..11A38 ; CM # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A39 ; CM # Mc ZANABAZAR SQUARE SIGN VISARGA +11A3A ; AL # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A3B..11A3E ; CM # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A3F ; BB # Po ZANABAZAR SQUARE INITIAL HEAD MARK +11A40 ; AL # Po ZANABAZAR SQUARE CLOSING HEAD MARK +11A41..11A44 ; BA # Po [4] ZANABAZAR SQUARE MARK TSHEG..ZANABAZAR SQUARE MARK LONG TSHEG +11A45 ; BB # Po ZANABAZAR SQUARE INITIAL DOUBLE-LINED HEAD MARK +11A46 ; AL # Po ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A47 ; CM # Mn ZANABAZAR SQUARE SUBJOINER +11A50 ; AL # Lo SOYOMBO LETTER A +11A51..11A56 ; CM # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A57..11A58 ; CM # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A59..11A5B ; CM # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A5C..11A89 ; AL # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A8A..11A96 ; CM # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A97 ; CM # Mc SOYOMBO SIGN VISARGA +11A98..11A99 ; CM # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11A9A..11A9C ; BA # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9D ; AL # Lo SOYOMBO MARK PLUTA +11A9E..11AA0 ; BB # Po [3] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO HEAD MARK WITH MOON AND SUN +11AA1..11AA2 ; BA # Po [2] SOYOMBO TERMINAL MARK-1..SOYOMBO TERMINAL MARK-2 +11AB0..11ABF ; AL # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA +11AC0..11AF8 ; AL # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B00..11B09 ; BB # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C00..11C08 ; AL # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; AL # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C2F ; CM # Mc BHAIKSUKI VOWEL SIGN AA +11C30..11C36 ; CM # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; CM # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3E ; CM # Mc BHAIKSUKI SIGN VISARGA +11C3F ; CM # Mn BHAIKSUKI SIGN VIRAMA +11C40 ; AL # Lo BHAIKSUKI SIGN AVAGRAHA +11C41..11C45 ; BA # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C50..11C59 ; NU # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11C5A..11C6C ; AL # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11C70 ; BB # Po MARCHEN HEAD MARK +11C71 ; EX # Po MARCHEN MARK SHAD +11C72..11C8F ; AL # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A +11C92..11CA7 ; CM # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CA9 ; CM # Mc MARCHEN SUBJOINED LETTER YA +11CAA..11CB0 ; CM # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB1 ; CM # Mc MARCHEN VOWEL SIGN I +11CB2..11CB3 ; CM # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB4 ; CM # Mc MARCHEN VOWEL SIGN O +11CB5..11CB6 ; CM # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D00..11D06 ; AL # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; AL # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; AL # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D31..11D36 ; CM # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; CM # Mn MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; CM # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; CM # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D46 ; AL # Lo MASARAM GONDI REPHA +11D47 ; CM # Mn MASARAM GONDI RA-KARA +11D50..11D59 ; NU # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11D60..11D65 ; AL # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; AL # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; AL # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D8A..11D8E ; CM # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; CM # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D94 ; CM # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D95 ; CM # Mn GUNJALA GONDI SIGN ANUSVARA +11D96 ; CM # Mc GUNJALA GONDI SIGN VISARGA +11D97 ; CM # Mn GUNJALA GONDI VIRAMA +11D98 ; AL # Lo GUNJALA GONDI OM +11DA0..11DA9 ; NU # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11EE0..11EF1 ; AS # Lo [18] MAKASAR LETTER KA..MAKASAR LETTER A +11EF2 ; BA # Lo MAKASAR ANGKA +11EF3..11EF4 ; CM # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11EF5..11EF6 ; CM # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11EF7..11EF8 ; BA # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F00..11F01 ; CM # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F02 ; AP # Lo KAWI SIGN REPHA +11F03 ; CM # Mc KAWI SIGN VISARGA +11F04..11F10 ; AK # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; AK # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11F34..11F35 ; CM # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A ; CM # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F ; CM # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40 ; CM # Mn KAWI VOWEL SIGN EU +11F41 ; CM # Mc KAWI SIGN KILLER +11F42 ; VI # Mn KAWI CONJOINER +11F43..11F44 ; BA # Po [2] KAWI DANDA..KAWI DOUBLE DANDA +11F45..11F4F ; ID # Po [11] KAWI PUNCTUATION SECTION MARKER..KAWI PUNCTUATION CLOSING SPIRAL +11F50..11F59 ; AS # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +11FB0 ; AL # Lo LISU LETTER YHA +11FC0..11FD4 ; AL # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +11FD5..11FDC ; AL # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FDD..11FE0 ; PO # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +11FE1..11FF1 ; AL # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +11FFF ; BA # Po TAMIL PUNCTUATION END OF TEXT +12000..12399 ; AL # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12400..1246E ; AL # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM +12470..12474 ; BA # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12480..12543 ; AL # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; AL # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +12FF1..12FF2 ; AL # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +13000..13257 ; AL # Lo [600] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH O006 +13258..1325A ; OP # Lo [3] EGYPTIAN HIEROGLYPH O006A..EGYPTIAN HIEROGLYPH O006C +1325B..1325D ; CL # Lo [3] EGYPTIAN HIEROGLYPH O006D..EGYPTIAN HIEROGLYPH O006F +1325E..13281 ; AL # Lo [36] EGYPTIAN HIEROGLYPH O007..EGYPTIAN HIEROGLYPH O033 +13282 ; CL # Lo EGYPTIAN HIEROGLYPH O033A +13283..13285 ; AL # Lo [3] EGYPTIAN HIEROGLYPH O034..EGYPTIAN HIEROGLYPH O036 +13286 ; OP # Lo EGYPTIAN HIEROGLYPH O036A +13287 ; CL # Lo EGYPTIAN HIEROGLYPH O036B +13288 ; OP # Lo EGYPTIAN HIEROGLYPH O036C +13289 ; CL # Lo EGYPTIAN HIEROGLYPH O036D +1328A..13378 ; AL # Lo [239] EGYPTIAN HIEROGLYPH O037..EGYPTIAN HIEROGLYPH V011 +13379 ; OP # Lo EGYPTIAN HIEROGLYPH V011A +1337A..1337B ; CL # Lo [2] EGYPTIAN HIEROGLYPH V011B..EGYPTIAN HIEROGLYPH V011C +1337C..1342E ; AL # Lo [179] EGYPTIAN HIEROGLYPH V012..EGYPTIAN HIEROGLYPH AA032 +1342F ; OP # Lo EGYPTIAN HIEROGLYPH V011D +13430..13436 ; GL # Cf [7] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH OVERLAY MIDDLE +13437 ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN SEGMENT +13438 ; CL # Cf EGYPTIAN HIEROGLYPH END SEGMENT +13439..1343B ; GL # Cf [3] EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH INSERT AT BOTTOM +1343C ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN ENCLOSURE +1343D ; CL # Cf EGYPTIAN HIEROGLYPH END ENCLOSURE +1343E ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN WALLED ENCLOSURE +1343F ; CL # Cf EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +13440 ; CM # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13441..13446 ; AL # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +13447..13455 ; CM # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +14400..145CD ; AL # Lo [462] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A409 +145CE ; OP # Lo ANATOLIAN HIEROGLYPH A410 BEGIN LOGOGRAM MARK +145CF ; CL # Lo ANATOLIAN HIEROGLYPH A410A END LOGOGRAM MARK +145D0..14646 ; AL # Lo [119] ANATOLIAN HIEROGLYPH A411..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; AL # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; AL # Lo [31] MRO LETTER TA..MRO LETTER TEK +16A60..16A69 ; NU # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE +16A6E..16A6F ; BA # Po [2] MRO DANDA..MRO DOUBLE DANDA +16A70..16ABE ; AL # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AC0..16AC9 ; NU # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16AD0..16AED ; AL # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16AF0..16AF4 ; CM # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16AF5 ; BA # Po BASSA VAH FULL STOP +16B00..16B2F ; AL # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B30..16B36 ; CM # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16B37..16B39 ; BA # Po [3] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN CIM CHEEM +16B3A..16B3B ; AL # Po [2] PAHAWH HMONG SIGN VOS THIAB..PAHAWH HMONG SIGN VOS FEEM +16B3C..16B3F ; AL # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B40..16B43 ; AL # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16B44 ; BA # Po PAHAWH HMONG SIGN XAUS +16B45 ; AL # So PAHAWH HMONG SIGN CIM TSOV ROG +16B50..16B59 ; NU # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +16B5B..16B61 ; AL # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16B63..16B77 ; AL # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; AL # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16E40..16E7F ; AL # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16E80..16E96 ; AL # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +16E97..16E98 ; BA # Po [2] MEDEFAIDRIN COMMA..MEDEFAIDRIN FULL STOP +16E99..16E9A ; AL # Po [2] MEDEFAIDRIN SYMBOL AIVA..MEDEFAIDRIN EXCLAMATION OH +16F00..16F4A ; AL # Lo [75] MIAO LETTER PA..MIAO LETTER RTE +16F4F ; CM # Mn MIAO SIGN CONSONANT MODIFIER BAR +16F50 ; AL # Lo MIAO LETTER NASALIZATION +16F51..16F87 ; CM # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16F8F..16F92 ; CM # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW +16F93..16F9F ; AL # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; NS # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE2 ; NS # Po OLD CHINESE HOOK MARK +16FE3 ; NS # Lm OLD CHINESE ITERATION MARK +16FE4 ; GL # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1 ; CM # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +17000..187F7 ; ID # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18AFF ; ID # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 +18B00..18CD5 ; AL # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; ID # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1AFF0..1AFF3 ; AL # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; AL # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; AL # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1B000..1B0FF ; ID # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 +1B100..1B122 ; ID # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU +1B132 ; CJ # Lo HIRAGANA LETTER SMALL KO +1B150..1B152 ; CJ # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; CJ # Lo KATAKANA LETTER SMALL KO +1B164..1B167 ; CJ # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; ID # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; AL # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; AL # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; AL # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; AL # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1BC9C ; AL # So DUPLOYAN SIGN O WITH CROSS +1BC9D..1BC9E ; CM # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1BC9F ; BA # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1BCA0..1BCA3 ; CM # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1CF00..1CF2D ; CM # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; CM # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1CF50..1CFC3 ; AL # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; AL # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; AL # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; AL # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D165..1D166 ; CM # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D167..1D169 ; CM # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D16A..1D16C ; AL # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D16D..1D172 ; CM # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 +1D173..1D17A ; CM # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +1D17B..1D182 ; CM # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D183..1D184 ; AL # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D185..1D18B ; CM # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D18C..1D1A9 ; AL # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AA..1D1AD ; CM # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D1AE..1D1EA ; AL # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; AL # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D242..1D244 ; CM # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1D245 ; AL # So GREEK MUSICAL LEIMMA +1D2C0..1D2D3 ; AL # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; AL # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D300..1D356 ; AL # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D360..1D378 ; AL # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1D400..1D454 ; AL # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D456..1D49C ; AL # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; AL # Lu [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; AL # Lu MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; AL # Lu [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; AL # Lu [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B9 ; AL # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BB ; AL # Ll MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; AL # Ll [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D505 ; AL # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; AL # Lu [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; AL # Lu [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; AL # Lu [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51E..1D539 ; AL # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; AL # Lu [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; AL # Lu [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; AL # Lu MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; AL # Lu [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D552..1D6A5 ; AL # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A8..1D6C0 ; AL # Lu [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6C1 ; AL # Sm MATHEMATICAL BOLD NABLA +1D6C2..1D6DA ; AL # Ll [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DB ; AL # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6DC..1D6FA ; AL # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA +1D6FB ; AL # Sm MATHEMATICAL ITALIC NABLA +1D6FC..1D714 ; AL # Ll [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D715 ; AL # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D716..1D734 ; AL # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D735 ; AL # Sm MATHEMATICAL BOLD ITALIC NABLA +1D736..1D74E ; AL # Ll [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D74F ; AL # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D750..1D76E ; AL # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D76F ; AL # Sm MATHEMATICAL SANS-SERIF BOLD NABLA +1D770..1D788 ; AL # Ll [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D789 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D78A..1D7A8 ; AL # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7A9 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7AA..1D7C2 ; AL # Ll [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C3 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1D7C4..1D7CB ; AL # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA +1D7CE..1D7FF ; NU # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1D800..1D9FF ; AL # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA00..1DA36 ; CM # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA37..1DA3A ; AL # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA3B..1DA6C ; CM # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA6D..1DA74 ; AL # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA75 ; CM # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA76..1DA83 ; AL # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA84 ; CM # Mn SIGNWRITING LOCATION HEAD NECK +1DA85..1DA86 ; AL # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1DA87..1DA8A ; BA # Po [4] SIGNWRITING COMMA..SIGNWRITING COLON +1DA8B ; AL # Po SIGNWRITING PARENTHESIS +1DA9B..1DA9F ; CM # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; CM # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1DF00..1DF09 ; AL # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0A ; AL # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1DF0B..1DF1E ; AL # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; AL # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E000..1E006 ; CM # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; CM # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; CM # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; CM # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; CM # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E030..1E06D ; AL # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E08F ; CM # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E100..1E12C ; AL # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E130..1E136 ; CM # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E137..1E13D ; AL # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E140..1E149 ; NU # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E14E ; AL # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E14F ; AL # So NYIAKENG PUACHUE HMONG CIRCLED CA +1E290..1E2AD ; AL # Lo [30] TOTO LETTER PA..TOTO LETTER A +1E2AE ; CM # Mn TOTO SIGN RISING TONE +1E2C0..1E2EB ; AL # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E2EC..1E2EF ; CM # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E2F0..1E2F9 ; NU # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E2FF ; PR # Sc WANCHO NGUN SIGN +1E4D0..1E4EA ; AL # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB ; AL # Lm NAG MUNDARI SIGN OJOD +1E4EC..1E4EF ; CM # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E4F0..1E4F9 ; NU # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E7E0..1E7E6 ; AL # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; AL # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; AL # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; AL # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; AL # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1E8C7..1E8CF ; AL # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1E8D0..1E8D6 ; CM # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E900..1E943 ; AL # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA +1E944..1E94A ; CM # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +1E94B ; AL # Lm ADLAM NASALIZATION MARK +1E950..1E959 ; NU # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1E95E..1E95F ; OP # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK +1EC71..1ECAB ; AL # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAC ; PO # So INDIC SIYAQ PLACEHOLDER +1ECAD..1ECAF ; AL # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB0 ; PO # Sc INDIC SIYAQ RUPEE MARK +1ECB1..1ECB4 ; AL # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; AL # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2E ; AL # So OTTOMAN SIYAQ MARRATAN +1ED2F..1ED3D ; AL # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1EE00..1EE03 ; AL # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; AL # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; AL # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; AL # Lo ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; AL # Lo ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; AL # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; AL # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; AL # Lo ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; AL # Lo ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; AL # Lo ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; AL # Lo ARABIC MATHEMATICAL TAILED HAH +1EE49 ; AL # Lo ARABIC MATHEMATICAL TAILED YEH +1EE4B ; AL # Lo ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; AL # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; AL # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; AL # Lo ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; AL # Lo ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; AL # Lo ARABIC MATHEMATICAL TAILED DAD +1EE5B ; AL # Lo ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; AL # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; AL # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; AL # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; AL # Lo ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; AL # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; AL # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; AL # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; AL # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; AL # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; AL # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; AL # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1EEF0..1EEF1 ; AL # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F000..1F02B ; ID # So [44] MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F02C..1F02F ; ID # Cn [4] .. +1F030..1F093 ; ID # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F094..1F09F ; ID # Cn [12] .. +1F0A0..1F0AE ; ID # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0AF..1F0B0 ; ID # Cn [2] .. +1F0B1..1F0BF ; ID # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C0 ; ID # Cn +1F0C1..1F0CF ; ID # So [15] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER +1F0D0 ; ID # Cn +1F0D1..1F0F5 ; ID # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F0F6..1F0FF ; ID # Cn [10] .. +1F100..1F10C ; AI # No [13] DIGIT ZERO FULL STOP..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO +1F10D..1F10F ; ID # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F110..1F12D ; AI # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD +1F12E..1F12F ; AL # So [2] CIRCLED WZ..COPYLEFT SYMBOL +1F130..1F169 ; AI # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z +1F16A..1F16C ; AL # So [3] RAISED MC SIGN..RAISED MR SIGN +1F16D..1F16F ; ID # So [3] CIRCLED CC..CIRCLED HUMAN FIGURE +1F170..1F1AC ; AI # So [61] NEGATIVE SQUARED LATIN CAPITAL LETTER A..SQUARED VOD +1F1AD ; ID # So MASK WORK SYMBOL +1F1AE..1F1E5 ; ID # Cn [56] .. +1F1E6..1F1FF ; RI # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z +1F200..1F202 ; ID # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA +1F203..1F20F ; ID # Cn [13] .. +1F210..1F23B ; ID # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F23C..1F23F ; ID # Cn [4] .. +1F240..1F248 ; ID # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F249..1F24F ; ID # Cn [7] .. +1F250..1F251 ; ID # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F252..1F25F ; ID # Cn [14] .. +1F260..1F265 ; ID # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F266..1F2FF ; ID # Cn [154] .. +1F300..1F384 ; ID # So [133] CYCLONE..CHRISTMAS TREE +1F385 ; EB # So FATHER CHRISTMAS +1F386..1F39B ; ID # So [22] FIREWORKS..CONTROL KNOBS +1F39C..1F39D ; AL # So [2] BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F3B4 ; ID # So [23] FILM FRAMES..FLOWER PLAYING CARDS +1F3B5..1F3B6 ; AL # So [2] MUSICAL NOTE..MULTIPLE MUSICAL NOTES +1F3B7..1F3BB ; ID # So [5] SAXOPHONE..VIOLIN +1F3BC ; AL # So MUSICAL SCORE +1F3BD..1F3C1 ; ID # So [5] RUNNING SHIRT WITH SASH..CHEQUERED FLAG +1F3C2..1F3C4 ; EB # So [3] SNOWBOARDER..SURFER +1F3C5..1F3C6 ; ID # So [2] SPORTS MEDAL..TROPHY +1F3C7 ; EB # So HORSE RACING +1F3C8..1F3C9 ; ID # So [2] AMERICAN FOOTBALL..RUGBY FOOTBALL +1F3CA..1F3CC ; EB # So [3] SWIMMER..GOLFER +1F3CD..1F3FA ; ID # So [46] RACING MOTORCYCLE..AMPHORA +1F3FB..1F3FF ; EM # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 +1F400..1F441 ; ID # So [66] RAT..EYE +1F442..1F443 ; EB # So [2] EAR..NOSE +1F444..1F445 ; ID # So [2] MOUTH..TONGUE +1F446..1F450 ; EB # So [11] WHITE UP POINTING BACKHAND INDEX..OPEN HANDS SIGN +1F451..1F465 ; ID # So [21] CROWN..BUSTS IN SILHOUETTE +1F466..1F478 ; EB # So [19] BOY..PRINCESS +1F479..1F47B ; ID # So [3] JAPANESE OGRE..GHOST +1F47C ; EB # So BABY ANGEL +1F47D..1F480 ; ID # So [4] EXTRATERRESTRIAL ALIEN..SKULL +1F481..1F483 ; EB # So [3] INFORMATION DESK PERSON..DANCER +1F484 ; ID # So LIPSTICK +1F485..1F487 ; EB # So [3] NAIL POLISH..HAIRCUT +1F488..1F48E ; ID # So [7] BARBER POLE..GEM STONE +1F48F ; EB # So KISS +1F490 ; ID # So BOUQUET +1F491 ; EB # So COUPLE WITH HEART +1F492..1F49F ; ID # So [14] WEDDING..HEART DECORATION +1F4A0 ; AL # So DIAMOND SHAPE WITH A DOT INSIDE +1F4A1 ; ID # So ELECTRIC LIGHT BULB +1F4A2 ; AL # So ANGER SYMBOL +1F4A3 ; ID # So BOMB +1F4A4 ; AL # So SLEEPING SYMBOL +1F4A5..1F4A9 ; ID # So [5] COLLISION SYMBOL..PILE OF POO +1F4AA ; EB # So FLEXED BICEPS +1F4AB..1F4AE ; ID # So [4] DIZZY SYMBOL..WHITE FLOWER +1F4AF ; AL # So HUNDRED POINTS SYMBOL +1F4B0 ; ID # So MONEY BAG +1F4B1..1F4B2 ; AL # So [2] CURRENCY EXCHANGE..HEAVY DOLLAR SIGN +1F4B3..1F4FF ; ID # So [77] CREDIT CARD..PRAYER BEADS +1F500..1F506 ; AL # So [7] TWISTED RIGHTWARDS ARROWS..HIGH BRIGHTNESS SYMBOL +1F507..1F516 ; ID # So [16] SPEAKER WITH CANCELLATION STROKE..BOOKMARK +1F517..1F524 ; AL # So [14] LINK SYMBOL..INPUT SYMBOL FOR LATIN LETTERS +1F525..1F531 ; ID # So [13] FIRE..TRIDENT EMBLEM +1F532..1F549 ; AL # So [24] BLACK SQUARE BUTTON..OM SYMBOL +1F54A..1F573 ; ID # So [42] DOVE OF PEACE..HOLE +1F574..1F575 ; EB # So [2] MAN IN BUSINESS SUIT LEVITATING..SLEUTH OR SPY +1F576..1F579 ; ID # So [4] DARK SUNGLASSES..JOYSTICK +1F57A ; EB # So MAN DANCING +1F57B..1F58F ; ID # So [21] LEFT HAND TELEPHONE RECEIVER..TURNED OK HAND SIGN +1F590 ; EB # So RAISED HAND WITH FINGERS SPLAYED +1F591..1F594 ; ID # So [4] REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; EB # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS +1F597..1F5D3 ; ID # So [61] WHITE DOWN POINTING LEFT HAND INDEX..SPIRAL CALENDAR PAD +1F5D4..1F5DB ; AL # So [8] DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5F3 ; ID # So [24] COMPRESSION..BALLOT BOX WITH BALLOT +1F5F4..1F5F9 ; AL # So [6] BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA..1F5FF ; ID # So [6] WORLD MAP..MOYAI +1F600..1F644 ; ID # So [69] GRINNING FACE..FACE WITH ROLLING EYES +1F645..1F647 ; EB # So [3] FACE WITH NO GOOD GESTURE..PERSON BOWING DEEPLY +1F648..1F64A ; ID # So [3] SEE-NO-EVIL MONKEY..SPEAK-NO-EVIL MONKEY +1F64B..1F64F ; EB # So [5] HAPPY PERSON RAISING ONE HAND..PERSON WITH FOLDED HANDS +1F650..1F675 ; AL # So [38] NORTH WEST POINTING LEAF..SWASH AMPERSAND ORNAMENT +1F676..1F678 ; QU # So [3] SANS-SERIF HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT..SANS-SERIF HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT +1F679..1F67B ; NS # So [3] HEAVY INTERROBANG ORNAMENT..HEAVY SANS-SERIF INTERROBANG ORNAMENT +1F67C..1F67F ; AL # So [4] VERY HEAVY SOLIDUS..REVERSE CHECKER BOARD +1F680..1F6A2 ; ID # So [35] ROCKET..SHIP +1F6A3 ; EB # So ROWBOAT +1F6A4..1F6B3 ; ID # So [16] SPEEDBOAT..NO BICYCLES +1F6B4..1F6B6 ; EB # So [3] BICYCLIST..PEDESTRIAN +1F6B7..1F6BF ; ID # So [9] NO PEDESTRIANS..SHOWER +1F6C0 ; EB # So BATH +1F6C1..1F6CB ; ID # So [11] BATHTUB..COUCH AND LAMP +1F6CC ; EB # So SLEEPING ACCOMMODATION +1F6CD..1F6D7 ; ID # So [11] SHOPPING BAGS..ELEVATOR +1F6D8..1F6DB ; ID # Cn [4] .. +1F6DC..1F6EC ; ID # So [17] WIRELESS..AIRPLANE ARRIVING +1F6ED..1F6EF ; ID # Cn [3] .. +1F6F0..1F6FC ; ID # So [13] SATELLITE..ROLLER SKATE +1F6FD..1F6FF ; ID # Cn [3] .. +1F700..1F773 ; AL # So [116] ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE +1F774..1F776 ; ID # So [3] LOT OF FORTUNE..LUNAR ECLIPSE +1F777..1F77A ; ID # Cn [4] .. +1F77B..1F77F ; ID # So [5] HAUMEA..ORCUS +1F780..1F7D4 ; AL # So [85] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..HEAVY TWELVE POINTED PINWHEEL STAR +1F7D5..1F7D9 ; ID # So [5] CIRCLED TRIANGLE..NINE POINTED WHITE STAR +1F7DA..1F7DF ; ID # Cn [6] .. +1F7E0..1F7EB ; ID # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7EC..1F7EF ; ID # Cn [4] .. +1F7F0 ; ID # So HEAVY EQUALS SIGN +1F7F1..1F7FF ; ID # Cn [15] .. +1F800..1F80B ; AL # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F80C..1F80F ; ID # Cn [4] .. +1F810..1F847 ; AL # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F848..1F84F ; ID # Cn [8] .. +1F850..1F859 ; AL # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F85A..1F85F ; ID # Cn [6] .. +1F860..1F887 ; AL # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F888..1F88F ; ID # Cn [8] .. +1F890..1F8AD ; AL # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8AE..1F8AF ; ID # Cn [2] .. +1F8B0..1F8B1 ; ID # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F8B2..1F8FF ; ID # Cn [78] .. +1F900..1F90B ; AL # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT +1F90C ; EB # So PINCHED FINGERS +1F90D..1F90E ; ID # So [2] WHITE HEART..BROWN HEART +1F90F ; EB # So PINCHING HAND +1F910..1F917 ; ID # So [8] ZIPPER-MOUTH FACE..HUGGING FACE +1F918..1F91F ; EB # So [8] SIGN OF THE HORNS..I LOVE YOU HAND SIGN +1F920..1F925 ; ID # So [6] FACE WITH COWBOY HAT..LYING FACE +1F926 ; EB # So FACE PALM +1F927..1F92F ; ID # So [9] SNEEZING FACE..SHOCKED FACE WITH EXPLODING HEAD +1F930..1F939 ; EB # So [10] PREGNANT WOMAN..JUGGLING +1F93A..1F93B ; ID # So [2] FENCER..MODERN PENTATHLON +1F93C..1F93E ; EB # So [3] WRESTLERS..HANDBALL +1F93F..1F976 ; ID # So [56] DIVING MASK..FREEZING FACE +1F977 ; EB # So NINJA +1F978..1F9B4 ; ID # So [61] DISGUISED FACE..BONE +1F9B5..1F9B6 ; EB # So [2] LEG..FOOT +1F9B7 ; ID # So TOOTH +1F9B8..1F9B9 ; EB # So [2] SUPERHERO..SUPERVILLAIN +1F9BA ; ID # So SAFETY VEST +1F9BB ; EB # So EAR WITH HEARING AID +1F9BC..1F9CC ; ID # So [17] MOTORIZED WHEELCHAIR..TROLL +1F9CD..1F9CF ; EB # So [3] STANDING PERSON..DEAF PERSON +1F9D0 ; ID # So FACE WITH MONOCLE +1F9D1..1F9DD ; EB # So [13] ADULT..ELF +1F9DE..1F9FF ; ID # So [34] GENIE..NAZAR AMULET +1FA00..1FA53 ; AL # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP +1FA54..1FA5F ; ID # Cn [12] .. +1FA60..1FA6D ; ID # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA6E..1FA6F ; ID # Cn [2] .. +1FA70..1FA7C ; ID # So [13] BALLET SHOES..CRUTCH +1FA7D..1FA7F ; ID # Cn [3] .. +1FA80..1FA88 ; ID # So [9] YO-YO..FLUTE +1FA89..1FA8F ; ID # Cn [7] .. +1FA90..1FABD ; ID # So [46] RINGED PLANET..WING +1FABE ; ID # Cn +1FABF..1FAC2 ; ID # So [4] GOOSE..PEOPLE HUGGING +1FAC3..1FAC5 ; EB # So [3] PREGNANT MAN..PERSON WITH CROWN +1FAC6..1FACD ; ID # Cn [8] .. +1FACE..1FADB ; ID # So [14] MOOSE..PEA POD +1FADC..1FADF ; ID # Cn [4] .. +1FAE0..1FAE8 ; ID # So [9] MELTING FACE..SHAKING FACE +1FAE9..1FAEF ; ID # Cn [7] .. +1FAF0..1FAF8 ; EB # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FAF9..1FAFF ; ID # Cn [7] .. +1FB00..1FB92 ; AL # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; AL # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON +1FBF0..1FBF9 ; NU # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +1FC00..1FFFD ; ID # Cn [1022] .. +20000..2A6DF ; ID # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A6E0..2A6FF ; ID # Cn [32] .. +2A700..2B739 ; ID # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F ; ID # Cn [6] .. +2B740..2B81D ; ID # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B81E..2B81F ; ID # Cn [2] .. +2B820..2CEA1 ; ID # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEA2..2CEAF ; ID # Cn [14] .. +2CEB0..2EBE0 ; ID # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBE1..2EBEF ; ID # Cn [15] .. +2EBF0..2EE5D ; ID # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2EE5E..2F7FF ; ID # Cn [2466] .. +2F800..2FA1D ; ID # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +2FA1E..2FA1F ; ID # Cn [2] .. +2FA20..2FFFD ; ID # Cn [1502] .. +30000..3134A ; ID # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +3134B..3134F ; ID # Cn [5] .. +31350..323AF ; ID # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..3FFFD ; ID # Cn [56398] .. +E0001 ; CM # Cf LANGUAGE TAG +E0020..E007F ; CM # Cf [96] TAG SPACE..CANCEL TAG +E0100..E01EF ; CM # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 +F0000..FFFFD ; XX # Co [65534] .. +100000..10FFFD ; XX # Co [65534] .. + +# EOF diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs new file mode 100644 index 000000000..8b5115369 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -0,0 +1,382 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +using Lumina.Text; + +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass; +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory; +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Enumerates line break offsets. +internal ref struct LineBreakEnumerator +{ + private readonly UtfEnumeratorFlags enumeratorFlags; + + private UtfEnumerator enumerator; + private int dataLength; + private int currentByteOffsetDelta; + + private Entry class1; + private Entry class2; + + private Entry space1; + private Entry space2; + private bool spaceStreak; + + private int consecutiveRegionalIndicators; + + /// Initializes a new instance of the struct. + /// UTF-N byte sequence. + /// Flags to pass to sub-enumerator. + public LineBreakEnumerator( + ReadOnlySpan data, + UtfEnumeratorFlags enumeratorFlags = UtfEnumeratorFlags.Default) + { + this.enumerator = UtfEnumerator.From(data, enumeratorFlags); + this.enumeratorFlags = enumeratorFlags; + this.dataLength = data.Length; + } + + private LineBreakEnumerator( + int dataLength, + UtfEnumerator enumerator, + UtfEnumeratorFlags enumeratorFlags) + { + this.dataLength = dataLength; + this.enumerator = enumerator; + this.enumeratorFlags = enumeratorFlags; + } + + private enum LineBreakMode : byte + { + Prohibited, + Mandatory, + Optional, + } + + /// + public (int ByteOffset, bool Mandatory) Current { get; private set; } + + /// Gets a value indicating whether the end of the underlying span has been reached. + public bool Finished { get; private set; } + + /// Resumes enumeration with the given data. + /// The data. + /// Offset to add to .ByteOffset. + public void ResumeWith(ReadOnlySpan data, int offsetDelta) + { + this.enumerator = UtfEnumerator.From(data, this.enumeratorFlags); + this.dataLength = data.Length; + this.currentByteOffsetDelta = offsetDelta; + this.Finished = false; + } + + /// + [SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement", Justification = "No")] + public bool MoveNext() + { + if (this.Finished) + return false; + + while (this.enumerator.MoveNext()) + { + var effectiveInt = + this.enumerator.Current.IsSeStringPayload + ? UtfEnumerator.RepresentativeCharFor(this.enumerator.Current.MacroCode) + : this.enumerator.Current.EffectiveInt; + if (effectiveInt == -1) + continue; + + switch (this.HandleCharacter(effectiveInt)) + { + case LineBreakMode.Mandatory: + this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, true); + return true; + case LineBreakMode.Optional: + this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, false); + return true; + case LineBreakMode.Prohibited: + default: + continue; + } + } + + // Start and end of text: + // LB3 Always break at the end of text. + this.Current = (this.dataLength + this.currentByteOffsetDelta, true); + this.Finished = true; + return true; + } + + /// + public LineBreakEnumerator GetEnumerator() => + new(this.dataLength, this.enumerator.GetEnumerator(), this.enumeratorFlags); + + private LineBreakMode HandleCharacter(int c) + { + // https://unicode.org/reports/tr14/#Algorithm + + // 6.1 Non-tailorable Line Breaking Rules + // Resolve line breaking classes: + // LB1 Assign a line breaking class to each code point of the input. + // => done inside Entry ctor + var curr = new Entry(c); + var (prev1, prev2) = (this.class1, this.class2); + (this.class2, this.class1) = (this.class1, curr); + + if (curr.Class == RI) + this.consecutiveRegionalIndicators++; + else + this.consecutiveRegionalIndicators = 0; + + var (prevSpaceStreak, prevSpace1, prevSpace2) = (this.spaceStreak, this.space1, this.space2); + this.spaceStreak = curr.Class == SP; + if (this.spaceStreak && !prevSpaceStreak) + (this.space1, this.space2) = (prev1, prev2); + + if (!prevSpaceStreak) + (prevSpace1, prevSpace2) = (prev1, prev2); + + // Start and end of text: + // LB2 Never break at the start of text. + if (prev1.Class is sot) + return LineBreakMode.Prohibited; + + // Mandatory breaks: + // LB4 Always break after hard line breaks. + if (prev1.Class is BK) + return LineBreakMode.Mandatory; + + // LB5 Treat CR followed by LF, as well as CR, LF, and NL as hard line breaks. + if (prev2.Class is CR && prev1.Class is LF) + return LineBreakMode.Mandatory; + if (prev1.Class is CR && curr.Class is LF) + return LineBreakMode.Prohibited; + if (prev1.Class is CR or LF or NL) + return LineBreakMode.Mandatory; + + // LB6 Do not break before hard line breaks. + if (curr.Class is BK or CR or LF or NL) + return LineBreakMode.Prohibited; + + // Explicit breaks and non-breaks: + // LB7 Do not break before spaces or zero width space. + if (curr.Class is SP or ZW) + return LineBreakMode.Prohibited; + + // LB8 Break before any character following a zero-width space, even if one or more spaces intervene. + if (prev1.Class is ZW) + return LineBreakMode.Optional; + if (prevSpaceStreak && prevSpace1.Class is ZW) + return LineBreakMode.Optional; + + // LB8a Do not break after a zero width joiner. + if (prev1.Class is ZWJ) + return LineBreakMode.Prohibited; + + // Combining marks: + // LB9 Do not break a combining character sequence; treat it as if it has the line breaking class of the base character in all of the following rules. Treat ZWJ as if it were CM. + // ? + + // LB10 Treat any remaining combining mark or ZWJ as AL. + if (curr.Class is CM or ZWJ) + this.class1 = curr = new('A'); + + // Word joiner: + // LB11 Do not break before or after Word joiner and related characters. + if (prev1.Class is WJ || curr.Class is WJ) + return LineBreakMode.Prohibited; + + // Non-breaking characters: + // LB12 Do not break after NBSP and related characters. + if (prev1.Class is GL) + return LineBreakMode.Prohibited; + + // 6.2 Tailorable Line Breaking Rules + // Non-breaking characters: + // LB12a Do not break before NBSP and related characters, except after spaces and hyphens. + if (prev1.Class is not SP and not BA and not HY && + curr.Class is GL) + return LineBreakMode.Prohibited; + + // Opening and closing: + // LB13 Do not break before ‘]’ or ‘!’ or ‘;’ or ‘/’, even after spaces. + if (curr.Class is CL or CP or EX or IS or SY) + return LineBreakMode.Prohibited; + + // LB14 Do not break after ‘[’, even after spaces. + if (prevSpace1.Class is OP) + return LineBreakMode.Prohibited; + + // LB15a Do not break after an unresolved initial punctuation that lies at the start of the line, after a space, after opening punctuation, or after an unresolved quotation mark, even after spaces. + if (prevSpace2.Class is sot or BK or CR or LF or NL or OP or QU or GL or SP or ZW && + prevSpace1.Class is QU && + prevSpace1.GeneralCategory is Pi) + return LineBreakMode.Prohibited; + + var next = this.enumerator.TryPeekNext(out var nextSubsequence, out _) + ? new Entry(nextSubsequence.EffectiveChar) + : new(eot); + + // LB15b Do not break before an unresolved final punctuation that lies at the end of the line, before a space, before a prohibited break, or before an unresolved quotation mark, even after spaces. + if (curr.Class is QU && curr.GeneralCategory is Pf && + next.Class is SP or GL or WJ or CL or QU or CP or EX or IS or SY or BK or CR or LF or NL or ZW or eot) + return LineBreakMode.Prohibited; + + // LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with intervening spaces. + if (prevSpace1.Class is CL or CP && next.Class is NS) + return LineBreakMode.Prohibited; + + // LB17 Do not break within ‘——’, even with intervening spaces. + if (prevSpace1.Class is B2 && next.Class is B2) + return LineBreakMode.Prohibited; + + // Spaces: + // LB18 Break after spaces. + if (prev1.Class is SP) + return LineBreakMode.Optional; + + // Special case rules: + // LB19 Do not break before or after quotation marks, such as ‘ ” ’. + if (prev1.Class is QU || curr.Class is QU) + return LineBreakMode.Prohibited; + + // LB20 Break before and after unresolved CB. + if (prev1.Class is CB || curr.Class is CB) + return LineBreakMode.Optional; + + // LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small kana, and other non-starters, or after acute accents. + if (curr.Class is BA or HY or NS || prev1.Class is BB) + return LineBreakMode.Prohibited; + + // LB21a Don't break after Hebrew + Hyphen. + if (prev2.Class is HL && prev1.Class is HY or BA) + return LineBreakMode.Prohibited; + + // LB21b Don’t break between Solidus and Hebrew letters. + if (prev1.Class is SY && curr.Class is HL) + return LineBreakMode.Prohibited; + + // LB22 Do not break before ellipses. + if (curr.Class is IN) + return LineBreakMode.Prohibited; + + // Numbers: + // LB23 Do not break between digits and letters. + if (prev1.Class is AL or HL && curr.Class is NU) + return LineBreakMode.Prohibited; + if (prev1.Class is NU && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB23a Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes. + if (prev1.Class is PR && curr.Class is ID or EB or EM) + return LineBreakMode.Prohibited; + if (prev1.Class is ID or EB or EM && curr.Class is PR) + return LineBreakMode.Prohibited; + + // LB24 Do not break between numeric prefix/postfix and letters, or between letters and prefix/postfix. + if (prev1.Class is PR or PO && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + if (prev1.Class is AL or HL && curr.Class is PR or PO) + return LineBreakMode.Prohibited; + + // LB25 Do not break between the following pairs of classes relevant to numbers: + if ((prev1.Class, curr.Class) is (CL, PO) or (CP, PO) or (CL, PR) or (CP, PR) or (NU, PO) or (NU, PR) + or (PO, OP) or (PO, NU) or (PR, OP) or (PR, NU) or (HY, NU) or (IS, NU) or (NU, NU) or (SY, NU)) + return LineBreakMode.Prohibited; + + // Korean syllable blocks: + // LB26 Do not break a Korean syllable. + if (prev1.Class is JL && curr.Class is JL or JV or H2 or H3) + return LineBreakMode.Prohibited; + if (prev1.Class is JV or H2 && curr.Class is JV or JT) + return LineBreakMode.Prohibited; + + // LB27 Treat a Korean Syllable Block the same as ID. + if (prev1.Class is JL or JV or JT or H2 or H3 && curr.Class is PO) + return LineBreakMode.Prohibited; + if (prev1.Class is PR && curr.Class is JL or JV or JT or H2 or H3) + return LineBreakMode.Prohibited; + + // Finally, join alphabetic letters into words and break everything else. + // LB28 Do not break between alphabetics (“at”). + if (prev1.Class is AL or HL && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB28a Do not break inside the orthographic syllables of Brahmic scripts. + // TODO: what's "◌"? + if (prev1.Class is AP && curr.Class is AK or AS) + return LineBreakMode.Prohibited; + if (prev1.Class is AK or AS && curr.Class is VF or VI) + return LineBreakMode.Prohibited; + if (prev2.Class is AK or AS && prev1.Class is VI && curr.Class is AK) + return LineBreakMode.Prohibited; + if (prev1.Class is AK or AS && curr.Class is AK or AS && next.Class is VF) + return LineBreakMode.Prohibited; + + // LB29 Do not break between numeric punctuation and alphabetics (“e.g.”). + if (prev1.Class is IS && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB30 Do not break between letters, numbers, or ordinary symbols and opening or closing parentheses. + if (prev1.Class is AL or HL or NU && + curr.Class is OP && curr.EastAsianWidth is not F and not W and not H) + return LineBreakMode.Prohibited; + if (prev1.Class is CP && prev1.EastAsianWidth is not F and not W and not H && + curr.Class is AL or HL or NU) + return LineBreakMode.Prohibited; + + // LB30a Break between two regional indicator symbols if and only if there are an even number of regional indicators preceding the position of the break. + if (this.consecutiveRegionalIndicators % 2 == 0) + return LineBreakMode.Optional; + + // LB30b Do not break between an emoji base (or potential emoji) and an emoji modifier. + if (prev1.Class is EB && curr.Class is EM) + return LineBreakMode.Prohibited; + if (prev1.GeneralCategory is Cn && + (prev1.EmojiProperty & UnicodeEmojiProperty.Extended_Pictographic) != 0 && + curr.Class is EM) + return LineBreakMode.Prohibited; + + // LB31 Break everywhere else. + return LineBreakMode.Optional; + } + + private readonly struct Entry + { + public readonly UnicodeLineBreakClass Class; + public readonly UnicodeGeneralCategory GeneralCategory; + public readonly UnicodeEastAsianWidthClass EastAsianWidth; + public readonly UnicodeEmojiProperty EmojiProperty; + + public Entry(int c) + { + this.Class = UnicodeData.LineBreak[c] switch + { + AI or SG or XX => AL, + SA when UnicodeData.GeneralCategory[c] is Mn or Mc => CM, + SA => AL, + CJ => NS, + var x => x, + }; + this.GeneralCategory = UnicodeData.GeneralCategory[c]; + this.EastAsianWidth = UnicodeData.EastAsianWidth[c]; + this.EmojiProperty = UnicodeData.EmojiProperty[c]; + } + + public Entry( + UnicodeLineBreakClass lineBreakClass, + UnicodeGeneralCategory generalCategory = Cn, + UnicodeEastAsianWidthClass eastAsianWidth = N, + UnicodeEmojiProperty emojiProperty = 0) + { + this.Class = lineBreakClass; + this.GeneralCategory = generalCategory; + this.EastAsianWidth = eastAsianWidth; + this.EmojiProperty = emojiProperty; + } + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs new file mode 100644 index 000000000..3e4f74ada --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs @@ -0,0 +1,118 @@ +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Stores unicode data. +internal static class UnicodeData +{ + /// Line break classes. + public static readonly UnicodeLineBreakClass[] LineBreak; + + /// East asian width classes. + public static readonly UnicodeEastAsianWidthClass[] EastAsianWidth; + + /// General categories. + public static readonly UnicodeGeneralCategory[] GeneralCategory; + + /// Emoji properties. + public static readonly UnicodeEmojiProperty[] EmojiProperty; + + static UnicodeData() + { + // File is from https://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt + LineBreak = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("LineBreak.txt")!, + UnicodeLineBreakClass.XX); + + // https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt + EastAsianWidth = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("EastAsianWidth.txt")!, + UnicodeEastAsianWidthClass.N); + + // https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt + GeneralCategory = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("DerivedGeneralCategory.txt")!, + UnicodeGeneralCategory.Cn); + + // https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt + EmojiProperty = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("emoji-data.txt")!, + default(UnicodeEmojiProperty)); + } + + private static T[] Parse(Stream stream, T defaultValue) + where T : unmanaged, Enum + { + if (Unsafe.SizeOf() != 1) + throw new InvalidOperationException("Enum must be of size 1 byte"); + + var isFlag = typeof(T).GetCustomAttribute() is not null; + using var sr = new StreamReader(stream); + var res = new T[0x110000]; + res.AsSpan().Fill(defaultValue); + for (string? line; (line = sr.ReadLine()) != null;) + { + var span = line.AsSpan(); + + // strip comment + var i = span.IndexOf('#'); + if (i != -1) + span = span[..i]; + + span = span.Trim(); + if (span.IsEmpty) + continue; + + // find delimiter + i = span.IndexOf(';'); + if (i == -1) + throw new InvalidDataException(); + + var range = span[..i].Trim(); + var entry = Enum.Parse(span[(i + 1)..].Trim()); + + i = range.IndexOf(".."); + int from, to; + if (i == -1) + { + from = int.Parse(range, NumberStyles.HexNumber); + to = from + 1; + } + else + { + from = int.Parse(range[..i], NumberStyles.HexNumber); + to = int.Parse(range[(i + 2)..], NumberStyles.HexNumber) + 1; + } + + if (from > char.MaxValue) + continue; + + from = Math.Min(from, res.Length); + to = Math.Min(to, res.Length); + if (isFlag) + { + foreach (ref var v in res.AsSpan()[from..to]) + { + unsafe + { + fixed (void* p = &v) + *(byte*)p |= *(byte*)&entry; + } + } + } + else + { + res.AsSpan()[from..to].Fill(entry); + } + } + + return res; + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs new file mode 100644 index 000000000..184168795 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Unicode east asian width. +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +internal enum UnicodeEastAsianWidthClass : byte +{ + /// East Asian Ambiguous. + A, + + /// East Asian Fullwidth. + F, + + /// East Asian Halfwidth. + H, + + /// Neutral. + N, + + /// East Asian Narrow. + Na, + + /// East Asian Wide. + W, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs new file mode 100644 index 000000000..3952a5178 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Unicode emoji property. +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[Flags] +internal enum UnicodeEmojiProperty : byte +{ + /// Characters that are emoji. + Emoji = 1 << 0, + + /// Characters that have emoji presentation by default. + Emoji_Presentation = 1 << 1, + + /// Characters that are emoji modifiers. + Emoji_Modifier = 1 << 2, + + /// Characters that can serve as a base for emoji modifiers. + Emoji_Modifier_Base = 1 << 3, + + /// Characters used in emoji sequences that normally do not appear on emoji keyboards as separate choices, + /// such as keycap base characters or Regional_Indicator characters. + Emoji_Component = 1 << 4, + + /// Characters that are used to future-proof segmentation. + Extended_Pictographic = 1 << 5, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs new file mode 100644 index 000000000..f24f5b357 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs @@ -0,0 +1,99 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Unicode general category.. +/// +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +internal enum UnicodeGeneralCategory : byte +{ + /// Uppercase_Letter; an uppercase letter. + Lu, + + /// Lowercase_Letter; a lowercase letter. + Ll, + + /// Titlecase_Letter; a digraph encoded as a single character, with first part uppercase. + Lt, + + /// Modifier_Letter; a modifier letter. + Lm, + + /// Other_Letter; other letters, including syllables and ideographs. + Lo, + + /// Nonspacing_Mark; a nonspacing combining mark (zero advance width). + Mn, + + /// Spacing_Mark; a spacing combining mark (positive advance width). + Mc, + + /// Enclosing_Mark; an enclosing combining mark. + Me, + + /// Decimal_Number; a decimal digit. + Nd, + + /// Letter_Number; a letterlike numeric character. + Nl, + + /// Other_Number; a numeric character of other type. + No, + + /// Connector_Punctuation; a connecting punctuation mark, like a tie. + Pc, + + /// Dash_Punctuation; a dash or hyphen punctuation mark. + Pd, + + /// Open_Punctuation; an opening punctuation mark (of a pair). + Ps, + + /// Close_Punctuation; a closing punctuation mark (of a pair). + Pe, + + /// Initial_Punctuation; an initial quotation mark. + Pi, + + /// Final_Punctuation; a final quotation mark. + Pf, + + /// Other_Punctuation; a punctuation mark of other type. + Po, + + /// Math_Symbol; a symbol of mathematical use. + Sm, + + /// Currency_Symbol; a currency sign. + Sc, + + /// Modifier_Symbol; a non-letterlike modifier symbol. + Sk, + + /// Other_Symbol; a symbol of other type. + So, + + /// Space_Separator; a space character (of various non-zero widths). + Zs, + + /// Line_Separator; U+2028 LINE SEPARATOR only. + Zl, + + /// Paragraph_Separator; U+2029 PARAGRAPH SEPARATOR only. + Zp, + + /// Control; a C0 or C1 control code. + Cc, + + /// Format; a format control character. + Cf, + + /// Surrogate; a surrogate code point. + Cs, + + /// Private_Use; a private-use character. + Co, + + /// Unassigned; a reserved unassigned code point or a noncharacter. + Cn, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs new file mode 100644 index 000000000..bbab3170f --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs @@ -0,0 +1,162 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; + +/// Unicode line break class. +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1300:Element should begin with an uppercase letter", + Justification = "Unicode Data")] +internal enum UnicodeLineBreakClass : byte +{ + /// Start of text. + sot, + + /// End of text. + eot, + + /// Mandatory Break; NL, PARAGRAPH SEPARATOR; Cause a line break (after). + BK, + + /// Carriage Return; CR; Cause a line break (after), except between CR and LF. + CR, + + /// Line Feed; LF; Cause a line break (after). + LF, + + /// Combining Mark; Combining marks, control codes; Prohibit a line break between the character and the preceding character. + CM, + + /// Next Line; NEL; Cause a line break (after). + NL, + + /// Surrogate; Surrogates; Do not occur in well-formed text. + SG, + + /// Word Joiner; WJ; Prohibit line breaks before and after. + WJ, + + /// Zero Width Space; ZWSP; Provide a break opportunity. + ZW, + + /// Non-breaking (“Glue”); CGJ, NBSP, ZWNBSP; Prohibit line breaks before and after. + GL, + + /// Space; SPACE; Enable indirect line breaks. + SP, + + /// Zero Width Joiner; Zero Width Joiner; Prohibit line breaks within joiner sequences. + ZWJ, + + /// Break Opportunity Before and After; Em dash; Provide a line break opportunity before and after the character. + B2, + + /// Break After; Spaces, hyphens; Generally provide a line break opportunity after the character. + BA, + + /// Break Before; Punctuation used in dictionaries; Generally provide a line break opportunity before the character. + BB, + + /// Hyphen; HYPHEN-MINUS; Provide a line break opportunity after the character, except in numeric context. + HY, + + /// Contingent Break Opportunity; Inline objects; Provide a line break opportunity contingent on additional information. + CB, + + /// Close Punctuation; “}”, “❳”, “⟫” etc.; Prohibit line breaks before. + CL, + + /// Close Parenthesis; “)”, “]”; Prohibit line breaks before. + CP, + + /// Exclamation/Interrogation; “!”, “?”, etc.; Prohibit line breaks before. + EX, + + /// Inseparable; Leaders; Allow only indirect line breaks between pairs. + IN, + + /// Nonstarter; “‼”, “‽”, “⁇”, “⁉”, etc.; Allow only indirect line breaks before. + NS, + + /// Open Punctuation; “(“, “[“, “{“, etc.; Prohibit line breaks after. + OP, + + /// Quotation; Quotation marks; Act like they are both opening and closing. + QU, + + /// Infix Numeric Separator; . ,; Prevent breaks after any and before numeric. + IS, + + /// Numeric; Digits; Form numeric expressions for line breaking purposes. + NU, + + /// Postfix Numeric; %, ¢; Do not break following a numeric expression. + PO, + + /// Prefix Numeric; $, £, ¥, etc.; Do not break in front of a numeric expression. + PR, + + /// Symbols Allowing Break After; /; Prevent a break before, and allow a break after. + SY, + + /// Ambiguous (Alphabetic or Ideographic); Characters with Ambiguous East Asian Width; Act like AL when the resolved EAW is N; otherwise, act as ID. + AI, + + /// Aksara; Consonants; Form orthographic syllables in Brahmic scripts. + AK, + + /// Alphabetic; Alphabets and regular symbols; Are alphabetic characters or symbols that are used with alphabetic characters. + AL, + + /// Aksara Pre-Base; Pre-base repha; Form orthographic syllables in Brahmic scripts. + AP, + + /// Aksara Start; Independent vowels; Form orthographic syllables in Brahmic scripts. + AS, + + /// Conditional Japanese Starter; Small kana; Treat as NS or ID for strict or normal breaking. + CJ, + + /// Emoji Base; All emoji allowing modifiers; Do not break from following Emoji Modifier. + EB, + + /// Emoji Modifier; Skin tone modifiers; Do not break from preceding Emoji Base. + EM, + + /// Hangul LV Syllable; Hangul; Form Korean syllable blocks. + H2, + + /// Hangul LVT Syllable; Hangul; Form Korean syllable blocks. + H3, + + /// Hebrew Letter; Hebrew; Do not break around a following hyphen; otherwise act as Alphabetic. + HL, + + /// Ideographic; Ideographs; Break before or after, except in some numeric context. + ID, + + /// Hangul L Jamo; Conjoining jamo; Form Korean syllable blocks. + JL, + + /// Hangul V Jamo; Conjoining jamo; Form Korean syllable blocks. + JV, + + /// Hangul T Jamo; Conjoining jamo; Form Korean syllable blocks. + JT, + + /// Regional Indicator; REGIONAL INDICATOR SYMBOL LETTER A .. Z; Keep pairs together. For pairs, break before and after other classes. + RI, + + /// Complex Context Dependent (South East Asian); South East Asian: Thai, Lao, Khmer; Provide a line break opportunity contingent on additional, language-specific context analysis. + SA, + + /// Virama Final; Viramas for final consonants; Form orthographic syllables in Brahmic scripts. + VF, + + /// Virama; Conjoining viramas; Form orthographic syllables in Brahmic scripts. + VI, + + /// Unknown; Most unassigned, private-use; Have as yet unknown line breaking behavior or unassigned code positions. + XX, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/emoji-data.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/emoji-data.txt new file mode 100644 index 000000000..0ba10e9ce --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/emoji-data.txt @@ -0,0 +1,1320 @@ +# emoji-data.txt +# Date: 2023-02-01, 02:22:54 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Emoji Data for UTS #51 +# Used with Emoji Version 15.1 and subsequent minor revisions (if any) +# +# For documentation and usage, see https://www.unicode.org/reports/tr51 +# +# Format: +# ; # +# Note: there is no guarantee as to the structure of whitespace or comments +# +# Characters and sequences are listed in code point order. Users should be shown a more natural order. +# See the CLDR collation order for Emoji. + + +# ================================================ + +# All omitted code points have Emoji=No + +0023 ; Emoji # E0.0 [1] (#️) hash sign +002A ; Emoji # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji # E0.0 [10] (0️..9️) digit zero..digit nine +00A9 ; Emoji # E0.6 [1] (©️) copyright +00AE ; Emoji # E0.6 [1] (®️) registered +203C ; Emoji # E0.6 [1] (‼️) double exclamation mark +2049 ; Emoji # E0.6 [1] (⁉️) exclamation question mark +2122 ; Emoji # E0.6 [1] (™️) trade mark +2139 ; Emoji # E0.6 [1] (ℹ️) information +2194..2199 ; Emoji # E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Emoji # E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Emoji # E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Emoji # E1.0 [1] (⌨️) keyboard +23CF ; Emoji # E1.0 [1] (⏏️) eject button +23E9..23EC ; Emoji # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Emoji # E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Emoji # E1.0 [1] (⏯️) play or pause button +23F0 ; Emoji # E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Emoji # E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Emoji # E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Emoji # E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Emoji # E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Emoji # E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Emoji # E0.6 [1] (▶️) play button +25C0 ; Emoji # E0.6 [1] (◀️) reverse button +25FB..25FE ; Emoji # E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Emoji # E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Emoji # E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Emoji # E1.0 [1] (☄️) comet +260E ; Emoji # E0.6 [1] (☎️) telephone +2611 ; Emoji # E0.6 [1] (☑️) check box with check +2614..2615 ; Emoji # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2618 ; Emoji # E1.0 [1] (☘️) shamrock +261D ; Emoji # E0.6 [1] (☝️) index pointing up +2620 ; Emoji # E1.0 [1] (☠️) skull and crossbones +2622..2623 ; Emoji # E1.0 [2] (☢️..☣️) radioactive..biohazard +2626 ; Emoji # E1.0 [1] (☦️) orthodox cross +262A ; Emoji # E0.7 [1] (☪️) star and crescent +262E ; Emoji # E1.0 [1] (☮️) peace symbol +262F ; Emoji # E0.7 [1] (☯️) yin yang +2638..2639 ; Emoji # E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Emoji # E0.6 [1] (☺️) smiling face +2640 ; Emoji # E4.0 [1] (♀️) female sign +2642 ; Emoji # E4.0 [1] (♂️) male sign +2648..2653 ; Emoji # E0.6 [12] (♈..♓) Aries..Pisces +265F ; Emoji # E11.0 [1] (♟️) chess pawn +2660 ; Emoji # E0.6 [1] (♠️) spade suit +2663 ; Emoji # E0.6 [1] (♣️) club suit +2665..2666 ; Emoji # E0.6 [2] (♥️..♦️) heart suit..diamond suit +2668 ; Emoji # E0.6 [1] (♨️) hot springs +267B ; Emoji # E0.6 [1] (♻️) recycling symbol +267E ; Emoji # E11.0 [1] (♾️) infinity +267F ; Emoji # E0.6 [1] (♿) wheelchair symbol +2692 ; Emoji # E1.0 [1] (⚒️) hammer and pick +2693 ; Emoji # E0.6 [1] (⚓) anchor +2694 ; Emoji # E1.0 [1] (⚔️) crossed swords +2695 ; Emoji # E4.0 [1] (⚕️) medical symbol +2696..2697 ; Emoji # E1.0 [2] (⚖️..⚗️) balance scale..alembic +2699 ; Emoji # E1.0 [1] (⚙️) gear +269B..269C ; Emoji # E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +26A0..26A1 ; Emoji # E0.6 [2] (⚠️..⚡) warning..high voltage +26A7 ; Emoji # E13.0 [1] (⚧️) transgender symbol +26AA..26AB ; Emoji # E0.6 [2] (⚪..⚫) white circle..black circle +26B0..26B1 ; Emoji # E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26BD..26BE ; Emoji # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C8 ; Emoji # E0.7 [1] (⛈️) cloud with lightning and rain +26CE ; Emoji # E0.6 [1] (⛎) Ophiuchus +26CF ; Emoji # E0.7 [1] (⛏️) pick +26D1 ; Emoji # E0.7 [1] (⛑️) rescue worker’s helmet +26D3 ; Emoji # E0.7 [1] (⛓️) chains +26D4 ; Emoji # E0.6 [1] (⛔) no entry +26E9 ; Emoji # E0.7 [1] (⛩️) shinto shrine +26EA ; Emoji # E0.6 [1] (⛪) church +26F0..26F1 ; Emoji # E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Emoji # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Emoji # E0.7 [1] (⛴️) ferry +26F5 ; Emoji # E0.6 [1] (⛵) sailboat +26F7..26F9 ; Emoji # E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Emoji # E0.6 [1] (⛺) tent +26FD ; Emoji # E0.6 [1] (⛽) fuel pump +2702 ; Emoji # E0.6 [1] (✂️) scissors +2705 ; Emoji # E0.6 [1] (✅) check mark button +2708..270C ; Emoji # E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Emoji # E0.7 [1] (✍️) writing hand +270F ; Emoji # E0.6 [1] (✏️) pencil +2712 ; Emoji # E0.6 [1] (✒️) black nib +2714 ; Emoji # E0.6 [1] (✔️) check mark +2716 ; Emoji # E0.6 [1] (✖️) multiply +271D ; Emoji # E0.7 [1] (✝️) latin cross +2721 ; Emoji # E0.7 [1] (✡️) star of David +2728 ; Emoji # E0.6 [1] (✨) sparkles +2733..2734 ; Emoji # E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Emoji # E0.6 [1] (❄️) snowflake +2747 ; Emoji # E0.6 [1] (❇️) sparkle +274C ; Emoji # E0.6 [1] (❌) cross mark +274E ; Emoji # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji # E0.6 [1] (❗) red exclamation mark +2763 ; Emoji # E1.0 [1] (❣️) heart exclamation +2764 ; Emoji # E0.6 [1] (❤️) red heart +2795..2797 ; Emoji # E0.6 [3] (➕..➗) plus..divide +27A1 ; Emoji # E0.6 [1] (➡️) right arrow +27B0 ; Emoji # E0.6 [1] (➰) curly loop +27BF ; Emoji # E1.0 [1] (➿) double curly loop +2934..2935 ; Emoji # E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Emoji # E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Emoji # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji # E0.6 [1] (⭐) star +2B55 ; Emoji # E0.6 [1] (⭕) hollow red circle +3030 ; Emoji # E0.6 [1] (〰️) wavy dash +303D ; Emoji # E0.6 [1] (〽️) part alternation mark +3297 ; Emoji # E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Emoji # E0.6 [1] (㊙️) Japanese “secret” button +1F004 ; Emoji # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji # E0.6 [1] (🃏) joker +1F170..1F171 ; Emoji # E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Emoji # E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Emoji # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201..1F202 ; Emoji # E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F21A ; Emoji # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Emoji # E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F250..1F251 ; Emoji # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji # E0.6 [1] (🌑) new moon +1F312 ; Emoji # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji # E1.0 [1] (🌚) new moon face +1F31B ; Emoji # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Emoji # E0.7 [1] (🌡️) thermometer +1F324..1F32C ; Emoji # E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Emoji # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji # E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Emoji # E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Emoji # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji # E1.0 [1] (🍼) baby bottle +1F37D ; Emoji # E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Emoji # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F396..1F397 ; Emoji # E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F399..1F39B ; Emoji # E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39E..1F39F ; Emoji # E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Emoji # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji # E0.6 [1] (🏈) american football +1F3C9 ; Emoji # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji # E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Emoji # E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Emoji # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Emoji # E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Emoji # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji # E0.6 [12] (🏥..🏰) hospital..castle +1F3F3 ; Emoji # E0.7 [1] (🏳️) white flag +1F3F4 ; Emoji # E1.0 [1] (🏴) black flag +1F3F5 ; Emoji # E0.7 [1] (🏵️) rosette +1F3F7 ; Emoji # E0.7 [1] (🏷️) label +1F3F8..1F407 ; Emoji # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji # E1.0 [1] (🐓) rooster +1F414 ; Emoji # E0.6 [1] (🐔) chicken +1F415 ; Emoji # E0.7 [1] (🐕) dog +1F416 ; Emoji # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Emoji # E0.7 [1] (🐿️) chipmunk +1F440 ; Emoji # E0.6 [1] (👀) eyes +1F441 ; Emoji # E0.7 [1] (👁️) eye +1F442..1F464 ; Emoji # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji # E0.6 [1] (📮) postbox +1F4EF ; Emoji # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji # E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Emoji # E0.7 [1] (📽️) film projector +1F4FF..1F502 ; Emoji # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F549..1F54A ; Emoji # E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Emoji # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F56F..1F570 ; Emoji # E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F573..1F579 ; Emoji # E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Emoji # E3.0 [1] (🕺) man dancing +1F587 ; Emoji # E0.7 [1] (🖇️) linked paperclips +1F58A..1F58D ; Emoji # E0.7 [4] (🖊️..🖍️) pen..crayon +1F590 ; Emoji # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji # E3.0 [1] (🖤) black heart +1F5A5 ; Emoji # E0.7 [1] (🖥️) desktop computer +1F5A8 ; Emoji # E0.7 [1] (🖨️) printer +1F5B1..1F5B2 ; Emoji # E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5BC ; Emoji # E0.7 [1] (🖼️) framed picture +1F5C2..1F5C4 ; Emoji # E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5D1..1F5D3 ; Emoji # E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5DC..1F5DE ; Emoji # E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5E1 ; Emoji # E0.7 [1] (🗡️) dagger +1F5E3 ; Emoji # E0.7 [1] (🗣️) speaking head +1F5E8 ; Emoji # E2.0 [1] (🗨️) left speech bubble +1F5EF ; Emoji # E0.7 [1] (🗯️) right anger bubble +1F5F3 ; Emoji # E0.7 [1] (🗳️) ballot box with ballot +1F5FA ; Emoji # E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Emoji # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji # E0.6 [1] (😏) smirking face +1F610 ; Emoji # E0.7 [1] (😐) neutral face +1F611 ; Emoji # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji # E1.0 [1] (😕) confused face +1F616 ; Emoji # E0.6 [1] (😖) confounded face +1F617 ; Emoji # E1.0 [1] (😗) kissing face +1F618 ; Emoji # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji # E1.0 [1] (😬) grimacing face +1F62D ; Emoji # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji # E1.0 [1] (😴) sleeping face +1F635 ; Emoji # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji # E1.0 [1] (🚆) train +1F687 ; Emoji # E0.6 [1] (🚇) metro +1F688 ; Emoji # E1.0 [1] (🚈) light rail +1F689 ; Emoji # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji # E0.6 [1] (🚌) bus +1F68D ; Emoji # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji # E0.6 [1] (🚏) bus stop +1F690 ; Emoji # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji # E0.6 [1] (🚕) taxi +1F696 ; Emoji # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji # E0.6 [1] (🚗) automobile +1F698 ; Emoji # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji # E0.6 [1] (🚢) ship +1F6A3 ; Emoji # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji # E1.0 [1] (🚿) shower +1F6C0 ; Emoji # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CB ; Emoji # E0.7 [1] (🛋️) couch and lamp +1F6CC ; Emoji # E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Emoji # E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Emoji # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E9 ; Emoji # E0.7 [1] (🛩️) small airplane +1F6EB..1F6EC ; Emoji # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F0 ; Emoji # E0.7 [1] (🛰️) satellite +1F6F3 ; Emoji # E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Emoji # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji # E12.0 [1] (🥱) yawning face +1F972 ; Emoji # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji # E11.0 [1] (🥺) pleading face +1F97B ; Emoji # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1424 + +# ================================================ + +# All omitted code points have Emoji_Presentation=No + +231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done +23E9..23EC ; Emoji_Presentation # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23F0 ; Emoji_Presentation # E0.6 [1] (⏰) alarm clock +23F3 ; Emoji_Presentation # E0.6 [1] (⏳) hourglass not done +25FD..25FE ; Emoji_Presentation # E0.6 [2] (◽..◾) white medium-small square..black medium-small square +2614..2615 ; Emoji_Presentation # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2648..2653 ; Emoji_Presentation # E0.6 [12] (♈..♓) Aries..Pisces +267F ; Emoji_Presentation # E0.6 [1] (♿) wheelchair symbol +2693 ; Emoji_Presentation # E0.6 [1] (⚓) anchor +26A1 ; Emoji_Presentation # E0.6 [1] (⚡) high voltage +26AA..26AB ; Emoji_Presentation # E0.6 [2] (⚪..⚫) white circle..black circle +26BD..26BE ; Emoji_Presentation # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji_Presentation # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26CE ; Emoji_Presentation # E0.6 [1] (⛎) Ophiuchus +26D4 ; Emoji_Presentation # E0.6 [1] (⛔) no entry +26EA ; Emoji_Presentation # E0.6 [1] (⛪) church +26F2..26F3 ; Emoji_Presentation # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F5 ; Emoji_Presentation # E0.6 [1] (⛵) sailboat +26FA ; Emoji_Presentation # E0.6 [1] (⛺) tent +26FD ; Emoji_Presentation # E0.6 [1] (⛽) fuel pump +2705 ; Emoji_Presentation # E0.6 [1] (✅) check mark button +270A..270B ; Emoji_Presentation # E0.6 [2] (✊..✋) raised fist..raised hand +2728 ; Emoji_Presentation # E0.6 [1] (✨) sparkles +274C ; Emoji_Presentation # E0.6 [1] (❌) cross mark +274E ; Emoji_Presentation # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji_Presentation # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji_Presentation # E0.6 [1] (❗) red exclamation mark +2795..2797 ; Emoji_Presentation # E0.6 [3] (➕..➗) plus..divide +27B0 ; Emoji_Presentation # E0.6 [1] (➰) curly loop +27BF ; Emoji_Presentation # E1.0 [1] (➿) double curly loop +2B1B..2B1C ; Emoji_Presentation # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji_Presentation # E0.6 [1] (⭐) star +2B55 ; Emoji_Presentation # E0.6 [1] (⭕) hollow red circle +1F004 ; Emoji_Presentation # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji_Presentation # E0.6 [1] (🃏) joker +1F18E ; Emoji_Presentation # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji_Presentation # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji_Presentation # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201 ; Emoji_Presentation # E0.6 [1] (🈁) Japanese “here” button +1F21A ; Emoji_Presentation # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji_Presentation # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F236 ; Emoji_Presentation # E0.6 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button +1F238..1F23A ; Emoji_Presentation # E0.6 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button +1F250..1F251 ; Emoji_Presentation # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji_Presentation # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji_Presentation # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji_Presentation # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji_Presentation # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji_Presentation # E0.6 [1] (🌑) new moon +1F312 ; Emoji_Presentation # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji_Presentation # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji_Presentation # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji_Presentation # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji_Presentation # E1.0 [1] (🌚) new moon face +1F31B ; Emoji_Presentation # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji_Presentation # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji_Presentation # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji_Presentation # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F32D..1F32F ; Emoji_Presentation # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji_Presentation # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji_Presentation # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji_Presentation # E0.6 [2] (🌴..🌵) palm tree..cactus +1F337..1F34A ; Emoji_Presentation # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji_Presentation # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji_Presentation # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji_Presentation # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji_Presentation # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji_Presentation # E1.0 [1] (🍼) baby bottle +1F37E..1F37F ; Emoji_Presentation # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji_Presentation # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F3A0..1F3C4 ; Emoji_Presentation # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji_Presentation # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji_Presentation # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji_Presentation # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji_Presentation # E0.6 [1] (🏈) american football +1F3C9 ; Emoji_Presentation # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji_Presentation # E0.6 [1] (🏊) person swimming +1F3CF..1F3D3 ; Emoji_Presentation # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3E0..1F3E3 ; Emoji_Presentation # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji_Presentation # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji_Presentation # E0.6 [12] (🏥..🏰) hospital..castle +1F3F4 ; Emoji_Presentation # E1.0 [1] (🏴) black flag +1F3F8..1F407 ; Emoji_Presentation # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji_Presentation # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji_Presentation # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji_Presentation # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji_Presentation # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji_Presentation # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji_Presentation # E1.0 [1] (🐓) rooster +1F414 ; Emoji_Presentation # E0.6 [1] (🐔) chicken +1F415 ; Emoji_Presentation # E0.7 [1] (🐕) dog +1F416 ; Emoji_Presentation # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji_Presentation # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji_Presentation # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji_Presentation # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F440 ; Emoji_Presentation # E0.6 [1] (👀) eyes +1F442..1F464 ; Emoji_Presentation # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji_Presentation # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji_Presentation # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Presentation # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji_Presentation # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji_Presentation # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji_Presentation # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji_Presentation # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji_Presentation # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji_Presentation # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji_Presentation # E0.6 [1] (📮) postbox +1F4EF ; Emoji_Presentation # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji_Presentation # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji_Presentation # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji_Presentation # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji_Presentation # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji_Presentation # E0.6 [4] (📹..📼) video camera..videocassette +1F4FF..1F502 ; Emoji_Presentation # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji_Presentation # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji_Presentation # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji_Presentation # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji_Presentation # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji_Presentation # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji_Presentation # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji_Presentation # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji_Presentation # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji_Presentation # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F54B..1F54E ; Emoji_Presentation # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji_Presentation # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji_Presentation # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F57A ; Emoji_Presentation # E3.0 [1] (🕺) man dancing +1F595..1F596 ; Emoji_Presentation # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji_Presentation # E3.0 [1] (🖤) black heart +1F5FB..1F5FF ; Emoji_Presentation # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji_Presentation # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji_Presentation # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji_Presentation # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji_Presentation # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji_Presentation # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji_Presentation # E0.6 [1] (😏) smirking face +1F610 ; Emoji_Presentation # E0.7 [1] (😐) neutral face +1F611 ; Emoji_Presentation # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji_Presentation # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji_Presentation # E1.0 [1] (😕) confused face +1F616 ; Emoji_Presentation # E0.6 [1] (😖) confounded face +1F617 ; Emoji_Presentation # E1.0 [1] (😗) kissing face +1F618 ; Emoji_Presentation # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji_Presentation # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji_Presentation # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji_Presentation # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji_Presentation # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji_Presentation # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji_Presentation # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji_Presentation # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji_Presentation # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji_Presentation # E1.0 [1] (😬) grimacing face +1F62D ; Emoji_Presentation # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji_Presentation # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji_Presentation # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji_Presentation # E1.0 [1] (😴) sleeping face +1F635 ; Emoji_Presentation # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji_Presentation # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji_Presentation # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji_Presentation # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji_Presentation # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji_Presentation # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji_Presentation # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji_Presentation # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji_Presentation # E1.0 [1] (🚆) train +1F687 ; Emoji_Presentation # E0.6 [1] (🚇) metro +1F688 ; Emoji_Presentation # E1.0 [1] (🚈) light rail +1F689 ; Emoji_Presentation # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji_Presentation # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji_Presentation # E0.6 [1] (🚌) bus +1F68D ; Emoji_Presentation # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji_Presentation # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji_Presentation # E0.6 [1] (🚏) bus stop +1F690 ; Emoji_Presentation # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji_Presentation # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji_Presentation # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji_Presentation # E0.6 [1] (🚕) taxi +1F696 ; Emoji_Presentation # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji_Presentation # E0.6 [1] (🚗) automobile +1F698 ; Emoji_Presentation # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji_Presentation # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji_Presentation # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji_Presentation # E0.6 [1] (🚢) ship +1F6A3 ; Emoji_Presentation # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji_Presentation # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji_Presentation # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji_Presentation # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji_Presentation # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji_Presentation # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji_Presentation # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji_Presentation # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji_Presentation # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji_Presentation # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji_Presentation # E1.0 [1] (🚿) shower +1F6C0 ; Emoji_Presentation # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji_Presentation # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CC ; Emoji_Presentation # E1.0 [1] (🛌) person in bed +1F6D0 ; Emoji_Presentation # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji_Presentation # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji_Presentation # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji_Presentation # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji_Presentation # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji_Presentation # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji_Presentation # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji_Presentation # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji_Presentation # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji_Presentation # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji_Presentation # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Presentation # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji_Presentation # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji_Presentation # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji_Presentation # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Presentation # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji_Presentation # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji_Presentation # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji_Presentation # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji_Presentation # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji_Presentation # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji_Presentation # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji_Presentation # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji_Presentation # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji_Presentation # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji_Presentation # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji_Presentation # E12.0 [1] (🥱) yawning face +1F972 ; Emoji_Presentation # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji_Presentation # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji_Presentation # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji_Presentation # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji_Presentation # E11.0 [1] (🥺) pleading face +1F97B ; Emoji_Presentation # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji_Presentation # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji_Presentation # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji_Presentation # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji_Presentation # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji_Presentation # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji_Presentation # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji_Presentation # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji_Presentation # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji_Presentation # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji_Presentation # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji_Presentation # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji_Presentation # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji_Presentation # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji_Presentation # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji_Presentation # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji_Presentation # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji_Presentation # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji_Presentation # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1205 + +# ================================================ + +# All omitted code points have Emoji_Modifier=No + +1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone + +# Total elements: 5 + +# ================================================ + +# All omitted code points have Emoji_Modifier_Base=No + +261D ; Emoji_Modifier_Base # E0.6 [1] (☝️) index pointing up +26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹️) person bouncing ball +270A..270C ; Emoji_Modifier_Base # E0.6 [3] (✊..✌️) raised fist..victory hand +270D ; Emoji_Modifier_Base # E0.7 [1] (✍️) writing hand +1F385 ; Emoji_Modifier_Base # E0.6 [1] (🎅) Santa Claus +1F3C2..1F3C4 ; Emoji_Modifier_Base # E0.6 [3] (🏂..🏄) snowboarder..person surfing +1F3C7 ; Emoji_Modifier_Base # E1.0 [1] (🏇) horse racing +1F3CA ; Emoji_Modifier_Base # E0.6 [1] (🏊) person swimming +1F3CB..1F3CC ; Emoji_Modifier_Base # E0.7 [2] (🏋️..🏌️) person lifting weights..person golfing +1F442..1F443 ; Emoji_Modifier_Base # E0.6 [2] (👂..👃) ear..nose +1F446..1F450 ; Emoji_Modifier_Base # E0.6 [11] (👆..👐) backhand index pointing up..open hands +1F466..1F46B ; Emoji_Modifier_Base # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Modifier_Base # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F478 ; Emoji_Modifier_Base # E0.6 [11] (👮..👸) police officer..princess +1F47C ; Emoji_Modifier_Base # E0.6 [1] (👼) baby angel +1F481..1F483 ; Emoji_Modifier_Base # E0.6 [3] (💁..💃) person tipping hand..woman dancing +1F485..1F487 ; Emoji_Modifier_Base # E0.6 [3] (💅..💇) nail polish..person getting haircut +1F48F ; Emoji_Modifier_Base # E0.6 [1] (💏) kiss +1F491 ; Emoji_Modifier_Base # E0.6 [1] (💑) couple with heart +1F4AA ; Emoji_Modifier_Base # E0.6 [1] (💪) flexed biceps +1F574..1F575 ; Emoji_Modifier_Base # E0.7 [2] (🕴️..🕵️) person in suit levitating..detective +1F57A ; Emoji_Modifier_Base # E3.0 [1] (🕺) man dancing +1F590 ; Emoji_Modifier_Base # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji_Modifier_Base # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F645..1F647 ; Emoji_Modifier_Base # E0.6 [3] (🙅..🙇) person gesturing NO..person bowing +1F64B..1F64F ; Emoji_Modifier_Base # E0.6 [5] (🙋..🙏) person raising hand..folded hands +1F6A3 ; Emoji_Modifier_Base # E1.0 [1] (🚣) person rowing boat +1F6B4..1F6B5 ; Emoji_Modifier_Base # E1.0 [2] (🚴..🚵) person biking..person mountain biking +1F6B6 ; Emoji_Modifier_Base # E0.6 [1] (🚶) person walking +1F6C0 ; Emoji_Modifier_Base # E0.6 [1] (🛀) person taking bath +1F6CC ; Emoji_Modifier_Base # E1.0 [1] (🛌) person in bed +1F90C ; Emoji_Modifier_Base # E13.0 [1] (🤌) pinched fingers +1F90F ; Emoji_Modifier_Base # E12.0 [1] (🤏) pinching hand +1F918 ; Emoji_Modifier_Base # E1.0 [1] (🤘) sign of the horns +1F919..1F91E ; Emoji_Modifier_Base # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Modifier_Base # E5.0 [1] (🤟) love-you gesture +1F926 ; Emoji_Modifier_Base # E3.0 [1] (🤦) person facepalming +1F930 ; Emoji_Modifier_Base # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Modifier_Base # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F939 ; Emoji_Modifier_Base # E3.0 [7] (🤳..🤹) selfie..person juggling +1F93C..1F93E ; Emoji_Modifier_Base # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F977 ; Emoji_Modifier_Base # E13.0 [1] (🥷) ninja +1F9B5..1F9B6 ; Emoji_Modifier_Base # E11.0 [2] (🦵..🦶) leg..foot +1F9B8..1F9B9 ; Emoji_Modifier_Base # E11.0 [2] (🦸..🦹) superhero..supervillain +1F9BB ; Emoji_Modifier_Base # E12.0 [1] (🦻) ear with hearing aid +1F9CD..1F9CF ; Emoji_Modifier_Base # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..🧝) person..elf +1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 134 + +# ================================================ + +# All omitted code points have Emoji_Component=No + +0023 ; Emoji_Component # E0.0 [1] (#️) hash sign +002A ; Emoji_Component # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji_Component # E0.0 [10] (0️..9️) digit zero..digit nine +200D ; Emoji_Component # E0.0 [1] (‍) zero width joiner +20E3 ; Emoji_Component # E0.0 [1] (⃣) combining enclosing keycap +FE0F ; Emoji_Component # E0.0 [1] () VARIATION SELECTOR-16 +1F1E6..1F1FF ; Emoji_Component # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F3FB..1F3FF ; Emoji_Component # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone +1F9B0..1F9B3 ; Emoji_Component # E11.0 [4] (🦰..🦳) red hair..white hair +E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..cancel tag + +# Total elements: 146 + +# ================================================ + +# All omitted code points have Extended_Pictographic=No + +00A9 ; Extended_Pictographic# E0.6 [1] (©️) copyright +00AE ; Extended_Pictographic# E0.6 [1] (®️) registered +203C ; Extended_Pictographic# E0.6 [1] (‼️) double exclamation mark +2049 ; Extended_Pictographic# E0.6 [1] (⁉️) exclamation question mark +2122 ; Extended_Pictographic# E0.6 [1] (™️) trade mark +2139 ; Extended_Pictographic# E0.6 [1] (ℹ️) information +2194..2199 ; Extended_Pictographic# E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard +2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL +23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button +23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Extended_Pictographic# E1.0 [1] (⏯️) play or pause button +23F0 ; Extended_Pictographic# E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Extended_Pictographic# E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Extended_Pictographic# E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Extended_Pictographic# E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Extended_Pictographic# E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Extended_Pictographic# E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Extended_Pictographic# E0.6 [1] (▶️) play button +25C0 ; Extended_Pictographic# E0.6 [1] (◀️) reverse button +25FB..25FE ; Extended_Pictographic# E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet +2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR +2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION +260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone +260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX +2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check +2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X +2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock +2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX +261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up +261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones +2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN +2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard +2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH +2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross +2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM +262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent +262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE +262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol +262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang +2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH +2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face +263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY +2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign +2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH +2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign +2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO +2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces +2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT +265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn +2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit +2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT +2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit +2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT +2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit +2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT +2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs +2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS +267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol +267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL +267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity +267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol +2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 +2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG +2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick +2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor +2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords +2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol +2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic +2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER +2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear +269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES +269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT +26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage +26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN +26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol +26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle +26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL +26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE +26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball +26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING +26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN +26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain +26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR +26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus +26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick +26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING +26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet +26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES +26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains +26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry +26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD +26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine +26EA ; Extended_Pictographic# E0.6 [1] (⛪) church +26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE +26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry +26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat +26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS +26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent +26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump +26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS +2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors +2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS +2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button +2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand +270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL +270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil +2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB +2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib +2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark +2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply +271D ; Extended_Pictographic# E0.7 [1] (✝️) latin cross +2721 ; Extended_Pictographic# E0.7 [1] (✡️) star of David +2728 ; Extended_Pictographic# E0.6 [1] (✨) sparkles +2733..2734 ; Extended_Pictographic# E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Extended_Pictographic# E0.6 [1] (❄️) snowflake +2747 ; Extended_Pictographic# E0.6 [1] (❇️) sparkle +274C ; Extended_Pictographic# E0.6 [1] (❌) cross mark +274E ; Extended_Pictographic# E0.6 [1] (❎) cross mark button +2753..2755 ; Extended_Pictographic# E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark +2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation +2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart +2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide +27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow +27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop +27BF ; Extended_Pictographic# E1.0 [1] (➿) double curly loop +2934..2935 ; Extended_Pictographic# E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Extended_Pictographic# E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Extended_Pictographic# E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Extended_Pictographic# E0.6 [1] (⭐) star +2B55 ; Extended_Pictographic# E0.6 [1] (⭕) hollow red circle +3030 ; Extended_Pictographic# E0.6 [1] (〰️) wavy dash +303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark +3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button +1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon +1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker +1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. +1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL +1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button +1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. +1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Extended_Pictographic# E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Extended_Pictographic# E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. +1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. +1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Extended_Pictographic# E1.0 [1] (🌐) globe with meridians +1F311 ; Extended_Pictographic# E0.6 [1] (🌑) new moon +1F312 ; Extended_Pictographic# E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Extended_Pictographic# E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Extended_Pictographic# E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Extended_Pictographic# E0.6 [1] (🌙) crescent moon +1F31A ; Extended_Pictographic# E1.0 [1] (🌚) new moon face +1F31B ; Extended_Pictographic# E0.6 [1] (🌛) first quarter moon face +1F31C ; Extended_Pictographic# E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer +1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN +1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Extended_Pictographic# E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Extended_Pictographic# E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Extended_Pictographic# E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Extended_Pictographic# E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Extended_Pictographic# E1.0 [1] (🍋) lemon +1F34C..1F34F ; Extended_Pictographic# E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Extended_Pictographic# E1.0 [1] (🍐) pear +1F351..1F37B ; Extended_Pictographic# E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Extended_Pictographic# E1.0 [1] (🍼) baby bottle +1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS +1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS +1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal +1F3C6 ; Extended_Pictographic# E0.6 [1] (🏆) trophy +1F3C7 ; Extended_Pictographic# E1.0 [1] (🏇) horse racing +1F3C8 ; Extended_Pictographic# E0.6 [1] (🏈) american football +1F3C9 ; Extended_Pictographic# E1.0 [1] (🏉) rugby football +1F3CA ; Extended_Pictographic# E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Extended_Pictographic# E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Extended_Pictographic# E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Extended_Pictographic# E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle +1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT +1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag +1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag +1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette +1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE +1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label +1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora +1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit +1F408 ; Extended_Pictographic# E0.7 [1] (🐈) cat +1F409..1F40B ; Extended_Pictographic# E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Extended_Pictographic# E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Extended_Pictographic# E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Extended_Pictographic# E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Extended_Pictographic# E1.0 [1] (🐓) rooster +1F414 ; Extended_Pictographic# E0.6 [1] (🐔) chicken +1F415 ; Extended_Pictographic# E0.7 [1] (🐕) dog +1F416 ; Extended_Pictographic# E1.0 [1] (🐖) pig +1F417..1F429 ; Extended_Pictographic# E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Extended_Pictographic# E1.0 [1] (🐪) camel +1F42B..1F43E ; Extended_Pictographic# E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Extended_Pictographic# E0.7 [1] (🐿️) chipmunk +1F440 ; Extended_Pictographic# E0.6 [1] (👀) eyes +1F441 ; Extended_Pictographic# E0.7 [1] (👁️) eye +1F442..1F464 ; Extended_Pictographic# E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Extended_Pictographic# E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Extended_Pictographic# E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Extended_Pictographic# E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Extended_Pictographic# E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Extended_Pictographic# E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Extended_Pictographic# E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Extended_Pictographic# E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Extended_Pictographic# E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Extended_Pictographic# E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Extended_Pictographic# E0.6 [1] (📮) postbox +1F4EF ; Extended_Pictographic# E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Extended_Pictographic# E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Extended_Pictographic# E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Extended_Pictographic# E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector +1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO +1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Extended_Pictographic# E0.7 [1] (🔈) speaker low volume +1F509 ; Extended_Pictographic# E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Extended_Pictographic# E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Extended_Pictographic# E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS +1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah +1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA +1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK +1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY +1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing +1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE +1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips +1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL +1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon +1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN +1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed +1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart +1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer +1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS +1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer +1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE +1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE +1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture +1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER +1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES +1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART +1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger +1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS +1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head +1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT +1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble +1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE +1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble +1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD +1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot +1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face +1F601..1F606 ; Extended_Pictographic# E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Extended_Pictographic# E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Extended_Pictographic# E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Extended_Pictographic# E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Extended_Pictographic# E0.6 [1] (😏) smirking face +1F610 ; Extended_Pictographic# E0.7 [1] (😐) neutral face +1F611 ; Extended_Pictographic# E1.0 [1] (😑) expressionless face +1F612..1F614 ; Extended_Pictographic# E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Extended_Pictographic# E1.0 [1] (😕) confused face +1F616 ; Extended_Pictographic# E0.6 [1] (😖) confounded face +1F617 ; Extended_Pictographic# E1.0 [1] (😗) kissing face +1F618 ; Extended_Pictographic# E0.6 [1] (😘) face blowing a kiss +1F619 ; Extended_Pictographic# E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Extended_Pictographic# E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Extended_Pictographic# E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Extended_Pictographic# E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Extended_Pictographic# E1.0 [1] (😟) worried face +1F620..1F625 ; Extended_Pictographic# E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Extended_Pictographic# E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Extended_Pictographic# E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Extended_Pictographic# E1.0 [1] (😬) grimacing face +1F62D ; Extended_Pictographic# E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Extended_Pictographic# E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Extended_Pictographic# E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Extended_Pictographic# E1.0 [1] (😴) sleeping face +1F635 ; Extended_Pictographic# E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Extended_Pictographic# E1.0 [1] (😶) face without mouth +1F637..1F640 ; Extended_Pictographic# E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Extended_Pictographic# E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Extended_Pictographic# E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Extended_Pictographic# E0.6 [1] (🚀) rocket +1F681..1F682 ; Extended_Pictographic# E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Extended_Pictographic# E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Extended_Pictographic# E1.0 [1] (🚆) train +1F687 ; Extended_Pictographic# E0.6 [1] (🚇) metro +1F688 ; Extended_Pictographic# E1.0 [1] (🚈) light rail +1F689 ; Extended_Pictographic# E0.6 [1] (🚉) station +1F68A..1F68B ; Extended_Pictographic# E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Extended_Pictographic# E0.6 [1] (🚌) bus +1F68D ; Extended_Pictographic# E0.7 [1] (🚍) oncoming bus +1F68E ; Extended_Pictographic# E1.0 [1] (🚎) trolleybus +1F68F ; Extended_Pictographic# E0.6 [1] (🚏) bus stop +1F690 ; Extended_Pictographic# E1.0 [1] (🚐) minibus +1F691..1F693 ; Extended_Pictographic# E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Extended_Pictographic# E0.7 [1] (🚔) oncoming police car +1F695 ; Extended_Pictographic# E0.6 [1] (🚕) taxi +1F696 ; Extended_Pictographic# E1.0 [1] (🚖) oncoming taxi +1F697 ; Extended_Pictographic# E0.6 [1] (🚗) automobile +1F698 ; Extended_Pictographic# E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Extended_Pictographic# E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Extended_Pictographic# E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Extended_Pictographic# E0.6 [1] (🚢) ship +1F6A3 ; Extended_Pictographic# E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Extended_Pictographic# E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Extended_Pictographic# E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Extended_Pictographic# E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Extended_Pictographic# E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Extended_Pictographic# E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Extended_Pictographic# E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Extended_Pictographic# E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Extended_Pictographic# E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Extended_Pictographic# E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower +1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL +1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp +1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA +1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator +1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE +1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane +1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. +1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite +1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE +1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Extended_Pictographic# E11.0 [1] (🛹) skateboard +1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. +1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS +1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square +1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. +1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign +1F7F1..1F7FF ; Extended_Pictographic# E0.0 [15] (🟱..🟿) .. +1F80C..1F80F ; Extended_Pictographic# E0.0 [4] (🠌..🠏) .. +1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. +1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. +1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. +1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Extended_Pictographic# E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Extended_Pictographic# E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Extended_Pictographic# E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Extended_Pictographic# E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Extended_Pictographic# E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Extended_Pictographic# E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Extended_Pictographic# E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Extended_Pictographic# E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Extended_Pictographic# E12.0 [1] (🤿) diving mask +1F940..1F945 ; Extended_Pictographic# E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Extended_Pictographic# E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Extended_Pictographic# E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Extended_Pictographic# E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Extended_Pictographic# E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Extended_Pictographic# E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Extended_Pictographic# E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Extended_Pictographic# E12.0 [1] (🥱) yawning face +1F972 ; Extended_Pictographic# E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Extended_Pictographic# E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Extended_Pictographic# E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Extended_Pictographic# E14.0 [1] (🥹) face holding back tears +1F97A ; Extended_Pictographic# E11.0 [1] (🥺) pleading face +1F97B ; Extended_Pictographic# E12.0 [1] (🥻) sari +1F97C..1F97F ; Extended_Pictographic# E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Extended_Pictographic# E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Extended_Pictographic# E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Extended_Pictographic# E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Extended_Pictographic# E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Extended_Pictographic# E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Extended_Pictographic# E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Extended_Pictographic# E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Extended_Pictographic# E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Extended_Pictographic# E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Extended_Pictographic# E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Extended_Pictographic# E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Extended_Pictographic# E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Extended_Pictographic# E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Extended_Pictographic# E13.0 [1] (🧋) bubble tea +1F9CC ; Extended_Pictographic# E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch +1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (🩽..🩿) .. +1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute +1FA89..1FA8F ; Extended_Pictographic# E0.0 [7] (🪉..🪏) .. +1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing +1FABE ; Extended_Pictographic# E0.0 [1] (🪾) +1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAC6..1FACD ; Extended_Pictographic# E0.0 [8] (🫆..🫍) .. +1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod +1FADC..1FADF ; Extended_Pictographic# E0.0 [4] (🫜..🫟) .. +1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face +1FAE9..1FAEF ; Extended_Pictographic# E0.0 [7] (🫩..🫯) .. +1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand +1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. +1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. + +# Total elements: 3537 + +#EOF diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs new file mode 100644 index 000000000..d34a9ee5b --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs @@ -0,0 +1,42 @@ +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Predefined channels for drawing onto, for out-of-order drawing. +// Notes: values must be consecutively increasing, starting from 0. Higher values has higher priority. +public enum SeStringDrawChannel +{ + /// Next draw operation on the draw list will be put below . + BelowBackground, + + /// Next draw operation on the draw list will be put onto the background channel. + Background, + + /// Next draw operation on the draw list will be put above . + AboveBackground, + + /// Next draw operation on the draw list will be put below . + BelowShadow, + + /// Next draw operation on the draw list will be put onto the shadow channel. + Shadow, + + /// Next draw operation on the draw list will be put above . + AboveShadow, + + /// Next draw operation on the draw list will be put below . + BelowEdge, + + /// Next draw operation on the draw list will be put onto the edge channel. + Edge, + + /// Next draw operation on the draw list will be put above . + AboveEdge, + + /// Next draw operation on the draw list will be put below . + BelowForeground, + + /// Next draw operation on the draw list will be put onto the foreground channel. + Foreground, + + /// Next draw operation on the draw list will be put above . + AboveForeground, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs new file mode 100644 index 000000000..e03f60a32 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs @@ -0,0 +1,117 @@ +using System.Numerics; + +using ImGuiNET; + +using Lumina.Text.Payloads; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Render styles for a SeString. +public record struct SeStringDrawParams +{ + /// Gets or sets the target draw list. + /// Target draw list, default(ImDrawListPtr) to not draw, or null to use + /// (the default). + /// If this value is set, will not be called, and ImGui ID will be ignored. + /// + public ImDrawListPtr? TargetDrawList { get; set; } + + /// Gets or sets the function to be called on every codepoint and payload for the purpose of offering + /// chances to draw something else instead of glyphs or SeString payload entities. + public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; } + + /// Gets or sets the screen offset of the left top corner. + /// Screen offset to draw at, or null to use . + public Vector2? ScreenOffset { get; set; } + + /// Gets or sets the font to use. + /// Font to use, or null to use (the default). + public ImFontPtr? Font { get; set; } + + /// Gets or sets the font size. + /// Font size in pixels, or 0 to use the current ImGui font size . + /// + public float? FontSize { get; set; } + + /// Gets or sets the line height ratio. + /// 1 or null (the default) will use as the line height. + /// 2 will make line height twice the . + public float? LineHeight { get; set; } + + /// Gets or sets the wrapping width. + /// Width in pixels, or null to wrap at the end of available content region from + /// (the default). + public float? WrapWidth { get; set; } + + /// Gets or sets the thickness of underline under links. + public float? LinkUnderlineThickness { get; set; } + + /// Gets or sets the opacity, commonly called "alpha". + /// Opacity value ranging from 0(invisible) to 1(fully visible), or null to use the current ImGui + /// opacity from accessed using . + public float? Opacity { get; set; } + + /// Gets or sets the strength of the edge, which will have effects on the edge opacity. + /// Strength value ranging from 0(invisible) to 1(fully visible), or null to use the default value + /// of 0.25f that might be subject to change in the future. + public float? EdgeStrength { get; set; } + + /// Gets or sets the theme that will decide the colors to use for + /// and . + /// 0 to use colors for Dark theme, 1 to use colors for Light theme, 2 to use colors + /// for Classic FF theme, 3 to use colors for Clear Blue theme, or null to use the theme set from the + /// game configuration. + public int? ThemeIndex { get; set; } + + /// Gets or sets the color of the rendered text. + /// Color in RGBA, or null to use (the default). + public uint? Color { get; set; } + + /// Gets or sets the color of the rendered text edge. + /// Color in RGBA, or null to use opaque black (the default). + public uint? EdgeColor { get; set; } + + /// Gets or sets the color of the rendered text shadow. + /// Color in RGBA, or null to use opaque black (the default). + public uint? ShadowColor { get; set; } + + /// Gets or sets the background color of a link when hovered. + /// Color in RGBA, or null to use (the default). + public uint? LinkHoverBackColor { get; set; } + + /// Gets or sets the background color of a link when active. + /// Color in RGBA, or null to use (the default). + public uint? LinkActiveBackColor { get; set; } + + /// Gets or sets a value indicating whether to force the color of the rendered text edge. + /// If set, then and will be + /// ignored. + public bool ForceEdgeColor { get; set; } + + /// Gets or sets a value indicating whether the text is rendered bold. + public bool Bold { get; set; } + + /// Gets or sets a value indicating whether the text is rendered italic. + public bool Italic { get; set; } + + /// Gets or sets a value indicating whether the text is rendered with edge. + /// If an edge color is pushed with or + /// , it will be drawn regardless. Set to + /// true and set to 0 to fully disable edge. + public bool Edge { get; set; } + + /// Gets or sets a value indicating whether the text is rendered with shadow. + public bool Shadow { get; set; } + + /// Gets the effective font. + internal readonly unsafe ImFont* EffectiveFont => + (this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null + ? f.NativePtr + : throw new ArgumentException("Specified font is empty."); + + /// Gets the effective line height in pixels. + internal readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f); + + /// Gets the effective opacity. + internal readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha; +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs new file mode 100644 index 000000000..f9dae288b --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Numerics; + +using Dalamud.Game.Text.SeStringHandling; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Represents the result of a rendered interactable SeString. +public ref struct SeStringDrawResult +{ + private Payload? lazyPayload; + + /// Gets the visible size of the text rendered/to be rendered. + public Vector2 Size { get; init; } + + /// Gets a value indicating whether a payload or the whole text has been clicked. + public bool Clicked { get; init; } + + /// Gets the offset of the interacted payload, or -1 if none. + public int InteractedPayloadOffset { get; init; } + + /// Gets the interacted payload envelope, or if none. + public ReadOnlySpan InteractedPayloadEnvelope { get; init; } + + /// Gets the interacted payload, or null if none. + public Payload? InteractedPayload => + this.lazyPayload ??= + this.InteractedPayloadEnvelope.IsEmpty + ? default + : SeString.Parse(this.InteractedPayloadEnvelope).Payloads.FirstOrDefault(); +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs new file mode 100644 index 000000000..5f95ac1b9 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -0,0 +1,406 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Calculated values from using ImGui styles. +[StructLayout(LayoutKind.Sequential)] +public unsafe ref struct SeStringDrawState +{ + private static readonly int ChannelCount = Enum.GetValues().Length; + + private readonly ImDrawList* drawList; + private readonly SeStringColorStackSet colorStackSet; + private readonly ImDrawListSplitter* splitter; + + /// Initializes a new instance of the struct. + /// Raw SeString byte span. + /// Instance of to initialize from. + /// Instance of to use. + /// Instance of ImGui Splitter to use. + internal SeStringDrawState( + ReadOnlySpan span, + scoped in SeStringDrawParams ssdp, + SeStringColorStackSet colorStackSet, + ImDrawListSplitter* splitter) + { + this.colorStackSet = colorStackSet; + this.splitter = splitter; + this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); + this.Span = span; + this.GetEntity = ssdp.GetEntity; + this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); + this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y)); + this.Font = ssdp.EffectiveFont; + this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); + this.FontSizeScale = this.FontSize / this.Font->FontSize; + this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight); + this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; + this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; + this.Opacity = ssdp.EffectiveOpacity; + this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity; + this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); + this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000; + this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000; + this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); + this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); + this.ForceEdgeColor = ssdp.ForceEdgeColor; + this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; + this.Bold = ssdp.Bold; + this.Italic = ssdp.Italic; + this.Edge = ssdp.Edge; + this.Shadow = ssdp.Shadow; + } + + /// + public readonly ImDrawListPtr DrawList => new(this.drawList); + + /// Gets the raw SeString byte span. + public ReadOnlySpan Span { get; } + + /// + public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; } + + /// + public Vector2 ScreenOffset { get; } + + /// + public ImFont* Font { get; } + + /// + public float FontSize { get; } + + /// Gets the multiplier value for glyph metrics, so that it scales to . + /// Multiplied to , + /// , and distance values from + /// . + public float FontSizeScale { get; } + + /// + public float LineHeight { get; } + + /// + public float WrapWidth { get; } + + /// + public float LinkUnderlineThickness { get; } + + /// + public float Opacity { get; } + + /// + public float EdgeOpacity { get; } + + /// + public int ThemeIndex { get; } + + /// + public uint Color { get; set; } + + /// + public uint EdgeColor { get; set; } + + /// + public uint ShadowColor { get; set; } + + /// + public uint LinkHoverBackColor { get; } + + /// + public uint LinkActiveBackColor { get; } + + /// + public bool ForceEdgeColor { get; } + + /// + public bool Bold { get; set; } + + /// + public bool Italic { get; set; } + + /// + public bool Edge { get; set; } + + /// + public bool Shadow { get; set; } + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawEdge => + (this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000; + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 }; + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 }; + + /// Sets the current channel in the ImGui draw list splitter. + /// Channel to switch to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetCurrentChannel(SeStringDrawChannel channelIndex) => + ImGuiNative.ImDrawListSplitter_SetCurrentChannel(this.splitter, this.drawList, (int)channelIndex); + + /// Draws a single texture. + /// ImGui texture ID to draw from. + /// Offset of the glyph in pixels w.r.t. . + /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. + /// Color of the glyph in RGBA. + public readonly void Draw( + nint igTextureId, + Vector2 offset, + Vector2 size, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue) + { + offset += this.ScreenOffset; + this.DrawList.AddImageQuad( + igTextureId, + offset, + offset + size with { X = 0 }, + offset + size, + offset + size with { Y = 0 }, + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + /// Draws a single texture. + /// ImGui texture ID to draw from. + /// Offset of the glyph in pixels w.r.t. . + /// Left top corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. + /// Color of the glyph in RGBA. + /// Transformation for and that will push + /// top and bottom pixels to apply faux italicization by and + /// respectively. + public readonly void Draw( + nint igTextureId, + Vector2 offset, + Vector2 xy0, + Vector2 xy1, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue, + Vector2 dyItalic = default) + { + offset += this.ScreenOffset; + this.DrawList.AddImageQuad( + igTextureId, + offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), + offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + /// Draws a single glyph using current styling configurations. + /// Glyph to draw. + /// Offset of the glyph in pixels w.r.t. . + internal readonly void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) + { + var texId = this.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID; + var xy0 = new Vector2( + MathF.Round(g.X0 * this.FontSizeScale), + MathF.Round(g.Y0 * this.FontSizeScale)); + var xy1 = new Vector2( + MathF.Round(g.X1 * this.FontSizeScale), + MathF.Round(g.Y1 * this.FontSizeScale)); + var dxBold = this.Bold ? 2 : 1; + var dyItalic = this.Italic + ? new Vector2(this.FontSize - xy0.Y, this.FontSize - xy1.Y) / 6 + : Vector2.Zero; + // Note: dyItalic values can be non-rounded; the glyph will be rendered sheared anyway. + + offset.Y += MathF.Round((this.LineHeight - this.FontSize) / 2f); + + if (this.ShouldDrawShadow) + { + this.SetCurrentChannel(SeStringDrawChannel.Shadow); + for (var i = 0; i < dxBold; i++) + this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.ShadowColor, dyItalic); + } + + if (this.ShouldDrawEdge) + { + this.SetCurrentChannel(SeStringDrawChannel.Edge); + + // Top & Bottom + for (var i = -1; i <= dxBold; i++) + { + this.Draw(texId, offset + new Vector2(i, -1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + } + + // Left & Right + this.Draw(texId, offset + new Vector2(-1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + this.Draw(texId, offset + new Vector2(1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + } + + if (this.ShouldDrawForeground) + { + this.SetCurrentChannel(SeStringDrawChannel.Foreground); + for (var i = 0; i < dxBold; i++) + this.Draw(texId, offset + new Vector2(i, 0), xy0, xy1, g.UV0, g.UV1, this.Color, dyItalic); + } + } + + /// Draws an underline, for links. + /// Offset of the glyph in pixels w.r.t. + /// . + /// Advance width of the glyph. + internal readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth) + { + if (this.LinkUnderlineThickness < 1f) + return; + + offset += this.ScreenOffset; + offset.Y += (this.LinkUnderlineThickness - 1) / 2f; + offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale)); + + this.SetCurrentChannel(SeStringDrawChannel.Foreground); + this.DrawList.AddLine( + offset, + offset + new Vector2(advanceWidth, 0), + this.Color, + this.LinkUnderlineThickness); + + if (this is { Shadow: true, ShadowColor: >= 0x1000000 }) + { + this.SetCurrentChannel(SeStringDrawChannel.Shadow); + this.DrawList.AddLine( + offset + new Vector2(0, 1), + offset + new Vector2(advanceWidth, 1), + this.ShadowColor, + this.LinkUnderlineThickness); + } + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) + { + var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue + ? ImGuiNative.ImFont_FindGlyph(this.Font, (ushort)rune.Value) + : this.Font->FallbackGlyph; + return ref *(ImGuiHelpers.ImFontGlyphReal*)p; + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display, that will be + /// changed on return to the rune corresponding to the fallback glyph if a glyph not corresponding to the + /// requested glyph is being returned. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(ref Rune rune) + { + ref var glyph = ref this.FindGlyph(rune); + if (rune.Value != glyph.Codepoint && !Rune.TryCreate(glyph.Codepoint, out rune)) + rune = Rune.ReplacementChar; + return ref glyph; + } + + /// Gets the kerning adjustment between two glyphs in a succession corresponding to the given runes. + /// + /// Rune representing the glyph on the left side of a pair. + /// Rune representing the glyph on the right side of a pair. + /// Distance adjustment in pixels, scaled to the size specified from + /// , and rounded. + internal readonly float CalculateScaledDistance(Rune left, Rune right) + { + // Kerning distance entries are ignored if NUL, U+FFFF(invalid Unicode character), or characters outside + // the basic multilingual plane(BMP) is involved. + if (left.Value is <= 0 or >= char.MaxValue) + return 0; + if (right.Value is <= 0 or >= char.MaxValue) + return 0; + + return MathF.Round( + ImGuiNative.ImFont_GetDistanceAdjustmentForPair( + this.Font, + (ushort)left.Value, + (ushort)right.Value) * this.FontSizeScale); + } + + /// Handles style adjusting payloads. + /// Payload to handle. + /// true if the payload was handled. + internal bool HandleStyleAdjustingPayloads(ReadOnlySePayloadSpan payload) + { + switch (payload.MacroCode) + { + case MacroCode.Color: + this.colorStackSet.HandleColorPayload(ref this, payload); + return true; + + case MacroCode.EdgeColor: + this.colorStackSet.HandleEdgeColorPayload(ref this, payload); + return true; + + case MacroCode.ShadowColor: + this.colorStackSet.HandleShadowColorPayload(ref this, payload); + return true; + + case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + // doesn't actually work in chat log + this.Bold = u != 0; + return true; + + case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Italic = u != 0; + return true; + + case MacroCode.Edge when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Edge = u != 0; + return true; + + case MacroCode.Shadow when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Shadow = u != 0; + return true; + + case MacroCode.ColorType: + this.colorStackSet.HandleColorTypePayload(ref this, payload); + return true; + + case MacroCode.EdgeColorType: + this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload); + return true; + + default: + return false; + } + } + + /// Splits the draw list. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly void SplitDrawList() => + ImGuiNative.ImDrawListSplitter_Split(this.splitter, this.drawList, ChannelCount); + + /// Merges the draw list. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly void MergeDrawList() => ImGuiNative.ImDrawListSplitter_Merge(this.splitter, this.drawList); +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs new file mode 100644 index 000000000..b14e12073 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs @@ -0,0 +1,48 @@ +using System.Numerics; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Replacement entity to draw instead while rendering a SeString. +public readonly record struct SeStringReplacementEntity +{ + /// Initializes a new instance of the struct. + /// Number of bytes taken by this entity. Must be at least 0. If 0, then the entity + /// is considered as empty. + /// Size of this entity in pixels. Components must be non-negative. + /// Draw callback. + public SeStringReplacementEntity(int byteLength, Vector2 size, DrawDelegate draw) + { + ArgumentOutOfRangeException.ThrowIfNegative(byteLength); + ArgumentOutOfRangeException.ThrowIfNegative(size.X, nameof(size)); + ArgumentOutOfRangeException.ThrowIfNegative(size.Y, nameof(size)); + ArgumentNullException.ThrowIfNull(draw); + this.ByteLength = byteLength; + this.Size = size; + this.Draw = draw; + } + + /// Gets the replacement entity. + /// Draw state. + /// Byte offset in . + /// Replacement entity definition, or default if none. + public delegate SeStringReplacementEntity GetEntityDelegate(scoped in SeStringDrawState state, int byteOffset); + + /// Draws the replacement entity. + /// Draw state. + /// Byte offset in . + /// Relative offset in pixels w.r.t. . + public delegate void DrawDelegate(scoped in SeStringDrawState state, int byteOffset, Vector2 offset); + + /// Gets the number of bytes taken by this entity. + public int ByteLength { get; init; } + + /// Gets the size of this entity in pixels. + public Vector2 Size { get; init; } + + /// Gets the Draw callback. + public DrawDelegate Draw { get; init; } + + /// Gets a value indicating whether this entity is empty. + /// Instance of to test. + public static implicit operator bool(in SeStringReplacementEntity e) => e.ByteLength != 0; +} diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs new file mode 100644 index 000000000..56050cdfb --- /dev/null +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -0,0 +1,228 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; + +using Dalamud.Utility; + +using Serilog; + +namespace Dalamud.Interface.Internal.Asserts; + +/// +/// Class responsible for registering and handling ImGui asserts. +/// +internal class AssertHandler : IDisposable +{ + private const int HideThreshold = 20; + private const int HidePrintEvery = 500; + + private readonly HashSet ignoredAsserts = []; + private readonly Dictionary assertCounts = new(); + + // Store callback to avoid it from being GC'd + private readonly AssertCallbackDelegate callback; + + private bool everShownAssertThisSession = false; + + /// + /// Initializes a new instance of the class. + /// + public AssertHandler() + { + this.callback = (expr, file, line) => this.OnImGuiAssert(expr, file, line); + } + + private delegate void AssertCallbackDelegate( + [MarshalAs(UnmanagedType.LPStr)] string expr, + [MarshalAs(UnmanagedType.LPStr)] string file, + int line); + + /// + /// Gets or sets a value indicating whether ImGui asserts should be shown to the user. + /// + public bool ShowAsserts { get; set; } + + /// + /// Gets or sets a value indicating whether we want to hide asserts that occur frequently (= every update) + /// and whether we want to log callstacks. + /// + public bool EnableVerboseLogging { get; set; } + + /// + /// Register the cimgui assert handler with the native library. + /// + public void Setup() + { + CustomNativeFunctions.igCustom_SetAssertCallback(this.callback); + } + + /// + /// Unregister the cimgui assert handler with the native library. + /// + public void Shutdown() + { + CustomNativeFunctions.igCustom_SetAssertCallback(null); + } + + /// + public void Dispose() + { + this.Shutdown(); + } + + private void OnImGuiAssert(string expr, string file, int line) + { + var key = $"{file}:{line}"; + if (this.ignoredAsserts.Contains(key)) + return; + + // Don't log unless we've ever shown an assert this session + if (!this.ShowAsserts && !this.everShownAssertThisSession) + return; + + Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace()).ToString()); + + if (!this.EnableVerboseLogging) + { + if (this.assertCounts.TryGetValue(key, out var count)) + { + this.assertCounts[key] = count + 1; + + if (count <= HideThreshold || count % HidePrintEvery == 0) + { + Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)", + expr, + file, + line, + count); + } + } + else + { + this.assertCounts[key] = 1; + } + } + else + { + Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}", + expr, + file, + line, + stackTrace.Value); + } + + if (!this.ShowAsserts) + return; + + this.everShownAssertThisSession = true; + + string? GetRepoUrl() + { + // TODO: implot, imguizmo? + const string userName = "goatcorp"; + const string repoName = "gc-imgui"; + const string branch = "1.88-enhanced-abifix"; + + if (!file.Contains("imgui", StringComparison.OrdinalIgnoreCase)) + return null; + + var lastSlash = file.LastIndexOf('\\'); + var fileName = file[(lastSlash + 1)..]; + return $"https://github.com/{userName}/{repoName}/blob/{branch}/{fileName}#L{line}"; + } + + // grab the stack trace now that we've decided to show UI. + _ = stackTrace.Value; + + var gitHubUrl = GetRepoUrl(); + var showOnGitHubButton = new TaskDialogButton + { + Text = "Open GitHub", + AllowCloseDialog = false, + Enabled = !gitHubUrl.IsNullOrEmpty(), + }; + showOnGitHubButton.Click += (_, _) => + { + if (!gitHubUrl.IsNullOrEmpty()) + Util.OpenLink(gitHubUrl); + }; + + var breakButton = new TaskDialogButton + { + Text = "Break", + AllowCloseDialog = true, + }; + + var disableButton = new TaskDialogButton + { + Text = "Disable for this session", + AllowCloseDialog = true, + }; + + var ignoreButton = TaskDialogButton.Ignore; + + TaskDialogButton? result = null; + void DialogThreadStart() + { + // TODO(goat): This is probably not gonna work if we showed the loading dialog + // this session since it already loaded visual styles... + Application.EnableVisualStyles(); + + var page = new TaskDialogPage + { + Heading = "ImGui assertion failed", + Caption = "Dalamud", + Expander = new TaskDialogExpander + { + CollapsedButtonText = "Show stack trace", + ExpandedButtonText = "Hide stack trace", + Text = stackTrace.Value, + }, + Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}", + Icon = TaskDialogIcon.Warning, + Buttons = + [ + showOnGitHubButton, + breakButton, + disableButton, + ignoreButton, + ], + DefaultButton = showOnGitHubButton, + }; + + result = TaskDialog.ShowDialog(page); + } + + // Run in a separate thread because of STA and to not mess up other stuff + var thread = new Thread(DialogThreadStart) + { + Name = "Dalamud ImGui Assert Dialog", + }; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + if (result == breakButton) + { + Debugger.Break(); + } + else if (result == disableButton) + { + this.ShowAsserts = false; + } + else if (result == ignoreButton) + { + this.ignoredAsserts.Add(key); + } + } + + private static class CustomNativeFunctions + { + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] +#pragma warning disable SA1300 + public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate? callback); +#pragma warning restore SA1300 + } +} diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index fb64ad979..636f71bfa 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -178,7 +178,7 @@ internal class DalamudCommands : IServiceType if (arguments.IsNullOrWhitespace()) { chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); - foreach (var cmd in commandManager.Commands) + foreach (var cmd in commandManager.Commands.OrderBy(cInfo => cInfo.Key)) { if (!cmd.Value.ShowInHelp) continue; @@ -266,7 +266,7 @@ internal class DalamudCommands : IServiceType else { // Revert to the original BGM by specifying an invalid one - gameGui.SetBgm(9999); + gameGui.ResetBgm(); } } diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index c061ec12d..a1c4a0a95 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -16,7 +16,6 @@ using Dalamud.Game.Text; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.Colors; using Dalamud.Interface.GameFonts; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility; @@ -38,9 +37,6 @@ namespace Dalamud.Interface.Internal; [ServiceManager.EarlyLoadedService] internal sealed unsafe class DalamudIme : IInternalDisposableService { - private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; - private const int CImGuiStbTextUndoOffset = 0xB59C0; - private const int ImePageSize = 9; private static readonly Dictionary WmNames = @@ -70,11 +66,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService UnicodeRanges.HangulJamoExtendedB, }; - private static readonly delegate* unmanaged - StbTextMakeUndoReplace; - - private static readonly delegate* unmanaged StbTextUndo; - [ServiceManager.ServiceDependency] private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); @@ -135,13 +126,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService { return; } - - StbTextMakeUndoReplace = - (delegate* unmanaged) - (cimgui + CImGuiStbTextCreateUndoOffset); - StbTextUndo = - (delegate* unmanaged) - (cimgui + CImGuiStbTextUndoOffset); } [ServiceManager.ServiceConstructor] @@ -185,7 +169,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService return true; if (!ImGui.GetIO().ConfigInputTextCursorBlink) return true; - var textState = TextState; + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0) return true; if (textState->CursorAnim <= 0) @@ -194,9 +178,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService } } - private static ImGuiInputTextState* TextState => - (ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset); - /// Gets a value indicating whether to display partial conversion status. private bool ShowPartialConversion => this.partialConversionFrom != 0 || this.partialConversionTo != this.compositionString.Length; @@ -341,7 +322,8 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService try { - var invalidTarget = TextState->Id == 0 || (TextState->Flags & ImGuiInputTextFlags.ReadOnly) != 0; + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); + var invalidTarget = textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0; #if IMEDEBUG switch (args.Message) @@ -570,19 +552,20 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService this.ReflectCharacterEncounters(newString); + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); if (this.temporaryUndoSelection is not null) { - TextState->Undo(); - TextState->SelectionTuple = this.temporaryUndoSelection.Value; + textState->Undo(); + textState->SelectionTuple = this.temporaryUndoSelection.Value; this.temporaryUndoSelection = null; } - TextState->SanitizeSelectionRange(); - if (TextState->ReplaceSelectionAndPushUndo(newString)) - this.temporaryUndoSelection = TextState->SelectionTuple; + textState->SanitizeSelectionRange(); + if (textState->ReplaceSelectionAndPushUndo(newString)) + this.temporaryUndoSelection = textState->SelectionTuple; // Put the cursor at the beginning, so that the candidate window appears aligned with the text. - TextState->SetSelectionRange(TextState->SelectionTuple.Start, newString.Length, 0); + textState->SetSelectionRange(textState->SelectionTuple.Start, newString.Length, 0); if (finalCommit) { @@ -627,7 +610,10 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService this.partialConversionFrom = this.partialConversionTo = 0; this.compositionCursorOffset = 0; this.temporaryUndoSelection = null; - TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd; + + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); + textState->Stb.SelectStart = textState->Stb.Cursor = textState->Stb.SelectEnd; + this.candidateStrings.Clear(); this.immCandNative = default; if (invokeCancel) @@ -1030,14 +1016,14 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService (s, e) = (e, s); } - public void Undo() => StbTextUndo(this.ThisPtr, &this.ThisPtr->Stb); + public void Undo() => CustomNativeFunctions.igCustom_StbTextUndo(this.ThisPtr); public bool MakeUndoReplace(int offset, int oldLength, int newLength) { if (oldLength == 0 && newLength == 0) return false; - StbTextMakeUndoReplace(this.ThisPtr, &this.ThisPtr->Stb, offset, oldLength, newLength); + CustomNativeFunctions.igCustom_StbTextMakeUndoReplace(this.ThisPtr, offset, oldLength, newLength); return true; } @@ -1113,6 +1099,20 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService } } + private static class CustomNativeFunctions + { +#pragma warning disable SA1300 + [DllImport("cimgui")] + public static extern ImGuiInputTextState* igCustom_GetInputTextState(); + + [DllImport("cimgui")] + public static extern void igCustom_StbTextMakeUndoReplace(ImGuiInputTextState* str, int where, int old_length, int new_length); + + [DllImport("cimgui")] + public static extern void igCustom_StbTextUndo(ImGuiInputTextState* str); +#pragma warning restore SA1300 + } + #if IMEDEBUG private static class ImeDebug { diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 793dfddd9..5d21e954a 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -3,11 +3,14 @@ using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CheapLoc; using Dalamud.Configuration.Internal; using Dalamud.Console; +using Dalamud.Data; +using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Keys; @@ -16,7 +19,6 @@ using Dalamud.Game.Internal; using Dalamud.Hooking; using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.Colors; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.Internal.Windows.Data; using Dalamud.Interface.Internal.Windows.PluginInstaller; @@ -25,6 +27,7 @@ using Dalamud.Interface.Internal.Windows.Settings; using Dalamud.Interface.Internal.Windows.StyleEditor; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Style; +using Dalamud.Interface.Textures; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; @@ -35,6 +38,8 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; using ImPlotNET; @@ -90,7 +95,7 @@ internal class DalamudInterface : IInternalDisposableService private bool isImPlotDrawDemoWindow = false; private bool isImGuiTestWindowsInMonospace = false; private bool isImGuiDrawMetricsWindow = false; - + [ServiceManager.ServiceConstructor] private DalamudInterface( Dalamud dalamud, @@ -103,14 +108,15 @@ internal class DalamudInterface : IInternalDisposableService ClientState clientState, TitleScreenMenu titleScreenMenu, GameGui gameGui, - ConsoleManager consoleManager) + ConsoleManager consoleManager, + AddonLifecycle addonLifecycle) { this.dalamud = dalamud; this.configuration = configuration; this.interfaceManager = interfaceManager; this.WindowSystem = new WindowSystem("DalamudCore"); - + this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; this.dataWindow = new DataWindow() { IsOpen = false }; @@ -129,7 +135,8 @@ internal class DalamudInterface : IInternalDisposableService framework, gameGui, titleScreenMenu, - consoleManager) { IsOpen = false }; + consoleManager, + addonLifecycle) { IsOpen = false }; this.changelogWindow = new ChangelogWindow( this.titleScreenMenuWindow, fontAtlasFactory, @@ -156,7 +163,7 @@ internal class DalamudInterface : IInternalDisposableService this.WindowSystem.AddWindow(this.branchSwitcherWindow); this.WindowSystem.AddWindow(this.hitchSettingsWindow); - ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; + this.interfaceManager.ShowAsserts = configuration.ImGuiAssertsEnabledAtStartup ?? false; this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; this.interfaceManager.Draw += this.OnDraw; @@ -166,16 +173,16 @@ internal class DalamudInterface : IInternalDisposableService { titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), this.OpenPluginInstaller); titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), this.OpenSettings); titleScreenMenu.AddEntryCore( "Toggle Dev Menu", - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), () => Service.GetNullable()?.ToggleDevMenu(), VirtualKey.SHIFT); @@ -183,14 +190,14 @@ internal class DalamudInterface : IInternalDisposableService { titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), () => this.isImGuiDrawDevMenu = true); } }); this.creditsDarkeningAnimation.Point1 = Vector2.Zero; this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha); - + // This is temporary, until we know the repercussions of vtable hooking mode consoleManager.AddCommand( "dalamud.interface.swapchain_mode", @@ -209,14 +216,14 @@ internal class DalamudInterface : IInternalDisposableService Log.Error("Unknown swapchain mode: {Mode}", mode); return false; } - + this.configuration.QueueSave(); return true; }); } - + private delegate nint CrashDebugDelegate(nint self); - + /// /// Gets the number of frames since Dalamud has loaded. /// @@ -316,7 +323,7 @@ internal class DalamudInterface : IInternalDisposableService this.pluginStatWindow.IsOpen = true; this.pluginStatWindow.BringToFront(); } - + /// /// Opens the on the plugin installed. /// @@ -381,7 +388,7 @@ internal class DalamudInterface : IInternalDisposableService this.profilerWindow.IsOpen = true; this.profilerWindow.BringToFront(); } - + /// /// Opens the . /// @@ -693,7 +700,7 @@ internal class DalamudInterface : IInternalDisposableService ImGui.EndMenu(); } - + var logSynchronously = this.configuration.LogSynchronously; if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously)) { @@ -785,14 +792,14 @@ internal class DalamudInterface : IInternalDisposableService } ImGui.Separator(); - + if (ImGui.BeginMenu("Crash game")) { if (ImGui.MenuItem("Access Violation")) { Marshal.ReadByte(IntPtr.Zero); - } - + } + if (ImGui.MenuItem("Set UiModule to NULL")) { unsafe @@ -801,7 +808,7 @@ internal class DalamudInterface : IInternalDisposableService framework->UIModule = (UIModule*)0; } } - + if (ImGui.MenuItem("Set UiModule to invalid ptr")) { unsafe @@ -810,7 +817,7 @@ internal class DalamudInterface : IInternalDisposableService framework->UIModule = (UIModule*)0x12345678; } } - + if (ImGui.MenuItem("Deref nullptr in Hook")) { unsafe @@ -825,7 +832,25 @@ internal class DalamudInterface : IInternalDisposableService hook.Enable(); } } - + + if (ImGui.MenuItem("Cause CLR fastfail")) + { + unsafe void CauseFastFail() + { + // ReSharper disable once NotAccessedVariable + var texture = Unsafe.AsRef((void*)0x12345678); + texture.TextureType = TextureType.Crest; + } + + Service.Get().RunOnTick(CauseFastFail); + } + + if (ImGui.MenuItem("Cause ImGui assert")) + { + ImGui.PopStyleVar(); + ImGui.PopStyleVar(); + } + ImGui.EndMenu(); } @@ -841,7 +866,7 @@ internal class DalamudInterface : IInternalDisposableService { this.OpenBranchSwitcher(); } - + ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false); ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false); ImGui.MenuItem($"CLR: {Environment.Version}", false); @@ -858,18 +883,27 @@ internal class DalamudInterface : IInternalDisposableService ImGui.Separator(); - var val = ImGuiManagedAsserts.AssertsEnabled; - if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) + var showAsserts = this.interfaceManager.ShowAsserts; + if (ImGui.MenuItem("Enable assert popups", string.Empty, ref showAsserts)) { - ImGuiManagedAsserts.AssertsEnabled = val; + this.interfaceManager.ShowAsserts = showAsserts; } - if (ImGui.MenuItem("Enable asserts at startup", null, this.configuration.AssertsEnabledAtStartup)) + var enableVerboseAsserts = this.interfaceManager.EnableVerboseAssertLogging; + if (ImGui.MenuItem("Enable verbose assert logging", string.Empty, ref enableVerboseAsserts)) { - this.configuration.AssertsEnabledAtStartup = !this.configuration.AssertsEnabledAtStartup; + this.interfaceManager.EnableVerboseAssertLogging = enableVerboseAsserts; + } + + var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false; + if (ImGui.MenuItem("Enable asserts at startup", null, assertsEnabled)) + { + this.configuration.ImGuiAssertsEnabledAtStartup = !assertsEnabled; this.configuration.QueueSave(); } + ImGui.Separator(); + if (ImGui.MenuItem("Clear focus")) { ImGui.SetWindowFocus(null); @@ -917,7 +951,7 @@ internal class DalamudInterface : IInternalDisposableService { this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo; } - + ImGui.Separator(); if (ImGui.MenuItem("Show loading window")) @@ -994,6 +1028,11 @@ internal class DalamudInterface : IInternalDisposableService pluginManager.LoadBannedPlugins = !pluginManager.LoadBannedPlugins; } + if (pluginManager.SafeMode && ImGui.MenuItem("Disable Safe Mode")) + { + pluginManager.SafeMode = false; + } + ImGui.Separator(); ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false); diff --git a/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs b/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs index f0ce6bc82..3d31bbda6 100644 --- a/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs +++ b/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs @@ -32,19 +32,21 @@ internal static partial class DalamudComponents var pm = Service.GetNullable(); if (pm == null) return 0; - + var addPluginToProfilePopupId = ImGui.GetID(id); using var popup = ImRaii.Popup(id); if (popup.Success) { var width = ImGuiHelpers.GlobalScale * 300; - + ImGui.SetNextItemWidth(width); ImGui.InputTextWithHint("###pluginPickerSearch", Locs.SearchHint, ref pickerSearch, 255); var currentSearchString = pickerSearch; - if (ImGui.BeginListBox("###pluginPicker", new Vector2(width, width - 80))) + + using var listBox = ImRaii.ListBox("###pluginPicker", new Vector2(width, width - 80)); + if (listBox.Success) { // TODO: Plugin searching should be abstracted... installer and this should use the same search var plugins = pm.InstalledPlugins.Where( @@ -53,19 +55,15 @@ internal static partial class DalamudComponents currentSearchString, StringComparison.InvariantCultureIgnoreCase))) .Where(pluginFiltered ?? (_ => true)); - + foreach (var plugin in plugins) { - using var disabled2 = - ImRaii.Disabled(pluginDisabled(plugin)); - + using var disabled2 = ImRaii.Disabled(pluginDisabled(plugin)); if (ImGui.Selectable($"{plugin.Manifest.Name}{(plugin is LocalDevPlugin ? "(dev plugin)" : string.Empty)}###selector{plugin.Manifest.InternalName}")) { onClicked(plugin); } } - - ImGui.EndListBox(); } } diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs deleted file mode 100644 index 139dd96e2..000000000 --- a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Diagnostics; -using System.Linq; -using System.Numerics; - -using Dalamud.Hooking; - -using ImGuiNET; - -namespace Dalamud.Interface.Internal; - -/// -/// Fixes ImDrawList not correctly dealing with the current texture for that draw list not in tune with the global -/// state. Currently, ImDrawList::AddPolyLine and ImDrawList::AddRectFilled are affected. -/// -/// * The implementation for AddRectFilled is entirely replaced with the hook below. -/// * The implementation for AddPolyLine is wrapped with Push/PopTextureID. -/// -/// TODO: -/// * imgui_draw.cpp:1433 ImDrawList::AddRectFilled -/// The if block needs a PushTextureID(_Data->TexIdCommon)/PopTextureID() block, -/// if _Data->TexIdCommon != _CmdHeader.TextureId. -/// * imgui_draw.cpp:729 ImDrawList::AddPolyLine -/// The if block always needs to call PushTextureID if the abovementioned condition is not met. -/// Change push_texture_id to only have one condition. -/// -[ServiceManager.EarlyLoadedService] -internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableService -{ - private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; - private const int CImGuiImDrawListAddRectFilled = 0x59FD0; - private const int CImGuiImDrawListSharedDataTexIdCommonOffset = 0; - - private readonly Hook hookImDrawListAddPolyline; - private readonly Hook hookImDrawListAddRectFilled; - - [ServiceManager.ServiceConstructor] - private ImGuiDrawListFixProvider(InterfaceManager.InterfaceManagerWithScene imws) - { - // Force cimgui.dll to be loaded. - _ = ImGui.GetCurrentContext(); - var cimgui = Process.GetCurrentProcess().Modules.Cast() - .First(x => x.ModuleName == "cimgui.dll") - .BaseAddress; - - this.hookImDrawListAddPolyline = Hook.FromAddress( - cimgui + CImGuiImDrawListAddPolyLineOffset, - this.ImDrawListAddPolylineDetour); - this.hookImDrawListAddRectFilled = Hook.FromAddress( - cimgui + CImGuiImDrawListAddRectFilled, - this.ImDrawListAddRectFilledDetour); - this.hookImDrawListAddPolyline.Enable(); - this.hookImDrawListAddRectFilled.Enable(); - } - - private delegate void ImDrawListAddPolyLine( - ImDrawListPtr drawListPtr, - ref Vector2 points, - int pointsCount, - uint color, - ImDrawFlags flags, - float thickness); - - private delegate void ImDrawListAddRectFilled( - ImDrawListPtr drawListPtr, - ref Vector2 min, - ref Vector2 max, - uint col, - float rounding, - ImDrawFlags flags); - - /// - void IInternalDisposableService.DisposeService() - { - this.hookImDrawListAddPolyline.Dispose(); - this.hookImDrawListAddRectFilled.Dispose(); - } - - private void ImDrawListAddRectFilledDetour( - ImDrawListPtr drawListPtr, - ref Vector2 min, - ref Vector2 max, - uint col, - float rounding, - ImDrawFlags flags) - { - // Skip drawing if we're drawing something with alpha value of 0. - if ((col & 0xFF000000) == 0) - return; - - if (rounding < 0.5f || (flags & ImDrawFlags.RoundCornersMask) == ImDrawFlags.RoundCornersMask) - { - // Take the fast path of drawing two triangles if no rounded corners are required. - - var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); - var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; - if (pushTextureId) - drawListPtr.PushTextureID(texIdCommon); - - drawListPtr.PrimReserve(6, 4); - drawListPtr.PrimRect(min, max, col); - - if (pushTextureId) - drawListPtr.PopTextureID(); - } - else - { - // Defer drawing rectangle with rounded corners to path drawing operations. - // Note that this may have a slightly different extent behaviors from the above if case. - // This is how it is in imgui_draw.cpp. - drawListPtr.PathRect(min, max, rounding, flags); - drawListPtr.PathFillConvex(col); - } - } - - private void ImDrawListAddPolylineDetour( - ImDrawListPtr drawListPtr, - ref Vector2 points, - int pointsCount, - uint color, - ImDrawFlags flags, - float thickness) - { - var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); - var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; - if (pushTextureId) - drawListPtr.PushTextureID(texIdCommon); - - this.hookImDrawListAddPolyline.Original(drawListPtr, ref points, pointsCount, color, flags, thickness); - - if (pushTextureId) - drawListPtr.PopTextureID(); - } -} diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 65cce15e2..bf559a26e 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -20,18 +20,23 @@ using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.ImGuiBackend; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; +using Dalamud.Interface.Internal.Asserts; using Dalamud.Interface.Internal.DesignSystem; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Style; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; +using Dalamud.Interface.Windowing.Persistence; +using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.Timing; +using FFXIVClientStructs.FFXIV.Client.Graphics.Environment; + using ImGuiNET; using JetBrains.Annotations; @@ -59,6 +64,7 @@ namespace Dalamud.Interface.Internal; /// This class manages interaction with the ImGui interface. ///
[ServiceManager.EarlyLoadedService] +[InherentDependency] // Used by window system windows to restore state from the configuration internal partial class InterfaceManager : IInternalDisposableService { /// @@ -94,6 +100,7 @@ internal partial class InterfaceManager : IInternalDisposableService private readonly ConcurrentQueue runAfterImGuiRender = new(); private IWin32Backend? backend; + private readonly AssertHandler assertHandler = new(); private Hook? setCursorHook; private Hook? reShadeDxgiSwapChainPresentHook; @@ -113,6 +120,7 @@ internal partial class InterfaceManager : IInternalDisposableService [ServiceManager.ServiceConstructor] private InterfaceManager() { + this.framework.Update += this.FrameworkOnUpdate; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] @@ -260,11 +268,27 @@ internal partial class InterfaceManager : IInternalDisposableService /// public long CumulativePresentCalls { get; private set; } + /// + public bool ShowAsserts + { + get => this.assertHandler.ShowAsserts; + set => this.assertHandler.ShowAsserts = value; + } + + /// + public bool EnableVerboseAssertLogging + { + get => this.assertHandler.EnableVerboseLogging; + set => this.assertHandler.EnableVerboseLogging = value; + } + /// /// Dispose of managed and unmanaged resources. /// void IInternalDisposableService.DisposeService() { + this.assertHandler.Dispose(); + // Unload hooks from the framework thread if possible. // We're currently off the framework thread, as this function can only be called from // ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread. @@ -479,13 +503,26 @@ internal partial class InterfaceManager : IInternalDisposableService { var im = Service.GetNullable(); if (im?.dalamudAtlas is not { } atlas) - throw new InvalidOperationException($"Tried to access fonts before {nameof(ContinueConstruction)} call."); + throw new InvalidOperationException($"Tried to access fonts before {nameof(SetupHooks)} call."); if (!atlas.HasBuiltAtlas) atlas.BuildTask.GetAwaiter().GetResult(); return im; } + private unsafe void FrameworkOnUpdate(IFramework framework1) + { + // We now delay hooking until Framework is set up and has fired its first update. + // Some graphics drivers seem to consider the game's shader cache as invalid if we hook too early. + // The game loads shader packages on the file thread and then compiles them. It will show the logo once it is done. + // This is a workaround, but it fixes an issue where the game would take a very long time to get to the title screen. + if (EnvManager.Instance() == null) + return; + + this.SetupHooks(Service.Get(), Service.Get()); + this.framework.Update -= this.FrameworkOnUpdate; + } + /// Checks if the provided swap chain is the target that Dalamud should draw its interface onto, /// and initializes ImGui for drawing. /// The swap chain to test and initialize ImGui with if conditions are met. @@ -559,6 +596,7 @@ internal partial class InterfaceManager : IInternalDisposableService try { newBackend = new Dx11Win32Backend(swapChain); + this.assertHandler.Setup(); } catch (DllNotFoundException ex) { @@ -612,6 +650,10 @@ internal partial class InterfaceManager : IInternalDisposableService iniFileInfo.Delete(); } } + catch (FileNotFoundException) + { + Log.Warning("dalamudUI.ini did not exist, ImGUI will create a new one."); + } catch (Exception ex) { Log.Error(ex, "Could not delete dalamudUI.ini"); @@ -725,9 +767,7 @@ internal partial class InterfaceManager : IInternalDisposableService } } - [ServiceManager.CallWhenServicesReady( - "InterfaceManager accepts event registration and stuff even when the game window is not ready.")] - private unsafe void ContinueConstruction( + private unsafe void SetupHooks( TargetSigScanner sigScanner, FontAtlasFactory fontAtlasFactory) { @@ -803,7 +843,7 @@ internal partial class InterfaceManager : IInternalDisposableService SwapChainHelper.BusyWaitForGameDeviceSwapChain(); var swapChainDesc = default(DXGI_SWAP_CHAIN_DESC); if (SwapChainHelper.GameDeviceSwapChain->GetDesc(&swapChainDesc).SUCCEEDED) - this.gameWindowHandle = swapChainDesc.OutputWindow; + this.gameWindowHandle = swapChainDesc.OutputWindow; try { @@ -823,7 +863,8 @@ internal partial class InterfaceManager : IInternalDisposableService 0, this.SetCursorDetour); - if (ReShadeAddonInterface.ReShadeIsSignedByReShade) + if (ReShadeAddonInterface.ReShadeIsSignedByReShade && + this.dalamudConfiguration.ReShadeHandlingMode is ReShadeHandlingMode.ReShadeAddonPresent or ReShadeHandlingMode.ReShadeAddonReShadeOverlay) { Log.Warning("Signed ReShade binary detected"); Service @@ -945,7 +986,7 @@ internal partial class InterfaceManager : IInternalDisposableService switch (this.dalamudConfiguration.SwapChainHookMode) { - case SwapChainHelper.HookMode.ByteCode: + case SwapChainHelper.HookMode.ByteCode: default: { Log.Information("Hooking using bytecode..."); @@ -1126,15 +1167,22 @@ internal partial class InterfaceManager : IInternalDisposableService WindowSystem.HasAnyWindowSystemFocus = false; WindowSystem.FocusedWindowSystemNamespace = string.Empty; - var snap = ImGuiManagedAsserts.GetSnapshot(); - if (this.IsDispatchingEvents) { - this.Draw?.Invoke(); + try + { + this.Draw?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, "Error when invoking global Draw"); + + // We should always handle this in the callbacks. + Util.Fatal("An internal error occurred while drawing the Dalamud UI and the game must close.\nPlease report this error.", "Dalamud"); + } + Service.GetNullable()?.Draw(); } - - ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); } /// diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs deleted file mode 100644 index 89e23ab78..000000000 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Offsets to various data in ImGui context. -/// -/// -/// Last updated for ImGui 1.83. -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] -internal static class ImGuiContextOffsets -{ - public const int CurrentWindowStackOffset = 0x73A; - - public const int ColorStackOffset = 0x79C; - - public const int StyleVarStackOffset = 0x7A0; - - public const int FontStackOffset = 0x7A4; - - public const int BeginPopupStackOffset = 0x7B8; - - public const int TextStateOffset = 0x4588; -} diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs deleted file mode 100644 index ff42e93f3..000000000 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System.Diagnostics; - -using ImGuiNET; - -using static Dalamud.NativeFunctions; - -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Report ImGui problems with a MessageBox dialog. -/// -internal static class ImGuiManagedAsserts -{ - /// - /// Gets or sets a value indicating whether asserts are enabled for ImGui. - /// - public static bool AssertsEnabled { get; set; } - - /// - /// Create a snapshot of the current ImGui context. - /// Should be called before rendering an ImGui frame. - /// - /// A snapshot of the current context. - public static unsafe ImGuiContextSnapshot GetSnapshot() - { - var contextPtr = ImGui.GetCurrentContext(); - - var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size - var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size - var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size - var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size - var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size - - return new ImGuiContextSnapshot - { - StyleVarStackSize = styleVarStack, - ColorStackSize = colorStack, - FontStackSize = fontStack, - BeginPopupStackSize = popupStack, - WindowStackSize = windowStack, - }; - } - - /// - /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. - /// - /// The source of any problems, something to blame. - /// ImGui context snapshot. - public static void ReportProblems(string source, ImGuiContextSnapshot before) - { - // TODO: Needs to be updated for ImGui 1.88 - return; - -#pragma warning disable CS0162 - if (!AssertsEnabled) - { - return; - } - - var cSnap = GetSnapshot(); - - if (before.StyleVarStackSize != cSnap.StyleVarStackSize) - { - ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); - return; - } - - if (before.ColorStackSize != cSnap.ColorStackSize) - { - ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); - return; - } - - if (before.FontStackSize != cSnap.FontStackSize) - { - ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); - return; - } - - if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) - { - ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); - return; - } - - if (cSnap.WindowStackSize != 1) - { - if (cSnap.WindowStackSize > 1) - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - else - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - } -#pragma warning restore CS0162 - } - - private static void ShowAssert(string source, string message) - { - var caption = $"You fucked up"; - message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; - var flags = MessageBoxType.Ok | MessageBoxType.IconError; - - _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - AssertsEnabled = false; - } - - /// - /// A snapshot of various ImGui context properties. - /// - public class ImGuiContextSnapshot - { - /// - /// Gets the ImGui style var stack size. - /// - public int StyleVarStackSize { get; init; } - - /// - /// Gets the ImGui color stack size. - /// - public int ColorStackSize { get; init; } - - /// - /// Gets the ImGui font stack size. - /// - public int FontStackSize { get; init; } - - /// - /// Gets the ImGui begin popup stack size. - /// - public int BeginPopupStackSize { get; init; } - - /// - /// Gets the ImGui window stack size. - /// - public int WindowStackSize { get; init; } - } -} diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 97eec1ee1..9dfff75ec 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -1,13 +1,19 @@ using System.Numerics; -using System.Runtime.CompilerServices; +using Dalamud.Game; using Dalamud.Game.Gui; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; -using Dalamud.Memory; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; +using Lumina.Text.ReadOnly; + // Customised version of https://github.com/aers/FFXIVUIDebug namespace Dalamud.Interface.Internal; @@ -203,10 +209,24 @@ internal unsafe class UiDebug { case NodeType.Text: var textNode = (AtkTextNode*)node; - ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)textNode->NodeText.StringPtr)}"); + ImGui.Text("text: "); + ImGui.SameLine(); + Service.Get().Draw(textNode->NodeText); ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); + ImGui.SameLine(); + if (ImGui.Button($"Encode##{(ulong)textNode:X}")) + { + using var tmp = new Utf8String(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, textNode->NodeText.StringPtr); + textNode->NodeText.Copy(&tmp); + } + + ImGui.SameLine(); + if (ImGui.Button($"Decode##{(ulong)textNode:X}")) + textNode->NodeText.SetString(textNode->NodeText.StringPtr.AsReadOnlySeStringSpan().ToString()); + ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); int b = textNode->AlignmentFontType; if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) @@ -230,7 +250,9 @@ internal unsafe class UiDebug break; case NodeType.Counter: var counterNode = (AtkCounterNode*)node; - ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)counterNode->NodeText.StringPtr)}"); + ImGui.Text("text: "); + ImGui.SameLine(); + Service.Get().Draw(counterNode->NodeText); break; case NodeType.Image: var imageNode = (AtkImageNode*)node; @@ -272,20 +294,15 @@ internal unsafe class UiDebug $"texture type: {texType} part_id={partId} part_id_count={partsList->PartCount}"); if (texType == TextureType.Resource) { - var texFileNameStdString = - &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; - var texString = texFileNameStdString->Length < 16 - ? MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->Buffer) - : MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->BufferPtr); - - ImGui.Text($"texture path: {texString}"); + ImGui.Text( + $"texture path: {textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName}"); var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) { ImGui.Image( new IntPtr(kernelTexture->D3D11ShaderResourceView), - new Vector2(kernelTexture->Width, kernelTexture->Height)); + new Vector2(kernelTexture->ActualWidth, kernelTexture->ActualHeight)); ImGui.TreePop(); } } @@ -297,11 +314,37 @@ internal unsafe class UiDebug ImGui.Image( new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2( - textureInfo->AtkTexture.KernelTexture->Width, - textureInfo->AtkTexture.KernelTexture->Height)); + textureInfo->AtkTexture.KernelTexture->ActualWidth, + textureInfo->AtkTexture.KernelTexture->ActualHeight)); ImGui.TreePop(); } } + + if (ImGui.Button($"Replace with a random image##{(ulong)textureInfo:X}")) + { + var texm = Service.Get(); + texm.Shared + .GetFromGame( + Random.Shared.Next(0, 1) == 0 + ? $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}.tex" + : $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}_hr1.tex") + .RentAsync() + .ContinueWith( + r => Service.Get().RunOnFrameworkThread( + () => + { + if (!r.IsCompletedSuccessfully) + return; + + using (r.Result) + { + textureInfo->AtkTexture.ReleaseTexture(); + textureInfo->AtkTexture.KernelTexture = + texm.ConvertToKernelTexture(r.Result); + textureInfo->AtkTexture.TextureType = TextureType.KernelTexture; + } + })); + } } } else @@ -372,13 +415,33 @@ internal unsafe class UiDebug { case ComponentType.TextInput: var textInputComponent = (AtkComponentTextInput*)compNode->Component; - ImGui.Text($"InputBase Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text($"InputBase Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text($"Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText01.StringPtr))}"); - ImGui.Text($"Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText02.StringPtr))}"); - ImGui.Text($"Text3: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText03.StringPtr))}"); - ImGui.Text($"Text4: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText04.StringPtr))}"); - ImGui.Text($"Text5: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText05.StringPtr))}"); + ImGui.Text("InputBase Text1: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText1); + + ImGui.Text("InputBase Text2: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText2); + + ImGui.Text("Text1: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->UnkText01); + + ImGui.Text("Text2: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->UnkText02); + + ImGui.Text("Text3: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->UnkText03); + + ImGui.Text("Text4: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->UnkText04); + + ImGui.Text("Text5: "); + ImGui.SameLine(); + Service.Get().Draw(textInputComponent->UnkText05); break; } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs new file mode 100644 index 000000000..c3f6133dd --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -0,0 +1,123 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Memory; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +public unsafe partial class AddonTree +{ + /// + /// Prints a table of AtkValues associated with a given addon. + /// + /// The addon to look up. + internal static void PrintAtkValues(AtkUnitBase* addon) + { + var atkValue = addon->AtkValues; + if (addon->AtkValuesCount > 0 && atkValue != null) + { + using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}"); + if (tree.Success) + { + using var tbl = ImRaii.Table("atkUnitBase_atkValueTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + + if (tbl.Success) + { + ImGui.TableSetupColumn("Index"); + ImGui.TableSetupColumn("Type"); + ImGui.TableSetupColumn("Value"); + ImGui.TableHeadersRow(); + + try + { + for (var i = 0; i < addon->AtkValuesCount; i++) + { + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled($"#{i}"); + } + else + { + ImGui.Text($"#{i}"); + } + + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled("Not Set"); + } + else + { + ImGui.Text($"{atkValue->Type}"); + } + + ImGui.TableNextColumn(); + + switch (atkValue->Type) + { + case 0: + break; + case ValueType.Int: + case ValueType.UInt: + { + ImGui.TextUnformatted($"{atkValue->Int}"); + break; + } + + case ValueType.ManagedString: + case ValueType.String8: + case ValueType.String: + { + if (atkValue->String.Value == null) + { + ImGui.TextDisabled("null"); + } + else + { + Util.ShowStruct(atkValue->String.ToString(), (ulong)atkValue); + } + + break; + } + + case ValueType.Bool: + { + ImGui.TextUnformatted($"{atkValue->Byte != 0}"); + break; + } + + case ValueType.Pointer: + ImGui.TextUnformatted($"{(nint)atkValue->Pointer}"); + break; + + default: + { + ImGui.TextDisabled("Unhandled Type"); + ImGui.SameLine(); + Util.ShowStruct(atkValue); + break; + } + } + + atkValue++; + } + } + catch (Exception ex) + { + ImGui.TextColored(new(1, 0, 0, 1), $"{ex}"); + } + } + } + + Gui.PaddedSeparator(); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs new file mode 100644 index 000000000..0b1dcb66c --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +using FFXIVClientStructs.Attributes; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static System.Reflection.BindingFlags; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +public unsafe partial class AddonTree +{ + private static readonly Dictionary AddonTypeDict = []; + + private static readonly Assembly? ClientStructsAssembly = typeof(AddonAttribute).Assembly; + + /// + /// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs. + /// + internal Dictionary> FieldNames { get; set; } = []; + + /// + /// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs. + /// + internal int AddonSize { get; set; } + + private object? GetAddonObj(AtkUnitBase* addon) + { + if (addon == null) + { + return null; + } + + if (AddonTypeDict.TryAdd(this.AddonName, null) && ClientStructsAssembly != null) + { + try + { + foreach (var t in from t in ClientStructsAssembly.GetTypes() + where t.IsPublic + let xivAddonAttr = (AddonAttribute?)t.GetCustomAttribute(typeof(AddonAttribute), false) + where xivAddonAttr != null + where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName) + select t) + { + AddonTypeDict[this.AddonName] = t; + + var size = t.StructLayoutAttribute?.Size; + if (size != null) + { + this.AddonSize = size.Value; + } + + break; + } + } + catch + { + // ignored + } + } + + return AddonTypeDict.TryGetValue(this.AddonName, out var result) && result != null ? Marshal.PtrToStructure(new(addon), result) : *addon; + } + + private void PopulateFieldNames(nint ptr) + { + this.PopulateFieldNames(this.GetAddonObj((AtkUnitBase*)ptr), ptr); + } + + private void PopulateFieldNames(object? obj, nint baseAddr, List? path = null) + { + if (obj == null) + { + return; + } + + path ??= []; + var baseType = obj.GetType(); + + foreach (var field in baseType.GetFields(Static | Public | NonPublic | Instance)) + { + if (field.GetCustomAttribute(typeof(FieldOffsetAttribute)) is FieldOffsetAttribute offset) + { + try + { + var fieldAddr = baseAddr + offset.Value; + var name = field.Name[0] == '_' ? char.ToUpperInvariant(field.Name[1]) + field.Name[2..] : field.Name; + var fieldType = field.FieldType; + + if (!field.IsStatic && fieldType.IsPointer) + { + var pointer = (nint)Pointer.Unbox((Pointer)field.GetValue(obj)!); + var itemType = fieldType.GetElementType(); + ParsePointer(fieldAddr, pointer, itemType, name); + } + else if (fieldType.IsExplicitLayout) + { + ParseExplicitField(fieldAddr, field, fieldType, name); + } + else if (fieldType.Name.Contains("FixedSizeArray")) + { + ParseFixedSizeArray(fieldAddr, fieldType, name); + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse field at {offset.Value:X} in {baseType}!\n{ex}"); + } + } + } + + return; + + void ParseExplicitField(nint fieldAddr, FieldInfo field, MemberInfo fieldType, string name) + { + try + { + if (this.FieldNames.TryAdd(fieldAddr, [..path, name]) && fieldType.DeclaringType == baseType) + { + this.PopulateFieldNames(field.GetValue(obj), fieldAddr, [..path, name]); + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse explicit field: {fieldType} {name} in {baseType}!\n{ex}"); + } + } + + void ParseFixedSizeArray(nint fieldAddr, Type fieldType, string name) + { + try + { + var spanLength = (int)(fieldType.CustomAttributes.ToArray()[0].ConstructorArguments[0].Value ?? 0); + + if (spanLength <= 0) + { + return; + } + + var itemType = fieldType.UnderlyingSystemType.GenericTypeArguments[0]; + + if (!itemType.IsGenericType) + { + var size = Marshal.SizeOf(itemType); + for (var i = 0; i < spanLength; i++) + { + var itemAddr = fieldAddr + (size * i); + var itemName = $"{name}[{i}]"; + + this.FieldNames.TryAdd(itemAddr, [..path, itemName]); + + var item = Marshal.PtrToStructure(itemAddr, itemType); + if (itemType.DeclaringType == baseType) + { + this.PopulateFieldNames(item, itemAddr, [..path, itemName]); + } + } + } + else if (itemType.Name.Contains("Pointer")) + { + itemType = itemType.GenericTypeArguments[0]; + + for (var i = 0; i < spanLength; i++) + { + var itemAddr = fieldAddr + (0x08 * i); + var pointer = Marshal.ReadIntPtr(itemAddr); + ParsePointer(itemAddr, pointer, itemType, $"{name}[{i}]"); + } + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse fixed size array: {fieldType} {name} in {baseType}!\n{ex}"); + } + } + + void ParsePointer(nint fieldAddr, nint pointer, Type? itemType, string name) + { + try + { + if (pointer == 0) + { + return; + } + + this.FieldNames.TryAdd(fieldAddr, [..path, name]); + this.FieldNames.TryAdd(pointer, [..path, name]); + + if (itemType?.DeclaringType != baseType || itemType.IsPointer) + { + return; + } + + var item = Marshal.PtrToStructure(pointer, itemType); + this.PopulateFieldNames(item, pointer, [..path, name]); + } + catch (Exception ex) + { + Log.Warning($"Failed to parse pointer: {itemType}* {name} in {baseType}!\n{ex}"); + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs new file mode 100644 index 000000000..9d6575a55 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -0,0 +1,259 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Components; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.ElementSelector; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A class representing an , allowing it to be browsed within an ImGui window. +/// +public unsafe partial class AddonTree : IDisposable +{ + private AddonPopoutWindow? window; + + private AddonTree(string name, nint ptr) + { + this.AddonName = name; + this.InitialPtr = ptr; + this.PopulateFieldNames(ptr); + } + + /// + /// Gets the name of the addon this tree represents. + /// + internal string AddonName { get; init; } + + /// + /// Gets the addon's pointer at the time this was created. + /// + internal nint InitialPtr { get; init; } + + /// + /// Gets or sets a collection of trees representing nodes within this addon. + /// + internal Dictionary NodeTrees { get; set; } = []; + + /// + public void Dispose() + { + foreach (var nodeTree in this.NodeTrees) + { + nodeTree.Value.Dispose(); + } + + this.NodeTrees.Clear(); + this.FieldNames.Clear(); + AddonTrees.Remove(this.AddonName); + if (this.window != null && PopoutWindows.Windows.Contains(this.window)) + { + PopoutWindows.RemoveWindow(this.window); + this.window?.Dispose(); + } + + GC.SuppressFinalize(this); + } + + /// + /// Gets an instance of for the given addon name (or creates one if none are found). + /// The tree can then be drawn within the Addon Inspector and browsed. + /// + /// The name of the addon. + /// The for the named addon. Returns null if it does not exist, or if it is not at the expected address. + internal static AddonTree? GetOrCreate(string? name) + { + if (name == null) + { + return null; + } + + try + { + var ptr = GameGui.GetAddonByName(name); + + if ((AtkUnitBase*)ptr != null) + { + if (AddonTrees.TryGetValue(name, out var tree)) + { + if (tree.InitialPtr == ptr) + { + return tree; + } + + tree.Dispose(); + } + + var newTree = new AddonTree(name, ptr); + AddonTrees.Add(name, newTree); + return newTree; + } + } + catch + { + Log.Warning("Couldn't get addon!"); + } + + return null; + } + + /// + /// Draws this AddonTree within a window. + /// + internal void Draw() + { + if (!this.ValidateAddon(out var addon)) + { + return; + } + + var isVisible = addon->IsVisible; + + ImGui.TextUnformatted($"{this.AddonName}"); + ImGui.SameLine(); + + ImGui.SameLine(); + ImGui.TextColored(isVisible ? new(0.1f, 1f, 0.1f, 1f) : new(0.6f, 0.6f, 0.6f, 1), isVisible ? "Visible" : "Not Visible"); + + ImGui.SameLine(ImGui.GetWindowWidth() - 100); + + if (ImGuiComponents.IconButton($"##vis{(nint)addon:X}", isVisible ? Eye : EyeSlash, isVisible ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1))) + { + addon->IsVisible = !isVisible; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Visibility"); + } + + ImGui.SameLine(); + if (ImGuiComponents.IconButton("pop", this.window?.IsOpen == true ? Times : ArrowUpRightFromSquare, null)) + { + this.TogglePopout(); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Popout Window"); + } + + PaddedSeparator(1); + + var uldManager = addon->UldManager; + + PrintFieldValuePair("Address", $"{(nint)addon:X}"); + PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}"); + + PrintFieldValuePairs( + ("X", $"{addon->X}"), + ("Y", $"{addon->Y}"), + ("Scale", $"{addon->Scale}"), + ("Widget Count", $"{uldManager.ObjectCount}")); + + var addonObj = this.GetAddonObj(addon); + if (addonObj != null) + { + PaddedSeparator(); + ShowStruct(addonObj, (ulong)addon); + } + + PaddedSeparator(); + + PrintAtkValues(addon); + + if (addon->RootNode != null) + { + ResNodeTree.GetOrCreate(addon->RootNode, this).Print(0); + PaddedSeparator(); + } + + if (uldManager.NodeList != null) + { + var count = uldManager.NodeListCount; + ResNodeTree.PrintNodeListAsTree(uldManager.NodeList, count, $"Node List [{count}]:", this, new(0, 0.85F, 1, 1)); + PaddedSeparator(); + } + + if (addon->CollisionNodeList != null) + { + ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + } + + if (SearchResults.Length > 0 && Countdown <= 0) + { + SearchResults = []; + } + } + + /// + /// Checks whether a given exists somewhere within this 's associated (or any of its s). + /// + /// The node to check. + /// true if the node was found. + internal bool ContainsNode(AtkResNode* node) => this.ValidateAddon(out var addon) && FindNode(node, addon); + + private static bool FindNode(AtkResNode* node, AtkUnitBase* addon) => addon != null && FindNode(node, addon->UldManager); + + private static bool FindNode(AtkResNode* node, AtkComponentNode* compNode) => compNode != null && FindNode(node, compNode->Component->UldManager); + + private static bool FindNode(AtkResNode* node, AtkUldManager uldManager) => FindNode(node, uldManager.NodeList, uldManager.NodeListCount); + + private static bool FindNode(AtkResNode* node, AtkResNode** list, int count) + { + for (var i = 0; i < count; i++) + { + var listNode = list[i]; + if (listNode == node) + { + return true; + } + + if ((int)listNode->Type >= 1000 && FindNode(node, (AtkComponentNode*)listNode)) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether the addon exists at the expected address. If the addon is null or has a new address, disposes this instance of . + /// + /// The addon, if successfully found. + /// true if the addon is found. + private bool ValidateAddon(out AtkUnitBase* addon) + { + addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); + if (addon == null || (nint)addon != this.InitialPtr) + { + this.Dispose(); + return false; + } + + return true; + } + + private void TogglePopout() + { + if (this.window == null) + { + this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.InitialPtr}"); + PopoutWindows.AddWindow(this.window); + } + else + { + this.window.IsOpen = !this.window.IsOpen; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs new file mode 100644 index 000000000..c98cc933f --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -0,0 +1,71 @@ +using System.Numerics; + +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// Class that prints the events table for a node, where applicable. +/// +public static class Events +{ + /// + /// Prints out each for a given node. + /// + /// The node to print events for. + internal static unsafe void PrintEvents(AtkResNode* node) + { + var evt = node->AtkEventManager.Event; + if (evt == null) + { + return; + } + + using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); + + if (tree.Success) + { + using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg); + + if (tbl.Success) + { + ImGui.TableSetupColumn("#", WidthFixed); + ImGui.TableSetupColumn("Type", WidthFixed); + ImGui.TableSetupColumn("Param", WidthFixed); + ImGui.TableSetupColumn("Flags", WidthFixed); + ImGui.TableSetupColumn("StateFlags1", WidthFixed); + ImGui.TableSetupColumn("Target", WidthFixed); + ImGui.TableSetupColumn("Listener", WidthFixed); + + ImGui.TableHeadersRow(); + + var i = 0; + while (evt != null) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{i++}"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{evt->State.EventType}"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{evt->Param}"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{evt->State.StateFlags}"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{evt->State.UnkFlags1}"); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); + evt = evt->NextEvent; + } + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs new file mode 100644 index 000000000..cfba1a2bc --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs @@ -0,0 +1,35 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class ClippingMaskNodeTree : ImageNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ClippingMaskNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override uint PartId => this.CmNode->PartId; + + /// + private protected override AtkUldPartsList* PartsList => this.CmNode->PartsList; + + private AtkClippingMaskNode* CmNode => (AtkClippingMaskNode*)this.Node; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.CmNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) => this.DrawTextureAndParts(); +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs new file mode 100644 index 000000000..c447afac9 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs @@ -0,0 +1,24 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class CollisionNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal CollisionNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override void PrintNodeObject() => ShowStruct((AtkCollisionNode*)this.Node); +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs new file mode 100644 index 000000000..4a1989441 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -0,0 +1,312 @@ +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.ComponentType; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class ComponentNodeTree : ResNodeTree +{ + private readonly ComponentType componentType; + + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ComponentNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + this.NodeType = 0; + this.componentType = ((AtkUldComponentInfo*)this.UldManager->Objects)->ComponentType; + } + + private AtkComponentBase* Component => this.CompNode->Component; + + private AtkComponentNode* CompNode => (AtkComponentNode*)this.Node; + + private AtkUldManager* UldManager => &this.Component->UldManager; + + private int? ComponentFieldOffset { get; set; } + + /// + private protected override string GetHeaderText() + { + var childCount = (int)this.UldManager->NodeListCount; + return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)}"; + } + + /// + private protected override void PrintNodeObject() + { + base.PrintNodeObject(); + this.PrintComponentObject(); + ImGui.SameLine(); + ImGui.NewLine(); + this.PrintComponentDataObject(); + ImGui.SameLine(); + ImGui.NewLine(); + } + + /// + private protected override void PrintChildNodes() + { + base.PrintChildNodes(); + var count = this.UldManager->NodeListCount; + PrintNodeListAsTree(this.UldManager->NodeList, count, $"Node List [{count}]:", this.AddonTree, new(0f, 0.5f, 0.8f, 1f)); + } + + /// + private protected override void PrintFieldLabels() + { + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); + this.PrintFieldLabel((nint)this.Component, new(0f, 0.5f, 0.8f, 1f), this.ComponentFieldOffset); + } + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (this.Component == null) + { + return; + } + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (this.componentType) + { + case TextInput: + var textInputComponent = (AtkComponentTextInput*)this.Component; + ImGui.TextUnformatted( + $"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); + ImGui.TextUnformatted( + $"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); + ImGui.TextUnformatted( + $"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}"); + ImGui.TextUnformatted( + $"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}"); + ImGui.TextUnformatted( + $"Text3: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText03.StringPtr))}"); + ImGui.TextUnformatted( + $"Text4: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText04.StringPtr))}"); + ImGui.TextUnformatted( + $"Text5: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText05.StringPtr))}"); + break; + case List: + case TreeList: + var l = (AtkComponentList*)this.Component; + if (ImGui.SmallButton("Inc.Selected")) + { + l->SelectedItemIndex++; + } + + break; + } + } + + /// + private protected override void GetFieldOffset() + { + var nodeFound = false; + var componentFound = false; + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + var readPtr = Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i); + + if (readPtr == (nint)this.Node) + { + this.NodeFieldOffset = i; + nodeFound = true; + } + + if (readPtr == (nint)this.Component) + { + this.ComponentFieldOffset = i; + componentFound = true; + } + + if (nodeFound && componentFound) + { + break; + } + } + } + + private void PrintComponentObject() + { + PrintFieldValuePair("Component", $"{(nint)this.Component:X}"); + + ImGui.SameLine(); + + switch (this.componentType) + { + case Button: + ShowStruct((AtkComponentButton*)this.Component); + break; + case Slider: + ShowStruct((AtkComponentSlider*)this.Component); + break; + case Window: + ShowStruct((AtkComponentWindow*)this.Component); + break; + case CheckBox: + ShowStruct((AtkComponentCheckBox*)this.Component); + break; + case GaugeBar: + ShowStruct((AtkComponentGaugeBar*)this.Component); + break; + case RadioButton: + ShowStruct((AtkComponentRadioButton*)this.Component); + break; + case TextInput: + ShowStruct((AtkComponentTextInput*)this.Component); + break; + case Icon: + ShowStruct((AtkComponentIcon*)this.Component); + break; + case NumericInput: + ShowStruct((AtkComponentNumericInput*)this.Component); + break; + case List: + ShowStruct((AtkComponentList*)this.Component); + break; + case TreeList: + ShowStruct((AtkComponentTreeList*)this.Component); + break; + case DropDownList: + ShowStruct((AtkComponentDropDownList*)this.Component); + break; + case ScrollBar: + ShowStruct((AtkComponentScrollBar*)this.Component); + break; + case ListItemRenderer: + ShowStruct((AtkComponentListItemRenderer*)this.Component); + break; + case IconText: + ShowStruct((AtkComponentIconText*)this.Component); + break; + case ComponentType.DragDrop: + ShowStruct((AtkComponentDragDrop*)this.Component); + break; + case GuildLeveCard: + ShowStruct((AtkComponentGuildLeveCard*)this.Component); + break; + case TextNineGrid: + ShowStruct((AtkComponentTextNineGrid*)this.Component); + break; + case JournalCanvas: + ShowStruct((AtkComponentJournalCanvas*)this.Component); + break; + case HoldButton: + ShowStruct((AtkComponentHoldButton*)this.Component); + break; + case Portrait: + ShowStruct((AtkComponentPortrait*)this.Component); + break; + default: + ShowStruct(this.Component); + break; + } + } + + private void PrintComponentDataObject() + { + var componentData = this.Component->UldManager.ComponentData; + PrintFieldValuePair("Data", $"{(nint)componentData:X}"); + + if (componentData != null) + { + ImGui.SameLine(); + switch (this.componentType) + { + case Base: + ShowStruct(componentData); + break; + case Button: + ShowStruct((AtkUldComponentDataButton*)componentData); + break; + case Window: + ShowStruct((AtkUldComponentDataWindow*)componentData); + break; + case CheckBox: + ShowStruct((AtkUldComponentDataCheckBox*)componentData); + break; + case RadioButton: + ShowStruct((AtkUldComponentDataRadioButton*)componentData); + break; + case GaugeBar: + ShowStruct((AtkUldComponentDataGaugeBar*)componentData); + break; + case Slider: + ShowStruct((AtkUldComponentDataSlider*)componentData); + break; + case TextInput: + ShowStruct((AtkUldComponentDataTextInput*)componentData); + break; + case NumericInput: + ShowStruct((AtkUldComponentDataNumericInput*)componentData); + break; + case List: + ShowStruct((AtkUldComponentDataList*)componentData); + break; + case DropDownList: + ShowStruct((AtkUldComponentDataDropDownList*)componentData); + break; + case Tab: + ShowStruct((AtkUldComponentDataTab*)componentData); + break; + case TreeList: + ShowStruct((AtkUldComponentDataTreeList*)componentData); + break; + case ScrollBar: + ShowStruct((AtkUldComponentDataScrollBar*)componentData); + break; + case ListItemRenderer: + ShowStruct((AtkUldComponentDataListItemRenderer*)componentData); + break; + case Icon: + ShowStruct((AtkUldComponentDataIcon*)componentData); + break; + case IconText: + ShowStruct((AtkUldComponentDataIconText*)componentData); + break; + case ComponentType.DragDrop: + ShowStruct((AtkUldComponentDataDragDrop*)componentData); + break; + case GuildLeveCard: + ShowStruct((AtkUldComponentDataGuildLeveCard*)componentData); + break; + case TextNineGrid: + ShowStruct((AtkUldComponentDataTextNineGrid*)componentData); + break; + case JournalCanvas: + ShowStruct((AtkUldComponentDataJournalCanvas*)componentData); + break; + case Multipurpose: + ShowStruct((AtkUldComponentDataMultipurpose*)componentData); + break; + case Map: + ShowStruct((AtkUldComponentDataMap*)componentData); + break; + case Preview: + ShowStruct((AtkUldComponentDataPreview*)componentData); + break; + case HoldButton: + ShowStruct((AtkUldComponentDataHoldButton*)componentData); + break; + case Portrait: + ShowStruct((AtkUldComponentDataPortrait*)componentData); + break; + default: + ShowStruct(componentData); + break; + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs new file mode 100644 index 000000000..ff40db37a --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs @@ -0,0 +1,36 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class CounterNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal CounterNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + private AtkCounterNode* CntNode => (AtkCounterNode*)this.Node; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.CntNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (!isEditorOpen) + { + PrintFieldValuePairs(("Text", ((AtkCounterNode*)this.Node)->NodeText.ToString())); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs new file mode 100644 index 000000000..1f5abd0bf --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -0,0 +1,386 @@ +using System.Collections.Generic; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Interface.Utility.ImGuiHelpers; +using static ImGuiNET.ImGuiColorEditFlags; +using static ImGuiNET.ImGuiInputTextFlags; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +internal unsafe partial class ResNodeTree +{ + /// + /// Sets up the table for the node editor, if the "Edit" checkbox is ticked. + /// + private protected void DrawNodeEditorTable() + { + using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); + if (tbl.Success) + { + this.DrawEditorRows(); + } + } + + /// + /// Draws each row in the node editor table. + /// + private protected virtual void DrawEditorRows() + { + var pos = new Vector2(this.Node->X, this.Node->Y); + var size = new Vector2(this.Node->Width, this.Node->Height); + var scale = new Vector2(this.Node->ScaleX, this.Node->ScaleY); + var origin = new Vector2(this.Node->OriginX, this.Node->OriginY); + var angle = (float)((this.Node->Rotation * (180 / Math.PI)) + 360); + + var rgba = RgbaUintToVector4(this.Node->Color.RGBA); + var mult = new Vector3(this.Node->MultiplyRed, this.Node->MultiplyGreen, this.Node->MultiplyBlue) / 255f; + var add = new Vector3(this.Node->AddRed, this.Node->AddGreen, this.Node->AddBlue); + + var hov = false; + + ImGui.TableSetupColumn("Labels", WidthFixed); + ImGui.TableSetupColumn("Editors", WidthFixed); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Position:"); + + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}position", ref pos, 1, default, default, "%.0f")) + { + this.Node->X = pos.X; + this.Node->Y = pos.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("X", "Y") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Size:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}size", ref size, 1, 0, default, "%.0f")) + { + this.Node->Width = (ushort)Math.Max(size.X, 0); + this.Node->Height = (ushort)Math.Max(size.Y, 0); + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("Width", "Height") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Scale:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}scale", ref scale, 0.05f)) + { + this.Node->ScaleX = scale.X; + this.Node->ScaleY = scale.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("ScaleX", "ScaleY") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Origin:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}origin", ref origin, 1, default, default, "%.0f")) + { + this.Node->OriginX = origin.X; + this.Node->OriginY = origin.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("OriginX", "OriginY") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Rotation:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + while (angle > 180) + { + angle -= 360; + } + + if (ImGui.DragFloat($"##{(nint)this.Node:X}rotation", ref angle, 0.05f, default, default, "%.2f°")) + { + this.Node->Rotation = (float)(angle / (180 / Math.PI)); + this.Node->DrawFlags |= 0xD; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Rotation (deg)"); + hov = true; + } + + hov |= ImGui.IsItemActive(); + + if (hov) + { + Vector4 brightYellow = new(1, 1, 0.5f, 0.8f); + new NodeBounds(this.Node).Draw(brightYellow); + new NodeBounds(origin, this.Node).Draw(brightYellow); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("RGBA:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}RGBA", ref rgba, DisplayHex)) + { + this.Node->Color = new() { RGBA = RgbaVector4ToUint(rgba) }; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Multiply:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit3($"##{(nint)this.Node:X}multiplyRGB", ref mult, DisplayHex)) + { + this.Node->MultiplyRed = (byte)(mult.X * 255); + this.Node->MultiplyGreen = (byte)(mult.Y * 255); + this.Node->MultiplyBlue = (byte)(mult.Z * 255); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Add:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(124); + + if (ImGui.DragFloat3($"##{(nint)this.Node:X}addRGB", ref add, 1, -255, 255, "%.0f")) + { + this.Node->AddRed = (short)add.X; + this.Node->AddGreen = (short)add.Y; + this.Node->AddBlue = (short)add.Z; + } + + SplitTooltip("+/- Red", "+/- Green", "+/- Blue"); + + var addTransformed = (add / 510f) + new Vector3(0.5f); + + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - (4 * GlobalScale)); + if (ImGui.ColorEdit3($"##{(nint)this.Node:X}addRGBPicker", ref addTransformed, NoAlpha | NoInputs)) + { + this.Node->AddRed = (short)Math.Floor((addTransformed.X * 510f) - 255f); + this.Node->AddGreen = (short)Math.Floor((addTransformed.Y * 510f) - 255f); + this.Node->AddBlue = (short)Math.Floor((addTransformed.Z * 510f) - 255f); + } + } +} + +/// +internal unsafe partial class CounterNodeTree +{ + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var str = this.CntNode->NodeText.ToString(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Counter:"); + ImGui.TableNextColumn(); + + ImGui.SetNextItemWidth(150); + if (ImGui.InputText($"##{(nint)this.Node:X}counterEdit", ref str, 512, EnterReturnsTrue)) + { + this.CntNode->SetText(str); + } + } +} + +/// +internal unsafe partial class ImageNodeTree +{ + private static int TexDisplayStyle { get; set; } + + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var partId = (int)this.PartId; + var partcount = this.ImgNode->PartsList->PartCount; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Part Id:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.InputInt($"##partId{(nint)this.Node:X}", ref partId, 1, 1)) + { + if (partId < 0) + { + partId = 0; + } + + if (partId >= partcount) + { + partId = (int)(partcount - 1); + } + + this.ImgNode->PartId = (ushort)partId; + } + } +} + +/// +internal unsafe partial class NineGridNodeTree +{ + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var lr = new Vector2(this.Offsets.Left, this.Offsets.Right); + var tb = new Vector2(this.Offsets.Top, this.Offsets.Bottom); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Ninegrid Offsets:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}ngOffsetLR", ref lr, 1, 0)) + { + this.NgNode->LeftOffset = (short)Math.Max(0, lr.X); + this.NgNode->RightOffset = (short)Math.Max(0, lr.Y); + } + + SplitTooltip("Left", "Right"); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}ngOffsetTB", ref tb, 1, 0)) + { + this.NgNode->TopOffset = (short)Math.Max(0, tb.X); + this.NgNode->BottomOffset = (short)Math.Max(0, tb.Y); + } + + SplitTooltip("Top", "Bottom"); + } +} + +/// +internal unsafe partial class TextNodeTree +{ + private static readonly List FontList = [.. Enum.GetValues()]; + + private static readonly string[] FontNames = Enum.GetNames(); + + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var text = this.TxtNode->NodeText.ToString(); + var fontIndex = FontList.IndexOf(this.TxtNode->FontType); + int fontSize = this.TxtNode->FontSize; + var alignment = this.TxtNode->AlignmentType; + var textColor = RgbaUintToVector4(this.TxtNode->TextColor.RGBA); + var edgeColor = RgbaUintToVector4(this.TxtNode->EdgeColor.RGBA); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Text:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(Math.Max(ImGui.GetWindowContentRegionMax().X - ImGui.GetCursorPosX() - 50f, 150)); + if (ImGui.InputText($"##{(nint)this.Node:X}textEdit", ref text, 512, EnterReturnsTrue)) + { + this.TxtNode->SetText(text); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Font:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.Combo($"##{(nint)this.Node:X}fontType", ref fontIndex, FontNames, FontList.Count)) + { + this.TxtNode->FontType = FontList[fontIndex]; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Font Size:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.InputInt($"##{(nint)this.Node:X}fontSize", ref fontSize, 1, 10)) + { + this.TxtNode->FontSize = (byte)fontSize; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Alignment:"); + ImGui.TableNextColumn(); + if (InputAlignment($"##{(nint)this.Node:X}alignment", ref alignment)) + { + this.TxtNode->AlignmentType = alignment; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Text Color:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}TextRGB", ref textColor, DisplayHex)) + { + this.TxtNode->TextColor = new() { RGBA = RgbaVector4ToUint(textColor) }; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Edge Color:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}EdgeRGB", ref edgeColor, DisplayHex)) + { + this.TxtNode->EdgeColor = new() { RGBA = RgbaVector4ToUint(edgeColor) }; + } + } + + private static bool InputAlignment(string label, ref AlignmentType alignment) + { + var hAlign = (int)alignment % 3; + var vAlign = ((int)alignment - hAlign) / 3; + + var hAlignInput = ImGuiComponents.IconButtonSelect($"{label}H", ref hAlign, [AlignLeft, AlignCenter, AlignRight], [0, 1, 2], 3u, new(25, 0)); + var vAlignInput = ImGuiComponents.IconButtonSelect($"{label}V", ref vAlign, [ArrowsUpToLine, GripLines, ArrowsDownToLine], [0, 1, 2], 3u, new(25, 0)); + + if (hAlignInput || vAlignInput) + { + alignment = (AlignmentType)((vAlign * 3) + hAlign); + return true; + } + + return false; + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs new file mode 100644 index 000000000..8d93b3e76 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -0,0 +1,317 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.TextureType; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; +using static ImGuiNET.ImGuiTreeNodeFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class ImageNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ImageNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + /// Gets the part ID that this node uses. + /// + private protected virtual uint PartId => this.ImgNode->PartId; + + /// + /// Gets the parts list that this node uses. + /// + private protected virtual AtkUldPartsList* PartsList => this.ImgNode->PartsList; + + /// + /// Gets or sets a summary of pertinent data about this 's texture. Updated each time is called. + /// + private protected TextureData TexData { get; set; } + + private AtkImageNode* ImgNode => (AtkImageNode*)this.Node; + + /// + /// Draws the texture inside the window, in either of two styles.

+ /// Full Image (0)presents the texture in full as a spritesheet.
+ /// Parts List (1)presents the individual parts as rows in a table. + ///
+ private protected void DrawTextureAndParts() + { + this.TexData = new TextureData(this.PartsList, this.PartId); + + if (this.TexData.Texture == null) + { + return; + } + + using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); + + if (tree.Success) + { + PrintFieldValuePairs( + ("Texture Type", $"{this.TexData.TexType}"), + ("Part ID", $"{this.TexData.PartId}"), + ("Part Count", $"{this.TexData.PartCount}")); + + if (this.TexData.Path != null) + { + PrintFieldValuePairs(("Texture Path", this.TexData.Path)); + } + + if (ImGui.RadioButton("Full Image##textureDisplayStyle0", TexDisplayStyle == 0)) + { + TexDisplayStyle = 0; + } + + ImGui.SameLine(); + if (ImGui.RadioButton("Parts List##textureDisplayStyle1", TexDisplayStyle == 1)) + { + TexDisplayStyle = 1; + } + + ImGui.NewLine(); + + if (TexDisplayStyle == 1) + { + this.PrintPartsTable(); + } + else + { + this.DrawFullTexture(); + } + } + } + + /// + /// Draws an outline of a given part within the texture. + /// + /// The part ID. + /// The absolute position of the cursor onscreen. + /// The relative position of the cursor within the window. + /// The color of the outline. + /// Whether this outline requires the user to mouse over it. + private protected virtual void DrawPartOutline(uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) + { + var part = this.TexData.PartsList->Parts[partId]; + + var hrFactor = this.TexData.HiRes ? 2f : 1f; + + var uv = new Vector2(part.U, part.V) * hrFactor; + var wh = new Vector2(part.Width, part.Height) * hrFactor; + + var partBegin = cursorScreenPos + uv; + var partEnd = partBegin + wh; + + if (reqHover && !ImGui.IsMouseHoveringRect(partBegin, partEnd)) + { + return; + } + + var savePos = ImGui.GetCursorPos(); + + ImGui.GetWindowDrawList().AddRect(partBegin, partEnd, RgbaVector4ToUint(col)); + + ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20)); + ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}"); + ImGui.SetCursorPos(savePos); + } + + /// + private protected override void PrintNodeObject() => ShowStruct(this.ImgNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + PrintFieldValuePairs( + ("Wrap", $"{this.ImgNode->WrapMode}"), + ("Image Flags", $"0x{this.ImgNode->Flags:X}")); + this.DrawTextureAndParts(); + } + + private static void PrintPartCoords(float u, float v, float w, float h, bool asFloat = false, bool lineBreak = false) + { + ImGui.TextDisabled($"{u}, {v},{(lineBreak ? "\n" : " ")}{w}, {h}"); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Click to copy as Vector2\nShift-click to copy as Vector4"); + } + + var suffix = asFloat ? "f" : string.Empty; + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText( + ImGui.IsKeyDown(ImGuiKey.ModShift) + ? $"new Vector4({u}{suffix}, {v}{suffix}, {w}{suffix}, {h}{suffix})" + : $"new Vector2({u}{suffix}, {v}{suffix});\nnew Vector2({w}{suffix}, {h}{suffix})"); + } + } + + private void DrawFullTexture() + { + var cursorScreenPos = ImGui.GetCursorScreenPos(); + var cursorLocalPos = ImGui.GetCursorPos(); + + ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(this.TexData.Texture->ActualWidth, this.TexData.Texture->ActualHeight)); + + for (uint p = 0; p < this.TexData.PartsList->PartCount; p++) + { + if (p == this.TexData.PartId) + { + continue; + } + + this.DrawPartOutline(p, cursorScreenPos, cursorLocalPos, new(0.6f, 0.6f, 0.6f, 1), true); + } + + this.DrawPartOutline(this.TexData.PartId, cursorScreenPos, cursorLocalPos, new(0, 0.85F, 1, 1)); + } + + private void PrintPartsTable() + { + using var tbl = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); + if (tbl.Success) + { + ImGui.TableSetupColumn("Part ID", WidthFixed); + ImGui.TableSetupColumn("Part Texture", WidthFixed); + ImGui.TableSetupColumn("Coordinates", WidthFixed); + + ImGui.TableHeadersRow(); + + var tWidth = this.TexData.Texture->ActualWidth; + var tHeight = this.TexData.Texture->ActualHeight; + var textureSize = new Vector2(tWidth, tHeight); + + for (ushort i = 0; i < this.TexData.PartCount; i++) + { + ImGui.TableNextColumn(); + + var col = i == this.TexData.PartId ? new Vector4(0, 0.85F, 1, 1) : new(1); + ImGui.TextColored(col, $"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); + + ImGui.TableNextColumn(); + + var part = this.TexData.PartsList->Parts[i]; + var hiRes = this.TexData.HiRes; + + var u = hiRes ? part.U * 2f : part.U; + var v = hiRes ? part.V * 2f : part.V; + var width = hiRes ? part.Width * 2f : part.Width; + var height = hiRes ? part.Height * 2f : part.Height; + + ImGui.Image( + new(this.TexData.Texture->D3D11ShaderResourceView), + new(width, height), + new Vector2(u, v) / textureSize, + new Vector2(u + width, v + height) / textureSize); + + ImGui.TableNextColumn(); + + ImGui.TextColored(!hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Standard:\t"); + ImGui.SameLine(); + var cursX = ImGui.GetCursorPosX(); + + PrintPartCoords(u / 2f, v / 2f, width / 2f, height / 2f); + + ImGui.TextColored(hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Hi-Res:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u, v, width, height); + + ImGui.Text("UV:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true); + } + } + } + + /// + /// A summary of pertinent data about a node's texture. + /// + protected struct TextureData + { + /// The texture's partslist. + public AtkUldPartsList* PartsList; + + /// The number of parts in the texture. + public uint PartCount; + + /// The part ID the node is using. + public uint PartId; + + /// The texture itself. + public Texture* Texture = null; + + /// The type of texture. + public TextureType TexType = 0; + + /// The texture's file path (if , otherwise this value is null). + public string? Path = null; + + /// Whether this is a high-resolution texture. + public bool HiRes = false; + + /// + /// Initializes a new instance of the struct. + /// + /// The texture's parts list. + /// The part ID being used by the node. + public TextureData(AtkUldPartsList* partsList, uint partId) + { + this.PartsList = partsList; + this.PartCount = this.PartsList->PartCount; + this.PartId = partId >= this.PartCount ? 0 : partId; + + if (this.PartsList == null) + { + return; + } + + var asset = this.PartsList->Parts[this.PartId].UldAsset; + + if (asset == null) + { + return; + } + + this.TexType = asset->AtkTexture.TextureType; + + if (this.TexType == Resource) + { + var resource = asset->AtkTexture.Resource; + this.Texture = resource->KernelTextureObject; + this.Path = Marshal.PtrToStringAnsi(new(resource->TexFileResourceHandle->ResourceHandle.FileName.BufferPtr)); + } + else + { + this.Texture = this.TexType == KernelTexture ? asset->AtkTexture.KernelTexture : null; + this.Path = null; + } + + this.HiRes = this.Path?.Contains("_hr1") ?? false; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs new file mode 100644 index 000000000..48825becb --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs @@ -0,0 +1,148 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Utility.Util; + +using Vector2 = System.Numerics.Vector2; +using Vector4 = System.Numerics.Vector4; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class NineGridNodeTree : ImageNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal NineGridNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override uint PartId => this.NgNode->PartId; + + /// + private protected override AtkUldPartsList* PartsList => this.NgNode->PartsList; + + private AtkNineGridNode* NgNode => (AtkNineGridNode*)this.Node; + + private NineGridOffsets Offsets => new(this.NgNode); + + /// + private protected override void DrawPartOutline(uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) + { + var part = this.TexData.PartsList->Parts[partId]; + + var hrFactor = this.TexData.HiRes ? 2f : 1f; + var uv = new Vector2(part.U, part.V) * hrFactor; + var wh = new Vector2(part.Width, part.Height) * hrFactor; + var partBegin = cursorScreenPos + uv; + var partEnd = cursorScreenPos + uv + wh; + + var savePos = ImGui.GetCursorPos(); + + if (!reqHover || ImGui.IsMouseHoveringRect(partBegin, partEnd)) + { + var adjustedOffsets = this.Offsets * hrFactor; + var ngBegin1 = partBegin with { X = partBegin.X + adjustedOffsets.Left }; + var ngEnd1 = partEnd with { X = partEnd.X - adjustedOffsets.Right }; + + var ngBegin2 = partBegin with { Y = partBegin.Y + adjustedOffsets.Top }; + var ngEnd2 = partEnd with { Y = partEnd.Y - adjustedOffsets.Bottom }; + + var ngCol = RgbaVector4ToUint(col with { W = 0.75f * col.W }); + + ImGui.GetWindowDrawList() + .AddRect(partBegin, partEnd, RgbaVector4ToUint(col)); + ImGui.GetWindowDrawList().AddRect(ngBegin1, ngEnd1, ngCol); + ImGui.GetWindowDrawList().AddRect(ngBegin2, ngEnd2, ngCol); + + ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20)); + ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}"); + } + + ImGui.SetCursorPos(savePos); + } + + /// + private protected override void PrintNodeObject() => ShowStruct(this.NgNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (!isEditorOpen) + { + ImGui.Text("NineGrid Offsets:\t"); + ImGui.SameLine(); + this.Offsets.Print(); + } + + this.DrawTextureAndParts(); + } + + /// + /// A struct representing the four offsets of an . + /// + internal struct NineGridOffsets + { + /// Top offset. + internal int Top; + + /// Left offset. + internal int Left; + + /// Right offset. + internal int Right; + + /// Bottom offset. + internal int Bottom; + + /// + /// Initializes a new instance of the struct. + /// + /// The top offset. + /// The right offset. + /// The bottom offset. + /// The left offset. + internal NineGridOffsets(int top, int right, int bottom, int left) + { + this.Top = top; + this.Right = right; + this.Left = left; + this.Bottom = bottom; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The node using these offsets. + internal NineGridOffsets(AtkNineGridNode* ngNode) + : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) + { + } + + private NineGridOffsets(Vector4 v) + : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) + { + } + + public static implicit operator NineGridOffsets(Vector4 v) => new(v); + + public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); + + public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; + + public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; + + /// Prints the offsets in ImGui. + internal readonly void Print() => Gui.PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs new file mode 100644 index 000000000..6c12d3b4c --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -0,0 +1,453 @@ +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.Browsing.Events; +using static Dalamud.Interface.Internal.UiDebug2.ElementSelector; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; + +using static ImGuiNET.ImGuiCol; +using static ImGuiNET.ImGuiTreeNodeFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +/// As with the structs they represent, this class serves as the base class for other types of NodeTree. +internal unsafe partial class ResNodeTree : IDisposable +{ + private NodePopoutWindow? window; + + private bool editorOpen; + + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + private protected ResNodeTree(AtkResNode* node, AddonTree addonTree) + { + this.Node = node; + this.AddonTree = addonTree; + this.NodeType = node->Type; + this.AddonTree.NodeTrees.Add((nint)this.Node, this); + } + + /// + /// Gets or sets the this tree represents. + /// + protected internal AtkResNode* Node { get; set; } + + /// + /// Gets the containing this tree. + /// + protected internal AddonTree AddonTree { get; private set; } + + /// + /// Gets this node's type. + /// + private protected NodeType NodeType { get; init; } + + /// + /// Gets or sets the offset of this node within its parent Addon. + /// + private protected int? NodeFieldOffset { get; set; } + + /// + /// Clears this NodeTree's popout window, if it has one. + /// + public void Dispose() + { + if (this.window != null && PopoutWindows.Windows.Contains(this.window)) + { + PopoutWindows.RemoveWindow(this.window); + this.window.Dispose(); + } + } + + /// + /// Gets an instance of (or one of its inheriting types) for the given node. If no instance exists, one is created. + /// + /// The node to get a tree for. + /// The tree for the node's containing addon. + /// An existing or newly-created instance of . + internal static ResNodeTree GetOrCreate(AtkResNode* node, AddonTree addonTree) => + addonTree.NodeTrees.TryGetValue((nint)node, out var nodeTree) ? nodeTree + : (int)node->Type > 1000 + ? new ComponentNodeTree(node, addonTree) + : node->Type switch + { + NodeType.Text => new TextNodeTree(node, addonTree), + NodeType.Image => new ImageNodeTree(node, addonTree), + NodeType.NineGrid => new NineGridNodeTree(node, addonTree), + NodeType.ClippingMask => new ClippingMaskNodeTree(node, addonTree), + NodeType.Counter => new CounterNodeTree(node, addonTree), + NodeType.Collision => new CollisionNodeTree(node, addonTree), + _ => new ResNodeTree(node, addonTree), + }; + + /// + /// Prints a list of NodeTrees for a given list of nodes. + /// + /// The address of the start of the list. + /// The number of nodes in the list. + /// The tree for the containing addon. + internal static void PrintNodeList(AtkResNode** nodeList, int count, AddonTree addonTree) + { + for (uint j = 0; j < count; j++) + { + GetOrCreate(nodeList[j], addonTree).Print(j); + } + } + + /// + /// Calls , but outputs the results as a collapsible tree. + /// + /// The address of the start of the list. + /// The number of nodes in the list. + /// The heading text of the tree. + /// The tree for the containing addon. + /// The text color of the heading. + internal static void PrintNodeListAsTree(AtkResNode** nodeList, int count, string label, AddonTree addonTree, Vector4 color) + { + if (count <= 0) + { + return; + } + + using var col = ImRaii.PushColor(Text, color); + using var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); + col.Pop(); + + if (tree.Success) + { + var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); + + PrintNodeList(nodeList, count, addonTree); + + var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; + + if (lineStart.Y < lineEnd.Y) + { + ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color), 1); + } + } + } + + /// + /// Prints this tree in the window. + /// + /// The index of the tree within its containing node or addon, if applicable. + /// Whether the tree should default to being open. + internal void Print(uint? index, bool forceOpen = false) + { + if (SearchResults.Length > 0 && SearchResults[0] == (nint)this.Node) + { + this.PrintWithHighlights(index); + } + else + { + this.PrintTree(index, forceOpen); + } + } + + /// + /// Prints out the tree's header text. + /// + internal void WriteTreeHeading() + { + ImGui.TextUnformatted(this.GetHeaderText()); + this.PrintFieldLabels(); + } + + /// + /// If the given pointer is referenced with the addon struct, the offset within the addon will be printed. If the given pointer has been identified as a field within the addon struct, this method also prints that field's name. + /// + /// The pointer to check. + /// The text color to use. + /// The field offset of the pointer, if it was found in the addon. + private protected void PrintFieldLabel(nint ptr, Vector4 color, int? fieldOffset) + { + if (fieldOffset != null) + { + ImGui.SameLine(0, -1); + ImGui.TextColored(color * 0.85f, $"[0x{fieldOffset:X}]"); + } + + if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result)) + { + ImGui.SameLine(0, -1); + ImGui.TextColored(color, string.Join(".", result)); + } + } + + /// + /// Builds a string that will serve as the header text for the tree. Indicates the node type, the number of direct children it contains, and its pointer. + /// + /// The resulting header text string. + private protected virtual string GetHeaderText() + { + var count = this.GetDirectChildCount(); + return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)}"; + } + + /// + /// Prints any field names for the node. + /// + private protected virtual void PrintFieldLabels() + { + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); + } + + /// + /// Prints the node struct. + /// + private protected virtual void PrintNodeObject() + { + ShowStruct(this.Node); + ImGui.SameLine(); + ImGui.NewLine(); + } + + /// + /// Prints all direct children of this node. + /// + private protected virtual void PrintChildNodes() + { + var prevNode = this.Node->ChildNode; + while (prevNode != null) + { + GetOrCreate(prevNode, this.AddonTree).Print(null); + prevNode = prevNode->PrevSiblingNode; + } + } + + /// + /// Prints any specific fields pertaining to the specific type of node. + /// + /// Whether the "Edit" box is currently checked. + private protected virtual void PrintFieldsForNodeType(bool isEditorOpen = false) + { + } + + /// + /// Attempts to retrieve the field offset of the given pointer within the parent addon. + /// + private protected virtual void GetFieldOffset() + { + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + if (Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i) == (nint)this.Node) + { + this.NodeFieldOffset = i; + break; + } + } + } + + private int GetDirectChildCount() + { + var count = 0; + if (this.Node->ChildNode != null) + { + count++; + + var prev = this.Node->ChildNode; + while (prev->PrevSiblingNode != null) + { + prev = prev->PrevSiblingNode; + count++; + } + } + + return count; + } + + private void PrintWithHighlights(uint? index) + { + if (!Scrolled) + { + ImGui.SetScrollHereY(); + Scrolled = true; + } + + var start = ImGui.GetCursorScreenPos() - new Vector2(5); + this.PrintTree(index, true); + var end = new Vector2(ImGui.GetMainViewport().WorkSize.X, ImGui.GetCursorScreenPos().Y + 5); + + ImGui.GetWindowDrawList().AddRectFilled(start, end, RgbaVector4ToUint(new Vector4(1, 1, 0.2f, 1) { W = Countdown / 200f })); + } + + private void PrintTree(uint? index, bool forceOpen = false) + { + var visible = this.Node->NodeFlags.HasFlag(Visible); + var label = $"{(index == null ? string.Empty : $"[{index}] ")}[#{this.Node->NodeId}]###{(nint)this.Node:X}nodeTree"; + var displayColor = !visible ? new Vector4(0.8f, 0.8f, 0.8f, 1) : + this.Node->Color.A == 0 ? new(0.015f, 0.575f, 0.355f, 1) : + new(0.1f, 1f, 0.1f, 1f); + + if (forceOpen || SearchResults.Contains((nint)this.Node)) + { + ImGui.SetNextItemOpen(true, ImGuiCond.Always); + } + + this.GetFieldOffset(); + + using var col = ImRaii.PushColor(Text, displayColor); + using var tree = ImRaii.TreeNode(label, SpanFullWidth); + + if (ImGui.IsItemHovered()) + { + new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f)); + } + + ImGui.SameLine(0, -1); + this.WriteTreeHeading(); + + col.Pop(); + + if (tree.Success) + { + var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); + try + { + PrintFieldValuePair("Node", $"{(nint)this.Node:X}"); + + ImGui.SameLine(); + this.PrintNodeObject(); + + PrintFieldValuePairs( + ("NodeID", $"{this.Node->NodeId}"), + ("Type", $"{this.Node->Type}")); + + this.DrawBasicControls(); + + if (this.editorOpen) + { + this.DrawNodeEditorTable(); + } + else + { + this.PrintResNodeFields(); + } + + this.PrintFieldsForNodeType(this.editorOpen); + PrintEvents(this.Node); + new TimelineTree(this.Node).Print(); + + this.PrintChildNodes(); + } + catch (Exception ex) + { + ImGui.TextDisabled($"Couldn't display node!\n\n{ex}"); + } + + var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; + + if (lineStart.Y < lineEnd.Y) + { + ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(displayColor), 1); + } + } + } + + private void DrawBasicControls() + { + ImGui.SameLine(); + var y = ImGui.GetCursorPosY(); + + ImGui.SetCursorPosY(y - 2); + var isVisible = this.Node->NodeFlags.HasFlag(Visible); + if (ImGuiComponents.IconButton("vis", isVisible ? Eye : EyeSlash, isVisible ? new Vector4(0.0f, 0.8f, 0.2f, 1f) : new(0.6f, 0.6f, 0.6f, 1))) + { + if (isVisible) + { + this.Node->NodeFlags &= ~Visible; + } + else + { + this.Node->NodeFlags |= Visible; + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Visibility"); + } + + ImGui.SameLine(); + ImGui.SetCursorPosY(y - 2); + ImGui.Checkbox($"Edit###editCheckBox{(nint)this.Node}", ref this.editorOpen); + + ImGui.SameLine(); + ImGui.SetCursorPosY(y - 2); + if (ImGuiComponents.IconButton($"###{(nint)this.Node}popoutButton", this.window?.IsOpen == true ? Times : ArrowUpRightFromSquare, null)) + { + this.TogglePopout(); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Popout Window"); + } + } + + private void TogglePopout() + { + if (this.window != null) + { + this.window.IsOpen = !this.window.IsOpen; + } + else + { + this.window = new NodePopoutWindow(this, $"{this.AddonTree.AddonName}: {this.GetHeaderText()}###nodePopout{(nint)this.Node}"); + PopoutWindows.AddWindow(this.window); + } + } + + private void PrintResNodeFields() + { + PrintFieldValuePairs( + ("X", $"{this.Node->X}"), + ("Y", $"{this.Node->Y}"), + ("Width", $"{this.Node->Width}"), + ("Height", $"{this.Node->Height}"), + ("Priority", $"{this.Node->Priority}"), + ("Depth", $"{this.Node->Depth}"), + ("DrawFlags", $"0x{this.Node->DrawFlags:X}")); + + PrintFieldValuePairs( + ("ScaleX", $"{this.Node->ScaleX:F2}"), + ("ScaleY", $"{this.Node->ScaleY:F2}"), + ("OriginX", $"{this.Node->OriginX}"), + ("OriginY", $"{this.Node->OriginY}"), + ("Rotation", $"{this.Node->Rotation * (180d / Math.PI):F1}° / {this.Node->Rotation:F7}rad ")); + + var color = this.Node->Color; + var add = new Vector3(this.Node->AddRed, this.Node->AddGreen, this.Node->AddBlue); + var multiply = new Vector3(this.Node->MultiplyRed, this.Node->MultiplyGreen, this.Node->MultiplyBlue); + + PrintColor(RgbaUintToVector4(color.RGBA) with { W = 1 }, $"RGB: {SwapEndianness(color.RGBA) >> 8:X6}"); + ImGui.SameLine(); + PrintColor(color, $"Alpha: {color.A}"); + ImGui.SameLine(); + PrintColor((add / new Vector3(510f)) + new Vector3(0.5f), $"Add: {add.X} {add.Y} {add.Z}"); + ImGui.SameLine(); + PrintColor(multiply / 255f, $"Multiply: {multiply.X} {multiply.Y} {multiply.Z}"); + + PrintFieldValuePairs(("Flags", $"0x{(uint)this.Node->NodeFlags:X} ({this.Node->NodeFlags})")); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs new file mode 100644 index 000000000..02bd5feca --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -0,0 +1,118 @@ +using System.Runtime.InteropServices; + +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class TextNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal TextNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + private AtkTextNode* TxtNode => (AtkTextNode*)this.Node; + + private Utf8String NodeText => this.TxtNode->NodeText; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.TxtNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (isEditorOpen) + { + return; + } + + ImGui.TextColored(new(1), "Text:"); + ImGui.SameLine(); + + try + { + var style = new SeStringDrawParams + { + Color = this.TxtNode->TextColor.RGBA, + EdgeColor = this.TxtNode->EdgeColor.RGBA, + ForceEdgeColor = true, + EdgeStrength = 1f, + }; + + ImGuiHelpers.SeStringWrapped(this.NodeText.AsSpan(), style); + } + catch + { + ImGui.TextUnformatted(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? string.Empty); + } + + PrintFieldValuePairs( + ("Font", $"{this.TxtNode->FontType}"), + ("Font Size", $"{this.TxtNode->FontSize}"), + ("Alignment", $"{this.TxtNode->AlignmentType}")); + + PrintColor(this.TxtNode->TextColor, $"Text Color: {SwapEndianness(this.TxtNode->TextColor.RGBA):X8}"); + ImGui.SameLine(); + PrintColor(this.TxtNode->EdgeColor, $"Edge Color: {SwapEndianness(this.TxtNode->EdgeColor.RGBA):X8}"); + + this.PrintPayloads(); + } + + private void PrintPayloads() + { + using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); + + if (tree.Success) + { + var utf8String = this.NodeText; + var seStringBytes = new byte[utf8String.BufUsed]; + for (var i = 0L; i < utf8String.BufUsed; i++) + { + seStringBytes[i] = utf8String.StringPtr.Value[i]; + } + + var seString = SeString.Parse(seStringBytes); + for (var i = 0; i < seString.Payloads.Count; i++) + { + var payload = seString.Payloads[i]; + ImGui.TextUnformatted($"[{i}]"); + ImGui.SameLine(); + switch (payload.Type) + { + case PayloadType.RawText when payload is TextPayload tp: + { + Gui.PrintFieldValuePair("Raw Text", tp.Text ?? string.Empty); + break; + } + + default: + { + ImGui.TextUnformatted(payload.ToString()); + break; + } + } + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs new file mode 100644 index 000000000..2ba416b4f --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +public readonly partial struct TimelineTree +{ + /// + /// An interface for retrieving and printing the contents of a given column in an animation timeline table. + /// + public interface IKeyGroupColumn + { + /// Gets the column's name/heading. + public string Name { get; } + + /// Gets the number of cells in the column. + public int Count { get; } + + /// Gets the column's width. + public float Width { get; } + + /// + /// Calls this column's print function for a given row. + /// + /// The row number. + public void PrintValueAt(int i); + } + + /// + /// A column within an animation timeline table, representing a particular KeyGroup. + /// + /// The value type of the KeyGroup. + public struct KeyGroupColumn : IKeyGroupColumn + { + /// The values of each cell in the column. + public List Values; + + /// The method that should be used to format and print values in this KeyGroup. + public Action PrintFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// The column's name/heading. + /// The method that should be used to format and print values in this KeyGroup. + internal KeyGroupColumn(string name, Action? printFunc = null) + { + this.Name = name; + this.PrintFunc = printFunc ?? PlainTextCell; + this.Values = []; + this.Width = 50; + } + + /// + public string Name { get; set; } + + /// + public float Width { get; init; } + + /// + public readonly int Count => this.Values.Count; + + /// + /// The default print function, if none is specified. + /// + /// The value to print. + public static void PlainTextCell(T value) => ImGui.TextUnformatted($"{value}"); + + /// + /// Adds a value to this column. + /// + /// The value to add. + public readonly void Add(T val) => this.Values.Add(val); + + /// + public readonly void PrintValueAt(int i) + { + if (this.Values.Count > i) + { + this.PrintFunc.Invoke(this.Values[i]); + } + else + { + ImGui.TextDisabled("..."); + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs new file mode 100644 index 000000000..57e5eff99 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -0,0 +1,384 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.Graphics; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeType; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; +using static ImGuiNET.ImGuiTreeNodeFlags; + +// ReSharper disable SuggestBaseTypeForParameter +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A struct allowing a node's animation timeline to be printed and browsed. +/// +public readonly unsafe partial struct TimelineTree +{ + private readonly AtkResNode* node; + + /// + /// Initializes a new instance of the struct. + /// + /// The node whose timelines are to be displayed. + internal TimelineTree(AtkResNode* node) + { + this.node = node; + } + + private AtkTimeline* NodeTimeline => this.node->Timeline; + + private AtkTimelineResource* Resource => this.NodeTimeline->Resource; + + private AtkTimelineAnimation* ActiveAnimation => this.NodeTimeline->ActiveAnimation; + + /// + /// Prints out this timeline tree within a window. + /// + internal void Print() + { + if (this.NodeTimeline == null) + { + return; + } + + var count = this.Resource->AnimationCount; + + if (count > 0) + { + using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); + + if (tree.Success) + { + PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}"); + + ImGui.SameLine(); + + ShowStruct(this.NodeTimeline); + + PrintFieldValuePairs( + ("Id", $"{this.NodeTimeline->Resource->Id}"), + ("Parent Time", $"{this.NodeTimeline->ParentFrameTime:F2} ({this.NodeTimeline->ParentFrameTime * 30:F0})"), + ("Frame Time", $"{this.NodeTimeline->FrameTime:F2} ({this.NodeTimeline->FrameTime * 30:F0})")); + + PrintFieldValuePairs(("Active Label Id", $"{this.NodeTimeline->ActiveLabelId}"), ("Duration", $"{this.NodeTimeline->LabelFrameIdxDuration}"), ("End Frame", $"{this.NodeTimeline->LabelEndFrameIdx}")); + ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), "Animation List"); + + for (var a = 0; a < count; a++) + { + var animation = this.Resource->Animations[a]; + var isActive = this.ActiveAnimation != null && &animation == this.ActiveAnimation; + this.PrintAnimation(animation, a, isActive, (nint)(this.NodeTimeline->Resource->Animations + (a * sizeof(AtkTimelineAnimation)))); + } + } + } + } + + private static void GetFrameColumn(Span keyGroups, List columns, ushort endFrame) + { + for (var i = 0; i < keyGroups.Length; i++) + { + if (keyGroups[i].Type != AtkTimelineKeyGroupType.None) + { + var keyGroup = keyGroups[i]; + var idColumn = new KeyGroupColumn("Frame"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + idColumn.Add(keyGroup.KeyFrames[f].FrameIdx); + } + + if (idColumn.Values.Last() != endFrame) + { + idColumn.Add(endFrame); + } + + columns.Add(idColumn); + break; + } + } + } + + private static void GetPosColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var xColumn = new KeyGroupColumn("X"); + var yColumn = new KeyGroupColumn("Y"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var (x, y) = keyGroup.KeyFrames[f].Value.Float2; + + xColumn.Add(x); + yColumn.Add(y); + } + + columns.Add(xColumn); + columns.Add(yColumn); + } + + private static void GetRotationColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var rotColumn = new KeyGroupColumn("Rotation", static r => ImGui.TextUnformatted($"{r * (180d / Math.PI):F1}°")); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + rotColumn.Add(keyGroup.KeyFrames[f].Value.Float); + } + + columns.Add(rotColumn); + } + + private static void GetScaleColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var scaleXColumn = new KeyGroupColumn("ScaleX"); + var scaleYColumn = new KeyGroupColumn("ScaleY"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var (scaleX, scaleY) = keyGroup.KeyFrames[f].Value.Float2; + + scaleXColumn.Add(scaleX); + scaleYColumn.Add(scaleY); + } + + columns.Add(scaleXColumn); + columns.Add(scaleYColumn); + } + + private static void GetAlphaColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var alphaColumn = new KeyGroupColumn("Alpha", PrintAlpha); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + alphaColumn.Add(keyGroup.KeyFrames[f].Value.Byte); + } + + columns.Add(alphaColumn); + } + + private static void GetTintColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var addRGBColumn = new KeyGroupColumn("Add", PrintAddCell) { Width = 110 }; + var multiplyRGBColumn = new KeyGroupColumn("Multiply", PrintMultiplyCell) { Width = 110 }; + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var nodeTint = keyGroup.KeyFrames[f].Value.NodeTint; + + addRGBColumn.Add(new Vector3(nodeTint.AddR, nodeTint.AddG, nodeTint.AddB)); + multiplyRGBColumn.Add(nodeTint.MultiplyRGB); + } + + columns.Add(addRGBColumn); + columns.Add(multiplyRGBColumn); + } + + private static void GetTextColorColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var textColorColumn = new KeyGroupColumn("Text Color", PrintRGB); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + textColorColumn.Add(keyGroup.KeyFrames[f].Value.RGB); + } + + columns.Add(textColorColumn); + } + + private static void GetPartIdColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var partColumn = new KeyGroupColumn("Part ID"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + partColumn.Add(keyGroup.KeyFrames[f].Value.UShort); + } + + columns.Add(partColumn); + } + + private static void GetEdgeColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var edgeColorColumn = new KeyGroupColumn("Edge Color", PrintRGB); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + edgeColorColumn.Add(keyGroup.KeyFrames[f].Value.RGB); + } + + columns.Add(edgeColorColumn); + } + + private static void GetLabelColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var labelColumn = new KeyGroupColumn("Label"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + labelColumn.Add(keyGroup.KeyFrames[f].Value.Label.LabelId); + } + + columns.Add(labelColumn); + } + + private static void PrintRGB(ByteColor c) => PrintColor(c, $"0x{SwapEndianness(c.RGBA):X8}"); + + private static void PrintAlpha(byte b) => PrintColor(new Vector4(b / 255f), PadEvenly($"{b}", 25)); + + private static void PrintAddCell(Vector3 add) + { + var fmt = PadEvenly($"{PadEvenly($"{add.X}", 30)}{PadEvenly($"{add.Y}", 30)}{PadEvenly($"{add.Z}", 30)}", 100); + PrintColor(new Vector4((add / new Vector3(510f)) + new Vector3(0.5f), 1), fmt); + } + + private static void PrintMultiplyCell(ByteColor byteColor) + { + var multiply = new Vector3(byteColor.R, byteColor.G, byteColor.B); + var fmt = PadEvenly($"{PadEvenly($"{multiply.X}", 25)}{PadEvenly($"{multiply.Y}", 25)}{PadEvenly($"{multiply.Z}", 25)}", 100); + PrintColor(multiply / 255f, fmt); + } + + private static string PadEvenly(string str, float size) + { + while (ImGui.CalcTextSize(str).X < size * ImGuiHelpers.GlobalScale) + { + str = $" {str} "; + } + + return str; + } + + private void PrintAnimation(AtkTimelineAnimation animation, int a, bool isActive, nint address) + { + var columns = this.BuildColumns(animation); + + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 0.65F, 0.4F, 1), isActive)) + { + using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); + + if (tree.Success) + { + PrintFieldValuePair("Animation", $"{address:X}"); + + ShowStruct((AtkTimelineAnimation*)address); + + if (columns.Count > 0) + { + using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + + if (tbl.Success) + { + foreach (var c in columns) + { + ImGui.TableSetupColumn(c.Name, WidthFixed, c.Width); + } + + ImGui.TableHeadersRow(); + + var rows = columns.Select(static c => c.Count).Max(); + + for (var i = 0; i < rows; i++) + { + ImGui.TableNextRow(); + + foreach (var c in columns) + { + ImGui.TableNextColumn(); + c.PrintValueAt(i); + } + } + } + } + } + } + } + + private List BuildColumns(AtkTimelineAnimation animation) + { + var keyGroups = animation.KeyGroups; + var columns = new List(); + + GetFrameColumn(keyGroups, columns, animation.EndFrameIdx); + + GetPosColumns(keyGroups[0], columns); + + GetRotationColumn(keyGroups[1], columns); + + GetScaleColumns(keyGroups[2], columns); + + GetAlphaColumn(keyGroups[3], columns); + + GetTintColumns(keyGroups[4], columns); + + if (this.node->Type is Image or NineGrid or ClippingMask) + { + GetPartIdColumn(keyGroups[5], columns); + } + else if (this.node->Type == Text) + { + GetTextColorColumn(keyGroups[5], columns); + } + + GetEdgeColumn(keyGroups[6], columns); + + GetLabelColumn(keyGroups[7], columns); + + return columns; + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs new file mode 100644 index 000000000..65537e210 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -0,0 +1,494 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.Globalization.NumberFormatInfo; + +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.UiBuilder; +using static Dalamud.Interface.Utility.ImGuiHelpers; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; +using static ImGuiNET.ImGuiCol; +using static ImGuiNET.ImGuiWindowFlags; +// ReSharper disable StructLacksIEquatable.Global + +#pragma warning disable CS0659 + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A tool that enables the user to select UI elements within the inspector by mousing over them onscreen. +/// +internal unsafe class ElementSelector : IDisposable +{ + private const int UnitListCount = 18; + + private readonly UiDebug2 uiDebug2; + + private string addressSearchInput = string.Empty; + + private int index; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of this Element Selector belongs to. + internal ElementSelector(UiDebug2 uiDebug2) + { + this.uiDebug2 = uiDebug2; + } + + /// + /// Gets or sets the results retrieved by the Element Selector. + /// + internal static nint[] SearchResults { get; set; } = []; + + /// + /// Gets or sets a value governing the highlighting of nodes when found via search. + /// + internal static float Countdown { get; set; } + + /// + /// Gets or sets a value indicating whether the window has scrolled down to the position of the search result. + /// + internal static bool Scrolled { get; set; } + + /// + /// Gets or sets a value indicating whether the mouseover UI is currently active. + /// + internal bool Active { get; set; } + + /// + public void Dispose() + { + this.Active = false; + } + + /// + /// Draws the Element Selector and Address Search interface at the bottom of the sidebar. + /// + internal void DrawInterface() + { + using var ch = ImRaii.Child("###sidebar_elementSelector", new(250, -1), true); + + if (ch.Success) + { + using (ImRaii.PushFont(IconFont)) + { + using (ImRaii.PushColor(Text, new Vector4(1, 1, 0.2f, 1), this.Active)) + { + if (ImGui.Button($"{(char)ObjectUngroup}")) + { + this.Active = !this.Active; + } + + if (Countdown > 0) + { + Countdown -= 1; + if (Countdown < 0) + { + Countdown = 0; + } + } + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Element Selector"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 32); + ImGui.InputTextWithHint( + "###addressSearchInput", + "Address Search", + ref this.addressSearchInput, + 18, + ImGuiInputTextFlags.AutoSelectAll); + ImGui.SameLine(); + + if (ImGuiComponents.IconButton("###elemSelectorAddrSearch", Search) && nint.TryParse( + this.addressSearchInput, + NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier, + InvariantInfo, + out var address)) + { + this.PerformSearch(address); + } + } + } + + /// + /// Draws the Element Selector's search output within the main window. + /// + internal void DrawSelectorOutput() + { + ImGui.GetIO().WantCaptureKeyboard = true; + ImGui.GetIO().WantCaptureMouse = true; + ImGui.GetIO().WantTextInput = true; + if (ImGui.IsKeyPressed(ImGuiKey.Escape)) + { + this.Active = false; + return; + } + + ImGui.Text("ELEMENT SELECTOR"); + ImGui.TextDisabled("Use the mouse to hover and identify UI elements, then click to jump to them in the inspector"); + ImGui.TextDisabled("Use the scrollwheel to choose between overlapping elements"); + ImGui.TextDisabled("Press ESCAPE to cancel"); + ImGui.Spacing(); + + var mousePos = ImGui.GetMousePos() - MainViewport.Pos; + var addonResults = GetAtkUnitBaseAtPosition(mousePos); + + using (ImRaii.PushColor(WindowBg, new Vector4(0.5f))) + { + using var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); + if (ch.Success) + { + using var gr = ImRaii.Group(); + if (gr.Success) + { + Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); + ImGui.Spacing(); + ImGui.Text("RESULTS:\n"); + + var i = 0; + foreach (var a in addonResults) + { + var name = a.Addon->NameString; + ImGui.TextUnformatted($"[Addon] {name}"); + ImGui.Indent(15); + foreach (var n in a.Nodes) + { + var nSelected = i++ == this.index; + + PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); + + if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + { + this.Active = false; + + this.uiDebug2.SelectedAddonName = a.Addon->NameString; + + var ptrList = new List { (nint)n.Node }; + + var nextNode = n.Node->ParentNode; + while (nextNode != null) + { + ptrList.Add((nint)nextNode); + nextNode = nextNode->ParentNode; + } + + SearchResults = [.. ptrList]; + Countdown = 100; + Scrolled = false; + } + + if (nSelected) + { + n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + } + } + + ImGui.Indent(-15); + } + + if (i != 0) + { + this.index -= (int)ImGui.GetIO().MouseWheel; + while (this.index < 0) + { + this.index += i; + } + + while (this.index >= i) + { + this.index -= i; + } + } + } + } + } + } + + private static List GetAtkUnitBaseAtPosition(Vector2 position) + { + var addonResults = new List(); + var unitListBaseAddr = GetUnitListBaseAddr(); + if (unitListBaseAddr == null) + { + return addonResults; + } + + foreach (var unit in UnitListOptions) + { + var unitManager = &unitListBaseAddr[unit.Index]; + + var safeCount = Math.Min(unitManager->Count, unitManager->Entries.Length); + + for (var i = 0; i < safeCount; i++) + { + var addon = unitManager->Entries[i].Value; + + if (addon == null || addon->RootNode == null) + { + continue; + } + + if (!addon->IsVisible || !addon->RootNode->NodeFlags.HasFlag(Visible)) + { + continue; + } + + var addonResult = new AddonResult(addon, []); + + if (addonResults.Contains(addonResult)) + { + continue; + } + + if (addon->X > position.X || addon->Y > position.Y) + { + continue; + } + + if (addon->X + addon->RootNode->Width < position.X) + { + continue; + } + + if (addon->Y + addon->RootNode->Height < position.Y) + { + continue; + } + + addonResult.Nodes.AddRange(GetNodeAtPosition(&addon->UldManager, position, true)); + addonResults.Add(addonResult); + } + } + + return [.. addonResults.OrderBy(static w => w.Area)]; + } + + private static List GetNodeAtPosition(AtkUldManager* uldManager, Vector2 position, bool reverse) + { + var nodeResults = new List(); + for (var i = 0; i < uldManager->NodeListCount; i++) + { + var node = uldManager->NodeList[i]; + + var bounds = new NodeBounds(node); + + if (!bounds.ContainsPoint(position)) + { + continue; + } + + if ((int)node->Type >= 1000) + { + var compNode = (AtkComponentNode*)node; + nodeResults.AddRange(GetNodeAtPosition(&compNode->Component->UldManager, position, false)); + } + + nodeResults.Add(new() { NodeBounds = bounds, Node = node }); + } + + if (reverse) + { + nodeResults.Reverse(); + } + + return nodeResults; + } + + private static bool FindByAddress(AtkUnitBase* atkUnitBase, nint address) + { + if (atkUnitBase->RootNode == null) + { + return false; + } + + if (!FindByAddress(atkUnitBase->RootNode, address, out var path)) + { + return false; + } + + Scrolled = false; + SearchResults = path?.ToArray() ?? []; + Countdown = 100; + return true; + } + + private static bool FindByAddress(AtkResNode* node, nint address, out List? path) + { + if (node == null) + { + path = null; + return false; + } + + if ((nint)node == address) + { + path = [(nint)node]; + return true; + } + + if ((int)node->Type >= 1000) + { + var cNode = (AtkComponentNode*)node; + + if (cNode->Component != null) + { + if ((nint)cNode->Component == address) + { + path = [(nint)node]; + return true; + } + + if (FindByAddress(cNode->Component->UldManager.RootNode, address, out path) && path != null) + { + path.Add((nint)node); + return true; + } + } + } + + if (FindByAddress(node->ChildNode, address, out path) && path != null) + { + path.Add((nint)node); + return true; + } + + if (FindByAddress(node->PrevSiblingNode, address, out path) && path != null) + { + return true; + } + + path = null; + return false; + } + + private static void PrintNodeHeaderOnly(AtkResNode* node, bool selected, AtkUnitBase* addon) + { + if (addon == null) + { + return; + } + + if (node == null) + { + return; + } + + var tree = AddonTree.GetOrCreate(addon->NameString); + if (tree == null) + { + return; + } + + using (ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1))) + { + ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); + } + } + + private void PerformSearch(nint address) + { + var unitListBaseAddr = GetUnitListBaseAddr(); + if (unitListBaseAddr == null) + { + return; + } + + for (var i = 0; i < UnitListCount; i++) + { + var unitManager = &unitListBaseAddr[i]; + var safeCount = Math.Min(unitManager->Count, unitManager->Entries.Length); + + for (var j = 0; j < safeCount; j++) + { + var addon = unitManager->Entries[j].Value; + if ((nint)addon == address || FindByAddress(addon, address)) + { + this.uiDebug2.SelectedAddonName = addon->NameString; + return; + } + } + } + } + + /// + /// An found by the Element Selector. + /// + internal struct AddonResult + { + /// The addon itself. + internal AtkUnitBase* Addon; + + /// A list of nodes discovered within this addon by the Element Selector. + internal List Nodes; + + /// The calculated area of the addon's root node. + internal float Area; + + /// + /// Initializes a new instance of the struct. + /// + /// The addon found. + /// A list for documenting nodes found within the addon. + public AddonResult(AtkUnitBase* addon, List nodes) + { + this.Addon = addon; + this.Nodes = nodes; + var rootNode = addon->RootNode; + this.Area = rootNode != null ? rootNode->Width * rootNode->Height * rootNode->ScaleY * rootNode->ScaleX : 0; + } + + /// + public override readonly bool Equals(object? obj) + { + if (obj is not AddonResult ar) + { + return false; + } + + return (nint)this.Addon == (nint)ar.Addon; + } + } + + /// + /// An found by the Element Selector. + /// + internal struct NodeResult + { + /// The node itself. + internal AtkResNode* Node; + + /// A struct representing the perimeter of the node. + internal NodeBounds NodeBounds; + + /// + public override readonly bool Equals(object? obj) + { + if (obj is not NodeResult nr) + { + return false; + } + + return nr.Node == this.Node; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs new file mode 100644 index 000000000..76112945e --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -0,0 +1,53 @@ +using System.Numerics; + +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A popout window for an . +/// +internal class AddonPopoutWindow : Window, IDisposable +{ + private readonly AddonTree addonTree; + + /// + /// Initializes a new instance of the class. + /// + /// The AddonTree this popout will show. + /// the window's name. + public AddonPopoutWindow(AddonTree tree, string name) + : base(name) + { + this.addonTree = tree; + this.PositionCondition = ImGuiCond.Once; + + var pos = ImGui.GetMousePos() + new Vector2(50, -50); + var workSize = ImGui.GetMainViewport().WorkSize; + var pos2 = new Vector2(Math.Min(workSize.X - 750, pos.X), Math.Min(workSize.Y - 250, pos.Y)); + + this.Position = pos2; + this.SizeCondition = ImGuiCond.Once; + this.Size = new(700, 200); + this.IsOpen = true; + this.SizeConstraints = new() { MinimumSize = new(100, 100) }; + } + + /// + public override void Draw() + { + using var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true); + if (ch.Success) + { + this.addonTree.Draw(); + } + } + + /// + public void Dispose() + { + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs new file mode 100644 index 000000000..fe8bc87ea --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -0,0 +1,72 @@ +using System.Numerics; + +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A popout window for a . +/// +internal unsafe class NodePopoutWindow : Window, IDisposable +{ + private readonly ResNodeTree resNodeTree; + + private bool firstDraw = true; + + /// + /// Initializes a new instance of the class. + /// + /// The node tree this window will show. + /// The name of the window. + public NodePopoutWindow(ResNodeTree nodeTree, string windowName) + : base(windowName) + { + this.resNodeTree = nodeTree; + + var pos = ImGui.GetMousePos() + new Vector2(50, -50); + var workSize = ImGui.GetMainViewport().WorkSize; + var pos2 = new Vector2(Math.Min(workSize.X - 750, pos.X), Math.Min(workSize.Y - 250, pos.Y)); + + this.Position = pos2; + this.IsOpen = true; + this.PositionCondition = ImGuiCond.Once; + this.SizeCondition = ImGuiCond.Once; + this.Size = new(700, 200); + this.SizeConstraints = new() { MinimumSize = new(100, 100) }; + } + + private AddonTree AddonTree => this.resNodeTree.AddonTree; + + private AtkResNode* Node => this.resNodeTree.Node; + + /// + public override void Draw() + { + if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) + { + using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); + if (ch.Success) + { + ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); + this.firstDraw = false; + } + } + else + { + Log.Warning($"Popout closed ({this.WindowName}); Node or Addon no longer exists."); + this.IsOpen = false; + this.Dispose(); + } + } + + /// + public void Dispose() + { + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs new file mode 100644 index 000000000..50967453d --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -0,0 +1,217 @@ +using System.Collections.Generic; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.StringComparison; +using static Dalamud.Interface.FontAwesomeIcon; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +internal unsafe partial class UiDebug2 +{ + /// + /// All unit lists to check for addons. + /// + internal static readonly List UnitListOptions = + [ + new(13, "Loaded"), + new(14, "Focused"), + new(0, "Depth Layer 1"), + new(1, "Depth Layer 2"), + new(2, "Depth Layer 3"), + new(3, "Depth Layer 4"), + new(4, "Depth Layer 5"), + new(5, "Depth Layer 6"), + new(6, "Depth Layer 7"), + new(7, "Depth Layer 8"), + new(8, "Depth Layer 9"), + new(9, "Depth Layer 10"), + new(10, "Depth Layer 11"), + new(11, "Depth Layer 12"), + new(12, "Depth Layer 13"), + new(15, "Units 16"), + new(16, "Units 17"), + new(17, "Units 18") + ]; + + private string addonNameSearch = string.Empty; + + private bool visFilter; + + /// + /// Gets the base address for all unit lists. + /// + /// The address, if found. + internal static AtkUnitList* GetUnitListBaseAddr() => &((UIModule*)GameGui.GetUIModule())->GetRaptureAtkModule()->RaptureAtkUnitManager.AtkUnitManager.DepthLayerOneList; + + private void DrawSidebar() + { + using var gr = ImRaii.Group(); + if (gr.Success) + { + this.DrawNameSearch(); + this.DrawAddonSelectionList(); + this.elementSelector.DrawInterface(); + } + } + + private void DrawNameSearch() + { + using var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true); + + if (ch.Success) + { + var atkUnitBaseSearch = this.addonNameSearch; + + Vector4? defaultColor = this.visFilter ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1); + if (ImGuiComponents.IconButton("filter", LowVision, defaultColor)) + { + this.visFilter = !this.visFilter; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Filter by visibility"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("###atkUnitBaseSearch", "Filter by name", ref atkUnitBaseSearch, 0x20)) + { + this.addonNameSearch = atkUnitBaseSearch; + } + } + } + + private void DrawAddonSelectionList() + { + using var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); + if (ch.Success) + { + var unitListBaseAddr = GetUnitListBaseAddr(); + + foreach (var unit in UnitListOptions) + { + this.DrawUnitListOption(unitListBaseAddr, unit); + } + } + } + + private void DrawUnitListOption(AtkUnitList* unitListBaseAddr, UnitListOption unit) + { + var atkUnitList = &unitListBaseAddr[unit.Index]; + var safeLength = Math.Min(atkUnitList->Count, atkUnitList->Entries.Length); + + var options = new List(); + var totalCount = 0; + var matchCount = 0; + var anyVisible = false; + + var usingFilter = this.visFilter || !string.IsNullOrEmpty(this.addonNameSearch); + + for (var i = 0; i < safeLength; i++) + { + var addon = atkUnitList->Entries[i].Value; + + if (addon == null) + { + continue; + } + + totalCount++; + + if (this.visFilter && !addon->IsVisible) + { + continue; + } + + if (!string.IsNullOrEmpty(this.addonNameSearch) && !addon->NameString.Contains(this.addonNameSearch, InvariantCultureIgnoreCase)) + { + continue; + } + + matchCount++; + anyVisible |= addon->IsVisible; + options.Add(new AddonOption(addon->NameString, addon->IsVisible)); + } + + if (matchCount == 0) + { + return; + } + + var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; + + using var col = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + using var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); + col.Pop(); + + if (tree.Success) + { + foreach (var option in options) + { + using (ImRaii.PushColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1))) + { + if (ImGui.Selectable($"{option.Name}##select{option.Name}", this.SelectedAddonName == option.Name)) + { + this.SelectedAddonName = option.Name; + } + } + } + } + } + + /// + /// A struct representing a unit list that can be browed in the sidebar. + /// + internal struct UnitListOption + { + /// The index of the unit list. + internal uint Index; + + /// The name of the unit list. + internal string Name; + + /// + /// Initializes a new instance of the struct. + /// + /// The index of the unit list. + /// The name of the unit list. + internal UnitListOption(uint i, string name) + { + this.Index = i; + this.Name = name; + } + } + + /// + /// A struct representing an addon that can be selected in the sidebar. + /// + internal struct AddonOption + { + /// The name of the addon. + internal string Name; + + /// Whether the addon is visible. + internal bool Visible; + + /// + /// Initializes a new instance of the struct. + /// + /// The name of the addon. + /// Whether the addon is visible. + internal AddonOption(string name, bool visible) + { + this.Name = name; + this.Visible = visible; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs new file mode 100644 index 000000000..74727f2a5 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; + +using Dalamud.Game.Gui; +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using static ImGuiNET.ImGuiWindowFlags; + +namespace Dalamud.Interface.Internal.UiDebug2; + +// Original version by aers https://github.com/aers/FFXIVUIDebug +// Also incorporates features from Caraxi's fork https://github.com/Caraxi/SimpleTweaksPlugin/blob/main/Debugging/UIDebug.cs + +/// +/// A tool for browsing the contents and structure of UI elements. +/// +internal partial class UiDebug2 : IDisposable +{ + private readonly ElementSelector elementSelector; + + /// + /// Initializes a new instance of the class. + /// + internal UiDebug2() + { + this.elementSelector = new(this); + } + + /// + internal static ModuleLog Log { get; set; } = new("UiDebug2"); + + /// + internal static IGameGui GameGui { get; set; } = Service.Get(); + + /// + /// Gets a collection of instances, each representing an . + /// + internal static Dictionary AddonTrees { get; } = []; + + /// + /// Gets or sets a window system to handle any popout windows for addons or nodes. + /// + internal static WindowSystem PopoutWindows { get; set; } = new("UiDebugPopouts"); + + /// + /// Gets or sets the name of the currently-selected . + /// + internal string? SelectedAddonName { get; set; } + + /// + /// Clears all windows and s. + /// + public void Dispose() + { + foreach (var a in AddonTrees) + { + a.Value.Dispose(); + } + + AddonTrees.Clear(); + PopoutWindows.RemoveAllWindows(); + this.elementSelector.Dispose(); + } + + /// + /// Draws the UiDebug tool's interface and contents. + /// + internal void Draw() + { + PopoutWindows.Draw(); + this.DrawSidebar(); + this.DrawMainPanel(); + } + + private void DrawMainPanel() + { + ImGui.SameLine(); + + using var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); + + if (ch.Success) + { + if (this.elementSelector.Active) + { + this.elementSelector.DrawSelectorOutput(); + } + else + { + if (this.SelectedAddonName != null) + { + var addonTree = AddonTree.GetOrCreate(this.SelectedAddonName); + + if (addonTree == null) + { + this.SelectedAddonName = null; + return; + } + + addonTree.Draw(); + } + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs new file mode 100644 index 000000000..cc4f1b698 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -0,0 +1,136 @@ +using System.Numerics; + +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.Graphics; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static ImGuiNET.ImGuiCol; + +namespace Dalamud.Interface.Internal.UiDebug2.Utility; + +/// +/// Miscellaneous ImGui tools used by . +/// +internal static class Gui +{ + /// + /// Prints field name and its value. + /// + /// The name of the field. + /// The value of the field. + /// Whether to enable click-to-copy. + internal static void PrintFieldValuePair(string fieldName, string value, bool copy = true) + { + ImGui.TextUnformatted($"{fieldName}:"); + ImGui.SameLine(); + var grey60 = new Vector4(0.6f, 0.6f, 0.6f, 1); + if (copy) + { + ImGuiHelpers.ClickToCopyText(value, null, grey60); + } + else + { + ImGui.TextColored(grey60, value); + } + } + + /// + /// Prints a set of fields and their values. + /// + /// Tuples of fieldnames and values to display. + internal static void PrintFieldValuePairs(params (string FieldName, string Value)[] pairs) + { + for (var i = 0; i < pairs.Length; i++) + { + if (i != 0) + { + ImGui.SameLine(); + } + + PrintFieldValuePair(pairs[i].FieldName, pairs[i].Value, false); + } + } + + /// + internal static void PrintColor(ByteColor color, string fmt) => PrintColor(RgbaUintToVector4(color.RGBA), fmt); + + /// + internal static void PrintColor(Vector3 color, string fmt) => PrintColor(new Vector4(color, 1), fmt); + + /// + /// Prints a text string representing a color, with a backdrop in that color. + /// + /// The color value. + /// The text string to print. + /// Colors the text itself either white or black, depending on the luminosity of the background color. + internal static void PrintColor(Vector4 color, string fmt) + { + using (ImRaii.PushColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color)) + { + ImGui.SmallButton(fmt); + } + + return; + + static double Luminosity(Vector4 vector4) => + Math.Pow( + (Math.Pow(vector4.X, 2) * 0.299f) + + (Math.Pow(vector4.Y, 2) * 0.587f) + + (Math.Pow(vector4.Z, 2) * 0.114f), + 0.5f) * vector4.W; + } + + /// + /// Draws a tooltip that changes based on the cursor's x-position within the hovered item. + /// + /// The text for each section. + /// true if the item is hovered. + internal static bool SplitTooltip(params string[] tooltips) + { + if (!ImGui.IsItemHovered()) + { + return false; + } + + var mouseX = ImGui.GetMousePos().X; + var minX = ImGui.GetItemRectMin().X; + var maxX = ImGui.GetItemRectMax().X; + var prog = (mouseX - minX) / (maxX - minX); + + var index = (int)Math.Floor(prog * tooltips.Length); + + using var tt = ImRaii.Tooltip(); + + if (tt.Success) + { + ImGui.TextUnformatted(tooltips[index]); + } + + return true; + } + + /// + /// Draws a separator with some padding above and below. + /// + /// Governs whether to pad above, below, or both. + /// The amount of padding. + internal static void PaddedSeparator(uint mask = 0b11, float padding = 5f) + { + if ((mask & 0b10) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + + ImGui.Separator(); + if ((mask & 0b01) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs new file mode 100644 index 000000000..3d28cb836 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.MathF; +using static Dalamud.Interface.ColorHelpers; + +namespace Dalamud.Interface.Internal.UiDebug2.Utility; + +/// +/// A struct representing the perimeter of an , accounting for all transformations. +/// +public unsafe struct NodeBounds +{ + /// + /// Initializes a new instance of the struct. + /// + /// The node to calculate the bounds of. + internal NodeBounds(AtkResNode* node) + { + if (node == null) + { + return; + } + + var w = node->Width; + var h = node->Height; + this.Points = w == 0 && h == 0 ? [new(0)] : [new(0), new(w, 0), new(w, h), new(0, h)]; + + this.TransformPoints(node); + } + + /// + /// Initializes a new instance of the struct, containing only a single given point. + /// + /// The point onscreen. + /// The node used to calculate transformations. + internal NodeBounds(Vector2 point, AtkResNode* node) + { + this.Points = [point]; + this.TransformPoints(node); + } + + private List Points { get; set; } = []; + + /// + /// Draws the bounds onscreen. + /// + /// The color of line to use. + /// The thickness of line to use. + /// If there is only a single point to draw, it will be indicated with a circle and dot. + internal readonly void Draw(Vector4 col, int thickness = 1) + { + if (this.Points == null || this.Points.Count == 0) + { + return; + } + + if (this.Points.Count == 1) + { + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], thickness, RgbaVector4ToUint(col), 12, thickness + 1); + } + else + { + var path = new ImVectorWrapper(this.Points.Count); + foreach (var p in this.Points) + { + path.Add(p); + } + + ImGui.GetBackgroundDrawList() + .AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + + path.Dispose(); + } + } + + /// + /// Draws the bounds onscreen, filled in. + /// + /// The fill and border color. + /// The border thickness. + internal readonly void DrawFilled(Vector4 col, int thickness = 1) + { + if (this.Points == null || this.Points.Count == 0) + { + return; + } + + if (this.Points.Count == 1) + { + ImGui.GetBackgroundDrawList() + .AddCircleFilled(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col), 12, thickness); + } + else + { + var path = new ImVectorWrapper(this.Points.Count); + foreach (var p in this.Points) + { + path.Add(p); + } + + ImGui.GetBackgroundDrawList() + .AddConvexPolyFilled(ref path[0], path.Length, RgbaVector4ToUint(col with { W = col.W / 2 })); + ImGui.GetBackgroundDrawList() + .AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + + path.Dispose(); + } + } + + /// + /// Checks whether the bounds contain a given point. + /// + /// The point to check. + /// True if the point exists within the bounds. + internal readonly bool ContainsPoint(Vector2 p) + { + var count = this.Points.Count; + var inside = false; + + for (var i = 0; i < count; i++) + { + var p1 = this.Points[i]; + var p2 = this.Points[(i + 1) % count]; + + if (p.Y > Min(p1.Y, p2.Y) && + p.Y <= Max(p1.Y, p2.Y) && + p.X <= Max(p1.X, p2.X) && + (p1.X.Equals(p2.X) || p.X <= (((p.Y - p1.Y) * (p2.X - p1.X)) / (p2.Y - p1.Y)) + p1.X)) + { + inside = !inside; + } + } + + return inside; + } + + private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s) + { + var cosR = Cos(r); + var sinR = Sin(r); + var d = (p - o) * s; + + return new( + (o.X + (d.X * cosR)) - (d.Y * sinR), + o.Y + (d.X * sinR) + (d.Y * cosR)); + } + + private void TransformPoints(AtkResNode* transformNode) + { + while (transformNode != null) + { + var offset = new Vector2(transformNode->X, transformNode->Y); + var origin = offset + new Vector2(transformNode->OriginX, transformNode->OriginY); + var rotation = transformNode->Rotation; + var scale = new Vector2(transformNode->ScaleX, transformNode->ScaleY); + + this.Points = this.Points.Select(b => TransformPoint(b + offset, origin, rotation, scale)).ToList(); + + transformNode = transformNode->ParentNode; + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs index 0b704990b..c0d2e4c61 100644 --- a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs @@ -147,7 +147,7 @@ internal sealed class ComponentDemoWindow : Window ImGui.Bullet(); ImGui.SetCursorPos(cursor + new Vector2(0, 10)); - ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); + ImGui.Text($"{easing.GetType().Name} ({easing.ValueClamped})"); ImGuiHelpers.ScaledDummy(5); } } diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 8ac49aef4..7326f6745 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -21,6 +21,7 @@ internal class DataWindow : Window, IDisposable private readonly IDataWindowWidget[] modules = { new AddonInspectorWidget(), + new AddonInspectorWidget2(), new AddonLifecycleWidget(), new AddonWidget(), new AddressesWidget(), @@ -42,20 +43,26 @@ internal class DataWindow : Window, IDisposable new HookWidget(), new IconBrowserWidget(), new ImGuiWidget(), + new InventoryWidget(), new KeyStateWidget(), new MarketBoardWidget(), new NetworkMonitorWidget(), + new NounProcessorWidget(), new ObjectTableWidget(), new PartyListWidget(), new PluginIpcWidget(), new SeFontTestWidget(), new ServicesWidget(), + new SeStringCreatorWidget(), + new SeStringRendererTestWidget(), new StartInfoWidget(), new TargetWidget(), new TaskSchedulerWidget(), new TexWidget(), new ToastWidget(), - new UIColorWidget(), + new UiColorWidget(), + new UldWidget(), + new VfsWidget(), }; private readonly IOrderedEnumerable orderedModules; @@ -63,6 +70,7 @@ internal class DataWindow : Window, IDisposable private bool isExcept; private bool selectionCollapsed; private IDataWindowWidget currentWidget; + private bool isLoaded; /// /// Initializes a new instance of the class. @@ -76,8 +84,6 @@ internal class DataWindow : Window, IDisposable this.RespectCloseHotkey = false; this.orderedModules = this.modules.OrderBy(module => module.DisplayName); this.currentWidget = this.orderedModules.First(); - - this.Load(); } /// @@ -86,6 +92,7 @@ internal class DataWindow : Window, IDisposable /// public override void OnOpen() { + this.Load(); } /// @@ -178,6 +185,7 @@ internal class DataWindow : Window, IDisposable if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync)) { + this.isLoaded = false; this.Load(); } @@ -231,6 +239,11 @@ internal class DataWindow : Window, IDisposable private void Load() { + if (this.isLoaded) + return; + + this.isLoaded = true; + foreach (var widget in this.modules) { widget.Load(); diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs new file mode 100644 index 000000000..24adb8bc5 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs @@ -0,0 +1,58 @@ +using System.Numerics; + +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.ImGuiNotification.Internal; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data; + +/// Useful functions for implementing data window widgets. +internal static class DataWindowWidgetExtensions +{ + /// Draws a text column, and make it copiable by clicking. + /// Owner widget. + /// String to display. + /// Whether to align to right. + /// Whether to offset to frame padding. + public static void TextColumnCopiable(this IDataWindowWidget widget, string s, bool alignRight, bool framepad) + { + var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); + if (framepad) + ImGui.AlignTextToFramePadding(); + if (alignRight) + { + var width = ImGui.CalcTextSize(s).X; + var xoff = ImGui.GetColumnWidth() - width; + offset.X += xoff; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xoff); + ImGui.TextUnformatted(s); + } + else + { + ImGui.TextUnformatted(s); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); + var vp = ImGui.GetWindowViewport(); + var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X; + ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue)); + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(wrx); + ImGui.TextWrapped(s.Replace("%", "%%")); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(s); + Service.Get().AddNotification( + $"Copied {ImGui.TableGetColumnName()} to clipboard.", + widget.DisplayName, + NotificationType.Success); + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs b/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs new file mode 100644 index 000000000..209970e24 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs @@ -0,0 +1,34 @@ +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data; + +/// +/// Common utilities used in Widgets. +/// +internal class WidgetUtil +{ + /// + /// Draws text that can be copied on click. + /// + /// The text shown and to be copied. + /// The text in the tooltip. + internal static void DrawCopyableText(string text, string tooltipText = "Copy") + { + ImGuiHelpers.SafeTextWrapped(text); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltipText); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(text); + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs index d4bea2931..e11404dec 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Interface.Internal.Windows.Data.Widgets; +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying addon inspector. diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs new file mode 100644 index 000000000..6cd6ecb0b --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs @@ -0,0 +1,35 @@ +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying addon inspector. +/// +internal class AddonInspectorWidget2 : IDataWindowWidget +{ + private UiDebug2.UiDebug2? addonInspector2; + + /// + public string[]? CommandShortcuts { get; init; } = ["ai2", "addoninspector2"]; + + /// + public string DisplayName { get; init; } = "Addon Inspector v2 (Testing)"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.addonInspector2 = new UiDebug2.UiDebug2(); + + if (this.addonInspector2 is not null) + { + this.Ready = true; + } + } + + /// + public void Draw() + { + this.addonInspector2?.Draw(); + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs index 606c49c49..9a3b231e5 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Aetherytes; +using Dalamud.Game.ClientState.Aetherytes; using ImGuiNET; @@ -56,7 +56,7 @@ internal class AetherytesWidget : IDataWindowWidget ImGui.TextUnformatted($"{i}"); ImGui.TableNextColumn(); // Name - ImGui.TextUnformatted($"{info.AetheryteData.GameData?.PlaceName.Value?.Name}"); + ImGui.TextUnformatted($"{info.AetheryteData.ValueNullable?.PlaceName.ValueNullable?.Name}"); ImGui.TableNextColumn(); // ID ImGui.TextUnformatted($"{info.AetheryteId}"); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index ee79764de..c3499570c 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -1,8 +1,15 @@ -using System.Numerics; +using System.Numerics; + +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; -using Dalamud.Memory; using ImGuiNET; +using Lumina.Text.ReadOnly; + namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// @@ -10,6 +17,20 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget { + private readonly Type numberType = typeof(NumberArrayType); + private readonly Type stringType = typeof(StringArrayType); + private readonly Type extendType = typeof(ExtendArrayType); + + private int selectedNumberArray; + private int selectedStringArray; + private int selectedExtendArray; + + private string searchTerm = string.Empty; + private bool hideUnsetStringArrayEntries = false; + private bool hideUnsetExtendArrayEntries = false; + private bool showTextAddress = false; + private bool showMacroString = false; + /// public bool Ready { get; set; } @@ -17,7 +38,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget public string[]? CommandShortcuts { get; init; } = { "atkarray" }; /// - public string DisplayName { get; init; } = "Atk Array Data"; + public string DisplayName { get; init; } = "Atk Array Data"; /// public void Load() @@ -28,148 +49,352 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget /// public void Draw() { - var fontWidth = ImGui.CalcTextSize("A").X; - var fontHeight = ImGui.GetTextLineHeightWithSpacing(); - var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUIModule(); + using var tabs = ImRaii.TabBar("AtkArrayDataTabs"); + if (!tabs) return; - if (uiModule == null) + this.DrawNumberArrayTab(); + this.DrawStringArrayTab(); + this.DrawExtendArrayTab(); + } + + private void DrawArrayList(Type? arrayType, int arrayCount, short* arrayKeys, AtkArrayData** arrays, ref int selectedIndex) + { + using var table = ImRaii.Table("ArkArrayTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Borders, new Vector2(300, -1)); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + var hasSearchTerm = !string.IsNullOrEmpty(this.searchTerm); + + for (var arrayIndex = 0; arrayIndex < arrayCount; arrayIndex++) { - ImGui.Text("UIModule unavailable."); - return; + var inUse = arrayKeys[arrayIndex] != -1; + + var rowsFound = 0; + + if (hasSearchTerm && arrayType == typeof(StringArrayType)) + { + if (!inUse) + continue; + + var stringArrayData = (StringArrayData*)arrays[arrayIndex]; + for (var rowIndex = 0; rowIndex < arrays[arrayIndex]->Size; rowIndex++) + { + var isNull = (nint)stringArrayData->StringArray[rowIndex] == 0; + if (isNull) + continue; + + if (new ReadOnlySeStringSpan(stringArrayData->StringArray[rowIndex]).ExtractText().Contains(this.searchTerm, StringComparison.InvariantCultureIgnoreCase)) + rowsFound++; + } + + if (rowsFound == 0) + continue; + } + + using var disabled = ImRaii.Disabled(!inUse); + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); // Index + if (ImGui.Selectable($"#{arrayIndex}", selectedIndex == arrayIndex, ImGuiSelectableFlags.SpanAllColumns)) + selectedIndex = arrayIndex; + + ImGui.TableNextColumn(); // Type + if (arrayType != null && Enum.IsDefined(arrayType, arrayIndex)) + { + ImGui.TextUnformatted(Enum.GetName(arrayType, arrayIndex)); + } + else if (inUse && arrays[arrayIndex]->SubscribedAddonsCount > 0) + { + var raptureAtkUnitManager = RaptureAtkUnitManager.Instance(); + + for (var j = 0; j < arrays[arrayIndex]->SubscribedAddonsCount; j++) + { + if (arrays[arrayIndex]->SubscribedAddons[j] == 0) + continue; + + using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF)) + ImGui.TextUnformatted(raptureAtkUnitManager->GetAddonById(arrays[arrayIndex]->SubscribedAddons[j])->NameString); + break; + } + } + + ImGui.TableNextColumn(); // Size + if (inUse) + ImGui.TextUnformatted((rowsFound > 0 ? rowsFound : arrays[arrayIndex]->Size).ToString()); + } + } + + private void DrawArrayHeader(Type? arrayType, string type, int index, AtkArrayData* array) + { + ImGui.TextUnformatted($"{type} Array #{index}"); + + if (arrayType != null && Enum.IsDefined(arrayType, index)) + { + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" ({Enum.GetName(arrayType, index)})"); } - var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; + ImGui.SameLine(); + ImGui.TextUnformatted("–"); + ImGui.SameLine(); + ImGui.TextUnformatted("Address: "); + ImGui.SameLine(0, 0); + WidgetUtil.DrawCopyableText($"0x{(nint)array:X}", "Copy address"); - if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) + if (array->SubscribedAddonsCount > 0) { - if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) + ImGui.SameLine(); + ImGui.TextUnformatted("–"); + ImGui.SameLine(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF)) + ImGui.TextUnformatted($"{array->SubscribedAddonsCount} Subscribed Addon" + (array->SubscribedAddonsCount > 1 ? 's' : string.Empty)); + + if (ImGui.IsItemHovered()) { - if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + using var tooltip = ImRaii.Tooltip(); + if (tooltip) { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) + var raptureAtkUnitManager = RaptureAtkUnitManager.Instance(); + + for (var j = 0; j < array->SubscribedAddonsCount; j++) { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; - if (numberArrayData != null) - { - ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); - ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); - ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); - ImGui.TableHeadersRow(); - for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberIndex}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); - ImGui.TableNextColumn(); - ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); - } + if (array->SubscribedAddons[j] == 0) + continue; - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } + ImGui.TextUnformatted(raptureAtkUnitManager->GetAddonById(array->SubscribedAddons[j])->NameString); } - - ImGui.EndTable(); } + } + } + } - ImGui.EndTabItem(); + private void DrawNumberArrayTab() + { + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + using var tab = ImRaii.TabItem("Number Arrays"); + if (!tab) return; + + this.DrawArrayList( + this.numberType, + atkArrayDataHolder.NumberArrayCount, + atkArrayDataHolder.NumberArrayKeys, + (AtkArrayData**)atkArrayDataHolder.NumberArrays, + ref this.selectedNumberArray); + + if (this.selectedNumberArray >= atkArrayDataHolder.NumberArrayCount || atkArrayDataHolder.NumberArrayKeys[this.selectedNumberArray] == -1) + this.selectedNumberArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + if (!child) return; + + var array = atkArrayDataHolder.NumberArrays[this.selectedNumberArray]; + this.DrawArrayHeader(this.numberType, "Number", this.selectedNumberArray, (AtkArrayData*)array); + + using var table = ImRaii.Table("NumberArrayDataTable", 7, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Short", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Byte", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupScrollFreeze(7, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < array->Size; i++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + var ptr = &array->IntArray[i]; + + ImGui.TableNextColumn(); // Address + WidgetUtil.DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address"); + + ImGui.TableNextColumn(); // Integer + WidgetUtil.DrawCopyableText((*ptr).ToString(), "Copy value"); + + ImGui.TableNextColumn(); // Short + WidgetUtil.DrawCopyableText((*(short*)ptr).ToString(), "Copy as short"); + + ImGui.TableNextColumn(); // Byte + WidgetUtil.DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte"); + + ImGui.TableNextColumn(); // Float + WidgetUtil.DrawCopyableText((*(float*)ptr).ToString(), "Copy as float"); + + ImGui.TableNextColumn(); // Hex + WidgetUtil.DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex"); + } + } + + private void DrawStringArrayTab() + { + using var tab = ImRaii.TabItem("String Arrays"); + if (!tab) return; + + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + using (var sidebarchild = ImRaii.Child("StringArraySidebar", new Vector2(300, -1), false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings)) + { + if (sidebarchild) + { + ImGui.SetNextItemWidth(-1); + ImGui.InputTextWithHint("##TextSearch", "Search...", ref this.searchTerm, 256, ImGuiInputTextFlags.AutoSelectAll); + + this.DrawArrayList( + this.stringType, + atkArrayDataHolder.StringArrayCount, + atkArrayDataHolder.StringArrayKeys, + (AtkArrayData**)atkArrayDataHolder.StringArrays, + ref this.selectedStringArray); + } + } + + if (this.selectedStringArray >= atkArrayDataHolder.StringArrayCount || atkArrayDataHolder.StringArrayKeys[this.selectedStringArray] == -1) + this.selectedStringArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + if (!child) return; + + var array = atkArrayDataHolder.StringArrays[this.selectedStringArray]; + this.DrawArrayHeader(this.stringType, "String", this.selectedStringArray, (AtkArrayData*)array); + ImGui.Checkbox("Hide unset entries##HideUnsetStringArrayEntriesCheckbox", ref this.hideUnsetStringArrayEntries); + ImGui.SameLine(); + ImGui.Checkbox("Show text address##WordWrapCheckbox", ref this.showTextAddress); + ImGui.SameLine(); + ImGui.Checkbox("Show macro string##RenderStringsCheckbox", ref this.showMacroString); + + using var table = ImRaii.Table("StringArrayDataTable", 4, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn(this.showTextAddress ? "Text Address" : "Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Managed", ImGuiTableColumnFlags.WidthFixed, 60); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(4, 1); + ImGui.TableHeadersRow(); + + var hasSearchTerm = !string.IsNullOrEmpty(this.searchTerm); + + for (var i = 0; i < array->Size; i++) + { + var isNull = (nint)array->StringArray[i] == 0; + if (isNull && this.hideUnsetStringArrayEntries) + continue; + + if (hasSearchTerm) + { + if (isNull) + continue; + + if (!new ReadOnlySeStringSpan(array->StringArray[i]).ExtractText().Contains(this.searchTerm, StringComparison.InvariantCultureIgnoreCase)) + continue; } - if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) + using var disabledColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), isNull); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + ImGui.TableNextColumn(); // Address + if (this.showTextAddress) { - if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; - if (stringArrayData != null) - { - ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringIndex}"); - ImGui.TableNextColumn(); - if (stringArrayData->StringArray[stringIndex] != null) - { - ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); - } - else - { - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.EndTabItem(); + if (!isNull) + WidgetUtil.DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); + } + else + { + WidgetUtil.DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address"); } - ImGui.EndTabBar(); + ImGui.TableNextColumn(); // Managed + if (!isNull) + { + ImGui.TextUnformatted(((nint)array->StringArray[i] != 0 && array->ManagedStringArray[i] == array->StringArray[i]).ToString()); + } + + ImGui.TableNextColumn(); // Text + if (!isNull) + { + if (this.showMacroString) + { + WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); + } + else + { + ImGuiHelpers.SeStringWrapped(new ReadOnlySeStringSpan(array->StringArray[i])); + } + } + } + } + + private void DrawExtendArrayTab() + { + using var tab = ImRaii.TabItem("Extend Arrays"); + if (!tab) return; + + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + this.DrawArrayList( + this.extendType, + atkArrayDataHolder.ExtendArrayCount, + atkArrayDataHolder.ExtendArrayKeys, + (AtkArrayData**)atkArrayDataHolder.ExtendArrays, + ref this.selectedExtendArray); + + if (this.selectedExtendArray >= atkArrayDataHolder.ExtendArrayCount || atkArrayDataHolder.ExtendArrayKeys[this.selectedExtendArray] == -1) + this.selectedExtendArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + + var array = atkArrayDataHolder.ExtendArrays[this.selectedExtendArray]; + this.DrawArrayHeader(null, "Extend", this.selectedExtendArray, (AtkArrayData*)array); + ImGui.Checkbox("Hide unset entries##HideUnsetExtendArrayEntriesCheckbox", ref this.hideUnsetExtendArrayEntries); + + using var table = ImRaii.Table("ExtendArrayDataTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < array->Size; i++) + { + var isNull = (nint)array->DataArray[i] == 0; + if (isNull && this.hideUnsetExtendArrayEntries) + continue; + + using var disabledColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), isNull); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + ImGui.TableNextColumn(); // Address + WidgetUtil.DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address"); + + ImGui.TableNextColumn(); // Pointer + if (!isNull) + WidgetUtil.DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address"); } } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs index c35280f92..961d3c3c0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Buddy; +using Dalamud.Game.ClientState.Buddy; using Dalamud.Utility; using ImGuiNET; @@ -32,8 +32,6 @@ internal class BuddyListWidget : IDataWindowWidget var buddyList = Service.Get(); ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); { var member = buddyList.CompanionBuddy; if (member == null) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs index 1f58541be..8b7a692d4 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs @@ -1,4 +1,8 @@ -using Dalamud.Configuration.Internal; +using System.Linq; +using System.Threading; + +using Dalamud.Configuration.Internal; +using Dalamud.Game; using Dalamud.Game.Gui.Dtr; using ImGuiNET; @@ -8,17 +12,20 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying dtr test. /// -internal class DtrBarWidget : IDataWindowWidget +internal class DtrBarWidget : IDataWindowWidget, IDisposable { private IDtrBarEntry? dtrTest1; private IDtrBarEntry? dtrTest2; private IDtrBarEntry? dtrTest3; - + + private Thread? loadTestThread; + private CancellationTokenSource? loadTestThreadCt; + /// public string[]? CommandShortcuts { get; init; } = { "dtr", "dtrbar" }; - + /// - public string DisplayName { get; init; } = "DTR Bar"; + public string DisplayName { get; init; } = "DTR Bar"; /// public bool Ready { get; set; } @@ -26,31 +33,145 @@ internal class DtrBarWidget : IDataWindowWidget /// public void Load() { + this.ClearState(); this.Ready = true; } + /// + public void Dispose() => this.ClearState(); + /// public void Draw() { + if (this.loadTestThread?.IsAlive is not true) + { + if (ImGui.Button("Do multithreaded add/remove operation")) + { + var ct = this.loadTestThreadCt = new(); + var dbar = Service.Get(); + var fw = Service.Get(); + var rng = new Random(); + this.loadTestThread = new( + () => + { + var threads = Enumerable + .Range(0, Environment.ProcessorCount) + .Select( + i => new Thread( + (i % 4) switch + { + 0 => () => + { + try + { + while (true) + { + var n = $"DtrBarWidgetTest{rng.NextInt64(8)}"; + dbar.Get(n, n[^5..]); + fw.DelayTicks(1, ct.Token).Wait(ct.Token); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + 1 => () => + { + try + { + while (true) + { + dbar.Remove($"DtrBarWidgetTest{rng.NextInt64(8)}"); + fw.DelayTicks(1, ct.Token).Wait(ct.Token); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + 2 => () => + { + try + { + while (true) + { + var n = $"DtrBarWidgetTest{rng.NextInt64(8)}_"; + dbar.Get(n, n[^6..]); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + _ => () => + { + try + { + while (true) + { + dbar.Remove($"DtrBarWidgetTest{rng.NextInt64(8)}_"); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + })) + .ToArray(); + foreach (var t in threads) t.Start(); + foreach (var t in threads) t.Join(); + for (var i = 0; i < 8; i++) dbar.Remove($"DtrBarWidgetTest{i % 8}"); + for (var i = 0; i < 8; i++) dbar.Remove($"DtrBarWidgetTest{i % 8}_"); + }); + this.loadTestThread.Start(); + } + } + else + { + if (ImGui.Button("Stop multithreaded add/remove operation")) + this.ClearState(); + } + + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest1, "DTR Test #1"); + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest2, "DTR Test #2"); + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest3, "DTR Test #3"); + ImGui.Separator(); + ImGui.Text("IDtrBar.Entries:"); + foreach (var e in Service.Get().Entries) + ImGui.Text(e.Title); var configuration = Service.Get(); if (configuration.DtrOrder != null) { ImGui.Separator(); - + ImGui.Text("DtrOrder:"); foreach (var order in configuration.DtrOrder) - { ImGui.Text(order); - } } } - + + private void ClearState() + { + this.loadTestThreadCt?.Cancel(); + this.loadTestThread?.Join(); + this.loadTestThread = null; + this.loadTestThreadCt = null; + } + private void DrawDtrTestEntry(ref IDtrBarEntry? entry, string title) { var dtrBar = Service.Get(); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs index b36a7836c..34b04dae0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs @@ -1,4 +1,7 @@ -using Dalamud.Game.ClientState.Fates; +using Dalamud.Game.ClientState.Fates; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using ImGuiNET; @@ -9,8 +12,6 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class FateTableWidget : IDataWindowWidget { - private bool resolveGameData; - /// public string[]? CommandShortcuts { get; init; } = { "fate", "fatetable" }; @@ -29,44 +30,136 @@ internal class FateTableWidget : IDataWindowWidget /// public void Draw() { - ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - var fateTable = Service.Get(); + var textureManager = Service.Get(); - var stateString = string.Empty; if (fateTable.Length == 0) { ImGui.TextUnformatted("No fates or data not ready."); + return; } - else + + using var table = ImRaii.Table("FateTable", 13, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("FateId", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Level", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("MapIcon", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Progress", ImGuiTableColumnFlags.WidthFixed, 55); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Bonus", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Position", ImGuiTableColumnFlags.WidthFixed, 240); + ImGui.TableSetupColumn("Radius", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(7, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < fateTable.Length; i++) { - stateString += $"FateTableLen: {fateTable.Length}\n"; + var fate = fateTable[i]; + if (fate == null) + continue; - ImGui.TextUnformatted(stateString); + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); - for (var i = 0; i < fateTable.Length; i++) + ImGui.TableNextColumn(); // Address + WidgetUtil.DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address"); + + ImGui.TableNextColumn(); // FateId + WidgetUtil.DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)"); + + ImGui.TableNextColumn(); // State + ImGui.TextUnformatted(fate.State.ToString()); + + ImGui.TableNextColumn(); // Level + + if (fate.Level == fate.MaxLevel) { - var fate = fateTable[i]; - if (fate == null) - continue; + ImGui.TextUnformatted($"{fate.Level}"); + } + else + { + ImGui.TextUnformatted($"{fate.Level}-{fate.MaxLevel}"); + } - var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + - $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + - $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + - $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; + ImGui.TableNextColumn(); // Icon - fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + - $" - Duration: {fate.Duration}" + - $" - State: {fate.State}" + - $" - GameData name: {(this.resolveGameData ? (fate.GameData.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; - - ImGui.TextUnformatted(fateString); - ImGui.SameLine(); - if (ImGui.Button($"C##{fate.Address.ToInt64():X}")) + if (fate.IconId != 0) + { + if (textureManager.Shared.GetFromGameIcon(fate.IconId).TryGetWrap(out var texture, out _)) { - ImGui.SetClipboardText(fate.Address.ToString("X")); + ImGui.Image(texture.ImGuiHandle, new(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy IconId"); + ImGui.TextUnformatted($"ID: {fate.IconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(fate.IconId.ToString()); + } } } + + ImGui.TableNextColumn(); // MapIconId + + if (fate.MapIconId != 0) + { + if (textureManager.Shared.GetFromGameIcon(fate.MapIconId).TryGetWrap(out var texture, out _)) + { + ImGui.Image(texture.ImGuiHandle, new(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy MapIconId"); + ImGui.TextUnformatted($"ID: {fate.MapIconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(fate.MapIconId.ToString()); + } + } + } + + ImGui.TableNextColumn(); // Name + + WidgetUtil.DrawCopyableText(fate.Name.ToString(), "Click to copy Name"); + + ImGui.TableNextColumn(); // Progress + ImGui.TextUnformatted($"{fate.Progress}%"); + + ImGui.TableNextColumn(); // TimeRemaining + + if (fate.State == FateState.Running) + { + ImGui.TextUnformatted($"{TimeSpan.FromSeconds(fate.TimeRemaining):mm\\:ss} / {TimeSpan.FromSeconds(fate.Duration):mm\\:ss}"); + } + + ImGui.TableNextColumn(); // HasExpBonus + ImGui.TextUnformatted(fate.HasBonus.ToString()); + + ImGui.TableNextColumn(); // Position + WidgetUtil.DrawCopyableText(fate.Position.ToString(), "Click to copy Position"); + + ImGui.TableNextColumn(); // Radius + WidgetUtil.DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius"); } } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs index 5121d82e6..610fa90cc 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs @@ -12,9 +12,9 @@ internal class GamepadWidget : IDataWindowWidget { /// public string[]? CommandShortcuts { get; init; } = { "gamepad", "controller" }; - + /// - public string DisplayName { get; init; } = "Gamepad"; + public string DisplayName { get; init; } = "Gamepad"; /// public bool Ready { get; set; } @@ -42,24 +42,24 @@ internal class GamepadWidget : IDataWindowWidget this.DrawHelper( "Buttons Raw", - gamepadState.ButtonsRaw, + (uint)gamepadState.ButtonsRaw, gamepadState.Raw); this.DrawHelper( "Buttons Pressed", - gamepadState.ButtonsPressed, + (uint)gamepadState.ButtonsPressed, gamepadState.Pressed); this.DrawHelper( "Buttons Repeat", - gamepadState.ButtonsRepeat, + (uint)gamepadState.ButtonsRepeat, gamepadState.Repeat); this.DrawHelper( "Buttons Released", - gamepadState.ButtonsReleased, + (uint)gamepadState.ButtonsReleased, gamepadState.Released); ImGui.Text($"LeftStick {gamepadState.LeftStick}"); ImGui.Text($"RightStick {gamepadState.RightStick}"); } - + private void DrawHelper(string text, uint mask, Func resolve) { ImGui.Text($"{text} {mask:X4}"); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs index c16cc34e1..f30585406 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.JobGauge; using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.Utility; @@ -39,7 +39,7 @@ internal class GaugeWidget : IDataWindowWidget return; } - var jobID = player.ClassJob.Id; + var jobID = player.ClassJob.RowId; JobGaugeBase? gauge = jobID switch { 19 => jobGauges.Get(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs index 886f5cb19..3f510b088 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs @@ -17,6 +17,8 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// public class IconBrowserWidget : IDataWindowWidget { + private const int MaxIconId = 250_000; + private Vector2 iconSize = new(64.0f, 64.0f); private Vector2 editIconSize = new(64.0f, 64.0f); @@ -24,7 +26,7 @@ public class IconBrowserWidget : IDataWindowWidget private Task>? iconIdsTask; private int startRange; - private int stopRange = 200000; + private int stopRange = MaxIconId; private bool showTooltipImage; private Vector2 mouseDragStart; @@ -53,8 +55,8 @@ public class IconBrowserWidget : IDataWindowWidget { var texm = Service.Get(); - var result = new List<(int ItemId, string Path)>(200000); - for (var iconId = 0; iconId < 200000; iconId++) + var result = new List<(int ItemId, string Path)>(MaxIconId); + for (var iconId = 0; iconId < MaxIconId; iconId++) { // // Remove range 170,000 -> 180,000 by default, this specific range causes exceptions. // if (iconId is >= 170000 and < 180000) @@ -119,12 +121,18 @@ public class IconBrowserWidget : IDataWindowWidget ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##StartRange", ref this.startRange, 0, 0)) + { + this.startRange = Math.Clamp(this.startRange, 0, MaxIconId); this.valueRange = null; + } ImGui.NextColumn(); ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##StopRange", ref this.stopRange, 0, 0)) + { + this.stopRange = Math.Clamp(this.stopRange, 0, MaxIconId); this.valueRange = null; + } ImGui.NextColumn(); ImGui.Checkbox("Show Image in Tooltip", ref this.showTooltipImage); @@ -195,7 +203,7 @@ public class IconBrowserWidget : IDataWindowWidget ImGui.GetColorU32(ImGuiColors.DalamudRed), iconText); } - + if (ImGui.IsItemHovered()) ImGui.SetTooltip($"{iconId}\n{exc}".Replace("%", "%%")); @@ -215,7 +223,7 @@ public class IconBrowserWidget : IDataWindowWidget cursor + ((this.iconSize - textSize) / 2), color, text); - + if (ImGui.IsItemHovered()) ImGui.SetTooltip(iconId.ToString()); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs index 05d831b57..08d29398b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs @@ -5,11 +5,11 @@ using System.Threading.Tasks; using Dalamud.Game.Text; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Windowing; using Dalamud.Storage.Assets; -using Dalamud.Utility; using ImGuiNET; @@ -144,8 +144,6 @@ internal class ImGuiWidget : IDataWindowWidget "Action Bar (always on if not user dismissable for the example)", ref this.notificationTemplate.ActionBar); - ImGui.Checkbox("Leave Textures Open", ref this.notificationTemplate.LeaveTexturesOpen); - if (ImGui.Button("Add notification")) { var text = @@ -212,35 +210,34 @@ internal class ImGuiWidget : IDataWindowWidget switch (this.notificationTemplate.IconInt) { case 5: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - dam.GetDalamudTextureWrap( - Enum.Parse( - NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))), - this.notificationTemplate.LeaveTexturesOpen); - break; - case 6: - n.SetIconTexture( - dam.GetDalamudTextureWrapAsync( - Enum.Parse( - NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])) - .ContinueWith( - r => r.IsCompletedSuccessfully - ? Task.FromResult(DisposeLoggingTextureWrap.Wrap(r.Result)) - : r).Unwrap(), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap = DisposeLoggingTextureWrap.Wrap( + dam.GetDalamudTextureWrap( + Enum.Parse( + NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))); + + if (textureWrap != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap); + } + break; case 7: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - tm.Shared.GetFromGame(this.notificationTemplate.IconText).GetWrapOrDefault()), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap2 = DisposeLoggingTextureWrap.Wrap( + tm.Shared.GetFromGame(this.notificationTemplate.IconText).GetWrapOrDefault()); + if (textureWrap2 != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap2); + } + break; case 8: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - tm.Shared.GetFromFile(this.notificationTemplate.IconText).GetWrapOrDefault()), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap3 = DisposeLoggingTextureWrap.Wrap( + tm.Shared.GetFromFile(this.notificationTemplate.IconText).GetWrapOrDefault()); + if (textureWrap3 != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap3); + } + break; } @@ -303,15 +300,15 @@ internal class ImGuiWidget : IDataWindowWidget }; } } - + ImGui.SameLine(); if (ImGui.Button("Replace images using setter")) { foreach (var n in this.notifications) { var i = (uint)Random.Shared.NextInt64(0, 200000); - n.IconTexture = DisposeLoggingTextureWrap.Wrap( - Service.Get().Shared.GetFromGameIcon(new(i)).GetWrapOrDefault()); + + n.IconTexture = Service.Get().Shared.GetFromGameIcon(new(i, false, false)); } } } @@ -364,7 +361,7 @@ internal class ImGuiWidget : IDataWindowWidget public static readonly string[] AssetSources = Enum.GetValues() - .Where(x => x.GetAttribute()?.Purpose is DalamudAssetPurpose.TextureFromPng) + .Where(x => x.GetPurpose() is DalamudAssetPurpose.TextureFromPng) .Select(Enum.GetName) .ToArray(); @@ -428,7 +425,6 @@ internal class ImGuiWidget : IDataWindowWidget public bool Minimized; public bool UserDismissable; public bool ActionBar; - public bool LeaveTexturesOpen; public int ProgressMode; public void Reset() @@ -450,7 +446,6 @@ internal class ImGuiWidget : IDataWindowWidget this.Minimized = true; this.UserDismissable = true; this.ActionBar = true; - this.LeaveTexturesOpen = true; this.ProgressMode = 0; this.RespectUiHidden = true; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs new file mode 100644 index 000000000..f8aa4e500 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -0,0 +1,346 @@ +using System.Buffers.Binary; +using System.Numerics; +using System.Text; + +using Dalamud.Data; +using Dalamud.Game.Inventory; +using Dalamud.Game.Text; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game; + +using ImGuiNET; + +using Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying inventory data. +/// +internal class InventoryWidget : IDataWindowWidget +{ + private DataManager dataManager; + private TextureManager textureManager; + private InventoryType? selectedInventoryType = InventoryType.Inventory1; + + /// + public string[]? CommandShortcuts { get; init; } = ["inv", "inventory"]; + + /// + public string DisplayName { get; init; } = "Inventory"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.Ready = true; + } + + /// + public void Draw() + { + this.dataManager ??= Service.Get(); + this.textureManager ??= Service.Get(); + + this.DrawInventoryTypeList(); + + if (this.selectedInventoryType == null) + return; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + this.DrawInventoryType((InventoryType)this.selectedInventoryType); + } + + private static string StripSoftHypen(string input) + { + return input.Replace("\u00AD", string.Empty); + } + + private unsafe void DrawInventoryTypeList() + { + using var table = ImRaii.Table("InventoryTypeTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings, new Vector2(300, -1)); + if (!table) return; + + ImGui.TableSetupColumn("Type"); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(2, 1); + ImGui.TableHeadersRow(); + + foreach (var inventoryType in Enum.GetValues()) + { + var items = GameInventoryItem.GetReadOnlySpanOfInventory((GameInventoryType)inventoryType); + + using var itemDisabled = ImRaii.Disabled(items.IsEmpty); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Type + if (ImGui.Selectable(inventoryType.ToString(), this.selectedInventoryType == inventoryType, ImGuiSelectableFlags.SpanAllColumns)) + { + this.selectedInventoryType = inventoryType; + } + + using (var contextMenu = ImRaii.ContextPopupItem($"##InventoryContext{inventoryType}")) + { + if (contextMenu) + { + if (ImGui.MenuItem("Copy Name")) + { + ImGui.SetClipboardText(inventoryType.ToString()); + } + + if (ImGui.MenuItem("Copy Address")) + { + var container = InventoryManager.Instance()->GetInventoryContainer(inventoryType); + ImGui.SetClipboardText($"0x{(nint)container:X}"); + } + } + } + + ImGui.TableNextColumn(); // Size + ImGui.TextUnformatted(items.Length.ToString()); + } + } + + private unsafe void DrawInventoryType(InventoryType inventoryType) + { + var items = GameInventoryItem.GetReadOnlySpanOfInventory((GameInventoryType)inventoryType); + if (items.IsEmpty) + { + ImGui.TextUnformatted($"{inventoryType} is empty."); + return; + } + + using var itemTable = ImRaii.Table("InventoryItemTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!itemTable) return; + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 70); + ImGui.TableSetupColumn("Quantity", ImGuiTableColumnFlags.WidthFixed, 70); + ImGui.TableSetupColumn("Item"); + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); + + for (var slotIndex = 0; slotIndex < items.Length; slotIndex++) + { + var item = items[slotIndex]; + + using var disableditem = ImRaii.Disabled(item.ItemId == 0); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Slot + ImGui.TextUnformatted(slotIndex.ToString()); + + ImGui.TableNextColumn(); // ItemId + ImGuiHelpers.ClickToCopyText(item.ItemId.ToString()); + + ImGui.TableNextColumn(); // Quantity + ImGuiHelpers.ClickToCopyText(item.Quantity.ToString()); + + ImGui.TableNextColumn(); // Item + if (item.ItemId != 0 && item.Quantity != 0) + { + var itemName = ItemUtil.GetItemName(item.ItemId).ExtractText(); + var iconId = this.GetItemIconId(item.ItemId); + + if (this.textureManager.Shared.TryGetFromGameIcon(new GameIconLookup(iconId, item.IsHq), out var tex) && tex.TryGetWrap(out var texture, out _)) + { + ImGui.Image(texture.ImGuiHandle, new Vector2(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy IconId"); + ImGui.TextUnformatted($"ID: {iconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(iconId.ToString()); + } + + ImGui.SameLine(); + + using var itemNameColor = ImRaii.PushColor(ImGuiCol.Text, this.GetItemRarityColor(item.ItemId)); + using var node = ImRaii.TreeNode($"{itemName}###{inventoryType}_{slotIndex}", ImGuiTreeNodeFlags.SpanAvailWidth); + itemNameColor.Dispose(); + + using (var contextMenu = ImRaii.ContextPopupItem($"{inventoryType}_{slotIndex}_ContextMenu")) + { + if (contextMenu) + { + if (ImGui.MenuItem("Copy Name")) + { + ImGui.SetClipboardText(itemName); + } + } + } + + if (!node) continue; + + using var itemInfoTable = ImRaii.Table($"{inventoryType}_{slotIndex}_Table", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!itemInfoTable) continue; + + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 150); + ImGui.TableSetupColumn("Value"); + // ImGui.TableHeadersRow(); + + static void AddKeyValueRow(string fieldName, string value) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(fieldName); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value); + } + + static void AddValueValueRow(string value1, string value2) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value1); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value2); + } + + AddKeyValueRow("ItemId", item.ItemId.ToString()); + AddKeyValueRow("Quantity", item.Quantity.ToString()); + AddKeyValueRow("GlamourId", item.GlamourId.ToString()); + + if (!ItemUtil.IsEventItem(item.ItemId)) + { + AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.SpiritbondOrCollectability.ToString()); + + if (item.CrafterContentId != 0) + AddKeyValueRow("CrafterContentId", item.CrafterContentId.ToString()); + } + + var flagsBuilder = new StringBuilder(); + + if (item.IsHq) + { + flagsBuilder.Append("IsHq"); + } + + if (item.IsCompanyCrestApplied) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsCompanyCrestApplied"); + } + + if (item.IsRelic) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsRelic"); + } + + if (item.IsCollectable) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsCollectable"); + } + + if (flagsBuilder.Length == 0) + flagsBuilder.Append("None"); + + AddKeyValueRow("Flags", flagsBuilder.ToString()); + + if (ItemUtil.IsNormalItem(item.ItemId) && this.dataManager.Excel.GetSheet().TryGetRow(item.ItemId, out var itemRow)) + { + if (itemRow.DyeCount > 0 && item.Stains.Length > 0) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Stains"); + ImGui.TableNextColumn(); + + using var stainTable = ImRaii.Table($"{inventoryType}_{slotIndex}_StainTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!stainTable) continue; + + ImGui.TableSetupColumn("Stain Id", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Name"); + ImGui.TableHeadersRow(); + + for (var i = 0; i < itemRow.DyeCount; i++) + { + var stainId = item.Stains[i]; + AddValueValueRow(stainId.ToString(), this.GetStainName(stainId)); + } + } + + if (itemRow.MateriaSlotCount > 0 && item.Materia.Length > 0) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Materia"); + ImGui.TableNextColumn(); + + using var materiaTable = ImRaii.Table($"{inventoryType}_{slotIndex}_MateriaTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!materiaTable) continue; + + ImGui.TableSetupColumn("Materia Id", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("MateriaGrade Id"); + ImGui.TableHeadersRow(); + + for (var i = 0; i < Math.Min(itemRow.MateriaSlotCount, item.Materia.Length); i++) + { + AddValueValueRow(item.Materia[i].ToString(), item.MateriaGrade[i].ToString()); + } + } + } + } + } + } + + private string GetStainName(uint stainId) + { + return this.dataManager.Excel.GetSheet().TryGetRow(stainId, out var stainRow) + ? StripSoftHypen(stainRow.Name.ExtractText()) + : $"Stain#{stainId}"; + } + + private uint GetItemRarityColor(uint itemId, bool isEdgeColor = false) + { + if (ItemUtil.IsEventItem(itemId)) + return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; + + if (!this.dataManager.Excel.GetSheet().TryGetRow(ItemUtil.GetBaseId(itemId).ItemId, out var item)) + return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; + + var rowId = ItemUtil.GetItemRarityColorType(item.RowId, isEdgeColor); + return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) + ? BinaryPrimitives.ReverseEndianness(color.Dark) | 0xFF000000 + : 0xFFFFFFFF; + } + + private uint GetItemIconId(uint itemId) + { + // EventItem + if (ItemUtil.IsEventItem(itemId)) + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItem) ? eventItem.Icon : 0u; + + // HighQuality + if (ItemUtil.IsHighQuality(itemId)) + itemId -= 1_000_000; + + // Collectible + if (ItemUtil.IsCollectible(itemId)) + itemId -= 500_000; + + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var item) ? item.Icon : 0u; + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs new file mode 100644 index 000000000..bc0bd0ac9 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs @@ -0,0 +1,207 @@ +using System.Linq; +using System.Text; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.ClientState; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Interface.Utility.Raii; + +using ImGuiNET; +using Lumina.Data; +using Lumina.Excel; +using Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for the NounProcessor service. +/// +internal class NounProcessorWidget : IDataWindowWidget +{ + /// A list of German grammatical cases. + internal static readonly string[] GermanCases = ["Nominative", "Genitive", "Dative", "Accusative"]; + + private static readonly Type[] NounSheets = [ + typeof(Aetheryte), + typeof(BNpcName), + typeof(BeastTribe), + typeof(DeepDungeonEquipment), + typeof(DeepDungeonItem), + typeof(DeepDungeonMagicStone), + typeof(DeepDungeonDemiclone), + typeof(ENpcResident), + typeof(EObjName), + typeof(EurekaAetherItem), + typeof(EventItem), + typeof(GCRankGridaniaFemaleText), + typeof(GCRankGridaniaMaleText), + typeof(GCRankLimsaFemaleText), + typeof(GCRankLimsaMaleText), + typeof(GCRankUldahFemaleText), + typeof(GCRankUldahMaleText), + typeof(GatheringPointName), + typeof(Glasses), + typeof(GlassesStyle), + typeof(HousingPreset), + typeof(Item), + typeof(MJIName), + typeof(Mount), + typeof(Ornament), + typeof(TripleTriadCard), + ]; + + private ClientLanguage[] languages = []; + private string[] languageNames = []; + + private int selectedSheetNameIndex = 0; + private int selectedLanguageIndex = 0; + private int rowId = 1; + private int amount = 1; + + /// + public string[]? CommandShortcuts { get; init; } = { "noun" }; + + /// + public string DisplayName { get; init; } = "Noun Processor"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.languages = Enum.GetValues(); + this.languageNames = Enum.GetNames(); + this.selectedLanguageIndex = (int)Service.Get().ClientLanguage; + + this.Ready = true; + } + + /// + public void Draw() + { + var nounProcessor = Service.Get(); + var dataManager = Service.Get(); + var clientState = Service.Get(); + + var sheetType = NounSheets.ElementAt(this.selectedSheetNameIndex); + var language = this.languages[this.selectedLanguageIndex]; + + ImGui.SetNextItemWidth(300); + if (ImGui.Combo("###SelectedSheetName", ref this.selectedSheetNameIndex, NounSheets.Select(t => t.Name).ToArray(), NounSheets.Length)) + { + this.rowId = 1; + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(120); + if (ImGui.Combo("###SelectedLanguage", ref this.selectedLanguageIndex, this.languageNames, this.languageNames.Length)) + { + language = this.languages[this.selectedLanguageIndex]; + this.rowId = 1; + } + + ImGui.SetNextItemWidth(120); + var sheet = dataManager.Excel.GetSheet(Language.English, sheetType.Name); + var minRowId = (int)sheet.FirstOrDefault().RowId; + var maxRowId = (int)sheet.LastOrDefault().RowId; + if (ImGui.InputInt("RowId###RowId", ref this.rowId, 1, 10, ImGuiInputTextFlags.AutoSelectAll)) + { + if (this.rowId < minRowId) + this.rowId = minRowId; + + if (this.rowId >= maxRowId) + this.rowId = maxRowId; + } + + ImGui.SameLine(); + ImGui.TextUnformatted($"(Range: {minRowId} - {maxRowId})"); + + ImGui.SetNextItemWidth(120); + if (ImGui.InputInt("Amount###Amount", ref this.amount, 1, 10, ImGuiInputTextFlags.AutoSelectAll)) + { + if (this.amount <= 0) + this.amount = 1; + } + + var articleTypeEnumType = language switch + { + ClientLanguage.Japanese => typeof(JapaneseArticleType), + ClientLanguage.German => typeof(GermanArticleType), + ClientLanguage.French => typeof(FrenchArticleType), + _ => typeof(EnglishArticleType), + }; + + var numCases = language == ClientLanguage.German ? 4 : 1; + +#if DEBUG + if (ImGui.Button("Copy as self-test entry")) + { + var sb = new StringBuilder(); + + foreach (var articleType in Enum.GetValues(articleTypeEnumType)) + { + for (var grammaticalCase = 0; grammaticalCase < numCases; grammaticalCase++) + { + var nounParams = new NounParams() + { + SheetName = sheetType.Name, + RowId = (uint)this.rowId, + Language = language, + Quantity = this.amount, + ArticleType = (int)articleType, + GrammaticalCase = grammaticalCase, + }; + var output = nounProcessor.ProcessNoun(nounParams).ExtractText().Replace("\"", "\\\""); + var caseParam = language == ClientLanguage.German ? $"(int)GermanCases.{GermanCases[grammaticalCase]}" : "1"; + sb.AppendLine($"new(nameof(LSheets.{sheetType.Name}), {this.rowId}, ClientLanguage.{language}, {this.amount}, (int){articleTypeEnumType.Name}.{Enum.GetName(articleTypeEnumType, articleType)}, {caseParam}, \"{output}\"),"); + } + } + + ImGui.SetClipboardText(sb.ToString()); + } +#endif + + using var table = ImRaii.Table("TextDecoderTable", 1 + numCases, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("ArticleType", ImGuiTableColumnFlags.WidthFixed, 150); + for (var i = 0; i < numCases; i++) + ImGui.TableSetupColumn(language == ClientLanguage.German ? GermanCases[i] : "Text"); + ImGui.TableSetupScrollFreeze(6, 1); + ImGui.TableHeadersRow(); + + foreach (var articleType in Enum.GetValues(articleTypeEnumType)) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TableHeader(articleType.ToString()); + + for (var currentCase = 0; currentCase < numCases; currentCase++) + { + ImGui.TableNextColumn(); + + try + { + var nounParams = new NounParams() + { + SheetName = sheetType.Name, + RowId = (uint)this.rowId, + Language = language, + Quantity = this.amount, + ArticleType = (int)articleType, + GrammaticalCase = currentCase, + }; + ImGui.TextUnformatted(nounProcessor.ProcessNoun(nounParams).ExtractText()); + } + catch (Exception ex) + { + ImGui.TextUnformatted(ex.ToString()); + } + } + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs index 963138bec..761dc49a8 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; @@ -56,8 +56,8 @@ internal class ObjectTableWidget : IDataWindowWidget { stateString += $"ObjectTableLen: {objectTable.Length}\n"; stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; - stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData?.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; - stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData?.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; + stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.ValueNullable?.Name : clientState.LocalPlayer.CurrentWorld.RowId.ToString())}\n"; + stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.ValueNullable?.Name : clientState.LocalPlayer.HomeWorld.RowId.ToString())}\n"; stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs index b59abbff1..35a2a616e 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs @@ -1,4 +1,6 @@ -using Dalamud.Game.Text; +using System.Linq; + +using Dalamud.Game.Text; using ImGuiNET; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -28,8 +30,11 @@ internal class SeFontTestWidget : IDataWindowWidget { var specialChars = string.Empty; - for (var i = 0xE020; i <= 0xE0DB; i++) - specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; + var min = (char)Enum.GetValues().Min(); + var max = (char)Enum.GetValues().Max(); + + for (var i = min; i <= max; i++) + specialChars += $"0x{(int)i:X} - {(SeIconChar)i} - {i}\n"; ImGui.TextUnformatted(specialChars); } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs new file mode 100644 index 000000000..92e57ddac --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs @@ -0,0 +1,1276 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Text.Evaluator; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Memory; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using ImGuiNET; + +using Lumina.Data; +using Lumina.Data.Files.Excel; +using Lumina.Data.Structs.Excel; +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget to create SeStrings. +/// +internal class SeStringCreatorWidget : IDataWindowWidget +{ + private const LinkMacroPayloadType DalamudLinkType = (LinkMacroPayloadType)Payload.EmbeddedInfoType.DalamudLink - 1; + + private readonly Dictionary expressionNames = new() + { + { MacroCode.SetResetTime, ["Hour", "WeekDay"] }, + { MacroCode.SetTime, ["Time"] }, + { MacroCode.If, ["Condition", "StatementTrue", "StatementFalse"] }, + { MacroCode.Switch, ["Condition"] }, + { MacroCode.PcName, ["EntityId"] }, + { MacroCode.IfPcGender, ["EntityId", "CaseMale", "CaseFemale"] }, + { MacroCode.IfPcName, ["EntityId", "CaseTrue", "CaseFalse"] }, + // { MacroCode.Josa, [] }, + // { MacroCode.Josaro, [] }, + { MacroCode.IfSelf, ["EntityId", "CaseTrue", "CaseFalse"] }, + // { MacroCode.NewLine, [] }, + { MacroCode.Wait, ["Seconds"] }, + { MacroCode.Icon, ["IconId"] }, + { MacroCode.Color, ["Color"] }, + { MacroCode.EdgeColor, ["Color"] }, + { MacroCode.ShadowColor, ["Color"] }, + // { MacroCode.SoftHyphen, [] }, + // { MacroCode.Key, [] }, + // { MacroCode.Scale, [] }, + { MacroCode.Bold, ["Enabled"] }, + { MacroCode.Italic, ["Enabled"] }, + // { MacroCode.Edge, [] }, + // { MacroCode.Shadow, [] }, + // { MacroCode.NonBreakingSpace, [] }, + { MacroCode.Icon2, ["IconId"] }, + // { MacroCode.Hyphen, [] }, + { MacroCode.Num, ["Value"] }, + { MacroCode.Hex, ["Value"] }, + { MacroCode.Kilo, ["Value", "Separator"] }, + { MacroCode.Byte, ["Value"] }, + { MacroCode.Sec, ["Time"] }, + { MacroCode.Time, ["Value"] }, + { MacroCode.Float, ["Value", "Radix", "Separator"] }, + { MacroCode.Link, ["Type"] }, + { MacroCode.Sheet, ["SheetName", "RowId", "ColumnIndex", "ColumnParam"] }, + { MacroCode.String, ["String"] }, + { MacroCode.Caps, ["String"] }, + { MacroCode.Head, ["String"] }, + { MacroCode.Split, ["String", "Separator"] }, + { MacroCode.HeadAll, ["String"] }, + // { MacroCode.Fixed, [] }, + { MacroCode.Lower, ["String"] }, + { MacroCode.JaNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.EnNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.DeNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.FrNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.ChNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.LowerHead, ["String"] }, + { MacroCode.ColorType, ["ColorType"] }, + { MacroCode.EdgeColorType, ["ColorType"] }, + { MacroCode.Digit, ["Value", "TargetLength"] }, + { MacroCode.Ordinal, ["Value"] }, + { MacroCode.Sound, ["IsJingle", "SoundId"] }, + { MacroCode.LevelPos, ["LevelId"] }, + }; + + private readonly Dictionary linkExpressionNames = new() + { + { LinkMacroPayloadType.Character, ["Flags", "WorldId"] }, + { LinkMacroPayloadType.Item, ["ItemId", "Rarity"] }, + { LinkMacroPayloadType.MapPosition, ["TerritoryType/MapId", "RawX", "RawY"] }, + { LinkMacroPayloadType.Quest, ["QuestId"] }, + { LinkMacroPayloadType.Achievement, ["AchievementId"] }, + { LinkMacroPayloadType.HowTo, ["HowToId"] }, + // PartyFinderNotification + { LinkMacroPayloadType.Status, ["StatusId"] }, + { LinkMacroPayloadType.PartyFinder, ["ListingId", string.Empty, "WorldId"] }, + { LinkMacroPayloadType.AkatsukiNote, ["AkatsukiNoteId"] }, + { DalamudLinkType, ["CommandId", "Extra1", "Extra2", "ExtraString"] }, + }; + + private readonly Dictionary fixedExpressionNames = new() + { + { 1, ["Type0", "Type1", "WorldId"] }, + { 2, ["Type0", "Type1", "ClassJobId", "Level"] }, + { 3, ["Type0", "Type1", "TerritoryTypeId", "Instance & MapId", "RawX", "RawY", "RawZ", "PlaceNameIdOverride"] }, + { 4, ["Type0", "Type1", "ItemId", "Rarity", string.Empty, string.Empty, "Item Name"] }, + { 5, ["Type0", "Type1", "Sound Effect Id"] }, + { 6, ["Type0", "Type1", "ObjStrId"] }, + { 7, ["Type0", "Type1", "Text"] }, + { 8, ["Type0", "Type1", "Seconds"] }, + { 9, ["Type0", "Type1", string.Empty] }, + { 10, ["Type0", "Type1", "StatusId", "HasOverride", "NameOverride", "DescriptionOverride"] }, + { 11, ["Type0", "Type1", "ListingId", string.Empty, "WorldId", "CrossWorldFlag"] }, + { 12, ["Type0", "Type1", "QuestId", string.Empty, string.Empty, string.Empty, "QuestName"] }, + }; + + private readonly List entries = [ + new TextEntry(TextEntryType.String, "Welcome to "), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.String, "Dalamud"), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, " "), + ]; + + private SeStringParameter[]? localParameters = [Util.GetScmVersion()]; + private ReadOnlySeString input; + private ClientLanguage? language; + private int importSelectedSheetName; + private int importRowId; + private string[]? validImportSheetNames; + private float inputsWidth; + private float lastContentWidth; + + private enum TextEntryType + { + String, + Macro, + Fixed, + } + + /// + public string[]? CommandShortcuts { get; init; } = []; + + /// + public string DisplayName { get; init; } = "SeString Creator"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.language = Service.Get().EffectiveLanguage.ToClientLanguage(); + this.UpdateInputString(false); + this.Ready = true; + } + + /// + public void Draw() + { + var contentWidth = ImGui.GetContentRegionAvail().X; + + // split panels in the middle by default + if (this.inputsWidth == 0) + { + this.inputsWidth = contentWidth / 2f; + } + + // resize panels relative to the window size + if (contentWidth != this.lastContentWidth) + { + var originalWidth = this.lastContentWidth != 0 ? this.lastContentWidth : contentWidth; + this.inputsWidth = this.inputsWidth / originalWidth * contentWidth; + this.lastContentWidth = contentWidth; + } + + using var tabBar = ImRaii.TabBar("SeStringCreatorWidgetTabBar"); + if (!tabBar) return; + + this.DrawCreatorTab(contentWidth); + this.DrawGlobalParametersTab(); + } + + private void DrawCreatorTab(float contentWidth) + { + using var tab = ImRaii.TabItem("Creator"); + if (!tab) return; + + this.DrawControls(); + ImGui.Spacing(); + this.DrawInputs(); + + this.localParameters ??= this.GetLocalParameters(this.input.AsSpan(), []); + + var evaluated = Service.Get().Evaluate( + this.input.AsSpan(), + this.localParameters, + this.language); + + ImGui.SameLine(0, 0); + + ImGui.Button("###InputPanelResizer", new Vector2(4, -1)); + if (ImGui.IsItemActive()) + { + this.inputsWidth += ImGui.GetIO().MouseDelta.X; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeEW); + + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) + { + this.inputsWidth = contentWidth / 2f; + } + } + + ImGui.SameLine(); + + using var child = ImRaii.Child("Preview", new Vector2(ImGui.GetContentRegionAvail().X, -1)); + if (!child) return; + + if (this.localParameters!.Length != 0) + { + ImGui.Spacing(); + this.DrawParameters(); + } + + this.DrawPreview(evaluated); + + ImGui.Spacing(); + this.DrawPayloads(evaluated); + } + + private unsafe void DrawGlobalParametersTab() + { + using var tab = ImRaii.TabItem("Global Parameters"); + if (!tab) return; + + using var table = ImRaii.Table("GlobalParametersTable", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("ValuePtr", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(5, 1); + ImGui.TableHeadersRow(); + + var deque = RaptureTextModule.Instance()->GlobalParameters; + for (var i = 0u; i < deque.MySize; i++) + { + var item = deque[i]; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Id + ImGui.TextUnformatted(i.ToString()); + + ImGui.TableNextColumn(); // Type + ImGui.TextUnformatted(item.Type.ToString()); + + ImGui.TableNextColumn(); // ValuePtr + WidgetUtil.DrawCopyableText($"0x{(nint)item.ValuePtr:X}"); + + ImGui.TableNextColumn(); // Value + switch (item.Type) + { + case TextParameterType.Integer: + WidgetUtil.DrawCopyableText($"0x{item.IntValue:X}"); + ImGui.SameLine(); + WidgetUtil.DrawCopyableText(item.IntValue.ToString()); + break; + + case TextParameterType.ReferencedUtf8String: + if (item.ReferencedUtf8StringValue != null) + WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(item.ReferencedUtf8StringValue->Utf8String).ToString()); + else + ImGui.TextUnformatted("null"); + + break; + + case TextParameterType.String: + if (item.StringValue.Value != null) + WidgetUtil.DrawCopyableText(item.StringValue.ToString()); + else + ImGui.TextUnformatted("null"); + break; + } + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(i switch + { + 0 => "Player Name", + 1 => "Temp Player 1 Name", + 2 => "Temp Player 2 Name", + 3 => "Player Sex", + 4 => "Temp Player 1 Sex", + 5 => "Temp Player 2 Sex", + 6 => "Temp Player 1 Unk 1", + 7 => "Temp Player 2 Unk 1", + 10 => "Eorzea Time Hours", + 11 => "Eorzea Time Minutes", + 12 => "ColorSay", + 13 => "ColorShout", + 14 => "ColorTell", + 15 => "ColorParty", + 16 => "ColorAlliance", + 17 => "ColorLS1", + 18 => "ColorLS2", + 19 => "ColorLS3", + 20 => "ColorLS4", + 21 => "ColorLS5", + 22 => "ColorLS6", + 23 => "ColorLS7", + 24 => "ColorLS8", + 25 => "ColorFCompany", + 26 => "ColorPvPGroup", + 27 => "ColorPvPGroupAnnounce", + 28 => "ColorBeginner", + 29 => "ColorEmoteUser", + 30 => "ColorEmote", + 31 => "ColorYell", + 32 => "ColorFCAnnounce", + 33 => "ColorBeginnerAnnounce", + 34 => "ColorCWLS", + 35 => "ColorAttackSuccess", + 36 => "ColorAttackFailure", + 37 => "ColorAction", + 38 => "ColorItem", + 39 => "ColorCureGive", + 40 => "ColorBuffGive", + 41 => "ColorDebuffGive", + 42 => "ColorEcho", + 43 => "ColorSysMsg", + 51 => "Player Grand Company Rank (Maelstrom)", + 52 => "Player Grand Company Rank (Twin Adders)", + 53 => "Player Grand Company Rank (Immortal Flames)", + 54 => "Companion Name", + 55 => "Content Name", + 56 => "ColorSysBattle", + 57 => "ColorSysGathering", + 58 => "ColorSysErr", + 59 => "ColorNpcSay", + 60 => "ColorItemNotice", + 61 => "ColorGrowup", + 62 => "ColorLoot", + 63 => "ColorCraft", + 64 => "ColorGathering", + 65 => "Temp Player 1 Unk 2", + 66 => "Temp Player 2 Unk 2", + 67 => "Player ClassJobId", + 68 => "Player Level", + 70 => "Player Race", + 71 => "Player Synced Level", + 77 => "Client/Plattform?", + 78 => "Player BirthMonth", + 82 => "Datacenter Region", + 83 => "ColorCWLS2", + 84 => "ColorCWLS3", + 85 => "ColorCWLS4", + 86 => "ColorCWLS5", + 87 => "ColorCWLS6", + 88 => "ColorCWLS7", + 89 => "ColorCWLS8", + 91 => "Player Grand Company", + 92 => "TerritoryType Id", + 93 => "Is Soft Keyboard Enabled", + 94 => "LogSetRoleColor 1: LogColorRoleTank", + 95 => "LogSetRoleColor 2: LogColorRoleTank", + 96 => "LogSetRoleColor 1: LogColorRoleHealer", + 97 => "LogSetRoleColor 2: LogColorRoleHealer", + 98 => "LogSetRoleColor 1: LogColorRoleDPS", + 99 => "LogSetRoleColor 2: LogColorRoleDPS", + 100 => "LogSetRoleColor 1: LogColorOtherClass", + 101 => "LogSetRoleColor 2: LogColorOtherClass", + 102 => "Has Login Security Token", + _ => string.Empty, + }); + } + } + + private unsafe void DrawControls() + { + if (ImGui.Button("Add entry")) + { + this.entries.Add(new(TextEntryType.String, string.Empty)); + } + + ImGui.SameLine(); + + if (ImGui.Button("Add from Sheet")) + { + ImGui.OpenPopup("AddFromSheetPopup"); + } + + this.DrawAddFromSheetPopup(); + + ImGui.SameLine(); + + if (ImGui.Button("Print")) + { + var output = Utf8String.CreateEmpty(); + var temp = Utf8String.CreateEmpty(); + var temp2 = Utf8String.CreateEmpty(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + output->ConcatCStr(entry.Message); + break; + + case TextEntryType.Macro: + temp->Clear(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(temp, entry.Message); + output->Append(temp); + break; + + case TextEntryType.Fixed: + temp->SetString(entry.Message); + temp2->Clear(); + + RaptureTextModule.Instance()->TextModule.ProcessMacroCode(temp2, temp->StringPtr); + var out1 = PronounModule.Instance()->ProcessString(temp2, true); + var out2 = PronounModule.Instance()->ProcessString(out1, false); + + output->Append(out2); + break; + } + } + + RaptureLogModule.Instance()->PrintString(output->StringPtr); + temp2->Dtor(true); + temp->Dtor(true); + output->Dtor(true); + } + + ImGui.SameLine(); + + if (ImGui.Button("Print Evaluated")) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + RaptureLogModule.Instance()->PrintString(Service.Get().Evaluate(sb.ToReadOnlySeString())); + } + + if (this.entries.Count != 0) + { + ImGui.SameLine(); + + if (ImGui.Button("Copy MacroString")) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + ImGui.SetClipboardText(sb.ToReadOnlySeString().ToString()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Clear entries")) + { + this.entries.Clear(); + this.UpdateInputString(); + } + } + + var raptureTextModule = RaptureTextModule.Instance(); + if (!raptureTextModule->MacroEncoder.EncoderError.IsEmpty) + { + ImGui.SameLine(); + ImGui.TextUnformatted(raptureTextModule->MacroEncoder.EncoderError.ToString()); // TODO: EncoderError doesn't clear + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(90 * ImGuiHelpers.GlobalScale); + using (var dropdown = ImRaii.Combo("##Language", this.language.ToString() ?? "Language...")) + { + if (dropdown) + { + var values = Enum.GetValues().OrderBy((ClientLanguage lang) => lang.ToString()); + foreach (var value in values) + { + if (ImGui.Selectable(Enum.GetName(value), value == this.language)) + { + this.language = value; + this.UpdateInputString(); + } + } + } + } + } + + private void DrawAddFromSheetPopup() + { + using var popup = ImRaii.Popup("AddFromSheetPopup"); + if (!popup) return; + + var dataManager = Service.Get(); + + this.validImportSheetNames ??= dataManager.Excel.SheetNames.Where(sheetName => + { + try + { + var headerFile = dataManager.GameData.GetFile($"exd/{sheetName}.exh"); + if (headerFile.Header.Variant != ExcelVariant.Default) + return false; + + var sheet = dataManager.Excel.GetSheet(Language.English, sheetName); + return sheet.Columns.Any(col => col.Type == ExcelColumnDataType.String); + } + catch + { + return false; + } + }).OrderBy(sheetName => sheetName, StringComparer.InvariantCulture).ToArray(); + + var sheetChanged = ImGui.Combo("Sheet Name", ref this.importSelectedSheetName, this.validImportSheetNames, this.validImportSheetNames.Length); + + try + { + var sheet = dataManager.Excel.GetSheet(this.language?.ToLumina() ?? Language.English, this.validImportSheetNames[this.importSelectedSheetName]); + var minRowId = (int)sheet.FirstOrDefault().RowId; + var maxRowId = (int)sheet.LastOrDefault().RowId; + + var rowIdChanged = ImGui.InputInt("RowId", ref this.importRowId, 1, 10); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.TextUnformatted($"(Range: {minRowId} - {maxRowId})"); + + if (sheetChanged || rowIdChanged) + { + if (sheetChanged || this.importRowId < minRowId) + this.importRowId = minRowId; + + if (this.importRowId > maxRowId) + this.importRowId = maxRowId; + } + + if (!sheet.TryGetRow((uint)this.importRowId, out var row)) + { + ImGui.TextColored(new Vector4(1, 0, 0, 1), "Row not found"); + return; + } + + ImGui.TextUnformatted("Select string to add:"); + + using var table = ImRaii.Table("StringSelectionTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Column", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < sheet.Columns.Count; i++) + { + var column = sheet.Columns[i]; + if (column.Type != ExcelColumnDataType.String) + continue; + + var value = row.ReadStringColumn(i); + if (value.IsEmpty) + continue; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(i.ToString()); + + ImGui.TableNextColumn(); + if (ImGui.Selectable($"{value.ToString().Truncate(100)}###Column{i}")) + { + foreach (var payload in value) + { + switch (payload.Type) + { + case ReadOnlySePayloadType.Text: + this.entries.Add(new(TextEntryType.String, Encoding.UTF8.GetString(payload.Body.Span))); + break; + + case ReadOnlySePayloadType.Macro: + this.entries.Add(new(TextEntryType.Macro, payload.ToString())); + break; + } + } + + this.UpdateInputString(); + ImGui.CloseCurrentPopup(); + } + } + } + catch (Exception e) + { + ImGui.TextUnformatted(e.Message); + return; + } + } + + private unsafe void DrawInputs() + { + using var child = ImRaii.Child("Inputs", new Vector2(this.inputsWidth, -1)); + if (!child) return; + + using var table = ImRaii.Table("StringMakerTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + var arrowUpButtonSize = this.GetIconButtonSize(FontAwesomeIcon.ArrowUp); + var arrowDownButtonSize = this.GetIconButtonSize(FontAwesomeIcon.ArrowDown); + var trashButtonSize = this.GetIconButtonSize(FontAwesomeIcon.Trash); + var terminalButtonSize = this.GetIconButtonSize(FontAwesomeIcon.Terminal); + + var entryToRemove = -1; + var entryToMoveUp = -1; + var entryToMoveDown = -1; + var updateString = false; + + for (var i = 0; i < this.entries.Count; i++) + { + var key = $"##Entry{i}"; + var entry = this.entries[i]; + + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); // Type + var type = (int)entry.Type; + ImGui.SetNextItemWidth(-1); + if (ImGui.Combo($"##Type{i}", ref type, ["String", "Macro", "Fixed"], 3)) + { + entry.Type = (TextEntryType)type; + updateString |= true; + } + + ImGui.TableNextColumn(); // Text + var message = entry.Message; + ImGui.SetNextItemWidth(-1); + if (ImGui.InputText($"##{i}_Message", ref message, 255)) + { + entry.Message = message; + updateString |= true; + } + + ImGui.TableNextColumn(); // Actions + + if (i > 0) + { + if (this.IconButton(key + "_Up", FontAwesomeIcon.ArrowUp, "Move up")) + { + entryToMoveUp = i; + } + } + else + { + ImGui.Dummy(arrowUpButtonSize); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + if (i < this.entries.Count - 1) + { + if (this.IconButton(key + "_Down", FontAwesomeIcon.ArrowDown, "Move down")) + { + entryToMoveDown = i; + } + } + else + { + ImGui.Dummy(arrowDownButtonSize); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + if (ImGui.IsKeyDown(ImGuiKey.LeftShift) || ImGui.IsKeyDown(ImGuiKey.RightShift)) + { + if (this.IconButton(key + "_Delete", FontAwesomeIcon.Trash, "Delete")) + { + entryToRemove = i; + } + } + else + { + this.IconButton( + key + "_Delete", + FontAwesomeIcon.Trash, + "Delete with shift", + disabled: true); + } + } + + table.Dispose(); + + if (entryToMoveUp != -1) + { + var removedItem = this.entries[entryToMoveUp]; + this.entries.RemoveAt(entryToMoveUp); + this.entries.Insert(entryToMoveUp - 1, removedItem); + updateString |= true; + } + + if (entryToMoveDown != -1) + { + var removedItem = this.entries[entryToMoveDown]; + this.entries.RemoveAt(entryToMoveDown); + this.entries.Insert(entryToMoveDown + 1, removedItem); + updateString |= true; + } + + if (entryToRemove != -1) + { + this.entries.RemoveAt(entryToRemove); + updateString |= true; + } + + if (updateString) + { + this.UpdateInputString(); + } + } + + private unsafe void UpdateInputString(bool resetLocalParameters = true) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + this.input = sb.ToReadOnlySeString(); + + if (resetLocalParameters) + this.localParameters = null; + } + + private void DrawPreview(ReadOnlySeString str) + { + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00); + using var node = ImRaii.TreeNode("Preview", ImGuiTreeNodeFlags.DefaultOpen); + nodeColor.Pop(); + if (!node) return; + + ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeight())); + ImGui.SameLine(0, 0); + ImGuiHelpers.SeStringWrapped(str); + } + + private void DrawParameters() + { + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00); + using var node = ImRaii.TreeNode("Parameters", ImGuiTreeNodeFlags.DefaultOpen); + nodeColor.Pop(); + if (!node) return; + + for (var i = 0; i < this.localParameters!.Length; i++) + { + if (this.localParameters[i].IsString) + { + var str = this.localParameters[i].StringValue.ExtractText(); + if (ImGui.InputText($"lstr({i + 1})", ref str, 255)) + { + this.localParameters[i] = new(str); + } + } + else + { + var num = (int)this.localParameters[i].UIntValue; + if (ImGui.InputInt($"lnum({i + 1})", ref num)) + { + this.localParameters[i] = new((uint)num); + } + } + } + } + + private void DrawPayloads(ReadOnlySeString evaluated) + { + using (var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00)) + using (var node = ImRaii.TreeNode("Payloads", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + { + nodeColor.Pop(); + if (node) this.DrawSeString("payloads", this.input.AsSpan(), treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + } + + if (this.input.Equals(evaluated)) + return; + + using (var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00)) + using (var node = ImRaii.TreeNode("Payloads (Evaluated)", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + { + nodeColor.Pop(); + if (node) this.DrawSeString("payloads-evaluated", evaluated.AsSpan(), treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + } + } + + private void DrawSeString(string id, ReadOnlySeStringSpan rosss, bool asTreeNode = false, bool renderSeString = false, int depth = 0, ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags.None) + { + using var seStringId = ImRaii.PushId(id); + + if (rosss.PayloadCount == 0) + { + ImGui.Dummy(Vector2.Zero); + return; + } + + using var node = asTreeNode ? this.SeStringTreeNode(id, rosss) : null; + if (asTreeNode && !node!) return; + + if (!asTreeNode && renderSeString) + { + ImGuiHelpers.SeStringWrapped(rosss, new() + { + ForceEdgeColor = true, + }); + } + + var payloadIdx = -1; + foreach (var payload in rosss) + { + payloadIdx++; + using var payloadId = ImRaii.PushId(payloadIdx); + + var preview = payload.Type.ToString(); + if (payload.Type == ReadOnlySePayloadType.Macro) + preview += $": {payload.MacroCode}"; + + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF); + using var payloadNode = ImRaii.TreeNode($"[{payloadIdx}] {preview}", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + nodeColor.Pop(); + if (!payloadNode) continue; + + using var table = ImRaii.Table($"##Payload{payloadIdx}Table", 2); + if (!table) return; + + ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Tree", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(payload.Type == ReadOnlySePayloadType.Text ? "Text" : "ToString()"); + ImGui.TableNextColumn(); + var text = payload.ToString(); + WidgetUtil.DrawCopyableText($"\"{text}\"", text); + + if (payload.Type != ReadOnlySePayloadType.Macro) + continue; + + if (payload.ExpressionCount > 0) + { + var exprIdx = 0; + uint? subType = null; + uint? fixedType = null; + + if (payload.MacroCode == MacroCode.Link && payload.TryGetExpression(out var linkExpr1) && linkExpr1.TryGetUInt(out var linkExpr1Val)) + { + subType = linkExpr1Val; + } + else if (payload.MacroCode == MacroCode.Fixed && payload.TryGetExpression(out var fixedTypeExpr, out var linkExpr2) && fixedTypeExpr.TryGetUInt(out var fixedTypeVal) && linkExpr2.TryGetUInt(out var linkExpr2Val)) + { + subType = linkExpr2Val; + fixedType = fixedTypeVal; + } + + foreach (var expr in payload) + { + using var exprId = ImRaii.PushId(exprIdx); + + this.DrawExpression(payload.MacroCode, subType, fixedType, exprIdx++, expr); + } + } + } + } + + private unsafe void DrawExpression(MacroCode macroCode, uint? subType, uint? fixedType, int exprIdx, ReadOnlySeExpressionSpan expr) + { + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + var expressionName = this.GetExpressionName(macroCode, subType, exprIdx, expr); + ImGui.TextUnformatted($"[{exprIdx}] " + (string.IsNullOrEmpty(expressionName) ? $"Expr {exprIdx}" : expressionName)); + + ImGui.TableNextColumn(); + + if (expr.Body.IsEmpty) + { + ImGui.TextUnformatted("(?)"); + return; + } + + if (expr.TryGetUInt(out var u32)) + { + if (macroCode is MacroCode.Icon or MacroCode.Icon2 && exprIdx == 0) + { + var iconId = u32; + + if (macroCode == MacroCode.Icon2) + { + var iconMapping = RaptureAtkModule.Instance()->AtkFontManager.Icon2RemapTable; + for (var i = 0; i < 30; i++) + { + if (iconMapping[i].IconId == iconId) + { + iconId = iconMapping[i].RemappedIconId; + break; + } + } + } + + var builder = LSeStringBuilder.SharedPool.Get(); + builder.AppendIcon(iconId); + ImGuiHelpers.SeStringWrapped(builder.ToArray()); + LSeStringBuilder.SharedPool.Return(builder); + + ImGui.SameLine(); + } + + WidgetUtil.DrawCopyableText(u32.ToString()); + ImGui.SameLine(); + WidgetUtil.DrawCopyableText($"0x{u32:X}"); + + if (macroCode == MacroCode.Link && exprIdx == 0) + { + var name = subType != null && (LinkMacroPayloadType)subType == DalamudLinkType + ? "Dalamud" + : Enum.GetName((LinkMacroPayloadType)u32); + + if (!string.IsNullOrEmpty(name)) + { + ImGui.SameLine(); + ImGui.TextUnformatted(name); + } + } + + if (macroCode is MacroCode.JaNoun or MacroCode.EnNoun or MacroCode.DeNoun or MacroCode.FrNoun && exprIdx == 1) + { + var language = macroCode switch + { + MacroCode.JaNoun => ClientLanguage.Japanese, + MacroCode.DeNoun => ClientLanguage.German, + MacroCode.FrNoun => ClientLanguage.French, + _ => ClientLanguage.English, + }; + var articleTypeEnumType = language switch + { + ClientLanguage.Japanese => typeof(JapaneseArticleType), + ClientLanguage.German => typeof(GermanArticleType), + ClientLanguage.French => typeof(FrenchArticleType), + _ => typeof(EnglishArticleType), + }; + ImGui.SameLine(); + ImGui.TextUnformatted(Enum.GetName(articleTypeEnumType, u32)); + } + + if (macroCode is MacroCode.DeNoun && exprIdx == 4 && u32 is >= 0 and <= 3) + { + ImGui.SameLine(); + ImGui.TextUnformatted(NounProcessorWidget.GermanCases[u32]); + } + + if (macroCode is MacroCode.Fixed && subType != null && fixedType != null && fixedType is 100 or 200 && subType == 5 && exprIdx == 2) + { + ImGui.SameLine(); + if (ImGui.SmallButton("Play")) + { + UIGlobals.PlayChatSoundEffect(u32 + 1); + } + } + + if (macroCode is MacroCode.Link && subType != null && exprIdx == 1) + { + var dataManager = Service.Get(); + + switch ((LinkMacroPayloadType)subType) + { + case LinkMacroPayloadType.Item when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var itemRow): + ImGui.SameLine(); + ImGui.TextUnformatted(itemRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Quest when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var questRow): + ImGui.SameLine(); + ImGui.TextUnformatted(questRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Achievement when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var achievementRow): + ImGui.SameLine(); + ImGui.TextUnformatted(achievementRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.HowTo when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var howToRow): + ImGui.SameLine(); + ImGui.TextUnformatted(howToRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Status when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var statusRow): + ImGui.SameLine(); + ImGui.TextUnformatted(statusRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.AkatsukiNote when + dataManager.GetSubrowExcelSheet(this.language).TryGetRow(u32, out var akatsukiNoteRow) && + dataManager.GetExcelSheet(this.language).TryGetRow((uint)akatsukiNoteRow[0].Unknown2, out var akatsukiNoteStringRow): + ImGui.SameLine(); + ImGui.TextUnformatted(akatsukiNoteStringRow.Unknown0.ExtractText()); + break; + } + } + + return; + } + + if (expr.TryGetString(out var s)) + { + this.DrawSeString("Preview", s, treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen); + return; + } + + if (expr.TryGetPlaceholderExpression(out var exprType)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted(nativeName); + return; + } + + ImGui.TextUnformatted($"?x{exprType:X02}"); + return; + } + + if (expr.TryGetParameterExpression(out exprType, out var e1)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted($"{nativeName}({e1.ToString()})"); + return; + } + + throw new InvalidOperationException("All native names must be defined for unary expressions."); + } + + if (expr.TryGetBinaryExpression(out exprType, out e1, out var e2)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted($"{e1.ToString()} {nativeName} {e2.ToString()}"); + return; + } + + throw new InvalidOperationException("All native names must be defined for binary expressions."); + } + + var sb = new StringBuilder(); + sb.EnsureCapacity(1 + 3 * expr.Body.Length); + sb.Append($"({expr.Body[0]:X02}"); + for (var i = 1; i < expr.Body.Length; i++) + sb.Append($" {expr.Body[i]:X02}"); + sb.Append(')'); + ImGui.TextUnformatted(sb.ToString()); + } + + private string GetExpressionName(MacroCode macroCode, uint? subType, int idx, ReadOnlySeExpressionSpan expr) + { + if (this.expressionNames.TryGetValue(macroCode, out var names) && idx < names.Length) + return names[idx]; + + if (macroCode == MacroCode.Switch) + return $"Case {idx - 1}"; + + if (macroCode == MacroCode.Link && subType != null && this.linkExpressionNames.TryGetValue((LinkMacroPayloadType)subType, out var linkNames) && idx - 1 < linkNames.Length) + return linkNames[idx - 1]; + + if (macroCode == MacroCode.Fixed && subType != null && this.fixedExpressionNames.TryGetValue((uint)subType, out var fixedNames) && idx < fixedNames.Length) + return fixedNames[idx]; + + if (macroCode == MacroCode.Link && idx == 4) + return "Copy String"; + + return string.Empty; + } + + private SeStringParameter[] GetLocalParameters(ReadOnlySeStringSpan rosss, Dictionary? parameters) + { + parameters ??= []; + + void ProcessString(ReadOnlySeStringSpan rosss) + { + foreach (var payload in rosss) + { + foreach (var expression in payload) + { + ProcessExpression(expression); + } + } + } + + void ProcessExpression(ReadOnlySeExpressionSpan expression) + { + if (expression.TryGetString(out var exprString)) + { + ProcessString(exprString); + return; + } + + if (expression.TryGetBinaryExpression(out var expressionType, out var operand1, out var operand2)) + { + ProcessExpression(operand1); + ProcessExpression(operand2); + return; + } + + if (expression.TryGetParameterExpression(out expressionType, out var operand)) + { + if (!operand.TryGetUInt(out var index)) + return; + + if (parameters.ContainsKey(index)) + return; + + if (expressionType == (int)ExpressionType.LocalNumber) + { + parameters[index] = new SeStringParameter(0); + return; + } + else if (expressionType == (int)ExpressionType.LocalString) + { + parameters[index] = new SeStringParameter(string.Empty); + return; + } + } + } + + ProcessString(rosss); + + if (parameters.Count > 0) + { + var last = parameters.OrderBy(x => x.Key).Last(); + + if (parameters.Count != last.Key) + { + // fill missing local parameter slots, so we can go off the array index in SeStringContext + + for (var i = 1u; i <= last.Key; i++) + { + if (!parameters.ContainsKey(i)) + parameters[i] = new SeStringParameter(0); + } + } + } + + return parameters.OrderBy(x => x.Key).Select(x => x.Value).ToArray(); + } + + private ImRaii.IEndObject SeStringTreeNode(string id, ReadOnlySeStringSpan previewText, uint color = 0xFF00FFFF, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None) + { + using var titleColor = ImRaii.PushColor(ImGuiCol.Text, color); + var node = ImRaii.TreeNode("##" + id, flags); + ImGui.SameLine(); + ImGuiHelpers.SeStringWrapped(previewText, new() + { + ForceEdgeColor = true, + WrapWidth = 9999, + }); + return node; + } + + private bool IconButton(string key, FontAwesomeIcon icon, string tooltip, Vector2 size = default, bool disabled = false, bool active = false) + { + using var iconFont = ImRaii.PushFont(UiBuilder.IconFont); + if (!key.StartsWith("##")) key = "##" + key; + + var disposables = new List(); + + if (disabled) + { + disposables.Add(ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled])); + disposables.Add(ImRaii.PushColor(ImGuiCol.ButtonActive, ImGui.GetStyle().Colors[(int)ImGuiCol.Button])); + disposables.Add(ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGui.GetStyle().Colors[(int)ImGuiCol.Button])); + } + else if (active) + { + disposables.Add(ImRaii.PushColor(ImGuiCol.Button, ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonActive])); + } + + var pressed = ImGui.Button(icon.ToIconString() + key, size); + + foreach (var disposable in disposables) + disposable.Dispose(); + + iconFont?.Dispose(); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltip); + ImGui.EndTooltip(); + } + + return pressed; + } + + private Vector2 GetIconButtonSize(FontAwesomeIcon icon) + { + using var iconFont = ImRaii.PushFont(UiBuilder.IconFont); + return ImGui.CalcTextSize(icon.ToIconString()) + ImGui.GetStyle().FramePadding * 2; + } + + private class TextEntry(TextEntryType type, string text) + { + public string Message { get; set; } = text; + + public TextEntryType Type { get; set; } = type; + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs new file mode 100644 index 000000000..323c9ce62 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -0,0 +1,465 @@ +using System.Numerics; +using System.Text; + +using Dalamud.Data; +using Dalamud.Game.Gui; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Storage.Assets; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using Lumina.Excel.Sheets; +using Lumina.Text; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying Addon Data. +/// +internal unsafe class SeStringRendererTestWidget : IDataWindowWidget +{ + private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; + private ImVectorWrapper testStringBuffer; + private string testString = string.Empty; + private ReadOnlySeString? logkind; + private SeStringDrawParams style; + private bool interactable; + private bool useEntity; + private bool alignToFramePadding; + + /// + public string DisplayName { get; init; } = "SeStringRenderer Test"; + + /// + public string[]? CommandShortcuts { get; init; } + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.style = new() { GetEntity = this.GetEntity }; + this.logkind = null; + this.testString = string.Empty; + this.interactable = this.useEntity = true; + this.alignToFramePadding = false; + this.Ready = true; + } + + /// + public void Draw() + { + var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ?? ImGui.GetColorU32(ImGuiCol.Text)); + if (ImGui.ColorEdit4("Color", ref t2)) + this.style.Color = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ?? 0xFF000000u); + if (ImGui.ColorEdit4("Edge Color", ref t2)) + this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2); + + ImGui.SameLine(); + var t = this.style.ForceEdgeColor; + if (ImGui.Checkbox("Forced", ref t)) + this.style.ForceEdgeColor = t; + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ?? 0xFF000000u); + if (ImGui.ColorEdit4("Shadow Color", ref t2)) + this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered)); + if (ImGui.ColorEdit4("Link Hover Color", ref t2)) + this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive)); + if (ImGui.ColorEdit4("Link Active Color", ref t2)) + this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2); + + var t3 = this.style.LineHeight ?? 1f; + if (ImGui.DragFloat("Line Height", ref t3, 0.01f, 0.4f, 3f, "%.02f")) + this.style.LineHeight = t3; + + t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha; + if (ImGui.DragFloat("Opacity", ref t3, 0.005f, 0f, 1f, "%.02f")) + this.style.Opacity = t3; + + t3 = this.style.EdgeStrength ?? 0.25f; + if (ImGui.DragFloat("Edge Strength", ref t3, 0.005f, 0f, 1f, "%.02f")) + this.style.EdgeStrength = t3; + + t = this.style.Edge; + if (ImGui.Checkbox("Edge", ref t)) + this.style.Edge = t; + + ImGui.SameLine(); + t = this.style.Bold; + if (ImGui.Checkbox("Bold", ref t)) + this.style.Bold = t; + + ImGui.SameLine(); + t = this.style.Italic; + if (ImGui.Checkbox("Italic", ref t)) + this.style.Italic = t; + + ImGui.SameLine(); + t = this.style.Shadow; + if (ImGui.Checkbox("Shadow", ref t)) + this.style.Shadow = t; + + ImGui.SameLine(); + var t4 = this.style.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; + ImGui.PushItemWidth(ImGui.CalcTextSize("WWWWWWWWWWWWWW").X); + if (ImGui.Combo("##theme", ref t4, ThemeNames, ThemeNames.Length)) + this.style.ThemeIndex = t4; + + ImGui.SameLine(); + t = this.style.LinkUnderlineThickness > 0f; + if (ImGui.Checkbox("Link Underline", ref t)) + this.style.LinkUnderlineThickness = t ? 1f : 0f; + + ImGui.SameLine(); + t = this.style.WrapWidth is null; + if (ImGui.Checkbox("Word Wrap", ref t)) + this.style.WrapWidth = t ? null : float.PositiveInfinity; + + t = this.interactable; + if (ImGui.Checkbox("Interactable", ref t)) + this.interactable = t; + + ImGui.SameLine(); + t = this.useEntity; + if (ImGui.Checkbox("Use Entity Replacements", ref t)) + this.useEntity = t; + + ImGui.SameLine(); + t = this.alignToFramePadding; + if (ImGui.Checkbox("Align to Frame Padding", ref t)) + this.alignToFramePadding = t; + + if (ImGui.CollapsingHeader("LogKind Preview")) + { + if (this.logkind is null) + { + var tt = new SeStringBuilder(); + foreach (var uc in Service.Get().GetExcelSheet()) + { + var ucsp = uc.Format.AsSpan(); + if (ucsp.IsEmpty) + continue; + + tt.Append($"#{uc.RowId}: "); + foreach (var p in ucsp.GetOffsetEnumerator()) + { + if (p.Payload.Type == ReadOnlySePayloadType.Macro && p.Payload.MacroCode == MacroCode.String) + { + tt.Append("Text"u8); + continue; + } + + tt.Append(new ReadOnlySeStringSpan(ucsp.Data.Slice(p.Offset, p.Payload.EnvelopeByteLength))); + } + + tt.BeginMacro(MacroCode.NewLine).EndMacro(); + } + + this.logkind = tt.ToReadOnlySeString(); + } + + ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style); + } + + if (ImGui.CollapsingHeader("Addon Table")) + { + if (ImGui.BeginTable("Addon Sheet", 3)) + { + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("0000000").X); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn( + "Misc", + ImGuiTableColumnFlags.WidthFixed, + ImGui.CalcTextSize("AAAAAAAAAAAAAAAAA").X); + ImGui.TableHeadersRow(); + + var addon = Service.GetNullable()?.GetExcelSheet() ?? + throw new InvalidOperationException("Addon sheet not loaded."); + + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + clipper.Begin(addon.Count); + while (clipper.Step()) + { + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + var row = addon.GetRowAt(i); + + ImGui.TableNextRow(); + ImGui.PushID(i); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"{row.RowId}"); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.SeStringWrapped(row.Text, this.style); + + ImGui.TableNextColumn(); + if (ImGui.Button("Print to Chat")) + Service.Get().Print(row.Text.ToDalamudString()); + + ImGui.PopID(); + } + } + + clipper.Destroy(); + ImGui.EndTable(); + } + } + + if (ImGui.Button("Reset Text") || this.testStringBuffer.IsDisposed) + { + this.testStringBuffer.Dispose(); + this.testStringBuffer = ImVectorWrapper.CreateFromSpan( + "\n\nLorem ipsum dolor sit amet, conse<->ctetur adipi<->scing elit. Maece<->nas digni<->ssim sem at inter<->dum ferme<->ntum. Praes<->ent ferme<->ntum conva<->llis velit sit amet hendr<->erit. Sed eu nibh magna. Integ<->er nec lacus in velit porta euism<->od sed et lacus. Sed non mauri<->s venen<->atis, matti<->s metus in, aliqu<->et dolor. Aliqu<->am erat volut<->pat. Nulla venen<->atis velit ac susci<->pit euism<->od. suspe<->ndisse maxim<->us viver<->ra dui id dapib<->us. Nam torto<->r dolor, eleme<->ntum quis orci id, pulvi<->nar fring<->illa quam. Pelle<->ntesque laore<->et viver<->ra torto<->r eget matti<->s. Vesti<->bulum eget porta ante, a molli<->s nulla. Curab<->itur a ligul<->a leo. Aliqu<->am volut<->pat sagit<->tis dapib<->us.\n\nFusce iacul<->is aliqu<->am mi, eget portt<->itor arcu solli<->citudin conse<->ctetur. suspe<->ndisse aliqu<->am commo<->do tinci<->dunt. Duis sed posue<->re tellu<->s. Sed phare<->tra ex vel torto<->r pelle<->ntesque, inter<->dum porta sapie<->n digni<->ssim. Queue Dun Scait<->h. Cras aliqu<->et at nulla quis moles<->tie. Vesti<->bulum eu ligul<->a sapie<->n. Curab<->itur digni<->ssim feugi<->at volut<->pat.\n\nVesti<->bulum condi<->mentum laore<->et rhonc<->us. Vivam<->us et accum<->san purus. Curab<->itur inter<->dum vel ligul<->a ac euism<->od. Donec sed nisl digni<->ssim est tinci<->dunt iacul<->is. Praes<->ent hendr<->erit pelle<->ntesque nisl, quis lacin<->ia arcu dictu<->m sit amet. Aliqu<->am variu<->s lectu<->s vel mauri<->s imper<->diet posue<->re. Ut gravi<->da non sapie<->n sed hendr<->erit.\n\nProin quis dapib<->us odio. Cras sagit<->tis non sem sed porta. Donec iacul<->is est ligul<->a, digni<->ssim aliqu<->et augue matti<->s vitae. Duis ullam<->corper tempu<->s odio, non vesti<->bulum est biben<->dum quis. In purus elit, vehic<->ula tinci<->dunt dictu<->m in, aucto<->r nec enim. Curab<->itur a nisi in leo matti<->s pelle<->ntesque id nec sem. Nunc vel ultri<->ces nisl. Nam congu<->e vulpu<->tate males<->uada. Aenea<->n vesti<->bulum mauri<->s leo, sit amet iacul<->is est imper<->diet ut. Phase<->llus nec lobor<->tis lacus, sit amet scele<->risque purus. Nam id lacin<->ia velit, euism<->od feugi<->at dui. Nulla sodal<->es odio ligul<->a, et hendr<->erit torto<->r maxim<->us eu. Donec et sem eu magna volut<->pat accum<->san non ut lectu<->s.\n\nVivam<->us susci<->pit ferme<->ntum gravi<->da. Cras nec conse<->ctetur magna. Vivam<->us ante massa, accum<->san sit amet felis et, tempu<->s iacul<->is ipsum. Pelle<->ntesque vitae nisi accum<->san, venen<->atis lectu<->s aucto<->r, aliqu<->et liber<->o. Nam nec imper<->diet justo. Vivam<->us ut vehic<->ula turpi<->s. Nunc lobor<->tis pelle<->ntesque urna, sit amet solli<->citudin nibh fauci<->bus in. Curab<->itur eu lobor<->tis lacus. Donec eu hendr<->erit diam, vitae cursu<->s odio. Cras eget scele<->risque mi.\n\n· Testing aaaaalink aaaaabbbb.\n· Open example.com\n· Open example.org\n\n\n\ncolortype502,edgecolortype503\n\nOpacity values are ignored:\nopacity FF\nopacity 80\nopacity 00\nTest 1\nTest 2\nWithout edgeShadowWith edge"u8, + minCapacity: 65536); + this.testString = string.Empty; + } + + ImGui.SameLine(); + + if (ImGui.Button("Print to Chat Log")) + { + Service.Get().Print( + Game.Text.SeStringHandling.SeString.Parse( + Service.Get().CompileAndCache(this.testString).Data.Span)); + } + + ImGuiHelpers.ScaledDummy(3); + ImGuiHelpers.CompileSeStringWrapped( + "Optional features implemented for the following test input:
" + + "· line breaks are automatically replaced to \\
.
" + + "· Dalamud will display Dalamud.
" + + "· White will display White.
" + + "· DefaultIcon will display DefaultIcon.
" + + "· DisabledIcon will display DisabledIcon.
" + + "· OutdatedInstallableIcon will display OutdatedInstallableIcon.
" + + "· TroubleIcon will display TroubleIcon.
" + + "· DevPluginIcon will display DevPluginIcon.
" + + "· UpdateIcon will display UpdateIcon.
" + + "· InstalledIcon will display InstalledIcon.
" + + "· ThirdIcon will display ThirdIcon.
" + + "· ThirdInstalledIcon will display ThirdInstalledIcon.
" + + "· ChangelogApiBumpIcon will display ChangelogApiBumpIcon.
" + + "· icon(5) will display icon(5). This is different from \\(5)>.
" + + "· tex(ui/loadingimage/-nowloading_base25_hr1.tex) will display tex(ui/loadingimage/-nowloading_base25_hr1.tex).", + this.style); + ImGuiHelpers.ScaledDummy(3); + + fixed (byte* labelPtr = "Test Input"u8) + { + if (ImGuiNative.igInputTextMultiline( + labelPtr, + this.testStringBuffer.Data, + (uint)this.testStringBuffer.Capacity, + new(ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight() * 3), + 0, + null, + null) != 0) + { + var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0); + if (len + 4 >= this.testStringBuffer.Capacity) + this.testStringBuffer.EnsureCapacityExponential(len + 4); + if (len < this.testStringBuffer.Capacity) + { + this.testStringBuffer.LengthUnsafe = len; + this.testStringBuffer.StorageSpan[len] = default; + } + + this.testString = string.Empty; + } + } + + if (this.testString == string.Empty && this.testStringBuffer.Length != 0) + this.testString = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan); + + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + + if (this.interactable) + { + if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is + { + InteractedPayload: { } payload, InteractedPayloadOffset: var offset, + InteractedPayloadEnvelope: var envelope, + Clicked: var clicked + }) + { + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}"); + if (clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp) + Util.OpenLink(dlp.ExtraString); + } + else + { + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.CompileSeStringWrapped("If a link is hovered, it will be displayed here.", this.style); + } + } + else + { + ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style); + } + + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.CompileSeStringWrapped("Extra line for alignment testing.", this.style); + } + + private SeStringReplacementEntity GetEntity(scoped in SeStringDrawState state, int byteOffset) + { + if (!this.useEntity) + return default; + if (state.Span[byteOffset..].StartsWith("Dalamud"u8)) + return new(7, new(state.FontSize, state.FontSize), DrawDalamud); + if (state.Span[byteOffset..].StartsWith("White"u8)) + return new(5, new(state.FontSize, state.FontSize), DrawWhite); + if (state.Span[byteOffset..].StartsWith("DefaultIcon"u8)) + return new(11, new(state.FontSize, state.FontSize), DrawDefaultIcon); + if (state.Span[byteOffset..].StartsWith("DisabledIcon"u8)) + return new(12, new(state.FontSize, state.FontSize), DrawDisabledIcon); + if (state.Span[byteOffset..].StartsWith("OutdatedInstallableIcon"u8)) + return new(23, new(state.FontSize, state.FontSize), DrawOutdatedInstallableIcon); + if (state.Span[byteOffset..].StartsWith("TroubleIcon"u8)) + return new(11, new(state.FontSize, state.FontSize), DrawTroubleIcon); + if (state.Span[byteOffset..].StartsWith("DevPluginIcon"u8)) + return new(13, new(state.FontSize, state.FontSize), DrawDevPluginIcon); + if (state.Span[byteOffset..].StartsWith("UpdateIcon"u8)) + return new(10, new(state.FontSize, state.FontSize), DrawUpdateIcon); + if (state.Span[byteOffset..].StartsWith("ThirdIcon"u8)) + return new(9, new(state.FontSize, state.FontSize), DrawThirdIcon); + if (state.Span[byteOffset..].StartsWith("ThirdInstalledIcon"u8)) + return new(18, new(state.FontSize, state.FontSize), DrawThirdInstalledIcon); + if (state.Span[byteOffset..].StartsWith("ChangelogApiBumpIcon"u8)) + return new(20, new(state.FontSize, state.FontSize), DrawChangelogApiBumpIcon); + if (state.Span[byteOffset..].StartsWith("InstalledIcon"u8)) + return new(13, new(state.FontSize, state.FontSize), DrawInstalledIcon); + if (state.Span[byteOffset..].StartsWith("tex("u8)) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + var tex = Service + .Get() + .Shared + .GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)])) + .GetWrapOrEmpty(); + return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawTexture); + } + + if (state.Span[byteOffset..].StartsWith("icon("u8)) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + if (int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed)) + { + var tex = Service + .Get() + .Shared + .GetFromGameIcon(parsed) + .GetWrapOrEmpty(); + return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawIcon); + } + } + + return default; + + static void DrawTexture(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + var tex = Service + .Get() + .Shared + .GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)])) + .GetWrapOrEmpty(); + state.Draw( + tex.ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + tex.Size * (state.FontSize / tex.Size.Y), + Vector2.Zero, + Vector2.One); + } + + static void DrawIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + if (!int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed)) + return; + var tex = Service + .Get() + .Shared + .GetFromGameIcon(parsed) + .GetWrapOrEmpty(); + state.Draw( + tex.ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + tex.Size * (state.FontSize / tex.Size.Y), + Vector2.Zero, + Vector2.One); + } + + static void DrawAsset(scoped in SeStringDrawState state, Vector2 offset, DalamudAsset asset) => + state.Draw( + Service.Get().GetDalamudTextureWrap(asset).ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + new(state.FontSize, state.FontSize), + Vector2.Zero, + Vector2.One); + + static void DrawDalamud(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.LogoSmall); + + static void DrawWhite(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.White4X4); + + static void DrawDefaultIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DefaultIcon); + + static void DrawDisabledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DisabledIcon); + + static void DrawOutdatedInstallableIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.OutdatedInstallableIcon); + + static void DrawTroubleIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.TroubleIcon); + + static void DrawDevPluginIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DevPluginIcon); + + static void DrawUpdateIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.UpdateIcon); + + static void DrawInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.InstalledIcon); + + static void DrawThirdIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ThirdIcon); + + static void DrawThirdInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ThirdInstalledIcon); + + static void DrawChangelogApiBumpIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ChangelogApiBumpIcon); + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index 0d2b744b4..07b2d01ff 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Interface.Components; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.TextureWraps; @@ -457,7 +455,7 @@ internal class TexWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - this.TextCopiable($"0x{wrap.ResourceAddress:X}", true, true); + this.TextColumnCopiable($"0x{wrap.ResourceAddress:X}", true, true); ImGui.TableNextColumn(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) @@ -476,24 +474,24 @@ internal class TexWidget : IDataWindowWidget } ImGui.TableNextColumn(); - this.TextCopiable(wrap.Name, false, true); + this.TextColumnCopiable(wrap.Name, false, true); ImGui.TableNextColumn(); - this.TextCopiable($"{wrap.Width:n0}", true, true); + this.TextColumnCopiable($"{wrap.Width:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable($"{wrap.Height:n0}", true, true); + this.TextColumnCopiable($"{wrap.Height:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString(), false, true); + this.TextColumnCopiable(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString(), false, true); ImGui.TableNextColumn(); var bytes = wrap.RawSpecs.EstimatedBytes; - this.TextCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true); + this.TextColumnCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true); ImGui.TableNextColumn(); lock (wrap.OwnerPlugins) - this.TextCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true); + this.TextColumnCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true); ImGui.PopID(); } @@ -570,16 +568,16 @@ internal class TexWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - this.TextCopiable($"{texture.InstanceIdForDebug:n0}", true, true); + this.TextColumnCopiable($"{texture.InstanceIdForDebug:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(texture.SourcePathForDebug, false, true); + this.TextColumnCopiable(texture.SourcePathForDebug, false, true); ImGui.TableNextColumn(); - this.TextCopiable($"{texture.RefCountForDebug:n0}", true, true); + this.TextColumnCopiable($"{texture.RefCountForDebug:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true); + this.TextColumnCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true); ImGui.TableNextColumn(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) @@ -864,47 +862,6 @@ internal class TexWidget : IDataWindowWidget ImGuiHelpers.ScaledDummy(10); } - private void TextCopiable(string s, bool alignRight, bool framepad) - { - var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); - if (framepad) - ImGui.AlignTextToFramePadding(); - if (alignRight) - { - var width = ImGui.CalcTextSize(s).X; - var xoff = ImGui.GetColumnWidth() - width; - offset.X += xoff; - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xoff); - ImGui.TextUnformatted(s); - } - else - { - ImGui.TextUnformatted(s); - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); - var vp = ImGui.GetWindowViewport(); - var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X; - ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue)); - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(wrx); - ImGui.TextWrapped(s.Replace("%", "%%")); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText(s); - Service.Get().AddNotification( - $"Copied {ImGui.TableGetColumnName()} to clipboard.", - this.DisplayName, - NotificationType.Success); - } - } - private record TextureEntry( IDalamudTextureWrap? SharedResource = null, Task? Api10 = null, diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index 1f8b4c62c..45f1ad715 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -1,22 +1,28 @@ -using System.Numerics; +using System.Buffers.Binary; +using System.Numerics; +using System.Text; using Dalamud.Data; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.ImGuiNotification.Internal; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; + +using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying all UI Colors from Lumina. /// -internal class UIColorWidget : IDataWindowWidget +internal class UiColorWidget : IDataWindowWidget { /// - public string[]? CommandShortcuts { get; init; } = { "uicolor" }; - + public string[]? CommandShortcuts { get; init; } = ["uicolor"]; + /// - public string DisplayName { get; init; } = "UIColor"; + public string DisplayName { get; init; } = "UIColor"; /// public bool Ready { get; set; } @@ -28,37 +34,172 @@ internal class UIColorWidget : IDataWindowWidget } /// - public void Draw() + public unsafe void Draw() { - var colorSheet = Service.Get().GetExcelSheet(); - if (colorSheet is null) return; + var colors = Service.GetNullable()?.GetExcelSheet() + ?? throw new InvalidOperationException("UIColor sheet not loaded."); - foreach (var color in colorSheet) + Service.Get().CompileAndDrawWrapped( + "· Color notation is #" + + "RR" + + "GG" + + "BB.
" + + "· Click on a color to copy the color code.
" + + "· Hover on a color to preview the text with edge, when the next color has been used together."); + if (!ImGui.BeginTable("UIColor", 5)) + return; + + ImGui.TableSetupScrollFreeze(0, 1); + var rowidw = ImGui.CalcTextSize("9999999").X; + var colorw = ImGui.CalcTextSize("#999999").X; + colorw = Math.Max(colorw, ImGui.CalcTextSize("#AAAAAA").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#BBBBBB").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#CCCCCC").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#DDDDDD").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#EEEEEE").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#FFFFFF").X); + colorw += ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X; + ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, rowidw); + ImGui.TableSetupColumn("Dark", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Light", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Classic FF", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Clear Blue", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableHeadersRow(); + + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + clipper.Begin(colors.Count, ImGui.GetFrameHeightWithSpacing()); + while (clipper.Step()) { - this.DrawUiColor(color); - } - } - - private void DrawUiColor(UIColor color) - { - ImGui.Text($"[{color.RowId:D3}] "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.Unknown2), $"Unknown2 "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.UIForeground), "UIForeground "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.Unknown3), "Unknown3 "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.UIGlow), "UIGlow"); - } - - private Vector4 ConvertToVector4(uint color) - { - var r = (byte)(color >> 24); - var g = (byte)(color >> 16); - var b = (byte)(color >> 8); - var a = (byte)color; + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + var row = colors.GetRowAt(i); + UIColor? adjacentRow = null; + if (i + 1 < colors.Count) + { + var adjRow = colors.GetRowAt(i + 1); + if (adjRow.RowId == row.RowId + 1) + { + adjacentRow = adjRow; + } + } - return new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); + var id = row.RowId; + + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"{id}"); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_dark"); + if (this.DrawColorColumn(row.Dark) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.Dark, adjacentRow.Value.Dark); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_light"); + if (this.DrawColorColumn(row.Light) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.Light, adjacentRow.Value.Light); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_classic"); + if (this.DrawColorColumn(row.ClassicFF) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.ClassicFF, adjacentRow.Value.ClassicFF); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_blue"); + if (this.DrawColorColumn(row.ClearBlue) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.ClearBlue, adjacentRow.Value.ClearBlue); + ImGui.PopID(); + } + } + + clipper.Destroy(); + ImGui.EndTable(); + } + + private static void DrawEdgePreview(uint id, uint sheetColor, uint sheetColor2) + { + ImGui.BeginTooltip(); + Span buf = stackalloc byte[256]; + var ptr = 0; + ptr += Encoding.UTF8.GetBytes("", buf[ptr..]); + Service.Get().Draw( + buf[..ptr], + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + WrapWidth = float.PositiveInfinity, + }); + + ptr = 0; + ptr += Encoding.UTF8.GetBytes("", buf[ptr..]); + Service.Get().Draw( + buf[..ptr], + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + WrapWidth = float.PositiveInfinity, + }); + ImGui.EndTooltip(); + } + + private bool DrawColorColumn(uint sheetColor) + { + sheetColor = BinaryPrimitives.ReverseEndianness(sheetColor); + var rgbtext = $"#{sheetColor & 0xFF:X02}{(sheetColor >> 8) & 0xFF:X02}{(sheetColor >> 16) & 0xFF:X02}"; + var size = new Vector2(ImGui.GetFrameHeight()); + size.X += ImGui.CalcTextSize(rgbtext).X + ImGui.GetStyle().FramePadding.X; + + var off = ImGui.GetCursorScreenPos(); + ImGui.GetWindowDrawList().AddRectFilled( + off, + off + new Vector2(size.Y), + sheetColor | 0xFF000000u); + ImGui.GetWindowDrawList().AddText( + off + ImGui.GetStyle().FramePadding + new Vector2(size.Y, 0), + ImGui.GetColorU32(ImGuiCol.Text), + rgbtext); + + if (ImGui.InvisibleButton("##copy", size)) + { + ImGui.SetClipboardText(rgbtext); + Service.Get().AddNotification( + new() + { + Content = $"Copied \"{rgbtext}\".", + Title = this.DisplayName, + Type = NotificationType.Success, + }); + } + + return ImGui.IsItemHovered(); } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs new file mode 100644 index 000000000..ec39e38f1 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs @@ -0,0 +1,553 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Memory; + +using ImGuiNET; + +using Lumina.Data.Files; +using Lumina.Data.Parsing.Uld; + +using static Lumina.Data.Parsing.Uld.Keyframes; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Displays the full data of the selected ULD element. +/// +internal class UldWidget : IDataWindowWidget +{ + // ULD styles can be hardcoded for now as they don't add new ones regularly. Can later try and find where to load these from in the game EXE. + private static readonly string[] ThemeDisplayNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; + private static readonly string[] ThemeBasePaths = ["ui/uld/", "ui/uld/light/", "ui/uld/third/", "ui/uld/fourth/"]; + + // 48 8D 15 ?? ?? ?? ?? is the part of the signatures that contain the string location offset + // 48 = 64 bit register prefix + // 8D = LEA instruction + // 15 = register to store offset in (RDX in this case as Component::GUI::AtkUnitBase_LoadUldByName name component is loaded from RDX) + // ?? ?? ?? ?? = offset to string location + private static readonly (string Sig, nint Offset)[] UldSigLocations = + [ + ("45 33 C0 48 8D 15 ?? ?? ?? ?? 48 8B CF 48 8B 5C 24 30 48 83 C4 20 5F E9 ?? ?? ?? ??", 6), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CE 48 8B 5C ?? ?? 48 8B 74 ?? ?? 48 83 C4 20 5F E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB 48 83 C4 20 5B E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B0 01 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3) + ]; + + private CancellationTokenSource? cts; + private Task? uldNamesTask; + + private int selectedUld; + private int selectedFrameData; + private int selectedTimeline; + private int selectedParts; + private int selectedTheme; + private Task? selectedUldFileTask; + + /// + public string[]? CommandShortcuts { get; init; } = ["uld"]; + + /// + public string DisplayName { get; init; } = "ULD"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.cts?.Cancel(); + ClearTask(ref this.uldNamesTask); + this.uldNamesTask = null; + this.cts = new(); + + this.Ready = true; + this.selectedUld = this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; + this.selectedTheme = 0; + this.selectedUldFileTask = null; + } + + /// + public void Draw() + { + string[] uldNames; + var ct = (this.cts ??= new()).Token; + switch (this.uldNamesTask ??= ParseUldStringsAsync(ct)) + { + case { IsCompletedSuccessfully: true } t: + uldNames = t.Result; + break; + case { Exception: { } loadException }: + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, loadException.ToString()); + return; + case { IsCanceled: true }: + ClearTask(ref this.uldNamesTask); + goto default; + default: + ImGui.TextUnformatted("Loading..."); + return; + } + + var selectedUldPrev = this.selectedUld; + ImGui.Combo("##selectUld", ref this.selectedUld, uldNames, uldNames.Length); + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectUldLeft", FontAwesomeIcon.AngleLeft)) + this.selectedUld = ((this.selectedUld + uldNames.Length) - 1) % uldNames.Length; + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectUldRight", FontAwesomeIcon.AngleRight)) + this.selectedUld = (this.selectedUld + 1) % uldNames.Length; + ImGui.SameLine(); + ImGui.TextUnformatted("Select ULD File"); + if (selectedUldPrev != this.selectedUld) + { + // reset selected parts when changing ULD + this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; + ClearTask(ref this.selectedUldFileTask); + } + + ImGui.Combo("##selectTheme", ref this.selectedTheme, ThemeDisplayNames, ThemeDisplayNames.Length); + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectThemeLeft", FontAwesomeIcon.AngleLeft)) + this.selectedTheme = ((this.selectedTheme + ThemeDisplayNames.Length) - 1) % ThemeDisplayNames.Length; + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectThemeRight", FontAwesomeIcon.AngleRight)) + this.selectedTheme = (this.selectedTheme + 1) % ThemeDisplayNames.Length; + ImGui.SameLine(); + ImGui.TextUnformatted("Select Theme"); + + var dataManager = Service.Get(); + var textureManager = Service.Get(); + + UldFile uld; + switch (this.selectedUldFileTask ??= + dataManager.GetFileAsync($"ui/uld/{uldNames[this.selectedUld]}.uld", ct)) + { + case { IsCompletedSuccessfully: true }: + uld = this.selectedUldFileTask.Result; + break; + case { Exception: { } loadException }: + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Failed to load ULD file.\n{loadException}"); + return; + case { IsCanceled: true }: + this.selectedUldFileTask = null; + goto default; + default: + ImGui.TextUnformatted("Loading..."); + return; + } + + if (ImGui.CollapsingHeader("Texture Entries")) + { + if (ForceNullable(uld.AssetData) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.AssetData)} is not populated."); + } + else if (ImGui.BeginTable("##uldTextureEntries", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) + { + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000").X); + ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview___").X); + ImGui.TableHeadersRow(); + + foreach (var textureEntry in uld.AssetData) + this.DrawTextureEntry(textureEntry, textureManager); + + ImGui.EndTable(); + } + } + + if (ImGui.CollapsingHeader("Timeline##TimelineCollapsingHeader")) + { + if (ForceNullable(uld.Timelines) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.Timelines)} is not populated."); + } + else if (uld.Timelines.Length == 0) + { + ImGui.TextUnformatted("No entry exists."); + } + else + { + ImGui.SliderInt("Timeline##TimelineSlider", ref this.selectedTimeline, 0, uld.Timelines.Length - 1); + this.DrawTimelines(uld.Timelines[this.selectedTimeline]); + } + } + + if (ImGui.CollapsingHeader("Parts##PartsCollapsingHeader")) + { + if (ForceNullable(uld.Parts) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.Parts)} is not populated."); + } + else if (uld.Parts.Length == 0) + { + ImGui.TextUnformatted("No entry exists."); + } + else + { + ImGui.SliderInt("Parts##PartsSlider", ref this.selectedParts, 0, uld.Parts.Length - 1); + this.DrawParts(uld.Parts[this.selectedParts], uld.AssetData, textureManager); + } + } + + return; + static T? ForceNullable(T smth) => smth; + } + + /// + /// Gets all known ULD locations in the game based on a few signatures. + /// + /// Uld locations. + private static Task ParseUldStringsAsync(CancellationToken cancellationToken) => + Task.Run( + () => + { + // game contains possibly around 1500 ULD files but current sigs only find less than that due to how they are used + var locations = new List(1000); + var sigScanner = new SigScanner(Process.GetCurrentProcess().MainModule!); + foreach (var (uldSig, strLocOffset) in UldSigLocations) + { + foreach (var ea in sigScanner.ScanAllText(uldSig, cancellationToken)) + { + var strLoc = ea + strLocOffset; + // offset instruction is always 4 bytes so need to read as uint and cast to nint for offset calculation + var offset = (nint)MemoryHelper.Read(strLoc); + // strings are always stored as c strings and relative from end of offset instruction + var str = MemoryHelper.ReadStringNullTerminated(strLoc + 4 + offset); + locations.Add(str); + } + } + + return locations.Distinct().Order().ToArray(); + }, + cancellationToken); + + private static void ClearTask(ref Task? task) + { + try + { + task?.Wait(); + } + catch + { + // ignore + } + + task = null; + } + + private static string GetStringNullTerminated(ReadOnlySpan text) + { + var index = text.IndexOf((char)0); + return index == -1 ? new(text) : new(text[..index]); + } + + private string ToThemedPath(string path) => + ThemeBasePaths[this.selectedTheme] + path[ThemeBasePaths[0].Length..]; + + private void DrawTextureEntry(UldRoot.TextureEntry textureEntry, TextureManager textureManager) + { + var path = GetStringNullTerminated(textureEntry.Path); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(textureEntry.Id.ToString()); + + ImGui.TableNextColumn(); + this.TextColumnCopiable(path, false, false); + + ImGui.TableNextColumn(); + if (string.IsNullOrWhiteSpace(path)) + return; + + ImGui.TextUnformatted("Preview"); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + + var texturePath = GetStringNullTerminated(textureEntry.Path); + ImGui.TextUnformatted($"Base path at {texturePath}:"); + if (textureManager.Shared.GetFromGame(texturePath).TryGetWrap(out var wrap, out var e)) + ImGui.Image(wrap.ImGuiHandle, wrap.Size); + else if (e is not null) + ImGui.TextUnformatted(e.ToString()); + + if (this.selectedTheme != 0) + { + var texturePathThemed = this.ToThemedPath(texturePath); + ImGui.TextUnformatted($"Themed path at {texturePathThemed}:"); + if (textureManager.Shared.GetFromGame(texturePathThemed).TryGetWrap(out wrap, out e)) + ImGui.Image(wrap.ImGuiHandle, wrap.Size); + else if (e is not null) + ImGui.TextUnformatted(e.ToString()); + } + + ImGui.EndTooltip(); + } + } + + private void DrawTimelines(UldRoot.Timeline timeline) + { + ImGui.SliderInt("FrameData", ref this.selectedFrameData, 0, timeline.FrameData.Length - 1); + var frameData = timeline.FrameData[this.selectedFrameData]; + ImGui.TextUnformatted($"FrameInfo: {frameData.StartFrame} -> {frameData.EndFrame}"); + ImGui.Indent(); + foreach (var frameDataKeyGroup in frameData.KeyGroups) + { + ImGui.TextUnformatted($"{frameDataKeyGroup.Usage:G} {frameDataKeyGroup.Type:G}"); + foreach (var keyframe in frameDataKeyGroup.Frames) + this.DrawTimelineKeyGroupFrame(keyframe); + } + + ImGui.Unindent(); + } + + private void DrawTimelineKeyGroupFrame(IKeyframe frame) + { + switch (frame) + { + case BaseKeyframeData baseKeyframeData: + ImGui.TextUnformatted( + $"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}"); + break; + case Float1Keyframe float1Keyframe: + this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {float1Keyframe.Value}"); + break; + case Float2Keyframe float2Keyframe: + this.DrawTimelineKeyGroupFrame(float2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {float2Keyframe.Value[0]} | Value2: {float2Keyframe.Value[1]}"); + break; + case Float3Keyframe float3Keyframe: + this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}"); + break; + case SByte1Keyframe sbyte1Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {sbyte1Keyframe.Value}"); + break; + case SByte2Keyframe sbyte2Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {sbyte2Keyframe.Value[0]} | Value2: {sbyte2Keyframe.Value[1]}"); + break; + case SByte3Keyframe sbyte3Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}"); + break; + case Byte1Keyframe byte1Keyframe: + this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {byte1Keyframe.Value}"); + break; + case Byte2Keyframe byte2Keyframe: + this.DrawTimelineKeyGroupFrame(byte2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {byte2Keyframe.Value[0]} | Value2: {byte2Keyframe.Value[1]}"); + break; + case Byte3Keyframe byte3Keyframe: + this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}"); + break; + case Short1Keyframe short1Keyframe: + this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {short1Keyframe.Value}"); + break; + case Short2Keyframe short2Keyframe: + this.DrawTimelineKeyGroupFrame(short2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {short2Keyframe.Value[0]} | Value2: {short2Keyframe.Value[1]}"); + break; + case Short3Keyframe short3Keyframe: + this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}"); + break; + case UShort1Keyframe ushort1Keyframe: + this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {ushort1Keyframe.Value}"); + break; + case UShort2Keyframe ushort2Keyframe: + this.DrawTimelineKeyGroupFrame(ushort2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {ushort2Keyframe.Value[0]} | Value2: {ushort2Keyframe.Value[1]}"); + break; + case UShort3Keyframe ushort3Keyframe: + this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}"); + break; + case Int1Keyframe int1Keyframe: + this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {int1Keyframe.Value}"); + break; + case Int2Keyframe int2Keyframe: + this.DrawTimelineKeyGroupFrame(int2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {int2Keyframe.Value[0]} | Value2: {int2Keyframe.Value[1]}"); + break; + case Int3Keyframe int3Keyframe: + this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}"); + break; + case UInt1Keyframe uint1Keyframe: + this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {uint1Keyframe.Value}"); + break; + case UInt2Keyframe uint2Keyframe: + this.DrawTimelineKeyGroupFrame(uint2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {uint2Keyframe.Value[0]} | Value2: {uint2Keyframe.Value[1]}"); + break; + case UInt3Keyframe uint3Keyframe: + this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}"); + break; + case Bool1Keyframe bool1Keyframe: + this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {bool1Keyframe.Value}"); + break; + case Bool2Keyframe bool2Keyframe: + this.DrawTimelineKeyGroupFrame(bool2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {bool2Keyframe.Value[0]} | Value2: {bool2Keyframe.Value[1]}"); + break; + case Bool3Keyframe bool3Keyframe: + this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}"); + break; + case ColorKeyframe colorKeyframe: + this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}"); + break; + case LabelKeyframe labelKeyframe: + this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted( + $" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}"); + break; + } + } + + private void DrawParts( + UldRoot.PartsData partsData, + UldRoot.TextureEntry[] textureEntries, + TextureManager textureManager) + { + for (var index = 0; index < partsData.Parts.Length; index++) + { + ImGui.TextUnformatted($"Index: {index}"); + var partsDataPart = partsData.Parts[index]; + ImGui.SameLine(); + + char[]? path = null; + foreach (var textureEntry in textureEntries) + { + if (textureEntry.Id != partsDataPart.TextureId) + continue; + path = textureEntry.Path; + break; + } + + if (path is null) + { + ImGui.TextUnformatted($"Could not find texture for id {partsDataPart.TextureId}"); + continue; + } + + var texturePath = GetStringNullTerminated(path); + if (string.IsNullOrWhiteSpace(texturePath)) + { + ImGui.TextUnformatted("Texture path is empty."); + continue; + } + + var texturePathThemed = this.ToThemedPath(texturePath); + if (textureManager.Shared.GetFromGame(texturePathThemed).TryGetWrap(out var wrap, out var e)) + { + texturePath = texturePathThemed; + } + else + { + // try loading from default location if not found in selected style + if (!textureManager.Shared.GetFromGame(texturePath).TryGetWrap(out wrap, out var e2)) + { + // neither the supposedly original path nor themed path had a file we could load. + if (e is not null && e2 is not null) + { + ImGui.TextUnformatted($"{texturePathThemed}: {e}\n{texturePath}: {e2}"); + continue; + } + } + } + + var partSize = new Vector2(partsDataPart.W, partsDataPart.H); + if (wrap is null) + { + ImGuiHelpers.ScaledDummy(partSize); + } + else + { + var uv0 = new Vector2(partsDataPart.U, partsDataPart.V); + var uv1 = uv0 + partSize; + ImGui.Image(wrap.ImGuiHandle, partSize * ImGuiHelpers.GlobalScale, uv0 / wrap.Size, uv1 / wrap.Size); + } + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(texturePath); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy:"); + ImGui.TextUnformatted(texturePath); + ImGui.EndTooltip(); + } + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs new file mode 100644 index 000000000..019d743bc --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; +using System.IO; + +using Dalamud.Configuration.Internal; +using Dalamud.Storage; +using ImGuiNET; +using Serilog; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying configuration info. +/// +internal class VfsWidget : IDataWindowWidget +{ + private int numBytes = 1024; + private int reps = 1; + + /// + public string[]? CommandShortcuts { get; init; } = { "vfs" }; + + /// + public string DisplayName { get; init; } = "VFS Performance"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.Ready = true; + } + + /// + public void Draw() + { + var service = Service.Get(); + var dalamud = Service.Get(); + + ImGui.InputInt("Num bytes", ref this.numBytes); + ImGui.InputInt("Reps", ref this.reps); + + var path = Path.Combine(dalamud.StartInfo.WorkingDirectory!, "test.bin"); + + if (ImGui.Button("Write")) + { + Log.Information("=== WRITING ==="); + var data = new byte[this.numBytes]; + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + service.WriteAllBytes(path, data); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + + if (ImGui.Button("Read")) + { + Log.Information("=== READING ==="); + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + service.ReadAllBytes(path); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + + if (ImGui.Button("Test Config")) + { + var config = Service.Get(); + + Log.Information("=== READING ==="); + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + config.ForceSave(); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 40753a20d..dfd37431c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -98,6 +98,7 @@ internal class PluginInstallerWindow : Window, IDisposable private bool deletePluginConfigWarningModalDrawing = true; private bool deletePluginConfigWarningModalOnNextFrame = false; + private bool deletePluginConfigWarningModalExplainTesting = false; private string deletePluginConfigWarningModalPluginName = string.Empty; private TaskCompletionSource? deletePluginConfigWarningModalTaskCompletionSource; @@ -119,7 +120,7 @@ internal class PluginInstallerWindow : Window, IDisposable private List pluginListUpdatable = new(); private bool hasDevPlugins = false; private bool hasHiddenPlugins = false; - + private string searchText = string.Empty; private bool isSearchTextPrefilled = false; @@ -136,7 +137,7 @@ internal class PluginInstallerWindow : Window, IDisposable private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown; private string verifiedCheckmarkHoveredPlugin = string.Empty; - + private string? staleDalamudNewVersion = null; /// @@ -214,18 +215,19 @@ internal class PluginInstallerWindow : Window, IDisposable ProfileOrNot, SearchScore, } - - [Flags] + + [Flags] private enum PluginHeaderFlags { None = 0, IsThirdParty = 1 << 0, HasTrouble = 1 << 1, UpdateAvailable = 1 << 2, - IsNew = 1 << 3, - IsInstallableOutdated = 1 << 4, - IsOrphan = 1 << 5, - IsTesting = 1 << 6, + MainRepoCrossUpdate = 1 << 3, + IsNew = 1 << 4, + IsInstallableOutdated = 1 << 5, + IsOrphan = 1 << 6, + IsTesting = 1 << 7, } private enum InstalledPluginListFilter @@ -235,7 +237,7 @@ internal class PluginInstallerWindow : Window, IDisposable Updateable, Dev, } - + private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress || this.updateStatus == OperationStatus.InProgress || this.enableDisableStatus == OperationStatus.InProgress; @@ -281,6 +283,7 @@ internal class PluginInstallerWindow : Window, IDisposable var pluginManager = Service.Get(); _ = pluginManager.ReloadPluginMastersAsync(); + Service.Get().ScanDevPlugins(); if (!this.isSearchTextPrefilled) this.searchText = string.Empty; this.sortKind = PluginSortKind.Alphabetical; @@ -303,7 +306,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (!t.IsCompletedSuccessfully) return; - + var versionInfo = t.Result; if (versionInfo.AssemblyVersion != Util.GetScmVersion() && versionInfo.Track != "release" && @@ -412,7 +415,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (!task.IsFaulted && !task.IsCanceled) return true; - + var newErrorMessage = state as string; if (task.Exception != null) @@ -437,7 +440,7 @@ internal class PluginInstallerWindow : Window, IDisposable } } } - + if (task.IsCanceled) Log.Error("A task was cancelled"); @@ -445,14 +448,14 @@ internal class PluginInstallerWindow : Window, IDisposable return false; } - + private static void EnsureHaveTestingOptIn(IPluginManifest manifest) { var configuration = Service.Get(); - + if (configuration.PluginTestingOptIns.Any(x => x.InternalName == manifest.InternalName)) return; - + configuration.PluginTestingOptIns.Add(new PluginTestingOptIn(manifest.InternalName)); configuration.QueueSave(); } @@ -489,7 +492,7 @@ internal class PluginInstallerWindow : Window, IDisposable throw new ArgumentOutOfRangeException(nameof(kind), kind, null); } } - + private void DrawProgressOverlay() { var pluginManager = Service.Get(); @@ -753,8 +756,9 @@ internal class PluginInstallerWindow : Window, IDisposable Service.Get().OpenSettings(); } - // If any dev plugins are installed, allow a shortcut for the /xldev menu item - if (this.hasDevPlugins) + // If any dev plugin locations exist, allow a shortcut for the /xldev menu item + var hasDevPluginLocations = configuration.DevPluginLoadLocations.Count > 0; + if (hasDevPluginLocations) { ImGui.SameLine(); if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) @@ -801,7 +805,7 @@ internal class PluginInstallerWindow : Window, IDisposable { this.updateStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; - + var toUpdate = this.pluginListUpdatable .Where(x => x.InstalledPlugin.IsWantedByAnyProfile) .ToList(); @@ -972,10 +976,11 @@ internal class PluginInstallerWindow : Window, IDisposable } } - private Task ShowDeletePluginConfigWarningModal(string pluginName) + private Task ShowDeletePluginConfigWarningModal(string pluginName, bool explainTesting = false) { this.deletePluginConfigWarningModalOnNextFrame = true; this.deletePluginConfigWarningModalPluginName = pluginName; + this.deletePluginConfigWarningModalExplainTesting = explainTesting; this.deletePluginConfigWarningModalTaskCompletionSource = new TaskCompletionSource(); return this.deletePluginConfigWarningModalTaskCompletionSource.Task; } @@ -986,6 +991,13 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.BeginPopupModal(modalTitle, ref this.deletePluginConfigWarningModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) { + if (this.deletePluginConfigWarningModalExplainTesting) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); + ImGui.Text(Locs.DeletePluginConfigWarningModal_ExplainTesting()); + ImGui.PopStyleColor(); + } + ImGui.Text(Locs.DeletePluginConfigWarningModal_Body(this.deletePluginConfigWarningModalPluginName)); ImGui.Spacing(); @@ -1243,7 +1255,6 @@ internal class PluginInstallerWindow : Window, IDisposable if (filteredAvailableManifests.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); return proxies; } @@ -1255,7 +1266,7 @@ internal class PluginInstallerWindow : Window, IDisposable plugin.Manifest.RepoUrl == availableManifest.RepoUrl && !plugin.IsDev); - // We "consumed" this plugin from the pile and remove it. + // We "consumed" this plugin from the pile and remove it. if (plugin != null) { installedPlugins.Remove(plugin); @@ -1287,7 +1298,7 @@ internal class PluginInstallerWindow : Window, IDisposable return isHidden; return !isHidden; } - + // Filter out plugins that are not hidden proxies = proxies.Where(IsProxyHidden).ToList(); @@ -1295,6 +1306,23 @@ internal class PluginInstallerWindow : Window, IDisposable } #pragma warning restore SA1201 +#pragma warning disable SA1204 + private static void DrawMutedBodyText(string text, float paddingBefore, float paddingAfter) +#pragma warning restore SA1204 + { + ImGuiHelpers.ScaledDummy(paddingBefore); + + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) + { + foreach (var line in text.Split('\n')) + { + ImGuiHelpers.CenteredText(line); + } + } + + ImGuiHelpers.ScaledDummy(paddingAfter); + } + private void DrawAvailablePluginList() { var i = 0; @@ -1319,14 +1347,32 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopID(); } - + // Reset the category to "All" if we're on the "Hidden" category and there are no hidden plugins (we removed the last one) if (i == 0 && this.categoryManager.CurrentCategoryKind == PluginCategoryManager.CategoryKind.Hidden) { this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All; } + + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) + { + var hasSearch = !this.searchText.IsNullOrEmpty(); + + if (i == 0 && !hasSearch) + { + DrawMutedBodyText(Locs.TabBody_NoPluginsAvailable, 60, 20); + } + else if (i == 0 && hasSearch) + { + DrawMutedBodyText(Locs.TabBody_SearchNoMatching, 60, 20); + } + else if (hasSearch) + { + DrawMutedBodyText(Locs.TabBody_NoMoreResultsFor(this.searchText), 20, 20); + } + } } - + private void DrawInstalledPluginList(InstalledPluginListFilter filter) { var pluginList = this.pluginListInstalled; @@ -1334,7 +1380,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (pluginList.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + DrawMutedBodyText(Locs.TabBody_SearchNoInstalled, 60, 20); return; } @@ -1344,7 +1390,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (filteredList.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + DrawMutedBodyText(Locs.TabBody_SearchNoMatching, 60, 20); return; } @@ -1354,7 +1400,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (filter == InstalledPluginListFilter.Testing && !manager.HasTestingOptIn(plugin.Manifest)) continue; - + // Find applicable update and manifest, if we have them AvailablePluginUpdate? update = null; RemotePluginManifest? remoteManifest = null; @@ -1374,11 +1420,11 @@ internal class PluginInstallerWindow : Window, IDisposable { continue; } - + this.DrawInstalledPlugin(plugin, i++, remoteManifest, update); drewAny = true; } - + if (!drewAny) { var text = filter switch @@ -1389,7 +1435,7 @@ internal class PluginInstallerWindow : Window, IDisposable InstalledPluginListFilter.Dev => Locs.TabBody_NoPluginsDev, _ => throw new ArgumentException(null, nameof(filter)), }; - + ImGuiHelpers.ScaledDummy(60); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) @@ -1400,6 +1446,11 @@ internal class PluginInstallerWindow : Window, IDisposable } } } + else if (!this.searchText.IsNullOrEmpty()) + { + DrawMutedBodyText(Locs.TabBody_NoMoreResultsFor(this.searchText), 20, 20); + ImGuiHelpers.ScaledDummy(20); + } } private void DrawPluginCategories() @@ -1474,6 +1525,9 @@ internal class PluginInstallerWindow : Window, IDisposable if (!isCurrent) { this.categoryManager.CurrentGroupKind = groupInfo.GroupKind; + + // Reset search text when switching groups + this.searchText = string.Empty; } ImGui.Indent(); @@ -1481,7 +1535,7 @@ internal class PluginInstallerWindow : Window, IDisposable foreach (var categoryKind in groupInfo.Categories) { var categoryInfo = this.categoryManager.CategoryList.First(x => x.CategoryKind == categoryKind); - + switch (categoryInfo.Condition) { case PluginCategoryManager.CategoryInfo.AppearCondition.None: @@ -1540,7 +1594,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopFont(); ImGui.PopStyleColor(); } - + void DrawLinesCentered(string text) { var lines = text.Split('\n'); @@ -1549,7 +1603,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.CenteredText(line); } } - + var pm = Service.Get(); if (pm.SafeMode) { @@ -1614,7 +1668,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.IsTesting: this.DrawInstalledPluginList(InstalledPluginListFilter.Testing); break; - + case PluginCategoryManager.CategoryKind.UpdateablePlugins: this.DrawInstalledPluginList(InstalledPluginListFilter.Updateable); break; @@ -1622,7 +1676,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.PluginProfiles: this.profileManagerWidget.Draw(); break; - + default: ImGui.TextUnformatted("You found a secret category. Please feel a sense of pride and accomplishment."); break; @@ -1643,7 +1697,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.PluginChangelogs: this.DrawChangelogList(false, true); break; - + default: ImGui.TextUnformatted("You found a quiet category. Please don't wake it up."); break; @@ -1970,9 +2024,9 @@ internal class PluginInstallerWindow : Window, IDisposable var sectionSize = ImGuiHelpers.GlobalScale * 66; var tapeCursor = ImGui.GetCursorPos(); - + ImGui.Separator(); - + var startCursor = ImGui.GetCursorPos(); if (flags.HasFlag(PluginHeaderFlags.IsTesting)) @@ -1983,9 +2037,9 @@ internal class PluginInstallerWindow : Window, IDisposable var windowPos = ImGui.GetWindowPos(); var scroll = new Vector2(ImGui.GetScrollX(), ImGui.GetScrollY()); - + var adjustedPosition = windowPos + position - scroll; - + var yellow = ImGui.ColorConvertFloat4ToU32(new Vector4(1.0f, 0.9f, 0.0f, 0.10f)); var numStripes = (int)(size.X / stripeWidth) + (int)(size.Y / skewAmount) + 1; // +1 to cover partial stripe @@ -1995,19 +2049,19 @@ internal class PluginInstallerWindow : Window, IDisposable var x1 = x0 + stripeWidth; var y0 = adjustedPosition.Y; var y1 = y0 + size.Y; - + var p0 = new Vector2(x0, y0); var p1 = new Vector2(x1, y0); var p2 = new Vector2(x1 - skewAmount, y1); var p3 = new Vector2(x0 - skewAmount, y1); - + if (i % 2 != 0) continue; - + wdl.AddQuadFilled(p0, p1, p2, p3, yellow); } } - + DrawCautionTape(tapeCursor + new Vector2(0, 1), new Vector2(ImGui.GetWindowWidth(), sectionSize + ImGui.GetStyle().ItemSpacing.Y), ImGuiHelpers.GlobalScale * 40, 20); } @@ -2016,7 +2070,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - + ImGui.SetCursorPos(tapeCursor); if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetContentRegionAvail().X, sectionSize + ImGui.GetStyle().ItemSpacing.Y))) @@ -2185,7 +2239,7 @@ internal class PluginInstallerWindow : Window, IDisposable bodyText += " "; if (flags.HasFlag(PluginHeaderFlags.UpdateAvailable)) - bodyText += Locs.PluginBody_Outdated_CanNowUpdate; + bodyText += "\n" + Locs.PluginBody_Outdated_CanNowUpdate; else bodyText += Locs.PluginBody_Outdated_WaitForUpdate; @@ -2208,7 +2262,12 @@ internal class PluginInstallerWindow : Window, IDisposable else if (plugin is { IsDecommissioned: true, IsThirdParty: true }) { ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_NoServiceThird); + + ImGui.TextWrapped( + flags.HasFlag(PluginHeaderFlags.MainRepoCrossUpdate) + ? Locs.PluginBody_NoServiceThirdCrossUpdate + : Locs.PluginBody_NoServiceThird); + ImGui.PopStyleColor(); } else if (plugin != null && !plugin.CheckPolicy()) @@ -2359,11 +2418,11 @@ internal class PluginInstallerWindow : Window, IDisposable { label += Locs.PluginTitleMod_TestingAvailable; } - + var isThirdParty = manifest.SourceRepo.IsThirdParty; ImGui.PushID($"available{index}{manifest.InternalName}"); - + var flags = PluginHeaderFlags.None; if (isThirdParty) flags |= PluginHeaderFlags.IsThirdParty; @@ -2373,7 +2432,7 @@ internal class PluginInstallerWindow : Window, IDisposable flags |= PluginHeaderFlags.IsInstallableOutdated; if (useTesting || manifest.IsTestingExclusive) flags |= PluginHeaderFlags.IsTesting; - + if (this.DrawPluginCollapsingHeader(label, null, manifest, flags, () => this.DrawAvailablePluginContextMenu(manifest), index)) { if (!wasSeen) @@ -2435,7 +2494,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.ScaledDummy(3); } - if (!manifest.SourceRepo.IsThirdParty && manifest.AcceptsFeedback) + if (!manifest.SourceRepo.IsThirdParty && manifest.AcceptsFeedback && !isOutdated) { ImGui.SameLine(); this.DrawSendFeedbackButton(manifest, false, true); @@ -2469,7 +2528,7 @@ internal class PluginInstallerWindow : Window, IDisposable EnsureHaveTestingOptIn(manifest); this.StartInstall(manifest, true); } - + ImGui.Separator(); } @@ -2593,7 +2652,10 @@ internal class PluginInstallerWindow : Window, IDisposable availablePluginUpdate = null; // Update available - if (availablePluginUpdate != default) + var isMainRepoCrossUpdate = availablePluginUpdate != null && + availablePluginUpdate.UpdateManifest.RepoUrl != plugin.Manifest.RepoUrl && + availablePluginUpdate.UpdateManifest.RepoUrl == PluginRepository.MainRepoUrl; + if (availablePluginUpdate != null) { label += Locs.PluginTitleMod_HasUpdate; } @@ -2603,7 +2665,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (this.updatedPlugins != null && !plugin.IsDev) { var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); - if (update != default) + if (update != null) { if (update.Status == PluginUpdateStatus.StatusKind.Success) { @@ -2631,8 +2693,8 @@ internal class PluginInstallerWindow : Window, IDisposable trouble = true; } - // Orphaned - if (plugin.IsOrphaned) + // Orphaned, if we don't have a cross-repo update + if (plugin.IsOrphaned && !isMainRepoCrossUpdate) { label += Locs.PluginTitleMod_OrphanedError; trouble = true; @@ -2661,15 +2723,15 @@ internal class PluginInstallerWindow : Window, IDisposable string? availableChangelog = null; var didDrawAvailableChangelogInsideCollapsible = false; - if (availablePluginUpdate != default) + if (availablePluginUpdate != null) { availablePluginUpdateVersion = availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingAssemblyVersion : availablePluginUpdate.UpdateManifest.AssemblyVersion; - + availableChangelog = - availablePluginUpdate.UseTesting ? + availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingChangelog : availablePluginUpdate.UpdateManifest.Changelog; } @@ -2679,8 +2741,10 @@ internal class PluginInstallerWindow : Window, IDisposable flags |= PluginHeaderFlags.IsThirdParty; if (trouble) flags |= PluginHeaderFlags.HasTrouble; - if (availablePluginUpdate != default) + if (availablePluginUpdate != null) flags |= PluginHeaderFlags.UpdateAvailable; + if (isMainRepoCrossUpdate) + flags |= PluginHeaderFlags.MainRepoCrossUpdate; if (plugin.IsOrphaned) flags |= PluginHeaderFlags.IsOrphan; if (plugin.IsTesting) @@ -2715,8 +2779,8 @@ internal class PluginInstallerWindow : Window, IDisposable var canFeedback = !isThirdParty && !plugin.IsDev && !plugin.IsOrphaned && - (plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel - || plugin.Manifest.TestingDalamudApiLevel == PluginManager.DalamudApiLevel) && + (plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel || + (plugin.Manifest.TestingDalamudApiLevel == PluginManager.DalamudApiLevel && hasTestingAvailable)) && acceptsFeedback && availablePluginUpdate == default; @@ -2753,13 +2817,14 @@ internal class PluginInstallerWindow : Window, IDisposable var commands = commandManager.Commands .Where(cInfo => cInfo.Value is { ShowInHelp: true } && - commandManager.GetHandlerAssemblyName(cInfo.Key, cInfo.Value) == plugin.Manifest.InternalName) - .ToArray(); + commandManager.GetHandlerAssemblyName(cInfo.Key, cInfo.Value) == plugin.Manifest.InternalName); if (commands.Any()) { ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); - foreach (var command in commands) + foreach (var command in commands + .OrderBy(cInfo => cInfo.Value.DisplayOrder) + .ThenBy(cInfo => cInfo.Key)) { ImGuiHelpers.SafeTextWrapped($"{command.Key} → {command.Value.HelpMessage}"); } @@ -2826,7 +2891,7 @@ internal class PluginInstallerWindow : Window, IDisposable { this.DrawInstalledPluginChangelog(applicableChangelog); } - + if (this.categoryManager.CurrentCategoryKind == PluginCategoryManager.CategoryKind.UpdateablePlugins && !availableChangelog.IsNullOrWhitespace() && !didDrawAvailableChangelogInsideCollapsible) @@ -2898,7 +2963,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.MenuItem(Locs.PluginContext_DeletePluginConfigReload)) { - this.ShowDeletePluginConfigWarningModal(plugin.Manifest.Name).ContinueWith(t => + this.ShowDeletePluginConfigWarningModal(plugin.Manifest.Name, optIn != null).ContinueWith(t => { var shouldDelete = t.Result; @@ -3680,7 +3745,7 @@ internal class PluginInstallerWindow : Window, IDisposable this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); this.ResortPlugins(); } - + this.hasHiddenPlugins = this.pluginListAvailable.Any(x => configuration.HiddenPluginInternalName.Contains(x.InternalName)); this.UpdateCategoriesOnPluginsChange(); @@ -3934,16 +3999,18 @@ internal class PluginInstallerWindow : Window, IDisposable public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); - + public static string TabBody_NoPluginsTesting => Loc.Localize("InstallerNoPluginsTesting", "You aren't testing any plugins at the moment!\nYou can opt in to testing versions in the plugin context menu."); - + public static string TabBody_NoPluginsInstalled => string.Format(Loc.Localize("InstallerNoPluginsInstalled", "You don't have any plugins installed yet!\nYou can install them from the \"{0}\" tab."), PluginCategoryManager.Locs.Category_All); - + + public static string TabBody_NoPluginsAvailable => Loc.Localize("InstallerNoPluginsAvailable", "No plugins are available at the moment."); + public static string TabBody_NoPluginsUpdateable => Loc.Localize("InstallerNoPluginsUpdate", "No plugins have updates available at the moment."); - + public static string TabBody_NoPluginsDev => Loc.Localize("InstallerNoPluginsDev", "You don't have any dev plugins. Add them from the settings."); - + #endregion #region Search text @@ -3954,6 +4021,8 @@ internal class PluginInstallerWindow : Window, IDisposable public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the \"All Plugins\" tab."); + public static string TabBody_NoMoreResultsFor(string query) => Loc.Localize("InstallerNoMoreResultsForQuery", "No more search results for \"{0}\".").Format(query); + public static string TabBody_ChangelogNone => Loc.Localize("InstallerNoChangelog", "None of your installed plugins have a changelog."); public static string TabBody_ChangelogError => Loc.Localize("InstallerChangelogError", "Could not download changelogs."); @@ -4005,11 +4074,11 @@ internal class PluginInstallerWindow : Window, IDisposable public static string PluginContext_TestingOptIn => Loc.Localize("InstallerTestingOptIn", "Receive plugin testing versions"); public static string PluginContext_InstallTestingVersion => Loc.Localize("InstallerInstallTestingVersion", "Install testing version"); - + public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); - + public static string PluginContext_UnhidePlugin => Loc.Localize("InstallerUnhidePlugin", "Unhide from installer"); public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin data"); @@ -4046,6 +4115,8 @@ internal class PluginInstallerWindow : Window, IDisposable public static string PluginBody_NoServiceThird => Loc.Localize("InstallerNoServiceThirdPluginBody", "This plugin is no longer being serviced by its source repo. You may have to look for an updated version in another repo."); + public static string PluginBody_NoServiceThirdCrossUpdate => Loc.Localize("InstallerNoServiceThirdCrossUpdatePluginBody", "This plugin is no longer being serviced by its source repo. An update is available and will update it to a version from the official repository."); + public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information."); public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available."); @@ -4263,7 +4334,9 @@ internal class PluginInstallerWindow : Window, IDisposable public static string DeletePluginConfigWarningModal_Title => Loc.Localize("InstallerDeletePluginConfigWarning", "Warning###InstallerDeletePluginConfigWarning"); - public static string DeletePluginConfigWarningModal_Body(string pluginName) => Loc.Localize("InstallerDeletePluginConfigWarningBody", "Are you sure you want to delete all data and configuration for {0}?").Format(pluginName); + public static string DeletePluginConfigWarningModal_ExplainTesting() => Loc.Localize("InstallerDeletePluginConfigWarningExplainTesting", "Do not select this option if you are only trying to disable testing!"); + + public static string DeletePluginConfigWarningModal_Body(string pluginName) => Loc.Localize("InstallerDeletePluginConfigWarningBody", "Are you sure you want to delete all data and configuration for {0}?\nYou will lose all of your settings for this plugin.").Format(pluginName); public static string DeletePluginConfirmWarningModal_Yes => Loc.Localize("InstallerDeletePluginConfigWarningYes", "Yes"); diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index 95315dbd3..ddb89d38c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -625,13 +625,13 @@ internal class ProfileManagerWidget Loc.Localize("ProfileManagerTutorialCommands", "You can use the following commands in chat or in macros to manage active collections:"); public static string TutorialCommandsEnable => - Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(ProfileCommandHandler.CommandEnable); + Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(PluginManagementCommandHandler.CommandEnableProfile); public static string TutorialCommandsDisable => - Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(ProfileCommandHandler.CommandDisable); + Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(PluginManagementCommandHandler.CommandDisableProfile); public static string TutorialCommandsToggle => - Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(ProfileCommandHandler.CommandToggle); + Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(PluginManagementCommandHandler.CommandToggleProfile); public static string TutorialCommandsEnd => Loc.Localize("ProfileManagerTutorialCommandsEnd", "If you run multiple of these commands, they will be executed in order."); diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 314f023da..eeafa98e7 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -258,8 +258,6 @@ internal class PluginStatWindow : Window ImGui.EndTabItem(); } - var toRemove = new List(); - if (ImGui.BeginTabItem("Hooks")) { ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks); @@ -291,9 +289,6 @@ internal class PluginStatWindow : Window { try { - if (trackedHook.Hook.IsDisposed) - toRemove.Add(guid); - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue; @@ -355,14 +350,6 @@ internal class PluginStatWindow : Window } } - if (ImGui.IsWindowAppearing()) - { - foreach (var guid in toRemove) - { - HookManager.TrackedHooks.TryRemove(guid, out _); - } - } - ImGui.EndTabBar(); } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs deleted file mode 100644 index ccee570c7..000000000 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Dalamud.Game.ClientState.GamePad; - -using ImGuiNET; - -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Gamepad State. -/// -internal class GamepadStateAgingStep : IAgingStep -{ - /// - public string Name => "Test GamePadState"; - - /// - public SelfTestStepResult RunStep() - { - var gamepadState = Service.Get(); - - ImGui.Text("Hold down North, East, L1"); - - if (gamepadState.Raw(GamepadButtons.North) == 1 - && gamepadState.Raw(GamepadButtons.East) == 1 - && gamepadState.Raw(GamepadButtons.L1) == 1) - { - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored - } -} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs deleted file mode 100644 index a07b21e54..000000000 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -using Dalamud.Data; -using Dalamud.Utility; -using Lumina.Excel; - -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Lumina. -/// -/// ExcelRow to run test on. -internal class LuminaAgingStep : IAgingStep - where T : ExcelRow -{ - private int step = 0; - private List rows; - - /// - public string Name => "Test Lumina"; - - /// - public SelfTestStepResult RunStep() - { - var dataManager = Service.Get(); - - this.rows ??= dataManager.GetExcelSheet().ToList(); - - Util.ShowObject(this.rows[this.step]); - - this.step++; - return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored - } -} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs deleted file mode 100644 index f02eafc99..000000000 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Dalamud.Game.Gui.Toast; - -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for toasts. -/// -internal class ToastAgingStep : IAgingStep -{ - /// - public string Name => "Test Toasts"; - - /// - public SelfTestStepResult RunStep() - { - var toastGui = Service.Get(); - - toastGui.ShowNormal("Normal Toast"); - toastGui.ShowError("Error Toast"); - - return SelfTestStepResult.Pass; - } - - /// - public void CleanUp() - { - // ignored - } -} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 51c9b35f6..b6f08edf6 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -4,12 +4,13 @@ using System.Numerics; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; -using Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +using Dalamud.Interface.Internal.Windows.SelfTest.Steps; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.SelfTest; @@ -20,36 +21,43 @@ internal class SelfTestWindow : Window { private static readonly ModuleLog Log = new("AGING"); - private readonly List steps = - new() - { - new LoginEventAgingStep(), - new WaitFramesAgingStep(1000), - new EnterTerritoryAgingStep(148, "Central Shroud"), - new ItemPayloadAgingStep(), - new ContextMenuAgingStep(), - new NamePlateAgingStep(), - new ActorTableAgingStep(), - new FateTableAgingStep(), - new AetheryteListAgingStep(), - new ConditionAgingStep(), - new ToastAgingStep(), - new TargetAgingStep(), - new KeyStateAgingStep(), - new GamepadStateAgingStep(), - new ChatAgingStep(), - new HoverAgingStep(), - new LuminaAgingStep(), - new AddonLifecycleAgingStep(), - new PartyFinderAgingStep(), - new HandledExceptionAgingStep(), - new DutyStateAgingStep(), - new GameConfigAgingStep(), - new MarketBoardAgingStep(), - new LogoutEventAgingStep(), - }; + private readonly List steps = + [ + new LoginEventSelfTestStep(), + new WaitFramesSelfTestStep(1000), + new FrameworkTaskSchedulerSelfTestStep(), + new EnterTerritorySelfTestStep(148, "Central Shroud"), + new ItemPayloadSelfTestStep(), + new ContextMenuSelfTestStep(), + new NamePlateSelfTestStep(), + new ActorTableSelfTestStep(), + new FateTableSelfTestStep(), + new AetheryteListSelfTestStep(), + new ConditionSelfTestStep(), + new ToastSelfTestStep(), + new TargetSelfTestStep(), + new KeyStateSelfTestStep(), + new GamepadStateSelfTestStep(), + new ChatSelfTestStep(), + new HoverSelfTestStep(), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(false), + new AddonLifecycleSelfTestStep(), + new PartyFinderSelfTestStep(), + new HandledExceptionSelfTestStep(), + new DutyStateSelfTestStep(), + new GameConfigSelfTestStep(), + new MarketBoardSelfTestStep(), + new SheetRedirectResolverSelfTestStep(), + new NounProcessorSelfTestStep(), + new SeStringEvaluatorSelfTestStep(), + new LogoutEventSelfTestStep() + ]; - private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); + private readonly Dictionary testIndexToResult = new(); private bool selfTestRunning = false; private int currentStep = 0; @@ -82,7 +90,7 @@ internal class SelfTestWindow : Window if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) { - this.stepResults.Add((SelfTestStepResult.NotRan, null)); + this.testIndexToResult.Add(this.currentStep, (SelfTestStepResult.NotRan, null)); this.steps[this.currentStep].CleanUp(); this.currentStep++; this.lastTestStart = DateTimeOffset.Now; @@ -99,7 +107,7 @@ internal class SelfTestWindow : Window { this.selfTestRunning = true; this.currentStep = 0; - this.stepResults.Clear(); + this.testIndexToResult.Clear(); this.lastTestStart = DateTimeOffset.Now; } } @@ -121,7 +129,7 @@ internal class SelfTestWindow : Window this.StopTests(); } - if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) + if (this.testIndexToResult.Any(x => x.Value.Result == SelfTestStepResult.Fail)) { ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); } @@ -161,8 +169,8 @@ internal class SelfTestWindow : Window if (result != SelfTestStepResult.Waiting) { var duration = DateTimeOffset.Now - this.lastTestStart; + this.testIndexToResult.Add(this.currentStep, (result, duration)); this.currentStep++; - this.stepResults.Add((result, duration)); this.lastTestStart = DateTimeOffset.Now; } @@ -170,12 +178,13 @@ internal class SelfTestWindow : Window private void DrawResultTable() { - if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) + if (ImGui.BeginTable("agingResultTable", 5, ImGuiTableFlags.Borders)) { - ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); + ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Name"); - ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); - ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); + ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 30f * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); @@ -190,11 +199,10 @@ internal class SelfTestWindow : Window ImGui.TableSetColumnIndex(1); ImGui.Text(step.Name); - ImGui.TableSetColumnIndex(2); - ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); - if (this.stepResults.Count > i) + if (this.testIndexToResult.TryGetValue(i, out var result)) { - var result = this.stepResults[i]; + ImGui.TableSetColumnIndex(2); + ImGui.PushFont(InterfaceManager.MonoFont); switch (result.Result) { @@ -208,9 +216,18 @@ internal class SelfTestWindow : Window ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); break; } + + ImGui.PopFont(); + + ImGui.TableSetColumnIndex(3); + if (result.Duration.HasValue) + { + ImGui.TextUnformatted(result.Duration.Value.ToString("g")); + } } else { + ImGui.TableSetColumnIndex(2); if (this.selfTestRunning && this.currentStep == i) { ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); @@ -219,27 +236,29 @@ internal class SelfTestWindow : Window { ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); } - } - ImGui.PopFont(); - - ImGui.TableSetColumnIndex(3); - if (this.stepResults.Count > i) - { - var (_, duration) = this.stepResults[i]; - - if (duration.HasValue) - { - ImGui.TextUnformatted(duration.Value.ToString("g")); - } - } - else - { + ImGui.TableSetColumnIndex(3); if (this.selfTestRunning && this.currentStep == i) { ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); } } + + ImGui.TableSetColumnIndex(4); + using var id = ImRaii.PushId($"selfTest{i}"); + if (ImGuiComponents.IconButton(FontAwesomeIcon.FastForward)) + { + this.StopTests(); + this.testIndexToResult.Remove(i); + this.currentStep = i; + this.selfTestRunning = true; + this.lastTestStart = DateTimeOffset.Now; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Jump to this test"); + } } ImGui.EndTable(); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs similarity index 88% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs index 242e93a49..f246cb940 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Actor Table. /// -internal class ActorTableAgingStep : IAgingStep +internal class ActorTableSelfTestStep : ISelfTestStep { private int index = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs index 28edab88a..458edfaff 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs @@ -5,23 +5,23 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup AddonLifecycle Service. /// -internal class AddonLifecycleAgingStep : IAgingStep +internal class AddonLifecycleSelfTestStep : ISelfTestStep { private readonly List listeners; - + private AddonLifecycle? service; private TestStep currentStep = TestStep.CharacterRefresh; private bool listenersRegistered; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public AddonLifecycleAgingStep() + public AddonLifecycleSelfTestStep() { this.listeners = new List { @@ -33,7 +33,7 @@ internal class AddonLifecycleAgingStep : IAgingStep new(AddonEvent.PreFinalize, "Character", this.PreFinalize), }; } - + private enum TestStep { CharacterRefresh, @@ -44,10 +44,10 @@ internal class AddonLifecycleAgingStep : IAgingStep CharacterFinalize, Complete, } - + /// public string Name => "Test AddonLifecycle"; - + /// public SelfTestStepResult RunStep() { @@ -60,7 +60,7 @@ internal class AddonLifecycleAgingStep : IAgingStep { this.service.RegisterListener(listener); } - + this.listenersRegistered = true; } @@ -89,7 +89,7 @@ internal class AddonLifecycleAgingStep : IAgingStep // Nothing to report to tester. break; } - + return this.currentStep is TestStep.Complete ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; } @@ -101,32 +101,32 @@ internal class AddonLifecycleAgingStep : IAgingStep this.service?.UnregisterListener(listener); } } - + private void PostSetup(AddonEvent eventType, AddonArgs addonInfo) - { + { if (this.currentStep is TestStep.CharacterSetup) this.currentStep++; } - + private void PostUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterUpdate) this.currentStep++; } - + private void PostDraw(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterDraw) this.currentStep++; } - + private void PostRefresh(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRefresh) this.currentStep++; } - + private void PostRequestedUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRequestedUpdate) this.currentStep++; } - + private void PreFinalize(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterFinalize) this.currentStep++; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs similarity index 89% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs index 6a4519eab..207f718ff 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Aetheryte List. /// -internal class AetheryteListAgingStep : IAgingStep +internal class AetheryteListSelfTestStep : ISelfTestStep { private int index = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs index 2fdd8c060..8ab1809ad 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.Text.SeStringHandling; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Chat. /// -internal class ChatAgingStep : IAgingStep +internal class ChatSelfTestStep : ISelfTestStep { private int step = 0; private bool subscribed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs similarity index 86% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs index 8ce2111c9..2d49fbdcd 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs @@ -3,12 +3,12 @@ using Dalamud.Game.ClientState.Conditions; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Condition. /// -internal class ConditionAgingStep : IAgingStep +internal class ConditionSelfTestStep : ISelfTestStep { /// public string Name => "Test Condition"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs similarity index 81% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs index 3bc91088d..0e2f61aba 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs @@ -7,18 +7,17 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Utility; using ImGuiNET; using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests for context menu. /// -internal class ContextMenuAgingStep : IAgingStep +internal class ContextMenuSelfTestStep : ISelfTestStep { private SubStep currentSubStep; @@ -45,9 +44,9 @@ internal class ContextMenuAgingStep : IAgingStep { var contextMenu = Service.Get(); var dataMgr = Service.Get(); - this.itemSheet = dataMgr.GetExcelSheet()!; - this.materiaSheet = dataMgr.GetExcelSheet()!; - this.stainSheet = dataMgr.GetExcelSheet()!; + this.itemSheet = dataMgr.GetExcelSheet(); + this.materiaSheet = dataMgr.GetExcelSheet(); + this.stainSheet = dataMgr.GetExcelSheet(); ImGui.Text(this.currentSubStep.ToString()); @@ -83,7 +82,7 @@ internal class ContextMenuAgingStep : IAgingStep case SubStep.TestDefault: if (this.targetCharacter is { } character) { - ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.GameData!.Abbreviation.ToDalamudString()})?"); + ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.Value.Abbreviation.ExtractText()})?"); if (ImGui.Button("Yes")) this.currentSubStep++; @@ -111,7 +110,7 @@ internal class ContextMenuAgingStep : IAgingStep return SelfTestStepResult.Waiting; } - + /// public void CleanUp() { @@ -142,11 +141,11 @@ internal class ContextMenuAgingStep : IAgingStep OnClicked = (IMenuItemClickedArgs a) => { SeString name; - uint count; + int count; var targetItem = (a.Target as MenuTargetInventory)!.TargetItem; if (targetItem is { } item) { - name = (this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty); + name = (this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty); count = item.Quantity; } else @@ -194,7 +193,7 @@ internal class ContextMenuAgingStep : IAgingStep { var b = new StringBuilder(); b.AppendLine($"Target: {targetDefault.TargetName}"); - b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({targetDefault.TargetHomeWorld.Id})"); + b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({targetDefault.TargetHomeWorld.RowId})"); b.AppendLine($"Content Id: 0x{targetDefault.TargetContentId:X8}"); b.AppendLine($"Object Id: 0x{targetDefault.TargetObjectId:X8}"); Log.Verbose(b.ToString()); @@ -209,20 +208,20 @@ internal class ContextMenuAgingStep : IAgingStep b.AppendLine($"Content Id: 0x{character.ContentId:X8}"); b.AppendLine($"FC Tag: {character.FCTag}"); - b.AppendLine($"Job: {character.ClassJob.GameData?.Abbreviation.ToDalamudString() ?? "Unknown"} ({character.ClassJob.Id})"); - b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.GameData?.Name.ToDalamudString() ?? s.Id.ToString()))}"); - b.AppendLine($"Home World: {character.HomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.HomeWorld.Id})"); - b.AppendLine($"Current World: {character.CurrentWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.CurrentWorld.Id})"); + b.AppendLine($"Job: {character.ClassJob.ValueNullable?.Abbreviation.ExtractText() ?? "Unknown"} ({character.ClassJob.RowId})"); + b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.ValueNullable?.Name.ExtractText() ?? s.RowId.ToString()))}"); + b.AppendLine($"Home World: {character.HomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.HomeWorld.RowId})"); + b.AppendLine($"Current World: {character.CurrentWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.CurrentWorld.RowId})"); b.AppendLine($"Is From Other Server: {character.IsFromOtherServer}"); b.Append("Location: "); - if (character.Location.GameData is { } location) - b.Append($"{location.PlaceNameRegion.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceNameZone.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceName.Value?.Name.ToDalamudString() ?? "Unknown"}"); + if (character.Location.ValueNullable is { } location) + b.Append($"{location.PlaceNameRegion.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceNameZone.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceName.ValueNullable?.Name.ExtractText() ?? "Unknown"}"); else b.Append("Unknown"); - b.AppendLine($" ({character.Location.Id})"); + b.AppendLine($" ({character.Location.RowId})"); - b.AppendLine($"Grand Company: {character.GrandCompany.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.GrandCompany.Id})"); + b.AppendLine($"Grand Company: {character.GrandCompany.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.GrandCompany.RowId})"); b.AppendLine($"Client Language: {character.ClientLanguage}"); b.AppendLine($"Languages: {string.Join(", ", character.Languages)}"); b.AppendLine($"Gender: {character.Gender}"); @@ -241,11 +240,11 @@ internal class ContextMenuAgingStep : IAgingStep if (targetInventory.TargetItem is { } item) { var b = new StringBuilder(); - b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString())} ({item.ItemId})"); + b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText())} ({item.ItemId})"); b.AppendLine($"Container: {item.ContainerType}"); b.AppendLine($"Slot: {item.InventorySlot}"); b.AppendLine($"Quantity: {item.Quantity}"); - b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.Spiritbond}"); + b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.SpiritbondOrCollectability}"); b.AppendLine($"Condition: {item.Condition / 300f:0.00}% ({item.Condition})"); b.AppendLine($"Is HQ: {item.IsHq}"); b.AppendLine($"Is Company Crest Applied: {item.IsCompanyCrestApplied}"); @@ -259,7 +258,7 @@ internal class ContextMenuAgingStep : IAgingStep Log.Verbose($"{materiaId} {materiaGrade}"); if (this.materiaSheet.GetRow(materiaId) is { } materia && materia.Item[materiaGrade].Value is { } materiaItem) - materias.Add($"{materiaItem.Name.ToDalamudString()}"); + materias.Add($"{materiaItem.Name.ExtractText()}"); else materias.Add($"Unknown (Id: {materiaId}, Grade: {materiaGrade})"); } @@ -275,7 +274,7 @@ internal class ContextMenuAgingStep : IAgingStep var stainId = item.Stains[i]; if (stainId != 0) { - var stainName = this.stainSheet.GetRow(stainId)?.Name.ToDalamudString().ToString() ?? "Unknown"; + var stainName = this.stainSheet.GetRowOrDefault(stainId)?.Name.ExtractText() ?? "Unknown"; b.AppendLine($" Stain {i + 1}: {stainName} ({stainId})"); } else @@ -284,14 +283,9 @@ internal class ContextMenuAgingStep : IAgingStep } } - if (item.Stains[0] != 0) - b.AppendLine($"{this.stainSheet.GetRow(item.Stains[0])?.Name.ToDalamudString() ?? "Unknown"} ({item.Stains[0]})"); - else - b.AppendLine("None"); - b.Append("Glamoured Item: "); if (item.GlamourId != 0) - b.AppendLine($"{this.itemSheet.GetRow(item.GlamourId)?.Name.ToDalamudString() ?? "Unknown"} ({item.GlamourId})"); + b.AppendLine($"{this.itemSheet.GetRowOrDefault(item.GlamourId)?.Name.ExtractText() ?? "Unknown"} ({item.GlamourId})"); else b.AppendLine("None"); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs similarity index 90% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs index 19e218ecb..4c33347fc 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs @@ -2,12 +2,12 @@ using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the DutyState service class. /// -internal class DutyStateAgingStep : IAgingStep +internal class DutyStateSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs similarity index 88% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs index a61af7f94..04c8eda5e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Territory Change. /// -internal class EnterTerritoryAgingStep : IAgingStep +internal class EnterTerritorySelfTestStep : ISelfTestStep { private readonly ushort territory; private readonly string terriName; @@ -15,11 +15,11 @@ internal class EnterTerritoryAgingStep : IAgingStep private bool hasPassed = false; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The territory to check for. /// Name to show. - public EnterTerritoryAgingStep(ushort terri, string name) + public EnterTerritorySelfTestStep(ushort terri, string name) { this.terriName = name; this.territory = terri; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs similarity index 72% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs index a8fe60aa9..982633dcb 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs @@ -2,14 +2,14 @@ using Dalamud.Game.ClientState.Fates; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Fate Table. /// -internal class FateTableAgingStep : IAgingStep +internal class FateTableSelfTestStep : ISelfTestStep { - private int index = 0; + private byte index = 0; /// public string Name => "Test FateTable"; @@ -21,6 +21,12 @@ internal class FateTableAgingStep : IAgingStep ImGui.Text("Checking fate table..."); + if (fateTable.Length == 0) + { + ImGui.Text("Go to a zone that has FATEs currently up."); + return SelfTestStepResult.Waiting; + } + if (this.index == fateTable.Length - 1) { return SelfTestStepResult.Pass; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs new file mode 100644 index 000000000..d952be419 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs @@ -0,0 +1,89 @@ +using System.Threading.Tasks; + +using Dalamud.Game; +using Dalamud.Utility; + +using Microsoft.VisualBasic.Logging; + +using Log = Serilog.Log; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for Framework task scheduling. +/// +internal class FrameworkTaskSchedulerSelfTestStep : ISelfTestStep +{ + private bool passed = false; + private Task? task; + + /// + public string Name => "Test Framework Task Scheduler"; + + /// + public SelfTestStepResult RunStep() + { + var framework = Service.Get(); + + this.task ??= Task.Run(async () => + { + ThreadSafety.AssertNotMainThread(); + + await framework.Run(async () => + { + ThreadSafety.AssertMainThread(); + + await Task.Delay(100).ConfigureAwait(true); + ThreadSafety.AssertMainThread(); + + await Task.Delay(100).ConfigureAwait(false); + ThreadSafety.AssertNotMainThread(); + }).ConfigureAwait(true); + + ThreadSafety.AssertNotMainThread(); + + await framework.RunOnTick(async () => + { + ThreadSafety.AssertMainThread(); + + await Task.Delay(100).ConfigureAwait(true); + ThreadSafety.AssertNotMainThread(); + + await Task.Delay(100).ConfigureAwait(false); + ThreadSafety.AssertNotMainThread(); + }).ConfigureAwait(true); + + ThreadSafety.AssertNotMainThread(); + + await framework.RunOnTick(() => + { + ThreadSafety.AssertMainThread(); + }); + + ThreadSafety.AssertMainThread(); + + this.passed = true; + }).ContinueWith( + t => + { + if (t.IsFaulted) + { + Log.Error(t.Exception, "Framework Task scheduler test failed"); + } + }); + + if (this.task is { IsFaulted: true } or { IsCanceled: true }) + { + return SelfTestStepResult.Fail; + } + + return this.passed ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + this.passed = false; + this.task = null; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs similarity index 95% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs index 97c163590..440ec9f5d 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs @@ -2,12 +2,12 @@ using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test of GameConfig. /// -internal class GameConfigAgingStep : IAgingStep +internal class GameConfigSelfTestStep : ISelfTestStep { private bool started; private bool isStartedLegacy; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs new file mode 100644 index 000000000..04684d521 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs @@ -0,0 +1,65 @@ +using System.Linq; + +using Dalamud.Game.ClientState.GamePad; +using Dalamud.Interface.Utility; + +using Lumina.Text.Payloads; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for the Gamepad State. +/// +internal class GamepadStateSelfTestStep : ISelfTestStep +{ + /// + public string Name => "Test GamePadState"; + + /// + public SelfTestStepResult RunStep() + { + var gamepadState = Service.Get(); + + var buttons = new (GamepadButtons Button, uint IconId)[] + { + (GamepadButtons.North, 11), + (GamepadButtons.East, 8), + (GamepadButtons.L1, 12), + }; + + var builder = LSeStringBuilder.SharedPool.Get(); + + builder.Append("Hold down "); + + for (var i = 0; i < buttons.Length; i++) + { + var (button, iconId) = buttons[i]; + + builder.BeginMacro(MacroCode.Icon).AppendUIntExpression(iconId).EndMacro(); + builder.PushColorRgba(gamepadState.Raw(button) == 1 ? 0x0000FF00u : 0x000000FF); + builder.Append(button.ToString()); + builder.PopColor(); + + builder.Append(i < buttons.Length - 1 ? ", " : "."); + } + + ImGuiHelpers.SeStringWrapped(builder.ToReadOnlySeString()); + + LSeStringBuilder.SharedPool.Return(builder); + + if (buttons.All(tuple => gamepadState.Raw(tuple.Button) == 1)) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs similarity index 82% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs index 5d2173ad5..9757481e4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs @@ -1,11 +1,11 @@ using System.Runtime.InteropServices; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test dedicated to handling of Access Violations. /// -internal class HandledExceptionAgingStep : IAgingStep +internal class HandledExceptionSelfTestStep : ISelfTestStep { /// public string Name => "Test Handled Exception"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs similarity index 91% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs index 8f509b8e7..85b5d3784 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.Gui; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Hover events. /// -internal class HoverAgingStep : IAgingStep +internal class HoverSelfTestStep : ISelfTestStep { private bool clearedItem = false; private bool clearedAction = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs similarity index 85% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs index 680961344..529d788ab 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs @@ -1,9 +1,9 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Interface for test implementations. /// -internal interface IAgingStep +internal interface ISelfTestStep { /// /// Gets the name of the test. diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs index 1ccb5934f..0988413b0 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for item payloads. /// -internal class ItemPayloadAgingStep : IAgingStep +internal class ItemPayloadSelfTestStep : ISelfTestStep { private SubStep currentSubStep; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs similarity index 86% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs index 522943e87..667f3adc4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Key State. /// -internal class KeyStateAgingStep : IAgingStep +internal class KeyStateSelfTestStep : ISelfTestStep { /// public string Name => "Test KeyState"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs similarity index 90% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs index d755e95ef..9540c636a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the login events. /// -internal class LoginEventAgingStep : IAgingStep +internal class LoginEventSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs similarity index 86% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs index 1869dd108..13bcfcf35 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the login events. /// -internal class LogoutEventAgingStep : IAgingStep +internal class LogoutEventSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; @@ -50,7 +50,7 @@ internal class LogoutEventAgingStep : IAgingStep } } - private void ClientStateOnOnLogout() + private void ClientStateOnOnLogout(int type, int code) { this.hasPassed = true; } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs new file mode 100644 index 000000000..3fbc4361f --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs @@ -0,0 +1,53 @@ +using Dalamud.Data; +using Dalamud.Utility; +using Lumina.Excel; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for Lumina. +/// +/// ExcelRow to run test on. +/// Whether or not the sheet is large. If it is large, the self test will iterate through the full sheet in one frame and benchmark the time taken. +internal class LuminaSelfTestStep(bool isLargeSheet) : ISelfTestStep + where T : struct, IExcelRow +{ + private int step = 0; + private ExcelSheet rows; + + /// + public string Name => $"Test Lumina ({typeof(T).Name})"; + + /// + public SelfTestStepResult RunStep() + { + this.rows ??= Service.Get().GetExcelSheet(); + + if (isLargeSheet) + { + var i = 0; + T currentRow = default; + foreach (var row in this.rows) + { + i++; + currentRow = row; + } + + Util.ShowObject(currentRow); + return SelfTestStepResult.Pass; + } + else + { + Util.ShowObject(this.rows.GetRowAt(this.step)); + + this.step++; + return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + } + + /// + public void CleanUp() + { + this.step = 0; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs index 78d43662c..4a6dd185f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs @@ -5,12 +5,12 @@ using Dalamud.Game.MarketBoard; using Dalamud.Game.Network.Structures; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests the various market board events. /// -internal class MarketBoardAgingStep : IAgingStep +internal class MarketBoardSelfTestStep : ISelfTestStep { private SubStep currentSubStep; private bool eventsSubscribed; @@ -169,6 +169,7 @@ internal class MarketBoardAgingStep : IAgingStep ImGui.Text($"Kugane: {this.marketTaxRate.KuganeTax.ToString()}"); ImGui.Text($"Crystarium: {this.marketTaxRate.CrystariumTax.ToString()}"); ImGui.Text($"Sharlayan: {this.marketTaxRate.SharlayanTax.ToString()}"); + ImGui.Text($"Tuliyollal: {this.marketTaxRate.TuliyollalTax.ToString()}"); ImGui.Separator(); if (ImGui.Button("Looks Correct / Skip")) { diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs index 5a03a6dc2..e7ce8e42a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs @@ -6,12 +6,12 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests for nameplates. /// -internal class NamePlateAgingStep : IAgingStep +internal class NamePlateSelfTestStep : ISelfTestStep { private SubStep currentSubStep; private Dictionary? updateCount; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs new file mode 100644 index 000000000..7319eec7b --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs @@ -0,0 +1,257 @@ +using Dalamud.Game; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; + +using ImGuiNET; + +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for NounProcessor. +/// +internal class NounProcessorSelfTestStep : ISelfTestStep +{ + private NounTestEntry[] tests = + [ + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの蜂蜜酒の運び人"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a mead-porting Midlander"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the mead-porting Midlander"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "mead-porting Midlanders"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "mead-porting Midlanders"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppendem Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese Met schleppenden Wiesländer"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses livreurs d'hydromel"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その酔いどれのネル"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "酔いどれのネル"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "Nell Half-full"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "Nell Half-full"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "Nell die Beschwipste"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "ma Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ta Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "sa Nell la Boit-sans-soif"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの希少トームストーン:幻想"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "an irregular tomestone of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the irregular tomestone of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "irregular tomestones of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "irregular tomestones of phantasmagoria"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein ungewöhnliche Allagische Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichem Allagischem Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser ungewöhnliche Allagische Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der ungewöhnlicher Allagischer Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlicher Allagischer Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"), + ]; + + private enum GermanCases + { + Nominative, + Genitive, + Dative, + Accusative, + } + + /// + public string Name => "Test NounProcessor"; + + /// + public unsafe SelfTestStepResult RunStep() + { + var nounProcessor = Service.Get(); + + for (var i = 0; i < this.tests.Length; i++) + { + var e = this.tests[i]; + + var nounParams = new NounParams() + { + SheetName = e.SheetName, + RowId = e.RowId, + Language = e.Language, + Quantity = e.Quantity, + ArticleType = e.ArticleType, + GrammaticalCase = e.GrammaticalCase, + }; + var output = nounProcessor.ProcessNoun(nounParams); + + if (e.ExpectedResult != output) + { + ImGui.TextUnformatted($"Mismatch detected (Test #{i}):"); + ImGui.TextUnformatted($"Got: {output}"); + ImGui.TextUnformatted($"Expected: {e.ExpectedResult}"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + } + + return SelfTestStepResult.Pass; + } + + /// + public void CleanUp() + { + // ignored + } + + private record struct NounTestEntry( + string SheetName, + uint RowId, + ClientLanguage Language, + int Quantity, + int ArticleType, + int GrammaticalCase, + string ExpectedResult); +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs similarity index 92% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs index d6f38092b..475412e70 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs @@ -3,12 +3,12 @@ using Dalamud.Game.Gui.PartyFinder.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Party Finder events. /// -internal class PartyFinderAgingStep : IAgingStep +internal class PartyFinderSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs new file mode 100644 index 000000000..423a56172 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs @@ -0,0 +1,92 @@ +using Dalamud.Game.ClientState; +using Dalamud.Game.Text.Evaluator; + +using ImGuiNET; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for SeStringEvaluator. +/// +internal class SeStringEvaluatorSelfTestStep : ISelfTestStep +{ + private int step = 0; + + /// + public string Name => "Test SeStringEvaluator"; + + /// + public SelfTestStepResult RunStep() + { + var seStringEvaluator = Service.Get(); + + switch (this.step) + { + case 0: + ImGui.TextUnformatted("Is this the current time, and is it ticking?"); + + // This checks that EvaluateFromAddon fetches the correct Addon row, + // that MacroDecoder.GetMacroTime()->SetTime() has been called + // and that local and global parameters have been read correctly. + + ImGui.TextUnformatted(seStringEvaluator.EvaluateFromAddon(31, [(uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds()]).ExtractText()); + + if (ImGui.Button("Yes")) + this.step++; + + ImGui.SameLine(); + + if (ImGui.Button("No")) + return SelfTestStepResult.Fail; + + break; + + case 1: + ImGui.TextUnformatted("Checking pcname macro using the local player name..."); + + // This makes sure that NameCache.Instance()->TryGetCharacterInfoByEntityId() has been called, + // that it returned the local players name by using its EntityId, + // and that it didn't include the world name by checking the HomeWorldId against AgentLobby.Instance()->LobbyData.HomeWorldId. + + var clientState = Service.Get(); + var localPlayer = clientState.LocalPlayer; + if (localPlayer is null) + { + ImGui.TextUnformatted("You need to be logged in for this step."); + + if (ImGui.Button("Skip")) + return SelfTestStepResult.NotRan; + + return SelfTestStepResult.Waiting; + } + + var evaluatedPlayerName = seStringEvaluator.Evaluate(ReadOnlySeString.FromMacroString(""), [localPlayer.EntityId]).ExtractText(); + var localPlayerName = localPlayer.Name.TextValue; + + if (evaluatedPlayerName != localPlayerName) + { + ImGui.TextUnformatted("The player name doesn't match:"); + ImGui.TextUnformatted($"Evaluated Player Name (got): {evaluatedPlayerName}"); + ImGui.TextUnformatted($"Local Player Name (expected): {localPlayerName}"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored + this.step = 0; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs new file mode 100644 index 000000000..6ab08cd91 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs @@ -0,0 +1,130 @@ +using System.Runtime.InteropServices; + +using Dalamud.Game; +using Dalamud.Game.Text.Evaluator.Internal; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for SheetRedirectResolver. +/// +internal class SheetRedirectResolverSelfTestStep : ISelfTestStep +{ + private RedirectEntry[] redirects = + [ + new("Item", 10, SheetRedirectFlags.Item), + new("ItemHQ", 10, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality), + new("ItemMP", 10, SheetRedirectFlags.Item | SheetRedirectFlags.Collectible), + new("Item", 35588, SheetRedirectFlags.Item), + new("Item", 1035588, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality), + new("Item", 2000217, SheetRedirectFlags.Item | SheetRedirectFlags.EventItem), + new("ActStr", 10, SheetRedirectFlags.Action), // Trait + new("ActStr", 1000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action + new("ActStr", 2000010, SheetRedirectFlags.Action), // Item + new("ActStr", 3000010, SheetRedirectFlags.Action), // EventItem + new("ActStr", 4000010, SheetRedirectFlags.Action), // EventAction + new("ActStr", 5000010, SheetRedirectFlags.Action), // GeneralAction + new("ActStr", 6000010, SheetRedirectFlags.Action), // BuddyAction + new("ActStr", 7000010, SheetRedirectFlags.Action), // MainCommand + new("ActStr", 8000010, SheetRedirectFlags.Action), // Companion + new("ActStr", 9000010, SheetRedirectFlags.Action), // CraftAction + new("ActStr", 10000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action + new("ActStr", 11000010, SheetRedirectFlags.Action), // PetAction + new("ActStr", 12000010, SheetRedirectFlags.Action), // CompanyAction + new("ActStr", 13000010, SheetRedirectFlags.Action), // Mount + // new("ActStr", 14000010, RedirectFlags.Action), + // new("ActStr", 15000010, RedirectFlags.Action), + // new("ActStr", 16000010, RedirectFlags.Action), + // new("ActStr", 17000010, RedirectFlags.Action), + // new("ActStr", 18000010, RedirectFlags.Action), + new("ActStr", 19000010, SheetRedirectFlags.Action), // BgcArmyAction + new("ActStr", 20000010, SheetRedirectFlags.Action), // Ornament + new("ObjStr", 10), // BNpcName + new("ObjStr", 1000010), // ENpcResident + new("ObjStr", 2000010), // Treasure + new("ObjStr", 3000010), // Aetheryte + new("ObjStr", 4000010), // GatheringPointName + new("ObjStr", 5000010), // EObjName + new("ObjStr", 6000010), // Mount + new("ObjStr", 7000010), // Companion + // new("ObjStr", 8000010), + // new("ObjStr", 9000010), + new("ObjStr", 10000010), // Item + new("EObj", 2003479), // EObj => EObjName + new("Treasure", 1473), // Treasure (without name, falls back to rowId 0) + new("Treasure", 1474), // Treasure (with name) + new("WeatherPlaceName", 0), + new("WeatherPlaceName", 28), + new("WeatherPlaceName", 40), + new("WeatherPlaceName", 52), + new("WeatherPlaceName", 2300), + ]; + + private unsafe delegate SheetRedirectFlags ResolveSheetRedirect(RaptureTextModule* thisPtr, Utf8String* sheetName, uint* rowId, uint* flags); + + /// + public string Name => "Test SheetRedirectResolver"; + + /// + public unsafe SelfTestStepResult RunStep() + { + // Client::UI::Misc::RaptureTextModule_ResolveSheetRedirect + if (!Service.Get().TryScanText("E8 ?? ?? ?? ?? 44 8B E8 A8 10", out var addr)) + return SelfTestStepResult.Fail; + + var sheetRedirectResolver = Service.Get(); + var resolveSheetRedirect = Marshal.GetDelegateForFunctionPointer(addr); + var utf8SheetName = Utf8String.CreateEmpty(); + + try + { + for (var i = 0; i < this.redirects.Length; i++) + { + var redirect = this.redirects[i]; + + utf8SheetName->SetString(redirect.SheetName); + + var rowId1 = redirect.RowId; + uint colIndex1 = ushort.MaxValue; + var flags1 = resolveSheetRedirect(RaptureTextModule.Instance(), utf8SheetName, &rowId1, &colIndex1); + + var sheetName2 = redirect.SheetName; + var rowId2 = redirect.RowId; + uint colIndex2 = ushort.MaxValue; + var flags2 = sheetRedirectResolver.Resolve(ref sheetName2, ref rowId2, ref colIndex2); + + if (utf8SheetName->ToString() != sheetName2 || rowId1 != rowId2 || colIndex1 != colIndex2 || flags1 != flags2) + { + ImGui.TextUnformatted($"Mismatch detected (Test #{i}):"); + ImGui.TextUnformatted($"Input: {redirect.SheetName}#{redirect.RowId}"); + ImGui.TextUnformatted($"Game: {utf8SheetName->ToString()}#{rowId1}-{colIndex1} ({flags1})"); + ImGui.TextUnformatted($"Evaluated: {sheetName2}#{rowId2}-{colIndex2} ({flags2})"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + } + + return SelfTestStepResult.Pass; + } + finally + { + utf8SheetName->Dtor(true); + } + } + + /// + public void CleanUp() + { + // ignored + } + + private record struct RedirectEntry(string SheetName, uint RowId, SheetRedirectFlags Flags = SheetRedirectFlags.None); +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs index f8767ad53..b56b08ed5 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.ClientState.Objects.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for targets. /// -internal class TargetAgingStep : IAgingStep +internal class TargetSelfTestStep : ISelfTestStep { private int step = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs new file mode 100644 index 000000000..cd34fa30b --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs @@ -0,0 +1,50 @@ +using Dalamud.Game.Gui.Toast; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for toasts. +/// +internal class ToastSelfTestStep : ISelfTestStep +{ + private bool sentToasts = false; + + /// + public string Name => "Test Toasts"; + + /// + public SelfTestStepResult RunStep() + { + if (!this.sentToasts) + { + var toastGui = Service.Get(); + toastGui.ShowNormal("Normal Toast"); + toastGui.ShowError("Error Toast"); + toastGui.ShowQuest("Quest Toast"); + this.sentToasts = true; + } + + ImGui.Text("Did you see a normal toast, a quest toast and an error toast?"); + + if (ImGui.Button("Yes")) + { + return SelfTestStepResult.Pass; + } + + ImGui.SameLine(); + if (ImGui.Button("No")) + { + return SelfTestStepResult.Fail; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + this.sentToasts = false; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs similarity index 72% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs index 54aeee145..35c64376d 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs @@ -1,18 +1,18 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test that waits N frames. /// -internal class WaitFramesAgingStep : IAgingStep +internal class WaitFramesSelfTestStep : ISelfTestStep { private readonly int frames; private int cFrames; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Amount of frames to wait. - public WaitFramesAgingStep(int frames) + public WaitFramesSelfTestStep(int frames) { this.frames = frames; this.cFrames = frames; diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs index a72cae024..46013b72c 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs @@ -40,6 +40,13 @@ public abstract class SettingsEntry /// public abstract void Draw(); + /// + /// Called after the draw function and when the style overrides are removed. + /// + public virtual void PostDraw() + { + } + /// /// Function to be called when the tab is opened. /// diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs index d06fe0fb6..bd4a702f5 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs @@ -44,6 +44,14 @@ public abstract class SettingsTab : IDisposable ImGuiHelpers.ScaledDummy(15); } + public virtual void PostDraw() + { + foreach (var settingsEntry in this.Entries) + { + settingsEntry.PostDraw(); + } + } + public virtual void Load() { foreach (var settingsEntry in this.Entries) diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs index 5e3857f42..c678dff10 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs @@ -154,15 +154,23 @@ internal class SettingsWindow : Window } // Don't add padding for the about tab(credits) - using var padding = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(2, 2), - settingsTab is not SettingsTabAbout); - using var borderColor = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.ChildBg)); - using var tabChild = ImRaii.Child( - $"###settings_scrolling_{settingsTab.Title}", - new Vector2(-1, -1), - true); - if (tabChild) - settingsTab.Draw(); + { + using var padding = ImRaii.PushStyle( + ImGuiStyleVar.WindowPadding, + new Vector2(2, 2), + settingsTab is not SettingsTabAbout); + using var borderColor = ImRaii.PushColor( + ImGuiCol.Border, + ImGui.GetColorU32(ImGuiCol.ChildBg)); + using var tabChild = ImRaii.Child( + $"###settings_scrolling_{settingsTab.Title}", + new Vector2(-1, -1), + true); + if (tabChild) + settingsTab.Draw(); + } + + settingsTab.PostDraw(); } else if (settingsTab.IsOpen) { diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs index d32b37cdf..428be05c2 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; @@ -140,7 +140,7 @@ Dale Arcane Disgea Risu Tom -Blyoom +Blooym Valk @@ -194,6 +194,7 @@ Contribute at: https://github.com/goatcorp/Dalamud private readonly IFontAtlas privateAtlas; private string creditsText; + private bool isBgmSet; private bool resetNow = false; private IDalamudTextureWrap? logoTexture; @@ -222,10 +223,13 @@ Contribute at: https://github.com/goatcorp/Dalamud this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Util.GetGitHashClientStructs()); - var gg = Service.Get(); - if (!gg.IsOnTitleScreen() && UIState.Instance() != null) + var gameGui = Service.Get(); + var playerState = PlayerState.Instance(); + + if (!gameGui.IsInLobby() && playerState != null) { - gg.SetBgm((ushort)(UIState.Instance()->PlayerState.MaxExpansion > 3 ? 833 : 132)); + gameGui.SetBgm((ushort)(playerState->MaxExpansion > 3 ? 833 : 132)); + this.isBgmSet = true; } this.creditsThrottler.Restart(); @@ -242,9 +246,15 @@ Contribute at: https://github.com/goatcorp/Dalamud { this.creditsThrottler.Reset(); - var gg = Service.Get(); - if (!gg.IsOnTitleScreen()) - gg.SetBgm(9999); + if (this.isBgmSet) + { + var gameGui = Service.Get(); + + if (!gameGui.IsInLobby()) + gameGui.ResetBgm(); + + this.isBgmSet = false; + } Service.Get().SetCreditsDarkeningAnimation(false); } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs index 77c79c96d..9356131ad 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs @@ -23,10 +23,11 @@ public class SettingsTabAutoUpdates : SettingsTab { private AutoUpdateBehavior behavior; private bool checkPeriodically; + private bool chatNotification; private string pickerSearch = string.Empty; private List autoUpdatePreferences = []; - - public override SettingsEntry[] Entries { get; } = Array.Empty(); + + public override SettingsEntry[] Entries { get; } = []; public override string Title => Loc.Localize("DalamudSettingsAutoUpdates", "Auto-Updates"); @@ -36,15 +37,15 @@ public class SettingsTabAutoUpdates : SettingsTab "Dalamud can update your plugins automatically, making sure that you always " + "have the newest features and bug fixes. You can choose when and how auto-updates are run here.")); ImGuiHelpers.ScaledDummy(2); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdateDisclaimer1", "You can always update your plugins manually by clicking the update button in the plugin list. " + "You can also opt into updates for specific plugins by right-clicking them and selecting \"Always auto-update\".")); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdateDisclaimer2", "Dalamud will only notify you about updates while you are idle.")); - + ImGuiHelpers.ScaledDummy(8); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateBehavior", "When the game starts...")); var behaviorInt = (int)this.behavior; @@ -62,20 +63,21 @@ public class SettingsTabAutoUpdates : SettingsTab "These updates are not reviewed by the Dalamud team and may contain malicious code."); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudOrange, warning); } - + ImGuiHelpers.ScaledDummy(8); - + + ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdateChatMessage", "Show notification about updates available in chat"), ref this.chatNotification); ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePeriodically", "Periodically check for new updates while playing"), ref this.checkPeriodically); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePeriodicallyHint", "Plugins won't update automatically after startup, you will only receive a notification while you are not actively playing.")); - + ImGuiHelpers.ScaledDummy(5); ImGui.Separator(); ImGuiHelpers.ScaledDummy(5); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateOptedIn", "Per-plugin overrides")); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateOverrideHint", "Here, you can choose to receive or not to receive updates for specific plugins. " + "This will override the settings above for the selected plugins.")); @@ -83,25 +85,25 @@ public class SettingsTabAutoUpdates : SettingsTab if (this.autoUpdatePreferences.Count == 0) { ImGuiHelpers.ScaledDummy(20); - + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) { ImGuiHelpers.CenteredText(Loc.Localize("DalamudSettingsAutoUpdateOptedInHint2", "You don't have auto-update rules for any plugins.")); } - + ImGuiHelpers.ScaledDummy(2); } else { ImGuiHelpers.ScaledDummy(5); - + var pic = Service.Get(); var windowSize = ImGui.GetWindowSize(); var pluginLineHeight = 32 * ImGuiHelpers.GlobalScale; Guid? wantRemovePluginGuid = null; - + foreach (var preference in this.autoUpdatePreferences) { var pmPlugin = Service.Get().InstalledPlugins @@ -120,11 +122,12 @@ public class SettingsTabAutoUpdates : SettingsTab if (pmPlugin.IsDev) { ImGui.SetCursorPos(cursorBeforeIcon); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.7f); - ImGui.Image(pic.DevPluginIcon.ImGuiHandle, new Vector2(pluginLineHeight)); - ImGui.PopStyleVar(); + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.7f)) + { + ImGui.Image(pic.DevPluginIcon.ImGuiHandle, new Vector2(pluginLineHeight)); + } } - + ImGui.SameLine(); var text = $"{pmPlugin.Name}{(pmPlugin.IsDev ? " (dev plugin" : string.Empty)}"; @@ -147,7 +150,7 @@ public class SettingsTabAutoUpdates : SettingsTab ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (textHeight.Y / 2)); ImGui.TextUnformatted(text); - + ImGui.SetCursorPos(before); } @@ -166,19 +169,18 @@ public class SettingsTabAutoUpdates : SettingsTab } ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 250); - if (ImGui.BeginCombo( - $"###autoUpdateBehavior{preference.WorkingPluginId}", - OptKindToString(preference.Kind))) + using (var combo = ImRaii.Combo($"###autoUpdateBehavior{preference.WorkingPluginId}", OptKindToString(preference.Kind))) { - foreach (var kind in Enum.GetValues()) + if (combo.Success) { - if (ImGui.Selectable(OptKindToString(kind))) + foreach (var kind in Enum.GetValues()) { - preference.Kind = kind; + if (ImGui.Selectable(OptKindToString(kind))) + { + preference.Kind = kind; + } } } - - ImGui.EndCombo(); } ImGui.SameLine(); @@ -193,7 +195,7 @@ public class SettingsTabAutoUpdates : SettingsTab if (ImGui.IsItemHovered()) ImGui.SetTooltip(Loc.Localize("DalamudSettingsAutoUpdateOptInRemove", "Remove this override")); } - + if (wantRemovePluginGuid != null) { this.autoUpdatePreferences.RemoveAll(x => x.WorkingPluginId == wantRemovePluginGuid); @@ -205,19 +207,19 @@ public class SettingsTabAutoUpdates : SettingsTab var id = plugin.EffectiveWorkingPluginId; if (id == Guid.Empty) throw new InvalidOperationException("Plugin ID is empty."); - + this.autoUpdatePreferences.Add(new AutoUpdatePreference(id)); } - + bool IsPluginDisabled(LocalPlugin plugin) => this.autoUpdatePreferences.Any(x => x.WorkingPluginId == plugin.EffectiveWorkingPluginId); - + bool IsPluginFiltered(LocalPlugin plugin) => !plugin.IsDev; - + var pickerId = DalamudComponents.DrawPluginPicker( "###autoUpdatePicker", ref this.pickerSearch, OnPluginPicked, IsPluginDisabled, IsPluginFiltered); - + const FontAwesomeIcon addButtonIcon = FontAwesomeIcon.Plus; var addButtonText = Loc.Localize("DalamudSettingsAutoUpdateOptInAdd", "Add new override"); ImGuiHelpers.CenterCursorFor(ImGuiComponents.GetIconButtonWithTextWidth(addButtonIcon, addButtonText)); @@ -235,20 +237,22 @@ public class SettingsTabAutoUpdates : SettingsTab var configuration = Service.Get(); this.behavior = configuration.AutoUpdateBehavior ?? AutoUpdateBehavior.None; + this.chatNotification = configuration.SendUpdateNotificationToChat; this.checkPeriodically = configuration.CheckPeriodicallyForUpdates; this.autoUpdatePreferences = configuration.PluginAutoUpdatePreferences; - + base.Load(); } public override void Save() { var configuration = Service.Get(); - + configuration.AutoUpdateBehavior = this.behavior; + configuration.SendUpdateNotificationToChat = this.chatNotification; configuration.CheckPeriodicallyForUpdates = this.checkPeriodically; configuration.PluginAutoUpdatePreferences = this.autoUpdatePreferences; - + base.Save(); } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs index 2559911cf..d1040b5b2 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using CheapLoc; using Dalamud.Configuration.Internal; @@ -45,6 +46,10 @@ public class SettingsTabDtr : SettingsTab } var isOrderChange = false; + Span upButtonCenters = stackalloc Vector2[order.Count]; + Span downButtonCenters = stackalloc Vector2[order.Count]; + scoped Span moveMouseTo = default; + var moveMouseToIndex = -1; for (var i = 0; i < order.Count; i++) { var title = order[i]; @@ -65,9 +70,13 @@ public class SettingsTabDtr : SettingsTab { (order[i], order[i - 1]) = (order[i - 1], order[i]); isOrderChange = true; + moveMouseToIndex = i - 1; + moveMouseTo = upButtonCenters; } } + upButtonCenters[i] = (ImGui.GetItemRectMin() + ImGui.GetItemRectMax()) / 2; + ImGui.SameLine(); var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}"; @@ -81,9 +90,13 @@ public class SettingsTabDtr : SettingsTab { (order[i], order[i + 1]) = (order[i + 1], order[i]); isOrderChange = true; + moveMouseToIndex = i + 1; + moveMouseTo = downButtonCenters; } } + downButtonCenters[i] = (ImGui.GetItemRectMin() + ImGui.GetItemRectMax()) / 2; + ImGui.PopFont(); ImGui.SameLine(); @@ -107,6 +120,12 @@ public class SettingsTabDtr : SettingsTab // } } + if (moveMouseToIndex >= 0 && moveMouseToIndex < moveMouseTo.Length) + { + ImGui.GetIO().WantSetMousePos = true; + ImGui.GetIO().MousePos = moveMouseTo[moveMouseToIndex]; + } + configuration.DtrOrder = order.Concat(orderLeft).ToList(); configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList(); diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index d3298f61a..1aae0dfb3 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -39,18 +39,6 @@ public class SettingsTabExperimental : SettingsTab new GapSettingsEntry(5), - new SettingsEntry( - Loc.Localize( - "DalamudSettingEnablePluginUIAdditionalOptions", - "Add a button to the title bar of plugin windows to open additional options"), - Loc.Localize( - "DalamudSettingEnablePluginUIAdditionalOptionsHint", - "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), - c => c.EnablePluginUiAdditionalOptions, - (v, c) => c.EnablePluginUiAdditionalOptions = v), - - new GapSettingsEntry(5), - new ButtonSettingsEntry( Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"), Loc.Localize( @@ -66,6 +54,26 @@ public class SettingsTabExperimental : SettingsTab new DevPluginsSettingsEntry(), + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnableImGuiAsserts", + "Enable ImGui asserts"), + Loc.Localize( + "DalamudSettingEnableImGuiAssertsHint", + "If this setting is enabled, a window containing further details will be shown when an internal assertion in ImGui fails.\nWe recommend enabling this when developing plugins."), + c => Service.Get().ShowAsserts, + (v, _) => Service.Get().ShowAsserts = v), + + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnableImGuiAssertsAtStartup", + "Always enable ImGui asserts at startup"), + Loc.Localize( + "DalamudSettingEnableImGuiAssertsAtStartupHint", + "This will enable ImGui asserts every time the game starts."), + c => c.ImGuiAssertsEnabledAtStartup ?? false, + (v, c) => c.ImGuiAssertsEnabledAtStartup = v), + new GapSettingsEntry(5, true), new ThirdRepoSettingsEntry(), @@ -81,10 +89,24 @@ public class SettingsTabExperimental : SettingsTab (v, c) => c.ReShadeHandlingMode = v, fallbackValue: ReShadeHandlingMode.Default, warning: static rshm => - rshm is ReShadeHandlingMode.UnwrapReShade or ReShadeHandlingMode.None || - Service.Get().SwapChainHookMode == SwapChainHelper.HookMode.ByteCode - ? null - : "Current option will be ignored and no special ReShade handling will be done, because SwapChain vtable hook mode is set.") + { + var warning = string.Empty; + warning += rshm is ReShadeHandlingMode.UnwrapReShade or ReShadeHandlingMode.None || + Service.Get().SwapChainHookMode == SwapChainHelper.HookMode.ByteCode + ? string.Empty + : "Current option will be ignored and no special ReShade handling will be done, because SwapChain vtable hook mode is set."; + + if (ReShadeAddonInterface.ReShadeIsSignedByReShade) + { + warning += warning.Length > 0 ? "\n" : string.Empty; + warning += Loc.Localize( + "ReShadeNoAddonSupportNotificationContent", + "Your installation of ReShade does not have full addon support, and may not work with Dalamud and/or the game.\n" + + "Download and install ReShade with full addon-support."); + } + + return warning.Length > 0 ? warning : null; + }) { FriendlyEnumNameGetter = x => x switch { diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs index a582761ba..7f75dbf29 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs @@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal.Windows.Settings.Tabs; [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")] public class SettingsTabLook : SettingsTab { - private static readonly (string, float)[] GlobalUiScalePresets = + private static readonly (string, float)[] GlobalUiScalePresets = { ("80%##DalamudSettingsGlobalUiScaleReset96", 0.8f), ("100%##DalamudSettingsGlobalUiScaleReset12", 1f), @@ -107,7 +107,17 @@ public class SettingsTabLook : SettingsTab Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."), c => c.IsDocking, (v, c) => c.IsDocking = v), - + + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptions", + "Add a button to the title bar of plugin windows to open additional options"), + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptionsHint", + "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), + c => c.EnablePluginUiAdditionalOptions, + (v, c) => c.EnablePluginUiAdditionalOptions = v), + new SettingsEntry( Loc.Localize("DalamudSettingEnablePluginUISoundEffects", "Enable sound effects for plugin windows"), Loc.Localize("DalamudSettingEnablePluginUISoundEffectsHint", "This will allow you to enable or disable sound effects generated by plugin user interfaces.\nThis is affected by your in-game `System Sounds` volume settings."), @@ -122,22 +132,22 @@ public class SettingsTabLook : SettingsTab new SettingsEntry( Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"), - Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen."), + Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.\nDisabling this will also hide the Dalamud version text on the title screen."), c => c.ShowTsm, (v, c) => c.ShowTsm = v), - + new SettingsEntry( Loc.Localize("DalamudSettingInstallerOpenDefault", "Open the Plugin Installer to the \"Installed Plugins\" tab by default"), Loc.Localize("DalamudSettingInstallerOpenDefaultHint", "This will allow you to open the Plugin Installer to the \"Installed Plugins\" tab by default, instead of the \"Available Plugins\" tab."), c => c.PluginInstallerOpen == PluginInstallerOpenKind.InstalledPlugins, (v, c) => c.PluginInstallerOpen = v ? PluginInstallerOpenKind.InstalledPlugins : PluginInstallerOpenKind.AllPlugins), - + new SettingsEntry( Loc.Localize("DalamudSettingReducedMotion", "Reduce motions"), Loc.Localize("DalamudSettingReducedMotionHint", "This will suppress certain animations from Dalamud, such as the notification popup."), c => c.ReduceMotions ?? false, (v, c) => c.ReduceMotions = v), - + new SettingsEntry( Loc.Localize("DalamudSettingImeStateIndicatorOpacity", "IME State Indicator Opacity (CJK only)"), Loc.Localize("DalamudSettingImeStateIndicatorOpacityHint", "When any of CJK IMEs is in use, the state of IME will be shown with the opacity specified here."), diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index cfb1ff39f..4c5dc8b83 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -10,6 +10,7 @@ using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; +using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Internal; @@ -25,6 +26,7 @@ public class DevPluginsSettingsEntry : SettingsEntry private bool devPluginLocationsChanged; private string devPluginTempLocation = string.Empty; private string devPluginLocationAddError = string.Empty; + private FileDialogManager fileDialogManager = new(); public DevPluginsSettingsEntry() { @@ -68,7 +70,23 @@ public class DevPluginsSettingsEntry : SettingsEntry } } - ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add dev plugin load locations.\nThese can be either the directory or DLL path.")); + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add dev plugin load locations.\nThis must be a path to the plugin DLL.")); + + var locationSelect = Loc.Localize("DalamudDevPluginLocationSelect", "Select Dev Plugin DLL"); + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Folder, locationSelect)) + { + this.fileDialogManager.OpenFileDialog( + locationSelect, + ".dll", + (result, path) => + { + if (result) + { + this.devPluginTempLocation = path; + this.AddDevPlugin(); + } + }); + } ImGuiHelpers.ScaledDummy(5); @@ -167,26 +185,7 @@ public class DevPluginsSettingsEntry : SettingsEntry ImGui.NextColumn(); if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) - { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); - } - else if (!ValidDevPluginPath(this.devPluginTempLocation)) - { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginInvalid", "The entered value is not a valid path to a potential Dev Plugin.\nDid you mean to enter it as a custom plugin repository in the fields below instead?"); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); - } - else - { - this.devPluginLocations.Add(new DevPluginLocationSettings - { - Path = this.devPluginTempLocation.Replace("\"", string.Empty), - IsEnabled = true, - }); - this.devPluginLocationsChanged = true; - this.devPluginTempLocation = string.Empty; - } + this.AddDevPlugin(); } ImGui.Columns(1); @@ -197,6 +196,44 @@ public class DevPluginsSettingsEntry : SettingsEntry } } + public override void PostDraw() + { + this.fileDialogManager.Draw(); + } + private static bool ValidDevPluginPath(string path) - => Path.IsPathRooted(path) && (Path.GetExtension(path) == ".dll" || !Path.Exists(path) || Directory.Exists(path)); + => Path.IsPathRooted(path) && Path.GetExtension(path) == ".dll"; + + private void AddDevPlugin() + { + this.devPluginTempLocation = this.devPluginTempLocation.Trim('"'); + if (this.devPluginLocations.Any( + r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) + { + this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + } + else if (!ValidDevPluginPath(this.devPluginTempLocation)) + { + this.devPluginLocationAddError = Loc.Localize( + "DalamudDevPluginInvalid", + "The entered value is not a valid path to a potential Dev Plugin.\nDid you mean to enter it as a custom plugin repository in the fields below instead?"); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + return; + } + else + { + this.devPluginLocations.Add( + new DevPluginLocationSettings + { + Path = this.devPluginTempLocation, + IsEnabled = true, + }); + this.devPluginLocationsChanged = true; + this.devPluginTempLocation = string.Empty; + } + + // Enable ImGui asserts if a dev plugin is added, if no choice was made prior + Service.Get().ImGuiAssertsEnabledAtStartup ??= true; + } } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index c22dc76d3..cbb6998ac 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -5,8 +5,11 @@ using System.Numerics; using Dalamud.Configuration.Internal; using Dalamud.Console; using Dalamud.Game; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState; using Dalamud.Game.Gui; +using Dalamud.Game.Text; using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; @@ -14,12 +17,21 @@ using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Internal; using Dalamud.Plugin.Services; using Dalamud.Storage.Assets; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; +using Lumina.Text.ReadOnly; + +using Serilog; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + namespace Dalamud.Interface.Internal.Windows; /// @@ -39,17 +51,20 @@ internal class TitleScreenMenuWindow : Window, IDisposable private readonly IFontAtlas privateAtlas; private readonly Lazy myFontHandle; private readonly Lazy shadeTexture; + private readonly AddonLifecycleEventListener versionStringListener; private readonly Dictionary shadeEasings = new(); private readonly Dictionary moveEasings = new(); private readonly Dictionary logoEasings = new(); - + private readonly IConsoleVariable showTsm; private InOutCubic? fadeOutEasing; private State state = State.Hide; + private int lastLoadedPluginCount = -1; + /// /// Initializes a new instance of the class. /// @@ -61,6 +76,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable /// An instance of . /// An instance of . /// An instance of . + /// An instance of . public TitleScreenMenuWindow( ClientState clientState, DalamudConfiguration configuration, @@ -69,14 +85,15 @@ internal class TitleScreenMenuWindow : Window, IDisposable Framework framework, GameGui gameGui, TitleScreenMenu titleScreenMenu, - ConsoleManager consoleManager) + ConsoleManager consoleManager, + AddonLifecycle addonLifecycle) : base( "TitleScreenMenuOverlay", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) { this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true); - + this.clientState = clientState; this.configuration = configuration; this.gameGui = gameGui; @@ -109,6 +126,10 @@ internal class TitleScreenMenuWindow : Window, IDisposable framework.Update += this.FrameworkOnUpdate; this.scopedFinalizer.Add(() => framework.Update -= this.FrameworkOnUpdate); + + this.versionStringListener = new AddonLifecycleEventListener(AddonEvent.PreDraw, "_TitleRevision", this.OnVersionStringDraw); + addonLifecycle.RegisterListener(this.versionStringListener); + this.scopedFinalizer.Add(() => addonLifecycle.UnregisterListener(this.versionStringListener)); } private enum State @@ -117,7 +138,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable Show, FadeOut, } - + /// /// Gets or sets a value indicating whether drawing is allowed. /// @@ -146,7 +167,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable { if (!this.AllowDrawing || !this.showTsm.Value) return; - + var scale = ImGui.GetIO().FontGlobalScale; var entries = this.titleScreenMenu.PluginEntries; @@ -155,7 +176,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); Service.Get().OverrideGameCursor = !hovered; - + switch (this.state) { case State.Show: @@ -166,6 +187,23 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (!entry.IsShowConditionSatisfied()) continue; + if (entry.Texture.TryGetWrap(out var textureWrap, out var exception)) + { + if (textureWrap.Width != 64 && textureWrap.Height != 64) + { + Log.Error("Texture provided for ITitleScreenMenuEntry must be 64x64. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + } + + if (exception != null) + { + Log.Error(exception, "An exception occurred while attempting to get the texture wrap for a ITitleScreenMenuEntry. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing)) { moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400)); @@ -185,7 +223,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable moveEasing.Update(); var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale; - var pos = moveEasing.Value * finalPos; + var pos = moveEasing.ValueClamped * finalPos; // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment. if (moveEasing.IsDone) @@ -232,7 +270,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.ValueClamped)) { var i = 0; foreach (var entry in entries) @@ -240,6 +278,23 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (!entry.IsShowConditionSatisfied()) continue; + if (entry.Texture.TryGetWrap(out var textureWrap, out var exception)) + { + if (textureWrap.Width != 64 && textureWrap.Height != 64) + { + Log.Error($"Texture provided for ITitleScreenMenuEntry {entry.Name} must be 64x64. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + } + + if (exception != null) + { + Log.Error(exception, $"An exception occurred while attempting to get the texture wrap for ITitleScreenMenuEntry {entry.Name}. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale; this.DrawEntry(entry, i != 0, true, i == 0, false, false); @@ -298,7 +353,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable var initialCursor = ImGui.GetCursorPos(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.ValueClamped)) { var texture = this.shadeTexture.Value; ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height) * scale); @@ -348,14 +403,16 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.ValueClamped); } else if (isFirst) { ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f); } - ImGui.Image(entry.Texture.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); + // Wrap should always be valid at this point due to us checking the validity of the image each frame + var dalamudTextureWrap = entry.Texture.GetWrapOrEmpty(); + ImGui.Image(dalamudTextureWrap.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); if (overrideAlpha || isFirst) { ImGui.PopStyleVar(); @@ -369,32 +426,30 @@ internal class TitleScreenMenuWindow : Window, IDisposable var textHeight = ImGui.GetTextLineHeightWithSpacing(); var cursor = ImGui.GetCursorPos(); - cursor.Y += (entry.Texture.Height * scale / 2) - (textHeight / 2); + cursor.Y += (dalamudTextureWrap.Height * scale / 2) - (textHeight / 2); if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.ValueClamped : 0f); } // Drop shadow - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF000000)) - { - for (int i = 0, to = (int)Math.Ceiling(1 * scale); i < to; i++) - { - ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i)); - ImGui.Text(entry.Name); - } - } - ImGui.SetCursorPos(cursor); - ImGui.Text(entry.Name); + ImGuiHelpers.SeStringWrapped( + ReadOnlySeString.FromText(entry.Name), + new() + { + FontSize = TargetFontSizePx * ImGui.GetIO().FontGlobalScale, + Edge = true, + Shadow = true, + }); if (overrideAlpha) { ImGui.PopStyleVar(); } - initialCursor.Y += entry.Texture.Height * scale; + initialCursor.Y += dalamudTextureWrap.Height * scale; ImGui.SetCursorPos(initialCursor); return isHover; @@ -414,5 +469,49 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.IsOpen = false; } + private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args) + { + if (args is not AddonDrawArgs drawArgs) return; + + var addon = (AtkUnitBase*)drawArgs.Addon; + var textNode = addon->GetTextNodeById(3); + + // look and feel init. should be harmless to set. + textNode->TextFlags |= (byte)TextFlags.MultiLine; + textNode->AlignmentType = AlignmentType.TopLeft; + + var containsDalamudVersionString = textNode->OriginalTextPointer.Value == textNode->NodeText.StringPtr.Value; + if (!this.configuration.ShowTsm || !this.showTsm.Value) + { + if (containsDalamudVersionString) + textNode->SetText(addon->AtkValues[1].String); + this.lastLoadedPluginCount = -1; + return; + } + + var pm = Service.GetNullable(); + var count = pm?.LoadedPluginCount ?? 0; + + // Avoid rebuilding the string every frame. + if (containsDalamudVersionString && count == this.lastLoadedPluginCount) + return; + this.lastLoadedPluginCount = count; + + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String.Value)).Append("\n\n"); + lssb.PushEdgeColorType(701).PushColorType(539) + .Append(SeIconChar.BoxedLetterD.ToIconChar()) + .PopColorType().PopEdgeColorType(); + lssb.Append($" Dalamud: {Util.GetScmVersion()}"); + + lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded"); + + if (pm?.SafeMode is true) + lssb.PushColorType(17).Append(" [SAFE MODE]").PopColorType(); + + textNode->SetText(lssb.GetViewAsSpan()); + LSeStringBuilder.SharedPool.Return(lssb); + } + private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync(); } diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs index 0a9e9072e..803f6b82c 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs @@ -72,6 +72,7 @@ public interface IFontHandle : IDisposable /// /// fontHandle.Push(); /// ImGui.TextUnformatted("Test 2"); + /// fontHandle.Pop(); /// /// Push a font between two choices. /// diff --git a/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs b/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs new file mode 100644 index 000000000..12e312b3e --- /dev/null +++ b/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Storage.Assets; + +namespace Dalamud.Interface.Textures; + +/// +/// Wraps a dalamud texture allowing interoperability with certain services. Only use this if you need to provide a texture that has been created or rented as a ISharedImmediateTexture. +/// +public class ForwardingSharedImmediateTexture : ISharedImmediateTexture +{ + private readonly IDalamudTextureWrap textureWrap; + + /// + /// Initializes a new instance of the class. + /// + /// A textureWrap that has been created or provided by RentAsync. + public ForwardingSharedImmediateTexture(IDalamudTextureWrap textureWrap) + { + this.textureWrap = textureWrap; + } + + /// + public IDalamudTextureWrap GetWrapOrEmpty() + { + return this.textureWrap; + } + + /// + public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap = null) + { + return this.textureWrap; + } + + /// + public bool TryGetWrap(out IDalamudTextureWrap? texture, out Exception? exception) + { + texture = this.textureWrap; + exception = null; + return true; + } + + /// + public Task RentAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(this.textureWrap); + } +} diff --git a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs index 591b9846c..b6aa4da83 100644 --- a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs @@ -11,7 +11,6 @@ namespace Dalamud.Interface.Textures; /// A texture with a backing instance of that is shared across multiple /// requesters. /// -/// Calling on this interface is a no-op. /// and may stop returning the intended texture at any point. /// Use to lock the texture for use in any thread for any duration. /// @@ -25,7 +24,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// will be ignored. + /// will be ignored, including the cases when the returned texture wrap + /// is passed to a function with leaveWrapOpen parameter. /// If the texture is unavailable for any reason, then the returned instance of /// will point to an empty texture instead. /// @@ -42,7 +42,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// will be ignored. + /// will be ignored, including the cases when the returned texture wrap + /// is passed to a function with leaveWrapOpen parameter. /// If the texture is unavailable for any reason, then will be returned. /// [return: NotNullIfNotNull(nameof(defaultWrap))] @@ -59,7 +60,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// on the returned will be ignored. + /// on the returned will be ignored, including + /// the cases when the returned texture wrap is passed to a function with leaveWrapOpen parameter. /// /// Thrown when called outside the UI thread. bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception); diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs index c71d83fe8..5f9925ed3 100644 --- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs @@ -171,17 +171,14 @@ internal abstract class SharedImmediateTexture /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IDalamudTextureWrap GetWrapOrEmpty() => this.GetWrapOrDefault(Service.Get().Empty4X4); + public IDalamudTextureWrap GetWrapOrEmpty() => + this.TryGetWrap(out var texture, out _) ? texture : Service.Get().Empty4X4; /// [return: NotNullIfNotNull(nameof(defaultWrap))] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap) - { - if (!this.TryGetWrap(out var texture, out _)) - texture = null; - return texture ?? defaultWrap; - } + public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap) => + this.TryGetWrap(out var texture, out _) ? texture : defaultWrap; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs index a744114e8..829b8d0c5 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps.Internal; using Dalamud.Plugin.Internal.Types; @@ -10,6 +9,8 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.TerraFxCom; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; + using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -18,6 +19,69 @@ namespace Dalamud.Interface.Textures.Internal; /// Service responsible for loading and disposing ImGui texture wraps. internal sealed partial class TextureManager { + /// + unsafe nint ITextureProvider.ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen) => + (nint)this.ConvertToKernelTexture(wrap, leaveWrapOpen); + + /// + public unsafe Texture* ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false) + { + using var wrapAux = new WrapAux(wrap, leaveWrapOpen); + + var flags = TextureFlags.TextureType2D; + if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_IMMUTABLE) + flags |= TextureFlags.Immutable; + if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_DYNAMIC) + flags |= TextureFlags.ReadWrite; + if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) != 0) + flags |= TextureFlags.CpuRead; + if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET) != 0) + flags |= TextureFlags.TextureRenderTarget; + if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_DEPTH_STENCIL) != 0) + flags |= TextureFlags.TextureDepthStencil; + if (wrapAux.Desc.ArraySize != 1) + throw new NotSupportedException("TextureArray2D is currently not supported."); + + var gtex = Texture.CreateTexture2D( + (int)wrapAux.Desc.Width, + (int)wrapAux.Desc.Height, + (byte)wrapAux.Desc.MipLevels, + 0, // instructs the game to skip preprocessing it seems + flags, + 0); + + // Kernel::Texture owns these resources. We're passing the ownership to them. + wrapAux.TexPtr->AddRef(); + wrapAux.SrvPtr->AddRef(); + + // Not sure this is needed + gtex->TextureFormat = wrapAux.Desc.Format switch + { + DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TextureFormat.R32G32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TextureFormat.R16G16_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TextureFormat.R32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TextureFormat.D24_UNORM_S8_UINT, + DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TextureFormat.D16_UNORM, + DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TextureFormat.A8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TextureFormat.BC1_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TextureFormat.BC2_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TextureFormat.BC3_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TextureFormat.BC5_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TextureFormat.B4G4R4A4_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TextureFormat.B5G5R5A1_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TextureFormat.B8G8R8X8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TextureFormat.BC7_UNORM, + _ => 0, + }; + + gtex->D3D11Texture2D = wrapAux.TexPtr; + gtex->D3D11ShaderResourceView = wrapAux.SrvPtr; + return gtex; + } + /// bool ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) => this.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat); diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs index 156ffa56f..d7e185b68 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs @@ -4,6 +4,8 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using BitFaster.Caching.Lru; @@ -65,12 +67,21 @@ internal sealed partial class TextureManager private readonly ConcurrentDictionary<(Assembly, string), SharedImmediateTexture> manifestResourceDict = new(); private readonly HashSet invalidatedTextures = new(); + private readonly Thread sharedTextureReleaseThread; + + private readonly CancellationTokenSource disposingCancellationTokenSource = new(); + /// Initializes a new instance of the class. /// An instance of . public SharedTextureManager(TextureManager textureManager) { this.textureManager = textureManager; - this.textureManager.framework.Update += this.FrameworkOnUpdate; + + this.sharedTextureReleaseThread = new(this.ReleaseSharedTextures) + { + Priority = ThreadPriority.Lowest, + }; + this.sharedTextureReleaseThread.Start(); } /// Gets all the loaded textures from game resources. @@ -90,14 +101,20 @@ internal sealed partial class TextureManager Justification = "Debug use only; users are expected to lock around this")] public ICollection ForDebugInvalidatedTextures => this.invalidatedTextures; + private SharedTextureManager NonDisposed => + this.disposingCancellationTokenSource.IsCancellationRequested + ? throw new ObjectDisposedException(nameof(SharedTextureManager)) + : this; + /// public void Dispose() { - this.textureManager.framework.Update -= this.FrameworkOnUpdate; + this.disposingCancellationTokenSource.Cancel(); this.lookupCache.Clear(); ReleaseSelfReferences(this.gameDict); ReleaseSelfReferences(this.fileDict); ReleaseSelfReferences(this.manifestResourceDict); + this.sharedTextureReleaseThread.Join(); return; static void ReleaseSelfReferences(ConcurrentDictionary dict) @@ -111,12 +128,14 @@ internal sealed partial class TextureManager /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromGameIcon(in GameIconLookup lookup) => - this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue)); + this.NonDisposed.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetFromGameIcon(in GameIconLookup lookup, [NotNullWhen(true)] out SharedImmediateTexture.PureImpl? texture) { + ObjectDisposedException.ThrowIf(this.disposingCancellationTokenSource.IsCancellationRequested, this); + texture = null; if (!this.lookupCache.TryGet(lookup, out var path)) @@ -134,29 +153,29 @@ internal sealed partial class TextureManager /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromGame(string path) => - this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder) + this.NonDisposed.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFile(string path) => - this.GetFromFile(new FileInfo(path)); + this.NonDisposed.GetFromFile(new FileInfo(path)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFile(FileInfo file) => - this.GetFromFileAbsolute(file.FullName); + this.NonDisposed.GetFromFileAbsolute(file.FullName); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFileAbsolute(string fullPath) => - this.fileDict.GetOrAdd(fullPath, FileSystemSharedImmediateTexture.CreatePlaceholder) + this.NonDisposed.fileDict.GetOrAdd(fullPath, FileSystemSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromManifestResource(Assembly assembly, string name) => - this.manifestResourceDict.GetOrAdd( + this.NonDisposed.manifestResourceDict.GetOrAdd( (assembly, name), ManifestResourceSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; @@ -166,6 +185,9 @@ internal sealed partial class TextureManager /// The path to invalidate. public void FlushFromGameCache(string path) { + if (this.disposingCancellationTokenSource.IsCancellationRequested) + return; + if (this.gameDict.TryRemove(path, out var r)) { if (r.ReleaseSelfReference(true) != 0) @@ -178,19 +200,33 @@ internal sealed partial class TextureManager [MethodImpl(MethodImplOptions.AggressiveInlining)] private string GetIconPathByValue(GameIconLookup lookup) => - this.textureManager.TryGetIconPath(lookup, out var path) ? path : throw new IconNotFoundException(lookup); + this.NonDisposed.textureManager.TryGetIconPath(lookup, out var path) + ? path + : throw new IconNotFoundException(lookup); - private void FrameworkOnUpdate(IFramework unused) + private void ReleaseSharedTextures() { - RemoveFinalReleased(this.gameDict); - RemoveFinalReleased(this.fileDict); - RemoveFinalReleased(this.manifestResourceDict); - - // ReSharper disable once InconsistentlySynchronizedField - if (this.invalidatedTextures.Count != 0) + while (!this.disposingCancellationTokenSource.IsCancellationRequested) { - lock (this.invalidatedTextures) - this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate); + RemoveFinalReleased(this.gameDict); + RemoveFinalReleased(this.fileDict); + RemoveFinalReleased(this.manifestResourceDict); + + // ReSharper disable once InconsistentlySynchronizedField + if (this.invalidatedTextures.Count != 0) + { + lock (this.invalidatedTextures) + this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate); + } + + try + { + this.textureManager.framework.DelayTicks(60).Wait(this.disposingCancellationTokenSource.Token); + } + catch (Exception) + { + // who cares + } } return; @@ -198,13 +234,13 @@ internal sealed partial class TextureManager [MethodImpl(MethodImplOptions.AggressiveInlining)] static void RemoveFinalReleased(ConcurrentDictionary dict) { - if (!dict.IsEmpty) + if (dict.IsEmpty) + return; + + foreach (var (k, v) in dict) { - foreach (var (k, v) in dict) - { - if (TextureFinalReleasePredicate(v)) - _ = dict.TryRemove(k, out _); - } + if (TextureFinalReleasePredicate(v)) + _ = dict.TryRemove(k, out _); } } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs index 245a2a9ac..df84f9545 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs @@ -91,7 +91,7 @@ internal sealed partial class TextureManager throw new NullReferenceException($"{nameof(path)} cannot be null."); using var wrapAux = new WrapAux(wrap, true); - var pathTemp = Util.GetTempFileNameForFileReplacement(path); + var pathTemp = Util.GetReplaceableFileName(path); var trashfire = new List(); try { diff --git a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs index 68e2dde47..c62ad61b4 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs @@ -134,6 +134,10 @@ internal sealed class TextureManagerPluginScoped : $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name})"; } + /// + public unsafe nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false) => + (nint)this.ManagerOrThrow.ConvertToKernelTexture(wrap, leaveWrapOpen); + /// public IDalamudTextureWrap CreateEmpty( RawImageSpecification specs, diff --git a/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs index 8b0516e03..7d6ff8580 100644 --- a/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs +++ b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs @@ -37,7 +37,11 @@ public abstract class ForwardingTextureWrap : IDalamudTextureWrap public Vector2 Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); + get + { + var wrap = this.GetWrap(); + return new(wrap.Width, wrap.Height); + } } /// diff --git a/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs index 0dd5c9f25..3bb984be8 100644 --- a/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs +++ b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs @@ -1,20 +1,13 @@ -using Dalamud.Interface.Internal; - namespace Dalamud.Interface.Textures.TextureWraps.Internal; /// A texture wrap that ignores calls. -internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap +/// The inner wrap. +internal class DisposeSuppressingTextureWrap(IDalamudTextureWrap innerWrap) : ForwardingTextureWrap { - private readonly IDalamudTextureWrap innerWrap; - - /// Initializes a new instance of the class. - /// The inner wrap. - public DisposeSuppressingTextureWrap(IDalamudTextureWrap wrap) => this.innerWrap = wrap; - /// protected override bool TryGetWrap(out IDalamudTextureWrap? wrap) { - wrap = this.innerWrap; + wrap = innerWrap; return true; } } diff --git a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs index 6f98b9757..586d65559 100644 --- a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs @@ -3,8 +3,7 @@ using System.Linq; using System.Reflection; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; @@ -67,7 +66,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu } } } - + /// /// Adds a new entry to the title screen menu. /// @@ -76,13 +75,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public ITitleScreenMenuEntry AddPluginEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public ITitleScreenMenuEntry AddPluginEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -103,13 +97,13 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu } /// - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { return this.AddPluginEntry(text, texture, onTriggered); } /// - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { return this.AddPluginEntry(priority, text, texture, onTriggered); } @@ -123,13 +117,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public ITitleScreenMenuEntry AddPluginEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public ITitleScreenMenuEntry AddPluginEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -166,13 +155,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -199,15 +183,10 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// Thrown when the texture provided does not match the required resolution(64x64). internal TitleScreenMenuEntry AddEntryCore( string text, - IDalamudTextureWrap texture, + ISharedImmediateTexture texture, Action onTriggered, params VirtualKey[] showConditionKeys) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -240,7 +219,7 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS { [ServiceManager.ServiceDependency] private readonly TitleScreenMenu titleScreenMenuService = Service.Get(); - + private readonly List pluginEntries = new(); /// @@ -254,25 +233,25 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS this.titleScreenMenuService.RemoveEntry(entry); } } - + /// - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { var entry = this.titleScreenMenuService.AddPluginEntry(text, texture, onTriggered); this.pluginEntries.Add(entry); return entry; } - + /// - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { var entry = this.titleScreenMenuService.AddPluginEntry(priority, text, texture, onTriggered); this.pluginEntries.Add(entry); return entry; } - + /// public void RemoveEntry(IReadOnlyTitleScreenMenuEntry entry) { diff --git a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs index 2acb275ad..a98d32770 100644 --- a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs +++ b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs @@ -4,8 +4,7 @@ using System.Linq; using System.Reflection; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; namespace Dalamud.Interface; @@ -64,7 +63,7 @@ public interface IReadOnlyTitleScreenMenuEntry /// /// Gets the texture of this entry. /// - IDalamudTextureWrap Texture { get; } + ISharedImmediateTexture Texture { get; } } /// @@ -87,7 +86,7 @@ public class TitleScreenMenuEntry : ITitleScreenMenuEntry Assembly? callingAssembly, ulong priority, string text, - IDalamudTextureWrap texture, + ISharedImmediateTexture texture, Action onTriggered, IEnumerable? showConditionKeys = null) { @@ -106,8 +105,8 @@ public class TitleScreenMenuEntry : ITitleScreenMenuEntry public string Name { get; set; } /// - public IDalamudTextureWrap Texture { get; set; } - + public ISharedImmediateTexture Texture { get; set; } + /// public bool IsInternal { get; set; } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 1fd615b7e..801253003 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -9,7 +9,6 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Plugin.Internal.Types; @@ -725,8 +724,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder ImGui.End(); } - var snapshot = this.Draw is null ? null : ImGuiManagedAsserts.GetSnapshot(); - try { this.Draw?.InvokeSafely(); @@ -740,10 +737,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder this.hasErrorWindow = true; } - // Only if Draw was successful - if (this.Draw is not null && snapshot is not null) - ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); - this.FrameCount++; if (DoStats) diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index f32c4225d..37237438e 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -8,7 +8,11 @@ using System.Text; using System.Text.Unicode; using Dalamud.Configuration.Internal; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.ImGuiBackend.InputHandler; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility.Raii; @@ -109,7 +113,7 @@ public static partial class ImGuiHelpers /// /// The size of the indent. public static void ScaledIndent(float size) => ImGui.Indent(size * GlobalScale); - + /// /// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale. /// @@ -165,19 +169,70 @@ public static partial class ImGuiHelpers /// /// The text to show. /// The text to copy when clicked. - public static void ClickToCopyText(string text, string? textCopy = null) + /// The color of the text. + public static void ClickToCopyText(string text, string? textCopy = null, Vector4? color = null) { textCopy ??= text; - ImGui.Text($"{text}"); + + using (var col = new ImRaii.Color()) + { + if (color.HasValue) + { + col.Push(ImGuiCol.Text, color.Value); + } + + ImGui.TextUnformatted($"{text}"); + } + if (ImGui.IsItemHovered()) { ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); + + using (ImRaii.Tooltip()) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(textCopy); + } } - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(textCopy); + } } + /// Draws a SeString. + /// SeString to draw. + /// Initial rendering style. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public static SeStringDrawResult SeStringWrapped( + ReadOnlySpan sss, + scoped in SeStringDrawParams style = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + Service.Get().Draw(sss, style, imGuiId, buttonFlags); + + /// Creates and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Newline characters will be normalized to . + /// Initial rendering style. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public static SeStringDrawResult CompileSeStringWrapped( + string text, + scoped in SeStringDrawParams style = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + Service.Get().CompileAndDrawWrapped(text, style, imGuiId, buttonFlags); + /// /// Write unformatted text wrapped. /// @@ -233,7 +288,7 @@ public static partial class ImGuiHelpers foreach (ref var kp in new Span((void*)font->KerningPairs.Data, font->KerningPairs.Size)) kp.AdvanceXAdjustment = rounder(kp.AdvanceXAdjustment * scale); - + foreach (ref var fkp in new Span((void*)font->FrequentKerningPairs.Data, font->FrequentKerningPairs.Size)) fkp = rounder(fkp * scale); } @@ -482,7 +537,7 @@ public static partial class ImGuiHelpers builder.BuildRanges(out var vec); return new ReadOnlySpan((void*)vec.Data, vec.Size).ToArray(); } - + /// public static ushort[] CreateImGuiRangesFrom(params UnicodeRange[] ranges) => CreateImGuiRangesFrom((IEnumerable)ranges); @@ -565,7 +620,7 @@ public static partial class ImGuiHelpers ImGuiNative.ImGuiInputTextCallbackData_InsertChars(data, 0, pBuf, pBuf + len); ImGuiNative.ImGuiInputTextCallbackData_SelectAll(data); } - + /// /// Finds the corresponding ImGui viewport ID for the given window handle. /// diff --git a/Dalamud/Interface/Utility/ImGuiId.cs b/Dalamud/Interface/Utility/ImGuiId.cs new file mode 100644 index 000000000..0231f3749 --- /dev/null +++ b/Dalamud/Interface/Utility/ImGuiId.cs @@ -0,0 +1,176 @@ +using System.Runtime.CompilerServices; + +using ImGuiNET; + +namespace Dalamud.Interface.Utility; + +/// Represents any type of ImGui ID. +public readonly ref struct ImGuiId +{ + /// Type of the ID. + public readonly Type IdType; + + /// Numeric ID. Valid if is . + public readonly nint Numeric; + + /// UTF-16 string ID. Valid if is . + public readonly ReadOnlySpan U16; + + /// UTF-8 string ID. Valid if is . + public readonly ReadOnlySpan U8; + + /// Initializes a new instance of the struct. + /// A numeric ID, or 0 to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(nint id) + { + if (id != 0) + (this.IdType, this.Numeric) = (Type.Numeric, id); + } + + /// Initializes a new instance of the struct. + /// A UTF-16 string ID, or to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(ReadOnlySpan id) + { + if (!id.IsEmpty) + { + this.IdType = Type.U16; + this.U16 = id; + } + } + + /// Initializes a new instance of the struct. + /// A UTF-8 string ID, or to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(ReadOnlySpan id) + { + if (!id.IsEmpty) + { + this.IdType = Type.U8; + this.U8 = id; + } + } + + /// Possible types for an ImGui ID. + public enum Type + { + /// No ID is specified. + None, + + /// field is used. + Numeric, + + /// field is used. + U16, + + /// field is used. + U8, + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(void* id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(float id) => new(*(int*)&id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(double id) => new(*(nint*)&id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(sbyte id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(byte id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(char id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(short id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ushort id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(int id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(uint id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(nint id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(nuint id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Span id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlySpan id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Memory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlyMemory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(char[] id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(string id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Span id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlySpan id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Memory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlyMemory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(byte[] id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(ImGuiId id) => !id.IsEmpty(); + + /// Determines if no ID is stored. + /// true if no ID is stored. + public bool IsEmpty() => this.IdType switch + { + Type.None => true, + Type.Numeric => this.Numeric == 0, + Type.U16 => this.U16.IsEmpty, + Type.U8 => this.U8.IsEmpty, + _ => true, + }; + + /// Pushes ID if any is stored. + /// true if any ID is pushed. + public unsafe bool PushId() + { + switch (this.IdType) + { + case Type.Numeric: + ImGuiNative.igPushID_Ptr((void*)this.Numeric); + return true; + case Type.U16: + fixed (void* p = this.U16) + ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + (this.U16.Length * 2)); + return true; + case Type.U8: + fixed (void* p = this.U8) + ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + this.U8.Length); + return true; + case Type.None: + default: + return false; + } + } +} diff --git a/Dalamud/Interface/Utility/Raii/EndObjects.cs b/Dalamud/Interface/Utility/Raii/EndObjects.cs index 401af5415..261c071c3 100644 --- a/Dalamud/Interface/Utility/Raii/EndObjects.cs +++ b/Dalamud/Interface/Utility/Raii/EndObjects.cs @@ -77,6 +77,30 @@ public static partial class ImRaii return new EndUnconditionally(ImGui.EndTooltip, true); } + /// + /// Pushes the item width for the next widget and returns an IDisposable that pops + /// the width when done. + /// + /// The width to set the next widget to. + /// An for use in a using statement. + public static IEndObject ItemWidth(float width) + { + ImGui.PushItemWidth(width); + return new EndUnconditionally(ImGui.PopItemWidth, true); + } + + /// + /// Pushes the item wrapping width for the next string written and returns an IDisposable + /// that pops the wrap width when done. + /// + /// The wrap width to set the next text written to. + /// An for use in a using statement. + public static IEndObject TextWrapPos(float pos) + { + ImGui.PushTextWrapPos(pos); + return new EndUnconditionally(ImGui.PopTextWrapPos, true); + } + public static IEndObject ListBox(string label) => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label)); @@ -110,7 +134,7 @@ public static partial class ImRaii public static unsafe IEndObject TabItem(string label, ImGuiTabItemFlags flags) { ArgumentNullException.ThrowIfNull(label); - + // One-off for now, we should make this into a generic solution if we need it more often const int labelMaxAlloc = 2048; diff --git a/Dalamud/Interface/Windowing/Persistence/PresetModel.cs b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs new file mode 100644 index 000000000..f7910e0b2 --- /dev/null +++ b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Dalamud.Interface.Windowing.Persistence; + +/// +/// Class representing a Window System preset. +/// +internal class PresetModel +{ + /// + /// Gets or sets the ID of this preset. + /// + [JsonProperty("id")] + public Guid Id { get; set; } + + /// + /// Gets or sets the name of this preset. + /// + [JsonProperty("n")] + public string Name { get; set; } = "New Preset"; + + /// + /// Gets or sets a dictionary containing the windows in the preset, mapping their ID to the preset. + /// + [JsonProperty("w")] + public Dictionary Windows { get; set; } = new(); + + /// + /// Class representing a window in a preset. + /// + internal class PresetWindow + { + /// + /// Gets or sets a value indicating whether the window is pinned. + /// + [JsonProperty("p")] + public bool IsPinned { get; set; } + + /// + /// Gets or sets a value indicating whether the window is clickthrough. + /// + [JsonProperty("ct")] + public bool IsClickThrough { get; set; } + + /// + /// Gets or sets the window's opacity override. + /// + [JsonProperty("a")] + public float? Alpha { get; set; } + + /// + /// Gets a value indicating whether this preset is in the default state. + /// + public bool IsDefault => + !this.IsPinned && + !this.IsClickThrough && + !this.Alpha.HasValue; + } +} diff --git a/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs new file mode 100644 index 000000000..a64928003 --- /dev/null +++ b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs @@ -0,0 +1,57 @@ +using Dalamud.Configuration.Internal; + +namespace Dalamud.Interface.Windowing.Persistence; + +/// +/// Class handling persistence for window system windows. +/// +[ServiceManager.EarlyLoadedService] +internal class WindowSystemPersistence : IServiceType +{ + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration config = Service.Get(); + + /// + /// Initializes a new instance of the class. + /// + [ServiceManager.ServiceConstructor] + public WindowSystemPersistence() + { + } + + /// + /// Gets the active window system preset. + /// + public PresetModel ActivePreset => this.config.DefaultUiPreset; + + /// + /// Get or add a window to the active preset. + /// + /// The ID of the window. + /// The preset window instance, or null if the preset does not contain this window. + public PresetModel.PresetWindow? GetWindow(uint id) + { + return this.ActivePreset.Windows.TryGetValue(id, out var window) ? window : null; + } + + /// + /// Persist the state of a window to the active preset. + /// + /// The ID of the window. + /// The preset window instance. + public void SaveWindow(uint id, PresetModel.PresetWindow window) + { + // If the window is in the default state, don't save it to avoid saving every possible window + // if the user has not customized anything. + if (window.IsDefault) + { + this.ActivePreset.Windows.Remove(id); + } + else + { + this.ActivePreset.Windows[id] = window; + } + + this.config.QueueSave(); + } +} diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 7bf5a0363..b3a505c1d 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -1,19 +1,24 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using CheapLoc; -using Dalamud.Configuration.Internal; + using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; +using Dalamud.Interface.Windowing.Persistence; using Dalamud.Logging.Internal; using FFXIVClientStructs.FFXIV.Client.UI; + using ImGuiNET; + using PInvoke; namespace Dalamud.Interface.Windowing; @@ -26,7 +31,7 @@ public abstract class Window private static readonly ModuleLog Log = new("WindowSystem"); private static bool wasEscPressedLastFrame = false; - + private bool internalLastIsOpen = false; private bool internalIsOpen = false; private bool internalIsPinned = false; @@ -35,15 +40,19 @@ public abstract class Window private float? internalAlpha = null; private bool nextFrameBringToFront = false; + private bool hasInitializedFromPreset = false; + private PresetModel.PresetWindow? presetWindow; + private bool presetDirty = false; + /// /// Initializes a new instance of the class. /// /// The name/ID of this window. /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. + /// append a unique ID to it by specifying it after "###" behind the window title. /// /// The of this window. - /// Whether or not this window should be limited to the main game window. + /// Whether this window should be limited to the main game window. protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) { this.WindowName = name; @@ -51,6 +60,33 @@ public abstract class Window this.ForceMainWindow = forceMainWindow; } + /// + /// Flags to control window behavior. + /// + [Flags] + internal enum WindowDrawFlags + { + /// + /// Nothing. + /// + None = 0, + + /// + /// Enable window opening/closing sound effects. + /// + UseSoundEffects = 1 << 0, + + /// + /// Hook into the game's focus management. + /// + UseFocusManagement = 1 << 1, + + /// + /// Enable the built-in "additional options" menu on the title bar. + /// + UseAdditionalOptions = 1 << 2, + } + /// /// Gets or sets the namespace of the window. /// @@ -87,7 +123,7 @@ public abstract class Window /// Gets or sets a value representing the sound effect id to be played when the window is closed. /// public uint OnCloseSfxId { get; set; } = 24u; - + /// /// Gets or sets the position of this window. /// @@ -155,7 +191,7 @@ public abstract class Window /// /// Gets or sets a list of available title bar buttons. - /// + /// /// If or are set to true, and this features is not /// disabled globally by the user, an internal title bar button to manage these is added when drawing, but it will /// not appear in this collection. If you wish to remove this button, set both of these values to false. @@ -170,7 +206,7 @@ public abstract class Window get => this.internalIsOpen; set => this.internalIsOpen = value; } - + private bool CanShowCloseButton => this.ShowCloseButton && !this.internalIsClickthrough; /// @@ -267,17 +303,16 @@ public abstract class Window public virtual void Update() { } - + /// /// Draw the window via ImGui. /// - /// Configuration instance used to check if certain window management features should be enabled. - internal void DrawInternal(DalamudConfiguration? configuration) + /// Flags controlling window behavior. + /// Handler for window persistence data. + internal void DrawInternal(WindowDrawFlags internalDrawFlags, WindowSystemPersistence? persistence) { this.PreOpenCheck(); - var doSoundEffects = configuration?.EnablePluginUISoundEffects ?? false; - if (!this.IsOpen) { if (this.internalIsOpen != this.internalLastIsOpen) @@ -286,8 +321,9 @@ public abstract class Window this.OnClose(); this.IsFocused = false; - - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnCloseSfxId, 0, 0, 0); + + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds) + UIGlobals.PlaySoundEffect(this.OnCloseSfxId); } return; @@ -301,13 +337,16 @@ public abstract class Window if (hasNamespace) ImGui.PushID(this.Namespace); - + + this.PreHandlePreset(persistence); + if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) { this.internalLastIsOpen = this.internalIsOpen; this.OnOpen(); - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnOpenSfxId, 0, 0, 0); + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds) + UIGlobals.PlaySoundEffect(this.OnOpenSfxId); } this.PreDraw(); @@ -340,6 +379,18 @@ public abstract class Window if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags)) { + ImGuiNativeAdditions.igCustom_WindowSetInheritNoInputs(this.internalIsClickthrough); + + // Not supported yet on non-main viewports + if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) && + ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID) + { + this.internalAlpha = null; + this.internalIsPinned = false; + this.internalIsClickthrough = false; + this.presetDirty = true; + } + // Draw the actual window contents try { @@ -347,15 +398,15 @@ public abstract class Window } catch (Exception ex) { - Log.Error(ex, $"Error during Draw(): {this.WindowName}"); + Log.Error(ex, "Error during Draw(): {WindowName}", this.WindowName); } } - var additionsPopupName = "WindowSystemContextActions"; + const string additionsPopupName = "WindowSystemContextActions"; var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) && !flags.HasFlag(ImGuiWindowFlags.NoTitleBar); var showAdditions = (this.AllowPinning || this.AllowClickthrough) && - (configuration?.EnablePluginUiAdditionalOptions ?? true) && + internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) && flagsApplicableForTitleBarIcons; if (showAdditions) { @@ -364,10 +415,10 @@ public abstract class Window if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove)) { var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport(); - + if (!isAvailable) ImGui.BeginDisabled(); - + if (this.internalIsClickthrough) ImGui.BeginDisabled(); @@ -375,36 +426,51 @@ public abstract class Window { var showAsPinned = this.internalIsPinned || this.internalIsClickthrough; if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned)) + { this.internalIsPinned = showAsPinned; + this.presetDirty = true; + } + + ImGuiComponents.HelpMarker( + Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them, nor will they close when escape is pressed.")); } if (this.internalIsClickthrough) ImGui.EndDisabled(); if (this.AllowClickthrough) - ImGui.Checkbox(Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), ref this.internalIsClickthrough); + { + if (ImGui.Checkbox( + Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), + ref this.internalIsClickthrough)) + { + this.presetDirty = true; + } + + ImGuiComponents.HelpMarker( + Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert.")); + } var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f; if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f, 100f)) { this.internalAlpha = alpha / 100f; + this.presetDirty = true; } ImGui.SameLine(); if (ImGui.Button(Loc.Localize("WindowSystemContextActionReset", "Reset"))) { this.internalAlpha = null; + this.presetDirty = true; } if (isAvailable) { ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("WindowSystemContextActionClickthroughDisclaimer", - "Open this menu again to disable clickthrough.")); - ImGui.TextColored(ImGuiColors.DalamudGrey, - Loc.Localize("WindowSystemContextActionDisclaimer", - "These options may not work for all plugins at the moment.")); + "Open this menu again by clicking the three dashes to disable clickthrough.")); } else { @@ -415,7 +481,7 @@ public abstract class Window if (!isAvailable) ImGui.EndDisabled(); - + ImGui.EndPopup(); } @@ -435,6 +501,7 @@ public abstract class Window Click = _ => { this.internalIsClickthrough = false; + this.presetDirty = false; ImGui.OpenPopup(additionsPopupName); }, Priority = int.MinValue, @@ -457,8 +524,7 @@ public abstract class Window this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); - var isAllowed = configuration?.IsFocusManagementEnabled ?? false; - if (isAllowed) + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.internalIsPinned) { var escapeDown = Service.Get()[VirtualKey.ESCAPE]; if (escapeDown && this.IsFocused && !wasEscPressedLastFrame && this.RespectCloseHotkey) @@ -476,6 +542,8 @@ public abstract class Window this.PostDraw(); + this.PostHandlePreset(persistence); + if (hasNamespace) ImGui.PopID(); } @@ -511,7 +579,7 @@ public abstract class Window { ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); } - + // Manually set alpha takes precedence, if devs don't want that, they should turn it off if (this.internalAlpha.HasValue) { @@ -519,21 +587,65 @@ public abstract class Window } } + private void PreHandlePreset(WindowSystemPersistence? persistence) + { + if (persistence == null || this.hasInitializedFromPreset) + return; + + var id = ImGui.GetID(this.WindowName); + this.presetWindow = persistence.GetWindow(id); + + this.hasInitializedFromPreset = true; + + // Fresh preset - don't apply anything + if (this.presetWindow == null) + { + this.presetWindow = new PresetModel.PresetWindow(); + this.presetDirty = true; + return; + } + + this.internalIsPinned = this.presetWindow.IsPinned; + this.internalIsClickthrough = this.presetWindow.IsClickThrough; + this.internalAlpha = this.presetWindow.Alpha; + } + + private void PostHandlePreset(WindowSystemPersistence? persistence) + { + if (persistence == null) + return; + + Debug.Assert(this.presetWindow != null, "this.presetWindow != null"); + + if (this.presetDirty) + { + this.presetWindow.IsPinned = this.internalIsPinned; + this.presetWindow.IsClickThrough = this.internalIsClickthrough; + this.presetWindow.Alpha = this.internalAlpha; + + var id = ImGui.GetID(this.WindowName); + persistence.SaveWindow(id, this.presetWindow!); + this.presetDirty = false; + + Log.Verbose("Saved preset for {WindowName}", this.WindowName); + } + } + private unsafe void DrawTitleBarButtons(void* window, ImGuiWindowFlags flags, Vector4 titleBarRect, IEnumerable buttons) { ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false); - + var style = ImGui.GetStyle(); var fontSize = ImGui.GetFontSize(); var drawList = ImGui.GetWindowDrawList(); - + var padR = 0f; var buttonSize = ImGui.GetFontSize(); var numNativeButtons = 0; if (this.CanShowCloseButton) numNativeButtons++; - + if (!flags.HasFlag(ImGuiWindowFlags.NoCollapse) && style.WindowMenuButtonPosition == ImGuiDir.Right) numNativeButtons++; @@ -543,15 +655,15 @@ public abstract class Window // Pad to the left, to get out of the way of the native buttons padR += numNativeButtons * (buttonSize + style.ItemInnerSpacing.X); - - Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f); + + Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f); var numButtons = 0; bool DrawButton(TitleBarButton button, Vector2 pos) { var id = ImGui.GetID($"###CustomTbButton{numButtons}"); numButtons++; - + var min = pos; var max = pos + new Vector2(fontSize, fontSize); Vector4 bb = new(min.X, min.Y, max.X, max.Y); @@ -563,12 +675,12 @@ public abstract class Window { hovered = false; held = false; - + // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves if (ImGui.IsMouseHoveringRect(min, max)) { hovered = true; - + // We can't use ImGui native functions here, because they don't work with clickthrough if ((User32.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0) { @@ -581,7 +693,7 @@ public abstract class Window { pressed = ImGuiNativeAdditions.igButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags.None); } - + if (isClipped) return pressed; @@ -590,10 +702,10 @@ public abstract class Window var textCol = ImGui.GetColorU32(ImGuiCol.Text); if (hovered || held) drawList.AddCircleFilled(GetCenter(bb) + new Vector2(0.0f, -0.5f), (fontSize * 0.5f) + 1.0f, bgCol); - + var offset = button.IconOffset * ImGuiHelpers.GlobalScale; - drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString()); - + drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString()); + if (hovered) button.ShowTooltip?.Invoke(); @@ -608,14 +720,14 @@ public abstract class Window { if (this.internalIsClickthrough && !button.AvailableClickthrough) return; - + Vector2 position = new(titleBarRect.Z - padR - buttonSize, titleBarRect.Y + style.FramePadding.Y); padR += buttonSize + style.ItemInnerSpacing.X; - + if (DrawButton(button, position)) button.Click?.Invoke(ImGuiMouseButton.Left); } - + ImGui.PopClipRect(); } @@ -625,7 +737,7 @@ public abstract class Window public struct WindowSizeConstraints { private Vector2 internalMaxSize = new(float.MaxValue); - + /// /// Initializes a new instance of the struct. /// @@ -637,7 +749,7 @@ public abstract class Window /// Gets or sets the minimum size of the window. /// public Vector2 MinimumSize { get; set; } = new(0); - + /// /// Gets or sets the maximum size of the window. /// @@ -646,12 +758,12 @@ public abstract class Window get => this.GetSafeMaxSize(); set => this.internalMaxSize = value; } - + private Vector2 GetSafeMaxSize() { var currentMin = this.MinimumSize; - if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y) + if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y) return new Vector2(float.MaxValue); return this.internalMaxSize; @@ -667,53 +779,56 @@ public abstract class Window /// Gets or sets the icon of the button. /// public FontAwesomeIcon Icon { get; set; } - + /// /// Gets or sets a vector by which the position of the icon within the button shall be offset. /// Automatically scaled by the global font scale for you. /// public Vector2 IconOffset { get; set; } - + /// /// Gets or sets an action that is called when a tooltip shall be drawn. /// May be null if no tooltip shall be drawn. /// public Action? ShowTooltip { get; set; } - + /// /// Gets or sets an action that is called when the button is clicked. /// public Action Click { get; set; } - + /// /// Gets or sets the priority the button shall be shown in. /// Lower = closer to ImGui default buttons. /// public int Priority { get; set; } - + /// /// Gets or sets a value indicating whether or not the button shall be clickable /// when the respective window is set to clickthrough. /// public bool AvailableClickthrough { get; set; } } - + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "imports")] private static unsafe class ImGuiNativeAdditions { [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern bool igItemAdd(Vector4 bb, uint id, Vector4* navBb, uint flags); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern bool igButtonBehavior(Vector4 bb, uint id, bool* outHovered, bool* outHeld, ImGuiButtonFlags flags); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void* igGetCurrentWindow(); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void igStartMouseMovingWindow(void* window); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void ImGuiWindow_TitleBarRect(Vector4* pOut, void* window); + + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] + public static extern void igCustom_WindowSetInheritNoInputs(bool inherit); } } diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index dc4d6aca1..f79eea025 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Configuration.Internal; -using Dalamud.Interface.Internal.ManagedAsserts; +using Dalamud.Interface.Windowing.Persistence; using ImGuiNET; using Serilog; @@ -104,20 +104,28 @@ public class WindowSystem if (hasNamespace) ImGui.PushID(this.Namespace); + // These must be nullable, people are using stock WindowSystems and Windows without Dalamud for tests var config = Service.GetNullable(); + var persistence = Service.GetNullable(); + + var flags = Window.WindowDrawFlags.None; + + if (config?.EnablePluginUISoundEffects ?? false) + flags |= Window.WindowDrawFlags.UseSoundEffects; + + if (config?.EnablePluginUiAdditionalOptions ?? false) + flags |= Window.WindowDrawFlags.UseAdditionalOptions; + + if (config?.IsFocusManagementEnabled ?? false) + flags |= Window.WindowDrawFlags.UseFocusManagement; // Shallow clone the list of windows so that we can edit it without modifying it while the loop is iterating foreach (var window in this.windows.ToArray()) { #if DEBUG - // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); + // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif - var snapshot = ImGuiManagedAsserts.GetSnapshot(); - - window.DrawInternal(config); - - var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; - ImGuiManagedAsserts.ReportProblems(source, snapshot); + window.DrawInternal(flags, persistence); } var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); diff --git a/Dalamud/IoC/Internal/ServiceScope.cs b/Dalamud/IoC/Internal/ServiceScope.cs index 4fc299f6e..5ce8bc7d0 100644 --- a/Dalamud/IoC/Internal/ServiceScope.cs +++ b/Dalamud/IoC/Internal/ServiceScope.cs @@ -1,16 +1,18 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Serilog; +using Dalamud.Game; +using Dalamud.Utility; namespace Dalamud.IoC.Internal; /// /// Container enabling the creation of scoped services. /// -internal interface IServiceScope : IDisposable +internal interface IServiceScope : IAsyncDisposable { /// /// Register objects that may be injected to scoped services, @@ -47,21 +49,57 @@ internal class ServiceScopeImpl : IServiceScope private readonly List privateScopedObjects = []; private readonly ConcurrentDictionary> scopeCreatedObjects = new(); + private readonly ReaderWriterLockSlim disposeLock = new(LockRecursionPolicy.SupportsRecursion); + private bool disposed; + /// Initializes a new instance of the class. /// The container this scope will use to create services. public ServiceScopeImpl(ServiceContainer container) => this.container = container; /// - public void RegisterPrivateScopes(params object[] scopes) => - this.privateScopedObjects.AddRange(scopes); + public void RegisterPrivateScopes(params object[] scopes) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + this.privateScopedObjects.AddRange(scopes); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// - public Task CreateAsync(Type objectType, params object[] scopedObjects) => - this.container.CreateAsync(objectType, scopedObjects, this); + public Task CreateAsync(Type objectType, params object[] scopedObjects) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.container.CreateAsync(objectType, scopedObjects, this); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// - public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) => - this.container.InjectProperties(instance, scopedObjects, this); + public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.container.InjectProperties(instance, scopedObjects, this); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// /// Create a service scoped to this scope, with private scoped objects. @@ -69,39 +107,73 @@ internal class ServiceScopeImpl : IServiceScope /// The type of object to create. /// Additional scoped objects. /// The created object, or null. - public Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) => - this.scopeCreatedObjects.GetOrAdd( - objectType, - static (objectType, p) => p.Scope.container.CreateAsync( - objectType, - p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()), - (Scope: this, Objects: scopedObjects)); - - /// - public void Dispose() + public Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) { - foreach (var objectTask in this.scopeCreatedObjects) + this.disposeLock.EnterReadLock(); + try { - objectTask.Value.ContinueWith( - static r => - { - if (!r.IsCompletedSuccessfully) - { - if (r.Exception is { } e) - Log.Error(e, "{what}: Failed to load.", nameof(ServiceScopeImpl)); - return; - } - - switch (r.Result) - { - case IInternalDisposableService d: - d.DisposeService(); - break; - case IDisposable d: - d.Dispose(); - break; - } - }); + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.scopeCreatedObjects.GetOrAdd( + objectType, + static (objectType, p) => p.Scope.container.CreateAsync( + objectType, + p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()), + (Scope: this, Objects: scopedObjects)); + } + finally + { + this.disposeLock.ExitReadLock(); } } + + /// + public async ValueTask DisposeAsync() + { + this.disposeLock.EnterWriteLock(); + this.disposed = true; + this.disposeLock.ExitWriteLock(); + + List? exceptions = null; + while (!this.scopeCreatedObjects.IsEmpty) + { + try + { + await Task.WhenAll( + this.scopeCreatedObjects.Keys.Select( + async type => + { + if (!this.scopeCreatedObjects.Remove(type, out var serviceTask)) + return; + + switch (await serviceTask) + { + case IInternalDisposableService d: + d.DisposeService(); + break; + case IAsyncDisposable d: + await d.DisposeAsync(); + break; + case IDisposable d: + d.Dispose(); + break; + } + })); + } + catch (AggregateException ae) + { + exceptions ??= []; + exceptions.AddRange(ae.Flatten().InnerExceptions); + } + } + + // Unless Dalamud is unloading (plugin cannot be reloading at that point), ensure that there are no more + // event callback call in progress when this function returns. Since above service dispose operations should + // have unregistered the event listeners, on next framework tick, none can be running anymore. + // This has an additional effect of ensuring that DtrBar entries are completely removed on return. + // Note that this still does not handle Framework.RunOnTick with specified delays. + await (Service.GetNullable()?.DelayTicks(1) ?? Task.CompletedTask).SuppressException(); + + if (exceptions is not null) + throw new AggregateException(exceptions); + } } diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index bcbb6e2b1..00173b09d 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -1,5 +1,6 @@ using Serilog; using Serilog.Core; +using Serilog.Core.Enrichers; using Serilog.Events; namespace Dalamud.Logging.Internal; @@ -11,7 +12,7 @@ public class ModuleLog { private readonly string moduleName; private readonly ILogger moduleLogger; - + // FIXME (v9): Deprecate this class in favor of using contextualized ILoggers with proper formatting. // We can keep this class around as a Serilog helper, but ModuleLog should no longer be a returned // type, instead returning a (prepared) ILogger appropriately. @@ -27,6 +28,21 @@ public class ModuleLog this.moduleLogger = Log.ForContext("Dalamud.ModuleName", this.moduleName); } + /// + /// Initializes a new instance of the class. + /// This class will properly attach SourceContext and other attributes per Serilog standards. + /// + /// The type of the class this logger is for. + public ModuleLog(Type type) + { + this.moduleName = type.Name; + this.moduleLogger = Log.ForContext( + [ + new PropertyEnricher(Constants.SourceContextPropertyName, type.FullName), + new PropertyEnricher("Dalamud.ModuleName", this.moduleName) + ]); + } + /// /// Log a templated verbose message to the in-game debug log. /// @@ -160,4 +176,11 @@ public class ModuleLog messageTemplate: $"[{this.moduleName}] {messageTemplate}", values); } + + /// + /// Helper method to create a new instance based on a type. + /// + /// The class to create this ModuleLog for. + /// Returns a ModuleLog with name set. + internal static ModuleLog Create() => new(typeof(T)); } diff --git a/Dalamud/Logging/ScopedPluginLogService.cs b/Dalamud/Logging/ScopedPluginLogService.cs index 54f231e0d..7305aa87b 100644 --- a/Dalamud/Logging/ScopedPluginLogService.cs +++ b/Dalamud/Logging/ScopedPluginLogService.cs @@ -40,6 +40,9 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog this.Logger = loggerConfiguration.CreateLogger(); } + + /// + public ILogger Logger { get; } /// public LogEventLevel MinimumLogLevel @@ -47,12 +50,7 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog get => this.levelSwitch.MinimumLevel; set => this.levelSwitch.MinimumLevel = value; } - - /// - /// Gets a logger that may be exposed to plugins some day. - /// - public ILogger Logger { get; } - + /// public void Fatal(string messageTemplate, params object[] values) => this.Write(LogEventLevel.Fatal, null, messageTemplate, values); diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index ecd2e0799..d5cf360af 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -526,13 +526,40 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa /// If this plugin was affected by the change. internal void NotifyActivePluginsChanged(PluginListInvalidationKind kind, bool affectedThisPlugin) { - this.ActivePluginsChanged?.Invoke(kind, affectedThisPlugin); + if (this.ActivePluginsChanged is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(kind, affectedThisPlugin); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } } private void OnLocalizationChanged(string langCode) { this.UiLanguage = langCode; - this.LanguageChanged?.Invoke(langCode); + + if (this.LanguageChanged is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(langCode); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } } private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) diff --git a/Dalamud/Plugin/InstalledPluginState.cs b/Dalamud/Plugin/InstalledPluginState.cs index 6700a1f8d..64c8e40a5 100644 --- a/Dalamud/Plugin/InstalledPluginState.cs +++ b/Dalamud/Plugin/InstalledPluginState.cs @@ -1,4 +1,5 @@ -using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Internal.Types.Manifest; namespace Dalamud.Plugin; @@ -22,6 +23,47 @@ public interface IExposedPlugin /// bool IsLoaded { get; } + /// + /// Gets a value indicating whether this plugin's API level is out of date. + /// + bool IsOutdated { get; } + + /// + /// Gets a value indicating whether the plugin is for testing use only. + /// + bool IsTesting { get; } + + /// + /// Gets a value indicating whether or not this plugin is orphaned(belongs to a repo) or not. + /// + bool IsOrphaned { get; } + + /// + /// Gets a value indicating whether or not this plugin is serviced(repo still exists, but plugin no longer does). + /// + bool IsDecommissioned { get; } + + /// + /// Gets a value indicating whether this plugin has been banned. + /// + bool IsBanned { get; } + + /// + /// Gets a value indicating whether this plugin is dev plugin. + /// + bool IsDev { get; } + + /// + /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party + /// repo. + /// + bool IsThirdParty { get; } + + /// + /// Gets the plugin manifest. + /// + ILocalPluginManifest Manifest { get; } + /// /// Gets the version of the plugin. /// @@ -74,6 +116,30 @@ internal sealed class ExposedPlugin(LocalPlugin plugin) : IExposedPlugin /// public bool HasConfigUi => plugin.DalamudInterface?.LocalUiBuilder.HasConfigUi ?? false; + /// + public bool IsOutdated => plugin.IsOutdated; + + /// + public bool IsTesting => plugin.IsTesting; + + /// + public bool IsOrphaned => plugin.IsOrphaned; + + /// + public bool IsDecommissioned => plugin.IsDecommissioned; + + /// + public bool IsBanned => plugin.IsBanned; + + /// + public bool IsDev => plugin.IsDev; + + /// + public bool IsThirdParty => plugin.IsThirdParty; + + /// + public ILocalPluginManifest Manifest => plugin.Manifest; + /// public void OpenMainUi() { diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index 6404846f7..ce135b947 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -9,6 +9,10 @@ using Dalamud.Console; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.Gui; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.EventArgs; @@ -31,17 +35,17 @@ namespace Dalamud.Plugin.Internal.AutoUpdate; internal class AutoUpdateManager : IServiceType { private static readonly ModuleLog Log = new("AUTOUPDATE"); - + /// /// Time we should wait after login to update. /// private static readonly TimeSpan UpdateTimeAfterLogin = TimeSpan.FromSeconds(20); - + /// /// Time we should wait between scheduled update checks. /// private static readonly TimeSpan TimeBetweenUpdateChecks = TimeSpan.FromHours(2); - + /// /// Time we should wait between scheduled update checks if the user has dismissed the notification, /// instead of updating. We don't want to spam the user with notifications. @@ -56,28 +60,30 @@ internal class AutoUpdateManager : IServiceType [ServiceManager.ServiceDependency] private readonly PluginManager pluginManager = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly DalamudConfiguration config = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly NotificationManager notificationManager = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly DalamudInterface dalamudInterface = Service.Get(); - + private readonly IConsoleVariable isDryRun; - + + private readonly Task openInstallerWindowLinkTask; + private DateTime? loginTime; private DateTime? nextUpdateCheckTime; private DateTime? unblockedSince; - + private bool hasStartedInitialUpdateThisSession; private IActiveNotification? updateNotification; - + private Task? autoUpdateTask; - + /// /// Initializes a new instance of the class. /// @@ -89,10 +95,20 @@ internal class AutoUpdateManager : IServiceType t => { t.Result.Login += this.OnLogin; - t.Result.Logout += this.OnLogout; + t.Result.Logout += (int type, int code) => this.OnLogout(); }); Service.GetAsync().ContinueWith(t => { t.Result.Update += this.OnUpdate; }); - + + this.openInstallerWindowLinkTask = + Service.GetAsync().ContinueWith( + chatGuiTask => chatGuiTask.Result.AddChatLinkHandler( + "Dalamud", + 1001, + (_, _) => + { + Service.GetNullable()?.OpenPluginInstallerTo(PluginInstallerOpenKind.InstalledPlugins); + })); + this.isDryRun = console.AddVariable("dalamud.autoupdate.dry_run", "Simulate updates instead", false); console.AddCommand("dalamud.autoupdate.trigger_login", "Trigger a login event", () => { @@ -106,36 +122,36 @@ internal class AutoUpdateManager : IServiceType return true; }); } - + private enum UpdateListingRestriction { Unrestricted, AllowNone, AllowMainRepo, } - + /// /// Gets a value indicating whether or not auto-updates have already completed this session. /// public bool IsAutoUpdateComplete { get; private set; } - + /// /// Gets the time of the next scheduled update check. /// public DateTime? NextUpdateCheckTime => this.nextUpdateCheckTime; - + /// /// Gets the time the auto-update was unblocked. /// public DateTime? UnblockedSince => this.unblockedSince; - + private static UpdateListingRestriction DecideUpdateListingRestriction(AutoUpdateBehavior behavior) { return behavior switch { // We don't generally allow any updates in this mode, but specific opt-ins. AutoUpdateBehavior.None => UpdateListingRestriction.AllowNone, - + // If we're only notifying, I guess it's fine to list all plugins. AutoUpdateBehavior.OnlyNotify => UpdateListingRestriction.Unrestricted, @@ -144,7 +160,7 @@ internal class AutoUpdateManager : IServiceType _ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, null), }; } - + private static void DrawOpenInstallerNotificationButton(bool primary, PluginInstallerOpenKind kind, IActiveNotification notification) { if (primary ? @@ -179,7 +195,7 @@ internal class AutoUpdateManager : IServiceType this.updateNotification = null; } } - + // If we're blocked, we don't do anything. if (!isUnblocked) return; @@ -199,16 +215,16 @@ internal class AutoUpdateManager : IServiceType if (!this.hasStartedInitialUpdateThisSession && DateTime.Now > this.loginTime.Value.Add(UpdateTimeAfterLogin)) { this.hasStartedInitialUpdateThisSession = true; - + var currentlyUpdatablePlugins = this.GetAvailablePluginUpdates(DecideUpdateListingRestriction(behavior)); if (currentlyUpdatablePlugins.Count == 0) { this.IsAutoUpdateComplete = true; this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; - + return; } - + // TODO: This is not 100% what we want... Plugins that are opted-in should be updated regardless of the behavior, // and we should show a notification for the others afterwards. if (behavior == AutoUpdateBehavior.OnlyNotify) @@ -241,6 +257,7 @@ internal class AutoUpdateManager : IServiceType Log.Error(t.Exception!, "Failed to reload plugin masters for auto-update"); } + Log.Verbose($"Available Updates: {string.Join(", ", this.pluginManager.UpdatablePlugins.Select(s => s.UpdateManifest.InternalName))}"); var updatable = this.GetAvailablePluginUpdates( DecideUpdateListingRestriction(behavior)); @@ -252,7 +269,7 @@ internal class AutoUpdateManager : IServiceType { this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; Log.Verbose( - "Auto update found nothing to do, next update at {Time}", + "Auto update found nothing to do, next update at {Time}", this.nextUpdateCheckTime); } }); @@ -263,13 +280,13 @@ internal class AutoUpdateManager : IServiceType { if (this.updateNotification != null) throw new InvalidOperationException("Already showing a notification"); - + this.updateNotification = this.notificationManager.AddNotification(notification); this.updateNotification.Dismiss += _ => { this.updateNotification = null; - + // Schedule the next update opportunistically for when this closes. this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; }; @@ -291,7 +308,7 @@ internal class AutoUpdateManager : IServiceType { Log.Warning("Auto-update task was canceled"); } - + this.autoUpdateTask = null; this.IsAutoUpdateComplete = true; }); @@ -321,20 +338,20 @@ internal class AutoUpdateManager : IServiceType notification.Content = Locs.NotificationContentUpdating(updateProgress.CurrentPluginManifest.Name); notification.Progress = (float)updateProgress.PluginsProcessed / updateProgress.TotalPlugins; }; - + var pluginStates = (await this.pluginManager.UpdatePluginsAsync(updatablePlugins, this.isDryRun.Value, true, progress)).ToList(); this.pluginManager.PrintUpdatedPlugins(pluginStates, Loc.Localize("DalamudPluginAutoUpdate", "The following plugins were auto-updated:")); notification.Progress = 1; notification.UserDismissable = true; notification.HardExpiry = DateTime.Now.AddSeconds(30); - + notification.DrawActions += _ => { ImGuiHelpers.ScaledDummy(2); DrawOpenInstallerNotificationButton(true, PluginInstallerOpenKind.InstalledPlugins, notification); }; - + // Update the notification to show the final state if (pluginStates.All(x => x.Status == PluginUpdateStatus.StatusKind.Success)) { @@ -342,7 +359,7 @@ internal class AutoUpdateManager : IServiceType // Janky way to make sure the notification does not change before it's minimized... await Task.Delay(500); - + notification.Title = Locs.NotificationTitleUpdatesSuccessful; notification.MinimizedText = Locs.NotificationContentUpdatesSuccessfulMinimized; notification.Type = NotificationType.Success; @@ -354,11 +371,11 @@ internal class AutoUpdateManager : IServiceType notification.MinimizedText = Locs.NotificationContentUpdatesFailedMinimized; notification.Type = NotificationType.Error; notification.Content = Locs.NotificationContentUpdatesFailed; - + var failedPlugins = pluginStates .Where(x => x.Status != PluginUpdateStatus.StatusKind.Success) .Select(x => x.Name).ToList(); - + notification.Content += "\n" + Locs.NotificationContentFailedPlugins(failedPlugins); } } @@ -367,7 +384,7 @@ internal class AutoUpdateManager : IServiceType { if (updatablePlugins.Count == 0) return; - + var notification = this.GetBaseNotification(new Notification { Title = Locs.NotificationTitleUpdatesAvailable, @@ -400,16 +417,44 @@ internal class AutoUpdateManager : IServiceType notification.Dismiss += args => { if (args.Reason != NotificationDismissReason.Manual) return; - + this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecksIfDismissed; Log.Verbose("User dismissed update notification, next check at {Time}", this.nextUpdateCheckTime); }; + + // Send out a chat message only if the user requested so + if (!this.config.SendUpdateNotificationToChat) + return; + + var chatGui = Service.GetNullable(); + if (chatGui == null) + { + Log.Verbose("Unable to get chat gui, discard notification for chat."); + return; + } + + chatGui.Print(new XivChatEntry + { + Message = new SeString(new List + { + new TextPayload(Locs.NotificationContentUpdatesAvailableMinimized(updatablePlugins.Count)), + new TextPayload(" ["), + new UIForegroundPayload(500), + this.openInstallerWindowLinkTask.Result, + new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")), + RawPayload.LinkTerminator, + new UIForegroundPayload(0), + new TextPayload("]"), + }), + + Type = XivChatType.Urgent, + }); } - + private List GetAvailablePluginUpdates(UpdateListingRestriction restriction) { var optIns = this.config.PluginAutoUpdatePreferences.ToArray(); - + // Get all of our updatable plugins and do some initial filtering that must apply to all plugins. var updateablePlugins = this.pluginManager.UpdatablePlugins .Where( @@ -423,14 +468,14 @@ internal class AutoUpdateManager : IServiceType bool FilterPlugin(AvailablePluginUpdate availablePluginUpdate) { var optIn = optIns.FirstOrDefault(x => x.WorkingPluginId == availablePluginUpdate.InstalledPlugin.EffectiveWorkingPluginId); - + // If this is an opt-out, we don't update. if (optIn is { Kind: AutoUpdatePreference.OptKind.NeverUpdate }) return false; if (restriction == UpdateListingRestriction.AllowNone && optIn is not { Kind: AutoUpdatePreference.OptKind.AlwaysUpdate }) return false; - + if (restriction == UpdateListingRestriction.AllowMainRepo && availablePluginUpdate.InstalledPlugin.IsThirdParty) return false; @@ -442,7 +487,7 @@ internal class AutoUpdateManager : IServiceType { this.loginTime = DateTime.Now; } - + private void OnLogout() { this.loginTime = null; @@ -452,7 +497,7 @@ internal class AutoUpdateManager : IServiceType { var condition = Service.Get(); return this.IsPluginManagerReady() && - !this.dalamudInterface.IsPluginInstallerOpen && + !this.dalamudInterface.IsPluginInstallerOpen && condition.OnlyAny(ConditionFlag.NormalConditions, ConditionFlag.Jumping, ConditionFlag.Mounted, @@ -469,21 +514,21 @@ internal class AutoUpdateManager : IServiceType public static string NotificationButtonOpenPluginInstaller => Loc.Localize("AutoUpdateOpenPluginInstaller", "Open installer"); public static string NotificationButtonUpdate => Loc.Localize("AutoUpdateUpdate", "Update"); - + public static string NotificationTitleUpdatesAvailable => Loc.Localize("AutoUpdateUpdatesAvailable", "Updates available!"); - + public static string NotificationTitleUpdatesSuccessful => Loc.Localize("AutoUpdateUpdatesSuccessful", "Updates successful!"); - + public static string NotificationTitleUpdatingPlugins => Loc.Localize("AutoUpdateUpdatingPlugins", "Updating plugins..."); - + public static string NotificationTitleUpdatesFailed => Loc.Localize("AutoUpdateUpdatesFailed", "Updates failed!"); - + public static string NotificationContentUpdatesSuccessful => Loc.Localize("AutoUpdateUpdatesSuccessfulContent", "All plugins have been updated successfully."); - + public static string NotificationContentUpdatesSuccessfulMinimized => Loc.Localize("AutoUpdateUpdatesSuccessfulContentMinimized", "Plugins updated successfully."); - + public static string NotificationContentUpdatesFailed => Loc.Localize("AutoUpdateUpdatesFailedContent", "Some plugins failed to update. Please check the plugin installer for more information."); - + public static string NotificationContentUpdatesFailedMinimized => Loc.Localize("AutoUpdateUpdatesFailedContentMinimized", "Plugins failed to update."); public static string NotificationContentUpdatesAvailable(ICollection updatablePlugins) @@ -497,20 +542,20 @@ internal class AutoUpdateManager : IServiceType "There are {0} plugins that can be updated:"), updatablePlugins.Count)) + "\n\n" + string.Join(", ", updatablePlugins.Select(x => x.InstalledPlugin.Manifest.Name)); - + public static string NotificationContentUpdatesAvailableMinimized(int numUpdates) => numUpdates == 1 ? - Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedSingular", "1 plugin update available") : + Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedSingular", "1 plugin update available") : string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedPlural", "{0} plugin updates available"), numUpdates); - + public static string NotificationContentPreparingToUpdate(int numPlugins) => numPlugins == 1 ? - Loc.Localize("AutoUpdatePreparingToUpdateSingular", "Preparing to update 1 plugin...") : + Loc.Localize("AutoUpdatePreparingToUpdateSingular", "Preparing to update 1 plugin...") : string.Format(Loc.Localize("AutoUpdatePreparingToUpdatePlural", "Preparing to update {0} plugins..."), numPlugins); - + public static string NotificationContentUpdating(string name) => string.Format(Loc.Localize("AutoUpdateUpdating", "Updating {0}..."), name); - + public static string NotificationContentFailedPlugins(IEnumerable failedPlugins) => string.Format(Loc.Localize("AutoUpdateFailedPlugins", "Failed plugin(s): {0}"), string.Join(", ", failedPlugins)); } diff --git a/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs b/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs new file mode 100644 index 000000000..03e37afcf --- /dev/null +++ b/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs @@ -0,0 +1,16 @@ +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// An exception to be thrown when policy blocks a plugin from loading. +/// +internal class InternalPluginStateException : InvalidPluginOperationException +{ + /// + /// Initializes a new instance of the class. + /// + /// The message to associate with this exception. + public InternalPluginStateException(string message) + : base(message) + { + } +} diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 5d365d66c..a20f87241 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -15,13 +15,11 @@ using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.Gui; -using Dalamud.Game.Gui.Dtr; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows.PluginInstaller; using Dalamud.IoC; using Dalamud.Logging.Internal; using Dalamud.Networking.Http; @@ -50,12 +48,14 @@ internal class PluginManager : IInternalDisposableService /// public const int PluginWaitBeforeFreeDefault = 1000; // upped from 500ms, seems more stable - private static readonly ModuleLog Log = new("PLUGINM"); + private const string BrokenMarkerFileName = ".broken"; + + private static readonly ModuleLog Log = ModuleLog.Create(); private readonly object pluginListLock = new(); private readonly DirectoryInfo pluginDirectory; private readonly BannedPlugin[]? bannedPlugins; - + private readonly List installedPluginsList = new(); private readonly List availablePluginsList = new(); private readonly List updatablePluginsList = new(); @@ -136,9 +136,6 @@ internal class PluginManager : IInternalDisposableService this.configuration.PluginTestingOptIns ??= new(); this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient); - // NET8 CHORE - // this.ApplyPatches(); - registerStartupBlocker( Task.Run(this.LoadAndStartLoadSyncPlugins), "Waiting for plugins that asked to be loaded before the game."); @@ -164,6 +161,27 @@ internal class PluginManager : IInternalDisposableService /// public static int DalamudApiLevel { get; private set; } + /// + /// Gets the number of loaded plugins. + /// + public int LoadedPluginCount + { + get + { + var res = 0; + lock (this.pluginListLock) + { + foreach (var p in this.installedPluginsList) + { + if (p.State == PluginState.Loaded) + res++; + } + } + + return res; + } + } + /// /// Gets a copy of the list of all loaded plugins. /// @@ -191,7 +209,7 @@ internal class PluginManager : IInternalDisposableService } } } - + /// /// Gets a copy of the list of all plugins with an available update. /// @@ -227,9 +245,9 @@ internal class PluginManager : IInternalDisposableService public bool ReposReady { get; private set; } /// - /// Gets a value indicating whether the plugin manager started in safe mode. + /// Gets or sets a value indicating whether the plugin manager started in safe mode. /// - public bool SafeMode { get; init; } + public bool SafeMode { get; set; } /// /// Gets the object used when initializing plugins. @@ -245,7 +263,7 @@ internal class PluginManager : IInternalDisposableService /// Gets or sets a value indicating whether banned plugins will be loaded. /// public bool LoadBannedPlugins { get; set; } - + /// /// Gets a tracker for plugins that are loading at startup, used to display information to the user. /// @@ -320,6 +338,7 @@ internal class PluginManager : IInternalDisposableService new UIForegroundPayload(0), new TextPayload("]"), }), + Type = this.configuration.GeneralChatType, }); foreach (var metadata in updateMetadata) @@ -377,59 +396,42 @@ internal class PluginManager : IInternalDisposableService /// void IInternalDisposableService.DisposeService() { - var disposablePlugins = - this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray(); - if (disposablePlugins.Any()) - { - // Unload them first, just in case some of plugin codes are still running via callbacks initiated externally. - foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) - { - try - { - plugin.UnloadAsync(true, false).Wait(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error unloading {plugin.Name}"); - } - } + DisposeAsync( + this.installedPluginsList + .Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError) + .ToArray(), + this.configuration).Wait(); + return; - Task.WaitAll(disposablePlugins - .Where(plugin => plugin.Manifest.CanUnloadAsync) - .Select(plugin => Task.Run(async () => - { - try - { - await plugin.UnloadAsync(true, false); - } - catch (Exception ex) - { - Log.Error(ex, $"Error unloading {plugin.Name}"); - } - })).ToArray()); + static async Task DisposeAsync(LocalPlugin[] disposablePlugins, DalamudConfiguration configuration) + { + if (disposablePlugins.Length == 0) + return; + + // Any unload/dispose operation called from this function log errors on their own. + // Ignore all errors. + + // Unload plugins that requires to be unloaded synchronously, + // just in case some plugin codes are still running via callbacks initiated externally. + foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) + await plugin.UnloadAsync(PluginLoaderDisposalMode.None).SuppressException(); + + // Unload plugins that can be unloaded from any thread. + await Task.WhenAll( + disposablePlugins.Where(plugin => plugin.Manifest.CanUnloadAsync) + .Select(plugin => plugin.UnloadAsync(PluginLoaderDisposalMode.None))) + .SuppressException(); // Just in case plugins still have tasks running that they didn't cancel when they should have, // give them some time to complete it. - Thread.Sleep(this.configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault); + // This helps avoid plugins being reloaded from conflicting with itself of previous instance. + await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault); // Now that we've waited enough, dispose the whole plugin. - // Since plugins should have been unloaded above, this should be done quickly. - foreach (var plugin in disposablePlugins) - { - try - { - plugin.Dispose(); - } - catch (Exception e) - { - Log.Error(e, $"Error disposing {plugin.Name}"); - } - } + // Since plugins should have been unloaded above, this should complete quickly. + await Task.WhenAll(disposablePlugins.Select(plugin => plugin.DisposeAsync().AsTask())) + .SuppressException(); } - - // NET8 CHORE - // this.assemblyLocationMonoHook?.Dispose(); - // this.assemblyCodeBaseMonoHook?.Dispose(); } /// @@ -479,7 +481,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("No DLL found for plugin at {Path}", versionDir.FullName); continue; } - + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); if (!manifestFile.Exists) { @@ -506,7 +508,7 @@ internal class PluginManager : IInternalDisposableService } this.configuration.QueueSave(); - + if (versionsDefs.Count == 0) { Log.Verbose("No versions found for plugin: {Name}", pluginDir.Name); @@ -552,7 +554,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("DLL at {DllPath} has no manifest, this is no longer valid", dllFile.FullName); continue; } - + var manifest = LocalPluginManifest.Load(manifestFile); if (manifest == null) { @@ -761,7 +763,7 @@ internal class PluginManager : IInternalDisposableService .SelectMany(repo => repo.PluginMaster) .Where(this.IsManifestEligible) .Where(IsManifestVisible)); - + if (notify) { this.NotifyAvailablePluginsChanged(); @@ -783,14 +785,10 @@ internal class PluginManager : IInternalDisposableService { if (!setting.IsEnabled) continue; - + Log.Verbose("Scanning dev plugins at {Path}", setting.Path); - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) + if (File.Exists(setting.Path)) { devDllFiles.Add(new FileInfo(setting.Path)); } @@ -814,7 +812,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("DLL at {DllPath} has no manifest, this is no longer valid", dllFile.FullName); continue; } - + var manifest = LocalPluginManifest.Load(manifestFile); if (manifest == null) { @@ -858,7 +856,7 @@ internal class PluginManager : IInternalDisposableService var stream = await this.DownloadPluginAsync(repoManifest, useTesting); return await this.InstallPluginInternalAsync(repoManifest, useTesting, reason, stream, inheritedWorkingPluginId); } - + /// /// Remove a plugin. /// @@ -873,15 +871,12 @@ internal class PluginManager : IInternalDisposableService this.installedPluginsList.Remove(plugin); } - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); - this.NotifyinstalledPluginsListChanged(); this.NotifyAvailablePluginsChanged(); } /// - /// Cleanup disabled plugins. Does not target devPlugins. + /// Cleanup disabled and broken plugins. Does not target devPlugins. /// public void CleanupPlugins() { @@ -889,6 +884,13 @@ internal class PluginManager : IInternalDisposableService { try { + if (File.Exists(Path.Combine(pluginDir.FullName, BrokenMarkerFileName))) + { + Log.Warning("Cleaning up broken plugin {Name}", pluginDir.Name); + pluginDir.Delete(true); + continue; + } + var versionDirs = pluginDir.GetDirectories(); versionDirs = versionDirs @@ -1055,7 +1057,7 @@ internal class PluginManager : IInternalDisposableService Status = PluginUpdateStatus.StatusKind.Success, HasChangelog = !metadata.UpdateManifest.Changelog.IsNullOrWhitespace(), }; - + // Check if this plugin is already up to date (=> AvailablePluginUpdate was stale) lock (this.installedPluginsList) { @@ -1082,7 +1084,7 @@ internal class PluginManager : IInternalDisposableService updateStatus.Status = PluginUpdateStatus.StatusKind.FailedDownload; return updateStatus; } - + // Unload if loaded if (plugin.State is PluginState.Loaded or PluginState.LoadError or PluginState.DependencyResolutionFailed) { @@ -1123,10 +1125,6 @@ internal class PluginManager : IInternalDisposableService return updateStatus; } - // We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates. - var dtr = Service.Get(); - dtr.HandleRemovedNodes(); - try { await this.InstallPluginInternalAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update, updateStream, workingPluginId); @@ -1316,7 +1314,7 @@ internal class PluginManager : IInternalDisposableService { if (serviceType == typeof(PluginManager)) continue; - + // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away. // Nonetheless, their direct dependencies must be considered. if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService) @@ -1324,19 +1322,19 @@ internal class PluginManager : IInternalDisposableService var typeAsServiceT = ServiceHelpers.GetAsService(serviceType); var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT, false); ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count); - + foreach (var scopedDep in dependencies) { if (scopedDep == typeof(PluginManager)) throw new Exception("Scoped plugin services cannot depend on PluginManager."); - + ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!); yield return scopedDep; } continue; } - + var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true); if (pluginInterfaceAttribute == null) continue; @@ -1347,12 +1345,12 @@ internal class PluginManager : IInternalDisposableService } /// - /// Check if there are any inconsistencies with our plugins, their IDs, and our profiles. + /// Check if there are any inconsistencies with our plugins, their IDs, and our profiles. /// private void ParanoiaValidatePluginsAndProfiles() { var seenIds = new List(); - + foreach (var installedPlugin in this.InstalledPlugins) { if (installedPlugin.EffectiveWorkingPluginId == Guid.Empty) @@ -1363,13 +1361,13 @@ internal class PluginManager : IInternalDisposableService throw new Exception( $"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.EffectiveWorkingPluginId}'"); } - + seenIds.Add(installedPlugin.EffectiveWorkingPluginId); } - + this.profileManager.ParanoiaValidateProfiles(); } - + private async Task DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting) { var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; @@ -1402,7 +1400,7 @@ internal class PluginManager : IInternalDisposableService { var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting}, version={version}, reason={reason})"); - + // If this plugin is in the default profile for whatever reason, delete the state // If it was in multiple profiles and is still, the user uninstalled it and chose to keep it in there, // or the user removed the plugin manually in which case we don't care @@ -1434,9 +1432,10 @@ internal class PluginManager : IInternalDisposableService else { // If we are doing anything other than a fresh install, not having a workingPluginId is an error that must be fixed - Debug.Assert(inheritedWorkingPluginId != null, "inheritedWorkingPluginId != null"); + if (inheritedWorkingPluginId == null) + throw new ArgumentNullException(nameof(inheritedWorkingPluginId), "Inherited WorkingPluginId must not be null when updating"); } - + // Ensure that we have a testing opt-in for this plugin if we are installing a testing version if (useTesting && this.configuration.PluginTestingOptIns!.All(x => x.InternalName != repoManifest.InternalName)) { @@ -1445,31 +1444,28 @@ internal class PluginManager : IInternalDisposableService this.configuration.QueueSave(); } - var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version?.ToString() ?? string.Empty)); + var pluginVersionsDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName)); + var tempOutputDir = new DirectoryInfo(FilesystemUtil.GetTempFileName()); + var outputDir = new DirectoryInfo(Path.Combine(pluginVersionsDir.FullName, version?.ToString() ?? string.Empty)); + + FilesystemUtil.DeleteAndRecreateDirectory(tempOutputDir); + FilesystemUtil.DeleteAndRecreateDirectory(outputDir); + + Log.Debug("Extracting plugin to {TempOutputDir}", tempOutputDir); try { - if (outputDir.Exists) - outputDir.Delete(true); + using var archive = new ZipArchive(zipStream); - outputDir.Create(); - } - catch - { - // ignored, since the plugin may be loaded already - } - - Log.Debug("Extracting to {OutputDir}", outputDir); - - using (var archive = new ZipArchive(zipStream)) - { foreach (var zipFile in archive.Entries) { - var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); + var outputFile = new FileInfo( + Path.GetFullPath(Path.Combine(tempOutputDir.FullName, zipFile.FullName))); - if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) + if (!outputFile.FullName.StartsWith(tempOutputDir.FullName, StringComparison.OrdinalIgnoreCase)) { - throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); + throw new IOException( + "Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); } if (outputFile.Directory == null) @@ -1480,72 +1476,90 @@ internal class PluginManager : IInternalDisposableService if (zipFile.Name.IsNullOrEmpty()) { // Assuming Empty for Directory - Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); + Log.Verbose( + "ZipFile name is null or empty, treating as a directory: {Path}", outputFile.Directory.FullName); Directory.CreateDirectory(outputFile.Directory.FullName); continue; } // Ensure directory is created Directory.CreateDirectory(outputFile.Directory.FullName); - - try - { - zipFile.ExtractToFile(outputFile.FullName, true); - } - catch (Exception ex) - { - if (outputFile.Extension.EndsWith("dll")) - { - throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } - - Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } + zipFile.ExtractToFile(outputFile.FullName, true); } + + var tempDllFile = LocalPluginManifest.GetPluginFile(tempOutputDir, repoManifest); + var tempManifestFile = LocalPluginManifest.GetManifestFile(tempDllFile); + + // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. + FilesystemUtil.WriteAllTextSafe( + tempManifestFile.FullName, + JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); + + // Reload as a local manifest, add some attributes, and save again. + var tempManifest = LocalPluginManifest.Load(tempManifestFile); + + if (tempManifest == null) + throw new Exception("Plugin had no valid manifest"); + + if (tempManifest.InternalName != repoManifest.InternalName) + { + throw new Exception( + $"Distributed internal name does not match repo internal name: {tempManifest.InternalName} - {repoManifest.InternalName}"); + } + + if (tempManifest.WorkingPluginId != Guid.Empty) + throw new Exception("Plugin shall not specify a WorkingPluginId"); + + tempManifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid(); + + if (useTesting) + { + tempManifest.Testing = true; + } + + // Document the url the plugin was installed from + tempManifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty + ? repoManifest.SourceRepo.PluginMasterUrl + : SpecialPluginSource.MainRepo; + + tempManifest.Save(tempManifestFile, "installation"); + + // Copy the directory to the final location + Log.Debug("Copying plugin from {TempOutputDir} to {OutputDir}", tempOutputDir, outputDir); + FilesystemUtil.CopyFilesRecursively(tempOutputDir, outputDir); + + var finalDllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); + var finalManifestFile = LocalPluginManifest.GetManifestFile(finalDllFile); + var finalManifest = LocalPluginManifest.Load(finalManifestFile) ?? + throw new Exception("Plugin had no valid manifest after copy"); + + Log.Information("Installed plugin {InternalName} (testing={UseTesting})", tempManifest.Name, useTesting); + var plugin = await this.LoadPluginAsync(finalDllFile, finalManifest, reason); + + this.NotifyinstalledPluginsListChanged(); + return plugin; } - - var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - - // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. - Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); - - // Reload as a local manifest, add some attributes, and save again. - var manifest = LocalPluginManifest.Load(manifestFile); - - if (manifest == null) - throw new Exception("Plugin had no valid manifest"); - - if (manifest.InternalName != repoManifest.InternalName) + catch { - Directory.Delete(outputDir.FullName, true); - throw new Exception( - $"Distributed internal name does not match repo internal name: {manifest.InternalName} - {repoManifest.InternalName}"); + // Attempt to clean up if we can + try + { + outputDir.Delete(true); + } + catch + { + // Write marker file if we can't, we'll try to do it at the next start + File.WriteAllText(Path.Combine(pluginVersionsDir.FullName, BrokenMarkerFileName), string.Empty); + } + + throw; } - - if (manifest.WorkingPluginId != Guid.Empty) - throw new Exception("Plugin shall not specify a WorkingPluginId"); - - manifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid(); - - if (useTesting) + finally { - manifest.Testing = true; + tempOutputDir.Delete(true); } - - // Document the url the plugin was installed from - manifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty ? repoManifest.SourceRepo.PluginMasterUrl : SpecialPluginSource.MainRepo; - - manifest.Save(manifestFile, "installation"); - - Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); - - var plugin = await this.LoadPluginAsync(dllFile, manifest, reason); - - this.NotifyinstalledPluginsListChanged(); - return plugin; } - + /// /// Load a plugin. /// @@ -1558,7 +1572,6 @@ internal class PluginManager : IInternalDisposableService /// The loaded plugin. private async Task LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) { - var name = manifest?.Name ?? dllFile.Name; var loadPlugin = !doNotLoad; LocalPlugin? plugin; @@ -1571,15 +1584,19 @@ internal class PluginManager : IInternalDisposableService if (isDev) { - Log.Information($"Loading dev plugin {name}"); + Log.Information("Loading dev plugin {Name}", manifest.InternalName); plugin = new LocalDevPlugin(dllFile, manifest); + + // This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet + // TODO(goat): Re-enable this when we have better tracing for what was rendering when + // this.configuration.ImGuiAssertsEnabledAtStartup ??= true; } else { - Log.Information($"Loading plugin {name}"); + Log.Information("Loading plugin {Name}", manifest.InternalName); plugin = new LocalPlugin(dllFile, manifest); } - + // Perform a migration from InternalName to GUIDs. The plugin should definitely have a GUID here. // This will also happen if you are installing a plugin with the installer, and that's intended! // It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will @@ -1587,7 +1604,7 @@ internal class PluginManager : IInternalDisposableService if (plugin.EffectiveWorkingPluginId == Guid.Empty) throw new Exception("Plugin should have a WorkingPluginId at this point"); this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.EffectiveWorkingPluginId); - + var wantedByAnyProfile = false; // Now, if this is a devPlugin, figure out if we want to load it @@ -1603,11 +1620,11 @@ internal class PluginManager : IInternalDisposableService // We don't know about this plugin, so we don't want to do anything here. // The code below will take care of it and add it with the default value. Log.Verbose("DevPlugin {Name} not wanted in default plugin", plugin.Manifest.InternalName); - + // Check if any profile wants this plugin. We need to do this here, since we want to allow loading a dev plugin if a non-default profile wants it active. // Note that this will not add the plugin to the default profile. That's done below in any other case. wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); - + // If it is wanted by any other profile, we do want to load it. if (wantedByAnyProfile) loadPlugin = true; @@ -1654,12 +1671,12 @@ internal class PluginManager : IInternalDisposableService #pragma warning disable CS0618 var defaultState = manifest?.Disabled != true && loadPlugin; #pragma warning restore CS0618 - + // Plugins that aren't in any profile will be added to the default profile with this call. // We are skipping a double-lookup for dev plugins that are wanted by non-default profiles, as noted above. wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, defaultState); Log.Information("{Name} defaultState: {State} wantedByAnyProfile: {WantedByAny} loadPlugin: {LoadPlugin}", plugin.Manifest.InternalName, defaultState, wantedByAnyProfile, loadPlugin); - + if (loadPlugin) { try @@ -1670,13 +1687,11 @@ internal class PluginManager : IInternalDisposableService } else { - Log.Verbose($"{name} not loaded, wantToLoad:{wantedByAnyProfile} orphaned:{plugin.IsOrphaned}"); + Log.Verbose("{Name} not loaded, wantToLoad:{WantedByAnyProfile} orphaned:{IsOrphaned}", manifest.InternalName, wantedByAnyProfile, plugin.IsOrphaned); } } catch (InvalidPluginException) { - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); throw; } catch (BannedPluginException) @@ -1722,8 +1737,6 @@ internal class PluginManager : IInternalDisposableService } else { - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); throw; } } @@ -1747,11 +1760,11 @@ internal class PluginManager : IInternalDisposableService private void DetectAvailablePluginUpdates() { Log.Debug("Starting plugin update check..."); - + lock (this.pluginListLock) { this.updatablePluginsList.Clear(); - + foreach (var plugin in this.installedPluginsList) { var installedVersion = plugin.IsTesting @@ -1790,12 +1803,12 @@ internal class PluginManager : IInternalDisposableService } } } - + Log.Debug("Update check found {updateCount} available updates.", this.updatablePluginsList.Count); } private void NotifyAvailablePluginsChanged() - { + { this.DetectAvailablePluginUpdates(); this.OnAvailablePluginsChanged?.InvokeSafely(); @@ -1843,7 +1856,7 @@ internal class PluginManager : IInternalDisposableService using (Timings.Start("PM Load Sync Plugins")) { var loadAllPlugins = Task.Run(this.LoadAllPlugins); - + // We wait for all blocking services and tasks to finish before kicking off the main thread in any mode. // This means that we don't want to block here if this stupid thing isn't enabled. if (this.configuration.IsResumeGameAfterPluginLoad) @@ -1862,12 +1875,12 @@ internal class PluginManager : IInternalDisposableService Log.Error(ex, "Plugin load failed"); } } - + /// /// Class representing progress of an update operation. /// public record PluginUpdateProgress(int PluginsProcessed, int TotalPlugins, IPluginManifest CurrentPluginManifest); - + /// /// Simple class that tracks the internal names and public names of plugins that we are planning to load at startup, /// and are still actively loading. @@ -1877,12 +1890,12 @@ internal class PluginManager : IInternalDisposableService private readonly Dictionary internalToPublic = new(); private readonly ConcurrentBag allInternalNames = new(); private readonly ConcurrentBag finishedInternalNames = new(); - + /// /// Gets a value indicating the total load progress. /// public float Progress => (float)this.finishedInternalNames.Count / this.allInternalNames.Count; - + /// /// Calculate a set of internal names that are still pending. /// @@ -1893,7 +1906,7 @@ internal class PluginManager : IInternalDisposableService pending.ExceptWith(this.finishedInternalNames); return pending; } - + /// /// Track a new plugin. /// @@ -1904,7 +1917,7 @@ internal class PluginManager : IInternalDisposableService this.internalToPublic[internalName] = publicName; this.allInternalNames.Add(internalName); } - + /// /// Mark a plugin as finished loading. /// @@ -1913,7 +1926,7 @@ internal class PluginManager : IInternalDisposableService { this.finishedInternalNames.Add(internalName); } - + /// /// Get the public name for a given internal name. /// @@ -1932,114 +1945,3 @@ internal class PluginManager : IInternalDisposableService public static string DalamudPluginUpdateFailed(string name, Version version, string why) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed ({2}).").Format(name, version, why); } } - -// NET8 CHORE -/* -/// -/// Class responsible for loading and unloading plugins. -/// This contains the assembly patching functionality to resolve assembly locations. -/// -internal partial class PluginManager -{ - /// - /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading - /// plugins via byte[]. - /// - internal static readonly ConcurrentDictionary PluginLocations = new(); - - private MonoMod.RuntimeDetour.Hook? assemblyLocationMonoHook; - private MonoMod.RuntimeDetour.Hook? assemblyCodeBaseMonoHook; - - /// - /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin location, or the result from the original method. - private static string AssemblyLocationPatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.Location; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); - return result; - } - - /// - /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin code base, or the result from the original method. - private static string AssemblyCodeBasePatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.CodeBase; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); - return result; - } - - private static IEnumerable GetStackFrameAssemblyNames() - { - var stackTrace = new StackTrace(); - var stackFrames = stackTrace.GetFrames(); - - foreach (var stackFrame in stackFrames) - { - var methodBase = stackFrame.GetMethod(); - if (methodBase == null) - continue; - - yield return methodBase.Module.Assembly.FullName!; - } - } - - private void ApplyPatches() - { - var targetType = typeof(PluginManager).Assembly.GetType(); - - var locationTarget = targetType.GetProperty(nameof(Assembly.Location))!.GetGetMethod(); - var locationPatch = typeof(PluginManager).GetMethod(nameof(AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); - -#pragma warning disable CS0618 -#pragma warning disable SYSLIB0012 - var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase))?.GetGetMethod(); -#pragma warning restore SYSLIB0012 -#pragma warning restore CS0618 - var codebasePatch = typeof(PluginManager).GetMethod(nameof(AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); - } -} -*/ diff --git a/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs new file mode 100644 index 000000000..09cceebcb --- /dev/null +++ b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs @@ -0,0 +1,385 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using CheapLoc; +using Dalamud.Game; +using Dalamud.Game.Command; +using Dalamud.Game.Gui; +using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Services; +using Dalamud.Utility; +using Serilog; + +namespace Dalamud.Plugin.Internal.Profiles; + +/// +/// Service responsible for profile-related chat commands. +/// +[ServiceManager.EarlyLoadedService] +internal class PluginManagementCommandHandler : IInternalDisposableService +{ +#pragma warning disable SA1600 + public const string CommandEnableProfile = "/xlenablecollection"; + public const string CommandDisableProfile = "/xldisablecollection"; + public const string CommandToggleProfile = "/xltogglecollection"; + + public const string CommandEnablePlugin = "/xlenableplugin"; + public const string CommandDisablePlugin = "/xldisableplugin"; + public const string CommandTogglePlugin = "/xltoggleplugin"; +#pragma warning restore SA1600 + + private static readonly string LegacyCommandEnable = CommandEnableProfile.Replace("collection", "profile"); + private static readonly string LegacyCommandDisable = CommandDisableProfile.Replace("collection", "profile"); + private static readonly string LegacyCommandToggle = CommandToggleProfile.Replace("collection", "profile"); + + private readonly CommandManager cmd; + private readonly ProfileManager profileManager; + private readonly PluginManager pluginManager; + private readonly ChatGui chat; + private readonly Framework framework; + + private List<(Target Target, PluginCommandOperation Operation)> commandQueue = new(); + + /// + /// Initializes a new instance of the class. + /// + /// Command handler. + /// Profile manager. + /// Plugin manager. + /// Chat handler. + /// Framework. + [ServiceManager.ServiceConstructor] + public PluginManagementCommandHandler( + CommandManager cmd, + ProfileManager profileManager, + PluginManager pluginManager, + ChatGui chat, + Framework framework) + { + this.cmd = cmd; + this.profileManager = profileManager; + this.pluginManager = pluginManager; + this.chat = chat; + this.framework = framework; + + this.cmd.AddHandler(CommandEnableProfile, new CommandInfo(this.OnEnableProfile) + { + HelpMessage = Loc.Localize("ProfileCommandsEnableHint", "Enable a collection. Usage: /xlenablecollection \"Collection Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandDisableProfile, new CommandInfo(this.OnDisableProfile) + { + HelpMessage = Loc.Localize("ProfileCommandsDisableHint", "Disable a collection. Usage: /xldisablecollection \"Collection Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandToggleProfile, new CommandInfo(this.OnToggleProfile) + { + HelpMessage = Loc.Localize("ProfileCommandsToggleHint", "Toggle a collection. Usage: /xltogglecollection \"Collection Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(LegacyCommandEnable, new CommandInfo(this.OnEnableProfile) + { + ShowInHelp = false, + }); + + this.cmd.AddHandler(LegacyCommandDisable, new CommandInfo(this.OnDisableProfile) + { + ShowInHelp = false, + }); + + this.cmd.AddHandler(LegacyCommandToggle, new CommandInfo(this.OnToggleProfile) + { + ShowInHelp = false, + }); + + this.cmd.AddHandler(CommandEnablePlugin, new CommandInfo(this.OnEnablePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsEnableHint", "Enable a plugin. Usage: /xlenableplugin \"Plugin Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandDisablePlugin, new CommandInfo(this.OnDisablePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsDisableHint", "Disable a plugin. Usage: /xldisableplugin \"Plugin Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandTogglePlugin, new CommandInfo(this.OnTogglePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsToggleHint", "Toggle a plugin. Usage: /xltoggleplugin \"Plugin Name\""), + ShowInHelp = true, + }); + + this.framework.Update += this.FrameworkOnUpdate; + } + + private enum PluginCommandOperation + { + Enable, + Disable, + Toggle, + } + + /// + void IInternalDisposableService.DisposeService() + { + this.cmd.RemoveHandler(CommandEnableProfile); + this.cmd.RemoveHandler(CommandDisableProfile); + this.cmd.RemoveHandler(CommandToggleProfile); + this.cmd.RemoveHandler(LegacyCommandEnable); + this.cmd.RemoveHandler(LegacyCommandDisable); + this.cmd.RemoveHandler(LegacyCommandToggle); + + this.framework.Update += this.FrameworkOnUpdate; + } + + private void HandleProfileOperation(string profileName, PluginCommandOperation operation) + { + var profile = this.profileManager.Profiles.FirstOrDefault( + x => x.Name == profileName); + if (profile == null || profile.IsDefaultProfile) + return; + + switch (operation) + { + case PluginCommandOperation.Enable: + if (!profile.IsEnabled) + Task.Run(() => profile.SetStateAsync(true, false)).GetAwaiter().GetResult(); + break; + case PluginCommandOperation.Disable: + if (profile.IsEnabled) + Task.Run(() => profile.SetStateAsync(false, false)).GetAwaiter().GetResult(); + break; + case PluginCommandOperation.Toggle: + Task.Run(() => profile.SetStateAsync(!profile.IsEnabled, false)).GetAwaiter().GetResult(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(operation), operation, null); + } + + this.chat.Print( + profile.IsEnabled + ? Loc.Localize("ProfileCommandsEnabling", "Enabling collection \"{0}\"...").Format(profile.Name) + : Loc.Localize("ProfileCommandsDisabling", "Disabling collection \"{0}\"...").Format(profile.Name)); + + Task.Run(this.profileManager.ApplyAllWantStatesAsync).ContinueWith(t => + { + if (!t.IsCompletedSuccessfully && t.Exception != null) + { + Log.Error(t.Exception, "Could not apply profiles through commands"); + this.chat.PrintError(Loc.Localize("ProfileCommandsApplyFailed", "Failed to apply your collections. Please check the console for errors.")); + } + else + { + this.chat.Print(Loc.Localize("ProfileCommandsApplySuccess", "Collections applied.")); + } + }); + } + + private bool HandlePluginOperation(Guid workingPluginId, PluginCommandOperation operation) + { + var plugin = this.pluginManager.InstalledPlugins.FirstOrDefault(x => x.EffectiveWorkingPluginId == workingPluginId); + if (plugin == null) + return true; + + switch (plugin.State) + { + // Ignore if the plugin is in a fail state + case PluginState.LoadError or PluginState.UnloadError: + this.chat.Print(Loc.Localize("PluginCommandsFailed", "Plugin \"{0}\" has previously failed to load/unload, not continuing.").Format(plugin.Name)); + return true; + + case PluginState.Loaded when operation == PluginCommandOperation.Enable: + this.chat.Print(Loc.Localize("PluginCommandsAlreadyEnabled", "Plugin \"{0}\" is already enabled.").Format(plugin.Name)); + return true; + case PluginState.Unloaded when operation == PluginCommandOperation.Disable: + this.chat.Print(Loc.Localize("PluginCommandsAlreadyDisabled", "Plugin \"{0}\" is already disabled.").Format(plugin.Name)); + return true; + + // Defer if this plugin is busy right now + case PluginState.Loading or PluginState.Unloading: + return false; + } + + void Continuation(Task t, string onSuccess, string onError) + { + if (!t.IsCompletedSuccessfully && t.Exception != null) + { + Log.Error(t.Exception, "Plugin command operation failed for plugin {PluginName}", plugin.Name); + this.chat.PrintError(onError); + return; + } + + this.chat.Print(onSuccess); + } + + if (operation == PluginCommandOperation.Toggle) + { + operation = plugin.State == PluginState.Loaded ? PluginCommandOperation.Disable : PluginCommandOperation.Enable; + } + + switch (operation) + { + case PluginCommandOperation.Enable: + this.chat.Print(Loc.Localize("PluginCommandsEnabling", "Enabling plugin \"{0}\"...").Format(plugin.Name)); + Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) + .ContinueWith(t => Continuation(t, + Loc.Localize("PluginCommandsEnableSuccess", "Plugin \"{0}\" enabled.").Format(plugin.Name), + Loc.Localize("PluginCommandsEnableFailed", "Failed to enable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) + .ConfigureAwait(false); + break; + case PluginCommandOperation.Disable: + this.chat.Print(Loc.Localize("PluginCommandsDisabling", "Disabling plugin \"{0}\"...").Format(plugin.Name)); + Task.Run(() => plugin.UnloadAsync()) + .ContinueWith(t => Continuation(t, + Loc.Localize("PluginCommandsDisableSuccess", "Plugin \"{0}\" disabled.").Format(plugin.Name), + Loc.Localize("PluginCommandsDisableFailed", "Failed to disable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) + .ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(nameof(operation), operation, null); + } + + return true; + } + + private void FrameworkOnUpdate(IFramework framework1) + { + if (this.profileManager.IsBusy) + { + return; + } + + if (this.commandQueue.Count > 0) + { + var op = this.commandQueue[0]; + + var remove = true; + switch (op.Target) + { + case PluginTarget pluginTarget: + remove = this.HandlePluginOperation(pluginTarget.WorkingPluginId, op.Operation); + break; + case ProfileTarget profileTarget: + this.HandleProfileOperation(profileTarget.ProfileName, op.Operation); + break; + } + + if (remove) + { + this.commandQueue.RemoveAt(0); + } + } + } + + private void OnEnableProfile(string command, string arguments) + { + var name = this.ValidateProfileName(arguments); + if (name == null) + return; + + var target = new ProfileTarget(name); + this.commandQueue = this.commandQueue.Where(x => x.Target != target).ToList(); + this.commandQueue.Add((target, PluginCommandOperation.Enable)); + } + + private void OnDisableProfile(string command, string arguments) + { + var name = this.ValidateProfileName(arguments); + if (name == null) + return; + + var target = new ProfileTarget(name); + this.commandQueue = this.commandQueue.Where(x => x.Target != target).ToList(); + this.commandQueue.Add((target, PluginCommandOperation.Disable)); + } + + private void OnToggleProfile(string command, string arguments) + { + var name = this.ValidateProfileName(arguments); + if (name == null) + return; + + var target = new ProfileTarget(name); + this.commandQueue.Add((target, PluginCommandOperation.Toggle)); + } + + private void OnEnablePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Enable)); + } + + private void OnDisablePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Disable)); + } + + private void OnTogglePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Toggle)); + } + + private string? ValidateProfileName(string arguments) + { + var name = arguments.Replace("\"", string.Empty); + if (this.profileManager.Profiles.All(x => x.Name != name)) + { + this.chat.PrintError(Loc.Localize("ProfileCommandsNotFound", "Collection \"{0}\" not found.").Format(name)); + return null; + } + + return name; + } + + private LocalPlugin? ValidatePluginName(string arguments) + { + var name = arguments.Replace("\"", string.Empty); + var targetPlugin = + this.pluginManager.InstalledPlugins.FirstOrDefault(x => x.InternalName == name || x.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)); + + if (targetPlugin == null) + { + this.chat.PrintError(Loc.Localize("PluginCommandsNotFound", "Plugin \"{0}\" not found.").Format(name)); + return null; + } + + if (!this.profileManager.IsInDefaultProfile(targetPlugin.EffectiveWorkingPluginId)) + { + this.chat.PrintError(Loc.Localize("PluginCommandsNotInDefaultProfile", "Plugin \"{0}\" is in a collection and can't be managed through commands. Manage the collection instead.") + .Format(targetPlugin.Name)); + } + + return targetPlugin; + } + + private abstract record Target; + + private record PluginTarget(Guid WorkingPluginId) : Target; + + private record ProfileTarget(string ProfileName) : Target; +} diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs deleted file mode 100644 index 7b7b4cfd0..000000000 --- a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using CheapLoc; -using Dalamud.Game; -using Dalamud.Game.Command; -using Dalamud.Game.Gui; -using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; - -namespace Dalamud.Plugin.Internal.Profiles; - -/// -/// Service responsible for profile-related chat commands. -/// -[ServiceManager.EarlyLoadedService] -internal class ProfileCommandHandler : IInternalDisposableService -{ -#pragma warning disable SA1600 - public const string CommandEnable = "/xlenablecollection"; - public const string CommandDisable = "/xldisablecollection"; - public const string CommandToggle = "/xltogglecollection"; -#pragma warning restore SA1600 - - private static readonly string LegacyCommandEnable = CommandEnable.Replace("collection", "profile"); - private static readonly string LegacyCommandDisable = CommandDisable.Replace("collection", "profile"); - private static readonly string LegacyCommandToggle = CommandToggle.Replace("collection", "profile"); - - private readonly CommandManager cmd; - private readonly ProfileManager profileManager; - private readonly ChatGui chat; - private readonly Framework framework; - - private List<(string, ProfileOp)> queue = new(); - - /// - /// Initializes a new instance of the class. - /// - /// Command handler. - /// Profile manager. - /// Chat handler. - /// Framework. - [ServiceManager.ServiceConstructor] - public ProfileCommandHandler(CommandManager cmd, ProfileManager profileManager, ChatGui chat, Framework framework) - { - this.cmd = cmd; - this.profileManager = profileManager; - this.chat = chat; - this.framework = framework; - - this.cmd.AddHandler(CommandEnable, new CommandInfo(this.OnEnableProfile) - { - HelpMessage = Loc.Localize("ProfileCommandsEnableHint", "Enable a collection. Usage: /xlenablecollection \"Collection Name\""), - ShowInHelp = true, - }); - - this.cmd.AddHandler(CommandDisable, new CommandInfo(this.OnDisableProfile) - { - HelpMessage = Loc.Localize("ProfileCommandsDisableHint", "Disable a collection. Usage: /xldisablecollection \"Collection Name\""), - ShowInHelp = true, - }); - - this.cmd.AddHandler(CommandToggle, new CommandInfo(this.OnToggleProfile) - { - HelpMessage = Loc.Localize("ProfileCommandsToggleHint", "Toggle a collection. Usage: /xltogglecollection \"Collection Name\""), - ShowInHelp = true, - }); - - this.cmd.AddHandler(LegacyCommandEnable, new CommandInfo(this.OnEnableProfile) - { - ShowInHelp = false, - }); - - this.cmd.AddHandler(LegacyCommandDisable, new CommandInfo(this.OnDisableProfile) - { - ShowInHelp = true, - }); - - this.cmd.AddHandler(LegacyCommandToggle, new CommandInfo(this.OnToggleProfile) - { - ShowInHelp = true, - }); - - this.framework.Update += this.FrameworkOnUpdate; - } - - private enum ProfileOp - { - Enable, - Disable, - Toggle, - } - - /// - void IInternalDisposableService.DisposeService() - { - this.cmd.RemoveHandler(CommandEnable); - this.cmd.RemoveHandler(CommandDisable); - this.cmd.RemoveHandler(CommandToggle); - this.cmd.RemoveHandler(LegacyCommandEnable); - this.cmd.RemoveHandler(LegacyCommandDisable); - this.cmd.RemoveHandler(LegacyCommandToggle); - - this.framework.Update += this.FrameworkOnUpdate; - } - - private void FrameworkOnUpdate(IFramework framework1) - { - if (this.profileManager.IsBusy) - return; - - if (this.queue.Count > 0) - { - var op = this.queue[0]; - this.queue.RemoveAt(0); - - var profile = this.profileManager.Profiles.FirstOrDefault(x => x.Name == op.Item1); - if (profile == null || profile.IsDefaultProfile) - return; - - switch (op.Item2) - { - case ProfileOp.Enable: - if (!profile.IsEnabled) - Task.Run(() => profile.SetStateAsync(true, false)).GetAwaiter().GetResult(); - break; - case ProfileOp.Disable: - if (profile.IsEnabled) - Task.Run(() => profile.SetStateAsync(false, false)).GetAwaiter().GetResult(); - break; - case ProfileOp.Toggle: - Task.Run(() => profile.SetStateAsync(!profile.IsEnabled, false)).GetAwaiter().GetResult(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - if (profile.IsEnabled) - { - this.chat.Print(Loc.Localize("ProfileCommandsEnabling", "Enabling collection \"{0}\"...").Format(profile.Name)); - } - else - { - this.chat.Print(Loc.Localize("ProfileCommandsDisabling", "Disabling collection \"{0}\"...").Format(profile.Name)); - } - - Task.Run(this.profileManager.ApplyAllWantStatesAsync).ContinueWith(t => - { - if (!t.IsCompletedSuccessfully && t.Exception != null) - { - Log.Error(t.Exception, "Could not apply profiles through commands"); - this.chat.PrintError(Loc.Localize("ProfileCommandsApplyFailed", "Failed to apply your collections. Please check the console for errors.")); - } - else - { - this.chat.Print(Loc.Localize("ProfileCommandsApplySuccess", "Collections applied.")); - } - }); - } - } - - private void OnEnableProfile(string command, string arguments) - { - var name = this.ValidateName(arguments); - if (name == null) - return; - - this.queue = this.queue.Where(x => x.Item1 != name).ToList(); - this.queue.Add((name, ProfileOp.Enable)); - } - - private void OnDisableProfile(string command, string arguments) - { - var name = this.ValidateName(arguments); - if (name == null) - return; - - this.queue = this.queue.Where(x => x.Item1 != name).ToList(); - this.queue.Add((name, ProfileOp.Disable)); - } - - private void OnToggleProfile(string command, string arguments) - { - var name = this.ValidateName(arguments); - if (name == null) - return; - - this.queue.Add((name, ProfileOp.Toggle)); - } - - private string? ValidateName(string arguments) - { - var name = arguments.Replace("\"", string.Empty); - if (this.profileManager.Profiles.All(x => x.Name != name)) - { - this.chat.PrintError($"No collection like \"{name}\"."); - return null; - } - - return name; - } -} diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs index e36e9908b..8a1711b0d 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs @@ -57,7 +57,7 @@ internal class ProfileManager : IServiceType /// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins. /// public bool IsBusy => this.isBusy; - + /// /// Get a disposable that will lock the profile list while it is not disposed. /// You must NEVER use this in async code. @@ -77,7 +77,7 @@ internal class ProfileManager : IServiceType { var want = false; var wasInAnyProfile = false; - + lock (this.profiles) { foreach (var profile in this.profiles) @@ -93,7 +93,7 @@ internal class ProfileManager : IServiceType if (!wasInAnyProfile && addIfNotDeclared) { - Log.Warning("'{Guid}'('{InternalName}') was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState); + Log.Warning("{Guid}({InternalName}) was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState); await this.DefaultProfile.AddOrUpdateAsync(workingPluginId, internalName, defaultState, false); return defaultState; @@ -175,7 +175,7 @@ internal class ProfileManager : IServiceType { // Disable it modelV1.IsEnabled = false; - + // Try to find matching plugins for all plugins in the profile var pm = Service.Get(); foreach (var plugin in modelV1.Plugins) @@ -313,7 +313,7 @@ internal class ProfileManager : IServiceType profile.MigrateProfilesToGuidsForPlugin(internalName, newGuid); } } - + /// /// Validate profiles for errors. /// @@ -328,7 +328,7 @@ internal class ProfileManager : IServiceType { if (seenIds.Contains(pluginEntry.WorkingPluginId)) throw new Exception($"Plugin '{pluginEntry.WorkingPluginId}'('{pluginEntry.InternalName}') is twice in profile '{profile.Guid}'('{profile.Name}')"); - + seenIds.Add(pluginEntry.WorkingPluginId); } } diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs index 5a3552199..581bfd724 100644 --- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -16,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Types; /// This class represents a dev plugin and all facets of its lifecycle. /// The DLL on disk, dependencies, loaded assembly, etc. /// -internal class LocalDevPlugin : LocalPlugin, IDisposable +internal class LocalDevPlugin : LocalPlugin { private static readonly ModuleLog Log = new("PLUGIN"); @@ -101,7 +100,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable public List DismissedValidationProblems => this.devSettings.DismissedValidationProblems; /// - public new void Dispose() + public override ValueTask DisposeAsync() { if (this.fileWatcher != null) { @@ -110,7 +109,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable this.fileWatcher.Dispose(); } - base.Dispose(); + return base.DisposeAsync(); } /// diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 00fa9d243..1b9025538 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -1,13 +1,13 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; -using Dalamud.Game.Gui; -using Dalamud.Game.Gui.Dtr; using Dalamud.Interface.Internal; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -15,7 +15,6 @@ using Dalamud.Plugin.Internal.Exceptions; using Dalamud.Plugin.Internal.Loader; using Dalamud.Plugin.Internal.Profiles; using Dalamud.Plugin.Internal.Types.Manifest; -using Dalamud.Utility; namespace Dalamud.Plugin.Internal.Types; @@ -23,7 +22,7 @@ namespace Dalamud.Plugin.Internal.Types; /// This class represents a plugin and all facets of its lifecycle. /// The DLL on disk, dependencies, loaded assembly, etc. /// -internal class LocalPlugin : IDisposable +internal class LocalPlugin : IAsyncDisposable { /// /// The underlying manifest for this plugin. @@ -31,7 +30,7 @@ internal class LocalPlugin : IDisposable #pragma warning disable SA1401 protected LocalPluginManifest manifest; #pragma warning restore SA1401 - + private static readonly ModuleLog Log = new("LOCALPLUGIN"); private readonly FileInfo manifestFile; @@ -44,6 +43,8 @@ internal class LocalPlugin : IDisposable private Assembly? pluginAssembly; private Type? pluginType; private IDalamudPlugin? instance; + private IServiceScope? serviceScope; + private DalamudPluginInterface? dalamudInterface; /// /// Initializes a new instance of the class. @@ -110,7 +111,7 @@ internal class LocalPlugin : IDisposable /// /// Gets the associated with this plugin. /// - public DalamudPluginInterface? DalamudInterface { get; private set; } + public DalamudPluginInterface? DalamudInterface => this.dalamudInterface; /// /// Gets the path to the plugin DLL. @@ -223,40 +224,11 @@ internal class LocalPlugin : IDisposable /// /// Gets the service scope for this plugin. /// - public IServiceScope? ServiceScope { get; private set; } + public IServiceScope? ServiceScope => this.serviceScope; /// - public void Dispose() - { - var framework = Service.GetNullable(); - var configuration = Service.Get(); - - var didPluginDispose = false; - if (this.instance != null) - { - didPluginDispose = true; - if (this.manifest.CanUnloadAsync || framework == null) - this.instance.Dispose(); - else - framework.RunOnFrameworkThread(() => this.instance.Dispose()).Wait(); - - this.instance = null; - } - - this.DalamudInterface?.Dispose(); - - this.DalamudInterface = null; - - this.ServiceScope?.Dispose(); - this.ServiceScope = null; - - this.pluginType = null; - this.pluginAssembly = null; - - if (this.loader != null && didPluginDispose) - Thread.Sleep(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault); - this.loader?.Dispose(); - } + public virtual async ValueTask DisposeAsync() => + await this.ClearAndDisposeAllResources(PluginLoaderDisposalMode.ImmediateDispose); /// /// Load this plugin. @@ -266,7 +238,6 @@ internal class LocalPlugin : IDisposable /// A task. public async Task LoadAsync(PluginLoadReason reason, bool reloading = false) { - var framework = await Service.GetAsync(); var ioc = await Service.GetAsync(); var pluginManager = await Service.GetAsync(); var dalamud = await Service.GetAsync(); @@ -303,11 +274,17 @@ internal class LocalPlugin : IDisposable if (!this.IsDev) { throw new InvalidPluginOperationException( - $"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); + $"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); } break; case PluginState.Unloaded: + if (this.instance is not null) + { + throw new InternalPluginStateException( + "Plugin should have been unloaded but instance is not cleared"); + } + break; case PluginState.Loading: case PluginState.Unloading: @@ -337,7 +314,7 @@ internal class LocalPlugin : IDisposable this.State = PluginState.Loading; Log.Information($"Loading {this.DllFile.Name}"); - + this.EnsureLoader(); if (this.DllFile.DirectoryName != null && @@ -405,49 +382,38 @@ internal class LocalPlugin : IDisposable } } - // Update the location for the Location and CodeBase patches - // NET8 CHORE - // PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile); + this.dalamudInterface = new(this, reason); - this.DalamudInterface = - new DalamudPluginInterface(this, reason); - - this.ServiceScope = ioc.GetScope(); - this.ServiceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it + this.serviceScope = ioc.GetScope(); + this.serviceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it try { - var forceFrameworkThread = this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1; - var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create(); - this.instance = await newInstanceTask.ConfigureAwait(false); - - async Task Create() => - (IDalamudPlugin)await this.ServiceScope!.CreateAsync(this.pluginType!, this.DalamudInterface!); + this.instance = await CreatePluginInstance( + this.manifest, + this.serviceScope, + this.pluginType, + this.dalamudInterface); + this.State = PluginState.Loaded; + Log.Information("Finished loading {PluginName}", this.InternalName); } catch (Exception ex) - { - Log.Error(ex, "Exception during plugin initialization"); - this.instance = null; - } - - if (this.instance == null) { this.State = PluginState.LoadError; - this.UnloadAndDisposeState(); - Log.Error( - "Error while loading {PluginName}, failed to bind and call the plugin constructor", this.InternalName); - return; + ex, + "Error while loading {PluginName}, failed to bind and call the plugin constructor", + this.InternalName); + await this.ClearAndDisposeAllResources(PluginLoaderDisposalMode.ImmediateDispose); } - - this.State = PluginState.Loaded; - Log.Information("Finished loading {PluginName}", this.InternalName); } catch (Exception ex) { - this.State = PluginState.LoadError; + // These are "user errors", we don't want to mark the plugin as failed + if (ex is not InvalidPluginOperationException) + this.State = PluginState.LoadError; - // If a precondition fails, don't record it as an error, as it isn't really. + // If a precondition fails, don't record it as an error, as it isn't really. if (ex is PluginPreconditionFailedException) Log.Warning(ex.Message); else @@ -465,14 +431,10 @@ internal class LocalPlugin : IDisposable /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay /// in the plugin list until it has been actually disposed. /// - /// Unload while reloading. - /// Wait before disposing loader. + /// How to dispose loader. /// The task. - public async Task UnloadAsync(bool reloading = false, bool waitBeforeLoaderDispose = true) + public async Task UnloadAsync(PluginLoaderDisposalMode disposalMode = PluginLoaderDisposalMode.WaitBeforeDispose) { - var configuration = Service.Get(); - var framework = Service.GetNullable(); - await this.pluginLoadStateLock.WaitAsync(); try { @@ -501,31 +463,10 @@ internal class LocalPlugin : IDisposable this.State = PluginState.Unloading; Log.Information("Unloading {PluginName}", this.InternalName); - try - { - if (this.manifest.CanUnloadAsync || framework == null) - this.instance?.Dispose(); - else - await framework.RunOnFrameworkThread(() => this.instance?.Dispose()).ConfigureAwait(false); - } - catch (Exception e) + if (await this.ClearAndDisposeAllResources(disposalMode) is { } ex) { this.State = PluginState.UnloadError; - Log.Error(e, "Could not unload {PluginName}, error in plugin dispose", this.InternalName); - return; - } - finally - { - this.instance = null; - this.UnloadAndDisposeState(); - - if (!reloading) - { - if (waitBeforeLoaderDispose && this.loader != null) - await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault); - this.loader?.Dispose(); - this.loader = null; - } + throw ex; } this.State = PluginState.Unloaded; @@ -533,16 +474,16 @@ internal class LocalPlugin : IDisposable } catch (Exception ex) { - this.State = PluginState.UnloadError; + // These are "user errors", we don't want to mark the plugin as failed + if (ex is not InvalidPluginOperationException) + this.State = PluginState.UnloadError; + Log.Error(ex, "Error while unloading {PluginName}", this.InternalName); throw; } finally { - // We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates. - Service.GetNullable()?.HandleRemovedNodes(); - this.pluginLoadStateLock.Release(); } } @@ -555,7 +496,7 @@ internal class LocalPlugin : IDisposable { // Don't unload if we're a dev plugin and have an unload error, this is a bad idea but whatever if (this.IsDev && this.State != PluginState.UnloadError) - await this.UnloadAsync(true); + await this.UnloadAsync(PluginLoaderDisposalMode.None); await this.LoadAsync(PluginLoadReason.Reload, true); } @@ -569,9 +510,6 @@ internal class LocalPlugin : IDisposable var startInfo = Service.Get().StartInfo; var manager = Service.Get(); - if (startInfo.NoLoadPlugins) - return false; - if (startInfo.NoLoadThirdPartyPlugins && this.manifest.IsThirdParty) return false; @@ -615,7 +553,7 @@ internal class LocalPlugin : IDisposable /// /// Why it should be saved. protected void SaveManifest(string reason) => this.manifest.Save(this.manifestFile, reason); - + /// /// Called before a plugin is reloaded. /// @@ -623,6 +561,26 @@ internal class LocalPlugin : IDisposable { } + /// Creates a new instance of the plugin. + /// Plugin manifest. + /// Service scope. + /// Type of the plugin main class. + /// Instance of . + /// A new instance of the plugin. + private static async Task CreatePluginInstance( + LocalPluginManifest manifest, + IServiceScope scope, + Type type, + DalamudPluginInterface dalamudInterface) + { + var framework = await Service.GetAsync(); + var forceFrameworkThread = manifest.LoadSync && manifest.LoadRequiredState is 0 or 1; + var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create(); + return await newInstanceTask.ConfigureAwait(false); + + async Task Create() => (IDalamudPlugin)await scope.CreateAsync(type, dalamudInterface); + } + private static void SetupLoaderConfig(LoaderConfig config) { config.IsUnloadable = true; @@ -634,19 +592,23 @@ internal class LocalPlugin : IDisposable // but plugins may load other versions of assemblies that Dalamud depends on. config.SharedAssemblies.Add((typeof(EntryPoint).Assembly.GetName(), false)); config.SharedAssemblies.Add((typeof(Common.DalamudStartInfo).Assembly.GetName(), false)); - + // Pin Lumina since we expose it as an API surface. Before anyone removes this again, please see #1598. // Changes to Lumina should be upstreamed if feasible, and if there is a desire to re-add unpinned Lumina we // will need to put this behind some kind of feature flag somewhere. config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true)); - config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true)); + config.SharedAssemblies.Add((typeof(Lumina.Excel.Sheets.Addon).Assembly.GetName(), true)); } private void EnsureLoader() { if (this.loader != null) return; - + + this.DllFile.Refresh(); + if (!this.DllFile.Exists) + throw new Exception($"Plugin DLL file at '{this.DllFile.FullName}' did not exist, cannot load."); + try { this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig); @@ -694,18 +656,110 @@ internal class LocalPlugin : IDisposable } } - private void UnloadAndDisposeState() + /// Clears and disposes all resources associated with the plugin instance. + /// Whether to clear and dispose . + /// Exceptions, if any occurred. + private async Task ClearAndDisposeAllResources(PluginLoaderDisposalMode disposalMode) { - if (this.instance != null) - throw new InvalidOperationException("Plugin instance should be disposed at this point"); + List? exceptions = null; + Log.Verbose( + "{name}({id}): {fn}(disposalMode={disposalMode})", + this.InternalName, + this.EffectiveWorkingPluginId, + nameof(this.ClearAndDisposeAllResources), + disposalMode); - this.DalamudInterface?.Dispose(); - this.DalamudInterface = null; - - this.ServiceScope?.Dispose(); - this.ServiceScope = null; + // Clear the plugin instance first. + if (!await AttemptCleanup( + nameof(this.instance), + Interlocked.Exchange(ref this.instance, null), + this.manifest, + static async (inst, manifest) => + { + var framework = Service.GetNullable(); + if (manifest.CanUnloadAsync || framework is null) + inst.Dispose(); + else + await framework.RunOnFrameworkThread(inst.Dispose).ConfigureAwait(false); + })) + { + // Plugin was not loaded; loader is not referenced anyway, so no need to wait. + disposalMode = PluginLoaderDisposalMode.ImmediateDispose; + } + // Fields below are expected to be alive until the plugin is (attempted) disposed. + // Clear them after this point. this.pluginType = null; this.pluginAssembly = null; + + await AttemptCleanup( + nameof(this.serviceScope), + Interlocked.Exchange(ref this.serviceScope, null), + 0, + static (x, _) => x.DisposeAsync()); + + await AttemptCleanup( + nameof(this.dalamudInterface), + Interlocked.Exchange(ref this.dalamudInterface, null), + 0, + static (x, _) => + { + x.Dispose(); + return ValueTask.CompletedTask; + }); + + if (disposalMode != PluginLoaderDisposalMode.None) + { + await AttemptCleanup( + nameof(this.loader), + Interlocked.Exchange(ref this.loader, null), + disposalMode == PluginLoaderDisposalMode.WaitBeforeDispose + ? Service.Get().PluginWaitBeforeFree ?? + PluginManager.PluginWaitBeforeFreeDefault + : 0, + static async (ldr, waitBeforeDispose) => + { + // Just in case plugins still have tasks running that they didn't cancel when they should have, + // give them some time to complete it. + // This helps avoid plugins being reloaded from conflicting with itself of previous instance. + await Task.Delay(waitBeforeDispose); + + ldr.Dispose(); + }); + } + + return exceptions is not null + ? (AggregateException)ExceptionDispatchInfo.SetCurrentStackTrace(new AggregateException(exceptions)) + : null; + + async ValueTask AttemptCleanup( + string name, + T? what, + TContext context, + Func cb) + where T : class + { + if (what is null) + return false; + + try + { + await cb.Invoke(what, context); + Log.Verbose("{name}({id}): {what} disposed", this.InternalName, this.EffectiveWorkingPluginId, name); + } + catch (Exception ex) + { + exceptions ??= []; + exceptions.Add(ex); + Log.Error( + ex, + "{name}({id}): Failed to dispose {what}", + this.InternalName, + this.EffectiveWorkingPluginId, + name); + } + + return true; + } } } diff --git a/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs index dc05409b0..3aededa18 100644 --- a/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs @@ -57,15 +57,15 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest /// The reason the manifest was saved. public void Save(FileInfo manifestFile, string reason) { - Log.Verbose("Saving manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason); + Log.Verbose("Saving manifest for {PluginName} because {Reason}", this.InternalName, reason); try { - Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); + FilesystemUtil.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); } catch { - Log.Error("Could not write out manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason); + Log.Error("Could not write out manifest for {PluginName} because {Reason}", this.InternalName, reason); throw; } } @@ -78,7 +78,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest public static LocalPluginManifest? Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); /// - /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. + /// A standardized way to get the plugin DLL name that should accompany a manifest file. /// /// Manifest directory. /// The manifest. @@ -86,7 +86,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); /// - /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. + /// A standardized way to get the manifest file that should accompany a plugin DLL. /// /// The plugin DLL. /// The file. diff --git a/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs b/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs new file mode 100644 index 000000000..495205089 --- /dev/null +++ b/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +using Dalamud.Plugin.Internal.Loader; + +namespace Dalamud.Plugin.Internal.Types; + +/// Specify how to dispose . +internal enum PluginLoaderDisposalMode +{ + /// Do not dispose the plugin loader. + None, + + /// Whether to wait a few before disposing the loader, just in case there are s + /// from the plugin that are still running. + WaitBeforeDispose, + + /// Immediately dispose the plugin loader. + ImmediateDispose, +} diff --git a/Dalamud/Plugin/Internal/Types/PluginRepository.cs b/Dalamud/Plugin/Internal/Types/PluginRepository.cs index 2f63070c3..c5e703e4b 100644 --- a/Dalamud/Plugin/Internal/Types/PluginRepository.cs +++ b/Dalamud/Plugin/Internal/Types/PluginRepository.cs @@ -205,6 +205,13 @@ internal class PluginRepository return false; } + if (manifest.TestingAssemblyVersion != null && + manifest.TestingAssemblyVersion > manifest.AssemblyVersion && + manifest.TestingDalamudApiLevel == null) + { + Log.Warning("The plugin {PluginName} in {RepoLink} has a testing version available, but it lacks an associated testing API. The 'TestingDalamudApiLevel' property is required.", manifest.InternalName, this.PluginMasterUrl); + } + return true; } diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index cf4c59b2e..f4e5c76d7 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -1,11 +1,28 @@ using Dalamud.Plugin.Ipc.Internal; +using Dalamud.Utility; #pragma warning disable SA1402 // File may only contain a single type namespace Dalamud.Plugin.Ipc; -/// -public interface ICallGateProvider +/// +/// The backing interface for the provider ("server") half of an IPC channel. This interface is used to expose methods +/// to other plugins via RPC, as well as to allow other plugins to subscribe to notifications from this plugin. +/// +public interface ICallGateProvider +{ + /// + public int SubscriptionCount { get; } + + /// + public void UnregisterAction(); + + /// + public void UnregisterFunc(); +} + +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -13,18 +30,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -32,18 +43,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -51,18 +56,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -70,18 +69,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -89,18 +82,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -108,18 +95,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -127,18 +108,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -146,18 +121,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -165,12 +134,6 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); } diff --git a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs index 40d642a61..9bb1d0550 100644 --- a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs +++ b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs @@ -4,8 +4,20 @@ using Dalamud.Plugin.Ipc.Internal; namespace Dalamud.Plugin.Ipc; +/// +/// An interface for all IPC subscribers. +/// +public interface ICallGateSubscriber +{ + /// + public bool HasAction { get; } + + /// + public bool HasFunction { get; } +} + /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -21,7 +33,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -37,7 +49,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -53,7 +65,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -69,7 +81,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -84,7 +96,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -100,7 +112,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -116,7 +128,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -132,7 +144,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs index b6a4e8a61..308457373 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs @@ -16,71 +16,118 @@ internal abstract class CallGatePubSubBase this.Channel = Service.Get().GetOrCreateChannel(name); } + /// + /// Gets a value indicating whether this IPC call gate has an associated Action. Only exposed to + /// s. + /// + public bool HasAction => this.Channel.Action != null; + + /// + /// Gets a value indicating whether this IPC call gate has an associated Function. Only exposed to + /// s. + /// + public bool HasFunction => this.Channel.Func != null; + + /// + /// Gets the count of subscribers listening for messages through this call gate. Only exposed to + /// s, and can be used to determine if messages should be sent through the gate. + /// + public int SubscriptionCount => this.Channel.Subscriptions.Count; + /// /// Gets the underlying channel implementation. /// protected CallGateChannel Channel { get; init; } - + /// - /// Removes a registered Action from inter-plugin communication. + /// Removes the associated Action from this call gate, effectively disabling RPC calls. /// + /// public void UnregisterAction() => this.Channel.Action = null; /// - /// Removes a registered Func from inter-plugin communication. + /// Removes the associated Function from this call gate. /// + /// public void UnregisterFunc() => this.Channel.Func = null; /// - /// Registers an Action for inter-plugin communication. + /// Registers a for use by other plugins via RPC. This Delegate must satisfy the constraints + /// of an type as defined by the interface, meaning they may not return a value and must have + /// the proper number of parameters. /// /// Action to register. + /// + /// private protected void RegisterAction(Delegate action) => this.Channel.Action = action; /// - /// Registers a Func for inter-plugin communication. + /// Registers a for use by other plugins via RPC. This Delegate must satisfy the constraints + /// of a type as defined by the interface, meaning its return type and parameters must + /// match accordingly. /// /// Func to register. + /// + /// private protected void RegisterFunc(Delegate func) => this.Channel.Func = func; /// - /// Subscribe an expression to this registration. + /// Registers a (of type ) that will be called when the providing + /// plugin calls . This method can be used to receive notifications + /// of events or data updates from a specific plugin. /// /// Action to subscribe. + /// private protected void Subscribe(Delegate action) => this.Channel.Subscribe(action); /// - /// Unsubscribe an expression from this registration. + /// Removes a subscription created through . Note that the to be + /// unsubscribed must be the same instance as the one passed in. /// /// Action to unsubscribe. + /// private protected void Unsubscribe(Delegate action) => this.Channel.Unsubscribe(action); /// - /// Invoke an action registered for inter-plugin communication. + /// Executes the Action registered for this IPC call gate via . This method is intended + /// to be called by plugins wishing to access another plugin via RPC. The parameters passed to this method will be + /// passed to the owning plugin, with appropriate serialization for complex data types. Primitive data types will + /// be passed as-is. The target Action will be called on the same thread as the caller. /// /// Action arguments. /// This is thrown when the IPC publisher has not registered an action for calling yet. + /// + /// private protected void InvokeAction(params object?[]? args) => this.Channel.InvokeAction(args); /// - /// Invoke a function registered for inter-plugin communication. + /// Executes the Function registered for this IPC call gate via . This method is intended + /// to be called by plugins wishing to access another plugin via RPC. The parameters passed to this method will be + /// passed to the owning plugin, with appropriate serialization for complex data types. Primitive data types will + /// be passed as-is. The target Action will be called on the same thread as the caller. /// /// Parameter args. /// The return value. /// The return type. /// This is thrown when the IPC publisher has not registered a func for calling yet. + /// + /// private protected TRet InvokeFunc(params object?[]? args) => this.Channel.InvokeFunc(args); /// - /// Invoke all actions that have subscribed to this IPC. + /// Send the given arguments to all subscribers (through ) of this IPC call gate. This method + /// is intended to be used by the provider plugin to notify all subscribers of an event or data update. The + /// parameters passed to this method will be passed to all subscribers, with appropriate serialization for complex + /// data types. Primitive data types will be passed as-is. The subscription actions will be called sequentially in + /// order of registration on the same thread as the caller. /// /// Delegate arguments. private protected void SendMessage(params object?[]? args) diff --git a/Dalamud/Plugin/PluginLoadReason.cs b/Dalamud/Plugin/PluginLoadReason.cs index d4c1a3b26..2b494c549 100644 --- a/Dalamud/Plugin/PluginLoadReason.cs +++ b/Dalamud/Plugin/PluginLoadReason.cs @@ -3,32 +3,31 @@ namespace Dalamud.Plugin; /// /// This enum reflects reasons for loading a plugin. /// +[Flags] public enum PluginLoadReason { /// /// We don't know why this plugin was loaded. /// - Unknown, + Unknown = 1 << 0, /// /// This plugin was loaded because it was installed with the plugin installer. /// - Installer, + Installer = 1 << 1, /// /// This plugin was loaded because it was just updated. /// - Update, + Update = 1 << 2, /// /// This plugin was loaded because it was told to reload. /// - Reload, + Reload = 1 << 3, /// /// This plugin was loaded because the game was started or Dalamud was reinjected. /// - Boot, + Boot = 1 << 4, } - -// TODO(api9): This should be a mask, so that we can combine Installer | ProfileLoaded diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 09f485ac2..3f221b3bb 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -48,7 +48,7 @@ public interface IChatGui /// The sender name. /// The message sent. public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message); - + /// /// Event that will be fired when a chat message is sent to chat by the game. /// @@ -68,17 +68,17 @@ public interface IChatGui /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. /// public event OnMessageUnhandledDelegate ChatMessageUnhandled; - + /// /// Gets the ID of the last linked item. /// - public int LastLinkedItemId { get; } - + public uint LastLinkedItemId { get; } + /// /// Gets the flags of the last linked item. /// public byte LastLinkedItemFlags { get; } - + /// /// Gets the dictionary of Dalamud Link Handlers. /// @@ -121,4 +121,20 @@ public interface IChatGui /// String to prepend message with "[messageTag] ". /// Color to display the message tag with. public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); } diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index db4903178..bac2b3e3f 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -9,11 +9,42 @@ namespace Dalamud.Plugin.Services; /// public interface IClientState { + /// + /// A delegate type used for the event. + /// + /// The new ClassJob id. + public delegate void ClassJobChangeDelegate(uint classJobId); + + /// + /// A delegate type used for the event. + /// + /// The ClassJob id. + /// The level of the corresponding ClassJob. + public delegate void LevelChangeDelegate(uint classJobId, uint level); + + /// + /// A delegate type used for the event. + /// + /// The type of logout. + /// The success/failure code. + public delegate void LogoutDelegate(int type, int code); + /// /// Event that gets fired when the current Territory changes. /// public event Action TerritoryChanged; + /// + /// Event that fires when a characters ClassJob changed. + /// + public event ClassJobChangeDelegate? ClassJobChanged; + + /// + /// Event that fires when any character level changes, including levels + /// for a not-currently-active ClassJob (e.g. PvP matches, DoH/DoL). + /// + public event LevelChangeDelegate? LevelChanged; + /// /// Event that fires when a character is logging in, and the local character object is available. /// @@ -22,7 +53,7 @@ public interface IClientState /// /// Event that fires when a character is logging out. /// - public event Action Logout; + public event LogoutDelegate Logout; /// /// Event that fires when a character is entering PvP. @@ -37,7 +68,7 @@ public interface IClientState /// /// Event that gets fired when a duty is ready. /// - public event Action CfPop; + public event Action CfPop; /// /// Gets the language of the client. diff --git a/Dalamud/Plugin/Services/IDataManager.cs b/Dalamud/Plugin/Services/IDataManager.cs index dd649bd57..65c51a9fb 100644 --- a/Dalamud/Plugin/Services/IDataManager.cs +++ b/Dalamud/Plugin/Services/IDataManager.cs @@ -1,8 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; + using Dalamud.Game; using Lumina; using Lumina.Data; +using Lumina.Data.Structs.Excel; using Lumina.Excel; +using Lumina.Excel.Exceptions; namespace Dalamud.Plugin.Services; @@ -34,17 +39,38 @@ public interface IDataManager /// /// Get an with the given Excel sheet row type. /// + /// Language of the sheet to get. Leave or empty to use the default language. + /// Explicitly provide the name of the sheet to get. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. /// The excel sheet type to get. /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet() where T : ExcelRow; + /// + /// If the sheet type you want has subrows, use instead. + /// + /// Sheet name was not specified neither via 's nor . + /// does not have a valid . + /// Sheet does not exist. + /// Sheet had a mismatched column hash. + /// Sheet does not support nor . + /// Sheet was not a . + public ExcelSheet GetExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow; /// - /// Get an with the given Excel sheet row type with a specified language. + /// Get a with the given Excel sheet row type. /// - /// Language of the sheet to get. + /// Language of the sheet to get. Leave or empty to use the default language. + /// Explicitly provide the name of the sheet to get. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow; + /// The , giving access to game rows. + /// + /// If the sheet type you want has only rows, use instead. + /// + /// Sheet name was not specified neither via 's nor . + /// does not have a valid . + /// Sheet does not exist. + /// Sheet had a mismatched column hash. + /// Sheet does not support nor . + /// Sheet was not a . + public SubrowExcelSheet GetSubrowExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelSubrow; /// /// Get a with the given path. @@ -61,6 +87,16 @@ public interface IDataManager /// The of the file. public T? GetFile(string path) where T : FileResource; + /// + /// Get a with the given path, of the given type. + /// + /// The type of resource. + /// The path inside of the game files. + /// Cancellation token. + /// A containing the of the file on success. + /// + public Task GetFileAsync(string path, CancellationToken cancellationToken) where T : FileResource; + /// /// Check if the file with the given path exists within the game's index files. /// diff --git a/Dalamud/Plugin/Services/IDtrBar.cs b/Dalamud/Plugin/Services/IDtrBar.cs index 733a6d7e1..8ab34c6f2 100644 --- a/Dalamud/Plugin/Services/IDtrBar.cs +++ b/Dalamud/Plugin/Services/IDtrBar.cs @@ -2,7 +2,6 @@ using Dalamud.Game.Gui.Dtr; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Utility; namespace Dalamud.Plugin.Services; @@ -12,10 +11,13 @@ namespace Dalamud.Plugin.Services; public interface IDtrBar { /// - /// Gets a read-only list of all DTR bar entries. + /// Gets a read-only copy of the list of all DTR bar entries. /// - public IReadOnlyList Entries { get; } - + /// If the list changes due to changes in order or insertion/removal, then this property will return a + /// completely new object on getter invocation. The returned object is safe to use from any thread, and will not + /// change. + IReadOnlyList Entries { get; } + /// /// Get a DTR bar entry. /// This allows you to add your own text, and users to sort it. @@ -24,11 +26,13 @@ public interface IDtrBar /// The text the entry shows. /// The entry object used to update, hide and remove the entry. /// Thrown when an entry with the specified title exists. - public IDtrBarEntry Get(string title, SeString? text = null); + IDtrBarEntry Get(string title, SeString? text = null); /// /// Removes a DTR bar entry from the system. /// /// Title of the entry to remove. - public void Remove(string title); + /// Remove operation is not guaranteed to be immediately effective. Calls to may result + /// in an entry marked to be remove being revived and used again. + void Remove(string title); } diff --git a/Dalamud/Plugin/Services/IGameConfig.cs b/Dalamud/Plugin/Services/IGameConfig.cs index ed70b5753..5d8378659 100644 --- a/Dalamud/Plugin/Services/IGameConfig.cs +++ b/Dalamud/Plugin/Services/IGameConfig.cs @@ -101,7 +101,15 @@ public interface IGameConfig /// Details of the option: Minimum, Maximum, and Default values. /// A value representing the success. public bool TryGet(SystemConfigOption option, out FloatConfigProperties? properties); - + + /// + /// Attempts to get a string config value as a gamepad button enum value from the UiConfig section. + /// + /// Option to get the value of. + /// The returned value of the config option. + /// A value representing the success. + public bool TryGet(SystemConfigOption option, out PadButtonValue value); + /// /// Attempts to get the properties of a String option from the System section. /// diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs index a1b1114d7..0dff1ff03 100644 --- a/Dalamud/Plugin/Services/IGameInventory.cs +++ b/Dalamud/Plugin/Services/IGameInventory.cs @@ -103,4 +103,11 @@ public interface IGameInventory /// public event InventoryChangedDelegate ItemMergedExplicit; + + /// + /// Gets all item slots of the specified inventory type. + /// + /// The type of inventory to get the items for. + /// A read-only span of all items in the specified inventory type. + public ReadOnlySpan GetInventoryItems(GameInventoryType type); } diff --git a/Dalamud/Plugin/Services/INamePlateGui.cs b/Dalamud/Plugin/Services/INamePlateGui.cs index 713d9120b..eb2579bae 100644 --- a/Dalamud/Plugin/Services/INamePlateGui.cs +++ b/Dalamud/Plugin/Services/INamePlateGui.cs @@ -26,6 +26,15 @@ public interface INamePlateGui /// event OnPlateUpdateDelegate? OnNamePlateUpdate; + /// + /// An event which fires after nameplate data is updated and at least one nameplate had important updates. The + /// subscriber is provided with a list of handlers for nameplates with important updates. + /// + /// + /// Fires before . + /// + event OnPlateUpdateDelegate? OnPostNamePlateUpdate; + /// /// An event which fires when nameplate data is updated. The subscriber is provided with a list of handlers for all /// nameplates. @@ -36,6 +45,16 @@ public interface INamePlateGui /// event OnPlateUpdateDelegate? OnDataUpdate; + /// + /// An event which fires after nameplate data is updated. The subscriber is provided with a list of handlers for all + /// nameplates. + /// + /// + /// This event is likely to fire every frame even when no nameplates are actually updated, so in most cases + /// is preferred. Fires after . + /// + event OnPlateUpdateDelegate? OnPostDataUpdate; + /// /// Requests that all nameplates should be redrawn on the following frame. /// diff --git a/Dalamud/Plugin/Services/IPluginLog.cs b/Dalamud/Plugin/Services/IPluginLog.cs index dcba5bb29..38406fd91 100644 --- a/Dalamud/Plugin/Services/IPluginLog.cs +++ b/Dalamud/Plugin/Services/IPluginLog.cs @@ -1,4 +1,5 @@ -using Serilog.Events; +using Serilog; +using Serilog.Events; #pragma warning disable CS1573 // See https://github.com/dotnet/roslyn/issues/40325 @@ -9,6 +10,12 @@ namespace Dalamud.Plugin.Services; /// public interface IPluginLog { + /// + /// Gets a Serilog ILogger instance for this plugin. This is the entrypoint for plugins that wish to use more + /// advanced logging functionality. + /// + public ILogger Logger { get; } + /// /// Gets or sets the minimum log level that will be recorded from this plugin to Dalamud's logs. This may be set /// by either the plugin or by Dalamud itself. diff --git a/Dalamud/Plugin/Services/ISeStringEvaluator.cs b/Dalamud/Plugin/Services/ISeStringEvaluator.cs new file mode 100644 index 000000000..2bd423b7c --- /dev/null +++ b/Dalamud/Plugin/Services/ISeStringEvaluator.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.CodeAnalysis; + +using Dalamud.Game; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.Text.Evaluator; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Plugin.Services; + +/// +/// Defines a service for retrieving localized text for various in-game entities. +/// +[Experimental("SeStringEvaluator")] +public interface ISeStringEvaluator +{ + /// + /// Evaluates macros in a . + /// + /// The string containing macros. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString Evaluate(ReadOnlySeString str, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in a . + /// + /// The string containing macros. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString Evaluate(ReadOnlySeStringSpan str, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the Addon sheet. + /// + /// The row id of the Addon sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromAddon(uint addonId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the Lobby sheet. + /// + /// The row id of the Lobby sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromLobby(uint lobbyId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the LogMessage sheet. + /// + /// The row id of the LogMessage sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromLogMessage(uint logMessageId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates ActStr from the given ActionKind and id. + /// + /// The ActionKind. + /// The action id. + /// An optional language override. + /// The name of the action. + string EvaluateActStr(ActionKind actionKind, uint id, ClientLanguage? language = null); + + /// + /// Evaluates ObjStr from the given ObjectKind and id. + /// + /// The ObjectKind. + /// The object id. + /// An optional language override. + /// The singular name of the object. + string EvaluateObjStr(ObjectKind objectKind, uint id, ClientLanguage? language = null); +} diff --git a/Dalamud/Plugin/Services/ISigScanner.cs b/Dalamud/Plugin/Services/ISigScanner.cs index 1aedb01fd..c0ebd9310 100644 --- a/Dalamud/Plugin/Services/ISigScanner.cs +++ b/Dalamud/Plugin/Services/ISigScanner.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; namespace Dalamud.Game; @@ -146,4 +148,19 @@ public interface ISigScanner /// The real offset of the signature, if found. /// true if the signature was found. public bool TryScanText(string signature, out nint result); + + /// + /// Scan for all matching byte signatures in the .text section. + /// + /// The Signature. + /// The list of real offsets of the found elements based on signature. + public nint[] ScanAllText(string signature); + + /// + /// Scan for all matching byte signatures in the .text section. + /// + /// The Signature. + /// Cancellation token. + /// Enumerable yielding the real offsets of the found elements based on signature. + public IEnumerable ScanAllText(string signature, CancellationToken cancellationToken); } diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index d914b1091..ff13f11f1 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Windows.Data.Widgets; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; @@ -281,4 +280,20 @@ public interface ITextureProvider /// true if supported. /// This function does not throw exceptions. bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat); + + /// Converts an existing instance to a new instance of + /// which can be used to supply a custom + /// texture onto an in-game addon (UI element.) + /// Instance of to convert. + /// Whether to leave non-disposed when the returned + /// completes. + /// Address of the new . + /// See PrintTextureInfo in for an example + /// of replacing the texture of an image node. + /// + /// If the returned kernel texture is to be destroyed, call the fourth function in its vtable, by calling + /// or + /// ((delegate* unmanaged<nint, void>)(*(nint**)ptr)[3](ptr). + /// + nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false); } diff --git a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs index 309be103a..b41ded41f 100644 --- a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs +++ b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs @@ -22,6 +22,9 @@ public interface ITextureReadbackProvider /// /// The length of the returned RawData may not match /// * . + /// may not be the minimal value required to represent the texture + /// bitmap data. For example, if a texture is 4x4 B8G8R8A8, the minimal pitch would be 32, but the function may + /// return 64 instead. /// This function may throw an exception. /// Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync( diff --git a/Dalamud/Plugin/Services/ITitleScreenMenu.cs b/Dalamud/Plugin/Services/ITitleScreenMenu.cs index 5ebd80017..9f7b17ea3 100644 --- a/Dalamud/Plugin/Services/ITitleScreenMenu.cs +++ b/Dalamud/Plugin/Services/ITitleScreenMenu.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; using Dalamud.Interface; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; namespace Dalamud.Plugin.Services; @@ -20,22 +19,22 @@ public interface ITitleScreenMenu /// Adds a new entry to the title screen menu. /// /// The text to show. - /// The texture to show. + /// The texture to show. The texture must be 64x64 or the entry will be removed and an error will be logged. /// The action to execute when the option is selected. /// A object that can be reference the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered); + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered); /// /// Adds a new entry to the title screen menu. /// /// Priority of the entry. /// The text to show. - /// The texture to show. + /// The texture to show. The texture must be 64x64 or the entry will be removed and an error will be logged. /// The action to execute when the option is selected. /// A object that can be used to reference the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered); + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered); /// /// Remove an entry from the title screen menu. diff --git a/Dalamud/Service/LoadingDialog.cs b/Dalamud/Service/LoadingDialog.cs index f788ffb71..424087743 100644 --- a/Dalamud/Service/LoadingDialog.cs +++ b/Dalamud/Service/LoadingDialog.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; +using System.Threading.Tasks; using CheapLoc; @@ -31,15 +32,13 @@ namespace Dalamud; "StyleCop.CSharp.LayoutRules", "SA1519:Braces should not be omitted from multi-line child statement", Justification = "Multiple fixed blocks")] -internal sealed unsafe class LoadingDialog +internal sealed class LoadingDialog { private readonly RollingList logs = new(20); + private readonly TaskCompletionSource hwndTaskDialog = new(); private Thread? thread; - private HWND hwndTaskDialog; private DateTime firstShowTime; - private State currentState = State.LoadingDalamud; - private bool canHide; /// /// Enum representing the state of the dialog. @@ -72,35 +71,13 @@ internal sealed unsafe class LoadingDialog /// /// Gets or sets the current state of the dialog. /// - public State CurrentState - { - get => this.currentState; - set - { - if (this.currentState == value) - return; - - this.currentState = value; - this.UpdateMainInstructionText(); - } - } + public State CurrentState { get; set; } = State.LoadingDalamud; /// /// Gets or sets a value indicating whether the dialog can be hidden by the user. /// /// Thrown if called before the dialog has been created. - public bool CanHide - { - get => this.canHide; - set - { - if (this.canHide == value) - return; - - this.canHide = value; - this.UpdateButtonEnabled(); - } - } + public bool CanHide { get; set; } /// /// Show the dialog. @@ -110,7 +87,7 @@ internal sealed unsafe class LoadingDialog if (IsGloballyHidden) return; - if (this.thread?.IsAlive == true) + if (this.thread is not null) return; this.thread = new Thread(this.ThreadStart) @@ -126,22 +103,28 @@ internal sealed unsafe class LoadingDialog /// /// Hide the dialog. /// - public void HideAndJoin() + /// A representing the asynchronous operation. + public async Task HideAndJoin() { IsGloballyHidden = true; - if (this.thread?.IsAlive is not true) + if (this.hwndTaskDialog.TrySetCanceled() || this.hwndTaskDialog.Task.IsCanceled) return; - SendMessageW(this.hwndTaskDialog, WM.WM_CLOSE, default, default); - this.thread.Join(); + try + { + SendMessageW(await this.hwndTaskDialog.Task, WM.WM_CLOSE, default, default); + } + catch (OperationCanceledException) + { + // ignore + } + + this.thread?.Join(); } - private void UpdateMainInstructionText() + private unsafe void UpdateMainInstructionText(HWND hwnd) { - if (this.hwndTaskDialog == default) - return; - - fixed (void* pszText = this.currentState switch + fixed (void* pszText = this.CurrentState switch { State.LoadingDalamud => Loc.Localize( "LoadingDialogMainInstructionLoadingDalamud", @@ -156,18 +139,15 @@ internal sealed unsafe class LoadingDialog }) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION, (LPARAM)pszText); } } - private void UpdateContentText() + private unsafe void UpdateContentText(HWND hwnd) { - if (this.hwndTaskDialog == default) - return; - var contentBuilder = new StringBuilder( Loc.Localize( "LoadingDialogContentInfo", @@ -213,14 +193,14 @@ internal sealed unsafe class LoadingDialog fixed (void* pszText = contentBuilder.ToString()) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_CONTENT, (LPARAM)pszText); } } - private void UpdateExpandedInformation() + private unsafe void UpdateExpandedInformation(HWND hwnd) { const int maxCharactersPerLine = 80; @@ -261,57 +241,51 @@ internal sealed unsafe class LoadingDialog fixed (void* pszText = sb.ToString()) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION, (LPARAM)pszText); } } - private void UpdateButtonEnabled() - { - if (this.hwndTaskDialog == default) - return; - - SendMessageW(this.hwndTaskDialog, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.canHide ? 1 : 0); - } + private void UpdateButtonEnabled(HWND hwnd) => + SendMessageW(hwnd, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.CanHide ? 1 : 0); private HRESULT TaskDialogCallback(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { switch ((TASKDIALOG_NOTIFICATIONS)msg) { case TASKDIALOG_NOTIFICATIONS.TDN_CREATED: - this.hwndTaskDialog = hwnd; + if (!this.hwndTaskDialog.TrySetResult(hwnd)) + return E.E_FAIL; - this.UpdateMainInstructionText(); - this.UpdateContentText(); - this.UpdateExpandedInformation(); - this.UpdateButtonEnabled(); + this.UpdateMainInstructionText(hwnd); + this.UpdateContentText(hwnd); + this.UpdateExpandedInformation(hwnd); + this.UpdateButtonEnabled(hwnd); SendMessageW(hwnd, (int)TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0); // Bring to front + ShowWindow(hwnd, SW.SW_SHOW); SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE); SetWindowPos(hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE); - ShowWindow(hwnd, SW.SW_SHOW); SetForegroundWindow(hwnd); SetFocus(hwnd); SetActiveWindow(hwnd); return S.S_OK; - case TASKDIALOG_NOTIFICATIONS.TDN_DESTROYED: - this.hwndTaskDialog = default; - return S.S_OK; - case TASKDIALOG_NOTIFICATIONS.TDN_TIMER: - this.UpdateContentText(); - this.UpdateExpandedInformation(); + this.UpdateMainInstructionText(hwnd); + this.UpdateContentText(hwnd); + this.UpdateExpandedInformation(hwnd); + this.UpdateButtonEnabled(hwnd); return S.S_OK; } return S.S_OK; } - private void ThreadStart() + private unsafe void ThreadStart() { // We don't have access to the asset service here. var workingDirectory = Service.Get().StartInfo.WorkingDirectory; @@ -386,7 +360,7 @@ internal sealed unsafe class LoadingDialog gch = GCHandle.Alloc((Func)this.TaskDialogCallback); taskDialogConfig.lpCallbackData = GCHandle.ToIntPtr(gch); - TaskDialogIndirect(&taskDialogConfig, null, null, null).ThrowOnError(); + TaskDialogIndirect(&taskDialogConfig, null, null, null); } catch (Exception e) { diff --git a/Dalamud/Service/ServiceManager.cs b/Dalamud/Service/ServiceManager.cs index 29016bc69..206b24736 100644 --- a/Dalamud/Service/ServiceManager.cs +++ b/Dalamud/Service/ServiceManager.cs @@ -280,11 +280,8 @@ internal static class ServiceManager Log.Error(e, "Failed resolving blocking services"); } - finally - { - loadingDialog.HideAndJoin(); - } + await loadingDialog.HideAndJoin(); return; async Task WaitWithTimeoutConsent(IEnumerable tasksEnumerable, LoadingDialog.State state) @@ -414,13 +411,14 @@ internal static class ServiceManager try { BlockingServicesLoadedTaskCompletionSource.SetException(e); - loadingDialog.HideAndJoin(); } catch (Exception) { // don't care, as this means task result/exception has already been set } + await loadingDialog.HideAndJoin(); + while (tasks.Any()) { await Task.WhenAny(tasks); diff --git a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs index 9181f1a5d..4fe72240b 100644 --- a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs +++ b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs @@ -1,17 +1,37 @@ -using Dalamud.Utility; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Dalamud.Utility; namespace Dalamud.Storage.Assets; -/// -/// Extension methods for . -/// +/// Extension methods for . public static class DalamudAssetExtensions { - /// - /// Gets the purpose. - /// + private static readonly DalamudAssetAttribute EmptyAttribute = new(DalamudAssetPurpose.Empty, null, false); + private static readonly DalamudAssetAttribute[] AttributeCache = CreateCache(); + + /// Gets the purpose. /// The asset. /// The purpose. - public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) => - asset.GetAttribute()?.Purpose ?? DalamudAssetPurpose.Empty; + public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) => asset.GetAssetAttribute().Purpose; + + /// Gets the attribute. + /// The asset. + /// The attribute. + internal static DalamudAssetAttribute GetAssetAttribute(this DalamudAsset asset) => + (int)asset < 0 || (int)asset >= AttributeCache.Length + ? EmptyAttribute + : Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(AttributeCache), (int)asset); + + private static DalamudAssetAttribute[] CreateCache() + { + var assets = Enum.GetValues(); + var table = new DalamudAssetAttribute[assets.Max(x => (int)x) + 1]; + table.AsSpan().Fill(EmptyAttribute); + foreach (var asset in assets) + table[(int)asset] = asset.GetAttribute() ?? EmptyAttribute; + return table; + } } diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index 0109339fe..6fe26b90b 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps.Internal; @@ -36,10 +37,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud private const int DownloadAttemptCount = 10; private const int RenameAttemptCount = 10; - private readonly object syncRoot = new(); private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); - private readonly Dictionary?> fileStreams; - private readonly Dictionary?> textureWraps; + private readonly Task?[] fileStreams; + private readonly Task?[] textureWraps; private readonly Dalamud dalamud; private readonly HappyHttpClient httpClient; private readonly string localSourceDirectory; @@ -59,18 +59,18 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud Directory.CreateDirectory(this.localSourceDirectory); this.scopedFinalizer.Add(this.cancellationTokenSource = new()); - this.fileStreams = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); - this.textureWraps = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); + var numDalamudAssetSlots = Enum.GetValues().Max(x => (int)x) + 1; + this.fileStreams = new Task?[numDalamudAssetSlots]; + this.textureWraps = new Task?[numDalamudAssetSlots]; // Block until all the required assets to be ready. var loadTimings = Timings.Start("DAM LoadAll"); registerStartupBlocker( Task.WhenAll( Enum.GetValues() - .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAttribute()?.Required is true) + .Where(static x => x.GetAssetAttribute() is { Required: true, Data: null }) .Select(this.CreateStreamAsync) - .Select(x => x.ToContentDisposedTask())) + .Select(static x => x.ToContentDisposedTask())) .ContinueWith( r => { @@ -80,13 +80,13 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud .Unwrap(), "Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available."); + // Begin preloading optional(non-required) assets. Task.WhenAll( Enum.GetValues() - .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAttribute()?.Required is false) + .Where(static x => x.GetAssetAttribute() is { Required: false, Data: null }) .Select(this.CreateStreamAsync) - .Select(x => x.ToContentDisposedTask(true))) - .ContinueWith(r => Log.Verbose($"Optional assets load state: {r}")); + .Select(static x => x.ToContentDisposedTask(true))) + .ContinueWith(static r => Log.Verbose($"Optional assets load state: {r}")); } /// @@ -98,77 +98,97 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud /// void IInternalDisposableService.DisposeService() { - lock (this.syncRoot) - { - if (this.isDisposed) - return; + if (this.isDisposed) + return; - this.isDisposed = true; - } + this.isDisposed = true; this.cancellationTokenSource.Cancel(); Task.WaitAll( Array.Empty() - .Concat(this.fileStreams.Values) - .Concat(this.textureWraps.Values) - .Where(x => x is not null) - .Select(x => x.ContinueWith(r => { _ = r.Exception; })) - .ToArray()); + .Concat(this.fileStreams) + .Concat(this.textureWraps) + .Where(static x => x is not null) + .Select(static x => x.ContinueWith(static r => _ = r.Exception)) + .ToArray()); this.scopedFinalizer.Dispose(); } /// [Pure] public bool IsStreamImmediatelyAvailable(DalamudAsset asset) => - asset.GetAttribute()?.Data is not null - || this.fileStreams[asset]?.IsCompletedSuccessfully is true; + asset.GetAssetAttribute().Data is not null + || this.fileStreams[(int)asset]?.IsCompletedSuccessfully is true; /// [Pure] - public Stream CreateStream(DalamudAsset asset) - { - var s = this.CreateStreamAsync(asset); - s.Wait(); - if (s.IsCompletedSuccessfully) - return s.Result; - if (s.Exception is not null) - throw new AggregateException(s.Exception.InnerExceptions); - throw new OperationCanceledException(); - } + public Stream CreateStream(DalamudAsset asset) => this.CreateStreamAsync(asset).Result; /// [Pure] public Task CreateStreamAsync(DalamudAsset asset) { - if (asset.GetAttribute() is { Data: { } rawData }) - return Task.FromResult(new MemoryStream(rawData, false)); + ObjectDisposedException.ThrowIf(this.isDisposed, this); - Task task; - lock (this.syncRoot) + var attribute = asset.GetAssetAttribute(); + + // The corresponding asset does not exist. + if (attribute.Purpose is DalamudAssetPurpose.Empty) + return Task.FromException(new ArgumentOutOfRangeException(nameof(asset), asset, null)); + + // Special case: raw data is specified from asset definition. + if (attribute.Data is not null) + return Task.FromResult(new MemoryStream(attribute.Data, false)); + + // Range is guaranteed to be satisfied if the asset has a purpose; get the slot for the stream task. + ref var streamTaskRef = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(this.fileStreams), (int)asset); + + // The stream task is already set. + if (streamTaskRef is not null) + return CloneFileStreamAsync(streamTaskRef); + + var tcs = new TaskCompletionSource(); + if (Interlocked.CompareExchange(ref streamTaskRef, tcs.Task, null) is not { } streamTask) { - if (this.isDisposed) - throw new ObjectDisposedException(nameof(DalamudAssetManager)); - - task = this.fileStreams[asset] ??= CreateInnerAsync(); + // The stream task has just been set. Actually start the operation. + // In case it did not correctly finish the task in tcs, set the task to a failed state. + // Do not pass cancellation token here; we always want to touch tcs. + Task.Run( + async () => + { + try + { + tcs.SetResult(await CreateInnerAsync(this, asset)); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, + default); + return CloneFileStreamAsync(tcs.Task); } - return this.TransformImmediate( - task, - x => (Stream)new FileStream( - x.Name, + // Discard the new task, and return the already created task. + tcs.SetCanceled(); + return CloneFileStreamAsync(streamTask); + + static async Task CloneFileStreamAsync(Task fileStreamTask) => + new FileStream( + (await fileStreamTask).Name, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, - FileOptions.Asynchronous | FileOptions.SequentialScan)); + FileOptions.Asynchronous | FileOptions.SequentialScan); - async Task CreateInnerAsync() + static async Task CreateInnerAsync(DalamudAssetManager dam, DalamudAsset asset) { string path; List exceptions = null; - foreach (var name in asset.GetAttributes().Select(x => x.FileName)) + foreach (var name in asset.GetAttributes().Select(static x => x.FileName)) { - if (!File.Exists(path = Path.Combine(this.dalamud.AssetDirectory.FullName, name))) + if (!File.Exists(path = Path.Combine(dam.dalamud.AssetDirectory.FullName, name))) continue; try @@ -177,12 +197,12 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } catch (Exception e) when (e is not OperationCanceledException) { - exceptions ??= new(); + exceptions ??= []; exceptions.Add(e); } } - if (File.Exists(path = Path.Combine(this.localSourceDirectory, asset.ToString()))) + if (File.Exists(path = Path.Combine(dam.localSourceDirectory, asset.ToString()))) { try { @@ -190,7 +210,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } catch (Exception e) when (e is not OperationCanceledException) { - exceptions ??= new(); + exceptions ??= []; exceptions.Add(e); } } @@ -211,9 +231,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud await using (var tempPathStream = File.Open(tempPath, FileMode.Create, FileAccess.Write)) { await url.DownloadAsync( - this.httpClient.SharedHttpClient, + dam.httpClient.SharedHttpClient, tempPathStream, - this.cancellationTokenSource.Token); + dam.cancellationTokenSource.Token); } for (var j = RenameAttemptCount; ; j--) @@ -232,7 +252,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud nameof(DalamudAssetManager), asset, j); - await Task.Delay(1000, this.cancellationTokenSource.Token); + await Task.Delay(1000, dam.cancellationTokenSource.Token); continue; } @@ -255,14 +275,18 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud nameof(DalamudAssetManager), asset, delay); - await Task.Delay(delay * 1000, this.cancellationTokenSource.Token); + await Task.Delay(delay * 1000, dam.cancellationTokenSource.Token); } throw new FileNotFoundException($"Failed to load the asset {asset}.", asset.ToString()); } - catch (Exception e) when (e is not OperationCanceledException) + catch (OperationCanceledException) { - exceptions ??= new(); + throw; + } + catch (Exception e) + { + exceptions ??= []; exceptions.Add(e); try { @@ -272,9 +296,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud { // don't care } - } - throw new AggregateException(exceptions); + throw new AggregateException(exceptions); + } } } @@ -296,33 +320,63 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud [Pure] public Task GetDalamudTextureWrapAsync(DalamudAsset asset) { - var purpose = asset.GetPurpose(); - if (purpose is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw) - throw new ArgumentOutOfRangeException(nameof(asset), asset, "The asset cannot be taken as a Texture2D."); + ObjectDisposedException.ThrowIf(this.isDisposed, this); - Task task; - lock (this.syncRoot) + // Check if asset is a texture asset. + if (asset.GetPurpose() is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw) { - if (this.isDisposed) - throw new ObjectDisposedException(nameof(DalamudAssetManager)); - - task = this.textureWraps[asset] ??= CreateInnerAsync(); + return Task.FromException( + new ArgumentOutOfRangeException( + nameof(asset), + asset, + "The asset does not exist or cannot be taken as a Texture2D.")); } - return task; + // Range is guaranteed to be satisfied if the asset has a purpose; get the slot for the wrap task. + ref var wrapTaskRef = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(this.textureWraps), (int)asset); - async Task CreateInnerAsync() + // The wrap task is already set. + if (wrapTaskRef is not null) + return wrapTaskRef; + + var tcs = new TaskCompletionSource(); + if (Interlocked.CompareExchange(ref wrapTaskRef, tcs.Task, null) is not { } wrapTask) + { + // The stream task has just been set. Actually start the operation. + // In case it did not correctly finish the task in tcs, set the task to a failed state. + // Do not pass cancellation token here; we always want to touch tcs. + Task.Run( + async () => + { + try + { + tcs.SetResult(await CreateInnerAsync(this, asset)); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, + default); + return tcs.Task; + } + + // Discard the new task, and return the already created task. + tcs.SetCanceled(); + return wrapTask; + + static async Task CreateInnerAsync(DalamudAssetManager dam, DalamudAsset asset) { var buf = Array.Empty(); try { var tm = await Service.GetAsync(); - await using var stream = await this.CreateStreamAsync(asset); + await using var stream = await dam.CreateStreamAsync(asset); var length = checked((int)stream.Length); buf = ArrayPool.Shared.Rent(length); stream.ReadExactly(buf, 0, length); var name = $"{nameof(DalamudAsset)}[{Enum.GetName(asset)}]"; - var image = purpose switch + var texture = asset.GetPurpose() switch { DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf, name), DalamudAssetPurpose.TextureFromRaw => @@ -330,17 +384,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud ? await tm.CreateFromRawAsync(raw.Specification, buf, name) : throw new InvalidOperationException( "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), - _ => null, + _ => throw new InvalidOperationException(), // cannot happen }; - var disposeDeferred = - this.scopedFinalizer.Add(image) - ?? throw new InvalidOperationException("Something went wrong very badly"); - return new DisposeSuppressingTextureWrap(disposeDeferred); - } - catch (Exception e) - { - Log.Error(e, "[{name}] Failed to load {asset}.", nameof(DalamudAssetManager), asset); - throw; + return new DisposeSuppressingTextureWrap(dam.scopedFinalizer.Add(texture)); } finally { @@ -348,13 +394,4 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } } } - - private Task TransformImmediate(Task task, Func transformer) - { - if (task.IsCompletedSuccessfully) - return Task.FromResult(transformer(task.Result)); - if (task.Exception is { } exc) - return Task.FromException(exc); - return task.ContinueWith(_ => this.TransformImmediate(task, transformer)).Unwrap(); - } } diff --git a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs index b059cb3d6..e6c7bd920 100644 --- a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs +++ b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs @@ -6,7 +6,7 @@ namespace Dalamud.Storage.Assets; public enum DalamudAssetPurpose { /// - /// The asset has no purpose. + /// The asset has no purpose, and is not valid and/or not accessible. /// Empty = 0, diff --git a/Dalamud/Storage/ReliableFileStorage.cs b/Dalamud/Storage/ReliableFileStorage.cs index b78d16cc7..9b87a71a0 100644 --- a/Dalamud/Storage/ReliableFileStorage.cs +++ b/Dalamud/Storage/ReliableFileStorage.cs @@ -29,7 +29,7 @@ internal class ReliableFileStorage : IInternalDisposableService private readonly object syncRoot = new(); private SQLiteConnection? db; - + /// /// Initializes a new instance of the class. /// @@ -37,7 +37,7 @@ internal class ReliableFileStorage : IInternalDisposableService public ReliableFileStorage(string vfsDbPath) { var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); - + Log.Verbose("Initializing VFS database at {Path}", databasePath); try @@ -52,7 +52,7 @@ internal class ReliableFileStorage : IInternalDisposableService { if (File.Exists(databasePath)) File.Delete(databasePath); - + this.SetupDb(databasePath); } catch (Exception) @@ -79,13 +79,13 @@ internal class ReliableFileStorage : IInternalDisposableService if (this.db == null) return false; - + // If the file doesn't actually exist on the FS, but it does in the DB, we can say YES and read operations will read from the DB instead var normalizedPath = NormalizePath(path); var file = this.db.Table().FirstOrDefault(f => f.Path == normalizedPath && f.ContainerId == containerId); return file != null; } - + /// /// Write all text to a file. /// @@ -94,7 +94,7 @@ internal class ReliableFileStorage : IInternalDisposableService /// Container to write to. public void WriteAllText(string path, string? contents, Guid containerId = default) => this.WriteAllText(path, contents, Encoding.UTF8, containerId); - + /// /// Write all text to a file. /// @@ -107,7 +107,7 @@ internal class ReliableFileStorage : IInternalDisposableService var bytes = encoding.GetBytes(contents ?? string.Empty); this.WriteAllBytes(path, bytes, containerId); } - + /// /// Write all bytes to a file. /// @@ -122,10 +122,10 @@ internal class ReliableFileStorage : IInternalDisposableService { if (this.db == null) { - Util.WriteAllBytesSafe(path, bytes); + FilesystemUtil.WriteAllBytesSafe(path, bytes); return; } - + this.db.RunInTransaction(() => { var normalizedPath = NormalizePath(path); @@ -145,8 +145,8 @@ internal class ReliableFileStorage : IInternalDisposableService file.Data = bytes; this.db.Update(file); } - - Util.WriteAllBytesSafe(path, bytes); + + FilesystemUtil.WriteAllBytesSafe(path, bytes); }); } } @@ -180,7 +180,7 @@ internal class ReliableFileStorage : IInternalDisposableService var bytes = this.ReadAllBytes(path, forceBackup, containerId); return encoding.GetString(bytes); } - + /// /// Read all text from a file, and automatically try again with the backup if the file does not exist or /// the function throws an exception. If the backup read also throws an exception, @@ -208,11 +208,11 @@ internal class ReliableFileStorage : IInternalDisposableService public void ReadAllText(string path, Encoding encoding, Action reader, Guid containerId = default) { ArgumentException.ThrowIfNullOrEmpty(path); - + // TODO: We are technically reading one time too many here, if the file does not exist on the FS, ReadAllText // fails over to the backup, and then the backup fails to read in the lambda. We should do something about that, // but it's not a big deal. Would be nice if ReadAllText could indicate if it did fail over. - + // 1.) Try without using the backup try { @@ -229,7 +229,7 @@ internal class ReliableFileStorage : IInternalDisposableService { Log.Verbose(ex, "First chance read from {Path} failed, trying backup", path); } - + // 2.) Try using the backup try { @@ -256,13 +256,13 @@ internal class ReliableFileStorage : IInternalDisposableService public byte[] ReadAllBytes(string path, bool forceBackup = false, Guid containerId = default) { ArgumentException.ThrowIfNullOrEmpty(path); - + if (forceBackup) { // If the db failed to load, act as if the file does not exist if (this.db == null) throw new FileNotFoundException("Backup database was not available"); - + var normalizedPath = NormalizePath(path); var file = this.db.Table().FirstOrDefault(f => f.Path == normalizedPath && f.ContainerId == containerId); if (file == null) @@ -274,7 +274,7 @@ internal class ReliableFileStorage : IInternalDisposableService // If the file doesn't exist, immediately check the backup db if (!File.Exists(path)) return this.ReadAllBytes(path, true, containerId); - + try { return File.ReadAllBytes(path); @@ -302,10 +302,10 @@ internal class ReliableFileStorage : IInternalDisposableService // Replace users folder var usersFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); path = path.Replace(usersFolder, "%USERPROFILE%"); - + return path; } - + private void SetupDb(string path) { this.db = new SQLiteConnection(path, @@ -320,9 +320,9 @@ internal class ReliableFileStorage : IInternalDisposableService [PrimaryKey] [AutoIncrement] public int Id { get; set; } - + public Guid ContainerId { get; set; } - + public string Path { get; set; } = null!; public byte[] Data { get; set; } = null!; diff --git a/Dalamud/Utility/ActionKindExtensions.cs b/Dalamud/Utility/ActionKindExtensions.cs new file mode 100644 index 000000000..21026bc31 --- /dev/null +++ b/Dalamud/Utility/ActionKindExtensions.cs @@ -0,0 +1,26 @@ +using Dalamud.Game; + +namespace Dalamud.Utility; + +/// +/// Extension methods for the enum. +/// +public static class ActionKindExtensions +{ + /// + /// Converts the id of an ActionKind to the id used in the ActStr sheet redirect. + /// + /// The ActionKind this id is for. + /// The id. + /// An id that can be used in the ActStr sheet redirect. + public static uint GetActStrId(this ActionKind actionKind, uint id) + { + // See "83 F9 0D 76 0B" + var idx = (uint)actionKind; + + if (idx is <= 13 or 19 or 20) + return id + (1000000 * idx); + + return 0; + } +} diff --git a/Dalamud/Utility/Api10ToDoAttribute.cs b/Dalamud/Utility/Api10ToDoAttribute.cs deleted file mode 100644 index a13aaead5..000000000 --- a/Dalamud/Utility/Api10ToDoAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Dalamud.Utility; - -/// -/// Utility class for marking something to be changed for API 10, for ease of lookup. -/// -[AttributeUsage(AttributeTargets.All, Inherited = false)] -internal sealed class Api10ToDoAttribute : Attribute -{ - /// - /// Marks that this exists purely for making API 9 plugins work. - /// - public const string DeleteCompatBehavior = "Delete. This is for making API 9 plugins work."; - - /// - /// Marks that this should be moved to an another namespace. - /// - public const string MoveNamespace = "Move to another namespace."; - - /// - /// Initializes a new instance of the class. - /// - /// The explanation. - /// The explanation 2. - public Api10ToDoAttribute(string what, string what2 = "") - { - _ = what; - _ = what2; - } -} diff --git a/Dalamud/Utility/Api12ToDoAttribute.cs b/Dalamud/Utility/Api12ToDoAttribute.cs new file mode 100644 index 000000000..9f871274d --- /dev/null +++ b/Dalamud/Utility/Api12ToDoAttribute.cs @@ -0,0 +1,24 @@ +namespace Dalamud.Utility; + +/// +/// Utility class for marking something to be changed for API 11, for ease of lookup. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false)] +internal sealed class Api12ToDoAttribute : Attribute +{ + /// + /// Marks that this should be made internal. + /// + public const string MakeInternal = "Make internal."; + + /// + /// Initializes a new instance of the class. + /// + /// The explanation. + /// The explanation 2. + public Api12ToDoAttribute(string what, string what2 = "") + { + _ = what; + _ = what2; + } +} diff --git a/Dalamud/Utility/Api13ToDoAttribute.cs b/Dalamud/Utility/Api13ToDoAttribute.cs new file mode 100644 index 000000000..576401cda --- /dev/null +++ b/Dalamud/Utility/Api13ToDoAttribute.cs @@ -0,0 +1,24 @@ +namespace Dalamud.Utility; + +/// +/// Utility class for marking something to be changed for API 13, for ease of lookup. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false)] +internal sealed class Api13ToDoAttribute : Attribute +{ + /// + /// Marks that this should be made internal. + /// + public const string MakeInternal = "Make internal."; + + /// + /// Initializes a new instance of the class. + /// + /// The explanation. + /// The explanation 2. + public Api13ToDoAttribute(string what, string what2 = "") + { + _ = what; + _ = what2; + } +} diff --git a/Dalamud/Utility/CStringExtensions.cs b/Dalamud/Utility/CStringExtensions.cs new file mode 100644 index 000000000..83ebb186f --- /dev/null +++ b/Dalamud/Utility/CStringExtensions.cs @@ -0,0 +1,61 @@ +using Dalamud.Game.Text.SeStringHandling; + +using InteropGenerator.Runtime; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Utility; + +/// +/// A set of helpful utilities for working with s from ClientStructs. +/// +/// +/// WARNING: Will break if a custom ClientStructs is used. These are here for CONVENIENCE ONLY!. +/// +public static class CStringExtensions +{ + /// + /// Convert a CStringPointer to a ReadOnlySeStringSpan. + /// + /// The pointer to convert. + /// A span. + public static ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) + { + return ptr.AsSpan(); + } + + /// + /// Convert a CStringPointer to a Dalamud SeString. + /// + /// The pointer to convert. + /// A Dalamud-flavored SeString. + public static SeString AsDalamudSeString(this CStringPointer ptr) + { + return ptr.AsReadOnlySeStringSpan().ToDalamudString(); + } + + /// + /// Get a new ReadOnlySeString that's a copy of the text in this CStringPointer. + /// + /// + /// This should be functionally identical to , but exists + /// for convenience in places that already expect ReadOnlySeString as a type (and where a copy is desired). + /// + /// The pointer to copy. + /// A new Lumina ReadOnlySeString. + public static ReadOnlySeString AsReadOnlySeString(this CStringPointer ptr) + { + return new ReadOnlySeString(ptr.AsSpan().ToArray()); + } + + /// + /// Extract text from this CStringPointer following 's rules. Only + /// useful for SeStrings. + /// + /// The CStringPointer to process. + /// Extracted text. + public static string ExtractText(this CStringPointer ptr) + { + return ptr.AsReadOnlySeStringSpan().ExtractText(); + } +} diff --git a/Dalamud/Utility/ClientLanguageExtensions.cs b/Dalamud/Utility/ClientLanguageExtensions.cs index 69c39c9b8..47f0a2082 100644 --- a/Dalamud/Utility/ClientLanguageExtensions.cs +++ b/Dalamud/Utility/ClientLanguageExtensions.cs @@ -23,4 +23,40 @@ public static class ClientLanguageExtensions _ => throw new ArgumentOutOfRangeException(nameof(language)), }; } + + /// + /// Gets the language code from a ClientLanguage. + /// + /// The ClientLanguage to convert. + /// The language code (ja, en, de, fr). + /// An exception that is thrown when no valid ClientLanguage was given. + public static string ToCode(this ClientLanguage value) + { + return value switch + { + ClientLanguage.Japanese => "ja", + ClientLanguage.English => "en", + ClientLanguage.German => "de", + ClientLanguage.French => "fr", + _ => throw new ArgumentOutOfRangeException(nameof(value)), + }; + } + + /// + /// Gets the ClientLanguage from a language code. + /// + /// The language code to convert (ja, en, de, fr). + /// The ClientLanguage. + /// An exception that is thrown when no valid language code was given. + public static ClientLanguage ToClientLanguage(this string value) + { + return value switch + { + "ja" => ClientLanguage.Japanese, + "en" => ClientLanguage.English, + "de" => ClientLanguage.German, + "fr" => ClientLanguage.French, + _ => throw new ArgumentOutOfRangeException(nameof(value)), + }; + } } diff --git a/Dalamud/Utility/CultureFixes.cs b/Dalamud/Utility/CultureFixes.cs new file mode 100644 index 000000000..133e79c71 --- /dev/null +++ b/Dalamud/Utility/CultureFixes.cs @@ -0,0 +1,52 @@ +using System.Globalization; + +namespace Dalamud.Utility; + +/// +/// Class containing fixes for culture-specific issues. +/// +internal static class CultureFixes +{ + /// + /// Apply all fixes. + /// + public static void Apply() + { + PatchFrenchNumberSeparator(); + } + + private static void PatchFrenchNumberSeparator() + { + // Reset formatting specifier for the "digit grouping symbol" to an empty string + // for cultures that use a narrow no-break space (U+202F). + // This glyph is not present in any game fonts and not in the range for our Noto + // so it will be rendered as a geta (=) instead. That's a hack, but it works and + // doesn't look as weird. + CultureInfo PatchCulture(CultureInfo info) + { + var newCulture = (CultureInfo)info.Clone(); + + const string invalidGroupSeparator = "\u202F"; + const string replacedGroupSeparator = " "; + + if (info.NumberFormat.NumberGroupSeparator == invalidGroupSeparator) + newCulture.NumberFormat.NumberGroupSeparator = replacedGroupSeparator; + + if (info.NumberFormat.NumberDecimalSeparator == invalidGroupSeparator) + newCulture.NumberFormat.NumberDecimalSeparator = replacedGroupSeparator; + + if (info.NumberFormat.CurrencyGroupSeparator == invalidGroupSeparator) + newCulture.NumberFormat.CurrencyGroupSeparator = replacedGroupSeparator; + + if (info.NumberFormat.CurrencyDecimalSeparator == invalidGroupSeparator) + newCulture.NumberFormat.CurrencyDecimalSeparator = replacedGroupSeparator; + + return newCulture; + } + + CultureInfo.CurrentCulture = PatchCulture(CultureInfo.CurrentCulture); + CultureInfo.CurrentUICulture = PatchCulture(CultureInfo.CurrentUICulture); + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CurrentCulture; + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CurrentUICulture; + } +} diff --git a/Dalamud/Utility/DiagnosticUtil.cs b/Dalamud/Utility/DiagnosticUtil.cs new file mode 100644 index 000000000..9c9718c4e --- /dev/null +++ b/Dalamud/Utility/DiagnosticUtil.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using System.Linq; + +namespace Dalamud.Utility; + +/// +/// A set of utilities for diagnostics. +/// +public static class DiagnosticUtil +{ + private static readonly string[] IgnoredNamespaces = [ + nameof(System), + nameof(ImGuiNET.ImGuiNative) + ]; + + /// + /// Gets a stack trace that filters out irrelevant frames. + /// + /// The source stacktrace to filter. + /// Returns a stack trace with "extra" frames removed. + public static StackTrace GetUsefulTrace(StackTrace source) + { + var frames = source.GetFrames().SkipWhile( + f => + { + var frameNs = f.GetMethod()?.DeclaringType?.Namespace; + return frameNs == null || IgnoredNamespaces.Any(i => frameNs.StartsWith(i, true, null)); + }); + + return new StackTrace(frames); + } +} diff --git a/Dalamud/Utility/FilesystemUtil.cs b/Dalamud/Utility/FilesystemUtil.cs new file mode 100644 index 000000000..3b4298b37 --- /dev/null +++ b/Dalamud/Utility/FilesystemUtil.cs @@ -0,0 +1,116 @@ +using System.ComponentModel; +using System.IO; +using System.Text; + +using Windows.Win32.Storage.FileSystem; + +namespace Dalamud.Utility; + +/// +/// Helper functions for filesystem operations. +/// +public static class FilesystemUtil +{ + /// + /// Overwrite text in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The text to write. + public static void WriteAllTextSafe(string path, string text) + { + WriteAllTextSafe(path, text, Encoding.UTF8); + } + + /// + /// Overwrite text in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The text to write. + /// Encoding to use. + public static void WriteAllTextSafe(string path, string text, Encoding encoding) + { + WriteAllBytesSafe(path, encoding.GetBytes(text)); + } + + /// + /// Overwrite data in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The data to write. + public static unsafe void WriteAllBytesSafe(string path, byte[] bytes) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + // Open the temp file + var tempPath = path + ".tmp"; + + using var tempFile = Windows.Win32.PInvoke.CreateFile( + tempPath, + (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), + FILE_SHARE_MODE.FILE_SHARE_NONE, + null, + FILE_CREATION_DISPOSITION.CREATE_ALWAYS, + FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, + null); + + if (tempFile.IsInvalid) + throw new Win32Exception(); + + // Write the data + uint bytesWritten = 0; + if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) + throw new Win32Exception(); + + if (bytesWritten != bytes.Length) + throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); + + if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) + throw new Win32Exception(); + + tempFile.Close(); + + if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) + throw new Win32Exception(); + } + + /// + /// Generates a temporary file name. + /// + /// A temporary file name that is almost guaranteed to be unique. + internal static string GetTempFileName() + { + // https://stackoverflow.com/a/50413126 + return Path.Combine(Path.GetTempPath(), "dalamud_" + Guid.NewGuid()); + } + + /// + /// Copy files recursively from one directory to another. + /// + /// The source directory. + /// The target directory. + internal static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) + { + foreach (var dir in source.GetDirectories()) + CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name)); + + foreach (var file in source.GetFiles()) + file.CopyTo(Path.Combine(target.FullName, file.Name)); + } + + /// + /// Delete and recreate a directory. + /// + /// The directory to delete and recreate. + internal static void DeleteAndRecreateDirectory(DirectoryInfo dir) + { + if (dir.Exists) + { + dir.Delete(true); + } + + dir.Create(); + } +} diff --git a/Dalamud/Utility/ItemUtil.cs b/Dalamud/Utility/ItemUtil.cs new file mode 100644 index 000000000..32160aa15 --- /dev/null +++ b/Dalamud/Utility/ItemUtil.cs @@ -0,0 +1,157 @@ +using System.Runtime.CompilerServices; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Text; +using Lumina.Excel.Sheets; +using Lumina.Text; +using Lumina.Text.ReadOnly; + +using static Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload; + +namespace Dalamud.Utility; + +/// +/// Utilities related to Items. +/// +internal static class ItemUtil +{ + private static int? eventItemRowCount; + + /// Converts raw item ID to item ID with its classification. + /// Raw item ID. + /// Item ID and its classification. + internal static (uint ItemId, ItemKind Kind) GetBaseId(uint rawItemId) + { + if (IsEventItem(rawItemId)) return (rawItemId, ItemKind.EventItem); // EventItem IDs are NOT adjusted + if (IsHighQuality(rawItemId)) return (rawItemId - 1_000_000, ItemKind.Hq); + if (IsCollectible(rawItemId)) return (rawItemId - 500_000, ItemKind.Collectible); + return (rawItemId, ItemKind.Normal); + } + + /// Converts item ID with its classification to raw item ID. + /// Item ID. + /// Item classification. + /// Raw Item ID. + internal static uint GetRawId(uint itemId, ItemKind kind) + { + return kind switch + { + ItemKind.Collectible when itemId < 500_000 => itemId + 500_000, + ItemKind.Hq when itemId < 1_000_000 => itemId + 1_000_000, + ItemKind.EventItem => itemId, // EventItem IDs are not adjusted + _ => itemId, + }; + } + + /// + /// Checks if the item id belongs to a normal item. + /// + /// The item id to check. + /// true when the item id belongs to a normal item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNormalItem(uint itemId) + { + return itemId < 500_000; + } + + /// + /// Checks if the item id belongs to a collectible item. + /// + /// The item id to check. + /// true when the item id belongs to a collectible item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsCollectible(uint itemId) + { + return itemId is >= 500_000 and < 1_000_000; + } + + /// + /// Checks if the item id belongs to a high quality item. + /// + /// The item id to check. + /// true when the item id belongs to a high quality item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsHighQuality(uint itemId) + { + return itemId is >= 1_000_000 and < 2_000_000; + } + + /// + /// Checks if the item id belongs to an event item. + /// + /// The item id to check. + /// true when the item id belongs to an event item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsEventItem(uint itemId) + { + return itemId >= 2_000_000 && itemId - 2_000_000 < (eventItemRowCount ??= Service.Get().GetExcelSheet().Count); + } + + /// + /// Gets the name of an item. + /// + /// The raw item id. + /// Whether to include the High Quality or Collectible icon. + /// An optional client language override. + /// The item name. + internal static ReadOnlySeString GetItemName(uint itemId, bool includeIcon = true, ClientLanguage? language = null) + { + var dataManager = Service.Get(); + + if (IsEventItem(itemId)) + { + return dataManager + .GetExcelSheet(language) + .TryGetRow(itemId, out var eventItem) + ? eventItem.Name + : default; + } + + var (baseId, kind) = GetBaseId(itemId); + + if (!dataManager + .GetExcelSheet(language) + .TryGetRow(baseId, out var item)) + { + return default; + } + + if (!includeIcon || kind is not (ItemKind.Hq or ItemKind.Collectible)) + return item.Name; + + var builder = SeStringBuilder.SharedPool.Get(); + + builder.Append(item.Name); + + switch (kind) + { + case ItemKind.Hq: + builder.Append($" {(char)SeIconChar.HighQuality}"); + break; + case ItemKind.Collectible: + builder.Append($" {(char)SeIconChar.Collectible}"); + break; + } + + var itemName = builder.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(builder); + return itemName; + } + + /// + /// Gets the color row id for an item name. + /// + /// The raw item Id. + /// Wheather this color is used as edge color. + /// The Color row id. + internal static uint GetItemRarityColorType(uint itemId, bool isEdgeColor = false) + { + var rarity = 1u; + + if (!IsEventItem(itemId) && Service.Get().GetExcelSheet().TryGetRow(GetBaseId(itemId).ItemId, out var item)) + rarity = item.Rarity; + + return (isEdgeColor ? 548u : 547u) + (rarity * 2u); + } +} diff --git a/Dalamud/Utility/MapUtil.cs b/Dalamud/Utility/MapUtil.cs index 221f9cb55..3e72add9c 100644 --- a/Dalamud/Utility/MapUtil.cs +++ b/Dalamud/Utility/MapUtil.cs @@ -1,11 +1,11 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Utility; @@ -149,9 +149,7 @@ public static class MapUtil if (agentMap == null || agentMap->CurrentMapId == 0) throw new InvalidOperationException("Could not determine active map - data may not be loaded yet?"); - var territoryTransient = Service.Get() - .GetExcelSheet()! - .GetRow(agentMap->CurrentTerritoryId); + var territoryTransient = LuminaUtils.CreateRef(agentMap->CurrentTerritoryId); return WorldToMap( go.Position, @@ -161,7 +159,7 @@ public static class MapUtil */ -agentMap->CurrentOffsetX, -agentMap->CurrentOffsetY, - territoryTransient?.OffsetZ ?? 0, + territoryTransient.ValueNullable?.OffsetZ ?? 0, (uint)agentMap->CurrentMapSizeFactor, correctZOffset); } diff --git a/Dalamud/Utility/ObjectKindExtensions.cs b/Dalamud/Utility/ObjectKindExtensions.cs new file mode 100644 index 000000000..5d42dc760 --- /dev/null +++ b/Dalamud/Utility/ObjectKindExtensions.cs @@ -0,0 +1,33 @@ +using Dalamud.Game.ClientState.Objects.Enums; + +namespace Dalamud.Utility; + +/// +/// Extension methods for the enum. +/// +public static class ObjectKindExtensions +{ + /// + /// Converts the id of an ObjectKind to the id used in the ObjStr sheet redirect. + /// + /// The ObjectKind this id is for. + /// The id. + /// An id that can be used in the ObjStr sheet redirect. + public static uint GetObjStrId(this ObjectKind objectKind, uint id) + { + // See "8D 41 FE 83 F8 0C 77 4D" + return objectKind switch + { + ObjectKind.BattleNpc => id < 1000000 ? id : id - 900000, + ObjectKind.EventNpc => id, + ObjectKind.Treasure or + ObjectKind.Aetheryte or + ObjectKind.GatheringPoint or + ObjectKind.Companion or + ObjectKind.Housing => id + (1000000 * (uint)objectKind) - 2000000, + ObjectKind.EventObj => id + (1000000 * (uint)objectKind) - 4000000, + ObjectKind.CardStand => id + 3000000, + _ => 0, + }; + } +} diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 7eac9160f..904375250 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,4 +1,15 @@ -using Dalamud.Game.Text.SeStringHandling; +using System.Linq; + +using InteropGenerator.Runtime; + +using Lumina.Text.Parse; + +using Lumina.Text.ReadOnly; + +using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; +using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; +using LSeString = Lumina.Text.SeString; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; namespace Dalamud.Utility; @@ -13,7 +24,49 @@ public static class SeStringExtensions /// /// The original Lumina SeString. /// The re-parsed Dalamud SeString. - public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); + public static DSeString ToDalamudString(this LSeString originalString) => DSeString.Parse(originalString.RawData); + + /// + /// Convert a Lumina ReadOnlySeString into a Dalamud SeString. + /// This conversion re-parses the string. + /// + /// The original Lumina ReadOnlySeString. + /// The re-parsed Dalamud SeString. + public static DSeString ToDalamudString(this ReadOnlySeString originalString) => DSeString.Parse(originalString.Data.Span); + + /// + /// Convert a Lumina ReadOnlySeStringSpan into a Dalamud SeString. + /// This conversion re-parses the string. + /// + /// The original Lumina ReadOnlySeStringSpan. + /// The re-parsed Dalamud SeString. + public static DSeString ToDalamudString(this ReadOnlySeStringSpan originalString) => DSeString.Parse(originalString.Data); + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-8 to compile and append to . + /// this for method chaining. + public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) + { + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; + } + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-16 to compile and append to . + /// this for method chaining. + public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) + { + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; + } /// /// Validate if character name is valid. @@ -24,8 +77,155 @@ public static class SeStringExtensions /// /// character name to validate. /// indicator if character is name is valid. - public static bool IsValidCharacterName(this SeString value) + public static bool IsValidCharacterName(this DSeString value) => value.ToString().IsValidCharacterName(); + + /// + /// Determines whether the contains only text payloads. + /// + /// The to check. + /// true if the string contains only text payloads; otherwise, false. + public static bool IsTextOnly(this ReadOnlySeString ross) { - return value.ToString().IsValidCharacterName(); + return ross.AsSpan().IsTextOnly(); + } + + /// + /// Determines whether the contains only text payloads. + /// + /// The to check. + /// true if the span contains only text payloads; otherwise, false. + public static bool IsTextOnly(this ReadOnlySeStringSpan rosss) + { + foreach (var payload in rosss) + { + if (payload.Type != ReadOnlySePayloadType.Text) + return false; + } + + return true; + } + + /// + /// Determines whether the contains the specified text. + /// + /// The to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this ReadOnlySeString ross, ReadOnlySpan needle) + { + return ross.AsSpan().ContainsText(needle); + } + + /// + /// Determines whether the contains the specified text. + /// + /// The to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this ReadOnlySeStringSpan rosss, ReadOnlySpan needle) + { + foreach (var payload in rosss) + { + if (payload.Type != ReadOnlySePayloadType.Text) + continue; + + if (payload.Body.IndexOf(needle) != -1) + return true; + } + + return false; + } + + /// + /// Determines whether the contains the specified text. + /// + /// The builder to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this LSeStringBuilder builder, ReadOnlySpan needle) + { + return builder.ToReadOnlySeString().ContainsText(needle); + } + + /// + /// Replaces occurrences of a specified text in a with another text. + /// + /// The original string. + /// The text to find. + /// The replacement text. + /// A new with the replacements made. + public static ReadOnlySeString ReplaceText( + this ReadOnlySeString ross, + ReadOnlySpan toFind, + ReadOnlySpan replacement) + { + if (ross.IsEmpty) + return ross; + + var sb = LSeStringBuilder.SharedPool.Get(); + + foreach (var payload in ross) + { + if (payload.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (payload.Type != ReadOnlySePayloadType.Text) + { + sb.Append(payload); + continue; + } + + var index = payload.Body.Span.IndexOf(toFind); + if (index == -1) + { + sb.Append(payload); + continue; + } + + var lastIndex = 0; + while (index != -1) + { + sb.Append(payload.Body.Span[lastIndex..index]); + + if (!replacement.IsEmpty) + { + sb.Append(replacement); + } + + lastIndex = index + toFind.Length; + index = payload.Body.Span[lastIndex..].IndexOf(toFind); + + if (index != -1) + index += lastIndex; + } + + sb.Append(payload.Body.Span[lastIndex..]); + } + + var output = sb.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(sb); + return output; + } + + /// + /// Replaces occurrences of a specified text in an with another text. + /// + /// The builder to modify. + /// The text to find. + /// The replacement text. + public static void ReplaceText( + this LSeStringBuilder builder, + ReadOnlySpan toFind, + ReadOnlySpan replacement) + { + if (toFind.IsEmpty) + return; + + var str = builder.ToReadOnlySeString(); + if (str.IsEmpty) + return; + + var replaced = ReplaceText(new ReadOnlySeString(builder.GetViewAsMemory()), toFind, replacement); + builder.Clear().Append(replaced); } } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index 02dfdafbf..50973e338 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; +using System.Globalization; + +using FFXIVClientStructs.FFXIV.Client.UI; namespace Dalamud.Utility; @@ -40,7 +41,51 @@ public static class StringExtensions public static bool IsValidCharacterName(this string value, bool includeLegacy = true) { if (string.IsNullOrEmpty(value)) return false; - if (!FFXIVClientStructs.FFXIV.Client.UI.UIModule.IsPlayerCharacterName(value)) return false; + if (!UIGlobals.IsValidPlayerCharacterName(value)) return false; return includeLegacy || value.Length <= 21; } + + /// + /// Converts the first character of the string to uppercase while leaving the rest of the string unchanged. + /// + /// The input string. + /// + /// A new string with the first character converted to uppercase. + [return: NotNullIfNotNull("input")] + public static string? FirstCharToUpper(this string? input, CultureInfo? culture = null) => + string.IsNullOrWhiteSpace(input) + ? input + : $"{char.ToUpper(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}"; + + /// + /// Converts the first character of the string to lowercase while leaving the rest of the string unchanged. + /// + /// The input string. + /// + /// A new string with the first character converted to lowercase. + [return: NotNullIfNotNull("input")] + public static string? FirstCharToLower(this string? input, CultureInfo? culture = null) => + string.IsNullOrWhiteSpace(input) + ? input + : $"{char.ToLower(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}"; + + /// + /// Removes soft hyphen characters (U+00AD) from the input string. + /// + /// The input string to remove soft hyphen characters from. + /// A string with all soft hyphens removed. + public static string StripSoftHyphen(this string input) => input.Replace("\u00AD", string.Empty); + + /// + /// Truncates the given string to the specified maximum number of characters, + /// appending an ellipsis if truncation occurs. + /// + /// The string to truncate. + /// The maximum allowed length of the string. + /// The string to append if truncation occurs (defaults to "..."). + /// The truncated string, or the original string if no truncation is needed. + public static string? Truncate(this string input, int maxChars, string ellipses = "...") + { + return string.IsNullOrEmpty(input) || input.Length <= maxChars ? input : input[..maxChars] + ellipses; + } } diff --git a/Dalamud/Utility/TaskExtensions.cs b/Dalamud/Utility/TaskExtensions.cs index a65956325..f03a9e9e9 100644 --- a/Dalamud/Utility/TaskExtensions.cs +++ b/Dalamud/Utility/TaskExtensions.cs @@ -63,6 +63,35 @@ public static class TaskExtensions #pragma warning restore RS0030 } + /// Creates a new that resolves when completes, ignoring + /// exceptions thrown from the task, if any. + /// Task to await and ignore exceptions on failure. + /// A that completes successfully when completes in any state. + /// + /// Awaiting the returned will always complete without exceptions, but awaiting + /// will throw exceptions if it fails, even after this function is called. + /// + /// + /// Wrong use of this function + /// + /// var task = TaskThrowingException(); + /// task.SuppressException(); + /// await TaskThrowingException(); // This line will throw. + /// + /// + /// + /// Correct use of this function, if waiting for the task + /// await TaskThrowingException().SuppressException(); + /// + /// + /// Fire-and-forget
+ /// If not interested in the execution state of Task (fire-and-forget), simply calling this function will do. + /// This function consumes the task's exception, so that it won't bubble up on later garbage collection. + /// TaskThrowingException().SuppressException(); + ///
+ ///
+ public static Task SuppressException(this Task task) => task.ContinueWith(static r => r.Exception); + private static bool IsWaitingValid(Task task) { // In the case the task has been started with the LongRunning flag, it will not be in the TPL thread pool and we can allow waiting regardless. diff --git a/Dalamud/Utility/ThreadSafety.cs b/Dalamud/Utility/ThreadSafety.cs index ea8238d44..c31cc0005 100644 --- a/Dalamud/Utility/ThreadSafety.cs +++ b/Dalamud/Utility/ThreadSafety.cs @@ -18,13 +18,14 @@ public static class ThreadSafety /// /// Throws an exception when the current thread is not the main thread. /// + /// The message to be passed into the exception, if one is to be thrown. /// Thrown when the current thread is not the main thread. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AssertMainThread() + public static void AssertMainThread(string? message = null) { if (!threadStaticIsMainThread) { - throw new InvalidOperationException("Not on main thread!"); + throw new InvalidOperationException(message ?? "Not on main thread!"); } } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 7d7bb1380..966fa1e11 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -12,16 +12,16 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Dalamud.Support; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; using TerraFX.Interop.Windows; using Windows.Win32.Storage.FileSystem; @@ -64,7 +64,6 @@ public static class Util private static readonly Type GenericSpanType = typeof(Span<>); private static string? scmVersionInternal; private static string? gitHashInternal; - private static int? gitCommitCountInternal; private static string? gitHashClientStructsInternal; private static ulong moduleStartAddr; @@ -76,58 +75,6 @@ public static class Util public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - /// - /// Check two byte arrays for equality. - /// - /// The first byte array. - /// The second byte array. - /// Whether or not the byte arrays are equal. - public static unsafe bool FastByteArrayCompare(byte[]? a1, byte[]? a2) - { - // Copyright (c) 2008-2013 Hafthor Stefansson - // Distributed under the MIT/X11 software license - // Ref: http://www.opensource.org/licenses/mit-license.php. - // https://stackoverflow.com/a/8808245 - - if (a1 == a2) return true; - if (a1 == null || a2 == null || a1.Length != a2.Length) - return false; - fixed (byte* p1 = a1, p2 = a2) - { - byte* x1 = p1, x2 = p2; - var l = a1.Length; - for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) - { - if (*((long*)x1) != *((long*)x2)) - return false; - } - - if ((l & 4) != 0) - { - if (*((int*)x1) != *((int*)x2)) - return false; - x1 += 4; - x2 += 4; - } - - if ((l & 2) != 0) - { - if (*((short*)x1) != *((short*)x2)) - return false; - x1 += 2; - x2 += 2; - } - - if ((l & 1) != 0) - { - if (*((byte*)x1) != *((byte*)x2)) - return false; - } - - return true; - } - } - /// /// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return /// the git describe output for this build, which will be a raw version if this is a stable build or an @@ -137,11 +84,11 @@ public static class Util public static string GetScmVersion() { if (scmVersionInternal != null) return scmVersionInternal; - + var asm = typeof(Util).Assembly; var attrs = asm.GetCustomAttributes(); - return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value + return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value ?? asm.GetName().Version!.ToString(); } @@ -158,33 +105,11 @@ public static class Util var asm = typeof(Util).Assembly; var attrs = asm.GetCustomAttributes(); - return gitHashInternal = attrs.First(a => a.Key == "GitHash").Value; + return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A"; } /// - /// Gets the amount of commits in the current branch, or null if undetermined. - /// - /// The amount of commits in the current branch. - [Obsolete($"Planned for removal in API 11. Use {nameof(GetScmVersion)} for version tracking.")] - public static int? GetGitCommitCount() - { - if (gitCommitCountInternal != null) - return gitCommitCountInternal.Value; - - var asm = typeof(Util).Assembly; - var attrs = asm.GetCustomAttributes(); - - var value = attrs.First(a => a.Key == "GitCommitCount").Value; - if (value == null) - return null; - - gitCommitCountInternal = int.Parse(value); - return gitCommitCountInternal.Value; - } - - /// - /// Gets the git hash value from the assembly - /// or null if it cannot be found. + /// Gets the git hash value from the assembly or null if it cannot be found. /// /// The git hash of the assembly. public static string? GetGitHashClientStructs() @@ -574,55 +499,14 @@ public static class Util /// Determine if Dalamud is currently running within a Wine context (e.g. either on macOS or Linux). This method /// will not return information about the host operating system. /// - /// Returns true if Wine is detected, false otherwise. - public static bool IsWine() - { - if (EnvironmentConfiguration.XlWineOnLinux) return true; - if (Environment.GetEnvironmentVariable("XL_PLATFORM") is not null and not "Windows") return true; - - var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll"); - - // Test to see if any Wine specific exports exist. If they do, then we are running on Wine. - // The exports "wine_get_version", "wine_get_build_id", and "wine_get_host_version" will tend to be hidden - // by most Linux users (else FFXIV will want a macOS license), so we will additionally check some lesser-known - // exports as well. - return AnyProcExists( - ntdll, - "wine_get_version", - "wine_get_build_id", - "wine_get_host_version", - "wine_server_call", - "wine_unix_to_nt_file_name"); - - bool AnyProcExists(nint handle, params string[] procs) => - procs.Any(p => NativeFunctions.GetProcAddress(handle, p) != nint.Zero); - } + /// Returns true if running on Wine, false otherwise. + public static bool IsWine() => Service.Get().StartInfo.Platform != OSPlatform.Windows; /// - /// Gets the best guess for the current host's platform based on the XL_PLATFORM environment variable or - /// heuristics. + /// Gets the current host's platform based on the injector launch arguments or heuristics. /// - /// - /// macOS users running without XL_PLATFORM being set will be reported as Linux users. Due to the way our - /// Wines work, there isn't a great (consistent) way to split the two apart if we're not told. - /// /// Returns the that Dalamud is currently running on. - public static OSPlatform GetHostPlatform() - { - switch (Environment.GetEnvironmentVariable("XL_PLATFORM")) - { - case "Windows": return OSPlatform.Windows; - case "MacOS": return OSPlatform.OSX; - case "Linux": return OSPlatform.Linux; - } - - // n.b. we had some fancy code here to check if the Wine host version returned "Darwin" but apparently - // *all* our Wines report Darwin if exports aren't hidden. As such, it is effectively impossible (without some - // (very cursed and inaccurate heuristics) to determine if we're on macOS or Linux unless we're explicitly told - // by our launcher. See commit a7aacb15e4603a367e2f980578271a9a639d8852 for the old check. - - return IsWine() ? OSPlatform.Linux : OSPlatform.Windows; - } + public static OSPlatform GetHostPlatform() => Service.Get().StartInfo.Platform; /// /// Heuristically determine if the Windows version is higher than Windows 11's first build. @@ -720,10 +604,9 @@ public static class Util /// /// The path of the file to write to. /// The text to write. - public static void WriteAllTextSafe(string path, string text) - { - WriteAllTextSafe(path, text, Encoding.UTF8); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] + public static void WriteAllTextSafe(string path, string text) => FilesystemUtil.WriteAllTextSafe(path, text); /// /// Overwrite text in a file by first writing it to a temporary file, and then @@ -732,10 +615,9 @@ public static class Util /// The path of the file to write to. /// The text to write. /// Encoding to use. - public static void WriteAllTextSafe(string path, string text, Encoding encoding) - { - WriteAllBytesSafe(path, encoding.GetBytes(text)); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] + public static void WriteAllTextSafe(string path, string text, Encoding encoding) => FilesystemUtil.WriteAllTextSafe(path, text, encoding); /// /// Overwrite data in a file by first writing it to a temporary file, and then @@ -743,41 +625,9 @@ public static class Util /// /// The path of the file to write to. /// The data to write. - public static unsafe void WriteAllBytesSafe(string path, byte[] bytes) - { - ArgumentException.ThrowIfNullOrEmpty(path); - - // Open the temp file - var tempPath = path + ".tmp"; - - using var tempFile = Windows.Win32.PInvoke.CreateFile( - tempPath, - (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), - FILE_SHARE_MODE.FILE_SHARE_NONE, - null, - FILE_CREATION_DISPOSITION.CREATE_ALWAYS, - FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, - null); - - if (tempFile.IsInvalid) - throw new Win32Exception(); - - // Write the data - uint bytesWritten = 0; - if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) - throw new Win32Exception(); - - if (bytesWritten != bytes.Length) - throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); - - if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) - throw new Win32Exception(); - - tempFile.Close(); - - if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) - throw new Win32Exception(); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllBytesSafe()")] + public static void WriteAllBytesSafe(string path, byte[] bytes) => FilesystemUtil.WriteAllBytesSafe(path, bytes); /// Gets a temporary file name, for use as the sourceFileName in /// . @@ -785,7 +635,7 @@ public static class Util /// A temporary file name that should be usable with . /// /// No write operation is done on the filesystem. - public static string GetTempFileNameForFileReplacement(string targetFile) + public static string GetReplaceableFileName(string targetFile) { Span buf = stackalloc byte[9]; Random.Shared.NextBytes(buf); @@ -813,7 +663,7 @@ public static class Util var names = data.GetExcelSheet(ClientLanguage.English)!; var rng = new Random(); - return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; + return names.GetRowAt(rng.Next(0, names.Count - 1)).Singular.ExtractText(); } /// @@ -873,7 +723,7 @@ public static class Util // ignore } } - + /// /// Print formatted IGameObject Information to ImGui. /// @@ -891,13 +741,13 @@ public static class Util if (actor is ICharacter chara) { actorString += - $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.ValueNullable?.Name : chara.ClassJob.RowId.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; } if (actor is IPlayerCharacter pc) { actorString += - $" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + $" HomeWorld: {(resolveGameData ? pc.HomeWorld.ValueNullable?.Name : pc.HomeWorld.RowId.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.ValueNullable?.Name : pc.CurrentWorld.RowId.ToString())} FC: {pc.CompanyTag}\n"; } ImGui.TextUnformatted(actorString); @@ -925,13 +775,13 @@ public static class Util if (actor is Character chara) { actorString += - $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.ValueNullable?.Name : chara.ClassJob.RowId.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; } if (actor is PlayerCharacter pc) { actorString += - $" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + $" HomeWorld: {(resolveGameData ? pc.HomeWorld.ValueNullable?.Name : pc.HomeWorld.RowId.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.ValueNullable?.Name : pc.CurrentWorld.RowId.ToString())} FC: {pc.CompanyTag}\n"; } ImGui.TextUnformatted(actorString); @@ -1050,74 +900,72 @@ public static class Util dm.Invoke(null, new[] { obj, path, addr }); } +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowSpanPrivate(ulong addr, IList path, int offset, bool isTop, in Span spanobj) { -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type if (isTop) { fixed (void* p = spanobj) { - if (!ImGui.TreeNode( - $"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}-head")) + using var tree = ImRaii.TreeNode($"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}-head", ImGuiTreeNodeFlags.SpanFullWidth); + if (tree.Success) { - return; + ShowSpanEntryPrivate(addr, path, offset, spanobj); } } } - - try + else { - const int batchSize = 20; - if (spanobj.Length > batchSize) - { - var skip = batchSize; - while ((spanobj.Length + skip - 1) / skip > batchSize) - skip *= batchSize; - for (var i = 0; i < spanobj.Length; i += skip) - { - var next = Math.Min(i + skip, spanobj.Length); - path.Add($"{offset + i:X}_{skip}"); - if (ImGui.TreeNode( - $"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}")) - { - try - { - ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]); - } - finally - { - ImGui.TreePop(); - } - } - - path.RemoveAt(path.Count - 1); - } - } - else - { - fixed (T* p = spanobj) - { - var pointerType = typeof(T*); - for (var i = 0; i < spanobj.Length; i++) - { - ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); - ImGui.SameLine(); - path.Add($"{offset + i}"); - ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); - } - } - } + ShowSpanEntryPrivate(addr, path, offset, spanobj); } - finally - { - if (isTop) - ImGui.TreePop(); - } -#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type } + private static unsafe void ShowSpanEntryPrivate(ulong addr, IList path, int offset, Span spanobj) + { + const int batchSize = 20; + if (spanobj.Length > batchSize) + { + var skip = batchSize; + while ((spanobj.Length + skip - 1) / skip > batchSize) + { + skip *= batchSize; + } + + for (var i = 0; i < spanobj.Length; i += skip) + { + var next = Math.Min(i + skip, spanobj.Length); + path.Add($"{offset + i:X}_{skip}"); + + using (var tree = ImRaii.TreeNode($"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + if (tree.Success) + { + ShowSpanEntryPrivate(addr, path, offset + i, spanobj[i..next]); + } + } + + path.RemoveAt(path.Count - 1); + } + } + else + { + fixed (T* p = spanobj) + { + var pointerType = typeof(T*); + for (var i = 0; i < spanobj.Length; i++) + { + ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); + ImGui.SameLine(); + path.Add($"{offset + i}"); + ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); + } + } + } + } + +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowValue(ulong addr, IList path, Type type, object value, bool hideAddress) { if (type.IsPointer) @@ -1133,9 +981,10 @@ public static class Util if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) { ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); - ImGui.PopStyleColor(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xffcbc0ff)) + { + ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); + } } ImGui.SameLine(); @@ -1187,116 +1036,135 @@ public static class Util /// Do not print addresses. Use when displaying a copied value. private static void ShowStructInternal(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null, bool hideAddress = false) { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - path ??= new List(); - var pathList = path is List ? (List)path : path.ToList(); - - if (moduleEndAddr == 0 && moduleStartAddr == 0) + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2))) { - try + path ??= new List(); + var pathList = path as List ?? path.ToList(); + + if (moduleEndAddr == 0 && moduleStartAddr == 0) { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) + try { - moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); - moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); + moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + } + else + { + moduleEndAddr = 1; + } } - else + catch { moduleEndAddr = 1; } } - catch + + if (autoExpand) { - moduleEndAddr = 1; + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - } - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - } + using var col = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF); + using var tree = ImRaii.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}", ImGuiTreeNodeFlags.SpanFullWidth); + col.Pop(); - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}")) - { - ImGui.PopStyleColor(); - foreach (var f in obj.GetType() - .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + if (tree.Success) { - var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); - if (fixedBuffer != null) + foreach (var f in obj.GetType() + .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { - ImGui.Text($"fixed"); + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); + + if (fixedBuffer != null) + { + ImGui.Text("fixed"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); + } + else + { + if (offset != null) + { + ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.SameLine(); + } + + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + } + ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), - $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); - } - else - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); + ImGui.SameLine(); + + pathList.Add(f.Name); + try + { + if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + } + else if (f.FieldType == typeof(bool) && offset != null) + { + ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); + } + else + { + ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); + foreach (var p in obj.GetType().GetProperties().Where(static p => p.GetGetMethod()?.GetParameters().Length == 0)) + { + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); + ImGui.SameLine(); - pathList.Add(f.Name); - try - { - if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct - else - ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); - } - catch (Exception ex) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); + pathList.Add(p.Name); + try + { + if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) + { + ShowSpanProperty(addr, pathList, p, obj); + } + else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed properties."); + } + else + { + ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } } - - foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); - ImGui.SameLine(); - - pathList.Add(p.Name); - try - { - if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) - ShowSpanProperty(addr, pathList, p, obj); - else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed properties."); - else - ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); - } - catch (Exception ex) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); - } - } - - ImGui.TreePop(); } - else - { - ImGui.PopStyleColor(); - } - - ImGui.PopStyleVar(); } } diff --git a/Dalamud/Utility/WeakConcurrentCollection.cs b/Dalamud/Utility/WeakConcurrentCollection.cs new file mode 100644 index 000000000..a3bc04651 --- /dev/null +++ b/Dalamud/Utility/WeakConcurrentCollection.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Dalamud.Utility; + +/// +/// An implementation of a weak concurrent set based on a . +/// +/// The type of object that we're tracking. +public class WeakConcurrentCollection : ICollection where T : class +{ + private readonly ConditionalWeakTable cwt = new(); + + /// + public int Count => this.cwt.Count(); + + /// + public bool IsReadOnly => false; + + private IEnumerable Keys => this.cwt.Select(pair => pair.Key); + + /// + public IEnumerator GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + public void Add(T item) => this.cwt.AddOrUpdate(item, null); + + /// + public void Clear() => this.cwt.Clear(); + + /// + public bool Contains(T item) => this.Keys.Contains(item); + + /// + public void CopyTo(T[] array, int arrayIndex) => this.Keys.ToArray().CopyTo(array, arrayIndex); + + /// + public bool Remove(T item) => this.cwt.Remove(item); +} diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 4b1d4a6e5..62ccdd20a 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -941,7 +941,7 @@ int main() { log << std::format(L"Dump at: {}", dumpPath.wstring()) << std::endl; else log << std::format(L"Dump error: {}", dumpError) << std::endl; - log << L"System Time: " << std::chrono::system_clock::now() << std::endl; + log << std::format(L"System Time: {0:%F} {0:%T} {0:%Ez}", std::chrono::system_clock::now()) << std::endl; log << L"\n" << stackTrace << std::endl; if (pProgressDialog) diff --git a/Directory.Build.props b/Directory.Build.props index 0c5af2e37..ef07620a4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,21 @@ - + + + + net9.0-windows + x64 + x64 + 13.0 + + + + + 5.6.1 + 7.2.1 + 13.0.3 + + + diff --git a/README.md b/README.md index 8a925f1c6..bbf9ee1ce 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,18 @@ Dalamud

-Dalamud is a plugin development framework for FINAL FANTASY XIV that provides access to game data and native interoperability with the game itself to add functionality and quality-of-life. +Dalamud is a plugin development framework for FFXIV that provides access to game data and native interoperability with the game itself to add functionality and quality-of-life. -It is meant to be used in conjunction with [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher), which manages and launches Dalamud for you. __It is generally not recommended for users to try to run Dalamud manually as there are multiple dependencies and assumed folder paths.__ +It is meant to be used in conjunction with [XIVLauncher](https://github.com/goatcorp/FFXIVQuickLauncher), which manages and launches Dalamud for you. __It is generally not recommended for end users to try to run Dalamud manually as XIVLauncher manages multiple required dependencies.__ ## Hold Up! + If you are just trying to **use** Dalamud, you don't need to do anything on this page - please [download XIVLauncher](https://goatcorp.github.io/) from its official page and follow the setup instructions. +## Building and testing locally + +Please check the [docs page on building Dalamud](https://dalamud.dev/building) for more information and required dependencies. + ## Plugin development Dalamud features a growing API for in-game plugin development with game data and chat access and overlays. Please see our [Developer FAQ](https://goatcorp.github.io/faq/development) and the [API documentation](https://dalamud.dev) for more details. @@ -34,15 +39,6 @@ Dalamud can be loaded via DLL injection, or by rewriting a process' entrypoint. | *Dalamud* (C#) | Core API, game bindings, plugin framework | | *Dalamud.CorePlugin* (C#) | Testbed plugin that can access Dalamud internals, to prototype new Dalamud features | -## Branches - -We are currently working from the following branches. - -| Name | API Level | Purpose | .NET Version | Track | -|----------|-----------|------------------------------------------------------------|----------------------------|-------------------| -| *master* | **9** | Current release branch | .NET 8.0.0 (November 2023) | Release & Staging | -| *api10* | **10** | Next major version, slated for release alongside Patch 7.0 | .NET 8.0.0 (November 2023) | api10 | -
##### Final Fantasy XIV © 2010-2021 SQUARE ENIX CO., LTD. All Rights Reserved. We are not affiliated with SQUARE ENIX CO., LTD. in any way. diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index d704d54e0..d374c79f8 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Nuke.Common; @@ -5,6 +6,7 @@ using Nuke.Common.Execution; using Nuke.Common.Git; using Nuke.Common.IO; using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.MSBuild; using Serilog; @@ -47,21 +49,77 @@ public class DalamudBuild : NukeBuild AbsolutePath TestProjectDir => RootDirectory / "Dalamud.Test"; AbsolutePath TestProjectFile => TestProjectDir / "Dalamud.Test.csproj"; + AbsolutePath ExternalsDir => RootDirectory / "external"; + AbsolutePath CImGuiDir => ExternalsDir / "cimgui"; + AbsolutePath CImGuiProjectFile => CImGuiDir / "cimgui.vcxproj"; + AbsolutePath CImPlotDir => ExternalsDir / "cimplot"; + AbsolutePath CImPlotProjectFile => CImPlotDir / "cimplot.vcxproj"; + AbsolutePath CImGuizmoDir => ExternalsDir / "cimguizmo"; + AbsolutePath CImGuizmoProjectFile => CImGuizmoDir / "cimguizmo.vcxproj"; + AbsolutePath ArtifactsDirectory => RootDirectory / "bin" / Configuration; private static AbsolutePath LibraryDirectory => RootDirectory / "lib"; private static Dictionary EnvironmentVariables => new(EnvironmentInfo.Variables); + private static string ConsoleTemplate => "{Message:l}{NewLine}{Exception}"; + private static bool IsCIBuild => Environment.GetEnvironmentVariable("CI") == "true"; + Target Restore => _ => _ .Executes(() => { DotNetTasks.DotNetRestore(s => s .SetProjectFile(Solution)); }); - + + Target CompileCImGui => _ => _ + .Executes(() => + { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImGuiProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileCImPlot => _ => _ + .Executes(() => + { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImPlotProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileCImGuizmo => _ => _ + .Executes(() => + { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImGuizmoProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileImGuiNatives => _ => _ + .DependsOn(CompileCImGui) + .DependsOn(CompileCImPlot) + .DependsOn(CompileCImGuizmo); + Target CompileDalamud => _ => _ .DependsOn(Restore) + .DependsOn(CompileImGuiNatives) .Executes(() => { DotNetTasks.DotNetBuild(s => @@ -70,16 +128,20 @@ public class DalamudBuild : NukeBuild .SetProjectFile(DalamudProjectFile) .SetConfiguration(Configuration) .EnableNoRestore(); - + if (IsCIBuild) + { + s = s + .SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds + } // We need to emit compiler generated files for the docs build, since docfx can't run generators directly // TODO: This fails every build after this because of redefinitions... - if (IsDocsBuild) - { - Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); - s = s - .SetProperty("IsDocsBuild", "true"); - } + // if (IsDocsBuild) + // { + // Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); + // s = s + // .SetProperty("IsDocsBuild", "true"); + // } return s; }); }); @@ -118,12 +180,28 @@ public class DalamudBuild : NukeBuild .SetConfiguration(Configuration)); }); + Target SetCILogging => _ => _ + .DependentFor(Compile) + .OnlyWhenStatic(() => IsCIBuild) + .Executes(() => + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console(outputTemplate: ConsoleTemplate) + .CreateLogger(); + }); + Target Compile => _ => _ - .DependsOn(CompileDalamud) - .DependsOn(CompileDalamudBoot) - .DependsOn(CompileDalamudCrashHandler) - .DependsOn(CompileInjector) - .DependsOn(CompileInjectorBoot); + .DependsOn(CompileDalamud) + .DependsOn(CompileDalamudBoot) + .DependsOn(CompileDalamudCrashHandler) + .DependsOn(CompileInjector) + .DependsOn(CompileInjectorBoot) + ; + + Target CI => _ => _ + .DependsOn(Compile) + .Triggers(Test); Target Test => _ => _ .DependsOn(Compile) @@ -132,12 +210,28 @@ public class DalamudBuild : NukeBuild DotNetTasks.DotNetTest(s => s .SetProjectFile(TestProjectFile) .SetConfiguration(Configuration) + .AddProperty("WarningLevel", "0") .EnableNoRestore()); }); Target Clean => _ => _ .Executes(() => { + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImGuiProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImPlotProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImGuizmoProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + DotNetTasks.DotNetClean(s => s .SetProject(DalamudProjectFile) .SetConfiguration(Configuration)); diff --git a/build/build.csproj b/build/build.csproj index 219b668bd..37a4d3252 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -1,7 +1,6 @@  Exe - net8.0 disable IDE0002;IDE0051;IDE1006;CS0649;CS0169 @@ -12,5 +11,6 @@ + diff --git a/external/Directory.Build.props b/external/Directory.Build.props new file mode 100644 index 000000000..f719442cd --- /dev/null +++ b/external/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj new file mode 100644 index 000000000..55e737595 --- /dev/null +++ b/external/cimgui/cimgui.vcxproj @@ -0,0 +1,107 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {8430077c-f736-4246-a052-8ea1cece844e} + cimgui + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + bin\$(Configuration)\ + obj\$(Configuration)\ + + + + ..\..\lib\cimgui\imgui;..\..\lib\cimgui;%(AdditionalIncludeDirectories) + NotUsing + + + + + Level3 + true + _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + false + + + + + + + + + + diff --git a/external/cimgui/cimgui.vcxproj.filters b/external/cimgui/cimgui.vcxproj.filters new file mode 100644 index 000000000..d48c361f1 --- /dev/null +++ b/external/cimgui/cimgui.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj new file mode 100644 index 000000000..3014786b8 --- /dev/null +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -0,0 +1,113 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {F258347D-31BE-4605-98CE-40E43BDF6F9D} + cimplot + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + bin\$(Configuration)\ + obj\$(Configuration)\ + + + + + ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) + NotUsing + + + + + + Level3 + true + _DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + false + + + + + + + + + + diff --git a/external/cimguizmo/cimguizmo.vcxproj.filters b/external/cimguizmo/cimguizmo.vcxproj.filters new file mode 100644 index 000000000..f954dcc2c --- /dev/null +++ b/external/cimguizmo/cimguizmo.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj new file mode 100644 index 000000000..c2b468fab --- /dev/null +++ b/external/cimplot/cimplot.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {76caa246-c405-4a8c-b0ae-f4a0ef3d4e16} + cimplot + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + bin\$(Configuration)\ + obj\$(Configuration)\ + + + + + ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) + NotUsing + + + + + + Level3 + true + _DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + false + + + + + + + + + + diff --git a/external/cimplot/cimplot.vcxproj.filters b/external/cimplot/cimplot.vcxproj.filters new file mode 100644 index 000000000..ad8bfd11b --- /dev/null +++ b/external/cimplot/cimplot.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/global.json b/global.json index 7e8286fbe..ab1a4a2ec 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "9.0.0", "rollForward": "latestMinor", "allowPrerelease": true } diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index 54276aad1..50ddecb89 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "CoreCLR.h" #include "..\..\Dalamud.Boot\logging.h" @@ -27,6 +28,64 @@ void ConsoleTeardown() std::optional g_clr; +static wchar_t* GetRuntimePath() +{ + int result; + std::wstring buffer; + wchar_t* runtime_path; + wchar_t* _appdata; + DWORD username_len = UNLEN + 1; + wchar_t username[UNLEN + 1]; + + buffer.resize(0); + result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0); + + if (result) + { + buffer.resize(result); // The first pass returns the required length + result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); + return _wcsdup(buffer.c_str()); + } + + // Detect Windows first + result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata); + + if (result != 0) + { + logging::E("Unable to get RoamingAppData path (err={})", result); + return NULL; + } + + std::filesystem::path fs_app_data(_appdata); + runtime_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + // Next XLCore on Linux + result = GetUserNameW(username, &username_len); + if (result != 0) + { + logging::E("Unable to get user name (err={})", result); + return NULL; + } + + std::filesystem::path homeDir = L"Z:\\home\\" + std::wstring(username); + runtime_path = _wcsdup(homeDir.append(".xlcore").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + // Finally XOM + homeDir = L"Z:\\Users\\" + std::wstring(username); + runtime_path = _wcsdup(homeDir.append("Library").append("Application Suppor").append("XIV on Mac").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + return NULL; +} + HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, @@ -41,8 +100,7 @@ HRESULT InitializeClrAndGetEntryPoint( int result; SetEnvironmentVariable(L"DOTNET_MULTILEVEL_LOOKUP", L"0"); - SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); - SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1"); + SetEnvironmentVariable(L"COMPLUS_ForceENC", L"1"); SetEnvironmentVariable(L"DOTNET_ForceENC", L"1"); @@ -56,31 +114,12 @@ HRESULT InitializeClrAndGetEntryPoint( SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0"); - wchar_t* dotnet_path; - wchar_t* _appdata; + wchar_t* dotnet_path = GetRuntimePath(); - std::wstring buffer; - buffer.resize(0); - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0); - - if (result) + if (!dotnet_path || !std::filesystem::exists(dotnet_path)) { - buffer.resize(result); // The first pass returns the required length - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); - dotnet_path = _wcsdup(buffer.c_str()); - } - else - { - result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata); - - if (result != 0) - { - logging::E("Unable to get RoamingAppData path (err={})", result); - return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); - } - - std::filesystem::path fs_app_data(_appdata); - dotnet_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str()); + logging::E("Error: Unable to find .NET runtime path"); + return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); } // =========================================================================== // @@ -89,12 +128,6 @@ HRESULT InitializeClrAndGetEntryPoint( logging::I("with config_path: {}", runtimeconfig_path); logging::I("with module_path: {}", module_path); - if (!std::filesystem::exists(dotnet_path)) - { - logging::E("Error: Unable to find .NET runtime path"); - return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); - } - get_hostfxr_parameters init_parameters { sizeof(get_hostfxr_parameters), diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 731e3ab00..9e7f03ed6 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 731e3ab0006ce56c4fe789aee148bc967965b914 +Subproject commit 9e7f03ed6d3d5cb9e6952f00c4779ac64427bc81 diff --git a/lib/cimgui b/lib/cimgui new file mode 160000 index 000000000..122ee1681 --- /dev/null +++ b/lib/cimgui @@ -0,0 +1 @@ +Subproject commit 122ee16819437eea7eefe0c04398b44174106d86 diff --git a/lib/cimguizmo b/lib/cimguizmo new file mode 160000 index 000000000..dbad4fdb4 --- /dev/null +++ b/lib/cimguizmo @@ -0,0 +1 @@ +Subproject commit dbad4fdb4d465e1f48d20c4c54a20925095297b0 diff --git a/lib/cimplot b/lib/cimplot new file mode 160000 index 000000000..939f8f36d --- /dev/null +++ b/lib/cimplot @@ -0,0 +1 @@ +Subproject commit 939f8f36deebd895f6cda522ee4bb2b798920935 diff --git a/release.ps1 b/release.ps1 new file mode 100644 index 000000000..8863a1214 --- /dev/null +++ b/release.ps1 @@ -0,0 +1,31 @@ +param( + [string]$VersionString +) + +if (-not $VersionString) { + Write-Error "Version string is required as the first argument." + exit 1 +} + +$csprojPath = "Dalamud/Dalamud.csproj" + +if (-not (Test-Path $csprojPath)) { + Write-Error "Cannot find Dalamud.csproj at the specified path." + exit 1 +} + +# Update the version in the csproj file +(Get-Content $csprojPath) -replace '.*?', "$VersionString" | Set-Content $csprojPath + +# Commit the change +git add $csprojPath +git commit -m "build: $VersionString" + +# Get the current branch +$currentBranch = git rev-parse --abbrev-ref HEAD + +# Create a tag +git tag -a -m "v$VersionString" $VersionString + +# Push atomically +git push origin $currentBranch $VersionString \ No newline at end of file diff --git a/targets/Dalamud.Plugin.targets b/targets/Dalamud.Plugin.targets index dc5bec410..08d19735e 100644 --- a/targets/Dalamud.Plugin.targets +++ b/targets/Dalamud.Plugin.targets @@ -14,7 +14,7 @@ - + @@ -22,15 +22,14 @@ + -
diff --git a/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj b/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj index 5701e706f..aa6cabedc 100644 --- a/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj +++ b/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj @@ -2,10 +2,6 @@ Exe - net8.0-windows - x64 - x64;AnyCPU - 12.0 enable enable