mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 15:00:27 +02:00
terminal_printf started its variable arguments into a char *, which is what va_list is on the Xbox, 32-bit x86 and Apple silicon, but not on x86-64, where va_list is a one-element array of a register-save structure. On an Intel Mac (or under Rosetta) va_start wrote through an uninitialised pointer and vsnprintf read garbage, so the first terminal line printed with arguments crashed the game. Choosing an empty profile slot is such a line: "player profile begin editing" fails, as it does on the Xbox, and console_warning reports it through terminal_printf, so setting up a profile crashed on Intel Macs. (cherry picked from commit 024e41b8cb1553d7ad9b96233860848d49e5ad13)
516 lines
13 KiB
C
516 lines
13 KiB
C
/*
|
|
TERMINAL.C
|
|
|
|
symbols in this file:
|
|
000D2B20 0040:
|
|
_terminal_initialize (0000)
|
|
000D2B60 0030:
|
|
_terminal_dispose (0000)
|
|
000D2B90 0090:
|
|
_code_000d2b90 (0000)
|
|
000D2C20 0030:
|
|
_terminal_clear (0000)
|
|
000D2C50 0010:
|
|
_terminal_gets_active (0000)
|
|
000D2C60 0080:
|
|
_terminal_gets_begin (0000)
|
|
000D2CE0 0020:
|
|
_terminal_gets_end (0000)
|
|
000D2D00 00c0:
|
|
_code_000d2d00 (0000)
|
|
000D2DC0 0050:
|
|
_code_000d2dc0 (0000)
|
|
000D2E10 02b0:
|
|
_terminal_draw (0000)
|
|
000D30C0 00a0:
|
|
_code_000d30c0 (0000)
|
|
000D3160 0030:
|
|
_terminal_update (0000)
|
|
000D3190 0130:
|
|
_terminal_printf (0000)
|
|
002712D0 0006:
|
|
_terminal_tab_stops (0000)
|
|
002712D8 0010:
|
|
??_C@_0BA@LJDMLJDP@terminal?5output?$AA@ (0000)
|
|
002712E8 0006:
|
|
??_C@_05HDBKIDE@state?$AA@ (0000)
|
|
002712F0 0024:
|
|
??_C@_0CE@JGPOFDIE@c?3?2halo?2SOURCE?2interface?2termina@ (0000)
|
|
00271314 0015:
|
|
??_C@_0BF@NFLIKMOD@new_line_index?$CB?$DNNONE?$AA@ (0000)
|
|
00271330 0050:
|
|
??_C@_0FA@BEBNLNOA@terminal_printf?5call?5generated?5?$CF@ (0000)
|
|
00301D99 0001:
|
|
_terminal_render_enable (0000)
|
|
00454224 001c:
|
|
_bss_00454224 (0000)
|
|
*/
|
|
|
|
/* ---------- headers */
|
|
|
|
#include "cseries.h"
|
|
#include "terminal.h"
|
|
|
|
#include "cache_files.h"
|
|
#include "game.h"
|
|
#include "data.h"
|
|
#include "interface.h"
|
|
#include "console.h"
|
|
#include "telnet_console.h"
|
|
#include "rasterizer.h"
|
|
#include "render.h"
|
|
#include "main/main.h"
|
|
#include "draw_string.h"
|
|
#include "byte_swapping.h"
|
|
#include "tag_groups.h"
|
|
#include "text_group.h"
|
|
#include "font_group.h"
|
|
|
|
/* ---------- constants */
|
|
|
|
enum
|
|
{
|
|
OUTPUT_LINGER_TIME = 120,
|
|
OUTPUT_FADE_TIME = 30,
|
|
OUTPUT_TOTAL_TIME = 150,
|
|
OUTPUT_MAXIMUM_LINES = 32,
|
|
INSERTION_POINT_FLASH_PERIOD = 500,
|
|
};
|
|
|
|
/* ---------- macros */
|
|
|
|
#define output_line_get(index) ((struct output_line_datum *)datum_get(terminal_globals.output_lines, (index)))
|
|
|
|
/* ---------- structures */
|
|
|
|
struct terminal_globals
|
|
{
|
|
boolean initialized;
|
|
struct data_array *output_lines;
|
|
long newest_output_line_index;
|
|
long oldest_output_line_index;
|
|
struct terminal_gets_state *input_state;
|
|
boolean insertion_point_visible;
|
|
long last_insertion_point_toggle;
|
|
};
|
|
|
|
struct output_line_datum
|
|
{
|
|
short identifier;
|
|
long newer_line_index;
|
|
long older_line_index;
|
|
boolean tabstop;
|
|
char buffer[256];
|
|
real_argb_color color;
|
|
long timer;
|
|
};
|
|
|
|
/* ---------- prototypes */
|
|
|
|
static void terminal_remove_line(long line_index);
|
|
static boolean terminal_update_input(void);
|
|
static void terminal_update_output(void);
|
|
static long terminal_new_line(void);
|
|
|
|
/* ---------- globals */
|
|
|
|
boolean terminal_render_enable = TRUE;
|
|
|
|
/* name from the 2003 PC demo PDB and the HCEX PDB (terminal file static const short[3]); January's 6 bytes
|
|
* are identical to the demo's and it has no public for it (static) */
|
|
static const short terminal_tab_stops[] = {160, 320, 470};
|
|
|
|
static struct terminal_globals terminal_globals = {0};
|
|
|
|
/* ---------- public code */
|
|
|
|
void terminal_initialize(
|
|
void)
|
|
{
|
|
terminal_globals.output_lines = data_new("terminal output", OUTPUT_MAXIMUM_LINES, sizeof(struct output_line_datum));
|
|
terminal_globals.initialized = TRUE;
|
|
data_make_valid(terminal_globals.output_lines);
|
|
terminal_globals.input_state = NULL;
|
|
terminal_globals.newest_output_line_index = NONE;
|
|
terminal_globals.oldest_output_line_index = NONE;
|
|
|
|
return;
|
|
}
|
|
|
|
void terminal_dispose(
|
|
void)
|
|
{
|
|
if (terminal_globals.output_lines)
|
|
{
|
|
if (terminal_globals.output_lines->valid)
|
|
{
|
|
data_make_invalid(terminal_globals.output_lines);
|
|
}
|
|
data_dispose(terminal_globals.output_lines);
|
|
}
|
|
terminal_globals.initialized = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
void terminal_clear(
|
|
void)
|
|
{
|
|
if (terminal_globals.initialized)
|
|
{
|
|
terminal_globals.newest_output_line_index = NONE;
|
|
terminal_globals.oldest_output_line_index = NONE;
|
|
data_delete_all(terminal_globals.output_lines);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
boolean terminal_gets_active(
|
|
void)
|
|
{
|
|
return terminal_globals.input_state != NULL;
|
|
}
|
|
|
|
boolean terminal_gets_begin(
|
|
struct terminal_gets_state *state)
|
|
{
|
|
boolean result = FALSE;
|
|
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 195, state);
|
|
|
|
if (!terminal_globals.input_state)
|
|
{
|
|
terminal_globals.input_state = state;
|
|
terminal_globals.input_state->edit.buffer = terminal_globals.input_state->result;
|
|
|
|
terminal_globals.input_state->edit.maximum_length = TERMINAL_GETS_MAXIMUM_LINE_LENGTH;
|
|
edit_text_new(&terminal_globals.input_state->edit);
|
|
state->key_count = 0;
|
|
result = TRUE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void terminal_gets_end(
|
|
struct terminal_gets_state *state)
|
|
{
|
|
if (state==terminal_globals.input_state)
|
|
{
|
|
terminal_globals.input_state = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void terminal_draw(
|
|
void)
|
|
{
|
|
char buffer[288];
|
|
rectangle2d terminal_gets_bounds;
|
|
rectangle2d terminal_render_bounds;
|
|
rectangle2d line_bounds;
|
|
real_argb_color color;
|
|
|
|
long font_tag_index = interface_get_tag_index(_interface_font_terminal);
|
|
|
|
if (terminal_globals.initialized)
|
|
{
|
|
struct font_header *font = font_definition_get(font_tag_index);
|
|
short line_height = font->ascending_height + font->descending_height + font->leading_height;
|
|
if (terminal_globals.input_state)
|
|
{
|
|
short buff_len;
|
|
|
|
buffer[0] = '\0';
|
|
terminal_globals.input_state->prompt[TERMINAL_GETS_MAXIMUM_PROMPT_LENGTH] = '\0';
|
|
strcat(buffer, terminal_globals.input_state->prompt);
|
|
terminal_globals.input_state->result[TERMINAL_PRINTF_MAXIMUM_LINE_LENGTH] = '\0';
|
|
buff_len = csstrlen(buffer);
|
|
|
|
strcpy(&buffer[buff_len], terminal_globals.input_state->result);
|
|
|
|
terminal_gets_bounds.x0 = render.camera.window_bounds.x0;
|
|
terminal_gets_bounds.x1 = render.camera.window_bounds.x1;
|
|
terminal_gets_bounds.y0 = render.camera.window_bounds.y1 - line_height;
|
|
terminal_gets_bounds.y1 = render.camera.window_bounds.y1;
|
|
|
|
offset_rectangle2d(&terminal_gets_bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0);
|
|
draw_string_set_draw_mode(font_tag_index, NONE, 0, 0, &terminal_globals.input_state->color);
|
|
|
|
if (terminal_globals.insertion_point_visible)
|
|
{
|
|
short cursor_index = (buff_len + terminal_globals.input_state->edit.insertion_point_index);
|
|
if (buffer[cursor_index]=='\0')
|
|
{
|
|
buffer[cursor_index+1] = '\0';
|
|
}
|
|
buffer[cursor_index] = '_';
|
|
}
|
|
rasterizer_draw_string(&terminal_gets_bounds, NULL, NULL, 0, buffer);
|
|
}
|
|
|
|
if (terminal_render_enable)
|
|
{
|
|
struct output_line_datum *line;
|
|
long line_index;
|
|
short height = render.camera.window_bounds.y1 - line_height;
|
|
|
|
for (line_index = terminal_globals.newest_output_line_index;
|
|
line_index!=NONE && height - line_height > 0;
|
|
line_index = line->older_line_index)
|
|
{
|
|
real fade_progress;
|
|
|
|
line = output_line_get(line_index);
|
|
fade_progress = 4.f - line->timer * 0.033333335f;
|
|
|
|
color = line->color;
|
|
color.alpha*= PIN(fade_progress, 0.f, 1.f);
|
|
|
|
terminal_gets_bounds.x0 = render.camera.window_bounds.x0;
|
|
terminal_gets_bounds.x1 = render.camera.window_bounds.x1;
|
|
terminal_gets_bounds.y1 = height;
|
|
height -= line_height;
|
|
terminal_gets_bounds.y0 = height;
|
|
|
|
offset_rectangle2d(
|
|
&terminal_gets_bounds,
|
|
-render.camera.viewport_bounds.x0,
|
|
-render.camera.viewport_bounds.y0
|
|
);
|
|
|
|
if (line->tabstop)
|
|
{
|
|
draw_string_set_tab_stops(
|
|
terminal_tab_stops,
|
|
NUMBEROF(terminal_tab_stops)
|
|
);
|
|
}
|
|
|
|
draw_string_set_draw_mode(font_tag_index, NONE, 0, 0, &color);
|
|
rasterizer_draw_string(&terminal_gets_bounds, NULL, NULL, 0, line->buffer);
|
|
draw_string_set_tab_stops(terminal_tab_stops, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
boolean terminal_update(
|
|
void)
|
|
{
|
|
boolean result = FALSE;
|
|
if (terminal_globals.initialized)
|
|
{
|
|
result = terminal_update_input();
|
|
if (!console_is_active())
|
|
{
|
|
terminal_update_output();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* port/linux/src/port_config.c's */
|
|
const char *config_string(const char *name);
|
|
|
|
boolean terminal_command_running = FALSE;
|
|
|
|
/* port: whether the console shows a kind of what is logged on screen, as
|
|
config.toml's game.console_log says: "all", everything; "important" (and
|
|
anything else), the serious and the important; "none", the serious only
|
|
(the asserts that stop the game). debug.txt has every line whatever it is */
|
|
boolean terminal_shows(
|
|
short kind)
|
|
{
|
|
static short level = NONE;
|
|
|
|
if (level == NONE)
|
|
{
|
|
const char *setting = config_string("game.console_log");
|
|
|
|
level = _terminal_message_important;
|
|
if (setting && !csstrcmp(setting, "all"))
|
|
level = _terminal_message_chatter;
|
|
else if (setting && !csstrcmp(setting, "none"))
|
|
level = _terminal_message_serious;
|
|
}
|
|
return kind <= level;
|
|
}
|
|
|
|
void terminal_printf(
|
|
real_argb_color const *color,
|
|
char const *format,
|
|
...)
|
|
{
|
|
real_argb_color default_terminal_printf_color;
|
|
va_list arglist;
|
|
|
|
va_start(arglist, format);
|
|
|
|
default_terminal_printf_color.alpha = 1.f;
|
|
default_terminal_printf_color.red = 0.7f;
|
|
default_terminal_printf_color.green = 0.7f;
|
|
default_terminal_printf_color.blue = 0.7f;
|
|
|
|
if (terminal_globals.initialized)
|
|
{
|
|
long line_index = terminal_new_line();
|
|
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 397, format);
|
|
|
|
if (line_index!=NONE)
|
|
{
|
|
int count;
|
|
struct output_line_datum *line = output_line_get(line_index);
|
|
line->timer = 0;
|
|
line->color = !color ? default_terminal_printf_color : *color;
|
|
/* port: a line longer than the on-screen buffer is cut to fit (it
|
|
halted here, from inside error(), so the real halt was never
|
|
written down) */
|
|
count = _vsnprintf(line->buffer, NUMBEROF(line->buffer)-2, format, arglist);
|
|
if (count < 0 || count >= (int)(NUMBEROF(line->buffer)-2))
|
|
line->buffer[NUMBEROF(line->buffer)-2] = 0;
|
|
line->tabstop = strstr(line->buffer, "|t") != 0;
|
|
telnet_console_print(line->buffer);
|
|
}
|
|
}
|
|
|
|
va_end(arglist);
|
|
|
|
return;
|
|
}
|
|
|
|
/* ---------- private code */
|
|
|
|
static void terminal_remove_line(
|
|
long line_index)
|
|
{
|
|
struct output_line_datum *output_line = output_line_get(line_index);
|
|
|
|
if (output_line->older_line_index!=NONE)
|
|
{
|
|
output_line_get(output_line->older_line_index)->newer_line_index = output_line->newer_line_index;
|
|
}
|
|
else
|
|
{
|
|
terminal_globals.oldest_output_line_index = output_line->newer_line_index;
|
|
}
|
|
|
|
if (output_line->newer_line_index!=NONE)
|
|
{
|
|
output_line_get(output_line->newer_line_index)->older_line_index = output_line->older_line_index;
|
|
}
|
|
else
|
|
{
|
|
terminal_globals.newest_output_line_index = output_line->older_line_index;
|
|
}
|
|
|
|
datum_delete(terminal_globals.output_lines, line_index);
|
|
|
|
return;
|
|
}
|
|
|
|
static boolean terminal_update_input(
|
|
void)
|
|
{
|
|
struct key_stroke key;
|
|
|
|
boolean result = FALSE;
|
|
|
|
if (terminal_globals.input_state)
|
|
{
|
|
long time = local_time_get();
|
|
|
|
for (terminal_globals.input_state->key_count = 0;
|
|
input_get_key(&key);
|
|
terminal_globals.last_insertion_point_toggle = time)
|
|
{
|
|
if (terminal_globals.input_state->key_count < TERMINAL_GETS_MAXIMUM_KEYS)
|
|
{
|
|
terminal_globals.input_state->keys[terminal_globals.input_state->key_count] = key;
|
|
terminal_globals.input_state->key_count++;
|
|
}
|
|
|
|
edit_text_handle_key(&terminal_globals.input_state->edit, &key);
|
|
terminal_globals.insertion_point_visible = TRUE;
|
|
}
|
|
|
|
if (time > terminal_globals.last_insertion_point_toggle + OUTPUT_FADE_TIME)
|
|
{
|
|
terminal_globals.last_insertion_point_toggle = time;
|
|
terminal_globals.insertion_point_visible = terminal_globals.insertion_point_visible==FALSE;
|
|
}
|
|
result = TRUE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void terminal_update_output(
|
|
void)
|
|
{
|
|
struct output_line_datum *line;
|
|
long line_index = terminal_globals.newest_output_line_index;
|
|
/* This runs once a frame, several frames per tick on the native builds
|
|
(port/linux/game/render_interpolation.c): count the line timers in 30 Hz
|
|
ticks of real time, as they counted on the Xbox, not in frames. */
|
|
static real leftover_ticks = 0.f;
|
|
long ticks;
|
|
|
|
leftover_ticks += main_get_seconds_elapsed() * TICKS_PER_SECOND;
|
|
ticks = (long)leftover_ticks;
|
|
leftover_ticks -= (real)ticks;
|
|
|
|
while (line_index!=NONE)
|
|
{
|
|
long older_line_index;
|
|
|
|
line = output_line_get(line_index);
|
|
older_line_index = line->older_line_index;
|
|
line->timer += ticks;
|
|
|
|
if (line->timer>OUTPUT_TOTAL_TIME)
|
|
{
|
|
terminal_remove_line(line_index);
|
|
}
|
|
line_index = older_line_index;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static long terminal_new_line(
|
|
void)
|
|
{
|
|
long new_line_index;
|
|
struct output_line_datum *line;
|
|
|
|
if (terminal_globals.output_lines->count==OUTPUT_MAXIMUM_LINES)
|
|
{
|
|
terminal_remove_line(terminal_globals.oldest_output_line_index);
|
|
}
|
|
|
|
new_line_index = datum_new(terminal_globals.output_lines);
|
|
match_assert("c:\\halo\\SOURCE\\interface\\terminal.c", 122, new_line_index!=NONE);
|
|
|
|
line = output_line_get(new_line_index);
|
|
line->newer_line_index = NONE;
|
|
line->older_line_index = terminal_globals.newest_output_line_index;
|
|
terminal_globals.newest_output_line_index = new_line_index;
|
|
|
|
if (line->older_line_index!=NONE)
|
|
{
|
|
output_line_get(line->older_line_index)->newer_line_index = new_line_index;
|
|
}
|
|
else
|
|
{
|
|
terminal_globals.oldest_output_line_index = new_line_index;
|
|
}
|
|
|
|
return new_line_index;
|
|
}
|