diff --git a/Source/Core/Core/HLE/HLE_OS.cpp b/Source/Core/Core/HLE/HLE_OS.cpp index 71a27a4ec8..e3a1decbf6 100644 --- a/Source/Core/Core/HLE/HLE_OS.cpp +++ b/Source/Core/Core/HLE/HLE_OS.cpp @@ -3,9 +3,15 @@ #include "Core/HLE/HLE_OS.h" +#include #include #include +#include +#include +#include + +#include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" @@ -204,82 +210,365 @@ void HLE_LogVFPrint(const Core::CPUThreadGuard& guard) HLE_LogFPrint(guard, ParameterType::VariableArgumentList); } +namespace +{ +class HLEPrintArgsVAList final : public HLEPrintArgs +{ +public: + HLEPrintArgsVAList(const Core::CPUThreadGuard& guard, HLE::SystemVABI::VAList* va_list) + : m_guard(guard), m_va_list(va_list) + { + } + + u32 GetU32() override { return m_va_list->GetArgT(); } + u64 GetU64() override { return m_va_list->GetArgT(); } + double GetF64() override { return m_va_list->GetArgT(); } + std::string GetString(std::optional max_length) override + { + return max_length == 0u ? std::string() : + PowerPC::MMU::HostGetString(m_guard, m_va_list->GetArgT(), + max_length.value_or(0u)); + } + std::u16string GetU16String(std::optional max_length) override + { + return max_length == 0u ? std::u16string() : + PowerPC::MMU::HostGetU16String(m_guard, m_va_list->GetArgT(), + max_length.value_or(0u)); + } + +private: + const Core::CPUThreadGuard& m_guard; + HLE::SystemVABI::VAList* m_va_list; +}; +} // namespace + std::string GetStringVA(Core::System& system, const Core::CPUThreadGuard& guard, u32 str_reg, ParameterType parameter_type) { auto& ppc_state = system.GetPPCState(); - std::string ArgumentBuffer; - std::string result; std::string string = PowerPC::MMU::HostGetString(guard, ppc_state.gpr[str_reg]); - auto ap = + std::unique_ptr ap = parameter_type == ParameterType::VariableArgumentList ? - std::make_unique(system, guard, - ppc_state.gpr[str_reg + 1]) : - std::make_unique(system, ppc_state.gpr[1] + 0x8, str_reg + 1); + std::make_unique(guard, ppc_state.gpr[str_reg + 1]) : + std::make_unique(guard, ppc_state.gpr[1] + 0x8, str_reg + 1); - for (size_t i = 0; i < string.size(); i++) + HLEPrintArgsVAList args(guard, ap.get()); + return GetStringVA(&args, string); +} + +std::string GetStringVA(HLEPrintArgs* args, std::string_view string) +{ + std::string result; + for (size_t i = 0; i < string.size(); ++i) { - if (string[i] == '%') - { - ArgumentBuffer = '%'; - i++; - if (string[i] == '%') - { - result += '%'; - continue; - } - - while (i < string.size() && - (string[i] < 'A' || string[i] > 'z' || string[i] == 'l' || string[i] == '-')) - { - ArgumentBuffer += string[i++]; - } - if (i >= string.size()) - break; - - ArgumentBuffer += string[i]; - - switch (string[i]) - { - case 's': - result += - StringFromFormat(ArgumentBuffer.c_str(), - PowerPC::MMU::HostGetString(guard, ap->GetArgT(guard)).c_str()); - break; - - case 'a': - case 'A': - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - result += StringFromFormat(ArgumentBuffer.c_str(), ap->GetArgT(guard)); - break; - - case 'p': - // Override, so 64bit Dolphin prints 32bit pointers, since the ppc is 32bit :) - result += StringFromFormat("%x", ap->GetArgT(guard)); - break; - - case 'n': - // %n doesn't output anything, so the result variable is untouched - // the actual PPC function will take care of the memory write - break; - - default: - if (string[i - 1] == 'l' && string[i - 2] == 'l') - result += StringFromFormat(ArgumentBuffer.c_str(), ap->GetArgT(guard)); - else - result += StringFromFormat(ArgumentBuffer.c_str(), ap->GetArgT(guard)); - break; - } - } - else + if (string[i] != '%') { result += string[i]; + continue; + } + + const size_t formatting_start_position = i; + ++i; + if (i < string.size() && string[i] == '%') + { + result += '%'; + continue; + } + + bool left_justified_flag = false; + bool sign_prepended_flag = false; + bool space_prepended_flag = false; + bool alternative_form_flag = false; + bool padding_zeroes_flag = false; + while (i < string.size()) + { + if (string[i] == '-') + left_justified_flag = true; + else if (string[i] == '+') + sign_prepended_flag = true; + else if (string[i] == ' ') + space_prepended_flag = true; + else if (string[i] == '#') + alternative_form_flag = true; + else if (string[i] == '0') + padding_zeroes_flag = true; + else + break; + ++i; + } + + const auto take_field_or_precision = [&](bool* left_justified_flag_ptr) -> std::optional { + if (i >= string.size()) + return std::nullopt; + + if (string[i] == '*') + { + ++i; + const s32 result = Common::BitCast(args->GetU32()); + if (result >= 0) + return static_cast(result); + + if (left_justified_flag_ptr) + { + // field width; this results in positive field width and left_justified flag set + *left_justified_flag_ptr = true; + return static_cast(-static_cast(result)); + } + + // precision; this is ignored + return std::nullopt; + } + + size_t start = i; + while (i < string.size() && string[i] >= '0' && string[i] <= '9') + ++i; + if (start != i) + { + while (start < i && string[start] == '0') + ++start; + if (start == i) + return 0; + u32 result = 0; + const std::string tmp(string, start, i - start); + if (TryParse(tmp, &result, 10)) + return result; + } + + return std::nullopt; + }; + + const std::optional field_width = take_field_or_precision(&left_justified_flag); + std::optional precision = std::nullopt; + if (i < string.size() && string[i] == '.') + { + ++i; + precision = take_field_or_precision(nullptr).value_or(0); + } + + enum class LengthModifier + { + None, + hh, + h, + l, + ll, + L, + }; + auto length_modifier = LengthModifier::None; + + if (i < string.size() && (string[i] == 'h' || string[i] == 'l' || string[i] == 'L')) + { + ++i; + if (i < string.size() && string[i - 1] == 'h' && string[i] == 'h') + { + ++i; + length_modifier = LengthModifier::hh; + } + else if (i < string.size() && string[i - 1] == 'l' && string[i] == 'l') + { + ++i; + length_modifier = LengthModifier::ll; + } + else if (string[i - 1] == 'h') + { + length_modifier = LengthModifier::h; + } + else if (string[i - 1] == 'l') + { + length_modifier = LengthModifier::l; + } + else if (string[i - 1] == 'L') + { + length_modifier = LengthModifier::L; + } + } + + if (i >= string.size()) + { + // not a valid formatting string, print the formatting string as-is + result += string.substr(formatting_start_position); + break; + } + + const char format_specifier = string[i]; + switch (format_specifier) + { + case 's': + { + if (length_modifier == LengthModifier::l) + { + // This is a bit of a mess... wchar_t could be 16 bits or 32 bits per character depending + // on the software. Retail software seems usually to use 16 bits and homebrew 32 bits, but + // that's really just a guess. Ideally we can figure out a way to autodetect this, but if + // not we should probably just expose a setting for it in the debugger somewhere. For now + // we just assume 16 bits. + fmt::format_to( + std::back_inserter(result), fmt::runtime(left_justified_flag ? "{0:<{1}}" : "{0:>{1}}"), + UTF8ToSHIFTJIS(UTF16ToUTF8(args->GetU16String(precision))), field_width.value_or(0)); + } + else + { + fmt::format_to(std::back_inserter(result), + fmt::runtime(left_justified_flag ? "{0:<{1}}" : "{0:>{1}}"), + args->GetString(precision), field_width.value_or(0)); + } + break; + } + case 'c': + { + const s32 value = Common::BitCast(args->GetU32()); + if (length_modifier == LengthModifier::l) + { + // Same problem as with wide strings here. + const char16_t wide_char = static_cast(value); + fmt::format_to(std::back_inserter(result), + fmt::runtime(left_justified_flag ? "{0:<{1}}" : "{0:>{1}}"), + UTF8ToSHIFTJIS(UTF16ToUTF8(std::u16string_view(&wide_char, 1))), + field_width.value_or(0)); + } + else + { + fmt::format_to(std::back_inserter(result), + fmt::runtime(left_justified_flag ? "{0:<{1}}" : "{0:>{1}}"), + static_cast(value), field_width.value_or(0)); + } + break; + } + case 'd': + case 'i': + { + const auto options = fmt::format( + "{}{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", padding_zeroes_flag ? "0" : "", + field_width ? fmt::format("{}", *field_width) : "", + precision ? fmt::format(".{}", *precision) : ""); + if (length_modifier == LengthModifier::ll) + { + const s64 value = Common::BitCast(args->GetU64()); + result += fmt::sprintf(fmt::format("%{}" PRId64, options).c_str(), value); + } + else + { + s32 value = Common::BitCast(args->GetU32()); + if (length_modifier == LengthModifier::h) + value = static_cast(value); + else if (length_modifier == LengthModifier::hh) + value = static_cast(value); + result += fmt::sprintf(fmt::format("%{}" PRId32, options).c_str(), value); + } + break; + } + case 'o': + { + const auto options = fmt::format( + "{}{}{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", alternative_form_flag ? "#" : "", + padding_zeroes_flag ? "0" : "", field_width ? fmt::format("{}", *field_width) : "", + precision ? fmt::format(".{}", *precision) : ""); + if (length_modifier == LengthModifier::ll) + { + const u64 value = args->GetU64(); + result += fmt::sprintf(fmt::format("%{}" PRIo64, options).c_str(), value); + } + else + { + u32 value = args->GetU32(); + if (length_modifier == LengthModifier::h) + value = static_cast(value); + else if (length_modifier == LengthModifier::hh) + value = static_cast(value); + result += fmt::sprintf(fmt::format("%{}" PRIo32, options).c_str(), value); + } + break; + } + case 'x': + case 'X': + { + const auto options = fmt::format( + "{}{}{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", alternative_form_flag ? "#" : "", + padding_zeroes_flag ? "0" : "", field_width ? fmt::format("{}", *field_width) : "", + precision ? fmt::format(".{}", *precision) : ""); + if (length_modifier == LengthModifier::ll) + { + const u64 value = args->GetU64(); + result += fmt::sprintf( + fmt::format("%{}{}", options, format_specifier == 'x' ? PRIx64 : PRIX64).c_str(), + value); + } + else + { + u32 value = args->GetU32(); + if (length_modifier == LengthModifier::h) + value = static_cast(value); + else if (length_modifier == LengthModifier::hh) + value = static_cast(value); + result += fmt::sprintf( + fmt::format("%{}{}", options, format_specifier == 'x' ? PRIx32 : PRIX32).c_str(), + value); + } + break; + } + case 'u': + { + const auto options = fmt::format( + "{}{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", padding_zeroes_flag ? "0" : "", + field_width ? fmt::format("{}", *field_width) : "", + precision ? fmt::format(".{}", *precision) : ""); + if (length_modifier == LengthModifier::ll) + { + const u64 value = args->GetU64(); + result += fmt::sprintf(fmt::format("%{}" PRIu64, options).c_str(), value); + } + else + { + u32 value = args->GetU32(); + if (length_modifier == LengthModifier::h) + value = static_cast(value); + else if (length_modifier == LengthModifier::hh) + value = static_cast(value); + result += fmt::sprintf(fmt::format("%{}" PRIu32, options).c_str(), value); + } + break; + } + case 'f': + case 'F': + case 'e': + case 'E': + case 'a': + case 'A': + case 'g': + case 'G': + { + const auto options = fmt::format( + "{}{}{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", alternative_form_flag ? "#" : "", + padding_zeroes_flag ? "0" : "", field_width ? fmt::format("{}", *field_width) : "", + precision ? fmt::format(".{}", *precision) : ""); + double value = args->GetF64(); + result += fmt::sprintf(fmt::format("%{}{}", options, format_specifier).c_str(), value); + break; + } + case 'n': + // %n doesn't output anything, so the result variable is untouched + // the actual PPC function will take care of the memory write + break; + case 'p': + { + const auto options = + fmt::format("{}{}{}{}{}", left_justified_flag ? "-" : "", sign_prepended_flag ? "+" : "", + space_prepended_flag ? " " : "", padding_zeroes_flag ? "0" : "", + field_width ? fmt::format("{}", *field_width) : ""); + const u32 value = args->GetU32(); + result += fmt::sprintf(fmt::format("%{}" PRIx32, options).c_str(), value); + break; + } + default: + // invalid conversion specifier, print the formatting string as-is + result += string.substr(formatting_start_position, formatting_start_position - i + 1); + break; } } diff --git a/Source/Core/Core/HLE/HLE_OS.h b/Source/Core/Core/HLE/HLE_OS.h index 6e13110b9b..e53053f923 100644 --- a/Source/Core/Core/HLE/HLE_OS.h +++ b/Source/Core/Core/HLE/HLE_OS.h @@ -3,6 +3,11 @@ #pragma once +#include +#include + +#include "Common/CommonTypes.h" + namespace Core { class CPUThreadGuard; @@ -10,6 +15,18 @@ class CPUThreadGuard; namespace HLE_OS { +class HLEPrintArgs +{ +public: + virtual u32 GetU32() = 0; + virtual u64 GetU64() = 0; + virtual double GetF64() = 0; + virtual std::string GetString(std::optional max_length) = 0; + virtual std::u16string GetU16String(std::optional max_length) = 0; +}; + +std::string GetStringVA(HLEPrintArgs* args, std::string_view string); + void HLE_GeneralDebugPrint(const Core::CPUThreadGuard& guard); void HLE_GeneralDebugVPrint(const Core::CPUThreadGuard& guard); void HLE_write_console(const Core::CPUThreadGuard& guard); diff --git a/Source/Core/Core/HLE/HLE_VarArgs.cpp b/Source/Core/Core/HLE/HLE_VarArgs.cpp index 4c792e973e..faef168943 100644 --- a/Source/Core/Core/HLE/HLE_VarArgs.cpp +++ b/Source/Core/Core/HLE/HLE_VarArgs.cpp @@ -2,29 +2,29 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HLE/HLE_VarArgs.h" +#include "Core/Core.h" #include "Core/System.h" #include "Common/Logging/Log.h" HLE::SystemVABI::VAList::~VAList() = default; -u32 HLE::SystemVABI::VAList::GetGPR(const Core::CPUThreadGuard&, u32 gpr) const +u32 HLE::SystemVABI::VAList::GetGPR(u32 gpr) const { - return m_system.GetPPCState().gpr[gpr]; + return m_guard.GetSystem().GetPPCState().gpr[gpr]; } -double HLE::SystemVABI::VAList::GetFPR(const Core::CPUThreadGuard&, u32 fpr) const +double HLE::SystemVABI::VAList::GetFPR(u32 fpr) const { - return m_system.GetPPCState().ps[fpr].PS0AsDouble(); + return m_guard.GetSystem().GetPPCState().ps[fpr].PS0AsDouble(); } -HLE::SystemVABI::VAListStruct::VAListStruct(Core::System& system, const Core::CPUThreadGuard& guard, - u32 address) - : VAList(system, 0), m_va_list{PowerPC::MMU::HostRead_U8(guard, address), - PowerPC::MMU::HostRead_U8(guard, address + 1), - PowerPC::MMU::HostRead_U32(guard, address + 4), - PowerPC::MMU::HostRead_U32(guard, address + 8)}, - m_address(address), m_has_fpr_area(system.GetPPCState().cr.GetBit(6) == 1) +HLE::SystemVABI::VAListStruct::VAListStruct(const Core::CPUThreadGuard& guard, u32 address) + : VAList(guard, 0), m_va_list{PowerPC::MMU::HostRead_U8(guard, address), + PowerPC::MMU::HostRead_U8(guard, address + 1), + PowerPC::MMU::HostRead_U32(guard, address + 4), + PowerPC::MMU::HostRead_U32(guard, address + 8)}, + m_address(address), m_has_fpr_area(guard.GetSystem().GetPPCState().cr.GetBit(6) == 1) { m_stack = m_va_list.overflow_arg_area; m_gpr += m_va_list.gpr; @@ -41,7 +41,7 @@ u32 HLE::SystemVABI::VAListStruct::GetFPRArea() const return GetGPRArea() + 4 * 8; } -u32 HLE::SystemVABI::VAListStruct::GetGPR(const Core::CPUThreadGuard& guard, u32 gpr) const +u32 HLE::SystemVABI::VAListStruct::GetGPR(u32 gpr) const { if (gpr < 3 || gpr > 10) { @@ -49,10 +49,10 @@ u32 HLE::SystemVABI::VAListStruct::GetGPR(const Core::CPUThreadGuard& guard, u32 return 0; } const u32 gpr_address = Common::AlignUp(GetGPRArea() + 4 * (gpr - 3), 4); - return PowerPC::MMU::HostRead_U32(guard, gpr_address); + return PowerPC::MMU::HostRead_U32(m_guard, gpr_address); } -double HLE::SystemVABI::VAListStruct::GetFPR(const Core::CPUThreadGuard& guard, u32 fpr) const +double HLE::SystemVABI::VAListStruct::GetFPR(u32 fpr) const { if (!m_has_fpr_area || fpr < 1 || fpr > 8) { @@ -60,5 +60,5 @@ double HLE::SystemVABI::VAListStruct::GetFPR(const Core::CPUThreadGuard& guard, return 0.0; } const u32 fpr_address = Common::AlignUp(GetFPRArea() + 8 * (fpr - 1), 8); - return PowerPC::MMU::HostRead_F64(guard, fpr_address); + return PowerPC::MMU::HostRead_F64(m_guard, fpr_address); } diff --git a/Source/Core/Core/HLE/HLE_VarArgs.h b/Source/Core/Core/HLE/HLE_VarArgs.h index eecf231425..aac61ef74b 100644 --- a/Source/Core/Core/HLE/HLE_VarArgs.h +++ b/Source/Core/Core/HLE/HLE_VarArgs.h @@ -3,14 +3,14 @@ #pragma once +#include + #include "Common/Align.h" #include "Common/CommonTypes.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" -#include - namespace Core { class CPUThreadGuard; @@ -38,9 +38,9 @@ constexpr bool IS_ARG_REAL = std::is_floating_point(); class VAList { public: - explicit VAList(Core::System& system, u32 stack, u32 gpr = 3, u32 fpr = 1, u32 gpr_max = 10, - u32 fpr_max = 8) - : m_system(system), m_gpr(gpr), m_fpr(fpr), m_gpr_max(gpr_max), m_fpr_max(fpr_max), + explicit VAList(const Core::CPUThreadGuard& guard, u32 stack, u32 gpr = 3, u32 fpr = 1, + u32 gpr_max = 10, u32 fpr_max = 8) + : m_guard(guard), m_gpr(gpr), m_fpr(fpr), m_gpr_max(gpr_max), m_fpr_max(fpr_max), m_stack(stack) { } @@ -48,14 +48,14 @@ public: // 0 - arg_ARGPOINTER template >* = nullptr> - T GetArg(const Core::CPUThreadGuard& guard) + T GetArg() { T obj; - u32 addr = GetArg(guard); + u32 addr = GetArg(); for (size_t i = 0; i < sizeof(T); i += 1, addr += 1) { - reinterpret_cast(&obj)[i] = PowerPC::MMU::HostRead_U8(guard, addr); + reinterpret_cast(&obj)[i] = PowerPC::MMU::HostRead_U8(m_guard, addr); } return obj; @@ -63,20 +63,20 @@ public: // 1 - arg_WORD template >* = nullptr> - T GetArg(const Core::CPUThreadGuard& guard) + T GetArg() { static_assert(!std::is_pointer(), "VAList doesn't support pointers"); u64 value; if (m_gpr <= m_gpr_max) { - value = GetGPR(guard, m_gpr); + value = GetGPR(m_gpr); m_gpr += 1; } else { m_stack = Common::AlignUp(m_stack, 4); - value = PowerPC::MMU::HostRead_U32(guard, m_stack); + value = PowerPC::MMU::HostRead_U32(m_guard, m_stack); m_stack += 4; } @@ -85,7 +85,7 @@ public: // 2 - arg_DOUBLEWORD template >* = nullptr> - T GetArg(const Core::CPUThreadGuard& guard) + T GetArg() { u64 value; @@ -93,13 +93,13 @@ public: m_gpr += 1; if (m_gpr < m_gpr_max) { - value = static_cast(GetGPR(guard, m_gpr)) << 32 | GetGPR(guard, m_gpr + 1); + value = static_cast(GetGPR(m_gpr)) << 32 | GetGPR(m_gpr + 1); m_gpr += 2; } else { m_stack = Common::AlignUp(m_stack, 8); - value = PowerPC::MMU::HostRead_U64(guard, m_stack); + value = PowerPC::MMU::HostRead_U64(m_guard, m_stack); m_stack += 8; } @@ -108,19 +108,19 @@ public: // 3 - arg_ARGREAL template >* = nullptr> - T GetArg(const Core::CPUThreadGuard& guard) + T GetArg() { double value; if (m_fpr <= m_fpr_max) { - value = GetFPR(guard, m_fpr); + value = GetFPR(m_fpr); m_fpr += 1; } else { m_stack = Common::AlignUp(m_stack, 8); - value = PowerPC::MMU::HostRead_F64(guard, m_stack); + value = PowerPC::MMU::HostRead_F64(m_guard, m_stack); m_stack += 8; } @@ -129,13 +129,13 @@ public: // Helper template - T GetArgT(const Core::CPUThreadGuard& guard) + T GetArgT() { - return static_cast(GetArg(guard)); + return static_cast(GetArg()); } protected: - Core::System& m_system; + const Core::CPUThreadGuard& m_guard; u32 m_gpr = 3; u32 m_fpr = 1; const u32 m_gpr_max = 10; @@ -143,8 +143,8 @@ protected: u32 m_stack; private: - virtual u32 GetGPR(const Core::CPUThreadGuard& guard, u32 gpr) const; - virtual double GetFPR(const Core::CPUThreadGuard& guard, u32 fpr) const; + virtual u32 GetGPR(u32 gpr) const; + virtual double GetFPR(u32 fpr) const; }; // See System V ABI (SVR4) for more details @@ -156,7 +156,7 @@ private: class VAListStruct : public VAList { public: - explicit VAListStruct(Core::System& system, const Core::CPUThreadGuard& guard, u32 address); + explicit VAListStruct(const Core::CPUThreadGuard& guard, u32 address); ~VAListStruct() = default; private: @@ -174,8 +174,8 @@ private: u32 GetGPRArea() const; u32 GetFPRArea() const; - u32 GetGPR(const Core::CPUThreadGuard& guard, u32 gpr) const override; - double GetFPR(const Core::CPUThreadGuard& guard, u32 fpr) const override; + u32 GetGPR(u32 gpr) const override; + double GetFPR(u32 fpr) const override; }; } // namespace HLE::SystemVABI