Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions core/libs/prx/libc/include/GuestLocale.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <cstdint>
#include "general/VabiMacros.hpp"

struct GuestTm;

namespace GuestLocale {

struct LocinfoStorage {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
234 changes: 228 additions & 6 deletions core/libs/prx/libc/src/LocaleSupport.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "prx/libc/include/Api.hpp"
#include <climits>
#include <cmath>
#include <cstdlib>
#include <clocale>
#include <cstddef>
#include <cstdio>
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

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

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

void APS5_VABI TimePutMethodUnsupported() {
NotImplemented_nid_no_patch("time_put<char>::do_put");
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::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}};
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;

Expand Down
Loading
Loading