Files
wiicompiled/runtime/src/hle/c_stdio.cpp
T
patchzyy ec226e8348 init
2026-08-23 17:10:50 +02:00

272 lines
8.3 KiB
C++

#include "hle_stubs.h"
#include "hle/guest_printf.h"
#include "memory.h"
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
// Hardware boundary used by the translated MetroWerks stdio implementation.
// The original routine forwards completed FILE-buffer writes to UART/EXI or TRK.
// Keep __FileWrite (0x80011620), __fwrite, and fwide translated so their buffering,
// orientation, short-write, and return-value semantics remain guest-owned.
extern "C" uint32_t ConsoleWrite_HLE_80015500(
uint32_t handle, uint32_t bufferAddr, uint32_t lengthPtr, uint32_t refCon)
{
(void)handle;
(void)refCon;
if (!lengthPtr) {
return 1;
}
try {
const uint32_t length = Memory::Read32(lengthPtr);
if (length == 0) {
return 0;
}
const uint8_t* data = Memory::GetPointer(bufferAddr, length);
if (!data) {
Memory::Write32(lengthPtr, 0);
return 1;
}
return 0;
} catch (const Memory::AccessViolation&) {
try {
Memory::Write32(lengthPtr, 0);
} catch (const Memory::AccessViolation&) {
}
return 1;
}
}
PPC_NATIVE_OVERRIDE(80015500, ConsoleWrite_HLE_80015500, uint32_t,
(uint32_t handle, uint32_t bufferAddr, uint32_t lengthPtr, uint32_t refCon),
(handle, bufferAddr, lengthPtr, refCon));
// --------------------------------------------------------------------------
// Shared guest printf formatter used by CPU-context stdio and OSReport paths.
// --------------------------------------------------------------------------
std::string RuntimeHle::FormatGuestPrintf(const std::string& fmt,
size_t& outChars,
const PrintfNext32& next32,
const PrintfNext64& next64,
const PrintfNextDouble& nextDouble,
const PrintfReadString& readString)
{
std::ostringstream out;
outChars = 0;
auto appendText = [&](const std::string& text) {
out << text;
outChars += text.size();
};
auto appendChar = [&](char ch) {
out.put(ch);
++outChars;
};
size_t i = 0;
while (i < fmt.size()) {
if (fmt[i] != '%') {
appendChar(fmt[i]);
++i;
continue;
}
// Handle escaped %%
if (i + 1 < fmt.size() && fmt[i + 1] == '%') {
appendChar('%');
i += 2;
continue;
}
size_t cur = i + 1;
std::string flags;
while (cur < fmt.size() && std::strchr("-+ #0", fmt[cur])) {
flags.push_back(fmt[cur]);
++cur;
}
bool widthFromArg = false;
int width = -1;
if (cur < fmt.size() && fmt[cur] == '*') {
widthFromArg = true;
++cur;
} else {
int parsed = 0;
bool seen = false;
while (cur < fmt.size() && std::isdigit(static_cast<unsigned char>(fmt[cur]))) {
seen = true;
parsed = (parsed * 10) + (fmt[cur] - '0');
++cur;
}
if (seen) {
width = parsed;
}
}
bool precisionFromArg = false;
int precision = -1;
if (cur < fmt.size() && fmt[cur] == '.') {
++cur;
if (cur < fmt.size() && fmt[cur] == '*') {
precisionFromArg = true;
++cur;
} else {
int parsed = 0;
bool seen = false;
while (cur < fmt.size() && std::isdigit(static_cast<unsigned char>(fmt[cur]))) {
seen = true;
parsed = (parsed * 10) + (fmt[cur] - '0');
++cur;
}
precision = seen ? parsed : 0; // "%.s" -> precision 0
}
}
std::string length;
if (cur < fmt.size()) {
if (fmt[cur] == 'h') {
length.push_back('h');
++cur;
if (cur < fmt.size() && fmt[cur] == 'h') {
length.push_back('h');
++cur;
}
} else if (fmt[cur] == 'l') {
length.push_back('l');
++cur;
if (cur < fmt.size() && fmt[cur] == 'l') {
length.push_back('l');
++cur;
}
} else if (fmt[cur] == 'z' || fmt[cur] == 't') {
length.push_back(fmt[cur]);
++cur;
}
}
if (widthFromArg) {
width = static_cast<int32_t>(next32());
if (width < 0) {
flags.push_back('-');
width = -width;
}
}
if (precisionFromArg) {
precision = static_cast<int32_t>(next32());
if (precision < 0) {
precision = -1; // Negative precision is treated as if it's omitted.
}
}
if (cur >= fmt.size()) {
appendText(fmt.substr(i));
break;
}
char spec = fmt[cur];
++cur;
std::ostringstream pieceBuilder;
pieceBuilder << "%";
if (!flags.empty()) pieceBuilder << flags;
if (width >= 0) pieceBuilder << width;
if (precision >= 0) pieceBuilder << "." << precision;
pieceBuilder << length << spec;
const std::string piece = pieceBuilder.str();
auto appendWithSnprintf = [&](auto value) {
int needed = std::snprintf(nullptr, 0, piece.c_str(), value);
if (needed <= 0) {
return;
}
std::string buf(static_cast<size_t>(needed) + 1, '\0');
std::snprintf(buf.data(), buf.size(), piece.c_str(), value);
buf.resize(static_cast<size_t>(needed));
appendText(buf);
};
switch (spec) {
case 's': {
uint32_t ptr = next32();
std::string guest = readString(ptr);
appendWithSnprintf(guest.c_str());
break;
}
case 'c': {
char ch = static_cast<char>(next32() & 0xFF);
appendWithSnprintf(ch);
break;
}
case 'p': {
uint32_t ptr = next32();
appendWithSnprintf(reinterpret_cast<void*>(static_cast<uintptr_t>(ptr)));
break;
}
case 'd':
case 'i': {
if (length == "ll") {
int64_t v = static_cast<int64_t>(next64());
appendWithSnprintf(v);
} else {
int32_t v = static_cast<int32_t>(next32());
appendWithSnprintf(v);
}
break;
}
case 'u':
case 'x':
case 'X':
case 'o': {
if (length == "ll") {
uint64_t v = next64();
appendWithSnprintf(v);
} else {
uint32_t v = next32();
appendWithSnprintf(v);
}
break;
}
case 'f': case 'F':
case 'e': case 'E':
case 'g': case 'G':
case 'a': case 'A': {
double v = nextDouble();
appendWithSnprintf(v);
break;
}
case 'n': {
uint32_t ptr = next32();
if (ptr != 0) {
Memory::Write32(ptr, static_cast<uint32_t>(outChars));
}
break;
}
default: {
// Unknown specifier, copy literally so we don't drop information.
appendText(fmt.substr(i, cur - i));
break;
}
}
i = cur;
}
return out.str();
}
// 0x801D09CC is WUD_DEBUGPrint in the PAL executable. Its retail body only
// performs the compiler-generated variadic prologue and returns; it does not
// format or emit text. Keep it on the ordinary translated path so its guest
// memory effects remain exact without inventing expensive host-side logging.