From 3f5460a5ff318751b3effb85c4c57bbdd46e9ece Mon Sep 17 00:00:00 2001 From: Amber Brault Date: Thu, 7 May 2026 09:24:55 +0200 Subject: [PATCH] Core: Implement automatic symbol demangling --- Externals/licenses.md | 2 + Source/Core/Common/CMakeLists.txt | 2 + Source/Core/Common/CWDemangler.cpp | 772 ++++++++++++++++++ Source/Core/Common/CWDemangler.h | 67 ++ Source/Core/Common/StringUtil.cpp | 31 +- Source/Core/Common/StringUtil.h | 2 + Source/Core/Common/SymbolDB.cpp | 5 + Source/Core/Common/SymbolDB.h | 15 + Source/Core/DolphinLib.props | 2 + Source/Core/DolphinQt/Debugger/CodeWidget.cpp | 35 +- Source/Core/DolphinQt/Debugger/CodeWidget.h | 3 + Source/Core/DolphinQt/MenuBar.cpp | 8 + Source/Core/DolphinQt/MenuBar.h | 1 + Source/Core/DolphinQt/Settings.cpp | 14 + Source/Core/DolphinQt/Settings.h | 3 + Source/UnitTests/Common/CMakeLists.txt | 1 + Source/UnitTests/Common/CWDemanglerTest.cpp | 431 ++++++++++ Source/UnitTests/UnitTests.vcxproj | 1 + 18 files changed, 1388 insertions(+), 7 deletions(-) create mode 100644 Source/Core/Common/CWDemangler.cpp create mode 100644 Source/Core/Common/CWDemangler.h create mode 100644 Source/UnitTests/Common/CWDemanglerTest.cpp diff --git a/Externals/licenses.md b/Externals/licenses.md index 9db9cecf1a..b2d1a89957 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -12,6 +12,8 @@ Dolphin includes or links code of the following third-party software projects: [MIT](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE) - [cubeb](https://github.com/kinetiknz/cubeb): [ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE) +- [cwdemangle](https://github.com/encounter/cwdemangle) + [CC0-1.0](https://github.com/encounter/cwdemangle/blob/main/LICENSE) - [Discord-RPC](https://github.com/discordapp/discord-rpc): [MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE) - [ENet](http://enet.bespin.org/): diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 894fd38128..0759756629 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -47,6 +47,8 @@ add_library(common Crypto/HMAC.h Crypto/SHA1.cpp Crypto/SHA1.h + CWDemangler.cpp + CWDemangler.h Debug/MemoryPatches.cpp Debug/MemoryPatches.h Debug/Threads.h diff --git a/Source/Core/Common/CWDemangler.cpp b/Source/Core/Common/CWDemangler.cpp new file mode 100644 index 0000000000..8b4d700c2a --- /dev/null +++ b/Source/Core/Common/CWDemangler.cpp @@ -0,0 +1,772 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Based on: https://github.com/encounter/cwdemangle +// Copyright 2024 Luke Street +// SPDX-License-Identifier: CC0-1.0 + +#include "Common/CWDemangler.h" + +#include +#include +#include + +#include + +#include "Common/StringUtil.h" + +namespace CWDemangler +{ + +struct ParseQualifiersResult +{ + std::string pre; + std::string post; + std::string_view rest; +}; + +struct ParseDigitsResult +{ + std::size_t digits; + std::string_view rest; +}; + +inline static bool IsAscii(std::string_view s) +{ + return std::ranges::none_of(s, [](u8 c) { return c > 127; }); +} + +static const std::map operators = { + {"nw", "operator new"}, {"nwa", "operator new[]"}, + {"dl", "operator delete"}, {"dla", "operator delete[]"}, + {"pl", "operator+"}, {"mi", "operator-"}, + {"ml", "operator*"}, {"dv", "operator/"}, + {"md", "operator%"}, {"er", "operator^"}, + {"ad", "operator&"}, {"or", "operator|"}, + {"co", "operator~"}, {"nt", "operator!"}, + {"as", "operator="}, {"lt", "operator<"}, + {"gt", "operator>"}, {"apl", "operator+="}, + {"ami", "operator-="}, {"amu", "operator*="}, + {"adv", "operator/="}, {"amd", "operator%="}, + {"aer", "operator^="}, {"aad", "operator&="}, + {"aor", "operator|="}, {"ls", "operator<<"}, + {"rs", "operator>>"}, {"ars", "operator>>="}, + {"als", "operator<<="}, {"eq", "operator=="}, + {"ne", "operator!="}, {"le", "operator<="}, + {"ge", "operator>="}, {"aa", "operator&&"}, + {"oo", "operator||"}, {"pp", "operator++"}, + {"mm", "operator--"}, {"cm", "operator,"}, + {"rm", "operator->*"}, {"rf", "operator->"}, + {"cl", "operator()"}, {"vc", "operator[]"}, + {"vt", "__vtable"}}; + +static const std::map types = { + {'i', "int"}, {'b', "bool"}, {'c', "char"}, {'s', "short"}, + {'l', "long"}, {'x', "long long"}, {'f', "float"}, {'d', "double"}, + {'w', "wchar_t"}, {'v', "void"}, {'e', "..."}, {'r', "long double"}, + {'D', "short double"}, +}; + +// Finds the first double underscore in the string, excluding any that are part of a +// template argument list or operator name. +static std::optional find_split(std::string_view s, bool special, + DemangleOptions options) +{ + std::size_t start = 0; + + if (special && s.starts_with("op")) + { + const auto result = demangle_arg(s.substr(2), options); + if (!result) + return std::nullopt; + + const std::string_view rest = result->rest; + + start = s.length() - rest.length(); + } + + int depth = 0; + const std::size_t length = s.length(); + + for (std::size_t i = start; i < length; i++) + { + switch (s[i]) + { + case '<': + depth++; + break; + case '>': + depth--; + break; + case '_': + if (i < length - 1 && s[i + 1] == '_' && depth == 0) + { + return i; + } + break; + default: + break; + } + } + + return std::nullopt; +} + +ParseQualifiersResult parse_qualifiers(std::string_view str) +{ + std::string pre; + std::string post; + std::size_t index = 0; + + for (char c : str) + { + bool found_non_qualifier = false; + + switch (c) + { + case 'P': + if (pre.empty()) + { + post.insert(0, "*"); + } + else + { + post.insert(0, fmt::format("* {0}", StripTrailingWhitespace(pre))); + pre.clear(); + } + break; + case 'R': + if (pre.empty()) + { + post.insert(0, "&"); + } + else + { + post.insert(0, fmt::format("& {0}", StripTrailingWhitespace(pre))); + pre.clear(); + } + break; + case 'C': + pre += "const "; + break; + case 'V': + pre += "volatile "; + break; + case 'U': + pre += "unsigned "; + break; + case 'S': + pre += "signed "; + break; + default: + found_non_qualifier = true; + break; + } + + if (found_non_qualifier) + break; + index++; + } + str.remove_prefix(index); + post = StripTrailingWhitespace(post); + return {pre, post, str}; +} + +std::optional parse_digits(std::string_view str) +{ + if (str.empty()) + return std::nullopt; + + const char* str_start = str.data(); + const char* str_end = str_start + str.size(); + std::size_t val = 0; + std::string_view remainder; + + const auto result = std::from_chars(str_start, str_end, val); + + if (result.ec != std::errc{}) + return std::nullopt; + + const char* rest_start = result.ptr; + if (rest_start != str_end) + remainder = std::string_view(rest_start, str_end - rest_start); + + return {{val, remainder}}; +} + +std::optional demangle_template_args(std::string_view str, + DemangleOptions options) +{ + const std::size_t start_idx = str.find('<'); + + if (start_idx == std::string::npos) + return {{str, ""}}; + + const std::size_t end_idx = str.rfind('>'); + if (end_idx == std::string::npos || end_idx < start_idx) + { + return std::nullopt; + } + + std::string_view args(&str[start_idx + 1], &str[end_idx]); + const std::string_view template_name = str.substr(0, start_idx); + std::string tmpl_args = "<"; + + while (!args.empty()) + { + const auto result = demangle_arg(args, options); + if (!result) + return std::nullopt; + + const auto demangled_arg = result.value(); + const std::string_view rest = demangled_arg.rest; + tmpl_args += demangled_arg.arg_pre; + tmpl_args += demangled_arg.arg_post; + + if (rest.empty()) + break; + + tmpl_args += ", "; + + args = rest.substr(1); + } + + tmpl_args += ">"; + + return {{template_name, tmpl_args}}; +} + +std::optional demangle_name(std::string_view str, DemangleOptions options) +{ + const auto result = parse_digits(str); + if (!result) + return std::nullopt; + + auto [size, rest] = result.value(); + + if (rest.length() < size) + { + return std::nullopt; + } + + auto result1 = demangle_template_args(rest.substr(0, size), options); + if (!result1) + return std::nullopt; + + auto [name, args] = result1.value(); + const std::string result_name{name}; + return {{result_name, fmt::format("{0}{1}", name, args), rest.substr(size)}}; +} + +std::optional demangle_qualified_name(std::string_view str, + DemangleOptions options) +{ + if (!str.starts_with('Q')) + return demangle_name(str, options); + + if (str.length() < 3) + { + return std::nullopt; + } + + // MWCC only preserves up to 9 layers of namespace/class depth in symbol names, so we + // can just check one digit + const int digit = static_cast(str[1] - '0'); + if (digit < 0 || digit > 9) + return std::nullopt; + + const int count = digit; + + str = str.substr(2); + + std::string last_class; + std::string qualified; + + for (int i = 0; i < count; i++) + { + const auto result = demangle_name(str, options); + if (!result) + return std::nullopt; + + auto [class_name, full, rest] = *result; + qualified += full; + last_class = class_name; + str = rest; + if (i < count - 1) + { + qualified += "::"; + } + } + return {{last_class, qualified, str}}; +} + +std::optional demangle_arg(std::string_view str, DemangleOptions options) +{ + // Negative constant + if (str.starts_with('-')) + { + const auto parse_result = parse_digits(str.substr(1)); + if (!parse_result) + return std::nullopt; + + const std::size_t size = parse_result->digits; + const std::string out_val = fmt::format("-{}", size); + return {{out_val, "", parse_result->rest}}; + } + + std::string result; + + const auto parse_qual_result = parse_qualifiers(str); + std::string pre = parse_qual_result.pre; + std::string post = parse_qual_result.post; + std::string_view rest = parse_qual_result.rest; + result += pre; + str = rest; + + // Disambiguate arguments starting with a number + if (str.length() > 0 && std::isdigit(static_cast(str[0]))) + { + const auto parse_result = parse_digits(str); + if (!parse_result) + return std::nullopt; + + auto& [num, rest_value] = parse_result.value(); + rest = rest_value; + + // If the number is followed by a comma or the end of the string, it's a template argument + if (rest.empty() || rest.starts_with(',')) + { + // ...or a Metrowerks extension type + if (options.mw_extensions) + { + const std::string t = num == 1 ? "__int128" : num == 2 ? "__vec2x32float__" : ""; + + if (!t.empty()) + { + result += t; + return {{result, post, rest}}; + } + } + result += fmt::format("{}{}", num, post); + return {{result, "", rest}}; + } + // Otherwise, it's (probably) the size of a type + const auto demangle_name_result = demangle_name(str, options); + if (!demangle_name_result) + return std::nullopt; + + result += demangle_name_result->full; + result += post; + return {{result, "", demangle_name_result->rest}}; + } + + // Handle qualified names + if (str.starts_with('Q')) + { + const auto demangle_qual_result = demangle_qualified_name(str, options); + if (!demangle_qual_result) + return std::nullopt; + + result += demangle_qual_result->full; + result += post; + return {{result, "", demangle_qual_result->rest}}; + } + + bool is_member = false; + bool const_member = false; + if (str.starts_with('M')) + { + is_member = true; + const auto demangle_qual_result = demangle_qualified_name(str.substr(1), options); + if (!demangle_qual_result) + return std::nullopt; + + rest = demangle_qual_result->rest; + pre = fmt::format("{}::*{}", demangle_qual_result->full, pre); + if (!rest.starts_with('F')) + { + return std::nullopt; + } + str = rest; + } + if (is_member || str.starts_with('F')) + { + str.remove_prefix(1); + if (is_member) + { + // "const void*, const void*" or "const void*, void*" + if (str.starts_with("PCvPCv")) + { + const_member = true; + str.remove_prefix(6); + } + else if (str.starts_with("PCvPv")) + { + str.remove_prefix(5); + } + else + { + return std::nullopt; + } + } + else if (post.starts_with('*')) + { + post = StripLeadingWhitespace(post.substr(1)); + pre = fmt::format("*{}", pre); + } + else + { + return std::nullopt; + } + + const auto demangle_func_args_result = demangle_function_args(str, options); + if (!demangle_func_args_result) + return std::nullopt; + + const auto demangled_func_args = demangle_func_args_result.value(); + + if (!demangled_func_args.rest.starts_with('_')) + { + return std::nullopt; + } + + const auto demangle_arg_result = demangle_arg(demangled_func_args.rest.substr(1), options); + if (!demangle_arg_result) + return std::nullopt; + + const std::string_view const_str = const_member ? " const" : ""; + const std::string res_pre = fmt::format("{} ({}{}", demangle_arg_result->arg_pre, pre, post); + const std::string res_post = fmt::format(")({}){}{}", demangled_func_args.args, const_str, + demangle_arg_result->arg_post); + return {{res_pre, res_post, demangle_arg_result->rest}}; + } + + if (str.starts_with('A')) + { + const auto parse_result = parse_digits(str.substr(1)); + if (!parse_result) + return std::nullopt; + + auto& [count, rest_value] = parse_result.value(); + rest = rest_value; + + if (!rest.starts_with('_')) + { + return std::nullopt; + } + + const auto demangle_arg_result = demangle_arg(rest.substr(1), options); + if (!demangle_arg_result) + return std::nullopt; + + if (!post.empty()) + { + post = fmt::format("({})", post); + } + result = fmt::format("{}{}{}", pre, demangle_arg_result->arg_pre, post); + const std::string ret_post = fmt::format("[{}]{}", count, demangle_arg_result->arg_post); + return {{result, ret_post, demangle_arg_result->rest}}; + } + + if (str.length() == 0) + return std::nullopt; + + std::string_view type; + + const char c = str[0]; + + if (types.contains(c)) + { + type = types.at(c); + } + else + { + // Handle special cases + switch (c) + { + case '1': + if (options.mw_extensions) + type = "__int128"; + break; + case '2': + if (options.mw_extensions) + type = "__vec2x32float__"; + break; + case '_': + return {{result, "", rest}}; + default: + return std::nullopt; + } + } + + return {{fmt::format("{}{}{}", result, type, post), "", str.substr(1)}}; +} + +std::optional demangle_function_args(std::string_view str, + DemangleOptions options) +{ + std::string result; + + while (!str.empty()) + { + if (!result.empty()) + { + result += ", "; + } + + const auto demangle_arg_result = demangle_arg(str, options); + if (!demangle_arg_result) + return std::nullopt; + + result += demangle_arg_result->arg_pre; + result += demangle_arg_result->arg_post; + str = demangle_arg_result->rest; + + if (str.starts_with('_') || str.starts_with(',')) + { + break; + } + } + + return {{result, str}}; +} + +std::optional demangle_special_function(std::string_view str, + std::string_view class_name, + DemangleOptions options) +{ + if (str.starts_with("op")) + { + const std::string_view rest = str.substr(2); + const auto demangle_arg_result = demangle_arg(rest, options); + if (!demangle_arg_result) + return std::nullopt; + + return fmt::format("operator {}{}", demangle_arg_result->arg_pre, + demangle_arg_result->arg_post); + } + + const auto result = demangle_template_args(str, options); + if (!result) + return std::nullopt; + + auto& [op, args] = result.value(); + + std::string_view func_name; + + if (op == "dt") + { + return fmt::format("~{}{}", class_name, args); + } + else if (op == "ct") + { + func_name = class_name; + } + else if (operators.contains(op.data())) + { + func_name = operators.at(op.data()); + } + else + { + return fmt::format("__{}{}", op, args); + } + + return fmt::format("{0}{1}", func_name, args); +} + +// Demangle a symbol name. +// +// Returns `std::nullopt` if the input is not a valid mangled name. +std::optional demangle(std::string_view str, DemangleOptions options) +{ + if (!IsAscii(str)) + { + return std::nullopt; + } + + bool special = false; + bool cnst = false; + std::string fn_name; + std::string static_var; + + // Handle new static function variables (Wii CW) + const bool guard = str.starts_with("@GUARD@"); + if (guard || str.starts_with("@LOCAL@")) + { + str = str.substr(7); + std::size_t idx = str.rfind('@'); + if (idx == std::string::npos) + return std::nullopt; + + const std::string_view rest = str.substr(0, idx); + const std::string_view var = str.substr(idx); + + if (guard) + { + static_var = fmt::format("{0} guard", var.substr(1)); + } + else + { + static_var = var.substr(1); + } + str = rest; + } + + if (str.starts_with("__")) + { + special = true; + str = str.substr(2); + } + + const auto idx_temp = find_split(str, special, options); + if (!idx_temp) + return std::nullopt; + std::size_t idx = idx_temp.value(); + // Handle any trailing underscores in the function name + while (str[idx + 2] == '_') + { + idx++; + } + + const std::string_view fn_name_out = str.substr(0, idx); + std::string_view rest = str.substr(idx); + + if (special) + { + if (fn_name_out == "init") + { + // Special case for double __ + const std::size_t rest_idx = rest.substr(2).find("__"); + if (rest_idx == std::string::npos) + return std::nullopt; + fn_name = str.substr(0, rest_idx + 6); + rest.remove_prefix(rest_idx + 2); + } + else + { + fn_name = fn_name_out; + } + } + else + { + const auto result = demangle_template_args(fn_name_out, options); + if (!result) + return std::nullopt; + + const auto template_args = result.value(); + fn_name = fmt::format("{}{}", template_args.name, template_args.args); + } + + // Handle old static function variables (GC CW) + const std::size_t first_idx = fn_name.find('$'); + if (first_idx != std::string::npos) + { + const std::size_t second_idx = fn_name.substr(first_idx + 1).find('$'); + if (second_idx == std::string::npos) + return std::nullopt; + + const std::string var = fn_name.substr(0, first_idx); + std::string rest_temp = fn_name.substr(first_idx + 1); + + const std::string var_type = rest_temp.substr(0, second_idx); + rest_temp = rest_temp.substr(second_idx); + + if (!var_type.starts_with("localstatic")) + { + return std::nullopt; + } + + if (var == "init") + { + // Sadly, $localstatic doesn't provide the variable name in guard/init + static_var = fmt::format("{} guard", var_type); + } + else + { + static_var = var; + } + + fn_name = rest_temp.substr(1); + } + + str = rest.substr(2); + + std::string class_name; + std::string return_type_pre; + std::string return_type_post; + std::string qualified; + + if (!str.starts_with('F')) + { + const auto result = demangle_qualified_name(str, options); + if (!result) + return std::nullopt; + + class_name = result->class_name; + qualified = result->full; + str = result->rest; + } + if (special) + { + const auto result = demangle_special_function(fn_name, class_name, options); + if (!result) + return std::nullopt; + fn_name = result.value(); + } + if (str.starts_with('C')) + { + str.remove_prefix(1); + cnst = true; + } + if (str.starts_with('F')) + { + str.remove_prefix(1); + const auto result = demangle_function_args(str, options); + if (!result) + return std::nullopt; + + if (options.omit_empty_parameters && result->args == "void") + { + fn_name = fmt::format("{}()", fn_name); + } + else + { + fn_name = fmt::format("{}({})", fn_name, result->args); + } + str = result->rest; + } + if (str.starts_with('_')) + { + str.remove_prefix(1); + const auto result = demangle_arg(str, options); + if (!result) + return std::nullopt; + + return_type_pre = result->arg_pre; + return_type_post = result->arg_post; + str = result->rest; + } + if (!str.empty()) + { + return std::nullopt; + } + if (cnst) + { + fn_name = fmt::format("{} const", fn_name); + } + if (!qualified.empty()) + { + fn_name = fmt::format("{}::{}", qualified, fn_name); + } + if (!return_type_pre.empty()) + { + fn_name = fmt::format("{} {}{}", return_type_pre, fn_name, return_type_post); + } + if (!static_var.empty()) + { + fn_name = fmt::format("{}::{}", fn_name, static_var); + } + + return fn_name; +} + +} // namespace CWDemangler diff --git a/Source/Core/Common/CWDemangler.h b/Source/Core/Common/CWDemangler.h new file mode 100644 index 0000000000..b15aa94926 --- /dev/null +++ b/Source/Core/Common/CWDemangler.h @@ -0,0 +1,67 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Based on: https://github.com/encounter/cwdemangle +// Copyright 2024 Luke Street +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" + +namespace CWDemangler +{ + +// Options for [demangle]. +struct DemangleOptions +{ + // Replace `(void)` function parameters with `()` + bool omit_empty_parameters = true; + // Enable Metrowerks extension types (`__int128`, `__vec2x32float__`, etc.) + // + // Disabled by default since they conflict with template argument literals + // and can't always be demangled correctly. + bool mw_extensions = false; +}; + +struct DemangleTemplateArgsResult +{ + std::string_view name; + std::string args; +}; + +struct DemangleNameResult +{ + std::string class_name; + std::string full; + std::string_view rest; +}; + +struct DemangleArgResult +{ + std::string arg_pre; + std::string arg_post; + std::string_view rest; +}; + +struct DemangleFunctionArgsResult +{ + std::string args; + std::string_view rest; +}; + +std::optional demangle_template_args(std::string_view str, + DemangleOptions options); +std::optional demangle_name(std::string_view str, DemangleOptions options); +std::optional demangle_qualified_name(std::string_view str, + DemangleOptions options); +std::optional demangle_arg(std::string_view str, DemangleOptions options); +std::optional demangle_function_args(std::string_view str, + DemangleOptions options); +std::optional demangle_special_function(std::string_view str, + std::string_view class_name, + DemangleOptions options); +std::optional demangle(std::string_view str, DemangleOptions options); +} // namespace CWDemangler diff --git a/Source/Core/Common/StringUtil.cpp b/Source/Core/Common/StringUtil.cpp index ed4837b112..e84fabcbb5 100644 --- a/Source/Core/Common/StringUtil.cpp +++ b/Source/Core/Common/StringUtil.cpp @@ -193,7 +193,7 @@ std::string ArrayToString(const u8* data, u32 size, int line_len, bool spaces) template static std::string_view StripEnclosingChars(std::string_view str, T chars) { - const size_t s = str.find_first_not_of(chars); + const std::size_t s = str.find_first_not_of(chars); if (str.npos != s) return str.substr(s, str.find_last_not_of(chars) - s + 1); @@ -201,12 +201,41 @@ static std::string_view StripEnclosingChars(std::string_view str, T chars) return ""; } +template +static std::string_view StripLeadingChars(std::string_view str, T chars) +{ + const std::size_t s = str.find_first_not_of(chars); + + if (str.npos != s) + return str.substr(s); + else + return ""; +} + +template +static std::string_view StripTrailingChars(std::string_view str, T chars) +{ + return str.substr(0, str.find_last_not_of(chars) + 1); +} + // Turns "\n\r\t hello " into "hello" (trims at the start and end but not inside). std::string_view StripWhitespace(std::string_view str) { return StripEnclosingChars(str, " \t\r\n"); } +// Turns "\n\r\t hello " into "hello " (trims at the start). +std::string_view StripLeadingWhitespace(std::string_view str) +{ + return StripLeadingChars(str, " \t\r\n"); +} + +// Turns "\n\r\t hello " into "\n\r\t hello" (trims at the end). +std::string_view StripTrailingWhitespace(std::string_view str) +{ + return StripTrailingChars(str, " \t\r\n"); +} + std::string_view StripSpaces(std::string_view str) { return StripEnclosingChars(str, ' '); diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h index 0cc4e6c15e..61404376d3 100644 --- a/Source/Core/Common/StringUtil.h +++ b/Source/Core/Common/StringUtil.h @@ -49,6 +49,8 @@ inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) std::string ArrayToString(const u8* data, u32 size, int line_len = 20, bool spaces = true); std::string_view StripWhitespace(std::string_view s); +std::string_view StripLeadingWhitespace(std::string_view s); +std::string_view StripTrailingWhitespace(std::string_view s); std::string_view StripSpaces(std::string_view s); std::string_view StripQuotes(std::string_view s); diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index 2fa514ba86..e1778d2ca5 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -9,6 +9,7 @@ #include #include +#include "Common/CWDemangler.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -31,6 +32,10 @@ void Symbol::Rename(const std::string& symbol_name) { this->name = symbol_name; this->function_name = GetStrippedFunctionName(symbol_name); + + // Try demangling the symbol name, saving it in the symbol if valid. + auto demangle_result = CWDemangler::demangle(symbol_name, CWDemangler::DemangleOptions()); + this->demangled_name = demangle_result.value_or(""); } void SymbolDB::List() diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index 8a1767d696..5074b03ad5 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -54,7 +54,22 @@ struct Symbol void Rename(const std::string& symbol_name); + bool IsDemangled() const { return !demangled_name.empty(); } + + const std::string& GetDisplayName(bool use_demangled_name) const + { + if (use_demangled_name && IsDemangled()) + { + return demangled_name; + } + else + { + return name; + } + } + std::string name; + std::string demangled_name; // Demangled symbol name std::string function_name; // stripped function name std::string object_name; // name of object/source file symbol belongs to std::vector callers; // addresses of functions that call this function diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 9959caf60c..5ecd90d543 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -48,6 +48,7 @@ + @@ -847,6 +848,7 @@ + diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index a04f761cee..edac7e52b9 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -73,6 +73,9 @@ CodeWidget::CodeWidget(QWidget* parent) connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &CodeWidget::Update); + connect(&Settings::Instance(), &Settings::ShowDemangledNamesChanged, this, + &CodeWidget::OnShowDemangledNamesChanged); + ConnectWidgets(); m_code_splitter->restoreState( @@ -248,6 +251,16 @@ void CodeWidget::OnSetCodeAddress(u32 address) SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); } +void CodeWidget::OnShowDemangledNamesChanged() +{ + UpdateSymbols(); + if (const Common::Symbol* symbol = m_ppc_symbol_db.GetSymbolFromAddr(m_code_view->GetAddress())) + { + UpdateFunctionCalls(symbol); + UpdateFunctionCallers(symbol); + } +} + void CodeWidget::OnPPCSymbolsChanged() { UpdateSymbols(); @@ -424,7 +437,7 @@ void CodeWidget::UpdateSymbols() m_symbols_list->clear(); m_ppc_symbol_db.ForEachSymbol([&](const Common::Symbol& symbol) { - QString name = QString::fromStdString(symbol.name); + QString name = QString::fromStdString(GetSymbolDisplayName(&symbol)); // If the symbol has an object name, add it to the entry name. if (!symbol.object_name.empty()) @@ -488,15 +501,16 @@ void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol) if (call_symbol) { QString name; + const std::string& symbol_name = GetSymbolDisplayName(call_symbol); if (!call_symbol->object_name.empty()) { name = QString::fromStdString( - fmt::format("< {} ({}, {:08x})", call_symbol->name, call_symbol->object_name, addr)); + fmt::format("< {} ({}, {:08x})", symbol_name, call_symbol->object_name, addr)); } else { - name = QString::fromStdString(fmt::format("< {} ({:08x})", call_symbol->name, addr)); + name = QString::fromStdString(fmt::format("< {} ({:08x})", symbol_name, addr)); } if (!name.contains(filter, Qt::CaseInsensitive)) @@ -525,15 +539,16 @@ void CodeWidget::UpdateFunctionCallers(const Common::Symbol* symbol) if (caller_symbol) { QString name; + const std::string& symbol_name = GetSymbolDisplayName(caller_symbol); if (!caller_symbol->object_name.empty()) { - name = QString::fromStdString(fmt::format("< {} ({}, {:08x})", caller_symbol->name, - caller_symbol->object_name, addr)); + name = QString::fromStdString( + fmt::format("< {} ({}, {:08x})", symbol_name, caller_symbol->object_name, addr)); } else { - name = QString::fromStdString(fmt::format("< {} ({:08x})", caller_symbol->name, addr)); + name = QString::fromStdString(fmt::format("< {} ({:08x})", symbol_name, addr)); } if (!name.contains(filter, Qt::CaseInsensitive)) @@ -546,6 +561,14 @@ void CodeWidget::UpdateFunctionCallers(const Common::Symbol* symbol) } } +// Gets the name of this symbol based on the option for whether or not to show +// demangled names. +const std::string& CodeWidget::GetSymbolDisplayName(const Common::Symbol* symbol) const +{ + const bool show_demangled_names = Settings::Instance().IsShowDemangledNames(); + return symbol->GetDisplayName(show_demangled_names); +} + void CodeWidget::Step() { auto& cpu = m_system.GetCPU(); diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h index 76ada4f506..9a678e1c2c 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h @@ -64,6 +64,7 @@ private: void UpdateFunctionCallers(const Common::Symbol* symbol); void UpdateNotes(); + void OnShowDemangledNamesChanged(); void OnPPCSymbolsChanged(); void OnSearchAddress(); void OnSearchSymbols(); @@ -76,6 +77,8 @@ private: void closeEvent(QCloseEvent*) override; void showEvent(QShowEvent* event) override; + const std::string& GetSymbolDisplayName(const Common::Symbol* symbol) const; + Core::System& m_system; PPCSymbolDB& m_ppc_symbol_db; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index f5d87e0b77..a29ba272ec 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -1073,6 +1073,7 @@ void MenuBar::AddSymbolsMenu() generate->addAction(tr("Address"), this, &MenuBar::GenerateSymbolsFromAddress); generate->addAction(tr("Signature Database"), this, &MenuBar::GenerateSymbolsFromSignatureDB); generate->addAction(tr("RSO Modules"), this, &MenuBar::GenerateSymbolsFromRSO); + m_debugger_show_demangled_names = m_symbols->addAction(tr("Show &Demangled Names")); m_symbols->addSeparator(); m_symbols->addAction(tr("&Load Symbol Map"), this, &MenuBar::LoadSymbolMap); @@ -1096,6 +1097,13 @@ void MenuBar::AddSymbolsMenu() m_symbols->addSeparator(); m_symbols->addAction(tr("&Patch HLE Functions"), this, &MenuBar::PatchHLEFunctions); + + m_debugger_show_demangled_names->setCheckable(true); + m_debugger_show_demangled_names->setChecked(Settings::Instance().IsShowDemangledNames()); + connect(m_debugger_show_demangled_names, &QAction::toggled, &Settings::Instance(), + &Settings::SetShowDemangledNames); + connect(&Settings::Instance(), &Settings::ShowDemangledNamesChanged, + m_debugger_show_demangled_names, &QAction::setChecked); } void MenuBar::UpdateToolsMenu(const Core::State state) diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index a381353b0c..1535924510 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -310,6 +310,7 @@ private: QAction* m_jit_systemregisters_off; QAction* m_jit_branch_off; QAction* m_jit_register_cache_off; + QAction* m_debugger_show_demangled_names; bool m_game_selected = false; diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 595e9d8084..618a2b5ced 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -871,6 +871,20 @@ QFont Settings::GetDebugFont() const return GetQSettings().value(QStringLiteral("debugger/font"), default_font).value(); } +void Settings::SetShowDemangledNames(bool enabled) +{ + if (IsShowDemangledNames() == enabled) + return; + QSettings().setValue(QStringLiteral("debugger/showdemanglednames"), enabled); + + emit ShowDemangledNamesChanged(enabled); +} + +bool Settings::IsShowDemangledNames() const +{ + return QSettings().value(QStringLiteral("debugger/showdemanglednames")).toBool(); +} + void Settings::SetAutoUpdateTrack(const QString& mode) { if (mode == GetAutoUpdateTrack()) diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 2fe0d272e6..0c79020ff8 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -176,6 +176,8 @@ public: bool IsAssemblerVisible() const; QFont GetDebugFont() const; void SetDebugFont(const QFont& font); + void SetShowDemangledNames(bool enabled); + bool IsShowDemangledNames() const; // Auto-Update QString GetAutoUpdateTrack() const; @@ -223,6 +225,7 @@ signals: void AssemblerVisibilityChanged(bool visible); void DebugModeToggled(bool enabled); void DebugFontChanged(const QFont& font); + void ShowDemangledNamesChanged(bool enabled); void AutoUpdateTrackChanged(const QString& mode); void FallbackRegionChanged(const DiscIO::Region& region); void AnalyticsToggled(bool enabled); diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index a2e21d7a80..f7689b3ec4 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -7,6 +7,7 @@ add_dolphin_test(BusyLoopTest BusyLoopTest.cpp) add_dolphin_test(CommonFuncsTest CommonFuncsTest.cpp) add_dolphin_test(CryptoEcTest Crypto/EcTest.cpp) add_dolphin_test(CryptoSHA1Test Crypto/SHA1Test.cpp) +add_dolphin_test(CWDemanglerTest CWDemanglerTest.cpp) add_dolphin_test(EnumFormatterTest EnumFormatterTest.cpp) add_dolphin_test(EventTest EventTest.cpp) add_dolphin_test(FileUtilTest FileUtilTest.cpp) diff --git a/Source/UnitTests/Common/CWDemanglerTest.cpp b/Source/UnitTests/Common/CWDemanglerTest.cpp new file mode 100644 index 0000000000..7320e48472 --- /dev/null +++ b/Source/UnitTests/Common/CWDemanglerTest.cpp @@ -0,0 +1,431 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Based on: https://github.com/encounter/cwdemangle +// Copyright 2024 Luke Street +// SPDX-License-Identifier: CC0-1.0 + +#include +#include +#include +#include + +#include "Common/CWDemangler.h" + +using namespace CWDemangler; + +void DoDemangleTemplateArgsTest(std::string mangled, std::string name, std::string template_args) +{ + DemangleOptions options = DemangleOptions(); + + auto result = demangle_template_args(mangled, options); + DemangleTemplateArgsResult expected = {name, template_args}; + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) + { + EXPECT_EQ(result.value().args, expected.args); + EXPECT_EQ(result.value().name, expected.name); + } +} + +void DoDemangleNameTest(std::string mangled, std::string name, std::string full_name) +{ + DemangleOptions options = DemangleOptions(); + + auto result = demangle_name(mangled, options); + DemangleNameResult expected = {name, full_name, ""}; + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) + { + EXPECT_EQ(result.value().class_name, expected.class_name); + EXPECT_EQ(result.value().full, expected.full); + EXPECT_EQ(result.value().rest, expected.rest); + } +} + +void DoDemangleQualifiedNameTest(std::string mangled, std::string base_name, std::string full_name) +{ + DemangleOptions options = DemangleOptions(); + + auto result = demangle_qualified_name(mangled, options); + DemangleNameResult expected = {base_name, full_name, ""}; + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) + { + EXPECT_EQ(result.value().class_name, expected.class_name); + EXPECT_EQ(result.value().full, expected.full); + EXPECT_EQ(result.value().rest, expected.rest); + } +} + +void DoDemangleArgTest(std::string mangled, std::string type_pre, std::string type_post, + std::string remainder) +{ + DemangleOptions options = DemangleOptions(); + + auto result = CWDemangler::demangle_arg(mangled, options); + DemangleArgResult expected = {type_pre, type_post, remainder}; + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) + { + EXPECT_EQ(result.value().arg_post, expected.arg_post); + EXPECT_EQ(result.value().arg_pre, expected.arg_pre); + EXPECT_EQ(result.value().rest, expected.rest); + } +} + +void DoDemangleFunctionArgsTest(std::string mangled, std::string args, std::string remainder) +{ + DemangleOptions options = DemangleOptions(); + + auto result = demangle_function_args(mangled, options); + DemangleFunctionArgsResult expected = {args, remainder}; + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) + { + EXPECT_EQ(result.value().args, expected.args); + EXPECT_EQ(result.value().rest, expected.rest); + } +} + +void DoDemangleTest(std::string mangled, std::string demangled) +{ + DemangleOptions options = DemangleOptions(); + + auto result = demangle(mangled, options); + std::optional expected = {demangled}; + if (demangled == "") + expected = std::nullopt; + + EXPECT_EQ(result, expected); +} + +void DoDemangleOptionsTest(bool omit_empty_params, bool mw_extensions, std::string mangled, + std::string demangled) +{ + DemangleOptions options = DemangleOptions(omit_empty_params, mw_extensions); + + auto result = demangle(mangled, options); + std::optional expected = {demangled}; + + EXPECT_EQ(result, expected); +} + +TEST(CWDemangler, TestDemangleTemplateArgs) +{ + DoDemangleTemplateArgsTest("ShaderUid<24geometry_shader_uid_data>", "ShaderUid", + ""); + DoDemangleTemplateArgsTest("basic_string,Q23std12allocator>", + "basic_string", + ", std::allocator>"); +} + +TEST(CWDemangler, TestDemangleName) +{ + DoDemangleNameTest("37ShaderUid<24geometry_shader_uid_data>", "ShaderUid", + "ShaderUid"); + DoDemangleNameTest("59basic_string,Q23std12allocator>", + "basic_string", + "basic_string, std::allocator>"); +} + +TEST(CWDemangler, TestDemangleQualifiedName) +{ + DoDemangleQualifiedNameTest("7Wiimote", "Wiimote", "Wiimote"); + DoDemangleQualifiedNameTest("Q23Gen8XEmitter", "XEmitter", "Gen::XEmitter"); + DoDemangleQualifiedNameTest( + "Q23std59basic_string,Q23std12allocator>", "basic_string", + "std::basic_string, std::allocator>"); +} + +TEST(CWDemangler, TestDemangleArg) +{ + DoDemangleArgTest("v", "void", "", ""); + DoDemangleArgTest("b", "bool", "", ""); + DoDemangleArgTest("RC9CVector3fUc", "const CVector3f&", "", "Uc"); + DoDemangleArgTest("Q23std14char_traits,", "std::char_traits", "", ","); + DoDemangleArgTest("PFPCcPCc_v", "void (*", ")(const char*, const char*)", ""); + DoDemangleArgTest("RCPCVPCVUi", "const volatile unsigned int* const volatile* const&", "", ""); +} + +TEST(CWDemangler, TestDemangleFunctionArgs) +{ + DoDemangleFunctionArgsTest("v", "void", ""); + DoDemangleFunctionArgsTest("b", "bool", ""); + DoDemangleFunctionArgsTest("RC9CVector3fUc_x", "const CVector3f&, unsigned char", "_x"); +} + +TEST(CWDemangler, TestDemangle) +{ + DoDemangleTest("__ct__11AbstractGfxFv", "AbstractGfx::AbstractGfx()"); + + DoDemangleTest("__dt__11AbstractGfxFv", "AbstractGfx::~AbstractGfx()"); + + DoDemangleTest( + "OnPrimitiveCommand__Q216@unnamed@code_c@20FifoPlaybackAnalyzerFQ316@unnamed@code_c@" + "13OpcodeDecoder9PrimitiveUcUlUsPUc", + "@unnamed@code_c@::FifoPlaybackAnalyzer::OnPrimitiveCommand(@unnamed@code_c@::OpcodeDecoder::" + "Primitive, unsigned char, unsigned long, unsigned short, unsigned char*)"); + + DoDemangleTest("SetSIMDMode__Q26Common3FPUFQ36Common3FPU9RoundModeb", + "Common::FPU::SetSIMDMode(Common::FPU::RoundMode, bool)"); + + DoDemangleTest("SupportsUtilityDrawing__11AbstractGfxCFv", + "AbstractGfx::SupportsUtilityDrawing() const"); + + DoDemangleTest("SetViewport__11AbstractGfxFffffff", + "AbstractGfx::SetViewport(float, float, float, float, float, float)"); + + DoDemangleTest("CreateTexture__11AbstractGfxFR13TextureConfigQ23std43basic_string_view>", + "AbstractGfx::CreateTexture(TextureConfig&, std::basic_string_view>)"); + + DoDemangleTest("SetViewportAndScissor__11AbstractGfxFRQ28MathUtil12Rectangleff", + "AbstractGfx::SetViewportAndScissor(MathUtil::Rectangle&, float, float)"); + + DoDemangleTest("GetInstance__18AchievementManagerFv", "AchievementManager::GetInstance()"); + + DoDemangleTest("FilterApprovedPatches__18AchievementManagerCFRQ23std70vector<" + "Q211PatchEngine5Patch,Q23std32allocator>RQ23std59basic_" + "string,Q23std12allocator>Us", + "AchievementManager::FilterApprovedPatches(std::vector>&, std::basic_string, std::allocator>&, unsigned short) const"); + + DoDemangleTest( + "__as__Q218AchievementManager15FilereaderStateFPQ218AchievementManager15FilereaderState", + "AchievementManager::FilereaderState::operator=(AchievementManager::FilereaderState*)"); + + DoDemangleTest("LeaderboardEntriesCallback__18AchievementManagerFiPcP34rc_client_leaderboard_" + "entry_list_tP11rc_client_tPv", + "AchievementManager::LeaderboardEntriesCallback(int, char*, " + "rc_client_leaderboard_entry_list_t*, rc_client_t*, void*)"); + + DoDemangleTest("Request__18AchievementManagerFP16rc_api_request_tPFP24rc_api_server_response_tPv_" + "vPvP11rc_client_t", + "AchievementManager::Request(rc_api_request_t*, void " + "(*)(rc_api_server_response_t*, void*), void*, rc_client_t*)"); + + DoDemangleTest("MemoryPeeker__18AchievementManagerFUlPUcUlP11rc_client_t", + "AchievementManager::MemoryPeeker(unsigned long, unsigned char*, unsigned long, " + "rc_client_t*)"); + + DoDemangleTest("Write__11BoundingBoxFUlQ23std11span", + "BoundingBox::Write(unsigned long, std::span)"); + + DoDemangleTest( + "__ct__24CachedInterpreterEmitterFPUcPUc", + "CachedInterpreterEmitter::CachedInterpreterEmitter(unsigned char*, unsigned char*)"); + + DoDemangleTest("Write__24CachedInterpreterEmitterFPFRQ27PowerPC12PowerPCStatePv_iPvUx", + "CachedInterpreterEmitter::Write(int (*)(PowerPC::PowerPCState&, void*), void*, " + "unsigned long long)"); + + DoDemangleTest("Shear__Q26Common8Matrix44Fff", "Common::Matrix44::Shear(float, float)"); + + DoDemangleTest("__opQ310WiimoteEmu10Shinkansen10DataFormat__Q26Common61BitCastPtrType<" + "Q310WiimoteEmu10Shinkansen10DataFormat,A21_Uc>Fv", + "Common::BitCastPtrType::operator WiimoteEmu::Shinkansen::DataFormat()"); + + DoDemangleTest( + "__ct__Q26Common61BitCastPtrTypeFPA21_Uc", + "Common::BitCastPtrType::BitCastPtrType(unsigned char(*)[21])"); + + DoDemangleTest("__pp__Q36Common10BitSet8IteratorFi", + "Common::BitSet::Iterator::operator++(int)"); + + DoDemangleTest("__ne__Q36Common10BitSet8IteratorFQ36Common10BitSet8Iterator", + "Common::BitSet::Iterator::operator!=(Common::BitSet::Iterator)"); + + DoDemangleTest("__lt__Q26Common10BitSetFQ26Common10BitSet", + "Common::BitSet::operator<(Common::BitSet)"); + + DoDemangleTest("__opb__Q26Common10BitSetFv", + "Common::BitSet::operator bool()"); + + DoDemangleTest("AddChildCodeSpace__Q26Common28CodeBlockFPQ26Common28CodeBlock<" + "Q23Gen8XEmitter,1>Ux", + "Common::CodeBlock::AddChildCodeSpace(Common::CodeBlock*, unsigned long long)"); + + DoDemangleTest( + "__ct__Q26Common505Lazy," + "Q23std12allocator>,Q23std59basic_string,Q23std12allocator>," + "Q23std73hash,Q23std12allocator>>," + "Q23std77equal_to,Q23std12allocator>>," + "Q23std162allocator," + "Q23std12allocator>,Q23std59basic_string,Q23std12allocator>>>>" + ">Fv", + "Common::Lazy, " + "std::allocator>, std::basic_string, " + "std::allocator>, std::hash, " + "std::allocator>>, std::equal_to, " + "std::allocator>>, std::allocator, std::allocator>, std::basic_string, std::allocator>>>>>::Lazy()"); + + DoDemangleTest( + "Append__Q26Common59LinearDiskCache<37ShaderUid<24geometry_shader_uid_data>,Uc>" + "FRC37ShaderUid<24geometry_shader_uid_data>PCUcUl", + "Common::LinearDiskCache, unsigned char>::Append(const " + "ShaderUid&, const unsigned char*, unsigned long)"); + + DoDemangleTest("SetIsWii__Q24Core6SystemFb", "Core::System::SetIsWii(bool)"); + + DoDemangleTest( + "SetDOLFromFile__Q26DiscIO22DirectoryBlobPartitionFRQ23std59basic_string,Q23std12allocator>UxPQ23std32vector>", + "DiscIO::DirectoryBlobPartition::SetDOLFromFile(std::basic_string, std::allocator>&, unsigned long long, std::vector>*)"); + + DoDemangleTest( + "SetDOL__Q26DiscIO22DirectoryBlobPartitionFQ26DiscIO14FSTBuilderNodeUxPQ23std32vector>", + "DiscIO::DirectoryBlobPartition::SetDOL(DiscIO::FSTBuilderNode, unsigned long long, " + "std::vector>*)"); + + DoDemangleTest("ReadFromGroups__Q26DiscIO19WIARVZFileReader<1>FPUxPUxPPUcUxUlUxUxUlUlUl", + "DiscIO::WIARVZFileReader<1>::ReadFromGroups(unsigned long long*, unsigned long " + "long*, unsigned char**, unsigned long long, unsigned long, unsigned long long, " + "unsigned long long, unsigned long, unsigned long, unsigned long)"); + + DoDemangleTest("GetCompanyFromID__6DiscIOFRCQ23std59basic_string," + "Q23std12allocator>", + "DiscIO::GetCompanyFromID(const std::basic_string, " + "std::allocator>&)"); + + DoDemangleTest("DiscordJoinRequest__Q27Discord7HandlerFPcRQ23std59basic_string,Q23std12allocator>Pc", + "Discord::Handler::DiscordJoinRequest(char*, std::basic_string, std::allocator>&, char*)"); + + DoDemangleTest("AddVoice__Q33DSP3HLE18ZeldaAudioRendererFUs", + "DSP::HLE::ZeldaAudioRenderer::AddVoice(unsigned short)"); + + DoDemangleTest("SetConstPatterns__Q33DSP3HLE18ZeldaAudioRendererFPQ23std12array", + "DSP::HLE::ZeldaAudioRenderer::SetConstPatterns(std::array*)"); + + DoDemangleTest("ReJitConditional__Q33DSP3x6410DSPEmitterFUsMQ33DSP3x6410DSPEmitterFPCvPvUs_v", + "DSP::x64::DSPEmitter::ReJitConditional(unsigned short, void " + "(DSP::x64::DSPEmitter::*)(unsigned short))"); + + DoDemangleTest("decode_mb__9ERContextFPviiiPA4_A2_Piiiii", + "ERContext::decode_mb(void*, int, int, int, int*(*)[4][2], int, int, int, int)"); + + DoDemangleTest("Write__Q24File12DirectIOFileFQ23std11span", + "File::DirectIOFile::Write(std::span)"); + + DoDemangleTest("SetBoth__Q27PowerPC12PairedSingleFff", + "PowerPC::PairedSingle::SetBoth(float, float)"); + + DoDemangleTest("GetPaletteConversionPipeline__Q211VideoCommon11ShaderCacheF10TLUTFormat", + "VideoCommon::ShaderCache::GetPaletteConversionPipeline(TLUTFormat)"); + + DoDemangleTest( + "GetGXPipelineConfig__" + "Q211VideoCommon11ShaderCacheFP18NativeVertexFormatP14AbstractShaderP14AbstractShaderP14Abstr" + "actShaderR18RasterizationStateR10DepthStateR13BlendingState21AbstractPipelineUsage", + "VideoCommon::ShaderCache::GetGXPipelineConfig(NativeVertexFormat*, AbstractShader*, " + "AbstractShader*, AbstractShader*, RasterizationState&, DepthState&, BlendingState&, " + "AbstractPipelineUsage)"); + + DoDemangleTest("LoadCaches__Q211VideoCommon11ShaderCacheFv", + "VideoCommon::ShaderCache::LoadCaches()"); + + DoDemangleTest( + "LoadPipelineCache>,b>,Q23std40less<" + "Q211VideoCommon17GXUberPipelineUid>,Q23std159allocator>,b>>>>>__" + "Q211VideoCommon11ShaderCacheFRQ23std355map>,b>," + "Q23std40less,Q23std159allocator>,b>>>>RQ26Common64LinearDiskCache<" + "Q211VideoCommon27SerializedGXUberPipelineUid,Uc>7APITypePCcb_v", + "void VideoCommon::ShaderCache::LoadPipelineCache>, bool>, " + "std::less, std::allocator>, bool>>>>>(std::map>, bool>, " + "std::less, std::allocator>, bool>>>>&, " + "Common::LinearDiskCache&, APIType, " + "const char*, bool)"); + + DoDemangleTest( + "BuildDesiredExtensionState__Q210WiimoteEmu7TaTaConFPQ210WiimoteEmu21DesiredExtensionState", + "WiimoteEmu::TaTaCon::BuildDesiredExtensionState(WiimoteEmu::DesiredExtensionState*)"); + + DoDemangleTest("__ct__Q210WiimoteEmu7WiimoteFUl", "WiimoteEmu::Wiimote::Wiimote(unsigned long)"); + + DoDemangleTest("SetWiimoteDeviceIndex__Q210WiimoteEmu7WiimoteFUc", + "WiimoteEmu::Wiimote::SetWiimoteDeviceIndex(unsigned char)"); + + DoDemangleTest("GetAngularVelocity__Q210WiimoteEmu7WiimoteFQ26Common8TVec3", + "WiimoteEmu::Wiimote::GetAngularVelocity(Common::TVec3)"); + + DoDemangleTest("BareFn__FPFPCcPv_v_v", "void BareFn(void (*)(const char*, void*))"); + DoDemangleTest("BareFn__FPFPCcPv_v_PFPCvPv_v", + "void (* BareFn(void (*)(const char*, void*)))(const void*, void*)"); + DoDemangleTest( + "SomeFn__FRCPFPFPCvPv_v_RCPFPCvPv_v", + "SomeFn(void (*const& (*const&)(void (*)(const void*, void*)))(const void*, void*))"); + DoDemangleTest( + "SomeFn__Q29Namespace5ClassCFRCMQ29Namespace5ClassFPCvPCvMQ29Namespace5ClassFPCvPCvPCvPv_" + "v_RCMQ29Namespace5ClassFPCvPCvPCvPv_v", + "Namespace::Class::SomeFn(void (Namespace::Class::*const & (Namespace::Class::*const " + "&)(void (Namespace::Class::*)(const void*, void*) const) const)(const void*, void*) " + "const) const"); + DoDemangleTest("Matrix__FfPA2_A3_f", "Matrix(float, float(*)[2][3])"); + DoDemangleTest("test__FRCPCPCi", "test(const int* const* const&)"); + + DoDemangleTest("@GUARD@GetCompanyFromID__6DiscIOFRCQ23std59basic_string," + "Q23std12allocator>@EMPTY_STRING", + "DiscIO::GetCompanyFromID(const std::basic_string, " + "std::allocator>&)::EMPTY_STRING guard"); + + DoDemangleTest("@LOCAL@GetCompanyFromID__6DiscIOFRCQ23std59basic_string," + "Q23std12allocator>@EMPTY_STRING", + "DiscIO::GetCompanyFromID(const std::basic_string, " + "std::allocator>&)::EMPTY_STRING"); + + DoDemangleTest("@LOCAL@SetSIMDMode__Q26Common3FPUFQ36Common3FPU9RoundModeb@EXCEPTION_MASK", + "Common::FPU::SetSIMDMode(Common::FPU::RoundMode, bool)::EXCEPTION_MASK"); + + DoDemangleTest("@LOCAL@GetInstance__18AchievementManagerFv@s_instance", + "AchievementManager::GetInstance()::s_instance"); + + DoDemangleTest("s_instance$localstatic3$GetInstance__18AchievementManagerFv", + "AchievementManager::GetInstance()::s_instance"); + + DoDemangleTest("init$localstatic4$GetInstance__18AchievementManagerFv", + "AchievementManager::GetInstance()::localstatic4 guard"); +} + +TEST(CWDemangler, TestDemangleOptions) +{ + DoDemangleOptionsTest(true, false, "__dt__Q210WiimoteEmu7WiimoteFv", + "WiimoteEmu::Wiimote::~Wiimote()"); + DoDemangleOptionsTest(false, false, "__dt__Q210WiimoteEmu7WiimoteFv", + "WiimoteEmu::Wiimote::~Wiimote(void)"); + DoDemangleOptionsTest(true, true, "example__10HahaOne<1>CFv", + "HahaOne<__int128>::example() const"); + DoDemangleOptionsTest(true, true, "fn<3,PV2>__FPC2", + "fn<3, volatile __vec2x32float__*>(const __vec2x32float__*)"); +} diff --git a/Source/UnitTests/UnitTests.vcxproj b/Source/UnitTests/UnitTests.vcxproj index 6b476dad94..0cda54ff88 100644 --- a/Source/UnitTests/UnitTests.vcxproj +++ b/Source/UnitTests/UnitTests.vcxproj @@ -47,6 +47,7 @@ +