2020-08-22 22:30:12 -04:00
|
|
|
/*!
|
|
|
|
* @file kprint.cpp
|
|
|
|
* GOAL Print. Contains GOAL I/O, Print, Format...
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
#include <cmath>
|
2020-10-31 14:07:43 -04:00
|
|
|
#include <cstdarg>
|
|
|
|
#include <cstdio>
|
2020-08-22 22:30:12 -04:00
|
|
|
|
2020-08-26 23:07:55 -04:00
|
|
|
#include "common/goal_constants.h"
|
2020-08-22 22:30:12 -04:00
|
|
|
#include "common/common_types.h"
|
2020-10-31 14:07:43 -04:00
|
|
|
#include "common/cross_os_debug/xdbg.h"
|
2021-10-20 19:49:32 -04:00
|
|
|
#include "game/sce/sif_ee.h"
|
2020-08-22 22:30:12 -04:00
|
|
|
#include "kprint.h"
|
|
|
|
#include "kmachine.h"
|
|
|
|
#include "kboot.h"
|
|
|
|
#include "kmalloc.h"
|
|
|
|
#include "kdsnetm.h"
|
|
|
|
#include "fileio.h"
|
|
|
|
#include "kscheme.h"
|
|
|
|
#include "klisten.h"
|
|
|
|
#include "klink.h"
|
|
|
|
#include "common/symbols.h"
|
2022-01-02 18:02:10 -05:00
|
|
|
#include "common/util/assert.h"
|
2020-08-22 22:30:12 -04:00
|
|
|
|
|
|
|
///////////
|
|
|
|
// SDATA
|
|
|
|
///////////
|
|
|
|
|
|
|
|
// Pointer set to something in the middle of the output buffer, if there is something in the buffer.
|
|
|
|
Ptr<u8> OutputPending;
|
|
|
|
|
|
|
|
// Pointer set to something in the middle of the print buffer, if there is something in the buffer
|
|
|
|
Ptr<u8> PrintPending;
|
|
|
|
|
|
|
|
// Size of incoming message.
|
|
|
|
s32 MessCount;
|
|
|
|
|
|
|
|
// Pointer to message buffer, the compiler to target buffer
|
|
|
|
Ptr<u8> MessBufArea;
|
|
|
|
|
|
|
|
// Pointer to the output buffer, the runtime to compiler buffer
|
|
|
|
Ptr<u8> OutputBufArea;
|
|
|
|
|
|
|
|
// Pointer to print buffer, the buffer for printing and string formatting.
|
|
|
|
Ptr<u8> PrintBufArea;
|
|
|
|
|
|
|
|
// integer printing conversion table
|
|
|
|
char ConvertTable[16];
|
|
|
|
|
|
|
|
// buffer for sending an "acknowledge" message to the compiler
|
|
|
|
char AckBufArea[40];
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Initialize global variables for kprint
|
|
|
|
*/
|
|
|
|
void kprint_init_globals() {
|
|
|
|
OutputPending.offset = 0;
|
|
|
|
PrintPending.offset = 0;
|
|
|
|
MessCount = 0;
|
|
|
|
MessBufArea.offset = 0;
|
|
|
|
OutputBufArea.offset = 0;
|
|
|
|
PrintBufArea.offset = 0;
|
|
|
|
memcpy(ConvertTable, "0123456789abcdef", 16);
|
|
|
|
memset(AckBufArea, 0, sizeof(AckBufArea));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Initialize GOAL Kernel printing/messaging system.
|
|
|
|
* Allocates buffers.
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void init_output() {
|
|
|
|
if (MasterDebug) {
|
|
|
|
MessBufArea = kmalloc(kdebugheap, DEBUG_MESSAGE_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256,
|
|
|
|
"mess-buf");
|
|
|
|
OutputBufArea = kmalloc(kdebugheap, DEBUG_OUTPUT_BUFFER_SIZE,
|
|
|
|
KMALLOC_MEMSET | KMALLOC_ALIGN_256, "output-buf");
|
|
|
|
PrintBufArea = kmalloc(kdebugheap, DEBUG_PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256,
|
|
|
|
"print-buf");
|
|
|
|
} else {
|
|
|
|
// no compiler connection, so we do not allocate buffers
|
|
|
|
MessBufArea = Ptr<u8>(0);
|
|
|
|
OutputBufArea = Ptr<u8>(0);
|
|
|
|
|
|
|
|
// we still need a (small) print buffer for string maniuplation and debugging prints.
|
|
|
|
PrintBufArea =
|
|
|
|
kmalloc(kglobalheap, PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, "print-buf");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Empty output buffer (only if MasterDebug)
|
|
|
|
* DONE
|
|
|
|
* EXACT
|
|
|
|
*/
|
|
|
|
void clear_output() {
|
|
|
|
if (MasterDebug) {
|
2020-10-31 14:07:43 -04:00
|
|
|
kstrcpy((char*)Ptr<u8>(OutputBufArea + sizeof(ListenerMessageHeader)).c(), "");
|
2020-08-22 22:30:12 -04:00
|
|
|
OutputPending = Ptr<u8>(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Clear all data in the print buffer
|
|
|
|
* DONE
|
|
|
|
* EXACT
|
|
|
|
*/
|
|
|
|
void clear_print() {
|
2020-10-31 14:07:43 -04:00
|
|
|
*Ptr<u8>(PrintBufArea + sizeof(ListenerMessageHeader)) = 0;
|
2020-08-22 22:30:12 -04:00
|
|
|
PrintPending = Ptr<u8>(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Buffer message to compiler indicating the target has reset.
|
|
|
|
* Write to the beginning of the output buffer.
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void reset_output() {
|
|
|
|
if (MasterDebug) {
|
2021-11-12 22:00:41 -05:00
|
|
|
// original GOAL:
|
|
|
|
// sprintf(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader), "reset #x%x\n",
|
|
|
|
// s7.offset);
|
|
|
|
|
|
|
|
// modified for OpenGOAL:
|
|
|
|
#ifdef _WIN32
|
|
|
|
sprintf(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader),
|
|
|
|
"reset #x%x #x%llx %s\n", s7.offset, (unsigned long long)g_ee_main_mem, // grr
|
|
|
|
xdbg::get_current_thread_id().to_string().c_str());
|
|
|
|
#else
|
2020-10-31 14:07:43 -04:00
|
|
|
sprintf(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader), "reset #x%x #x%lx %s\n",
|
|
|
|
s7.offset, (uintptr_t)g_ee_main_mem, xdbg::get_current_thread_id().to_string().c_str());
|
2021-11-12 22:00:41 -05:00
|
|
|
#endif
|
2020-10-31 14:07:43 -04:00
|
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Buffer message to compiler indicating some object file has been unloaded.
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void output_unload(const char* name) {
|
2020-10-31 14:07:43 -04:00
|
|
|
if (MasterDebug) {
|
|
|
|
sprintf(strend(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader)),
|
|
|
|
"unload \"%s\"\n", name);
|
|
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Buffer message to compiler indicating some object file has been loaded.
|
|
|
|
*/
|
|
|
|
void output_segment_load(const char* name, Ptr<u8> link_block, u32 flags) {
|
|
|
|
if (MasterDebug) {
|
2020-10-31 14:07:43 -04:00
|
|
|
char* buffer = strend(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader));
|
2020-08-22 22:30:12 -04:00
|
|
|
char true_str[] = "t";
|
|
|
|
char false_str[] = "nil";
|
|
|
|
char* flag_str = (flags & LINK_FLAG_OUTPUT_TRUE) ? true_str : false_str;
|
|
|
|
auto lbp = link_block.cast<ObjectFileHeader>();
|
2020-11-13 22:33:57 -05:00
|
|
|
// modified to also include segment sizes.
|
|
|
|
sprintf(buffer, "load \"%s\" %s #x%x #x%x #x%x #x%x #x%x #x%x\n", name, flag_str,
|
|
|
|
lbp->code_infos[0].offset, lbp->code_infos[1].offset, lbp->code_infos[2].offset,
|
|
|
|
lbp->code_infos[0].size, lbp->code_infos[1].size, lbp->code_infos[2].size);
|
2020-10-31 14:07:43 -04:00
|
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Print to the GOAL print buffer from C
|
|
|
|
* seeks PrintPending to begining of what was just printed.
|
|
|
|
* This is a different behavior from all the other prints!
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void cprintf(const char* format, ...) {
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
char* str = PrintPending.cast<char>().c();
|
|
|
|
if (!PrintPending.offset)
|
2020-10-31 14:07:43 -04:00
|
|
|
str = PrintBufArea.cast<char>().c() + sizeof(ListenerMessageHeader);
|
2020-08-22 22:30:12 -04:00
|
|
|
PrintPending = make_ptr(strend(str)).cast<u8>();
|
|
|
|
vsprintf((char*)PrintPending.c(), format, args);
|
|
|
|
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Print directly to the C stdout
|
|
|
|
* The "k" parameter is ignored, so this is just like printf
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void Msg(s32 k, const char* format, ...) {
|
|
|
|
(void)k;
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vprintf(format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Print directly to the C stdout
|
|
|
|
* This is idential to Msg
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void MsgWarn(const char* format, ...) {
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vprintf(format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Print directly to the C stdout
|
|
|
|
* This is idential to Msg
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void MsgErr(const char* format, ...) {
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vprintf(format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Reverse string in place.
|
|
|
|
* DONE, EXACT
|
|
|
|
*/
|
|
|
|
void reverse(char* str) {
|
|
|
|
s32 i = 0;
|
|
|
|
s32 end = (s32)strlen(str);
|
|
|
|
while (end--, i < end) {
|
|
|
|
char c = str[i];
|
|
|
|
str[i] = str[end];
|
|
|
|
str[end] = c;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Some sort of rounding for printing floating point numbers.
|
|
|
|
* It is unused and believed to be not correct.
|
|
|
|
* Currently copy-pasta from GHIDRA
|
|
|
|
*/
|
|
|
|
char* round(float x, s32* param1, char* start, char* sEnd, char padchar, s32* param4) {
|
|
|
|
char cVar1;
|
|
|
|
char* local_58;
|
|
|
|
float f;
|
|
|
|
|
|
|
|
if (x == 0.00000000) {
|
|
|
|
f = padchar - '0';
|
|
|
|
} else {
|
|
|
|
modff(x * 10.00000000, &f);
|
|
|
|
}
|
|
|
|
if (4.00000000 < f) {
|
|
|
|
while (true) {
|
|
|
|
if (*sEnd == '.') {
|
|
|
|
sEnd = sEnd + -1;
|
|
|
|
}
|
|
|
|
cVar1 = *sEnd;
|
|
|
|
*sEnd = cVar1 + 1;
|
|
|
|
if ((char)(cVar1 + 1) <= '9') {
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
*sEnd = '0';
|
|
|
|
if (sEnd == start)
|
|
|
|
break;
|
|
|
|
sEnd = sEnd + -1;
|
|
|
|
}
|
|
|
|
if (param1 == (int*)0x0) {
|
|
|
|
sEnd[-1] = '1';
|
|
|
|
local_58 = start + -1;
|
|
|
|
} else {
|
|
|
|
*sEnd = '1';
|
|
|
|
*param1 = *param1 + 1;
|
|
|
|
local_58 = start;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
local_58 = start;
|
|
|
|
if (*param4 == 0x2d) {
|
|
|
|
while (true) {
|
|
|
|
if (*sEnd == '.') {
|
|
|
|
sEnd = sEnd + -1;
|
|
|
|
}
|
|
|
|
local_58 = start;
|
|
|
|
if (*sEnd != '0')
|
|
|
|
break;
|
|
|
|
if (sEnd == start) {
|
|
|
|
*param4 = 0;
|
|
|
|
}
|
|
|
|
sEnd = sEnd + -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return local_58;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Convert floating point to string intermediate function.
|
|
|
|
* Places a null character as the first character, then the integers, decimal, fraction digits
|
|
|
|
* Presumably, if rounding worked, it would sometimes add an additional digit in front instead of
|
|
|
|
* the null but rounding doesn't, so you get a null in front every time. There is no null terminator
|
|
|
|
* added, you have to do it yourself. returned value is length : buff[last_char] - buff[1] (not
|
|
|
|
* including the leading null) if the number is negative, *lead_char = '-'. Otherwise it's '\0'.
|
|
|
|
* The negative is not inserted for you.
|
|
|
|
* The precision is how many digits after decimal to print.
|
|
|
|
* The flag could have a 1 to enable rounding, but this doesn't work, so don't use it.
|
|
|
|
* Without rounding the printing is a little bit off but you don't notice unless you look too
|
|
|
|
* closely.
|
|
|
|
*
|
|
|
|
* DONE, added some sanity checks and removed support for "rounding" as round isn't implemented and
|
|
|
|
* rounding is never used in the game.
|
|
|
|
*/
|
|
|
|
s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* buff_end, u32 flags) {
|
|
|
|
// put a null at the beginning of the output
|
|
|
|
*buff_start = 0;
|
|
|
|
s32 forward_count = 0;
|
|
|
|
|
|
|
|
// compute absolute value and set lead char to `-` if needed.
|
|
|
|
float abs_x;
|
|
|
|
if (x < 0.0f) {
|
|
|
|
abs_x = -x;
|
|
|
|
*lead_char = '-';
|
|
|
|
} else {
|
|
|
|
*lead_char = 0;
|
|
|
|
abs_x = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
// nan check
|
|
|
|
u32 abs_x_u32 = *(u32*)&abs_x;
|
|
|
|
if ((abs_x_u32 & 0x7fffffff) == 0x7fffffff) {
|
|
|
|
kstrcpy(buff_start, "NaN");
|
|
|
|
return 3; // the length of NaN
|
|
|
|
}
|
|
|
|
|
|
|
|
// find fraction and integer parts of absolute value.
|
|
|
|
float integer_part;
|
|
|
|
float fraction_part = std::modf(abs_x, &integer_part);
|
|
|
|
|
|
|
|
char* start_ptr = buff_start + 1; // the null terminator is at buff_start[0].
|
|
|
|
char* end_ptr = buff_end - 1; // the last char we can write to.
|
|
|
|
|
|
|
|
// loop over integer digits (increasing significance)
|
|
|
|
while (start_ptr <= end_ptr && (integer_part != 0.0f)) {
|
|
|
|
// the fractional part will be the lowest place integer divided by 10
|
|
|
|
float next_int = std::modf(integer_part / 10.f, &integer_part);
|
|
|
|
|
|
|
|
// the float to round to get the integer
|
|
|
|
float rounder = next_int * 10.f + 0.5f;
|
|
|
|
|
|
|
|
// but wait, the PS2 is stupid and maybe has weird floats that might not convert well.
|
|
|
|
// this checks for very large exponent or negative sign bit, which would only happen if the
|
|
|
|
// float is all messed up.
|
|
|
|
u32 ru32 = *(u32*)&rounder;
|
|
|
|
s32 value;
|
|
|
|
if (((ru32 >> 0x17) & 0xff) < 0x9e) { // exponent
|
|
|
|
value = (char)rounder;
|
|
|
|
} else if (!(ru32 >> 31)) { // sign bit
|
|
|
|
value = 0;
|
2021-01-04 23:32:51 -05:00
|
|
|
// assert(false); // not sure what happens here.
|
2020-08-22 22:30:12 -04:00
|
|
|
} else {
|
|
|
|
value = -1; // happens on NaN's
|
|
|
|
}
|
|
|
|
|
|
|
|
// place number at the end of the buffer and move pointer back
|
|
|
|
*end_ptr = '0' + value;
|
|
|
|
end_ptr--;
|
|
|
|
|
|
|
|
// track how many integers we've written
|
|
|
|
forward_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* count_chrp = start_ptr; // one after the buffer start (null)
|
|
|
|
if (forward_count == 0) { // no integers
|
|
|
|
*start_ptr = '0'; // put leading zero
|
|
|
|
count_chrp = buff_start + 2; // and set count to two after buffer start
|
|
|
|
} else {
|
|
|
|
while (end_ptr = end_ptr + 1,
|
|
|
|
end_ptr < buff_end) { // copy back to the beginning (will be in right order)
|
|
|
|
*count_chrp = *end_ptr;
|
|
|
|
count_chrp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we have digits after decimal, place decimal.
|
|
|
|
if (precision) {
|
|
|
|
*count_chrp = '.';
|
|
|
|
count_chrp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 prec = precision;
|
|
|
|
|
|
|
|
// if we have a fractional part, we may need to either print it, or do rounding.
|
|
|
|
if (fraction_part != 0.0f) {
|
|
|
|
prec = precision;
|
|
|
|
if (precision) {
|
|
|
|
// same loop as before, but only over the number of digits we actually want to print.
|
|
|
|
do {
|
|
|
|
float next_int;
|
|
|
|
fraction_part = std::modf(fraction_part * 10.f, &next_int);
|
|
|
|
u32 ru32 = *(u32*)&next_int;
|
|
|
|
s32 value;
|
|
|
|
if (((ru32 >> 0x17) & 0xff) < 0x9e) {
|
|
|
|
value = (char)next_int;
|
|
|
|
} else if (!(ru32 >> 0x1f)) {
|
|
|
|
value = 0;
|
2021-01-04 23:32:51 -05:00
|
|
|
// assert(false); // not sure what happens here.
|
2020-08-22 22:30:12 -04:00
|
|
|
} else {
|
|
|
|
value = -1; // happens on NaN's
|
|
|
|
}
|
|
|
|
*count_chrp = value + '0';
|
|
|
|
count_chrp++;
|
|
|
|
prec--;
|
|
|
|
} while ((prec) && (fraction_part != 0.f));
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the rounding flag is enabled, we would round here.
|
|
|
|
// however, the rounding flag is always disabled and the rounding code doesn't work.
|
|
|
|
if ((fraction_part != 0.f) && ((flags & 1) != 0)) {
|
|
|
|
start_ptr = round(fraction_part, nullptr, start_ptr, count_chrp - 1, 0, lead_char);
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are any left over digits (fraction part = 0 before we got to the end), append zeros.
|
|
|
|
while (prec = prec - 1, prec != -1) {
|
|
|
|
*count_chrp = '0';
|
|
|
|
count_chrp++;
|
|
|
|
}
|
|
|
|
// return length. not including the null character at the beginning.
|
|
|
|
return count_chrp - start_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Convert floating point to a string.
|
|
|
|
* Don't set the precision too high or you get NaN (if float is longer than 31 digits without
|
|
|
|
* negative sign) Don't set the flags. Rounding is a little bit off, but it adds character. Highly
|
|
|
|
* recommended to pad with spaces, not zeros. If you pad with spaces you get " -1.23" If you pad
|
|
|
|
* with zeros, you get "0000000-1.23" which most people consider to be wrong.
|
|
|
|
* @param out_str : output buffer to write null terminated string into
|
|
|
|
* @param x : number
|
|
|
|
* @param desired_len : length (will pad if under, nothing if over)
|
|
|
|
* @param pad_char : character to pad with
|
|
|
|
* @param precision
|
|
|
|
* @param flags
|
|
|
|
*
|
|
|
|
* DONE
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
void ftoa(char* out_str, float x, s32 desired_len, char pad_char, s32 precision, u32 flags) {
|
|
|
|
char buff[0x100];
|
|
|
|
char* current_buff = buff;
|
|
|
|
s32 lead_char;
|
|
|
|
|
|
|
|
// do conversion, but only write into the first half of the buffer (128 chars)
|
|
|
|
s32 count = cvt_float(x, precision, &lead_char, current_buff, buff + 0x7f, flags);
|
|
|
|
|
|
|
|
// if it ends up larger than 31 characters, it's probably gone horribly wrong and we should just
|
|
|
|
// put NaN instead. Or maybe somebody requested a lot of precision. That would be stupid and they
|
|
|
|
// deserve to see NaN.
|
|
|
|
if (count > 0x3f) {
|
|
|
|
kstrcpy(current_buff, "NaN");
|
|
|
|
count = 3;
|
|
|
|
lead_char = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// always true because we don't round,
|
|
|
|
if (buff[0] == 0) {
|
|
|
|
current_buff = buff + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// length, including the leading negative (if we need it).
|
|
|
|
s32 real_count = (lead_char != 0) + count;
|
|
|
|
|
|
|
|
char* out_ptr = out_str;
|
|
|
|
|
|
|
|
// pad
|
|
|
|
if ((desired_len > 0) && (desired_len > real_count)) {
|
|
|
|
for (s32 i = 0; i < (desired_len - real_count); i++) {
|
|
|
|
*out_ptr = pad_char;
|
|
|
|
out_ptr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// leading
|
|
|
|
if (lead_char) {
|
|
|
|
*out_ptr = lead_char;
|
|
|
|
out_ptr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy numbers
|
|
|
|
for (s32 i = 0; i < count; i++) {
|
|
|
|
*out_ptr = *current_buff;
|
|
|
|
out_ptr++;
|
|
|
|
current_buff++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// null terminate!
|
|
|
|
*out_ptr = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Convert integer to string.
|
|
|
|
* @param buffer : buffer to print into. Must be at least as long as the longest possible number to
|
|
|
|
* print
|
|
|
|
* @param value : value to print.
|
|
|
|
* @param base : base to print in (2, 10, 16 supported)
|
|
|
|
* @param length : length. if shorter than length, pad with pad. If longer, only truncate leading
|
|
|
|
* f's 1's in base 16 or 2
|
|
|
|
* @param pad : character to pad with
|
|
|
|
* @param flags : flag. Only the 2nd bit is used, which will disable negative sings on
|
|
|
|
* binary/hexadecimal truncated numbers. Something like -1 (0xffffffff) will print as -fffffff....
|
|
|
|
*/
|
|
|
|
|
|
|
|
char* kitoa(char* buffer, s64 value, u64 base, s32 length, char pad, u32 flag) {
|
|
|
|
s64 negativeValue = 0;
|
|
|
|
s64 value_to_print = value;
|
|
|
|
|
|
|
|
// if negative and base ten, we print the opposite of the value and add a negative sign
|
|
|
|
if ((value < 0) && base == 10) {
|
|
|
|
negativeValue = value;
|
|
|
|
value_to_print = -value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write number in reverse
|
|
|
|
int count = 0;
|
|
|
|
do {
|
|
|
|
buffer[count++] = ConvertTable[(u64)value_to_print % (u64)base];
|
|
|
|
value_to_print = (u64)value_to_print / (u64)base;
|
|
|
|
} while (value_to_print);
|
|
|
|
|
|
|
|
// append negative if we need to
|
|
|
|
if (negativeValue < 0) {
|
|
|
|
buffer[count++] = '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
// pad (probably some sort of for loop)
|
|
|
|
s32 rLen = length;
|
|
|
|
if (0 < length - count) {
|
|
|
|
rLen = length - count;
|
|
|
|
while (0 < rLen) {
|
|
|
|
buffer[count++] = pad;
|
|
|
|
rLen--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// truncate f's / 1's
|
|
|
|
if (rLen > 0 && value < 0 && (base == 2 || base == 16) && rLen < count) {
|
|
|
|
char c = (base == 16) ? 'f' : '1';
|
|
|
|
|
|
|
|
while (rLen < count && (buffer[count - 1] == c)) {
|
|
|
|
count--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(flag & 2)) {
|
|
|
|
buffer[count++] = '-';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// null terminate, reverse, return!
|
|
|
|
buffer[count] = 0;
|
|
|
|
reverse(buffer);
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Convert 128-bit integer to string. Not implemented because it is never used in the game.
|
|
|
|
* It would also require passing 128-bit values between GOAL and C++ and this is not worth
|
|
|
|
* implementing. It is only used by the "format" function, which cannot use it properly. "format"
|
|
|
|
* uses C varags, but 128-bit varags don't work, so "format" always passes 0 for quadword printing.
|
|
|
|
*/
|
|
|
|
void kqtoa() {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
struct format_struct {
|
|
|
|
char data[0x40];
|
|
|
|
void reset() {
|
|
|
|
for (auto& c : data)
|
|
|
|
c = -1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* The GOAL "format" function. The actual function is named "format". However, GOAL's calling
|
|
|
|
* convention differs from x86-64, so GOAL cannot directly call format. There is an assembly
|
|
|
|
* function in format_wrapper.nasm named format. It takes the GOAL argument registers, stores them
|
|
|
|
* in an array on the stack, and calls this function with a pointer to that array.
|
|
|
|
*
|
|
|
|
* This function is a disaster. The Ghidra analyzer completely fails on it, so this is done by hand.
|
|
|
|
*
|
|
|
|
* To make this work correctly from GOAL with up to 8 arguments, there is an assembly function
|
|
|
|
* defined in format_wrapper.nasm that places the GOAL arguments on the stack and calls this
|
|
|
|
* format_impl function with a single argument that is a pointer to the argument array.
|
|
|
|
*/
|
|
|
|
s32 format_impl(uint64_t* args) {
|
|
|
|
// first two args are dest, format string
|
|
|
|
uint64_t* arg_regs = args + 2;
|
|
|
|
|
|
|
|
// data for arguments in a format command
|
|
|
|
format_struct argument_data[8];
|
|
|
|
|
|
|
|
u32 arg_reg_idx = 0;
|
|
|
|
|
|
|
|
// the gstring
|
|
|
|
char* format_gstring = Ptr<char>(args[1]).c();
|
|
|
|
|
|
|
|
u32 original_dest = args[0];
|
|
|
|
|
|
|
|
// set up print pending
|
|
|
|
char* print_temp = PrintPending.cast<char>().c();
|
|
|
|
if (!PrintPending.offset) {
|
2020-10-31 14:07:43 -04:00
|
|
|
print_temp = PrintBufArea.cast<char>().c() + sizeof(ListenerMessageHeader);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
PrintPending = make_ptr(strend(print_temp)).cast<u8>();
|
|
|
|
|
|
|
|
// what we write to
|
|
|
|
char* output_ptr = PrintPending.cast<char>().c();
|
|
|
|
|
|
|
|
// convert gstring to cstring
|
|
|
|
char* format_cstring = format_gstring + 4;
|
|
|
|
|
|
|
|
// mysteries
|
|
|
|
char* PrintPendingLocal2 = PrintPending.cast<char>().c();
|
|
|
|
char* PrintPendingLocal3 = output_ptr;
|
|
|
|
|
|
|
|
// start by computing indentation
|
|
|
|
u32 indentation = 0;
|
|
|
|
|
|
|
|
// read goal binteger
|
|
|
|
if (print_column.offset) {
|
|
|
|
// added the if check so we can format even if the kernel didn't load right.
|
|
|
|
indentation = (*print_column) >> 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
// which arg we're on
|
|
|
|
u32 arg_idx = 0;
|
|
|
|
|
|
|
|
// if last char was newline and we have tabs, do tabs
|
|
|
|
if (indentation && output_ptr[-1] == '\n') {
|
|
|
|
for (u32 i = 0; i < indentation; i++) {
|
|
|
|
*output_ptr = ' ';
|
|
|
|
output_ptr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// input pointer
|
|
|
|
char* format_ptr = format_cstring;
|
|
|
|
|
|
|
|
// loop over the format string
|
|
|
|
while (*format_ptr) {
|
|
|
|
// got a command?
|
|
|
|
if (*format_ptr == '~') {
|
|
|
|
char* arg_start = format_ptr;
|
|
|
|
// get some arguments
|
|
|
|
arg_idx = 0;
|
|
|
|
u8 justify = 0;
|
|
|
|
for (auto& x : argument_data) {
|
|
|
|
x.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// read arguments
|
|
|
|
while ((u8)(format_ptr[1] - '0') < 10 || // number 0 to 10
|
|
|
|
format_ptr[1] == ',' || // comma
|
|
|
|
format_ptr[1] == '\'' || // quote
|
|
|
|
format_ptr[1] == '`' || // backtick
|
|
|
|
(argument_data[arg_idx].data[0] == -1 &&
|
|
|
|
(format_ptr[1] == '-' || format_ptr[1] == '+') // flags1 == -1 && +/-
|
|
|
|
)) {
|
|
|
|
// here format_ptr[1] points to next unread character in argument
|
|
|
|
// format_ptr[0] is originally the ~
|
|
|
|
// should exit loop with format_ptr[1] == the command character
|
|
|
|
char arg_char = format_ptr[1]; // gVar1
|
|
|
|
|
|
|
|
if (arg_char == ',') {
|
|
|
|
// advance to next argument
|
|
|
|
arg_idx++; // increment which argument we're on
|
|
|
|
format_ptr++; // increment past comma, and try again
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// character argument
|
|
|
|
if (arg_char == '\'') { // 0x27
|
|
|
|
argument_data[arg_idx].data[0] = format_ptr[2];
|
|
|
|
format_ptr += 2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// string argument
|
|
|
|
if (arg_char == '`') { // 0x60
|
|
|
|
u32 i = 0;
|
|
|
|
format_ptr += 2;
|
|
|
|
// read string
|
|
|
|
while (*format_ptr != '`') {
|
|
|
|
argument_data[arg_idx].data[i] = *format_ptr;
|
|
|
|
i++;
|
|
|
|
format_ptr++;
|
|
|
|
}
|
|
|
|
// null terminate
|
|
|
|
argument_data[arg_idx].data[i] = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arg_char == '-') { // 0x2d
|
|
|
|
// negative flag
|
|
|
|
argument_data[arg_idx].data[1] = 1;
|
|
|
|
format_ptr++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arg_char == '+') { // 0x2b
|
|
|
|
// positive flag does nothing
|
|
|
|
format_ptr++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise:
|
|
|
|
|
|
|
|
// null terminate if we got no args
|
|
|
|
if (argument_data[arg_idx].data[0] == -1) {
|
|
|
|
argument_data[arg_idx].data[0] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise it's a number
|
|
|
|
argument_data[arg_idx].data[0] = argument_data[arg_idx].data[0] * 10 + arg_char - '0';
|
|
|
|
format_ptr++;
|
|
|
|
} // end argument while
|
|
|
|
|
|
|
|
// switch on command
|
|
|
|
switch (format_ptr[1]) {
|
|
|
|
// offset of 0x25
|
|
|
|
|
|
|
|
case '%': // newline
|
|
|
|
*output_ptr = '\n';
|
|
|
|
output_ptr++;
|
|
|
|
// indent the next line if there is one
|
|
|
|
if (indentation && format_ptr[2]) {
|
|
|
|
for (u32 i = 0; i < indentation; i++) {
|
|
|
|
*output_ptr = ' ';
|
|
|
|
output_ptr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '~': // tilde escape
|
|
|
|
*output_ptr = '~';
|
|
|
|
output_ptr++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// pass through arguments
|
|
|
|
case 'H': // 23 -> 48, H
|
|
|
|
case 'J': // 25 -> 4A, J
|
|
|
|
case 'K': // 26 -> 4B, K
|
|
|
|
case 'L': // 27 -> 4C, L
|
|
|
|
case 'N': // 29 -> 4E, N
|
|
|
|
case 'V': // 31 -> 56, V
|
|
|
|
case 'W': // 32 -> 57, W
|
|
|
|
case 'Y': // 34 -> 59, Y
|
|
|
|
case 'Z': // 35 -> 5A, Z
|
|
|
|
case 'h':
|
|
|
|
case 'j':
|
|
|
|
case 'k':
|
|
|
|
case 'l':
|
|
|
|
case 'n':
|
|
|
|
case 'v':
|
|
|
|
case 'w':
|
|
|
|
case 'y':
|
|
|
|
case 'z':
|
|
|
|
while (arg_start < format_ptr + 1) {
|
|
|
|
*output_ptr = *arg_start;
|
|
|
|
arg_start++;
|
|
|
|
output_ptr++;
|
|
|
|
}
|
|
|
|
*output_ptr = format_ptr[1];
|
|
|
|
output_ptr++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'G': // like %s, prints a C string
|
|
|
|
case 'g': {
|
|
|
|
*output_ptr = 0;
|
|
|
|
u32 in = arg_regs[arg_reg_idx++];
|
|
|
|
kstrcat(output_ptr, Ptr<char>(in).c());
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'A': // print a boxed object
|
|
|
|
case 'a': // pad,padchar (like ) ~8,'0A
|
|
|
|
{
|
|
|
|
s8 arg0 = argument_data[0].data[0];
|
|
|
|
s32 desired_length = arg0;
|
|
|
|
*output_ptr = 0;
|
|
|
|
u32 in = arg_regs[arg_reg_idx++];
|
|
|
|
print_object(in);
|
|
|
|
if (desired_length != -1) {
|
|
|
|
s32 print_len = strlen(output_ptr);
|
|
|
|
if (desired_length < print_len) {
|
|
|
|
// too long!
|
|
|
|
if (desired_length > 1) { // mark with tilde that we will truncate
|
|
|
|
output_ptr[desired_length - 1] = '~';
|
|
|
|
}
|
|
|
|
output_ptr[desired_length] = 0; // and truncate
|
|
|
|
} else if (print_len < desired_length) {
|
|
|
|
// too short
|
|
|
|
if (justify == 0) {
|
|
|
|
char pad = ' ';
|
|
|
|
if (argument_data[1].data[0] != -1) {
|
|
|
|
pad = argument_data[1].data[0];
|
|
|
|
}
|
|
|
|
kstrinsert(output_ptr, pad, desired_length - print_len);
|
|
|
|
} else {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
// output_ptr = strend(output_ptr);
|
|
|
|
// while(0 < (desired_length - print_len)) {
|
|
|
|
// char pad = ' ';
|
|
|
|
// if(argument_data[0].data[1] != -1) {
|
|
|
|
// pad = argument_data[0].data[1];
|
|
|
|
// }
|
|
|
|
// output_ptr[0] = pad;
|
|
|
|
// output_ptr++;
|
|
|
|
//
|
|
|
|
// }
|
|
|
|
// *output_ptr = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'S': // like A, but strings are printed without quotes
|
|
|
|
case 's': {
|
|
|
|
s8 arg0 = argument_data[0].data[0];
|
|
|
|
s32 desired_length = arg0;
|
|
|
|
*output_ptr = 0;
|
|
|
|
u32 in = arg_regs[arg_reg_idx++];
|
|
|
|
|
|
|
|
// if it's a string
|
|
|
|
if (((in & 0x7) == 0x4) && *Ptr<u32>(in - 4) == *(s7 + FIX_SYM_STRING_TYPE)) {
|
|
|
|
cprintf("%s", Ptr<char>(in).c() + 4);
|
|
|
|
} else {
|
|
|
|
print_object(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (desired_length != -1) {
|
|
|
|
s32 print_len = strlen(output_ptr);
|
|
|
|
if (desired_length < print_len) {
|
|
|
|
// too long!
|
|
|
|
if (desired_length > 1) { // mark with tilde that we will truncate
|
|
|
|
output_ptr[desired_length - 1] = '~';
|
|
|
|
}
|
|
|
|
output_ptr[desired_length] = 0; // and truncate
|
|
|
|
} else if (print_len < desired_length) {
|
|
|
|
// too short
|
|
|
|
if (justify == 0) {
|
|
|
|
char pad = ' ';
|
|
|
|
if (argument_data[1].data[0] != -1) {
|
|
|
|
pad = argument_data[1].data[0];
|
|
|
|
}
|
|
|
|
kstrinsert(output_ptr, pad, desired_length - print_len);
|
|
|
|
|
|
|
|
} else {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
// output_ptr = strend(output_ptr);
|
|
|
|
// u32 l140 = 0;
|
|
|
|
// while(l140 < (desired_length - print_len)) {
|
|
|
|
// char* l108 = output_ptr;
|
|
|
|
//
|
|
|
|
// char pad = ' ';
|
|
|
|
// if(argument_data[0].data[1] != -1) {
|
|
|
|
// pad = argument_data[0].data[1];
|
|
|
|
// }
|
|
|
|
// output_ptr[0] = pad;
|
|
|
|
// output_ptr++;
|
|
|
|
// }
|
|
|
|
// *output_ptr = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'C': // character
|
|
|
|
case 'c':
|
|
|
|
*output_ptr = arg_regs[arg_reg_idx++];
|
|
|
|
output_ptr++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'P': // like ~A, but can specify type explicitly
|
|
|
|
case 'p': {
|
|
|
|
*output_ptr = 0;
|
|
|
|
s8 arg0 = argument_data[0].data[0];
|
|
|
|
u32 in = arg_regs[arg_reg_idx++];
|
|
|
|
if (arg0 == -1) {
|
|
|
|
print_object(in);
|
|
|
|
} else {
|
|
|
|
auto sym = find_symbol_from_c(argument_data[0].data);
|
|
|
|
if (sym.offset) {
|
|
|
|
Ptr<Type> type = *sym.cast<Ptr<Type>>();
|
|
|
|
if (type.offset) {
|
2020-08-26 23:07:55 -04:00
|
|
|
call_method_of_type(in, type, GOAL_PRINT_METHOD);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
} else {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false); // bad type.
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'I': // like ~P, but calls inpsect
|
|
|
|
case 'i': {
|
|
|
|
*output_ptr = 0;
|
|
|
|
s8 arg0 = argument_data[0].data[0];
|
|
|
|
u32 in = arg_regs[arg_reg_idx++];
|
|
|
|
if (arg0 == -1) {
|
|
|
|
inspect_object(in);
|
|
|
|
} else {
|
|
|
|
auto sym = find_symbol_from_c(argument_data[0].data);
|
|
|
|
if (sym.offset) {
|
|
|
|
Ptr<Type> type = *sym.cast<Ptr<Type>>();
|
|
|
|
if (type.offset) {
|
2020-08-26 23:07:55 -04:00
|
|
|
call_method_of_type(in, type, GOAL_INSPECT_METHOD);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
} else {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false); // bad type
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'Q': // not yet implemented. hopefully andy gavin finishes this one soon.
|
|
|
|
case 'q':
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'X': // hex, 64 bit, pad padchar
|
|
|
|
case 'x': {
|
|
|
|
char pad = '0';
|
|
|
|
if (argument_data[1].data[0] != -1) {
|
|
|
|
pad = argument_data[1].data[0];
|
|
|
|
}
|
|
|
|
u64 in = arg_regs[arg_reg_idx++];
|
|
|
|
kitoa(output_ptr, in, 16, argument_data[0].data[0], pad, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'D': // integer 64, pad padchar
|
|
|
|
case 'd': {
|
|
|
|
char pad = ' ';
|
|
|
|
if (argument_data[1].data[0] != -1) {
|
|
|
|
pad = argument_data[1].data[0];
|
|
|
|
}
|
|
|
|
u64 in = arg_regs[arg_reg_idx++];
|
|
|
|
kitoa(output_ptr, in, 10, argument_data[0].data[0], pad, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'B': // integer 64, pad padchar
|
|
|
|
case 'b': {
|
|
|
|
char pad = '0';
|
|
|
|
if (argument_data[1].data[0] != -1) {
|
|
|
|
pad = argument_data[1].data[0];
|
|
|
|
}
|
|
|
|
u64 in = arg_regs[arg_reg_idx++];
|
|
|
|
kitoa(output_ptr, in, 2, argument_data[0].data[0], pad, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'F': // float 12 pad, 4 precision
|
|
|
|
{
|
|
|
|
float in = *(float*)&arg_regs[arg_reg_idx++];
|
|
|
|
ftoa(output_ptr, in, 0xc, ' ', 4, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'f': // float with args
|
|
|
|
{
|
|
|
|
float in = *(float*)&arg_regs[arg_reg_idx++];
|
|
|
|
s8 pad_length = argument_data[0].data[0];
|
|
|
|
s8 pad_char = argument_data[1].data[0];
|
|
|
|
if (pad_char == -1)
|
|
|
|
pad_char = ' ';
|
|
|
|
s8 precision = argument_data[2].data[0];
|
|
|
|
if (precision == -1)
|
|
|
|
precision = 4;
|
|
|
|
ftoa(output_ptr, in, pad_length, pad_char, precision, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'R': // rotation degrees
|
|
|
|
case 'r': {
|
|
|
|
float in = *(float*)&arg_regs[arg_reg_idx++];
|
|
|
|
s8 pad_length = argument_data[0].data[0];
|
|
|
|
s8 pad_char = argument_data[1].data[0];
|
|
|
|
if (pad_char == -1)
|
|
|
|
pad_char = ' ';
|
|
|
|
s8 precision = argument_data[2].data[0];
|
|
|
|
if (precision == -1)
|
|
|
|
precision = 4;
|
|
|
|
ftoa(output_ptr, in * 360.f / 65536.f, pad_length, pad_char, precision, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'M': // distance meters
|
|
|
|
case 'm': {
|
|
|
|
float in = *(float*)&arg_regs[arg_reg_idx++];
|
|
|
|
s8 pad_length = argument_data[0].data[0];
|
|
|
|
s8 pad_char = argument_data[1].data[0];
|
|
|
|
if (pad_char == -1)
|
|
|
|
pad_char = ' ';
|
|
|
|
s8 precision = argument_data[2].data[0];
|
|
|
|
if (precision == -1)
|
|
|
|
precision = 4;
|
|
|
|
ftoa(output_ptr, in / 4096.f, pad_length, pad_char, precision, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'E': // time seconds
|
|
|
|
case 'e': {
|
|
|
|
s64 in = arg_regs[arg_reg_idx++];
|
|
|
|
s8 pad_length = argument_data[0].data[0];
|
|
|
|
s8 pad_char = argument_data[0].data[1];
|
|
|
|
if (pad_char == -1)
|
|
|
|
pad_char = ' ';
|
|
|
|
s8 precision = argument_data[0].data[2];
|
|
|
|
if (precision == -1)
|
|
|
|
precision = 4;
|
|
|
|
float value;
|
|
|
|
if (in < 0) {
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false); // i don't get this one
|
2020-08-22 22:30:12 -04:00
|
|
|
} else {
|
|
|
|
value = in;
|
|
|
|
}
|
|
|
|
ftoa(output_ptr, value / 300.f, pad_length, pad_char, precision, 0);
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 'T':
|
|
|
|
case 't': {
|
|
|
|
sprintf(output_ptr, "\t");
|
|
|
|
output_ptr = strend(output_ptr);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
MsgErr("format: unknown code 0x%02x\n", format_ptr[1]);
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false);
|
2020-08-22 22:30:12 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
format_ptr++;
|
|
|
|
} else {
|
|
|
|
// got normal char, just copy it
|
|
|
|
*output_ptr = *format_ptr;
|
|
|
|
output_ptr++;
|
|
|
|
}
|
|
|
|
format_ptr++;
|
|
|
|
} // end format string while
|
|
|
|
|
|
|
|
// end
|
|
|
|
*output_ptr = 0;
|
|
|
|
output_ptr++;
|
|
|
|
|
|
|
|
if (original_dest == s7.offset + FIX_SYM_TRUE) {
|
|
|
|
// do nothing, we're done
|
|
|
|
return 0;
|
|
|
|
} else if (original_dest == s7.offset + FIX_SYM_FALSE) {
|
|
|
|
// #f means print to new string
|
|
|
|
u32 string = make_string_from_c(PrintPendingLocal3);
|
|
|
|
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
|
|
|
|
*PrintPendingLocal3 = 0;
|
|
|
|
return string;
|
|
|
|
} else if (original_dest == 0) {
|
|
|
|
printf("%s", PrintPendingLocal3);
|
|
|
|
fflush(stdout);
|
|
|
|
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
|
|
|
|
*PrintPendingLocal3 = 0;
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if ((original_dest & OFFSET_MASK) == BASIC_OFFSET) {
|
|
|
|
Ptr<Type> type = *Ptr<Ptr<Type>>(original_dest - 4);
|
|
|
|
if (type == *Ptr<Ptr<Type>>(s7.offset + FIX_SYM_STRING_TYPE)) {
|
|
|
|
u32 len = *Ptr<u32>(original_dest);
|
|
|
|
char* str = Ptr<char>(original_dest + 4).c();
|
|
|
|
kstrncat(str, PrintPendingLocal3, len);
|
|
|
|
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
|
|
|
|
*PrintPendingLocal3 = 0;
|
|
|
|
return 0;
|
|
|
|
} else if (type == *Ptr<Ptr<Type>>(s7.offset + FIX_SYM_FILE_STREAM_TYPE)) {
|
2021-10-20 19:49:32 -04:00
|
|
|
size_t len = strlen(PrintPendingLocal3);
|
|
|
|
// sceWrite
|
|
|
|
ee::sceWrite(*Ptr<s32>(original_dest + 12), PrintPendingLocal3, len);
|
|
|
|
|
|
|
|
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
|
|
|
|
*PrintPendingLocal3 = 0;
|
|
|
|
return 0;
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false); // unknown destination
|
2020-08-22 22:30:12 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-12-28 18:37:05 -05:00
|
|
|
assert(false); // ??????
|
2020-08-22 22:30:12 -04:00
|
|
|
return 7;
|
2021-01-04 23:32:51 -05:00
|
|
|
}
|