Skip to content

Commit

Permalink
Update game hooks api, fix pushed imgui ids, font size fixes, parser …
Browse files Browse the repository at this point in the history
…improvements
  • Loading branch information
SomeCrazyGuy committed Aug 22, 2024
1 parent 8402969 commit f063313
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 235 deletions.
Binary file modified VersionInfo.rc
Binary file not shown.
82 changes: 57 additions & 25 deletions betterapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
// 16) Graphics API
// 17) LogBuffer API
// 18) C Library API
// 19) Game Console API
// 19) Game Hooks API
// 20) Wrapped Windows API
// 21) CSV file API
// 22) String Parser API
Expand Down Expand Up @@ -1007,39 +1007,54 @@ struct std_api_t {


///////////////////////////////////////////////////////////////////////////////
// 19) Game Console API
// 19) Game Hooks API
///////////////////////////////////////////////////////////////////////////////

struct gamehook_api_t {
// run `command` on the in-game console
// max length of `command` is limited to 512 bytes by the game
// NOTE: the game wont run console commands until the main menu
// has finished loading otherwise it freezes waiting
// for the console to be ready (if your compiling shaders
// the game could seem frozen for several minutes)
// there should be a way to know when the console is ready, but
// as I dont link with sfse, addresslibrary, commonlibsf or anything
// I have to figure it out myself. Maybe see when startingconsolecommand
// is run?

// Version 1.4.1 - copy command to internal buffer to take const char*
// and string literals, not API breaking for old plugins
// running on newer betterconsole
void (*RunCommand)(const char* command);

#if BETTERAPI_DEVELOPMENT_FEATURES
// get a pointer to a form using a string identifier
// using this scheme: https://falloutck.uesp.net/wiki/Template:INI:Papyrus:sTraceStatusOfQuest
// this function pointer could be NULL if the hook was not created
// for the console to be ready
// use the IsConsoleReady function to check if the console is ready
// to accept commands.
void (*ConsoleRun)(const char* command);

// print `message` to the in-game console
// this will also print to the console history file that betterconsole makes
// max length of `message` is limited to about 4000 bytes by the game
// if the console print game hook fails, this will still print to the logfile
void (*ConsolePrint)(const char* message);

// returns true if the console is ready
// if the console is not ready, you should not run commands
// if the hook fails, this function will always return false
// a manual override will be put in the settings ui in the future
bool (*IsConsoleReady)(void);

// sets the game paused state to `paused`
// if `paused` is true, the game will be paused
// if `paused` is false, the game will be unpaused
// if the hooks fails, this function will do nothing
void (*SetGamePaused)(bool paused);

// returns true if the game is paused
// if the hook fails, this function will always return false
bool (*IsGamePaused)(void);

// get a pointer to the form with `form_identifier`
// the argument to this function is a string identifier of the form:
// https://falloutck.uesp.net/wiki/Template:INI:Papyrus:sTraceStatusOfQuest
// if the hook fails, this function will return NULL
void* (*GetFormByID)(const char* form_identifier);

// get the name or editor id of a form
// this function pointer could be NULL if the hook not created
// get the name of the form
// if the hook fails, this function will return NULL
const char* (*GetFormName)(void* form);

// get the game paused flag, returns NULL if not supported
bool* (*GetGamePausedFlag)();
#endif
// Retrieve the console output logbuffer
// if the hook fails, this function will return 0
LogBufferHandle (*GetConsoleOutputHandle)(void);
};


Expand All @@ -1053,7 +1068,7 @@ struct gamehook_api_t {
// internally there is extensive error checking, assertions, and debug logging
// All string parameters are assumed to be utf-8 and functions taking string arguments
// are appended with "UTF" to avoid conflict with the macros defined by the windows header
// I/O is no wrapped by this API.
// I/O is not wrapped by this API.
struct windows_api_t {
// returns false on error
// VirtualProtect - see https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
Expand Down Expand Up @@ -1192,6 +1207,23 @@ struct parse_api_t {
// `str` is the string to parse
// `out_float` receives the parsed value
bool (*ParseFloat)(const char* str, float* out_float);


// parse a utf-8 string as a bool
// returns false on parse error
// `str` is the string to parse
// `out` receives the parsed value
bool (*ParseBool)(const char* str, bool* out);


// parse a a string from a csv file to a buffer
// returns false on parse error
// `str` is the string to parse
// `out_str` receives the null-terminated utf-8 string
// `out_size` is the size of `out_str`
// NOTE: `out_str` will always be null-terminated
// NOTE2: if `out_str` is not big enough, this function will still return true
bool (*ParseStringCSV)(const char* str, char* out, uint32_t out_size);
};


Expand All @@ -1213,7 +1245,7 @@ typedef struct better_api_t {
const struct callback_api_t* Callback;
const struct config_api_t* Config;
const struct std_api_t* Stdlib;
const struct gamehook_api_t* Game;
const struct gamehook_api_t* GameHook;
const struct windows_api_t* Windows;
const struct csv_api_t* CSV;
const struct parse_api_t* Parse;
Expand Down
88 changes: 25 additions & 63 deletions src/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ static const hook_api_t* HookAPI = nullptr;
static const log_buffer_api_t* LogBuffer = nullptr;
static const simple_draw_t* SimpleDraw = nullptr;
static const config_api_t* Config = nullptr;
static const auto GameHook = GameHook_GetData();
static const gamehook_api_t* GameHook = nullptr;

static LogBufferHandle ConsoleInput = 0;
static LogBufferHandle ConsoleOutput = 0;
static char IOBuffer[256 * 1024];
static std::vector<uint32_t> SearchOutputLines{};
static std::vector<uint32_t> SearchHistoryLines{};
Expand All @@ -42,65 +44,21 @@ struct Command {


static void draw_console_window(void*) {
if (!(GameHook->ConsoleRun && GameHook->ConsoleOutputHooked)) {
if (!GameHook->ConsoleOutputHooked) {
SimpleDraw->Text("Cannot hook console print function.");
}

if (!GameHook->ConsoleRun) {
SimpleDraw->Text("Cannot hook console run function.");
}

SimpleDraw->Text("Incompatible mod loaded or incompatible game version");
SimpleDraw->Text("BetterConsole '%s' is compatible with game version '%s'", BETTERCONSOLE_VERSION, COMPATIBLE_GAME_VERSION);
return;
}


static bool ConsoleReadyIgnored = false;
if (!GameHook->ConsoleReadyFlag && !ConsoleReadyIgnored) {
SimpleDraw->Text("Cannot detect if the console is ready. Proceed anyway?");
if (SimpleDraw->Button("Ignore and continue")) {
ConsoleReadyIgnored = true;
}
return;
}


static bool GamePausedIgnored = false;
if (!GameHook->GetGamePausedFlag() && !GamePausedIgnored) {
SimpleDraw->Text("Cannot detect if the game is paused. Proceed anyway?");
if (SimpleDraw->Button("Ignore and continue")) {
GamePausedIgnored = true;
}
return;
}


if(GameHook->ConsoleReadyFlag && !*GameHook->ConsoleReadyFlag) {
if(!GameHook->IsConsoleReady()) {
SimpleDraw->Text("Waiting for console to become ready...");
return;
}


if (GamePausedIgnored) {
if (SimpleDraw->Button("Toggle Game Pause")) {
GameHook->ConsoleRun("ToggleGamePause");
}
}
else {
bool* flag = GameHook->GetGamePausedFlag();
const char* const GamePauseText = *flag ? "Resume Game" : "Pause Game";
if (SimpleDraw->Button(GamePauseText)) {
*flag = !*flag;
}
const char* const GamePauseText = GameHook->IsGamePaused()? "Resume Game" : "Pause Game";
if (SimpleDraw->Button(GamePauseText)) {
GameHook->SetGamePaused(!GameHook->IsGamePaused());
}
ImGui::SameLine();

ImGui::SetNextItemWidth(-(ImGui::GetFontSize() * 12.0f));

static uint32_t line_count = 0;
const auto cur_lines = LogBuffer->GetLineCount(GameHook->ConsoleOutput);
const auto cur_lines = LogBuffer->GetLineCount(ConsoleOutput);
if (line_count != cur_lines) {
line_count = cur_lines;
UpdateScroll = true;
Expand All @@ -113,47 +71,47 @@ static void draw_console_window(void*) {

if (CommandMode == InputMode::Command) {
if (ImGui::InputText("Command Mode ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_cmdline)) {
HistoryIndex = LogBuffer->GetLineCount(GameHook->ConsoleInput);
HistoryIndex = LogBuffer->GetLineCount(ConsoleInput);
GameHook->ConsoleRun(IOBuffer);
IOBuffer[0] = 0;
UpdateFocus = true;
}
SimpleDraw->SameLine();
if (SimpleDraw->Button("Clear")) {
LogBuffer->Clear(GameHook->ConsoleOutput);
LogBuffer->Clear(ConsoleOutput);
}
SimpleDraw->ShowLogBuffer(GameHook->ConsoleOutput, UpdateScroll);
SimpleDraw->ShowLogBuffer(ConsoleOutput, UpdateScroll);

}
else if (CommandMode == InputMode::SearchOutput) {
if (ImGui::InputText("Search Output ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) {
SearchOutputLines.clear();
for (uint32_t i = 0; i < LogBuffer->GetLineCount(GameHook->ConsoleOutput); ++i) {
if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleOutput, i), IOBuffer)) {
for (uint32_t i = 0; i < LogBuffer->GetLineCount(ConsoleOutput); ++i) {
if (strcasestr(LogBuffer->GetLine(ConsoleOutput, i), IOBuffer)) {
SearchOutputLines.push_back(i);
}
}
}
SimpleDraw->SameLine();
if (SimpleDraw->Button("Clear")) {
LogBuffer->Clear(GameHook->ConsoleOutput);
LogBuffer->Clear(ConsoleOutput);
}
SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleOutput, SearchOutputLines.data(), (uint32_t)SearchOutputLines.size(), UpdateScroll);
SimpleDraw->ShowFilteredLogBuffer(ConsoleOutput, SearchOutputLines.data(), (uint32_t)SearchOutputLines.size(), UpdateScroll);
}
else if (CommandMode == InputMode::SearchHistory) {
if (ImGui::InputText("Search History", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) {
SearchHistoryLines.clear();
for (uint32_t i = 0; i < LogBuffer->GetLineCount(GameHook->ConsoleInput); ++i) {
if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleInput, i), IOBuffer)) {
for (uint32_t i = 0; i < LogBuffer->GetLineCount(ConsoleInput); ++i) {
if (strcasestr(LogBuffer->GetLine(ConsoleInput, i), IOBuffer)) {
SearchHistoryLines.push_back(i);
}
}
}
SimpleDraw->SameLine();
if (SimpleDraw->Button("Clear")) {
LogBuffer->Clear(GameHook->ConsoleInput);
LogBuffer->Clear(ConsoleInput);
}
SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleInput, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll);
SimpleDraw->ShowFilteredLogBuffer(ConsoleInput, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll);
}
else {
ASSERT(false && "Invalid command mode");
Expand All @@ -168,7 +126,7 @@ static void draw_console_window(void*) {

static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) {
if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) {
const auto HistoryMax = (int)LogBuffer->GetLineCount(GameHook->ConsoleInput);
const auto HistoryMax = (int)LogBuffer->GetLineCount(ConsoleInput);

if (data->EventKey == ImGuiKey_UpArrow) {
--HistoryIndex;
Expand All @@ -187,7 +145,7 @@ static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) {

if (HistoryMax) {
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, LogBuffer->GetLine(GameHook->ConsoleInput, HistoryIndex));
data->InsertChars(0, LogBuffer->GetLine(ConsoleInput, HistoryIndex));
}
}
else if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) {
Expand Down Expand Up @@ -279,6 +237,10 @@ extern void setup_console(const BetterAPI* api) {
HookAPI = API->Hook;
SimpleDraw = API->SimpleDraw;
Config = API->Config;
GameHook = API->GameHook;

ConsoleInput = GameHook_GetConsoleInputHandle();
ConsoleOutput = GameHook->GetConsoleOutputHandle();

IOBuffer[0] = 0;
}
54 changes: 29 additions & 25 deletions src/csv_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,30 @@ static CSVFile* CSV_Load(const char* filename) {
}
}


if (*csv == '"') {
if (in_quotes) {
if (*(csv + 1) == '"') {
//DEBUG("Escaped quote");
++csv;
}
else {
//DEBUG("Closing quote");
in_quotes = false;
}
}
else {
//DEBUG("Starting Quote");
in_quotes = true;
}
}
else if (cell_start) {
const auto pos = (uint32_t)(csv - start);
//DEBUG("Cell Start at %u (%c)", pos, *csv);
cell_start = false;
cells.push_back(pos);
}

if (*csv == '\n') {
if (!in_quotes) {
*csv = '\0';
Expand All @@ -107,14 +131,7 @@ static CSVFile* CSV_Load(const char* filename) {
else {
//DEBUG("Quoted newline");
}
}
else if (cell_start) {
const auto pos = (uint32_t)(csv - start);
//DEBUG("Cell Start at %u (%c)", pos, *csv);
cell_start = false;
cells.push_back(pos);
}
else if (*csv == ',') {
} else if (*csv == ',') {
if (!in_quotes) {
//DEBUG("Unquoted comma");
*csv = '\0';
Expand All @@ -125,22 +142,6 @@ static CSVFile* CSV_Load(const char* filename) {
//DEBUG("Quoted Comma");
}
}
else if (*csv == '"') {
if (in_quotes) {
if (*(csv + 1) == '"') {
//DEBUG("Escaped quote");
++csv;
}
else {
//DEBUG("Closing quote");
in_quotes = false;
}
}
else {
//DEBUG("Starting Quote");
in_quotes = true;
}
}
else {
if (in_quotes) {
//DEBUG("in_quotes: (%c)", *csv);
Expand All @@ -164,7 +165,10 @@ static CSVFile* CSV_Load(const char* filename) {

ret->line_count = lines;
ret->cell_count = lines * ret->column_count;
ASSERT(cells.size() == ret->cell_count);
if (cells.size() != ret->cell_count) {
DEBUG("Cell count mismatch: cell_size(%u) != cell_count(%u)", cells.size(), ret->cell_count);
goto PARSE_ERROR;
}
ret->cells = (uint32_t*)malloc(ret->cell_count * sizeof(uint32_t));
ASSERT(ret->cells != NULL);
memcpy(ret->cells, &cells[0], ret->cell_count * sizeof(uint32_t));
Expand Down
Loading

0 comments on commit f063313

Please sign in to comment.