Merge pull request #11025 from AdmiralCurtiss/hle-printf

HLE_OS: Manually handle printfs from emulated software to prevent emulated software from crashing Dolphin with an invalid printf formatting string.
This commit is contained in:
Admiral H. Curtiss 2023-08-20 01:31:49 +02:00 committed by GitHub
commit f19651e49b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 429 additions and 105 deletions

View File

@ -3,9 +3,15 @@
#include "Core/HLE/HLE_OS.h"
#include <cinttypes>
#include <memory>
#include <string>
#include <string_view>
#include <fmt/format.h>
#include <fmt/printf.h>
#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<u32>(); }
u64 GetU64() override { return m_va_list->GetArgT<u64>(); }
double GetF64() override { return m_va_list->GetArgT<double>(); }
std::string GetString(std::optional<u32> max_length) override
{
return max_length == 0u ? std::string() :
PowerPC::MMU::HostGetString(m_guard, m_va_list->GetArgT<u32>(),
max_length.value_or(0u));
}
std::u16string GetU16String(std::optional<u32> max_length) override
{
return max_length == 0u ? std::u16string() :
PowerPC::MMU::HostGetU16String(m_guard, m_va_list->GetArgT<u32>(),
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<HLE::SystemVABI::VAList> ap =
parameter_type == ParameterType::VariableArgumentList ?
std::make_unique<HLE::SystemVABI::VAListStruct>(system, guard,
ppc_state.gpr[str_reg + 1]) :
std::make_unique<HLE::SystemVABI::VAList>(system, ppc_state.gpr[1] + 0x8, str_reg + 1);
std::make_unique<HLE::SystemVABI::VAListStruct>(guard, ppc_state.gpr[str_reg + 1]) :
std::make_unique<HLE::SystemVABI::VAList>(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<u32>(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<double>(guard));
break;
case 'p':
// Override, so 64bit Dolphin prints 32bit pointers, since the ppc is 32bit :)
result += StringFromFormat("%x", ap->GetArgT<u32>(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<u64>(guard));
else
result += StringFromFormat(ArgumentBuffer.c_str(), ap->GetArgT<u32>(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<u32> {
if (i >= string.size())
return std::nullopt;
if (string[i] == '*')
{
++i;
const s32 result = Common::BitCast<s32>(args->GetU32());
if (result >= 0)
return static_cast<u32>(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<u32>(-static_cast<s64>(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<u32> field_width = take_field_or_precision(&left_justified_flag);
std::optional<u32> 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<s32>(args->GetU32());
if (length_modifier == LengthModifier::l)
{
// Same problem as with wide strings here.
const char16_t wide_char = static_cast<char16_t>(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<char>(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<s64>(args->GetU64());
result += fmt::sprintf(fmt::format("%{}" PRId64, options).c_str(), value);
}
else
{
s32 value = Common::BitCast<s32>(args->GetU32());
if (length_modifier == LengthModifier::h)
value = static_cast<s16>(value);
else if (length_modifier == LengthModifier::hh)
value = static_cast<s8>(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<u16>(value);
else if (length_modifier == LengthModifier::hh)
value = static_cast<u8>(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<u16>(value);
else if (length_modifier == LengthModifier::hh)
value = static_cast<u8>(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<u16>(value);
else if (length_modifier == LengthModifier::hh)
value = static_cast<u8>(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;
}
}

View File

@ -3,6 +3,11 @@
#pragma once
#include <optional>
#include <string>
#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<u32> max_length) = 0;
virtual std::u16string GetU16String(std::optional<u32> 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);

View File

@ -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);
}

View File

@ -3,14 +3,14 @@
#pragma once
#include <type_traits>
#include "Common/Align.h"
#include "Common/CommonTypes.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
#include <type_traits>
namespace Core
{
class CPUThreadGuard;
@ -38,9 +38,9 @@ constexpr bool IS_ARG_REAL = std::is_floating_point<T>();
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 <typename T, typename std::enable_if_t<IS_ARG_POINTER<T>>* = nullptr>
T GetArg(const Core::CPUThreadGuard& guard)
T GetArg()
{
T obj;
u32 addr = GetArg<u32>(guard);
u32 addr = GetArg<u32>();
for (size_t i = 0; i < sizeof(T); i += 1, addr += 1)
{
reinterpret_cast<u8*>(&obj)[i] = PowerPC::MMU::HostRead_U8(guard, addr);
reinterpret_cast<u8*>(&obj)[i] = PowerPC::MMU::HostRead_U8(m_guard, addr);
}
return obj;
@ -63,20 +63,20 @@ public:
// 1 - arg_WORD
template <typename T, typename std::enable_if_t<IS_WORD<T>>* = nullptr>
T GetArg(const Core::CPUThreadGuard& guard)
T GetArg()
{
static_assert(!std::is_pointer<T>(), "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 <typename T, typename std::enable_if_t<IS_DOUBLE_WORD<T>>* = 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<u64>(GetGPR(guard, m_gpr)) << 32 | GetGPR(guard, m_gpr + 1);
value = static_cast<u64>(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 <typename T, typename std::enable_if_t<IS_ARG_REAL<T>>* = 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 <typename T>
T GetArgT(const Core::CPUThreadGuard& guard)
T GetArgT()
{
return static_cast<T>(GetArg<T>(guard));
return static_cast<T>(GetArg<T>());
}
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

View File

@ -867,6 +867,22 @@ std::string MMU::HostGetString(const Core::CPUThreadGuard& guard, u32 address, s
return s;
}
std::u16string MMU::HostGetU16String(const Core::CPUThreadGuard& guard, u32 address, size_t size)
{
std::u16string s;
do
{
if (!HostIsRAMAddress(guard, address) || !HostIsRAMAddress(guard, address + 1))
break;
const u16 res = HostRead_U16(guard, address);
if (!res)
break;
s += static_cast<char16_t>(res);
address += 2;
} while (size == 0 || s.length() < size);
return s;
}
std::optional<ReadResult<std::string>> MMU::HostTryReadString(const Core::CPUThreadGuard& guard,
u32 address, size_t size,
RequestedAddressSpace space)

View File

@ -132,6 +132,8 @@ public:
static double HostRead_F64(const Core::CPUThreadGuard& guard, u32 address);
static u32 HostRead_Instruction(const Core::CPUThreadGuard& guard, u32 address);
static std::string HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t size = 0);
static std::u16string HostGetU16String(const Core::CPUThreadGuard& guard, u32 address,
size_t size = 0);
// Try to read a value from emulated memory at the given address in the given memory space.
// If the read succeeds, the returned value will be present and the ReadResult contains the read