diff --git a/CMakeLists.txt b/CMakeLists.txt index b07d8996..1be51375 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ add_executable(shadps4 src/Core/PS4/HLE/Kernel/cpu_management.cpp src/Core/PS4/HLE/Kernel/cpu_management.h - "src/Util/Singleton.h" "src/Util/Disassembler.cpp" "src/Util/Disassembler.h" "src/Core/PS4/Util/aerolib.h" "src/Core/PS4/Loader/SymbolsResolver.h" "src/Core/PS4/Loader/SymbolsResolver.cpp" "src/Core/PS4/HLE/Libs.cpp" "src/Core/PS4/HLE/Libs.h" "src/Core/PS4/HLE/LibC.cpp" "src/Core/PS4/HLE/LibC.h" "src/Lib/Timer.cpp" "src/Lib/Timer.h" "src/Core/PS4/HLE/LibKernel.cpp" "src/Core/PS4/HLE/LibKernel.h" "src/Core/PS4/HLE/LibSceGnmDriver.cpp" "src/Core/PS4/HLE/LibSceGnmDriver.h" "src/Core/PS4/HLE/Kernel/ThreadManagement.cpp" "src/Core/PS4/HLE/Kernel/ThreadManagement.h" "src/Core/PS4/HLE/ErrorCodes.h" "src/debug.h" "src/Core/PS4/HLE/Kernel/memory_management.cpp" "src/Core/PS4/HLE/Kernel/memory_management.h" "src/Core/PS4/GPU/gpu_memory.cpp" "src/Core/PS4/GPU/gpu_memory.h" "src/emulator.cpp" "src/emulator.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.cpp" "src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp" "src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h" "src/Core/PS4/HLE/Graphics/graphics_ctx.h" "src/vulkan_util.cpp" "src/vulkan_util.h" "src/Core/PS4/GPU/video_out_buffer.cpp" "src/Core/PS4/GPU/video_out_buffer.h" "src/Core/PS4/HLE/Graphics/graphics_render.cpp" "src/Core/PS4/HLE/Graphics/graphics_render.h" "src/Core/PS4/GPU/tile_manager.cpp" "src/Core/PS4/GPU/tile_manager.h" "src/version.h") + "src/Util/Singleton.h" "src/Util/Disassembler.cpp" "src/Util/Disassembler.h" "src/Core/PS4/Util/aerolib.h" "src/Core/PS4/Loader/SymbolsResolver.h" "src/Core/PS4/Loader/SymbolsResolver.cpp" "src/Core/PS4/HLE/Libs.cpp" "src/Core/PS4/HLE/Libs.h" "src/Core/PS4/HLE/LibC.cpp" "src/Core/PS4/HLE/LibC.h" "src/Lib/Timer.cpp" "src/Lib/Timer.h" "src/Core/PS4/HLE/LibKernel.cpp" "src/Core/PS4/HLE/LibKernel.h" "src/Core/PS4/HLE/LibSceGnmDriver.cpp" "src/Core/PS4/HLE/LibSceGnmDriver.h" "src/Core/PS4/HLE/Kernel/ThreadManagement.cpp" "src/Core/PS4/HLE/Kernel/ThreadManagement.h" "src/Core/PS4/HLE/ErrorCodes.h" "src/debug.h" "src/Core/PS4/HLE/Kernel/memory_management.cpp" "src/Core/PS4/HLE/Kernel/memory_management.h" "src/Core/PS4/GPU/gpu_memory.cpp" "src/Core/PS4/GPU/gpu_memory.h" "src/emulator.cpp" "src/emulator.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.cpp" "src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp" "src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h" "src/Core/PS4/HLE/Graphics/graphics_ctx.h" "src/vulkan_util.cpp" "src/vulkan_util.h" "src/Core/PS4/GPU/video_out_buffer.cpp" "src/Core/PS4/GPU/video_out_buffer.h" "src/Core/PS4/HLE/Graphics/graphics_render.cpp" "src/Core/PS4/HLE/Graphics/graphics_render.h" "src/Core/PS4/GPU/tile_manager.cpp" "src/Core/PS4/GPU/tile_manager.h" "src/version.h" "src/Emulator/HLE/Libraries/LibC/printf.h" "src/Emulator/HLE/Libraries/LibC/va_ctx.h" "src/Emulator/HLE/Libraries/LibC/libc.cpp" "src/Emulator/HLE/Libraries/LibC/libc.h") find_package(OpenGL REQUIRED) target_link_libraries(shadps4 PUBLIC fmt mincore spdlog IMGUI SDL3-shared ${OPENGL_LIBRARY} vulkan-1 spirv-tools-opt spirv-tools) diff --git a/src/Core/PS4/HLE/LibC.cpp b/src/Core/PS4/HLE/LibC.cpp index 72624578..71e97a90 100644 --- a/src/Core/PS4/HLE/LibC.cpp +++ b/src/Core/PS4/HLE/LibC.cpp @@ -1,110 +1,97 @@ #include "LibC.h" -#include "Libs.h" -#include "../Loader/Elf.h" + #include #include +#include "../Loader/Elf.h" +#include "Libs.h" +#include "Emulator/HLE/Libraries/LibC/libc.h" + + namespace HLE::Libs::LibC { - static u32 g_need_sceLibc = 1; +static u32 g_need_sceLibc = 1; - static PS4_SYSV_ABI void init_env() // every game/demo should probably - { - //dummy no need atm - } +static PS4_SYSV_ABI void init_env() // every game/demo should probably +{ + // dummy no need atm +} - static pthread_mutex_t __guard_mutex; - static pthread_once_t __once_control = PTHREAD_ONCE_INIT; +static pthread_mutex_t __guard_mutex; +static pthread_once_t __once_control = PTHREAD_ONCE_INIT; - static void recursiveMutex() +static void recursiveMutex() { + pthread_mutexattr_t recursiveMutexAttr; + pthread_mutexattr_init(&recursiveMutexAttr); + pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&__guard_mutex, &recursiveMutexAttr); +} + +static pthread_mutex_t* mutex_quard() { + pthread_once(&__once_control, &recursiveMutex); + return &__guard_mutex; +} + +int PS4_SYSV_ABI __cxa_guard_acquire(u64* guard_object) { + if ((*((uint8_t*)guard_object) != 0)) // low 8 bits checks if its already init { - pthread_mutexattr_t recursiveMutexAttr; - pthread_mutexattr_init(&recursiveMutexAttr); - pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&__guard_mutex, &recursiveMutexAttr); + return 0; + } + int result = ::pthread_mutex_lock(mutex_quard()); + if (result != 0) { + BREAKPOINT(); } - static pthread_mutex_t* mutex_quard() { - pthread_once(&__once_control, &recursiveMutex); - return &__guard_mutex; - } - - int PS4_SYSV_ABI __cxa_guard_acquire(u64* guard_object) { - if ((*((uint8_t*)guard_object) != 0)) // low 8 bits checks if its already init - { - return 0; - } - int result = ::pthread_mutex_lock(mutex_quard()); - if (result != 0) { - BREAKPOINT(); - } - - // Check if another thread has completed initializer run - if ((*((uint8_t*)guard_object) != 0)) { // check again if other thread init it - int result = ::pthread_mutex_unlock(mutex_quard()); - if (result != 0) { - BREAKPOINT(); - } - return 0; - } - - if (((uint8_t*)guard_object)[1] != 0) { // the second lowest byte marks if it's being used by a thread - BREAKPOINT(); - } - - // mark this guard object as being in use - ((uint8_t*)guard_object)[1] = 1; - - return 1; - } - - void PS4_SYSV_ABI __cxa_guard_release(u64* guard_object) - { - *((uint8_t*)guard_object) = 1;//mark it as done - - // release global mutex + // Check if another thread has completed initializer run + if ((*((uint8_t*)guard_object) != 0)) { // check again if other thread init it int result = ::pthread_mutex_unlock(mutex_quard()); if (result != 0) { BREAKPOINT(); } - } - - int PS4_SYSV_ABI memcmp(const void* s1, const void* s2, size_t n) { - return ::memcmp(s1, s2, n); + return 0; } - void* PS4_SYSV_ABI memcpy(void* dest, const void* src, size_t n) { - return ::memcpy(dest, src, n); + if (((uint8_t*)guard_object)[1] != 0) { // the second lowest byte marks if it's being used by a thread + BREAKPOINT(); } - static PS4_SYSV_ABI void catchReturnFromMain(int status) { BREAKPOINT(); - } - static PS4_SYSV_ABI void exit(int code) { BREAKPOINT(); - } - static PS4_SYSV_ABI int atexit(void (*func)()) - { - int rt = ::atexit(func); - if (rt != 0) - { - BREAKPOINT(); - } - return rt; - } - static PS4_SYSV_ABI void _Assert() { BREAKPOINT(); } + // mark this guard object as being in use + ((uint8_t*)guard_object)[1] = 1; - void LibC_Register(SymbolsResolver* sym) - { - LIB_FUNCTION("bzQExy189ZI", "libc", 1, "libc", 1, 1, init_env); - LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, __cxa_guard_acquire); - LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, __cxa_guard_release); - LIB_FUNCTION("DfivPArhucg", "libc", 1, "libc", 1, 1, memcmp); - LIB_FUNCTION("Q3VBxCXhUHs", "libc", 1, "libc", 1, 1, memcpy); - LIB_FUNCTION("XKRegsFpEpk", "libc", 1, "libc", 1, 1, catchReturnFromMain); - LIB_FUNCTION("uMei1W9uyNo", "libc", 1, "libc", 1, 1, exit); - LIB_FUNCTION("8G2LB+A3rzg", "libc", 1, "libc", 1, 1, atexit); - LIB_FUNCTION("-QgqOT5u2Vk", "libc", 1, "libc", 1, 1, _Assert); + return 1; +} - LIB_OBJ("P330P3dFF68", "libc", 1, "libc", 1, 1, &HLE::Libs::LibC::g_need_sceLibc); +void PS4_SYSV_ABI __cxa_guard_release(u64* guard_object) { + *((uint8_t*)guard_object) = 1; // mark it as done + + // release global mutex + int result = ::pthread_mutex_unlock(mutex_quard()); + if (result != 0) { + BREAKPOINT(); } - -}; \ No newline at end of file +} + + + +static PS4_SYSV_ABI void catchReturnFromMain(int status) { + // dummy +} + +static PS4_SYSV_ABI void _Assert() { BREAKPOINT(); } + +void LibC_Register(SymbolsResolver* sym) { + LIB_FUNCTION("bzQExy189ZI", "libc", 1, "libc", 1, 1, init_env); + LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, __cxa_guard_acquire); + LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, __cxa_guard_release); + LIB_FUNCTION("DfivPArhucg", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memcmp); + LIB_FUNCTION("Q3VBxCXhUHs", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memcpy); + LIB_FUNCTION("XKRegsFpEpk", "libc", 1, "libc", 1, 1, catchReturnFromMain); + LIB_FUNCTION("uMei1W9uyNo", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::exit); + LIB_FUNCTION("8G2LB+A3rzg", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::atexit); + LIB_FUNCTION("-QgqOT5u2Vk", "libc", 1, "libc", 1, 1, _Assert); + LIB_FUNCTION("hcuQgD53UxM", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::printf); + + LIB_OBJ("P330P3dFF68", "libc", 1, "libc", 1, 1, &HLE::Libs::LibC::g_need_sceLibc); +} + +}; // namespace HLE::Libs::LibC \ No newline at end of file diff --git a/src/Core/PS4/HLE/LibC.h b/src/Core/PS4/HLE/LibC.h index ee82b993..ea3915e2 100644 --- a/src/Core/PS4/HLE/LibC.h +++ b/src/Core/PS4/HLE/LibC.h @@ -7,12 +7,9 @@ namespace HLE::Libs::LibC { //functions static PS4_SYSV_ABI void init_env(); - static PS4_SYSV_ABI void exit(int code); static PS4_SYSV_ABI void _Assert(); static PS4_SYSV_ABI void catchReturnFromMain(int status); int PS4_SYSV_ABI __cxa_guard_acquire(u64* guard_object); void PS4_SYSV_ABI __cxa_guard_release(u64* guard_object); - int PS4_SYSV_ABI memcmp(const void* s1, const void* s2, size_t n); - void* PS4_SYSV_ABI memcpy(void* dest, const void* src, size_t n); - + }; \ No newline at end of file diff --git a/src/Emulator/HLE/Libraries/LibC/libc.cpp b/src/Emulator/HLE/Libraries/LibC/libc.cpp new file mode 100644 index 00000000..7d99a1f2 --- /dev/null +++ b/src/Emulator/HLE/Libraries/LibC/libc.cpp @@ -0,0 +1,29 @@ +#include "libc.h" + +#include + +#include +#include + +namespace Emulator::HLE::Libraries::LibC { + +PS4_SYSV_ABI int printf(VA_ARGS) { + VA_CTX(ctx); + return printf_ctx(&ctx); +} + +PS4_SYSV_ABI void exit(int code) { std::exit(code); } + +PS4_SYSV_ABI int atexit(void (*func)()) { + int rt = std::atexit(func); + if (rt != 0) { + BREAKPOINT(); + } + return rt; +} + +int PS4_SYSV_ABI memcmp(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); } + +void* PS4_SYSV_ABI memcpy(void* dest, const void* src, size_t n) { return std::memcpy(dest, src, n); } + +}; // namespace Emulator::HLE::Libraries::LibC \ No newline at end of file diff --git a/src/Emulator/HLE/Libraries/LibC/libc.h b/src/Emulator/HLE/Libraries/LibC/libc.h new file mode 100644 index 00000000..c9f632fd --- /dev/null +++ b/src/Emulator/HLE/Libraries/LibC/libc.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "printf.h" + +namespace Emulator::HLE::Libraries::LibC { + +// HLE functions +PS4_SYSV_ABI int printf(VA_ARGS); +PS4_SYSV_ABI void exit(int code); +PS4_SYSV_ABI int atexit(void (*func)()); +int PS4_SYSV_ABI memcmp(const void* s1, const void* s2, size_t n); +void* PS4_SYSV_ABI memcpy(void* dest, const void* src, size_t n); +} // namespace Emulator::HLE::Libraries::LibC \ No newline at end of file diff --git a/src/Emulator/HLE/Libraries/LibC/printf.h b/src/Emulator/HLE/Libraries/LibC/printf.h new file mode 100644 index 00000000..1ed0cd84 --- /dev/null +++ b/src/Emulator/HLE/Libraries/LibC/printf.h @@ -0,0 +1,696 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2018, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant! +// +/////////////////////////////////////////////////////////////////////////////// +// Vita3K emulator project +// Copyright (C) 2023 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +// copied from Vita3k project at 6/10/2023 (latest update 30/06/2023) +// modifications for adapting va_args parameters + +#pragma once + +#include + +#include +#include +#include +#include + +#include "va_ctx.h" + +namespace Emulator::HLE::Libraries::LibC { +// ntoa conversion buffer size, this must be big enough to hold +// one converted numeric number including padded zeros (dynamically created on stack) +// 32 byte is a good default +#define PRINTF_NTOA_BUFFER_SIZE 32U + +// ftoa conversion buffer size, this must be big enough to hold +// one converted float number including padded zeros (dynamically created on stack) +// 32 byte is a good default +#define PRINTF_FTOA_BUFFER_SIZE 32U + +// define this to support floating point (%f) +#define PRINTF_SUPPORT_FLOAT + +// define this to support long long types (%llu or %p) +#define PRINTF_SUPPORT_LONG_LONG + +// define this to support the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +#define PRINTF_SUPPORT_PTRDIFF_T + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_WIDTH (1U << 11U) + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) { + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) { + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) { + (void)idx; + (void)maxlen; + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); +} + +// internal strlen +// \return The length of the string (excluding the terminating 0) +static inline unsigned int _strlen(const char* str) { + const char* s; + for (s = str; *s; ++s) + ; + return (unsigned int)(s - str); +} + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) { return (ch >= '0') && (ch <= '9'); } + +// internal ASCII string to unsigned int conversion +static inline unsigned int _atoi(const char** str) { + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + +// internal itoa format +static inline size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, + unsigned int prec, unsigned int width, unsigned int flags) { + const size_t start_idx = idx; + + // pad leading zeros + while (!(flags & FLAGS_LEFT) && (len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + + // handle hash + if (flags & FLAGS_HASH) { + if (((len == prec) || (len == width)) && (len > 0U)) { + len--; + if ((base == 16U) && (len > 0U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + // handle sign + if ((len == width) && (negative || (flags & FLAGS_PLUS) || (flags & FLAGS_SPACE))) { + len--; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + for (size_t i = 0U; i < len; i++) { + out(buf[len - i - 1U], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + +// internal itoa for 'long' type +static inline size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, + unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static inline size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, + unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + +#if defined(PRINTF_SUPPORT_FLOAT) +static inline size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, + unsigned int flags) { + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // if input is larger than thres_max, revert to exponential + const double thres_max = (double)0x7FFFFFFF; + + // powers of 10 + static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision to 6, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = 6U; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if ((diff == 0.5) && ((frac == 0U) || (frac & 1U))) { + // if halfway, round up if odd, OR if last digit is 0 + ++frac; + } + + // TBD: for very large numbers switch back to native sprintf for exponentials. Anyone want to write code to replace this? + // Normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad + if (value > thres_max) { + return 0U; + } + + if (prec == 0U) { + diff = value - (double)whole; + if (diff > 0.5) { + // greater than 0.5, round up, e.g. 1.6 -> 2 + ++whole; + } else if ((diff == 0.5) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + while (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + + // handle sign + if ((len == width) && (negative || (flags & FLAGS_PLUS) || (flags & FLAGS_SPACE))) { + len--; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + for (size_t i = 0U; i < len; i++) { + out(buf[len - i - 1U], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} +#endif // PRINTF_SUPPORT_FLOAT + +// internal vsnprintf +static inline int _vsnprintf(out_fct_type out, char* buffer, const char* format, VaList* va_list) { + unsigned int flags, width, precision, n; + size_t idx = 0U; + auto maxlen = static_cast(-1); + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } else if (*format == '*') { + const int w = vaArgInteger(va_list); // const int w = va.next(cpu, mem); + + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } else if (*format == '*') { + precision = vaArgInteger(va_list); // precision = (unsigned int)va.next(cpu, mem); + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } else if (*format == 'o') { + base = 8U; + } else if (*format == 'b') { + base = 2U; + flags &= ~FLAGS_HASH; // no hash for bin format + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + auto value = vaArgLongLong(va_list); // const long long value = va.next(cpu, mem); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, + precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + auto value = vaArgLong(va_list); // const long value = va.next(cpu, mem); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, + flags); + } else { + // const int value = (flags & FLAGS_CHAR) ? (char)va.next(cpu, mem) : (flags & FLAGS_SHORT) ? (short + // int)va.next(cpu, mem): va.next(cpu, mem); + const int value = (flags & FLAGS_CHAR) ? static_cast(vaArgInteger(va_list)) + : (flags & FLAGS_SHORT) ? static_cast(vaArgInteger(va_list)) + : vaArgInteger(va_list); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, + flags); + } + } else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + // idx = _ntoa_long_long(out, buffer, idx, maxlen, va.next(cpu, mem), false, base, precision, width, + // flags); + idx = + _ntoa_long_long(out, buffer, idx, maxlen, static_cast(vaArgLongLong(va_list)), false, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + // idx = _ntoa_long(out, buffer, idx, maxlen, va.next(cpu, mem), false, base, precision, width, flags); + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(vaArgLong(va_list)), false, base, precision, width, flags); + } else { + // const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va.next(cpu, mem) : (flags & FLAGS_SHORT) ? + // (unsigned short int)va.next(cpu, mem) : va.next(cpu, mem); + const unsigned int value = (flags & FLAGS_CHAR) ? static_cast(vaArgInteger(va_list)) + : (flags & FLAGS_SHORT) ? static_cast(vaArgInteger(va_list)) + : static_cast(vaArgInteger(va_list)); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + // idx = _ftoa(out, buffer, idx, maxlen, va.next(cpu, mem), precision, width, flags); + idx = _ftoa(out, buffer, idx, maxlen, vaArgDouble(va_list), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + // out((char)va.next(cpu, mem), buffer, idx++, maxlen); + out(static_cast(vaArgInteger(va_list)), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's': { + const char* p = vaArgPtr(va_list); // const char *p = va.next>(cpu, mem).get(mem); + p = p != nullptr ? p : "(null)"; + unsigned int l = _strlen(p); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + // idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va.next>(cpu, mem).address(), false, 16U, precision, + // width, flags); + idx = _ntoa_long_long(out, buffer, idx, maxlen, reinterpret_cast(vaArgPtr(va_list)), false, 16U, precision, + width, flags); + } else { +#endif + // idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va.next>(cpu, mem).address()), false, 16U, + // precision, width, flags); + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(reinterpret_cast(vaArgPtr(va_list))), false, + 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + out('%', buffer, idx++, maxlen); + format++; + break; + + default: + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + +static int printf_ctx(VaCtx* ctx) { + const char* format = vaArgPtr(&ctx->va_list); + char buffer[256]; + int result = _vsnprintf(_out_buffer, buffer, format, &ctx->va_list); + printf("%s", buffer); + return result; +} + +} // namespace Emulator::HLE::Libraries::LibC diff --git a/src/Emulator/HLE/Libraries/LibC/va_ctx.h b/src/Emulator/HLE/Libraries/LibC/va_ctx.h new file mode 100644 index 00000000..a2d78a52 --- /dev/null +++ b/src/Emulator/HLE/Libraries/LibC/va_ctx.h @@ -0,0 +1,106 @@ +#include +#include + +#define VA_ARGS \ + uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9, uint64_t overflow_arg_area, __m128 xmm0, __m128 xmm1, \ + __m128 xmm2, __m128 xmm3, __m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7, ... + +#define VA_CTX(ctx) \ + alignas(16) VaCtx ctx; \ + (ctx).reg_save_area.gp[0] = rdi; \ + (ctx).reg_save_area.gp[1] = rsi; \ + (ctx).reg_save_area.gp[2] = rdx; \ + (ctx).reg_save_area.gp[3] = rcx; \ + (ctx).reg_save_area.gp[4] = r8; \ + (ctx).reg_save_area.gp[5] = r9; \ + (ctx).reg_save_area.fp[0] = xmm0; \ + (ctx).reg_save_area.fp[1] = xmm1; \ + (ctx).reg_save_area.fp[2] = xmm2; \ + (ctx).reg_save_area.fp[3] = xmm3; \ + (ctx).reg_save_area.fp[4] = xmm4; \ + (ctx).reg_save_area.fp[5] = xmm5; \ + (ctx).reg_save_area.fp[6] = xmm6; \ + (ctx).reg_save_area.fp[7] = xmm7; \ + (ctx).va_list.reg_save_area = &(ctx).reg_save_area; \ + (ctx).va_list.gp_offset = offsetof(VaRegSave, gp); \ + (ctx).va_list.fp_offset = offsetof(VaRegSave, fp); \ + (ctx).va_list.overflow_arg_area = &overflow_arg_area; + +namespace Emulator::HLE::Libraries::LibC { + +// https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure + +struct VaList { + u32 gp_offset; + u32 fp_offset; + void* overflow_arg_area; + void* reg_save_area; +}; + +struct VaRegSave { + u64 gp[6]; + __m128 fp[8]; +}; + +struct VaCtx { + VaRegSave reg_save_area; + VaList va_list; +}; + +template +T vaArgRegSaveAreaGp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->gp_offset); + l->gp_offset += Size; + return *addr; +} +template +T vaArgOverflowArgArea(VaList* l) { + auto ptr = ((reinterpret_cast(l->overflow_arg_area) + (Align - 1)) & ~(Align - 1)); + auto* addr = reinterpret_cast(ptr); + l->overflow_arg_area = reinterpret_cast(ptr + Size); + return *addr; +} + +template +T vaArgRegSaveAreaFp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->fp_offset); + l->fp_offset += Size; + return *addr; +} + +inline int vaArgInteger(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline long long vaArgLongLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} +inline long vaArgLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline double vaArgDouble(VaList* l) { + if (l->fp_offset <= 160) { + return vaArgRegSaveAreaFp(l); + } + return vaArgOverflowArgArea(l); +} + +template +T* vaArgPtr(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +} // namespace Emulator::HLE::Libraries::LibC \ No newline at end of file