mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
e630b50690
* temp * split up kprint, other than format * start kmachine * split kmachine * split kscheme * split klink * split klisten * split remaining * jak2 ckernel gets to nokernel loop
572 lines
17 KiB
C++
572 lines
17 KiB
C++
#include "kprint.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include "common/cross_os_debug/xdbg.h"
|
|
#include "common/listener_common.h"
|
|
|
|
#include "game/kernel/common/Ptr.h"
|
|
#include "game/kernel/common/fileio.h"
|
|
#include "game/kernel/common/klink.h"
|
|
#include "game/kernel/common/kmalloc.h"
|
|
#include "game/kernel/common/kscheme.h"
|
|
|
|
// 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_common() {
|
|
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.
|
|
*/
|
|
void init_output() {
|
|
// Note: slightly different behavior in jak 1/jak 2 here.
|
|
// I think the jak 2 version makes clear_wrong do the wrong thing if you are masterdebug but not
|
|
// debugsegment.
|
|
bool use_debug;
|
|
switch (g_game_version) {
|
|
case GameVersion::Jak1:
|
|
use_debug = MasterDebug;
|
|
break;
|
|
case GameVersion::Jak2:
|
|
use_debug = MasterDebug && DebugSegment;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
if (use_debug) {
|
|
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)
|
|
*/
|
|
void clear_output() {
|
|
if (MasterDebug) {
|
|
kstrcpy((char*)Ptr<u8>(OutputBufArea + sizeof(ListenerMessageHeader)).c(), "");
|
|
OutputPending = Ptr<u8>(0);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Clear all data in the print buffer
|
|
*/
|
|
void clear_print() {
|
|
*Ptr<u8>(PrintBufArea + sizeof(ListenerMessageHeader)) = 0;
|
|
PrintPending = Ptr<u8>(0);
|
|
}
|
|
|
|
/*!
|
|
* Buffer message to compiler indicating the target has reset.
|
|
* Write to the beginning of the output buffer.
|
|
*/
|
|
void reset_output() {
|
|
if (MasterDebug) {
|
|
// 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
|
|
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());
|
|
#endif
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Buffer message to compiler indicating some object file has been unloaded.
|
|
* DONE, EXACT
|
|
*/
|
|
void output_unload(const char* name) {
|
|
if (MasterDebug) {
|
|
sprintf(strend(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader)),
|
|
"unload \"%s\"\n", name);
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* 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) {
|
|
char* buffer = strend(OutputBufArea.cast<char>().c() + sizeof(ListenerMessageHeader));
|
|
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>();
|
|
// modified to also include segment sizes, and work from opengoal linker
|
|
// original game used link_block_v3's here.
|
|
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);
|
|
OutputPending = OutputBufArea + sizeof(ListenerMessageHeader);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* 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)
|
|
str = PrintBufArea.cast<char>().c() + sizeof(ListenerMessageHeader);
|
|
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
|
|
* (not checked in jak2)
|
|
*/
|
|
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.
|
|
*
|
|
* Not checked closely in jak 2.
|
|
*/
|
|
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;
|
|
// ASSERT(false); // not sure what happens here.
|
|
} 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;
|
|
// ASSERT(false); // not sure what happens here.
|
|
} 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);
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
* Not checked super closely in jak 2.
|
|
*/
|
|
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....
|
|
* Not checked super closely in jak 2
|
|
*/
|
|
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() {
|
|
ASSERT(false);
|
|
} |