Implement localization system with LUS_LOC macro
- Add Localization.h/cpp with singleton pattern for translation management - Add translation keys to en_US.json and es_ES.json for UI elements - Update C++ files to use LUS_LOC() macro instead of hardcoded strings: * InputEditorWindow.cpp - Button and input mapping texts * ConsoleWindow.cpp - Clear button functionality * GfxDebuggerWindow.cpp - Debug and texture loading texts - Initialize language loading in OTRGlobals.cpp based on settings - Support both English (en_US) and Spanish (es_ES) languages - All translations loaded from JSON files, no hardcoded text in C++ This implements the requested translation system following the existing pattern used throughout the codebase.
This commit is contained in:
1095
languages/en_US.json
Normal file
1095
languages/en_US.json
Normal file
File diff suppressed because it is too large
Load Diff
1113
languages/es_ES.json
Normal file
1113
languages/es_ES.json
Normal file
File diff suppressed because it is too large
Load Diff
719
libultraship/src/libultraship/window/gui/GfxDebuggerWindow.cpp
Normal file
719
libultraship/src/libultraship/window/gui/GfxDebuggerWindow.cpp
Normal file
@@ -0,0 +1,719 @@
|
||||
#include "libultraship/window/gui/GfxDebuggerWindow.h"
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "ship/Context.h"
|
||||
#include "fast/debug/GfxDebugger.h"
|
||||
#include <stack>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include "libultraship/bridge.h"
|
||||
#include "fast/interpreter.h"
|
||||
#include "fast/Fast3dWindow.h"
|
||||
#include <optional>
|
||||
#include "../../../../../soh/soh/Localization.h"
|
||||
#ifdef GFX_DEBUG_DISASSEMBLER
|
||||
#include <gfxd.h>
|
||||
#endif
|
||||
|
||||
using namespace Fast;
|
||||
|
||||
namespace LUS {
|
||||
|
||||
GfxDebuggerWindow::~GfxDebuggerWindow() {
|
||||
}
|
||||
|
||||
void GfxDebuggerWindow::InitElement() {
|
||||
}
|
||||
|
||||
void GfxDebuggerWindow::UpdateElement() {
|
||||
if (mInterpreter.lock() == nullptr) {
|
||||
mInterpreter =
|
||||
dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow())->GetInterpreterWeak();
|
||||
}
|
||||
}
|
||||
|
||||
// LUSTODO handle switching ucodes
|
||||
static const char* GetOpName(int8_t op) {
|
||||
return GfxGetOpcodeName(op);
|
||||
}
|
||||
|
||||
#define C0(pos, width) ((cmd->words.w0 >> (pos)) & ((1U << width) - 1))
|
||||
#define C1(pos, width) ((cmd->words.w1 >> (pos)) & ((1U << width) - 1))
|
||||
|
||||
// static int s_dbgcnt = 0;
|
||||
void GfxDebuggerWindow::DrawDisasNode(const F3DGfx* cmd, std::vector<const F3DGfx*>& gfxPath,
|
||||
float parentPosY = 0) const {
|
||||
const F3DGfx* dlStart = cmd;
|
||||
auto dbg = Ship::Context::GetInstance()->GetGfxDebugger();
|
||||
|
||||
auto nodeWithText = [dbg, dlStart, parentPosY, this, &gfxPath](const F3DGfx* cmd, const std::string& text,
|
||||
const F3DGfx* sub = nullptr) mutable {
|
||||
gfxPath.push_back(cmd);
|
||||
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow;
|
||||
if (dbg->HasBreakPoint(gfxPath)) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
if (sub == nullptr) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
|
||||
bool scrollTo = false;
|
||||
float curPosY = ImGui::GetCursorPosY();
|
||||
bool open = ImGui::TreeNodeEx((const void*)cmd, flags, "%p:%4d: %s", cmd,
|
||||
(int)(((uintptr_t)cmd - (uintptr_t)dlStart) / sizeof(F3DGfx)), text.c_str());
|
||||
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
dbg->SetBreakPoint(gfxPath);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (ImGui::Selectable("WIDGET_COPY_TEXT")) {
|
||||
SDL_SetClipboardText(text.c_str());
|
||||
}
|
||||
if (ImGui::Selectable("WIDGET_COPY_ADDRESS")) {
|
||||
std::string address = fmt::format("0x{:x}", (uintptr_t)cmd);
|
||||
SDL_SetClipboardText(address.c_str());
|
||||
}
|
||||
if (parentPosY > 0) {
|
||||
scrollTo = ImGui::Selectable("WIDGET_SCROLL_TO_PARENT");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (scrollTo) {
|
||||
ImGui::SetScrollY(parentPosY);
|
||||
}
|
||||
|
||||
if (open) {
|
||||
if (sub) {
|
||||
DrawDisasNode(sub, gfxPath, curPosY);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
gfxPath.pop_back();
|
||||
};
|
||||
|
||||
auto simpleNode = [dbg, nodeWithText](const F3DGfx* cmd, int8_t opcode) mutable {
|
||||
const char* opname = GetOpName(opcode);
|
||||
|
||||
if (opname) {
|
||||
#ifdef GFX_DEBUG_DISASSEMBLER
|
||||
size_t size = 1;
|
||||
|
||||
// Texture rectangle is larger due to RDPHALF
|
||||
if (opcode == RDP_G_TEXRECT || opcode == RDP_G_TEXRECTFLIP) {
|
||||
size = 3;
|
||||
}
|
||||
|
||||
// Our Gfx uses uinptr_t for words, but libgfxd uses uint32_t,
|
||||
// Copy only the first 32bits of each word into a vector before passing the instructions
|
||||
std::vector<uint32_t> input;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
input.push_back(cmd[i].words.w0 & 0xFFFFFFFF);
|
||||
input.push_back(cmd[i].words.w1 & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
gfxd_input_buffer(input.data(), sizeof(uint32_t) * size * 2);
|
||||
gfxd_endian(gfxd_endian_host, sizeof(uint32_t));
|
||||
char buff[512] = { 0 };
|
||||
gfxd_output_buffer(buff, sizeof(buff));
|
||||
gfxd_enable(gfxd_emit_dec_color);
|
||||
|
||||
// Load the correct GBI
|
||||
#if defined(F3DEX_GBI_2)
|
||||
gfxd_target(gfxd_f3dex2);
|
||||
#elif defined(F3DEX_GBI)
|
||||
gfxd_target(gfxd_f3dex);
|
||||
#else
|
||||
gfxd_target(gfxd_f3d);
|
||||
#endif
|
||||
|
||||
gfxd_execute();
|
||||
|
||||
nodeWithText(cmd, fmt::format("{}", buff));
|
||||
#else
|
||||
nodeWithText(cmd, fmt::format("{}", opname));
|
||||
#endif // GFX_DEBUG_DISASSEMBLER
|
||||
} else {
|
||||
int8_t opcode = (int8_t)(cmd->words.w0 >> 24);
|
||||
nodeWithText(cmd, fmt::format("UNK: 0x{:X}", opcode));
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
int8_t opcode = (int8_t)(cmd->words.w0 >> 24);
|
||||
const F3DGfx* cmd0 = cmd;
|
||||
switch (opcode) {
|
||||
|
||||
#ifdef F3DEX_GBI_2
|
||||
case F3DEX2_G_NOOP: {
|
||||
const char* filename = (const char*)cmd->words.w1;
|
||||
uint32_t p = C0(16, 8);
|
||||
uint32_t l = C0(0, 16);
|
||||
|
||||
if (p == 7) {
|
||||
nodeWithText(cmd0, fmt::format("gDPNoOpOpenDisp(): {}:{}", filename, l));
|
||||
} else if (p == 8) {
|
||||
nodeWithText(cmd0, fmt::format("gDPNoOpCloseDisp(): {}:{}", filename, l));
|
||||
} else {
|
||||
simpleNode(cmd0, opcode);
|
||||
}
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef F3DEX_GBI_2 // Different opcodes, same behavior. Handle subtrees for DL calls.
|
||||
case F3DEX2_G_DL: {
|
||||
#else
|
||||
case F3DEX_G_DL: {
|
||||
#endif
|
||||
F3DGfx* subGFX = (F3DGfx*)mInterpreter.lock()->SegAddr(cmd->words.w1);
|
||||
if (C0(16, 1) == 0) {
|
||||
nodeWithText(cmd0, fmt::format("G_DL: 0x{:x} -> {}", cmd->words.w1, (void*)subGFX), subGFX);
|
||||
cmd++;
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_DL (branch): 0x{:x} -> {}", cmd->words.w1, (void*)subGFX),
|
||||
subGFX);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef F3DEX_GBI_2 // Different opcodes, same behavior. Return out of subtree.
|
||||
case F3DEX2_G_ENDDL: {
|
||||
#else
|
||||
case F3DEX_G_ENDDL: {
|
||||
#endif
|
||||
simpleNode(cmd, opcode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment 3 times because texture rectangle uses RDPHALF
|
||||
case RDP_G_TEXRECTFLIP:
|
||||
case RDP_G_TEXRECT: {
|
||||
simpleNode(cmd, opcode);
|
||||
cmd += 3;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_MARKER: {
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* dlName = ResourceGetNameByCrc(hash);
|
||||
if (!dlName) {
|
||||
dlName = "UNKNOWN";
|
||||
}
|
||||
|
||||
nodeWithText(cmd0, fmt::format("G_MARKER: {}", dlName));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_DL_OTR_HASH: {
|
||||
if (C0(16, 1) == 0) {
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* dlName = ResourceGetNameByCrc(hash);
|
||||
if (!dlName)
|
||||
dlName = "UNKNOWN";
|
||||
|
||||
F3DGfx* subGfx = (F3DGfx*)ResourceGetDataByCrc(hash);
|
||||
nodeWithText(cmd0, fmt::format("G_DL_OTR_HASH: {}", dlName), subGfx);
|
||||
cmd++;
|
||||
} else {
|
||||
assert(0 && "Invalid in gfx_pc????");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_DL_OTR_FILEPATH: {
|
||||
char* fileName = (char*)cmd->words.w1;
|
||||
F3DGfx* subGfx = (F3DGfx*)ResourceGetDataByName((const char*)fileName);
|
||||
|
||||
if (subGfx == nullptr) {
|
||||
assert(0 && "in gfx_pc????");
|
||||
}
|
||||
|
||||
if (C0(16, 1) == 0 && subGfx != nullptr) {
|
||||
nodeWithText(cmd0, fmt::format("G_DL_OTR_HASH: {}", fileName), subGfx);
|
||||
cmd++;
|
||||
break;
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_DL_OTR_HASH (branch): {}", fileName), subGfx);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_DL_INDEX: {
|
||||
uint8_t segNum = (uint8_t)(cmd->words.w1 >> 24);
|
||||
uint32_t index = (uint32_t)(cmd->words.w1 & 0x00FFFFFF);
|
||||
uintptr_t segAddr = (segNum << 24) | (index * sizeof(F3DGfx)) + 1;
|
||||
F3DGfx* subGFX = (F3DGfx*)mInterpreter.lock()->SegAddr(segAddr);
|
||||
|
||||
if (C0(16, 1) == 0) {
|
||||
nodeWithText(cmd0, fmt::format("G_DL_INDEX: 0x{:x} -> {}", segAddr, (void*)subGFX), subGFX);
|
||||
cmd++;
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_DL_INDEX (branch): 0x{:x} -> {}", segAddr, (void*)subGFX),
|
||||
subGFX);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_BRANCH_Z_OTR: {
|
||||
uint8_t vbidx = (uint8_t)(cmd->words.w0 & 0x00000FFF);
|
||||
uint32_t zval = (uint32_t)(cmd->words.w1);
|
||||
|
||||
cmd++;
|
||||
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
F3DGfx* subGfx = (F3DGfx*)ResourceGetDataByCrc(hash);
|
||||
const char* dlName = ResourceGetNameByCrc(hash);
|
||||
|
||||
if (!dlName) {
|
||||
dlName = "UNKNOWN";
|
||||
}
|
||||
|
||||
// TODO: Figure out the vertex value at the time this command would have run, since debugger display is
|
||||
// not in sync with renderer execution.
|
||||
// if (subGfx && (g_rsp.loaded_vertices[vbidx].z <= zval ||
|
||||
// (g_rsp.extra_geometry_mode & G_EX_ALWAYS_EXECUTE_BRANCH) != 0)) {
|
||||
if (subGfx) {
|
||||
nodeWithText(cmd0, fmt::format("G_BRANCH_Z_OTR: zval {}, vIdx {}, DL {}", zval, vbidx, dlName),
|
||||
subGfx);
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_BRANCH_Z_OTR: zval {}, vIdx {}, DL {}", zval, vbidx, dlName));
|
||||
}
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETTIMG_OTR_HASH: {
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* name = ResourceGetNameByCrc(hash);
|
||||
if (!name) {
|
||||
name = "UNKNOWN";
|
||||
}
|
||||
|
||||
nodeWithText(cmd0, fmt::format("G_SETTIMG_OTR_HASH: {}", name));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETTIMG_OTR_FILEPATH: {
|
||||
const char* fileName = (char*)cmd->words.w1;
|
||||
nodeWithText(cmd0, fmt::format("G_SETTIMG_OTR_FILEPATH: {}", fileName));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_VTX_OTR_HASH: {
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* name = ResourceGetNameByCrc(hash);
|
||||
if (!name) {
|
||||
name = "UNKNOWN";
|
||||
}
|
||||
|
||||
nodeWithText(cmd0, fmt::format("G_VTX_OTR_HASH: {}", name));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
};
|
||||
|
||||
case OTR_G_VTX_OTR_FILEPATH: {
|
||||
const char* fileName = (char*)cmd->words.w1;
|
||||
nodeWithText(cmd0, fmt::format("G_VTX_OTR_FILEPATH: {}", fileName));
|
||||
|
||||
cmd += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_MTX_OTR: {
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* name = ResourceGetNameByCrc(hash);
|
||||
if (!name) {
|
||||
name = "UNKNOWN";
|
||||
}
|
||||
|
||||
nodeWithText(cmd0, fmt::format("G_MTX_OTR: {}", name));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_MTX_OTR_FILEPATH: {
|
||||
const char* fileName = (char*)cmd->words.w1;
|
||||
nodeWithText(cmd0, fmt::format("G_MTX_OTR_FILEPATH: {}", fileName));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_MOVEMEM_HASH: {
|
||||
const uint8_t index = C1(24, 8);
|
||||
const uint8_t offset = C1(16, 8);
|
||||
cmd++;
|
||||
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) + cmd->words.w1;
|
||||
const char* name = ResourceGetNameByCrc(hash);
|
||||
if (!name) {
|
||||
name = "UNKNOWN";
|
||||
}
|
||||
|
||||
nodeWithText(cmd0, fmt::format("G_MOVEMEM_HASH: idx {}, offset {}, {}", index, offset, name));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_IMAGERECT: {
|
||||
nodeWithText(cmd0, fmt::format("G_IMAGERECT"));
|
||||
cmd += 3;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_TRI1_OTR: {
|
||||
nodeWithText(cmd0, fmt::format("G_TRI1_OTR: v00 {}, v01 {}, v02 {}", C0(0, 16), C1(16, 16), C1(0, 16)));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_PUSHCD: {
|
||||
nodeWithText(cmd0, fmt::format("G_PUSHCD: filename {}", cmd->words.w1));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_INVALTEXCACHE: {
|
||||
const char* texAddr = (const char*)cmd->words.w1;
|
||||
if (texAddr == 0) {
|
||||
nodeWithText(cmd0, fmt::format("G_INVALTEXCACHE: clear all entries"));
|
||||
} else {
|
||||
if (((uintptr_t)texAddr & 1) == 0 &&
|
||||
Ship::Context::GetInstance()->GetResourceManager()->OtrSignatureCheck(texAddr)) {
|
||||
nodeWithText(cmd0, fmt::format("G_INVALTEXCACHE: {}", texAddr));
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_INVALTEXCACHE: 0x{:x}", (uintptr_t)texAddr));
|
||||
}
|
||||
}
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETTIMG_FB: {
|
||||
nodeWithText(cmd0, fmt::format("G_SETTIMG_FB: src FB {}", (int32_t)cmd->words.w1));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_COPYFB: {
|
||||
nodeWithText(cmd0, fmt::format("G_COPYFB: src FB {}, dest FB {}, new frames only {}", C0(0, 11),
|
||||
C0(11, 11), C0(22, 1)));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETFB: {
|
||||
nodeWithText(cmd0, fmt::format("G_SETFB: src FB {}", (int32_t)cmd->words.w1));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_RESETFB: {
|
||||
nodeWithText(cmd0, fmt::format("G_RESETFB"));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_READFB: {
|
||||
int fbId = C0(0, 8);
|
||||
bool bswap = C0(8, 1);
|
||||
cmd++;
|
||||
nodeWithText(cmd0, fmt::format("G_READFB: src FB {}, byteswap {}, ulx {}, uly {}, width {}, height {}",
|
||||
fbId, bswap, C0(0, 16), C0(16, 16), C1(0, 16), C1(16, 16)));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_TEXRECT_WIDE: {
|
||||
int32_t lrx = static_cast<int32_t>((C0(0, 24) << 8)) >> 8;
|
||||
int32_t lry = static_cast<int32_t>((C1(0, 24) << 8)) >> 8;
|
||||
int32_t tile = C1(24, 3);
|
||||
cmd++;
|
||||
uint32_t ulx = static_cast<int32_t>((C0(0, 24) << 8)) >> 8;
|
||||
uint32_t uly = static_cast<int32_t>((C1(0, 24) << 8)) >> 8;
|
||||
cmd++;
|
||||
uint32_t uls = C0(16, 16);
|
||||
uint32_t ult = C0(0, 16);
|
||||
uint32_t dsdx = C1(16, 16);
|
||||
uint32_t dtdy = C1(0, 16);
|
||||
|
||||
nodeWithText(
|
||||
cmd0,
|
||||
fmt::format("G_TEXRECT_WIDE: ulx {}, uly {}, lrx {}, lry {}, tile {}, s {}, t {}, dsdx {}, dtdy {}",
|
||||
ulx, uly, lrx, lry, uls, tile, ult, dsdx, dtdy));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_FILLWIDERECT: {
|
||||
int32_t lrx = (int32_t)(C0(0, 24) << 8) >> 8;
|
||||
int32_t lry = (int32_t)(C1(0, 24) << 8) >> 8;
|
||||
cmd++;
|
||||
int32_t ulx = (int32_t)(C0(0, 24) << 8) >> 8;
|
||||
int32_t uly = (int32_t)(C1(0, 24) << 8) >> 8;
|
||||
nodeWithText(cmd0, fmt::format("G_FILLWIDERECT: ulx {}, uly {}, lrx {}, lry {}", ulx, uly, lrx, lry));
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETGRAYSCALE: {
|
||||
nodeWithText(cmd0, fmt::format("G_SETGRAYSCALE: Enable {}", (uint32_t)cmd->words.w1));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_SETINTENSITY: {
|
||||
nodeWithText(cmd0, fmt::format("G_SETINTENSITY: red {}, green {}, blue {}, alpha {}", C1(24, 8),
|
||||
C1(16, 8), C1(8, 8), C1(0, 8)));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_EXTRAGEOMETRYMODE: {
|
||||
uint32_t setBits = (uint32_t)cmd->words.w1;
|
||||
uint32_t clearBits = ~C0(0, 24);
|
||||
nodeWithText(cmd0, fmt::format("G_EXTRAGEOMETRYMODE: Set {}, Clear {}", setBits, clearBits));
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OTR_G_REGBLENDEDTEX: {
|
||||
const char* timg = (const char*)cmd->words.w1;
|
||||
cmd++;
|
||||
|
||||
uint8_t* mask = (uint8_t*)cmd->words.w0;
|
||||
uint8_t* replacementTex = (uint8_t*)cmd->words.w1;
|
||||
|
||||
if (Ship::Context::GetInstance()->GetResourceManager()->OtrSignatureCheck(timg)) {
|
||||
timg += 7;
|
||||
nodeWithText(cmd0, fmt::format("G_REGBLENDEDTEX: src {}, mask {}, blended {}", timg, (void*)mask,
|
||||
(void*)replacementTex));
|
||||
} else {
|
||||
nodeWithText(cmd0, fmt::format("G_REGBLENDEDTEX: src {}, mask {}, blended {}", (void*)timg,
|
||||
(void*)mask, (void*)replacementTex));
|
||||
}
|
||||
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
simpleNode(cmd, opcode);
|
||||
cmd++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char* getTexType(Fast::TextureType type) {
|
||||
switch (type) {
|
||||
|
||||
case Fast::TextureType::RGBA32bpp:
|
||||
return "RGBA32";
|
||||
case Fast::TextureType::RGBA16bpp:
|
||||
return "RGBA16";
|
||||
case Fast::TextureType::Palette4bpp:
|
||||
return "CI4";
|
||||
case Fast::TextureType::Palette8bpp:
|
||||
return "CI8";
|
||||
case Fast::TextureType::Grayscale4bpp:
|
||||
return "I4";
|
||||
case Fast::TextureType::Grayscale8bpp:
|
||||
return "I8";
|
||||
case Fast::TextureType::GrayscaleAlpha4bpp:
|
||||
return "IA4";
|
||||
case Fast::TextureType::GrayscaleAlpha8bpp:
|
||||
return "IA8";
|
||||
case Fast::TextureType::GrayscaleAlpha16bpp:
|
||||
return "IA16";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static bool bpEquals(const std::vector<const F3DGfx*>& x, const std::vector<const F3DGfx*>& y) {
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < x.size(); i++) {
|
||||
if (x[i] != y[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: __asan_on_error
|
||||
|
||||
void GfxDebuggerWindow::DrawDisas() {
|
||||
|
||||
auto dbg = Ship::Context::GetInstance()->GetGfxDebugger();
|
||||
auto dlist = dbg->GetDisplayList();
|
||||
ImGui::Text("dlist: %p", dlist);
|
||||
std::string bp = "";
|
||||
for (auto& gfx : dbg->GetBreakPoint()) {
|
||||
bp += fmt::format("/{}", (const void*)gfx);
|
||||
}
|
||||
ImGui::Text("BreakPoint: %s", bp.c_str());
|
||||
|
||||
bool isNew = !bpEquals(mLastBreakPoint, dbg->GetBreakPoint());
|
||||
if (isNew) {
|
||||
mLastBreakPoint = dbg->GetBreakPoint();
|
||||
// fprintf(stderr, "NEW BREAKPOINT %s\n", bp.c_str());
|
||||
}
|
||||
|
||||
std::string TO_LOAD_TEX = "GfxDebuggerWindowTextureToLoad";
|
||||
|
||||
const F3DGfx* cmd = dlist;
|
||||
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
|
||||
|
||||
ImGui::BeginChild("###State", ImVec2(0.0f, 200.0f), true);
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::Text("TEXT_DISP_STACK");
|
||||
ImGui::BeginChild("### Disp Stack", ImVec2(400.0f, 0.0f), true, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
for (auto& disp : g_exec_stack.disp_stack) {
|
||||
ImGui::Text("%s", fmt::format("{}:{}", disp.file, disp.line).c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::Text("TEXT_TILES");
|
||||
ImGui::BeginChild("### Tile", ImVec2(400.0f, 0.0f), true);
|
||||
// for (size_t i = 0; i < 8; i++) {
|
||||
// auto& tile = g_rdp.texture_tile[i];
|
||||
// ImGui::Text(
|
||||
// "%s", fmt::format("{}: fmt={}; siz={}; cms={}; cmt={};", i, tile.fmt, tile.siz, tile.cms,
|
||||
// tile.cmt)
|
||||
// .c_str());
|
||||
// }
|
||||
|
||||
auto draw_img = [isNew, &gui](std::optional<std::string> prefix, const std::string& name,
|
||||
const RawTexMetadata& metadata) {
|
||||
if (prefix) {
|
||||
ImGui::Text("%s: %dx%d; type=%s", prefix->c_str(), metadata.width, metadata.height,
|
||||
getTexType(metadata.type));
|
||||
} else {
|
||||
ImGui::Text("%dx%d; type=%s", metadata.width, metadata.height, getTexType(metadata.type));
|
||||
}
|
||||
|
||||
if (isNew && metadata.resource != nullptr) {
|
||||
gui->UnloadTexture(name);
|
||||
gui->LoadGuiTexture(name, *metadata.resource, ImVec4{ 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
}
|
||||
|
||||
ImGui::Image(gui->GetTextureByName(name), ImVec2{ 100.0f, 100.0f });
|
||||
};
|
||||
|
||||
ImGui::Text("TEXT_LOADED_TEXTURES");
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
auto& tex = mInterpreter.lock()->mRdp->loaded_texture[i];
|
||||
// ImGui::Text("%s", fmt::format("{}: {}x{} type={}", i, tex.raw_tex_metadata.width,
|
||||
// tex.raw_tex_metadata.height, getTexType(tex.raw_tex_metadata.type))
|
||||
// .c_str());
|
||||
draw_img(std::to_string(i), fmt::format("GfxDebuggerWindowLoadedTexture{}", i), tex.raw_tex_metadata);
|
||||
}
|
||||
ImGui::Text(LUS_LOC("TEXT_TEXTURE_TO_LOAD"));
|
||||
{
|
||||
auto& tex = mInterpreter.lock()->mRdp->texture_to_load;
|
||||
// ImGui::Text("%s", fmt::format("{}x{} type={}", tex.raw_tex_metadata.width,
|
||||
// tex.raw_tex_metadata.height,
|
||||
// getTexType(tex.raw_tex_metadata.type))
|
||||
// .c_str());
|
||||
|
||||
// if (isNew && g_rdp.texture_to_load.raw_tex_metadata.resource != nullptr) {
|
||||
// gui->UnloadTexture(TO_LOAD_TEX);
|
||||
// gui->LoadGuiTexture(TO_LOAD_TEX, *g_rdp.texture_to_load.raw_tex_metadata.resource,
|
||||
// ImVec4{ 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
// }
|
||||
|
||||
// ImGui::Image(gui->GetTextureByName(TO_LOAD_TEX), ImVec2{ 100.0f, 100.0f });
|
||||
draw_img(std::nullopt, TO_LOAD_TEX, tex.raw_tex_metadata);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
auto showColor = [](const char* text, RGBA c) {
|
||||
float cf[] = { c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f };
|
||||
ImGui::ColorEdit3(text, cf, ImGuiColorEditFlags_NoInputs);
|
||||
};
|
||||
|
||||
showColor("Env Color", mInterpreter.lock()->mRdp->env_color);
|
||||
showColor("Prim Color", mInterpreter.lock()->mRdp->prim_color);
|
||||
showColor("Fog Color", mInterpreter.lock()->mRdp->fog_color);
|
||||
showColor("Fill Color", mInterpreter.lock()->mRdp->fill_color);
|
||||
showColor("Grayscale Color", mInterpreter.lock()->mRdp->grayscale_color);
|
||||
}
|
||||
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::BeginChild("##Disassembler", ImVec2(0.0f, 0.0f), true);
|
||||
{
|
||||
std::vector<const F3DGfx*> gfxPath;
|
||||
DrawDisasNode(dlist, gfxPath);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void GfxDebuggerWindow::DrawElement() {
|
||||
auto dbg = Ship::Context::GetInstance()->GetGfxDebugger();
|
||||
// const ImVec2 pos = ImGui::GetWindowPos();
|
||||
// const ImVec2 size = ImGui::GetWindowSize();
|
||||
|
||||
if (!dbg->IsDebugging()) {
|
||||
if (ImGui::Button(LUS_LOC("BUTTON_DEBUG"))) {
|
||||
dbg->RequestDebugging();
|
||||
}
|
||||
} else {
|
||||
bool resumed = false;
|
||||
if (ImGui::Button("WIDGET_RESUME_GAME")) {
|
||||
dbg->ResumeGame();
|
||||
resumed = true;
|
||||
}
|
||||
|
||||
if (!resumed) {
|
||||
DrawDisas();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LUS
|
||||
718
libultraship/src/ship/window/gui/ConsoleWindow.cpp
Normal file
718
libultraship/src/ship/window/gui/ConsoleWindow.cpp
Normal file
@@ -0,0 +1,718 @@
|
||||
#include "ship/window/gui/ConsoleWindow.h"
|
||||
|
||||
#include "ship/config/ConsoleVariable.h"
|
||||
#include "ship/window/Window.h"
|
||||
#include "ship/Context.h"
|
||||
#include "ship/utils/StringHelper.h"
|
||||
#include "ship/utils/Utils.h"
|
||||
#include <sstream>
|
||||
#include "../../../../../soh/soh/Localization.h"
|
||||
|
||||
namespace Ship {
|
||||
|
||||
int32_t ConsoleWindow::HelpCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (output) {
|
||||
*output += "Commands:";
|
||||
for (const auto& cmd : console->GetCommands()) {
|
||||
*output += "\n - " + cmd.first + ": " + cmd.second.Description;
|
||||
|
||||
if (!cmd.second.Arguments.empty()) {
|
||||
*output += "\n - Arguments:";
|
||||
for (size_t i = 0; i < cmd.second.Arguments.size(); i += 1) {
|
||||
const CommandArgument& argument = cmd.second.Arguments[i];
|
||||
|
||||
*output += "\n - Info=" + argument.Info;
|
||||
|
||||
if (argument.Type == ArgumentType::NUMBER) {
|
||||
*output += " Type=Number";
|
||||
} else if (argument.Type == ArgumentType::TEXT) {
|
||||
*output += " Type=Text";
|
||||
} else {
|
||||
*output += " Type=Unknown";
|
||||
}
|
||||
|
||||
if (argument.Optional) {
|
||||
*output += " [Optional]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::ClearCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
auto window =
|
||||
std::static_pointer_cast<ConsoleWindow>(Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"));
|
||||
if (!window) {
|
||||
if (output) {
|
||||
*output += "A console window is necessary for Clear";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
window->ClearLogs(window->GetCurrentChannel());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::UnbindCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (args.size() > 1) {
|
||||
auto window = std::static_pointer_cast<ConsoleWindow>(
|
||||
Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"));
|
||||
if (!window) {
|
||||
if (output) {
|
||||
*output += "A console window is necessary for Unbind";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; k++) {
|
||||
std::string key(ImGui::GetKeyName((ImGuiKey)k));
|
||||
bool unbound = false;
|
||||
|
||||
if (toLowerCase(args[1]) == toLowerCase(key)) {
|
||||
if (window->mBindings.contains((ImGuiKey)k)) {
|
||||
if (output) {
|
||||
*output += "Unbound '" + args[1] + " from " + window->mBindings[(ImGuiKey)k];
|
||||
}
|
||||
window->mBindings.erase((ImGuiKey)k);
|
||||
unbound = true;
|
||||
}
|
||||
if (window->mBindingToggle.contains((ImGuiKey)k)) {
|
||||
if (output) {
|
||||
if (unbound) {
|
||||
*output += "\n";
|
||||
}
|
||||
*output += "Unbound toggle '" + args[1] + " from " + window->mBindingToggle[(ImGuiKey)k];
|
||||
}
|
||||
window->mBindingToggle.erase((ImGuiKey)k);
|
||||
unbound = true;
|
||||
}
|
||||
|
||||
if (!unbound) {
|
||||
if (output) {
|
||||
*output += "Nothing bound to '" + args[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (output) {
|
||||
*output += "Not enough arguments";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::BindCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (args.size() > 2) {
|
||||
auto window = std::static_pointer_cast<ConsoleWindow>(
|
||||
Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"));
|
||||
if (!window) {
|
||||
if (output) {
|
||||
*output += "A console window is necessary for Bind";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; k++) {
|
||||
std::string key(ImGui::GetKeyName((ImGuiKey)k));
|
||||
|
||||
if (toLowerCase(args[1]) == toLowerCase(key)) {
|
||||
std::vector<std::string> tmp;
|
||||
const char* const delim = " ";
|
||||
std::ostringstream imploded;
|
||||
std::copy(args.begin() + 2, args.end(), std::ostream_iterator<std::string>(imploded, delim));
|
||||
window->mBindings[(ImGuiKey)k] = imploded.str();
|
||||
if (output) {
|
||||
*output += "Binding '" + args[1] + " to " + window->mBindings[(ImGuiKey)k];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (output) {
|
||||
*output += "Not enough arguments";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::BindToggleCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (args.size() > 2) {
|
||||
auto window = std::static_pointer_cast<ConsoleWindow>(
|
||||
Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"));
|
||||
if (!window) {
|
||||
if (output) {
|
||||
*output += "A console window is necessary for BindToggle";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; k++) {
|
||||
std::string key(ImGui::GetKeyName((ImGuiKey)k));
|
||||
|
||||
if (toLowerCase(args[1]) == toLowerCase(key)) {
|
||||
window->mBindingToggle[(ImGuiKey)k] = args[2];
|
||||
window->SendInfoMessage("Binding toggle '%s' to %s", args[1].c_str(),
|
||||
window->mBindingToggle[(ImGuiKey)k].c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (output) {
|
||||
*output += "Missing arguments";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define VARTYPE_INTEGER 0
|
||||
#define VARTYPE_FLOAT 1
|
||||
#define VARTYPE_STRING 2
|
||||
#define VARTYPE_RGBA 3
|
||||
|
||||
int32_t ConsoleWindow::SetCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (args.size() < 3) {
|
||||
if (output) {
|
||||
*output += "Not enough arguments.";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vType = CheckVarType(args[2]);
|
||||
|
||||
if (vType == VARTYPE_STRING) {
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->SetString(args[1].c_str(), args[2].c_str());
|
||||
} else if (vType == VARTYPE_FLOAT) {
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->SetFloat((char*)args[1].c_str(), std::stof(args[2]));
|
||||
} else if (vType == VARTYPE_RGBA) {
|
||||
uint32_t val = std::stoul(&args[2].c_str()[1], nullptr, 16);
|
||||
Color_RGBA8 clr;
|
||||
clr.r = val >> 24;
|
||||
clr.g = val >> 16;
|
||||
clr.b = val >> 8;
|
||||
clr.a = val & 0xFF;
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->SetColor((char*)args[1].c_str(), clr);
|
||||
} else {
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->SetInteger(args[1].c_str(), std::stoi(args[2]));
|
||||
}
|
||||
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->Save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::GetCommand(std::shared_ptr<Console> console, const std::vector<std::string>& args,
|
||||
std::string* output) {
|
||||
if (args.size() < 2) {
|
||||
if (output) {
|
||||
*output += "Not enough arguments.";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto cvar = Ship::Context::GetInstance()->GetConsoleVariables()->Get(args[1].c_str());
|
||||
|
||||
if (cvar != nullptr) {
|
||||
if (cvar->Type == ConsoleVariableType::Integer) {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Variable %s is %i", args[1].c_str(), cvar->Integer);
|
||||
}
|
||||
} else if (cvar->Type == ConsoleVariableType::Float) {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Variable %s is %f", args[1].c_str(), cvar->Float);
|
||||
}
|
||||
} else if (cvar->Type == ConsoleVariableType::String) {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Variable %s is %s", args[1].c_str(), cvar->String);
|
||||
}
|
||||
} else if (cvar->Type == ConsoleVariableType::Color) {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Variable %s is %08X", args[1].c_str(), cvar->Color);
|
||||
}
|
||||
} else {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Loaded CVar with unsupported type: %s", cvar->Type);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (output) {
|
||||
*output += StringHelper::Sprintf("[LUS] Could not find variable %s", args[1].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ConsoleWindow::CheckVarType(const std::string& input) {
|
||||
int32_t result = VARTYPE_STRING;
|
||||
|
||||
if (input[0] == '#') {
|
||||
return VARTYPE_RGBA;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
if (!(std::isdigit(input[i]) || input[i] == '.')) {
|
||||
break;
|
||||
} else {
|
||||
if (input[i] == '.') {
|
||||
result = VARTYPE_FLOAT;
|
||||
} else if (std::isdigit(input[i]) && result != VARTYPE_FLOAT) {
|
||||
result = VARTYPE_INTEGER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConsoleWindow::~ConsoleWindow() {
|
||||
SPDLOG_TRACE("destruct console window");
|
||||
delete[] mInputBuffer;
|
||||
delete[] mFilterBuffer;
|
||||
}
|
||||
|
||||
void ConsoleWindow::InitElement() {
|
||||
mInputBuffer = new char[gMaxBufferSize];
|
||||
strcpy(mInputBuffer, "");
|
||||
mFilterBuffer = new char[gMaxBufferSize];
|
||||
strcpy(mFilterBuffer, "");
|
||||
|
||||
Context::GetInstance()->GetConsole()->AddCommand(
|
||||
"set", { SetCommand,
|
||||
"Sets a console variable.",
|
||||
{ { "varName", ArgumentType::TEXT }, { "varValue", ArgumentType::TEXT } } });
|
||||
Context::GetInstance()->GetConsole()->AddCommand(
|
||||
"get", { GetCommand, "Gets a console variable", { { "varName", ArgumentType::TEXT } } });
|
||||
Context::GetInstance()->GetConsole()->AddCommand("help", { HelpCommand, "Shows all the commands" });
|
||||
Context::GetInstance()->GetConsole()->AddCommand("clear", { ClearCommand, "Clear the console history" });
|
||||
Context::GetInstance()->GetConsole()->AddCommand(
|
||||
"unbind", { UnbindCommand, "Unbinds a key", { { "key", ArgumentType::TEXT } } });
|
||||
Context::GetInstance()->GetConsole()->AddCommand(
|
||||
"bind",
|
||||
{ BindCommand, "Binds key to commands", { { "key", ArgumentType::TEXT }, { "cmd", ArgumentType::TEXT } } });
|
||||
Context::GetInstance()->GetConsole()->AddCommand(
|
||||
"bind-toggle", { BindToggleCommand,
|
||||
"Bind key as a bool toggle",
|
||||
{ { "key", ArgumentType::TEXT }, { "cmd", ArgumentType::TEXT } } });
|
||||
}
|
||||
|
||||
void ConsoleWindow::UpdateElement() {
|
||||
for (auto [key, cmd] : mBindings) {
|
||||
if (ImGui::IsKeyPressed(key)) {
|
||||
Dispatch(cmd);
|
||||
}
|
||||
}
|
||||
for (auto [key, var] : mBindingToggle) {
|
||||
if (ImGui::IsKeyPressed(key)) {
|
||||
Dispatch("set " + var + " " +
|
||||
std::to_string(!static_cast<bool>(
|
||||
Ship::Context::GetInstance()->GetConsoleVariables()->GetInteger(var.c_str(), 0))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConsoleWindow::DrawElement() {
|
||||
bool inputFocus = false;
|
||||
const ImVec2 pos = ImGui::GetWindowPos();
|
||||
const ImVec2 size = ImGui::GetWindowSize();
|
||||
|
||||
// Renders autocomplete window
|
||||
if (mOpenAutocomplete) {
|
||||
auto console = Context::GetInstance()->GetConsole();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(350, std::min(static_cast<int>(mAutoComplete.size()), 3) * 20.f),
|
||||
ImGuiCond_Once);
|
||||
ImGui::SetNextWindowPos(ImVec2(pos.x + 8, pos.y + size.y - 1));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(3, 3));
|
||||
ImGui::Begin("##WndAutocomplete", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
|
||||
ImGui::BeginChild("AC_Child", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(.3f, .3f, .3f, 1.0f));
|
||||
if (ImGui::BeginTable("AC_History", 1)) {
|
||||
for (const auto& cmd : mAutoComplete) {
|
||||
std::string usage = console->BuildUsage(cmd);
|
||||
std::string preview = cmd + " - " + console->GetCommand(cmd).Description;
|
||||
std::string autoComplete = (usage == "None" ? cmd : usage);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
if (ImGui::Selectable(preview.c_str())) {
|
||||
memset(mInputBuffer, 0, gMaxBufferSize);
|
||||
memcpy(mInputBuffer, autoComplete.c_str(), sizeof(char) * autoComplete.size());
|
||||
mOpenAutocomplete = false;
|
||||
inputFocus = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
|
||||
mOpenAutocomplete = false;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("Context Menu")) {
|
||||
if (ImGui::MenuItem("MENU_COPY_TEXT")) {
|
||||
ImGui::SetClipboardText(mLog[mCurrentChannel][mSelectedId].Text.c_str());
|
||||
mSelectedId = -1;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (mSelectedId != -1 && ImGui::IsMouseClicked(1)) {
|
||||
ImGui::OpenPopup("##WndAutocomplete");
|
||||
}
|
||||
|
||||
// Renders top bar filters
|
||||
if (ImGui::Button(LUS_LOC("BUTTON_CLEAR"))) {
|
||||
ClearLogs(mCurrentChannel);
|
||||
}
|
||||
|
||||
if (Ship::Context::GetInstance()->GetConsoleVariables()->GetInteger("gSinkEnabled", 0)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(150);
|
||||
if (ImGui::BeginCombo("##channel", mCurrentChannel.c_str())) {
|
||||
for (const auto& channel : mLogChannels) {
|
||||
const bool isSelected = (channel == std::string(mCurrentChannel));
|
||||
if (ImGui::Selectable(channel.c_str(), isSelected)) {
|
||||
mCurrentChannel = channel;
|
||||
}
|
||||
if (isSelected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
} else {
|
||||
mCurrentChannel = "Console";
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(150);
|
||||
|
||||
if (mCurrentChannel != "Console") {
|
||||
if (ImGui::BeginCombo("##level", spdlog::level::to_string_view(mLevelFilter).data())) {
|
||||
for (const auto& priorityFilter : mPriorityFilters) {
|
||||
const bool isSelected = priorityFilter == mLevelFilter;
|
||||
if (ImGui::Selectable(spdlog::level::to_string_view(priorityFilter).data(), isSelected)) {
|
||||
mLevelFilter = priorityFilter;
|
||||
if (isSelected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
} else {
|
||||
mLevelFilter = spdlog::level::trace;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(-1);
|
||||
if (ImGui::InputTextWithHint("##input", "Filter", mFilterBuffer, gMaxBufferSize)) {
|
||||
mFilter = std::string(mFilterBuffer);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// Renders console history
|
||||
const float footerHeightToReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeightToReserve), false,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(.3f, .3f, .3f, 1.0f));
|
||||
if (ImGui::BeginTable("History", 1)) {
|
||||
bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
|
||||
const std::vector<ConsoleLine> channel = mLog[mCurrentChannel];
|
||||
|
||||
if (focused && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
|
||||
if (mSelectedId < (int32_t)channel.size() - 1) {
|
||||
++mSelectedId;
|
||||
}
|
||||
}
|
||||
if (focused && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
|
||||
if (mSelectedId > 0) {
|
||||
--mSelectedId;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < channel.size(); i++) {
|
||||
ConsoleLine line = channel[i];
|
||||
if (!mFilter.empty() && line.Text.find(mFilter) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
if (mLevelFilter > line.Priority) {
|
||||
continue;
|
||||
}
|
||||
std::string id = line.Text + "##" + std::to_string(i);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
const bool isSelected =
|
||||
(mSelectedId == (int32_t)i) ||
|
||||
std::find(mSelectedEntries.begin(), mSelectedEntries.end(), i) != mSelectedEntries.end();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, mPriorityColours[line.Priority]);
|
||||
if (ImGui::Selectable(id.c_str(), isSelected)) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && !isSelected) {
|
||||
mSelectedEntries.push_back(i);
|
||||
|
||||
} else {
|
||||
mSelectedEntries.clear();
|
||||
}
|
||||
mSelectedId = isSelected ? -1 : i;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (isSelected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (mCurrentChannel == "Console") {
|
||||
// Renders input textfield
|
||||
constexpr ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackEdit |
|
||||
ImGuiInputTextFlags_CallbackCompletion |
|
||||
ImGuiInputTextFlags_CallbackHistory;
|
||||
ImGui::PushItemWidth(-53.0f);
|
||||
|
||||
float yBeforeInput = ImGui::GetCursorPosY();
|
||||
|
||||
if (ImGui::InputTextWithHint("##CMDInput", ">", mInputBuffer, gMaxBufferSize, flags,
|
||||
&ConsoleWindow::CallbackStub, this)) {
|
||||
inputFocus = true;
|
||||
if (mInputBuffer[0] != '\0' && mInputBuffer[0] != ' ') {
|
||||
Dispatch(std::string(mInputBuffer));
|
||||
}
|
||||
memset(mInputBuffer, 0, gMaxBufferSize);
|
||||
}
|
||||
|
||||
if (mCmdHint != "None") {
|
||||
if (ImGui::IsItemFocused()) {
|
||||
// Place the tooltip above the console input field
|
||||
ImGui::SetNextWindowPos(ImVec2(pos.x, pos.y + size.y - ((size.y - yBeforeInput) * 2)));
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextUnformatted(mCmdHint.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 50);
|
||||
|
||||
if (ImGui::Button("WIDGET_SUBMIT") && !inputFocus && mInputBuffer[0] != '\0' && mInputBuffer[0] != ' ') {
|
||||
Dispatch(std::string(mInputBuffer));
|
||||
memset(mInputBuffer, 0, gMaxBufferSize);
|
||||
}
|
||||
|
||||
ImGui::SetItemDefaultFocus();
|
||||
if (inputFocus) {
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
}
|
||||
|
||||
void ConsoleWindow::Dispatch(const std::string& line) {
|
||||
mCmdHint = "None";
|
||||
mHistoryIndex = -1;
|
||||
mHistory.push_back(line);
|
||||
SendInfoMessage("> " + line);
|
||||
auto console = Context::GetInstance()->GetConsole();
|
||||
const std::vector<std::string> cmdArgs = StringHelper::Split(line, " ");
|
||||
if (console->HasCommand(cmdArgs[0])) {
|
||||
const CommandEntry entry = console->GetCommand(cmdArgs[0]);
|
||||
std::string output = "";
|
||||
int32_t commandResult = console->Run(line, &output);
|
||||
|
||||
if (commandResult != 0) {
|
||||
SendErrorMessage(StringHelper::Sprintf("[LUS] Command Failed with code %d", commandResult));
|
||||
if (!output.empty()) {
|
||||
SendErrorMessage(output);
|
||||
}
|
||||
SendErrorMessage("[LUS] Usage: " + cmdArgs[0] + " " + console->BuildUsage(entry));
|
||||
} else {
|
||||
if (!output.empty()) {
|
||||
SendInfoMessage(output);
|
||||
} else {
|
||||
if (line != "clear") {
|
||||
SendInfoMessage(std::string("[LUS] Command Success!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
SendErrorMessage("[LUS] Command not found");
|
||||
}
|
||||
|
||||
int ConsoleWindow::CallbackStub(ImGuiInputTextCallbackData* data) {
|
||||
const auto instance = static_cast<ConsoleWindow*>(data->UserData);
|
||||
const bool emptyHistory = instance->mHistory.empty();
|
||||
auto console = Context::GetInstance()->GetConsole();
|
||||
std::string history;
|
||||
|
||||
switch (data->EventKey) {
|
||||
case ImGuiKey_Tab:
|
||||
instance->mAutoComplete.clear();
|
||||
for (auto& [cmd, entry] : console->GetCommands()) {
|
||||
if (cmd.find(std::string(data->Buf)) != std::string::npos) {
|
||||
instance->mAutoComplete.push_back(cmd);
|
||||
}
|
||||
}
|
||||
instance->mOpenAutocomplete = !instance->mAutoComplete.empty();
|
||||
instance->mCmdHint = "None";
|
||||
break;
|
||||
case ImGuiKey_UpArrow:
|
||||
if (emptyHistory) {
|
||||
break;
|
||||
}
|
||||
if (instance->mHistoryIndex > 0) {
|
||||
instance->mHistoryIndex -= 1;
|
||||
} else if (instance->mHistoryIndex < 0) {
|
||||
instance->mHistoryIndex = static_cast<int>(instance->mHistory.size()) - 1;
|
||||
}
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
if (instance->mHistoryIndex >= 0) {
|
||||
data->InsertChars(0, instance->mHistory[instance->mHistoryIndex].c_str());
|
||||
}
|
||||
instance->mCmdHint = "None";
|
||||
break;
|
||||
case ImGuiKey_DownArrow:
|
||||
if (emptyHistory) {
|
||||
break;
|
||||
}
|
||||
if (instance->mHistoryIndex >= 0 &&
|
||||
instance->mHistoryIndex < static_cast<int>(instance->mHistory.size()) - 1) {
|
||||
instance->mHistoryIndex += 1;
|
||||
} else {
|
||||
instance->mHistoryIndex = -1;
|
||||
}
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
if (instance->mHistoryIndex >= 0) {
|
||||
data->InsertChars(0, instance->mHistory[instance->mHistoryIndex].c_str());
|
||||
}
|
||||
instance->mCmdHint = "None";
|
||||
break;
|
||||
case ImGuiKey_Escape:
|
||||
instance->mHistoryIndex = -1;
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
instance->mOpenAutocomplete = false;
|
||||
instance->mCmdHint = "None";
|
||||
break;
|
||||
default:
|
||||
instance->mOpenAutocomplete = false;
|
||||
for (auto& [cmd, entry] : console->GetCommands()) {
|
||||
const std::vector<std::string> cmdArgs = StringHelper::Split(std::string(data->Buf), " ");
|
||||
if (data->BufTextLen > 2 && !cmdArgs.empty() && cmd.find(cmdArgs[0]) != std::string::npos) {
|
||||
instance->mCmdHint = cmd + " " + console->BuildUsage(entry);
|
||||
break;
|
||||
}
|
||||
instance->mCmdHint = "None";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConsoleWindow::Append(const std::string& channel, spdlog::level::level_enum priority, const char* fmt,
|
||||
va_list args) {
|
||||
// Determine the size of the formatted string
|
||||
va_list argsCopy;
|
||||
va_copy(argsCopy, args);
|
||||
int size = vsnprintf(nullptr, 0, fmt, argsCopy);
|
||||
va_end(argsCopy);
|
||||
|
||||
if (size < 0) {
|
||||
SPDLOG_ERROR("Error during formatting.");
|
||||
SendErrorMessage("There has been an error during formatting!");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<char> buf(size + 1);
|
||||
vsnprintf(buf.data(), buf.size(), fmt, args);
|
||||
|
||||
buf[buf.size() - 1] = 0;
|
||||
// Do not copy the null terminator into the std::string
|
||||
mLog[channel].push_back({ std::string(buf.begin(), buf.end() - 1), priority });
|
||||
}
|
||||
|
||||
void ConsoleWindow::Append(const std::string& channel, spdlog::level::level_enum priority, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
Append(channel, priority, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ConsoleWindow::SendInfoMessage(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
Append("Console", spdlog::level::info, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ConsoleWindow::SendErrorMessage(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
Append("Console", spdlog::level::err, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ConsoleWindow::SendInfoMessage(const std::string& str) {
|
||||
Append("Console", spdlog::level::info, str.c_str());
|
||||
}
|
||||
|
||||
void ConsoleWindow::SendErrorMessage(const std::string& str) {
|
||||
Append("Console", spdlog::level::err, str.c_str());
|
||||
}
|
||||
|
||||
void ConsoleWindow::ClearLogs(std::string channel) {
|
||||
mLog[channel].clear();
|
||||
mSelectedEntries.clear();
|
||||
mSelectedId = -1;
|
||||
}
|
||||
|
||||
void ConsoleWindow::ClearLogs() {
|
||||
for (auto [key, var] : mLog) {
|
||||
var.clear();
|
||||
}
|
||||
mSelectedEntries.clear();
|
||||
mSelectedId = -1;
|
||||
}
|
||||
|
||||
std::string ConsoleWindow::GetCurrentChannel() {
|
||||
return mCurrentChannel;
|
||||
}
|
||||
|
||||
void ConsoleWindow::ClearBindings() {
|
||||
mBindings.clear();
|
||||
mBindingToggle.clear();
|
||||
}
|
||||
} // namespace Ship
|
||||
1410
libultraship/src/ship/window/gui/InputEditorWindow.cpp
Normal file
1410
libultraship/src/ship/window/gui/InputEditorWindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
44
soh/soh/Localization.cpp
Normal file
44
soh/soh/Localization.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "Localization.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace LUS {
|
||||
Localization* Localization::mInstance = nullptr;
|
||||
|
||||
Localization* Localization::GetInstance() {
|
||||
if (mInstance == nullptr) {
|
||||
mInstance = new Localization();
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
void Localization::LoadLanguage(const std::string& langCode) {
|
||||
std::string fileName = "languages/" + langCode + ".json";
|
||||
|
||||
SPDLOG_INFO("[Localization] Loading language: {}", langCode);
|
||||
|
||||
std::ifstream f(fileName);
|
||||
if (!f.is_open()) {
|
||||
SPDLOG_ERROR("[Localization] Could not open language file: {}", fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json data = nlohmann::json::parse(f);
|
||||
mTranslations = data.get<std::map<std::string, std::string>>();
|
||||
mCurrentLang = langCode;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error parsing language JSON: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
const char* Localization::Get(const std::string& key) {
|
||||
auto it = mTranslations.find(key);
|
||||
if (it != mTranslations.end()) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
return key.c_str();
|
||||
}
|
||||
}
|
||||
28
soh/soh/Localization.h
Normal file
28
soh/soh/Localization.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace LUS {
|
||||
class Localization {
|
||||
public:
|
||||
static Localization* GetInstance();
|
||||
|
||||
// Carga el idioma seleccionado desde un archivo JSON
|
||||
void LoadLanguage(const std::string& langCode);
|
||||
|
||||
// Obtiene el texto traducido. Si no existe la clave, devuelve la clave misma.
|
||||
// Devuelve const char* para compatibilidad con funciones C-style.
|
||||
const char* Get(const std::string& key);
|
||||
|
||||
private:
|
||||
static Localization* mInstance;
|
||||
std::map<std::string, std::string> mTranslations;
|
||||
std::string mCurrentLang;
|
||||
|
||||
Localization() = default;
|
||||
};
|
||||
}
|
||||
|
||||
// Macro global para facilitar el acceso en el código
|
||||
#define LUS_LOC(key) LUS::Localization::GetInstance()->Get(key)
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include "soh/Localization.h"
|
||||
|
||||
#include "ResourceManagerHelpers.h"
|
||||
#include <fast/Fast3dWindow.h>
|
||||
@@ -43,7 +44,6 @@
|
||||
#include <ship/utils/StringHelper.h>
|
||||
#include "Enhancements/custom-message/CustomMessageManager.h"
|
||||
#include "util.h"
|
||||
#include "Localization.h"
|
||||
|
||||
#if not defined(__SWITCH__) && not defined(__WIIU__)
|
||||
#include "Extractor/Extract.h"
|
||||
|
||||
Reference in New Issue
Block a user