mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #11054 from verusferro/feat/libc-num-put-do-put
feat(libc): format num_put<char>::do_put as the console's libc does
This commit is contained in:
4 files changed
+416
-12
No files matched your search
@@ -5,6 +5,8 @@
|
||||
#include <cstdint>
|
||||
#include "general/VabiMacros.hpp"
|
||||
|
||||
struct GuestTm;
|
||||
|
||||
namespace GuestLocale {
|
||||
|
||||
struct LocinfoStorage {
|
||||
@@ -87,11 +89,6 @@ struct NumpunctVtable {
|
||||
String* (APS5_VABI *truename)(String* result, const NumpunctFacet* self);
|
||||
};
|
||||
|
||||
struct UnsupportedMethodsVtable {
|
||||
FacetVtable facet;
|
||||
void (APS5_VABI *methods[8])();
|
||||
};
|
||||
|
||||
struct IosBase {
|
||||
const void* vtable;
|
||||
std::int64_t standardStream;
|
||||
@@ -106,6 +103,58 @@ struct IosBase {
|
||||
Implementation** locale;
|
||||
};
|
||||
|
||||
struct Streambuf;
|
||||
|
||||
struct StreambufVtable {
|
||||
const void* reserved[4];
|
||||
int (APS5_VABI *overflow)(Streambuf* self, int character);
|
||||
};
|
||||
|
||||
struct Streambuf {
|
||||
const StreambufVtable* vtable;
|
||||
const void* reserved[7];
|
||||
char** putNext;
|
||||
const void* reservedCounts[3];
|
||||
char** putEnd;
|
||||
};
|
||||
|
||||
struct OutputIterator {
|
||||
bool failed;
|
||||
Streambuf* buffer;
|
||||
};
|
||||
|
||||
struct TimePutVtable {
|
||||
FacetVtable facet;
|
||||
OutputIterator (APS5_VABI *put)(const Facet* self, OutputIterator out, IosBase* stream, char fill, const GuestTm* time, char format, char modifier);
|
||||
};
|
||||
|
||||
struct NumPutVtable {
|
||||
FacetVtable facet;
|
||||
OutputIterator (APS5_VABI *putBool)(const Facet* self, OutputIterator out, IosBase* stream, char fill, bool value);
|
||||
OutputIterator (APS5_VABI *putLong)(const Facet* self, OutputIterator out, IosBase* stream, char fill, std::int64_t value);
|
||||
OutputIterator (APS5_VABI *putUnsignedLong)(const Facet* self, OutputIterator out, IosBase* stream, char fill, std::uint64_t value);
|
||||
OutputIterator (APS5_VABI *putLongLong)(const Facet* self, OutputIterator out, IosBase* stream, char fill, std::int64_t value);
|
||||
OutputIterator (APS5_VABI *putUnsignedLongLong)(const Facet* self, OutputIterator out, IosBase* stream, char fill, std::uint64_t value);
|
||||
OutputIterator (APS5_VABI *putDouble)(const Facet* self, OutputIterator out, IosBase* stream, char fill, double value);
|
||||
OutputIterator (APS5_VABI *putLongDouble)(const Facet* self, OutputIterator out, IosBase* stream, char fill, long double value);
|
||||
OutputIterator (APS5_VABI *putPointer)(const Facet* self, OutputIterator out, IosBase* stream, char fill, const void* value);
|
||||
};
|
||||
|
||||
struct CtypeVtable {
|
||||
FacetVtable facet;
|
||||
const void* caseMethods[4];
|
||||
char (APS5_VABI *widen)(const Facet* self, char character);
|
||||
const char* (APS5_VABI *widenRange)(const Facet* self, const char* first, const char* last, char* destination);
|
||||
};
|
||||
|
||||
static_assert(offsetof(StreambufVtable, overflow) == 0x20);
|
||||
static_assert(offsetof(Streambuf, putNext) == 0x40);
|
||||
static_assert(offsetof(Streambuf, putEnd) == 0x60);
|
||||
static_assert(sizeof(OutputIterator) == 0x10);
|
||||
static_assert(offsetof(TimePutVtable, put) == 0x20);
|
||||
static_assert(offsetof(NumPutVtable, putBool) == 0x20);
|
||||
static_assert(offsetof(NumPutVtable, putPointer) == 0x58);
|
||||
static_assert(offsetof(CtypeVtable, widenRange) == 0x48);
|
||||
static_assert(sizeof(Facet) == 0x10);
|
||||
static_assert(offsetof(Implementation, facets) == 0x10);
|
||||
static_assert(offsetof(Implementation, facetCount) == 0x18);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <clocale>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
@@ -25,6 +27,12 @@
|
||||
#include "prx/libc/include/GuestLocale.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
|
||||
extern "C" {
|
||||
extern std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix;
|
||||
extern std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix;
|
||||
extern std::int32_t _ZNSt6locale2id7_Id_cntE_nid_postfix;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex g_localeInitMutex;
|
||||
@@ -281,16 +289,230 @@ GuestLocale::String* APS5_VABI NumpunctTruename(GuestLocale::String* result, con
|
||||
|
||||
const GuestLocale::NumpunctVtable g_numpunctVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, NumpunctDecimalPoint, NumpunctThousandsSep, NumpunctGrouping, NumpunctFalsename, NumpunctTruename};
|
||||
|
||||
void APS5_VABI NumPutMethodUnsupported() {
|
||||
NotImplemented_nid_no_patch("num_put<char>::do_put");
|
||||
void PutCharacter(GuestLocale::OutputIterator& out, char character) {
|
||||
GuestLocale::Streambuf* buffer = out.buffer;
|
||||
if (buffer == nullptr) {
|
||||
out.failed = true;
|
||||
return;
|
||||
}
|
||||
char* next = *buffer->putNext;
|
||||
if (next != nullptr && static_cast<std::int32_t>(static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(*buffer->putEnd) - reinterpret_cast<std::uintptr_t>(next))) > 0) {
|
||||
*buffer->putNext = next + 1;
|
||||
*next = character;
|
||||
return;
|
||||
}
|
||||
if (buffer->vtable->overflow(buffer, static_cast<unsigned char>(character)) == -1) out.failed = true;
|
||||
}
|
||||
|
||||
void APS5_VABI TimePutMethodUnsupported() {
|
||||
NotImplemented_nid_no_patch("time_put<char>::do_put");
|
||||
GuestLocale::OutputIterator APS5_VABI TimePutDoPut(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase*, char, const GuestTm* time, char format, char modifier) {
|
||||
const char pattern[] = {'!', '%', modifier != 0 ? modifier : format, modifier != 0 ? format : '\0', '\0'};
|
||||
std::string text;
|
||||
std::size_t length = 0;
|
||||
for (std::size_t size = 16; length == 0; size *= 2) {
|
||||
text.resize(size);
|
||||
length = strftime_nid_postfix(text.data(), size, pattern, time);
|
||||
}
|
||||
for (std::size_t index = 1; index < length; ++index) PutCharacter(out, text[index]);
|
||||
return out;
|
||||
}
|
||||
|
||||
const GuestLocale::UnsupportedMethodsVtable g_numPutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported}};
|
||||
const GuestLocale::UnsupportedMethodsVtable g_timePutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported}};
|
||||
constexpr std::int32_t IosLeft = 0x2;
|
||||
constexpr std::int32_t IosInternal = 0x8;
|
||||
constexpr std::int32_t IosAdjustField = 0xe;
|
||||
constexpr std::int32_t IosOct = 0x20;
|
||||
constexpr std::int32_t IosHex = 0x40;
|
||||
constexpr std::int32_t IosBaseField = 0x70;
|
||||
constexpr std::int32_t IosShowBase = 0x80;
|
||||
constexpr std::int32_t IosShowPoint = 0x100;
|
||||
constexpr std::int32_t IosUppercase = 0x200;
|
||||
constexpr std::int32_t IosShowPos = 0x400;
|
||||
constexpr std::int32_t IosScientific = 0x800;
|
||||
constexpr std::int32_t IosFixed = 0x1000;
|
||||
constexpr std::int32_t IosHexFloat = 0x1800;
|
||||
constexpr std::int32_t IosBoolAlpha = 0x8000;
|
||||
|
||||
const GuestLocale::Facet* FindFacet(const GuestLocale::IosBase* stream, std::uint64_t& id) {
|
||||
std::atomic_ref<std::uint64_t> slot(id);
|
||||
if (slot.load() == 0) {
|
||||
_Locksyslock_nid_postfix();
|
||||
if (slot.load() == 0) slot.store(static_cast<std::uint64_t>(++_ZNSt6locale2id7_Id_cntE_nid_postfix));
|
||||
_Unlocksyslock_nid_postfix();
|
||||
}
|
||||
const std::uint64_t index = slot.load();
|
||||
const GuestLocale::Implementation* locale = *stream->locale;
|
||||
const GuestLocale::Facet* facet = index < locale->facetCount ? locale->facets[index] : nullptr;
|
||||
if (facet != nullptr || !locale->transparent) return facet;
|
||||
const GuestLocale::Implementation* global = _ZNSt6locale16_GetgloballocaleEv_nid_postfix();
|
||||
return index < global->facetCount ? global->facets[index] : nullptr;
|
||||
}
|
||||
|
||||
const GuestLocale::Facet* StreamNumpunct(const GuestLocale::IosBase* stream) {
|
||||
if (const GuestLocale::Facet* facet = FindFacet(stream, _ZNSt8numpunctIcE2idE_nid_postfix)) return facet;
|
||||
static GuestLocale::Facet* const fallback = [] {
|
||||
GuestLocale::Facet* created = nullptr;
|
||||
_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&created, nullptr);
|
||||
return created;
|
||||
}();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const GuestLocale::NumpunctVtable& Punctuation(const GuestLocale::Facet* facet) {
|
||||
return *reinterpret_cast<const GuestLocale::NumpunctVtable*>(facet->vtable);
|
||||
}
|
||||
|
||||
const GuestLocale::NumpunctFacet* AsNumpunct(const GuestLocale::Facet* facet) {
|
||||
return reinterpret_cast<const GuestLocale::NumpunctFacet*>(facet);
|
||||
}
|
||||
|
||||
std::string TakeString(GuestLocale::String* (APS5_VABI* method)(GuestLocale::String*, const GuestLocale::NumpunctFacet*), const GuestLocale::Facet* facet) {
|
||||
GuestLocale::String value{};
|
||||
method(&value, AsNumpunct(facet));
|
||||
const bool onHeap = value.capacity > GuestStringInlineCapacity;
|
||||
std::string text(onHeap ? value.pointer : value.buffer, value.size);
|
||||
if (onHeap) _ZdlPv_nid_postfix(value.capacity + 1 < GuestStringLargeAllocation ? static_cast<void*>(value.pointer) : reinterpret_cast<void**>(value.pointer)[-1]);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string Widen(const GuestLocale::IosBase* stream, const char* text, std::size_t count) {
|
||||
std::string result(text, count);
|
||||
if (const GuestLocale::Facet* ctype = FindFacet(stream, _ZNSt5ctypeIcE2idE_nid_postfix)) reinterpret_cast<const GuestLocale::CtypeVtable*>(ctype->vtable)->widenRange(ctype, text, text + count, result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HexPrefix(const char* text, std::size_t prefix, std::size_t count) {
|
||||
return prefix + 2 <= count && text[prefix] == '0' && (text[prefix + 1] == 'x' || text[prefix + 1] == 'X');
|
||||
}
|
||||
|
||||
std::size_t SignPrefix(const char* text, std::size_t count) {
|
||||
return count > 0 && (text[0] == '+' || text[0] == '-') ? 1 : 0;
|
||||
}
|
||||
|
||||
void InsertSeparators(std::string& text, const std::string& grouping, char separator, std::size_t offset, std::size_t prefix) {
|
||||
const char* group = grouping.c_str();
|
||||
while (static_cast<signed char>(*group) != CHAR_MAX && static_cast<signed char>(*group) > 0 && static_cast<std::size_t>(*group) < offset - prefix) {
|
||||
offset -= static_cast<std::size_t>(*group);
|
||||
text.insert(offset, 1, separator);
|
||||
if (static_cast<signed char>(group[1]) > 0) ++group;
|
||||
}
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator PutField(GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const std::string& text, std::size_t prefix) {
|
||||
const std::int32_t width = stream->width;
|
||||
std::size_t fillCount = width <= 0 || static_cast<std::size_t>(width) <= text.size() ? 0 : static_cast<std::size_t>(width) - text.size();
|
||||
const std::int32_t adjust = stream->flags & IosAdjustField;
|
||||
if (adjust != IosLeft && adjust != IosInternal) {
|
||||
for (; fillCount != 0; --fillCount) PutCharacter(out, fill);
|
||||
}
|
||||
for (std::size_t index = 0; index < prefix; ++index) PutCharacter(out, text[index]);
|
||||
if (adjust == IosInternal) {
|
||||
for (; fillCount != 0; --fillCount) PutCharacter(out, fill);
|
||||
}
|
||||
for (std::size_t index = prefix; index < text.size(); ++index) PutCharacter(out, text[index]);
|
||||
stream->width = 0;
|
||||
for (; fillCount != 0; --fillCount) PutCharacter(out, fill);
|
||||
return out;
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator PutInteger(GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const char* digits, std::size_t count) {
|
||||
std::size_t prefix = SignPrefix(digits, count);
|
||||
if ((stream->flags & IosBaseField) == IosHex && HexPrefix(digits, prefix, count)) prefix += 2;
|
||||
std::string text = Widen(stream, digits, count);
|
||||
const GuestLocale::Facet* punct = StreamNumpunct(stream);
|
||||
const std::string grouping = TakeString(Punctuation(punct).grouping, punct);
|
||||
if (static_cast<signed char>(grouping[0]) != CHAR_MAX && static_cast<signed char>(grouping[0]) > 0) InsertSeparators(text, grouping, Punctuation(punct).thousandsSep(AsNumpunct(punct)), count, prefix);
|
||||
return PutField(out, stream, fill, text, prefix);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator PutFloating(GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const char* digits, std::size_t count) {
|
||||
const std::size_t sign = SignPrefix(digits, count);
|
||||
const std::size_t prefix = sign != 0 ? sign : HexPrefix(digits, 0, count) ? 2 : 0;
|
||||
const bool hex = (stream->flags & IosHexFloat) == IosHexFloat;
|
||||
const std::size_t exponent = std::strcspn(digits, hex ? "pP" : "eE");
|
||||
const char point[2] = {localeconv_nid_postfix()->decimal_point[0], '\0'};
|
||||
const std::size_t pointOffset = std::strcspn(digits, point);
|
||||
std::string text = Widen(stream, digits, count);
|
||||
const GuestLocale::Facet* punct = StreamNumpunct(stream);
|
||||
const std::string grouping = TakeString(Punctuation(punct).grouping, punct);
|
||||
const char separator = Punctuation(punct).thousandsSep(AsNumpunct(punct));
|
||||
std::size_t offset = exponent;
|
||||
if (pointOffset != count) {
|
||||
offset = pointOffset;
|
||||
text[pointOffset] = Punctuation(punct).decimalPoint(AsNumpunct(punct));
|
||||
}
|
||||
const char lead = digits[prefix];
|
||||
if (lead != 'I' && lead != 'N' && lead != 'i' && lead != 'n') InsertSeparators(text, grouping, separator, offset, prefix);
|
||||
return PutField(out, stream, fill, text, prefix);
|
||||
}
|
||||
|
||||
template<typename Value> GuestLocale::OutputIterator PutFormattedInteger(GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const char* qualifier, char decimal, Value value) {
|
||||
const std::int32_t flags = stream->flags;
|
||||
const std::int32_t base = flags & IosBaseField;
|
||||
const char conversion = base == IosOct ? 'o' : base != IosHex ? decimal : (flags & IosUppercase) != 0 ? 'X' : 'x';
|
||||
const std::string format = std::string("%") + ((flags & IosShowPos) != 0 ? "+" : "") + ((flags & IosShowBase) != 0 ? "#" : "") + qualifier + conversion;
|
||||
char digits[64];
|
||||
const int count = snprintf_nid_postfix(digits, sizeof(digits), format.c_str(), value);
|
||||
return PutInteger(out, stream, fill, digits, static_cast<std::size_t>(count));
|
||||
}
|
||||
|
||||
template<typename Value> GuestLocale::OutputIterator PutFormattedFloating(GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const char* qualifier, Value value) {
|
||||
const std::int32_t flags = stream->flags;
|
||||
const std::int32_t field = flags & IosHexFloat;
|
||||
const std::int32_t precision = field == IosHexFloat ? 6 : stream->precision;
|
||||
std::int64_t size = precision;
|
||||
if (field == IosFixed && std::fabs(value) > Value(1e10)) {
|
||||
int exponent = 0;
|
||||
std::frexp(value, &exponent);
|
||||
size += static_cast<std::int64_t>(std::abs(exponent)) * 30103 / 100000;
|
||||
}
|
||||
size += 50;
|
||||
const char conversion = field == IosFixed ? 'f' : field == IosHexFloat ? 'a' : field == IosScientific ? 'e' : 'g';
|
||||
const bool upper = (flags & IosUppercase) != 0 && field != IosFixed;
|
||||
const std::string format = std::string("%") + ((flags & IosShowPos) != 0 ? "+" : "") + ((flags & IosShowPoint) != 0 ? "#" : "") + (field != IosHexFloat ? ".*" : "") + qualifier + static_cast<char>(upper ? conversion - 'a' + 'A' : conversion);
|
||||
if (size <= 0) throw std::invalid_argument("num_put::do_put: the precision leaves no room for the converted value");
|
||||
std::string digits(static_cast<std::size_t>(size), '\0');
|
||||
const int count = snprintf_nid_postfix(digits.data(), digits.size(), format.c_str(), precision, value);
|
||||
if (count < 0 || count >= size) throw std::invalid_argument("num_put::do_put: the precision leaves no room for the converted value");
|
||||
return PutFloating(out, stream, fill, digits.c_str(), static_cast<std::size_t>(count));
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutBool(const GuestLocale::Facet* self, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, bool value) {
|
||||
if ((stream->flags & IosBoolAlpha) == 0) return reinterpret_cast<const GuestLocale::NumPutVtable*>(self->vtable)->putLong(self, out, stream, fill, value ? 1 : 0);
|
||||
const GuestLocale::Facet* punct = StreamNumpunct(stream);
|
||||
return PutField(out, stream, fill, TakeString(value ? Punctuation(punct).truename : Punctuation(punct).falsename, punct), 0);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutLong(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, std::int64_t value) {
|
||||
return PutFormattedInteger(out, stream, fill, "l", 'd', value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutUnsignedLong(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, std::uint64_t value) {
|
||||
return PutFormattedInteger(out, stream, fill, "l", 'u', value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutLongLong(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, std::int64_t value) {
|
||||
return PutFormattedInteger(out, stream, fill, "ll", 'd', value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutUnsignedLongLong(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, std::uint64_t value) {
|
||||
return PutFormattedInteger(out, stream, fill, "ll", 'u', value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutDouble(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, double value) {
|
||||
return PutFormattedFloating(out, stream, fill, "", value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutLongDouble(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, long double value) {
|
||||
return PutFormattedFloating(out, stream, fill, "L", value);
|
||||
}
|
||||
|
||||
GuestLocale::OutputIterator APS5_VABI NumPutPointer(const GuestLocale::Facet*, GuestLocale::OutputIterator out, GuestLocale::IosBase* stream, char fill, const void* value) {
|
||||
char digits[64];
|
||||
const int count = snprintf_nid_postfix(digits, sizeof(digits), "%p", value);
|
||||
return PutInteger(out, stream, fill, digits, static_cast<std::size_t>(count));
|
||||
}
|
||||
|
||||
const GuestLocale::NumPutVtable g_numPutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, NumPutBool, NumPutLong, NumPutUnsignedLong, NumPutLongLong, NumPutUnsignedLongLong, NumPutDouble, NumPutLongDouble, NumPutPointer};
|
||||
const GuestLocale::TimePutVtable g_timePutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, TimePutDoPut};
|
||||
|
||||
constexpr std::size_t GuestTimePutFacetSize = 0xb8;
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
extern GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
extern std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix;
|
||||
|
||||
extern std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
|
||||
extern std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
|
||||
@@ -326,6 +329,132 @@ static void CheckWideNumPutVtableObject() {
|
||||
}
|
||||
}
|
||||
|
||||
struct TestStreambuf {
|
||||
GuestLocale::Streambuf buffer;
|
||||
std::string accepted;
|
||||
int offered;
|
||||
int capacity;
|
||||
};
|
||||
|
||||
int APS5_VABI TestOverflow(GuestLocale::Streambuf* self, int character) {
|
||||
auto* stream = reinterpret_cast<TestStreambuf*>(self);
|
||||
++stream->offered;
|
||||
if (static_cast<int>(stream->accepted.size()) >= stream->capacity) return -1;
|
||||
stream->accepted += static_cast<char>(character);
|
||||
return character;
|
||||
}
|
||||
|
||||
const GuestLocale::StreambufVtable g_testStreambufVtable{{}, TestOverflow};
|
||||
|
||||
static void CheckTimePut() {
|
||||
const GuestLocale::Implementation* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
GuestLocale::Facet* facet = nullptr;
|
||||
Require(_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&facet, &locale) == 5);
|
||||
const auto put = reinterpret_cast<const GuestLocale::TimePutVtable*>(facet->vtable)->put;
|
||||
GuestTm time{};
|
||||
time.tm_year = 126;
|
||||
time.tm_mon = 9;
|
||||
time.tm_mday = 2;
|
||||
time.tm_hour = 13;
|
||||
time.tm_min = 5;
|
||||
time.tm_sec = 9;
|
||||
time.tm_wday = 5;
|
||||
time.tm_yday = 274;
|
||||
char storage[8];
|
||||
char* next = storage;
|
||||
char* end = storage + sizeof(storage);
|
||||
TestStreambuf stream{{&g_testStreambufVtable, {}, &next, {}, &end}, {}, 0, 100};
|
||||
GuestLocale::OutputIterator out = put(facet, {false, &stream.buffer}, nullptr, '*', &time, 'Y', 0);
|
||||
Require(!out.failed && out.buffer == &stream.buffer && next == storage + 4 && std::memcmp(storage, "2026", 4) == 0 && stream.offered == 0);
|
||||
out = put(facet, out, nullptr, '*', &time, 'c', 0);
|
||||
Require(!out.failed && next == end && std::memcmp(storage + 4, "Fri ", 4) == 0 && stream.accepted == "Oct 2 13:05:09 2026");
|
||||
stream.accepted.clear();
|
||||
out = put(facet, out, nullptr, '*', &time, 'Y', 'E');
|
||||
out = put(facet, out, nullptr, '*', &time, 'y', 'O');
|
||||
out = put(facet, out, nullptr, '*', &time, '\0', 0);
|
||||
out = put(facet, out, nullptr, '*', &time, 'k', 0);
|
||||
Require(!out.failed && stream.accepted == "202626%%k");
|
||||
stream.capacity = static_cast<int>(stream.accepted.size()) + 1;
|
||||
stream.offered = 0;
|
||||
out = put(facet, out, nullptr, '*', &time, 'T', 0);
|
||||
Require(out.failed && stream.offered == 8 && stream.accepted == "202626%%k1");
|
||||
next = nullptr;
|
||||
stream.offered = 0;
|
||||
out = put(facet, {false, &stream.buffer}, nullptr, '*', &time, 'H', 0);
|
||||
Require(out.failed && stream.offered == 2);
|
||||
out = put(facet, {false, nullptr}, nullptr, '*', &time, 'H', 0);
|
||||
Require(out.failed && out.buffer == nullptr);
|
||||
facet->vtable->deleteObject(facet);
|
||||
}
|
||||
|
||||
GuestLocale::String* AssignShortString(GuestLocale::String* result, const char* text) {
|
||||
*result = {};
|
||||
result->size = std::strlen(text);
|
||||
result->capacity = 15;
|
||||
std::memcpy(result->buffer, text, result->size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void APS5_VABI TestFacetNop(GuestLocale::Facet*) {}
|
||||
GuestLocale::Facet* APS5_VABI TestFacetRelease(GuestLocale::Facet*) { return nullptr; }
|
||||
char APS5_VABI TestDecimalPoint(const GuestLocale::NumpunctFacet*) { return ','; }
|
||||
char APS5_VABI TestThousandsSep(const GuestLocale::NumpunctFacet*) { return '.'; }
|
||||
GuestLocale::String* APS5_VABI TestGrouping(GuestLocale::String* result, const GuestLocale::NumpunctFacet*) { return AssignShortString(result, "\1\2"); }
|
||||
GuestLocale::String* APS5_VABI TestFalsename(GuestLocale::String* result, const GuestLocale::NumpunctFacet*) { return AssignShortString(result, "no"); }
|
||||
GuestLocale::String* APS5_VABI TestTruename(GuestLocale::String* result, const GuestLocale::NumpunctFacet*) { return AssignShortString(result, "yes"); }
|
||||
const GuestLocale::NumpunctVtable g_testNumpunctVtable{{TestFacetNop, TestFacetNop, TestFacetNop, TestFacetRelease}, TestDecimalPoint, TestThousandsSep, TestGrouping, TestFalsename, TestTruename};
|
||||
|
||||
template<typename TPut>
|
||||
static std::string NumPut(TPut put, GuestLocale::Implementation** locale, std::int32_t flags, std::int32_t precision, std::int32_t width) {
|
||||
char storage[64];
|
||||
char* next = storage;
|
||||
char* end = storage + sizeof(storage);
|
||||
TestStreambuf stream{{&g_testStreambufVtable, {}, &next, {}, &end}, {}, 0, 0};
|
||||
GuestLocale::IosBase ios{};
|
||||
ios.flags = flags;
|
||||
ios.precision = precision;
|
||||
ios.width = width;
|
||||
ios.locale = locale;
|
||||
const GuestLocale::OutputIterator out = put(GuestLocale::OutputIterator{false, &stream.buffer}, &ios);
|
||||
Require(!out.failed && out.buffer == &stream.buffer && ios.width == 0 && stream.offered == 0);
|
||||
return std::string(storage, next);
|
||||
}
|
||||
|
||||
static void CheckNumPut() {
|
||||
GuestLocale::Implementation** classic = &_ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
const GuestLocale::Implementation* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
GuestLocale::Facet* facet = nullptr;
|
||||
Require(_ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&facet, &locale) == 4);
|
||||
const auto* put = reinterpret_cast<const GuestLocale::NumPutVtable*>(facet->vtable);
|
||||
const double infinity = std::numeric_limits<double>::infinity();
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putLong(facet, out, ios, '*', -42); }, classic, 0x408, 6, 8) == "-*****42");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putLong(facet, out, ios, '*', 255); }, classic, 0x2c8, 6, 10) == "0X******FF");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putUnsignedLong(facet, out, ios, '*', std::numeric_limits<std::uint64_t>::max()); }, classic, 0x20, 6, 0) == "1777777777777777777777");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putLongLong(facet, out, ios, '*', std::numeric_limits<std::int64_t>::min()); }, classic, 0x2, 6, 25) == "-9223372036854775808*****");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putBool(facet, out, ios, '*', true); }, classic, 0x8002, 6, 7) == "true***");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putBool(facet, out, ios, '*', false); }, classic, 0, 6, 0) == "0");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putPointer(facet, out, ios, '*', reinterpret_cast<const void*>(0x1234)); }, classic, 0, 6, 6) == "**1234");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', 1234567.891); }, classic, 0, 6, 0) == "1.23457e+06");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', 1234567.891); }, classic, 0x1000, 2, 0) == "1234567.89");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', -0.000123); }, classic, 0xa00, 3, 12) == "**-1.230E-04");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', -1.5); }, classic, 0x1808, 6, 12) == "-***0x1.8p+0");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', 12345678901234.0); }, classic, 0x1000, 0, 0) == "12345678901234");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', -infinity); }, classic, 0x400, 6, 0) == "-inf");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putLongDouble(facet, out, ios, '*', -2.25L); }, classic, 0, 3, 0) == "-2.25");
|
||||
RequireException([&] { NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', 1.0); }, classic, 0x1000, -45, 0); });
|
||||
GuestLocale::Facet punct{&g_testNumpunctVtable.facet, 1, 0};
|
||||
std::vector<GuestLocale::Facet*> facets(_ZNSt8numpunctIcE2idE_nid_postfix + 1);
|
||||
facets.back() = &punct;
|
||||
GuestLocale::Implementation custom{{nullptr, 1, 0}, facets.data(), facets.size(), 0, false, "C"};
|
||||
GuestLocale::Implementation* customPointer = &custom;
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putLong(facet, out, ios, '*', 1234567); }, &customPointer, 0, 6, 0) == "12.34.56.7");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', 1234567.5); }, &customPointer, 0x1000, 1, 0) == "12.34.56.7,5");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', infinity); }, &customPointer, 0x1000, 1, 0) == "inf");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putDouble(facet, out, ios, '*', -infinity); }, &customPointer, 0, 6, 0) == "-inf");
|
||||
Require(NumPut([&](auto out, auto* ios) { return put->putBool(facet, out, ios, '*', true); }, &customPointer, 0x8000, 6, 5) == "**yes");
|
||||
facet->vtable->deleteObject(facet);
|
||||
}
|
||||
|
||||
static void CheckFacetGetcat() {
|
||||
const GuestLocale::Implementation* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
GuestLocale::Facet* codecvt = nullptr;
|
||||
@@ -428,6 +557,8 @@ int main() {
|
||||
_Locksyslock_nid_postfix();
|
||||
_Unlocksyslock_nid_postfix();
|
||||
_Unlocksyslock_nid_postfix();
|
||||
CheckTimePut();
|
||||
CheckNumPut();
|
||||
|
||||
std::cout << "Guest locale checks passed\n";
|
||||
}
|
||||
@@ -157,7 +157,9 @@
|
||||
- [%e, %f and %g](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) format a finite `double` or `long double` as the `libc.prx` that titles ship does, instead of through the host. It keeps the exact decimal digits down to position `4 * floor(k / 4) - 20`, where `k` is the `frexp` exponent times 30103 / 100000 truncated, and prints zeros after them; a shorter precision rounds half up on the kept digits, except that an exact tie rounds down when `k` is at least 8 (`%.0f` of 67108865.5 is `67108865`). The console computes the kept digits in extended precision, so about 7% of outputs longer than 17 significant digits end one unit lower than here (`%f` of 1e22 is `9999999999999999999999.000000`). Checked by running the copies of PPSA07923, PPSA05916 and PPSA13934 side by side, not measured on a console. The wide printf family uses the same digits, as the titles' `swprintf` gives the same text as their `snprintf`.
|
||||
- [%p and %lp](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) follow the console's `libSceLibcInternal.sprx` measured from a homebrew payload on firmware 12.70 ([#7549](https://github.com/boykopovar/AnyPS5/issues/7549)), not FreeBSD: bare lowercase hex with no `0x` prefix even under `#`, `0` for a null pointer, `+` and the space flag ignored, the width counting only the digits, `-` padding on the right, `0` padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under `%.0p`, and `l` accepted. Only `snprintf` and `swprintf` of that module were measured, from a payload process rather than a title process, so a width beside `%.0p`, the `0` flag beside a precision (which C17 ignores) and `%.0p` of a non-null pointer follow this formatter's own rules and are not measured; `swprintf` was not run with `%lp`, so the wide family still refuses that conversion and returns -1. Linux routes `%p` and `%lp` to this formatter because glibc prints the FreeBSD form and `(nil)` for a null pointer, which means a format that combines `%p` with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc
|
||||
- [collate<char> and collate<wchar_t> _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits<wchar_t>::compare` does), `transform` copies the characters into the result and records size and capacity in 16-bit elements (inline capacity 7, heap capacity `length | 7`, `(capacity + 1) * 2` bytes including the terminator), following Dinkumware's [VC12 `basic_string`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xstring#L500-L531), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title.
|
||||
- [codecvt, numpunct, num_put and time_put _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build facets matching a bundled console `libc.prx` (dynsym decoded by NID, vtables disassembled): categories 2, 4, 4 and 5, sizes 0x10, 0x30, 0x10 and 0xb8. `codecvt<char>` reproduces the console virtuals (`noconv` conversions, `length` the shorter range); `numpunct<char>` copies its C-locale table (`""`, `.`, `,`, `false`, `true`). `num_put`/`time_put` formatting virtuals throw rather than fabricate output, and `time_put`'s `_Locinfo` table is unpopulated — the console's `do_put` bodies were not mirrored. The facets share the no-op `destroy`/`deleteObject`/`retain`/`release` base used for `collate`, so `numpunct`'s static C-locale strings are never freed. Unlike `collate`, these `_Getcat` entries ignore the locale argument and always build the C-locale facet; the console's behaviour for a non-C name is unverified. `std::clog` and `std::wclog` are 0x400-byte zeroed objects like the rest of the `NSTDSTR` stream set (the console's `libc.prx` sizes `std::clog` at 96 bytes, measured from its dynsym `st_size`), and the `numpunct<char>::id` and `time_put<char>::id` objects are 8-byte zeros like the other facet ids.
|
||||
- [codecvt, numpunct, num_put and time_put _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build facets matching a bundled console `libc.prx` (dynsym decoded by NID, vtables disassembled): categories 2, 4, 4 and 5, sizes 0x10, 0x30, 0x10 and 0xb8. Unlike `collate`, they ignore the locale argument and always build the C-locale facet; the console's behaviour for a non-C name is unverified. `time_put`'s `do_put` formats through this library's `strftime` instead of the facet's unpopulated `_Locinfo` table, so the `strftime` gaps apply. `codecvt<char>` reproduces the console virtuals (`noconv` conversions, `length` the shorter range); `numpunct<char>` copies its C-locale table (`""`, `.`, `,`, `false`, `true`). The facets share the no-op `destroy`/`deleteObject`/`retain`/`release` base used for `collate`, so `numpunct`'s static C-locale strings are never freed.
|
||||
- [num_put<char>::do_put](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) follows the SDK 7.00 `libc.prx` that PPSA06416 ships, read from its disassembly and run, but formats through this library's `snprintf`. That converts floating point exactly and rounds ties to even, where the module keeps about 18 exact significant digits, prints later ones from an inexact scaling (1e10 at precision 22 is `9999999999.999999999999`) and gives `-3` for -2.5 at precision 0. A precision so negative that the module's buffer of precision + 50 bytes cannot hold the result throws, where the module reads stale bytes. A stream locale without a `ctype<char>` facet leaves digits unwidened, as the console's C-locale facet does.
|
||||
- [std::clog, std::wclog and the facet ids](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc): `std::clog` and `std::wclog` are 0x400-byte zeroed objects like the rest of the `NSTDSTR` stream set (the console's `libc.prx` sizes `std::clog` at 96 bytes, measured from its dynsym `st_size`), and the `numpunct<char>::id` and `time_put<char>::id` objects are 8-byte zeros like the other facet ids.
|
||||
- [std::__throw_bad_variant_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) throws `std::bad_variant_access` with the `what()` string `bad_variant_access`, the one [LLVM libc++](https://github.com/llvm/llvm-project/blob/main/libcxx/src/variant.cpp) returns, since the export carries libc++'s `__throw_bad_variant_access` name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from `libc`, and its bundled `libc.prx` does not export it.
|
||||
- [std::bad_optional_access and std::_Throw_bad_optional_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) provide the exception type, constructor, destructor, and `what()` exports alongside `_Throw_bad_optional_access`, returning the `what()` string `bad_optional_access` matching Dinkumware and libc++ conventions; the console libc.prx string is unverified. PPSA26578 (issue #7624) and PPSA34015 import `_ZSt26_Throw_bad_optional_accessv` (`KwzEip7m6TM`) from `libc`.
|
||||
- [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, decodes the input of the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) on Linux as UTF-8, mirroring the narrow printf family, where FreeBSD uses the locale's `mbrtowc` (one byte per character in the C locale); the console's locale encoding is unverified. Input that is not UTF-8, a character outside the BMP, a scanset member outside ASCII, positional arguments next to a wide conversion and wide conversions in `fscanf` throw. Each character is stored as one 16-bit unit, widths and `_s` sizes count characters, and a `%lc` field cut short by the end of the input keeps what it read, as FreeBSD's `convert_wchar` does. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits
|
||||
|
||||
Reference in new issue
Block a user