Merge branch 'master' into test2

This commit is contained in:
Aloqas 2024-07-31 18:55:20 +03:00 committed by GitHub
commit f1ee25c640
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1321 changed files with 284583 additions and 324303 deletions

5
.gitignore vendored
View File

@ -58,6 +58,11 @@ custom_assets/jak1/texture_replacements/*
custom_assets/jak2/texture_replacements/* custom_assets/jak2/texture_replacements/*
custom_assets/jak3/texture_replacements/* custom_assets/jak3/texture_replacements/*
# merc replacements
custom_assets/jak1/merc_replacements/*
custom_assets/jak2/merc_replacements/*
custom_assets/jak3/merc_replacements/*
# generated cmake files # generated cmake files
svnrev.h svnrev.h
common/versions/revision.h common/versions/revision.h

View File

@ -7,42 +7,58 @@
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Summary", "name": "Tests - Unit-Tests - Summary",
"args": ["--gtest_brief=1"] "args": [
"--gtest_brief=1"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Verbose", "name": "Tests - Unit-Tests - Verbose",
"args": ["--gtest_brief=0"] "args": [
"--gtest_brief=0"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Draft Tests - Verbose", "name": "Tests - Draft Tests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""] "args": [
"--gtest_brief=0",
"--gtest_filter=\"*Draft*\""
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - TypeConsistency - Verbose", "name": "Tests - TypeConsistency - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""] "args": [
"--gtest_brief=0",
"--gtest_filter=\"*TypeConsistency*\""
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - TypeConsistency - Jak 2 - Verbose", "name": "Tests - TypeConsistency - Jak 2 - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*Jak2TypeConsistency*\""] "args": [
"--gtest_brief=0",
"--gtest_filter=\"*Jak2TypeConsistency*\""
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - WithGameTests - Verbose", "name": "Tests - WithGameTests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""] "args": [
"--gtest_brief=0",
"--gtest_filter=\"*WithGameTests*\""
]
}, },
{ {
"type": "default", "type": "default",
@ -113,56 +129,109 @@
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 1 - Runtime", "name": "Game - Jak 1 - Runtime",
"args": ["-v", "--game", "jak1", "--", "-fakeiso", "-debug"] "args": [
"-v",
"--game",
"jak1",
"--",
"-fakeiso",
"-debug"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 1 - Runtime (boot)", "name": "Game - Jak 1 - Runtime (boot)",
"args": ["-v", "--game", "jak1", "--", "-boot", "-fakeiso", "-debug"] "args": [
"-v",
"--game",
"jak1",
"--",
"-boot",
"-fakeiso",
"-debug"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (no boot)", "name": "Game - Jak 2 - Runtime (no boot)",
"args": ["-v", "--game", "jak2", "--", "-fakeiso", "-debug"] "args": [
"-v",
"--game",
"jak2",
"--",
"-fakeiso",
"-debug"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (boot)", "name": "Game - Jak 2 - Runtime (boot)",
"args": ["-v", "--game", "jak2", "--", "-boot", "-fakeiso", "-debug"] "args": [
"-v",
"--game",
"jak2",
"--",
"-boot",
"-fakeiso",
"-debug"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (release)", "name": "Game - Jak 2 - Runtime (release)",
"args": ["-v", "--game", "jak2", "--", "-boot", "-fakeiso"] "args": [
"-v",
"--game",
"jak2",
"--",
"-boot",
"-fakeiso"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)", "projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 3 - Runtime (boot)", "name": "Game - Jak 3 - Runtime (boot)",
"args": ["-v", "--game", "jak3", "--", "-boot", "-fakeiso", "-debug"] "args": [
"-v",
"--game",
"jak3",
"--",
"-boot",
"-fakeiso",
"-debug"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)", "projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "REPL - Jak 1", "name": "REPL - Jak 1",
"args": ["--user-auto", "--game", "jak1"] "args": [
"--user-auto",
"--game",
"jak1"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)", "projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "REPL - Jak 2", "name": "REPL - Jak 2",
"args": ["--user-auto", "--game", "jak2"] "args": [
"--user-auto",
"--game",
"jak2"
]
}, },
{ {
"type": "default", "type": "default",
@ -262,14 +331,26 @@
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", "projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name": "Tools - EE Memory Analyze - Jak 1", "name": "Tools - EE Memory Analyze - Jak 1",
"args": ["\"${workspaceRoot}/eeMemory.bin\"", "--output-path", "\"${workspaceRoot}\"", "--game", "jak1"] "args": [
"\"${workspaceRoot}/eeMemory.bin\"",
"--output-path",
"\"${workspaceRoot}\"",
"--game",
"jak1"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", "projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name": "Tools - EE Memory Analyze - Jak 2", "name": "Tools - EE Memory Analyze - Jak 2",
"args": ["\"${workspaceRoot}/eeMemory.bin\"", "--output-path", "\"${workspaceRoot}\"", "--game", "jak2"] "args": [
"\"${workspaceRoot}/eeMemory.bin\"",
"--output-path",
"\"${workspaceRoot}\"",
"--game",
"jak2"
]
}, },
{ {
"type": "default", "type": "default",
@ -286,35 +367,53 @@
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "extractor.exe (bin\\extractor.exe)", "projectTarget": "extractor.exe (bin\\extractor.exe)",
"name": "Tools - Extractor - Full", "name": "Tools - Extractor - Full",
"args": ["\"E:\\ISOs\\Jak\\Jak 1.iso\""] "args": [
"\"E:\\ISOs\\Jak\\Jak 1.iso\""
]
}, },
{ {
"type" : "default", "type": "default",
"project" : "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget" : "lsp.exe (bin\\lsp.exe)", "projectTarget": "lsp.exe (bin\\lsp.exe)",
"name" : "Tools - LSP", "name": "Tools - LSP",
"args" : [] "args": []
}, },
{ {
"type" : "default", "type": "default",
"project" : "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget" : "type_searcher.exe (bin\\type_searcher.exe)", "projectTarget": "type_searcher.exe (bin\\type_searcher.exe)",
"name" : "Tools - Type Searcher", "name": "Tools - Type Searcher",
"args" : ["--game", "jak2", "--output-path", "./search-results.json", "--size", 255, "--fields", "[{\\\"type\\\":\\\"quaternion\\\",\\\"offset\\\":48}]"] "args": [
"--game",
"jak2",
"--output-path",
"./search-results.json",
"--size",
255,
"--fields",
"[{\\\"type\\\":\\\"quaternion\\\",\\\"offset\\\":48}]"
]
}, },
{ {
"type" : "default", "type": "default",
"project" : "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget" : "formatter.exe (bin\\formatter.exe)", "projectTarget": "formatter.exe (bin\\formatter.exe)",
"name" : "Tools - Formatter - Inplace", "name": "Tools - Formatter - Inplace",
"args" : ["--write", "--file", "C:\\Users\\xtvas\\Repos\\opengoal\\jak-project\\decompiler_out\\jak3\\mood-h_disasm.gc"] "args": [
"--write",
"--file",
"C:\\Users\\xtvas\\Repositories\\opengoal\\jak-project\\goal_src\\jak1\\engine\\camera\\cam-states.gc"
]
}, },
{ {
"type": "default", "type": "default",
"project": "CMakeLists.txt", "project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Formatter", "name": "Tests - Formatter",
"args": ["--gtest_brief=0", "--gtest_filter=\"*FormatterTests*\""] "args": [
"--gtest_brief=0",
"--gtest_filter=\"*FormatterTests*\""
]
} }
] ]
} }

4
.vscode/launch.json vendored
View File

@ -8,10 +8,10 @@
"name": "run python script", "name": "run python script",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/scripts/gsrc/copy-common-naming.py", "program": "${workspaceFolder}/scripts/gsrc/compare-compilation-outputs.py",
"console": "integratedTerminal", "console": "integratedTerminal",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"args": ["--update-names-from-refs", "--decompiler", "./out/build/Release/bin/decompiler"] "args": []
}, },
] ]
} }

View File

@ -51,7 +51,7 @@ add_library(common
repl/config.cpp repl/config.cpp
repl/nrepl/ReplClient.cpp repl/nrepl/ReplClient.cpp
repl/nrepl/ReplServer.cpp repl/nrepl/ReplServer.cpp
repl/util.cpp repl/repl_wrapper.cpp
serialization/subtitles/subtitles_v1.cpp serialization/subtitles/subtitles_v1.cpp
serialization/subtitles/subtitles_v2.cpp serialization/subtitles/subtitles_v2.cpp
serialization/subtitles/subtitles.cpp serialization/subtitles/subtitles.cpp

View File

@ -5,6 +5,8 @@
#include "common/cross_sockets/XSocket.h" #include "common/cross_sockets/XSocket.h"
#include "common/log/log.h" #include "common/log/log.h"
#include "fmt/core.h"
// clang-format off // clang-format off
#ifdef _WIN32 #ifdef _WIN32
#define NOMINMAX #define NOMINMAX
@ -13,9 +15,7 @@
#include <WinSock2.h> #include <WinSock2.h>
#include <WS2tcpip.h> #include <WS2tcpip.h>
#endif #endif
#include "common/repl/nrepl/ReplServer.h"
#include "fmt/core.h"
// clang-format on // clang-format on
XSocketClient::XSocketClient(int _tcp_port) { XSocketClient::XSocketClient(int _tcp_port) {

View File

@ -35,7 +35,7 @@ void XSocketServer::shutdown_server() {
close_server_socket(); close_server_socket();
} }
bool XSocketServer::init_server() { bool XSocketServer::init_server(bool failure_may_occur) {
listening_socket = open_socket(AF_INET, SOCK_STREAM, 0); listening_socket = open_socket(AF_INET, SOCK_STREAM, 0);
if (listening_socket < 0) { if (listening_socket < 0) {
listening_socket = -1; listening_socket = -1;
@ -76,19 +76,27 @@ bool XSocketServer::init_server() {
addr.sin_port = htons(tcp_port); addr.sin_port = htons(tcp_port);
if (bind(listening_socket, (sockaddr*)&addr, sizeof(addr)) < 0) { if (bind(listening_socket, (sockaddr*)&addr, sizeof(addr)) < 0) {
lg::error("[XSocketServer:{}] failed to bind", tcp_port); if (failure_may_occur) {
lg::debug("[XSocketServer:{}] failed to bind", tcp_port);
} else {
lg::error("[XSocketServer:{}] failed to bind", tcp_port);
}
close_server_socket(); close_server_socket();
return false; return false;
} }
if (listen(listening_socket, 0) < 0) { if (listen(listening_socket, 0) < 0) {
lg::error("[XSocketServer:{}] failed to listen", tcp_port); if (failure_may_occur) {
lg::debug("[XSocketServer:{}] failed to listen", tcp_port);
} else {
lg::error("[XSocketServer:{}] failed to listen", tcp_port);
}
close_server_socket(); close_server_socket();
return false; return false;
} }
server_initialized = true; server_initialized = true;
lg::info("[XSocketServer:{}] initialized", tcp_port); lg::debug("[XSocketServer:{}] initialized", tcp_port);
post_init(); post_init();
return true; return true;
} }

View File

@ -20,7 +20,7 @@ class XSocketServer {
XSocketServer(const XSocketServer&) = delete; XSocketServer(const XSocketServer&) = delete;
XSocketServer& operator=(const XSocketServer&) = delete; XSocketServer& operator=(const XSocketServer&) = delete;
bool init_server(); bool init_server(bool failure_may_occur = false);
void shutdown_server(); void shutdown_server();
void close_server_socket(); void close_server_socket();

View File

@ -492,6 +492,7 @@ void MercDraw::serialize(Serializer& ser) {
ser.from_ptr(&first_index); ser.from_ptr(&first_index);
ser.from_ptr(&index_count); ser.from_ptr(&index_count);
ser.from_ptr(&num_triangles); ser.from_ptr(&num_triangles);
ser.from_ptr(&no_strip);
} }
void Blerc::serialize(Serializer& ser) { void Blerc::serialize(Serializer& ser) {

View File

@ -18,7 +18,7 @@ namespace tfrag3 {
// - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage // - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage
// - if adding a new category to the memory usage, update extract_level to print it. // - if adding a new category to the memory usage, update extract_level to print it.
constexpr int TFRAG3_VERSION = 40; constexpr int TFRAG3_VERSION = 41;
enum MemoryUsageCategory { enum MemoryUsageCategory {
TEXTURE, TEXTURE,
@ -530,6 +530,8 @@ struct MercDraw {
u32 first_index; u32 first_index;
u32 index_count; u32 index_count;
u32 num_triangles; u32 num_triangles;
// no strip hack for custom models
bool no_strip = false;
void serialize(Serializer& ser); void serialize(Serializer& ser);
}; };

View File

@ -4,7 +4,7 @@
#include "fmt/core.h" #include "fmt/core.h"
std::string DmaTag::print() { std::string DmaTag::print() const {
std::string result; std::string result;
const char* mode_names[8] = {"refe", "cnt", "next", "ref", "refs", "call", "ret", "end"}; const char* mode_names[8] = {"refe", "cnt", "next", "ref", "refs", "call", "ret", "end"};
result += fmt::format("TAG: 0x{:08x} {:4s} qwc 0x{:04x}", addr, mode_names[(int)kind], qwc); result += fmt::format("TAG: 0x{:08x} {:4s} qwc 0x{:04x}", addr, mode_names[(int)kind], qwc);
@ -15,7 +15,7 @@ std::string DmaTag::print() {
return result; return result;
} }
std::string VifCode::print() { std::string VifCode::print() const {
std::string result; std::string result;
switch (kind) { switch (kind) {

View File

@ -52,7 +52,7 @@ struct DmaTag {
bool operator!=(const DmaTag& other) const { return !((*this) == other); } bool operator!=(const DmaTag& other) const { return !((*this) == other); }
std::string print(); std::string print() const;
}; };
inline void emulate_dma(const void* source_base, void* dest_base, u32 tadr, u32 dadr) { inline void emulate_dma(const void* source_base, void* dest_base, u32 tadr, u32 dadr) {
@ -148,7 +148,7 @@ struct VifCode {
u16 num; u16 num;
u16 immediate; u16 immediate;
std::string print(); std::string print() const;
}; };
struct VifCodeStcycl { struct VifCodeStcycl {

View File

@ -12,7 +12,6 @@ Also in general I found it _really_ hard to find modern and easy to understand a
Atleast for me, it helps to understand the path code takes as it flows through the formatting process. Originally I tried to do as much at once for sake of efficiency but this just makes things incredibly hard to reason about and fix bugs. So like any problem that is complex, break it down, the formatter goes through many distinct phases chipping away at the problem: Atleast for me, it helps to understand the path code takes as it flows through the formatting process. Originally I tried to do as much at once for sake of efficiency but this just makes things incredibly hard to reason about and fix bugs. So like any problem that is complex, break it down, the formatter goes through many distinct phases chipping away at the problem:
```mermaid ```mermaid
%%{init: {'theme': 'dark', "flowchart" : { "curve" : "basis" } } }%%
flowchart TB flowchart TB
subgraph top1 [Build a Formatting Tree] subgraph top1 [Build a Formatting Tree]
direction TB direction TB

View File

@ -33,14 +33,16 @@ int hang_indentation_width(const FormatterTreeNode& curr_node) {
return 1 + hang_indentation_width(first_elt); return 1 + hang_indentation_width(first_elt);
} }
// TODO - this doesn't account for paren's width contribution!
int get_total_form_inlined_width(const FormatterTreeNode& curr_node) { int get_total_form_inlined_width(const FormatterTreeNode& curr_node) {
if (curr_node.token) { if (curr_node.token) {
return curr_node.token->length(); return curr_node.token->length();
} }
int width = 1; int width = 1;
for (const auto& ref : curr_node.refs) { for (int i = 0; i < curr_node.refs.size(); i++) {
width += get_total_form_inlined_width(ref); width += get_total_form_inlined_width(curr_node.refs.at(i));
if (i != curr_node.refs.size() - 1) {
width += 1; // add the space between elements
}
} }
return width + 1; return width + 1;
} }
@ -49,7 +51,9 @@ int get_total_form_inlined_width(const FormatterTreeNode& curr_node) {
void apply_formatting_config( void apply_formatting_config(
FormatterTreeNode& curr_node, FormatterTreeNode& curr_node,
std::optional<std::shared_ptr<formatter_rules::config::FormFormattingConfig>> std::optional<std::shared_ptr<formatter_rules::config::FormFormattingConfig>>
config_from_parent = {}) { config_from_parent = {},
std::optional<std::shared_ptr<formatter_rules::config::FormFormattingConfig>>
config_merge_from_parent = {}) {
using namespace formatter_rules; using namespace formatter_rules;
// node is empty, base-case // node is empty, base-case
if (curr_node.token || curr_node.refs.empty()) { if (curr_node.token || curr_node.refs.empty()) {
@ -65,6 +69,13 @@ void apply_formatting_config(
predefined_config = config::opengoal_form_config.at(form_head.value()); predefined_config = config::opengoal_form_config.at(form_head.value());
curr_node.formatting_config = predefined_config.value(); curr_node.formatting_config = predefined_config.value();
} }
if (config_merge_from_parent) {
const auto& merge_config = *config_merge_from_parent.value();
curr_node.formatting_config.parent_mutable_extra_indent +=
merge_config.parent_mutable_extra_indent;
curr_node.formatting_config.prevent_inlining = merge_config.prevent_inlining;
}
} else if (config_from_parent) { } else if (config_from_parent) {
// TODO - doesn't merge just replaces, a bit inflexible // TODO - doesn't merge just replaces, a bit inflexible
predefined_config = *config_from_parent.value(); predefined_config = *config_from_parent.value();
@ -104,7 +115,7 @@ void apply_formatting_config(
if (curr_node.formatting_config.has_constant_pairs) { if (curr_node.formatting_config.has_constant_pairs) {
for (int i = 0; i < (int)curr_node.refs.size(); i++) { for (int i = 0; i < (int)curr_node.refs.size(); i++) {
auto& child_ref = curr_node.refs.at(i); auto& child_ref = curr_node.refs.at(i);
const auto type = child_ref.metadata.node_type; const auto& type = child_ref.metadata.node_type;
if (constant_types.find(type) == constant_types.end() && if (constant_types.find(type) == constant_types.end() &&
constant_pairs::is_element_second_in_constant_pair(curr_node, child_ref, i)) { constant_pairs::is_element_second_in_constant_pair(curr_node, child_ref, i)) {
child_ref.formatting_config.parent_mutable_extra_indent = 2; child_ref.formatting_config.parent_mutable_extra_indent = 2;
@ -129,13 +140,16 @@ void apply_formatting_config(
auto& ref = curr_node.refs.at(i); auto& ref = curr_node.refs.at(i);
if (!ref.token) { if (!ref.token) {
// If the child has a pre-defined configuration at that index, we pass it along // If the child has a pre-defined configuration at that index, we pass it along
if (predefined_config && if (predefined_config && predefined_config->index_config_override.find(i) !=
predefined_config->index_configs.find(i) != predefined_config->index_configs.end()) { predefined_config->index_config_override.end()) {
apply_formatting_config(ref, predefined_config->index_configs.at(i)); apply_formatting_config(ref, {}, predefined_config->index_config_override.at(i));
} else if (predefined_config && predefined_config->index_configs.find(i) !=
predefined_config->index_configs.end()) {
apply_formatting_config(ref, predefined_config->index_configs.at(i), {});
} else if (predefined_config && predefined_config->default_index_config) { } else if (predefined_config && predefined_config->default_index_config) {
apply_formatting_config(ref, predefined_config->default_index_config); apply_formatting_config(ref, predefined_config->default_index_config, {});
} else { } else {
apply_formatting_config(ref); apply_formatting_config(ref, {}, {});
} }
} }
} }
@ -151,10 +165,27 @@ void apply_formatting_config(
max_columns = field.refs.size(); max_columns = field.refs.size();
} }
} }
// if only one field has a value in the max col position, it looks weird for it to be indented
bool ignore_final_column_width = true;
int fields_with_atleast_max_col = 0;
for (const auto& field : curr_node.refs) {
if ((int)field.refs.size() == max_columns) {
fields_with_atleast_max_col++;
if (fields_with_atleast_max_col > 1) {
ignore_final_column_width = false;
break;
}
}
}
// Now find the column max widths // Now find the column max widths
std::vector<int> column_max_widths = {}; std::vector<int> column_max_widths = {};
for (int col = 0; col < max_columns; col++) { for (int col = 0; col < max_columns; col++) {
column_max_widths.push_back(0); column_max_widths.push_back(0);
// -2 because its the indentation before the final column that we want to skip
if (col >= curr_node.formatting_config.num_columns_to_compute_widths ||
(ignore_final_column_width && col == max_columns - 2)) {
continue;
}
for (const auto& field : curr_node.refs) { for (const auto& field : curr_node.refs) {
if ((int)field.refs.size() > col) { if ((int)field.refs.size() > col) {
const auto width = get_total_form_inlined_width(field.refs.at(col)); const auto width = get_total_form_inlined_width(field.refs.at(col));
@ -202,6 +233,10 @@ bool form_contains_node_that_prevents_inlining(const FormatterTreeNode& curr_nod
bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) { bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) {
using namespace formatter_rules; using namespace formatter_rules;
if (curr_node.formatting_config.force_inline) { if (curr_node.formatting_config.force_inline) {
// Ensure there are no comments, this still trumps this
if (form_contains_comment(curr_node)) {
return false;
}
return true; return true;
} }
// First off, we cannot inline the top level // First off, we cannot inline the top level
@ -264,13 +299,15 @@ std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
// Add new line entry // Add new line entry
if (ref.token) { if (ref.token) {
// Cleanup block-comments // Cleanup block-comments
std::string val = ref.token_str();
if (ref.metadata.node_type == "block_comment") { if (ref.metadata.node_type == "block_comment") {
// TODO - change this sanitization to return a list of lines instead of a single new-lined const auto comment_lines = comments::format_block_comment(ref.token_str());
// line for (const auto& line : comment_lines) {
val = comments::format_block_comment(ref.token_str()); form_lines.push_back(line);
}
} else {
form_lines.push_back(ref.token_str());
} }
form_lines.push_back(val);
if (!curr_node.metadata.is_top_level && i == (int)curr_node.refs.size() - 1 && if (!curr_node.metadata.is_top_level && i == (int)curr_node.refs.size() - 1 &&
(ref.metadata.is_comment)) { (ref.metadata.is_comment)) {
// if there's an inline comment at the end of a form, we have to force the paren to the next // if there's an inline comment at the end of a form, we have to force the paren to the next
@ -292,26 +329,41 @@ std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
if (i == (int)curr_node.refs.size() - 1 && form_lines.size() > 1 && if (i == (int)curr_node.refs.size() - 1 && form_lines.size() > 1 &&
(curr_node.formatting_config.hang_forms || (curr_node.formatting_config.hang_forms ||
curr_node.formatting_config.combine_first_two_lines)) { curr_node.formatting_config.combine_first_two_lines)) {
form_lines.at(0) += fmt::format(" {}", form_lines.at(1)); form_lines.at(0) += fmt::format(" {}", str_util::ltrim(form_lines.at(1)));
form_lines.erase(form_lines.begin() + 1); form_lines.erase(form_lines.begin() + 1);
} else if ((i + 1) < (int)curr_node.refs.size()) { } else if ((i + 1) < (int)curr_node.refs.size()) {
const auto& next_ref = curr_node.refs.at(i + 1); const auto& next_ref = curr_node.refs.at(i + 1);
// combine the next inline comment or constant pair // combine the next inline comment or constant pair
if ((next_ref.metadata.node_type == "comment" && next_ref.metadata.is_inline) || if ((next_ref.metadata.node_type == "comment" && next_ref.metadata.is_inline) ||
(curr_node.formatting_config.has_constant_pairs && (curr_node.formatting_config.has_constant_pairs &&
constant_pairs::is_element_second_in_constant_pair(curr_node, next_ref, i + 1))) { constant_pairs::is_element_second_in_constant_pair(curr_node, next_ref, i + 1)) ||
constant_pairs::is_element_second_in_constant_pair_new(curr_node.refs.at(i), next_ref)) {
// TODO // TODO
// has issues with not consolidating first lines, this should probably just be moved to // has issues with not consolidating first lines, this should probably just be moved to
// outside this loop for simplicity, do it later // outside this loop for simplicity, do it later
if (next_ref.token) { if (next_ref.token) {
form_lines.at(form_lines.size() - 1) += fmt::format(" {}", next_ref.token.value()); form_lines.at(form_lines.size() - 1) += fmt::format(" {}", next_ref.token_str());
i++; i++;
// We have to handle hang-consolidation here or else it will never be reached above!
if (i == (int)curr_node.refs.size() - 1 && form_lines.size() > 1 &&
(curr_node.formatting_config.hang_forms ||
curr_node.formatting_config.combine_first_two_lines)) {
form_lines.at(0) += fmt::format(" {}", form_lines.at(1));
form_lines.erase(form_lines.begin() + 1);
}
} else if (can_node_be_inlined(next_ref, cursor_pos)) { } else if (can_node_be_inlined(next_ref, cursor_pos)) {
const auto& lines = apply_formatting(next_ref, {}, cursor_pos); // TODO - cursor pos const auto& lines = apply_formatting(next_ref, {}, cursor_pos); // TODO - cursor pos
for (const auto& line : lines) { for (const auto& line : lines) {
form_lines.at(form_lines.size() - 1) += fmt::format(" {}", line); form_lines.at(form_lines.size() - 1) += fmt::format(" {}", line);
} }
i++; i++;
// We have to handle hang-consolidation here or else it will never be reached above!
if (i == (int)curr_node.refs.size() - 1 && form_lines.size() > 1 &&
(curr_node.formatting_config.hang_forms ||
curr_node.formatting_config.combine_first_two_lines)) {
form_lines.at(0) += fmt::format(" {}", form_lines.at(1));
form_lines.erase(form_lines.begin() + 1);
}
} }
if (!curr_node.metadata.is_top_level && next_ref.metadata.node_type == "comment" && if (!curr_node.metadata.is_top_level && next_ref.metadata.node_type == "comment" &&
(i + 1) == (int)curr_node.refs.size()) { (i + 1) == (int)curr_node.refs.size()) {
@ -328,7 +380,9 @@ std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
// Consolidate any lines if the configuration requires it // Consolidate any lines if the configuration requires it
// TODO there is a hack here so that multi-line forms that are consolidated still line up properly // TODO there is a hack here so that multi-line forms that are consolidated still line up properly
// i have to make consolidate a more first-class feature of the config // i have to make consolidate a more first-class feature of the config
if (curr_node.formatting_config.inline_until_index(form_lines)) { // TODO - hacky, but prevents a bad situation, clean up
if (curr_node.formatting_config.inline_until_index(form_lines) &&
!str_util::contains(form_lines.at(0), ";")) {
std::vector<std::string> new_form_lines = {}; std::vector<std::string> new_form_lines = {};
const auto original_form_head_width = str_util::split(form_lines.at(0), '\n').at(0).length(); const auto original_form_head_width = str_util::split(form_lines.at(0), '\n').at(0).length();
bool consolidating_lines = true; bool consolidating_lines = true;
@ -374,22 +428,32 @@ std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
form_lines[form_lines.size() - 1] = form_lines[form_lines.size() - 1] =
fmt::format("{}{}", form_lines[form_lines.size() - 1], form_surround_end); fmt::format("{}{}", form_lines[form_lines.size() - 1], form_surround_end);
} }
std::string curr_form = "";
if (curr_node.formatting_config.parent_mutable_extra_indent > 0) {
curr_form += str_util::repeat(curr_node.formatting_config.parent_mutable_extra_indent, " ");
}
if (inline_form) { if (inline_form) {
form_lines = {fmt::format("{}", fmt::join(form_lines, " "))}; // NOTE - not sure about this, if we are inlining a form, it always makes sense to eliminate
// trailing whitespace the only issue i can foresee is related to strings that span multiple
// lines.
std::vector<std::string> new_form_lines = {};
for (const auto& form_line : form_lines) {
new_form_lines.push_back(str_util::ltrim(form_line));
}
form_lines = {fmt::format("{}", fmt::join(new_form_lines, " "))};
} else { } else {
bool currently_in_block_comment = false;
for (int i = 0; i < (int)form_lines.size(); i++) { for (int i = 0; i < (int)form_lines.size(); i++) {
if (i > 0) { auto& line = form_lines.at(i);
auto& line = form_lines.at(i); if (str_util::contains(line, "|#")) {
currently_in_block_comment = false;
}
if (i > 0 && !currently_in_block_comment) {
line = fmt::format("{}{}", line = fmt::format("{}{}",
str_util::repeat(curr_node.formatting_config.indentation_width_for_index( str_util::repeat(curr_node.formatting_config.indentation_width_for_index(
curr_node.formatting_config, i), curr_node.formatting_config, i),
" "), " "),
line); line);
} }
if (str_util::contains(line, "#|") && !str_util::contains(line, "|#")) {
currently_in_block_comment = true;
}
} }
} }
return form_lines; return form_lines;

View File

@ -79,18 +79,18 @@ FormatterTree::FormatterTree(const std::string& source, const TSNode& root_node)
} }
const std::unordered_map<std::string, std::vector<std::string>> node_type_ignorable_contents = { const std::unordered_map<std::string, std::vector<std::string>> node_type_ignorable_contents = {
{"list_lit", {"(", ")"}}, {"list_lit", {"(", ")"}}};
{"quoting_lit", {"'"}},
{"unquoting_lit", {","}},
{"quasi_quoting_lit", {"`"}}};
// TODO make an imperative version eventually // TODO make an imperative version eventually
// TODO - cleanup duplication
void FormatterTree::construct_formatter_tree_recursive(const std::string& source, void FormatterTree::construct_formatter_tree_recursive(const std::string& source,
TSNode curr_node, TSNode curr_node,
FormatterTreeNode& tree_node, FormatterTreeNode& tree_node,
std::optional<std::string> node_prefix) { std::optional<std::string> node_prefix) {
if (ts_node_child_count(curr_node) == 0) { if (ts_node_child_count(curr_node) == 0) {
tree_node.refs.push_back(FormatterTreeNode(source, curr_node)); auto new_node = FormatterTreeNode(source, curr_node);
new_node.node_prefix = node_prefix;
tree_node.refs.push_back(new_node);
return; return;
} }
const std::string curr_node_type = ts_node_type(curr_node); const std::string curr_node_type = ts_node_type(curr_node);
@ -105,11 +105,37 @@ void FormatterTree::construct_formatter_tree_recursive(const std::string& source
tree_node.refs.push_back(FormatterTreeNode(source, curr_node)); tree_node.refs.push_back(FormatterTreeNode(source, curr_node));
return; return;
} else if (curr_node_type == "quoting_lit") { } else if (curr_node_type == "quoting_lit") {
next_node_prefix = "'"; if (node_prefix) {
node_prefix.value() += "'";
} else {
node_prefix = "'";
}
construct_formatter_tree_recursive(source, ts_node_child(curr_node, 1), tree_node, node_prefix);
return;
} else if (curr_node_type == "unquoting_lit") { } else if (curr_node_type == "unquoting_lit") {
next_node_prefix = ","; if (node_prefix) {
node_prefix.value() += ",";
} else {
node_prefix = ",";
}
construct_formatter_tree_recursive(source, ts_node_child(curr_node, 1), tree_node, node_prefix);
return;
} else if (curr_node_type == "quasi_quoting_lit") { } else if (curr_node_type == "quasi_quoting_lit") {
next_node_prefix = "`"; if (node_prefix) {
node_prefix.value() += "`";
} else {
node_prefix = "`";
}
construct_formatter_tree_recursive(source, ts_node_child(curr_node, 1), tree_node, node_prefix);
return;
} else if (curr_node_type == "unquote_splicing_lit") {
if (node_prefix) {
node_prefix.value() += ",@";
} else {
node_prefix = ",@";
}
construct_formatter_tree_recursive(source, ts_node_child(curr_node, 1), tree_node, node_prefix);
return;
} }
std::vector<std::string> skippable_nodes = {}; std::vector<std::string> skippable_nodes = {};
if (node_type_ignorable_contents.find(curr_node_type) != node_type_ignorable_contents.end()) { if (node_type_ignorable_contents.find(curr_node_type) != node_type_ignorable_contents.end()) {
@ -117,6 +143,7 @@ void FormatterTree::construct_formatter_tree_recursive(const std::string& source
} }
for (size_t i = 0; i < ts_node_child_count(curr_node); i++) { for (size_t i = 0; i < ts_node_child_count(curr_node); i++) {
const auto child_node = ts_node_child(curr_node, i); const auto child_node = ts_node_child(curr_node, i);
auto debug_child = ts_node_string(child_node);
const auto contents = get_source_code(source, child_node); const auto contents = get_source_code(source, child_node);
bool skip_node = false; bool skip_node = false;
for (const auto& skippable_content : skippable_nodes) { for (const auto& skippable_content : skippable_nodes) {
@ -129,19 +156,22 @@ void FormatterTree::construct_formatter_tree_recursive(const std::string& source
continue; continue;
} }
if (curr_node_type == "list_lit") { if (curr_node_type == "list_lit") {
construct_formatter_tree_recursive(source, child_node, list_node, next_node_prefix); construct_formatter_tree_recursive(source, child_node, list_node, {});
if (node_prefix) { if (node_prefix) {
list_node.node_prefix = node_prefix; list_node.node_prefix = node_prefix;
} }
} else { } else {
construct_formatter_tree_recursive(source, child_node, tree_node, next_node_prefix); construct_formatter_tree_recursive(source, child_node, tree_node, node_prefix);
// TODO - im not sure if this is correct
if (node_prefix && !tree_node.refs.empty()) { if (node_prefix && !tree_node.refs.empty()) {
tree_node.refs.at(tree_node.refs.size() - 1).node_prefix = node_prefix; tree_node.refs.at(tree_node.refs.size() - 1).node_prefix = node_prefix;
} }
} }
} }
if (curr_node_type == "list_lit") { if (curr_node_type == "list_lit") {
// special case for empty lists
if (node_prefix && !list_node.node_prefix) {
list_node.node_prefix = node_prefix;
}
tree_node.refs.push_back(list_node); tree_node.refs.push_back(list_node);
} }
} }

View File

@ -12,12 +12,16 @@ namespace formatter_rules {
// differentiate between a quoted symbol and a quoted form // differentiate between a quoted symbol and a quoted form
const std::set<std::string> constant_types = {"kwd_lit", "num_lit", "str_lit", const std::set<std::string> constant_types = {"kwd_lit", "num_lit", "str_lit",
"char_lit", "null_lit", "bool_lit"}; "char_lit", "null_lit", "bool_lit"};
const std::set<std::string> constant_type_forms = {"meters", "seconds", "degrees"};
namespace constant_list { namespace constant_list {
bool is_constant_list(const FormatterTreeNode& node) { bool is_constant_list(const FormatterTreeNode& node) {
if (!node.is_list() || node.refs.empty()) { if (!node.is_list() || node.refs.empty()) {
return false; return false;
} }
if (!node.refs.at(0).token) {
return true;
}
const auto& type = node.refs.at(0).metadata.node_type; const auto& type = node.refs.at(0).metadata.node_type;
return constant_types.find(type) != constant_types.end(); return constant_types.find(type) != constant_types.end();
} }
@ -36,21 +40,28 @@ bool should_insert_blank_line(const FormatterTreeNode& containing_node,
if (node.metadata.is_comment && node.metadata.num_blank_lines_following == 0) { if (node.metadata.is_comment && node.metadata.num_blank_lines_following == 0) {
return false; return false;
} }
// If the next form is a comment and is inline, don't insert a comment // If the next form is a comment and is inline, don't insert a new line
if ((index + 1) < (int)containing_node.refs.size() && if ((index + 1) < (int)containing_node.refs.size() &&
containing_node.refs.at(index + 1).metadata.is_comment && containing_node.refs.at(index + 1).metadata.is_comment &&
containing_node.refs.at(index + 1).metadata.is_inline) { containing_node.refs.at(index + 1).metadata.is_inline) {
return false; return false;
} }
// TODO - only if the form doesn't fit on a single line if (node.formatting_config.elide_top_level_newline) {
if ((index + 1) < (int)containing_node.refs.size() &&
containing_node.refs.at(index + 1).metadata.is_comment) {
return true;
}
return false;
}
return true; return true;
} }
} // namespace blank_lines } // namespace blank_lines
namespace comments { namespace comments {
std::string format_block_comment(const std::string& comment) { std::vector<std::string> format_block_comment(const std::string& comment) {
// Normalize block comments, remove any trailing or leading whitespace // Normalize block comments, remove any trailing or leading whitespace
// Only allow annotations on the first line, like #|@file // Only allow annotations on the first line, like #|@file
// Don't mess with internal indentation as the user might intend it to be a certain way. // Don't mess with internal indentation as the user might intend it to be a certain way.
@ -71,12 +82,22 @@ std::string format_block_comment(const std::string& comment) {
// Remove trailing whitespace // Remove trailing whitespace
comment_contents = str_util::rtrim(comment_contents); comment_contents = str_util::rtrim(comment_contents);
// remove |# // remove |#
// TODO - check suffix if (str_util::ends_with(comment_contents, "|#")) {
comment_contents.pop_back(); comment_contents.pop_back();
comment_contents.pop_back(); comment_contents.pop_back();
}
comment_contents = str_util::rtrim(comment_contents); comment_contents = str_util::rtrim(comment_contents);
new_comment += fmt::format("\n{}\n|#", comment_contents); std::vector<std::string> lines = {new_comment};
return new_comment; const auto contents_as_lines = str_util::split_string(comment_contents, "\n");
if (contents_as_lines.size() > 1) {
for (const auto& line : contents_as_lines) {
lines.push_back(line);
}
lines.push_back("|#");
} else {
lines.at(0) = fmt::format("{} {} |#", new_comment, str_util::trim(contents_as_lines.at(0)));
}
return lines;
} }
} // namespace comments } // namespace comments
@ -101,6 +122,46 @@ bool is_element_second_in_constant_pair(const FormatterTreeNode& containing_node
return true; return true;
} }
// TODO - potentially remove the above
bool is_element_second_in_constant_pair_new(const FormatterTreeNode& prev_node,
const FormatterTreeNode& curr_node) {
if (prev_node.metadata.node_type == "kwd_lit") {
// Handle standard constant types
// TODO - pair up sym_names as well
if (constant_types.find(curr_node.metadata.node_type) != constant_types.end()) {
if (curr_node.metadata.node_type != "kwd_lit") {
// NOTE - there is ambiugity here which cannot be totally solved (i think?)
// if the element itself is also a keyword, assume this is two adjacent keywords and they
// should not be paired
return true;
}
}
// Quoted symbols
if (curr_node.metadata.node_type == "sym_name" && curr_node.node_prefix &&
(curr_node.node_prefix.value() == "'" || curr_node.node_prefix.value() == ",")) {
return true;
}
if (!curr_node.refs.empty()) {
// Constant forms special cases (ie. meters)
if (constant_type_forms.find(curr_node.refs.at(0).token_str()) != constant_type_forms.end()) {
return true;
}
// If they are just a list of symbol names (enum or simple method call)
bool all_symbols = true;
for (const auto& ref : curr_node.refs) {
if (ref.metadata.node_type != "sym_name") {
all_symbols = false;
break;
}
}
if (all_symbols) {
return true;
}
}
}
return false;
}
bool form_should_be_constant_paired(const FormatterTreeNode& node) { bool form_should_be_constant_paired(const FormatterTreeNode& node) {
// Criteria for a list to be constant paired: // Criteria for a list to be constant paired:
// - needs to start with a non-symbol // - needs to start with a non-symbol

View File

@ -36,7 +36,7 @@ bool should_insert_blank_line(const FormatterTreeNode& containing_node,
// //
// Reference - https://github.com/kkinnear/zprint/blob/main/doc/options/comments.md // Reference - https://github.com/kkinnear/zprint/blob/main/doc/options/comments.md
namespace comments { namespace comments {
std::string format_block_comment(const std::string& comment); std::vector<std::string> format_block_comment(const std::string& comment);
} }
// Paired elements in a list will be kept in-line rather than the default new-line indentation // Paired elements in a list will be kept in-line rather than the default new-line indentation
@ -72,6 +72,8 @@ const static int min_pair_amount = 4;
bool is_element_second_in_constant_pair(const FormatterTreeNode& containing_node, bool is_element_second_in_constant_pair(const FormatterTreeNode& containing_node,
const FormatterTreeNode& node, const FormatterTreeNode& node,
const int index); const int index);
bool is_element_second_in_constant_pair_new(const FormatterTreeNode& prev_node,
const FormatterTreeNode& curr_node);
bool form_should_be_constant_paired(const FormatterTreeNode& node); bool form_should_be_constant_paired(const FormatterTreeNode& node);
} // namespace constant_pairs } // namespace constant_pairs

View File

@ -1,24 +1,146 @@
#include "rule_config.h" #include "rule_config.h"
#include "common/util/string_util.h"
namespace formatter_rules { namespace formatter_rules {
namespace config { namespace config {
static FormFormattingConfig new_inlinable_simple_flow_rule() {
return {.config_set = true, .hang_forms = false};
}
static FormFormattingConfig new_permissive_flow_rule() { static FormFormattingConfig new_permissive_flow_rule() {
return {.config_set = true, .hang_forms = false, .combine_first_two_lines = true}; return {.config_set = true, .hang_forms = false, .combine_first_two_lines = true};
} }
static FormFormattingConfig new_flow_rule(int start_index) { static FormFormattingConfig new_flow_rule(int start_index, bool has_constant_pairs = false) {
return {.config_set = true, return {.config_set = true,
.hang_forms = false, .hang_forms = false,
.inline_until_index = [start_index](const std::vector<std::string>& /*curr_lines*/) { .inline_until_index =
return start_index; [start_index](const std::vector<std::string>& /*curr_lines*/) { return start_index; },
}}; .has_constant_pairs = has_constant_pairs};
}
static FormFormattingConfig new_function_rule(int start_index, bool has_constant_pairs = false) {
FormFormattingConfig cfg = {
.config_set = true,
.hang_forms = false,
.inline_until_index =
[start_index](const std::vector<std::string>& /*curr_lines*/) { return start_index; },
.has_constant_pairs = has_constant_pairs};
auto arg_list_config = std::make_shared<FormFormattingConfig>();
arg_list_config->force_inline = true;
arg_list_config->hang_forms = false;
cfg.index_configs.emplace(2, arg_list_config);
return cfg;
}
static FormFormattingConfig new_inlineable_flow_rule(int start_index,
bool has_constant_pairs = false) {
return {.config_set = true,
.hang_forms = false,
.inline_until_index =
[start_index](const std::vector<std::string>& curr_lines) {
int total_width = 0;
for (const auto& line : curr_lines) {
// Check for comments
// TODO - this shows how this isn't really the best strategy but it holds up
if (str_util::contains(line, ";")) {
// Can't inline, there's a comment!
return start_index;
}
total_width += line.length();
// an empty line implies a new-line was forced, this is bleeding implementation
// details, but fine for now
if (line.empty()) {
return start_index;
}
}
if (total_width <= 120) {
return (int)curr_lines.size();
}
return start_index;
},
.has_constant_pairs = has_constant_pairs};
}
static FormFormattingConfig new_defstate_rule(int start_index, bool has_constant_pairs = false) {
FormFormattingConfig cfg = {
.config_set = true,
.hang_forms = false,
.inline_until_index =
[start_index](const std::vector<std::string>& /*curr_lines*/) { return start_index; },
.has_constant_pairs = has_constant_pairs};
std::vector<int> state_handler_indexes = {4, 6, 8, 10,
12, 14}; // NOTE - not all of these have to be defined
for (const auto& index : state_handler_indexes) {
auto temp_config = std::make_shared<FormFormattingConfig>();
temp_config->prevent_inlining = true;
temp_config->parent_mutable_extra_indent = 2;
cfg.index_config_override.emplace(index, temp_config);
}
return cfg;
}
static FormFormattingConfig new_defmethod_rule(int start_index, bool has_constant_pairs = false) {
// TODO - might be nice to have a function that returns a config based on a given index, instead
// of hardcoding them!
// Right now this only works for non-`new` methods (else we may bleed into the body of a normal
// method)
auto arg_list_config = std::make_shared<FormFormattingConfig>();
arg_list_config->force_inline = true;
arg_list_config->hang_forms = false;
FormFormattingConfig cfg = {.config_set = true,
.hang_forms = false,
.inline_until_index =
[start_index](const std::vector<std::string>& curr_lines) {
if (curr_lines.size() >= 2 && curr_lines.at(1) == "new") {
// defmethod was changed to omit the type name for everything
// except the `new` method, so special case.
return start_index + 1;
}
return start_index;
},
.has_constant_pairs = has_constant_pairs};
cfg.index_configs.emplace(2, arg_list_config);
return cfg;
}
static FormFormattingConfig new_lambda_rule(int start_index, bool has_constant_pairs = false) {
FormFormattingConfig cfg = {.config_set = true,
.hang_forms = false,
.inline_until_index =
[start_index](const std::vector<std::string>& curr_lines) {
if (curr_lines.size() >= 2 && curr_lines.at(1) == ":behavior") {
// defmethod was changed to omit the type name for everything
// except the `new` method, so special case.
return start_index + 2;
}
return start_index;
},
.has_constant_pairs = has_constant_pairs};
return cfg;
}
static FormFormattingConfig new_defenum_rule() {
auto temp_list_config = std::make_shared<FormFormattingConfig>();
temp_list_config->force_inline = true;
temp_list_config->hang_forms = false;
return {
.config_set = true,
.hang_forms = false,
.inline_until_index = [](const std::vector<std::string>& /*curr_lines*/) { return 2; },
.has_constant_pairs = true,
.default_index_config = temp_list_config,
};
} }
static FormFormattingConfig new_deftype_rule( static FormFormattingConfig new_deftype_rule(
int start_index, int start_index,
int num_columns_to_compute_widths,
const std::vector<int>& inlining_preventation_indices) { const std::vector<int>& inlining_preventation_indices) {
FormFormattingConfig cfg; FormFormattingConfig cfg;
cfg.has_constant_pairs = true;
cfg.config_set = true; cfg.config_set = true;
cfg.hang_forms = false; cfg.hang_forms = false;
cfg.inline_until_index = [start_index](std::vector<std::string> curr_lines) { cfg.inline_until_index = [start_index](std::vector<std::string> curr_lines) {
@ -39,6 +161,7 @@ static FormFormattingConfig new_deftype_rule(
temp_config->default_index_config = temp_list_config; temp_config->default_index_config = temp_list_config;
if (index == 3) { if (index == 3) {
temp_config->determine_column_widths_for_list_elements = true; temp_config->determine_column_widths_for_list_elements = true;
temp_config->num_columns_to_compute_widths = num_columns_to_compute_widths;
} }
cfg.index_configs.emplace(index, temp_config); cfg.index_configs.emplace(index, temp_config);
} }
@ -74,6 +197,29 @@ static FormFormattingConfig new_binding_rule(int form_head_width) {
return cfg; return cfg;
} }
static FormFormattingConfig new_inline_binding_rule(int form_head_width) {
FormFormattingConfig cfg;
cfg.config_set = true;
cfg.hang_forms = false;
cfg.combine_first_two_lines = true;
auto binding_list_config = std::make_shared<FormFormattingConfig>();
binding_list_config->config_set = true;
binding_list_config->hang_forms = false;
binding_list_config->indentation_width = 1;
binding_list_config->indentation_width_for_index = [form_head_width](FormFormattingConfig /*cfg*/,
int index) {
if (index == 0) {
return 0;
}
return form_head_width;
};
binding_list_config->should_prevent_inlining = [](FormFormattingConfig /*config*/, int num_refs) {
return false;
};
cfg.index_configs.emplace(1, binding_list_config);
return cfg;
}
static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) { static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) {
FormFormattingConfig cfg; FormFormattingConfig cfg;
cfg.config_set = true; cfg.config_set = true;
@ -88,23 +234,71 @@ static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) {
return cfg; return cfg;
} }
static FormFormattingConfig new_top_level_inline_form(bool elide_new_line) {
return {.force_inline = true, .elide_top_level_newline = elide_new_line};
}
const std::unordered_map<std::string, FormFormattingConfig> opengoal_form_config = { const std::unordered_map<std::string, FormFormattingConfig> opengoal_form_config = {
{"case", new_pair_rule(true)}, {"case", new_pair_rule(true)},
{"case-str", new_pair_rule(true)},
{"cond", new_pair_rule(false)}, {"cond", new_pair_rule(false)},
{"defmethod", new_flow_rule(3)}, {"#cond", new_pair_rule(false)},
{"deftype", new_deftype_rule(3, {3, 4, 5, 6})}, {"in-package", new_top_level_inline_form(true)},
{"bundles", new_top_level_inline_form(true)},
{"require", new_top_level_inline_form(true)},
{"def-art-elt", new_top_level_inline_form(true)},
{"def-joint-node", new_top_level_inline_form(true)},
{"declare-file", new_top_level_inline_form(false)},
{"defenum", new_defenum_rule()},
{"defmethod", new_defmethod_rule(3)},
{"lambda", new_lambda_rule(2)},
{"deftype", new_deftype_rule(3, 1, {3, 4, 5, 6})},
{"defun", new_flow_rule(3)}, {"defun", new_flow_rule(3)},
{"defun-recursive", new_flow_rule(4)},
{"defun-debug-recursive", new_flow_rule(4)},
{"defun-debug", new_flow_rule(3)}, {"defun-debug", new_flow_rule(3)},
{"defbehavior", new_flow_rule(4)}, {"defbehavior", new_flow_rule(4)},
{"if", new_permissive_flow_rule()}, {"if", new_inlineable_flow_rule(2)},
{"#if", new_inlineable_flow_rule(2)},
{"define", new_permissive_flow_rule()}, {"define", new_permissive_flow_rule()},
{"def-mips2c", new_permissive_flow_rule()},
{"defconstant", new_permissive_flow_rule()},
{"defglobalconstant", new_permissive_flow_rule()},
{"defmethod-mips2c", new_permissive_flow_rule()},
{"define-extern", new_permissive_flow_rule()}, {"define-extern", new_permissive_flow_rule()},
{"defmacro", new_flow_rule(3)}, {"declare-type", new_permissive_flow_rule()},
{"defmacro", new_function_rule(3)},
{"desfun", new_function_rule(3)},
{"defskelgroup", new_flow_rule(2, true)},
{"defpartgroup", new_flow_rule(2, true)},
{"defpart", new_flow_rule(2, true)},
{"defstate", new_defstate_rule(3, true)},
{"behavior", new_flow_rule(2)},
{"dotimes", new_flow_rule(2)}, {"dotimes", new_flow_rule(2)},
{"dolist", new_flow_rule(2)},
{"process-spawn-function", new_flow_rule(2)},
{"let", new_binding_rule(4)}, {"let", new_binding_rule(4)},
{"protect", new_binding_rule(4)},
{"let*", new_binding_rule(5)},
{"rlet", new_binding_rule(5)}, {"rlet", new_binding_rule(5)},
{"when", new_flow_rule(2)}, {"when", new_flow_rule(2)},
{"unless", new_flow_rule(2)},
{"with-profiler", new_flow_rule(2)},
{"with-pc", new_flow_rule(0)},
{"#unless", new_flow_rule(2)},
{"#when", new_flow_rule(2)},
{"countdown", new_flow_rule(2)},
{"until", new_flow_rule(2)},
{"loop", new_flow_rule(0)},
{"while", new_flow_rule(2)},
{"begin", new_flow_rule(0)}, {"begin", new_flow_rule(0)},
{"seval", new_flow_rule(0)},
{"with-pp", new_flow_rule(0)},
{"with-gensyms", new_flow_rule(2)},
{"with-vf0", new_flow_rule(0)},
{"with-vf", new_inline_binding_rule(8)},
{"with-cnt-vif-block", new_inline_binding_rule(19)},
{"local-vars", new_inlinable_simple_flow_rule()},
{"with-dma-buffer-add-bucket", new_flow_rule(2)}}; {"with-dma-buffer-add-bucket", new_flow_rule(2)}};
} // namespace config } // namespace config
} // namespace formatter_rules } // namespace formatter_rules

View File

@ -7,7 +7,6 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
// TODO - some way to apply a config to all list elements (index configs with -1?)
namespace formatter_rules { namespace formatter_rules {
namespace config { namespace config {
struct FormFormattingConfig { struct FormFormattingConfig {
@ -33,9 +32,14 @@ struct FormFormattingConfig {
int parent_mutable_extra_indent = 0; int parent_mutable_extra_indent = 0;
std::optional<std::shared_ptr<FormFormattingConfig>> default_index_config; std::optional<std::shared_ptr<FormFormattingConfig>> default_index_config;
std::unordered_map<int, std::shared_ptr<FormFormattingConfig>> index_configs = {}; std::unordered_map<int, std::shared_ptr<FormFormattingConfig>> index_configs = {};
// TODO / NOTe - not fully implemented, only doing `parent_mutable_extra_indent` right now
std::unordered_map<int, std::shared_ptr<FormFormattingConfig>> index_config_override = {};
bool determine_column_widths_for_list_elements = false; bool determine_column_widths_for_list_elements = false;
int num_columns_to_compute_widths = 0;
std::vector<int> list_element_column_widths = {}; std::vector<int> list_element_column_widths = {};
bool elide_top_level_newline = false;
}; };
extern const std::unordered_map<std::string, FormFormattingConfig> opengoal_form_config; extern const std::unordered_map<std::string, FormFormattingConfig> opengoal_form_config;

View File

@ -26,6 +26,8 @@ u64 get_current_tid() {
} }
#endif #endif
#include "common/log/log.h" #include "common/log/log.h"
#include "common/util/compress.h"
#include "common/util/string_util.h"
// clang-format on // clang-format on
u64 get_current_ts() { u64 get_current_ts() {
@ -34,12 +36,12 @@ u64 get_current_ts() {
GlobalProfiler::GlobalProfiler() { GlobalProfiler::GlobalProfiler() {
m_t0 = get_current_ts(); m_t0 = get_current_ts();
set_max_events(65536); m_nodes.resize(m_max_events);
} }
void GlobalProfiler::set_max_events(size_t event_count) { void GlobalProfiler::update_event_buffer_size(size_t new_size) {
ASSERT(!m_enabled); m_max_events = new_size;
m_nodes.resize(event_count); m_nodes.resize(m_max_events);
} }
void GlobalProfiler::set_waiting_for_event(const std::string& event_name) { void GlobalProfiler::set_waiting_for_event(const std::string& event_name) {
@ -72,6 +74,10 @@ void GlobalProfiler::root_event() {
instant_event("ROOT"); instant_event("ROOT");
} }
size_t GlobalProfiler::get_next_idx() {
return (m_next_idx % m_nodes.size());
}
void GlobalProfiler::begin_event(const char* name) { void GlobalProfiler::begin_event(const char* name) {
event(name, ProfNode::BEGIN); event(name, ProfNode::BEGIN);
} }
@ -96,8 +102,10 @@ void GlobalProfiler::set_enable(bool en) {
m_enabled = en; m_enabled = en;
} }
void GlobalProfiler::dump_to_json(const std::string& path) { void GlobalProfiler::dump_to_json() {
ASSERT(!m_enabled); if (m_enabled) {
set_enable(false);
}
nlohmann::json json; nlohmann::json json;
auto& trace_events = json["traceEvents"]; auto& trace_events = json["traceEvents"];
@ -185,7 +193,20 @@ void GlobalProfiler::dump_to_json(const std::string& path) {
t.second.highest_at_target); t.second.highest_at_target);
} }
file_util::write_text_file(path, json.dump()); if (m_enable_compression) {
const auto json_str = json.dump();
const auto compressed_data =
compression::compress_zstd_no_header(json_str.data(), json_str.size());
auto file_path = file_util::get_jak_project_dir() / "profile_data" /
fmt::format("prof-{}.json.zst", str_util::current_local_timestamp_no_colons());
file_util::create_dir_if_needed_for_file(file_path);
file_util::write_binary_file(file_path, compressed_data.data(), compressed_data.size());
} else {
auto file_path = file_util::get_jak_project_dir() / "profile_data" /
fmt::format("prof-{}.json", str_util::current_local_timestamp_no_colons());
file_util::create_dir_if_needed_for_file(file_path);
file_util::write_text_file(file_path, json.dump());
}
} }
GlobalProfiler gprof; GlobalProfiler gprof;

View File

@ -17,7 +17,8 @@ struct ProfNode {
class GlobalProfiler { class GlobalProfiler {
public: public:
GlobalProfiler(); GlobalProfiler();
void set_max_events(size_t event_count); size_t get_max_events() { return m_max_events; }
void update_event_buffer_size(size_t new_size);
void set_waiting_for_event(const std::string& event_name); void set_waiting_for_event(const std::string& event_name);
void instant_event(const char* name); void instant_event(const char* name);
void begin_event(const char* name); void begin_event(const char* name);
@ -25,11 +26,16 @@ class GlobalProfiler {
void end_event(); void end_event();
void clear(); void clear();
void set_enable(bool en); void set_enable(bool en);
void dump_to_json(const std::string& path); void dump_to_json();
void root_event(); void root_event();
bool is_enabled() { return m_enabled; }
size_t get_next_idx();
bool m_enable_compression = false;
private: private:
std::atomic_bool m_enabled = false; std::atomic_bool m_enabled = false;
size_t m_max_events = 65536;
u64 m_t0 = 0; u64 m_t0 = 0;
std::atomic_size_t m_next_idx = 0; std::atomic_size_t m_next_idx = 0;
std::vector<ProfNode> m_nodes; std::vector<ProfNode> m_nodes;

View File

@ -12,7 +12,6 @@
#include "Reader.h" #include "Reader.h"
#include "common/log/log.h" #include "common/log/log.h"
#include "common/repl/util.h"
#include "common/util/FileUtil.h" #include "common/util/FileUtil.h"
#include "common/util/FontUtils.h" #include "common/util/FontUtils.h"

View File

@ -18,7 +18,7 @@
#include "common/goos/Object.h" #include "common/goos/Object.h"
#include "common/goos/TextDB.h" #include "common/goos/TextDB.h"
#include "common/repl/util.h" #include "common/repl/repl_wrapper.h"
#include "common/util/Assert.h" #include "common/util/Assert.h"
namespace goos { namespace goos {

View File

@ -140,7 +140,7 @@ void printstd(const char* format, va_list arg_list) {
internal::log_vprintf(format, arg_list); internal::log_vprintf(format, arg_list);
} }
// how many extra log files for a single program should be kept? // how many extra log files for a single program should be kept
constexpr int LOG_ROTATE_MAX = 10; constexpr int LOG_ROTATE_MAX = 10;
void set_file(const std::string& filename, void set_file(const std::string& filename,
@ -163,7 +163,7 @@ void set_file(const std::string& filename,
file_util::find_files_in_dir(fs::path(complete_filename).parent_path(), file_util::find_files_in_dir(fs::path(complete_filename).parent_path(),
std::regex(fmt::format("{}\\.(\\d\\.)?log", filename))); std::regex(fmt::format("{}\\.(\\d\\.)?log", filename)));
for (const auto& file : old_log_files) { for (const auto& file : old_log_files) {
lg::info("removing {}", file.string()); lg::debug("removing {}", file.string());
fs::remove(file); fs::remove(file);
} }
// remove the oldest log file if there are more than LOG_ROTATE_MAX // remove the oldest log file if there are more than LOG_ROTATE_MAX
@ -172,9 +172,9 @@ void set_file(const std::string& filename,
// sort the names and remove them // sort the names and remove them
existing_log_files = file_util::sort_filepaths(existing_log_files, true); existing_log_files = file_util::sort_filepaths(existing_log_files, true);
if (existing_log_files.size() > (LOG_ROTATE_MAX - 1)) { if (existing_log_files.size() > (LOG_ROTATE_MAX - 1)) {
lg::info("removing {} log files", existing_log_files.size() - (LOG_ROTATE_MAX - 1)); lg::debug("removing {} log files", existing_log_files.size() - (LOG_ROTATE_MAX - 1));
for (int i = 0; i < (int)existing_log_files.size() - (LOG_ROTATE_MAX - 1); i++) { for (int i = 0; i < (int)existing_log_files.size() - (LOG_ROTATE_MAX - 1); i++) {
lg::info("removing {}", existing_log_files.at(i).string()); lg::debug("removing {}", existing_log_files.at(i).string());
fs::remove(existing_log_files.at(i)); fs::remove(existing_log_files.at(i));
} }
} }

View File

@ -7,15 +7,21 @@
namespace REPL { namespace REPL {
void to_json(json& j, const Config& obj) { void to_json(json& j, const Config& obj) {
j = json{ j = json{
{"nreplPort", obj.nrepl_port},
{"gameVersionFolder", obj.game_version_folder}, {"gameVersionFolder", obj.game_version_folder},
{"numConnectToTargetAttempts", obj.target_connect_attempts}, {"numConnectToTargetAttempts", obj.target_connect_attempts},
{"asmFileSearchDirs", obj.asm_file_search_dirs}, {"asmFileSearchDirs", obj.asm_file_search_dirs},
{"keybinds", obj.keybinds}, {"keybinds", obj.keybinds},
{"perGameHistory", obj.per_game_history}, {"perGameHistory", obj.per_game_history},
{"permissiveRedefinitions", obj.permissive_redefinitions},
}; };
} }
void from_json(const json& j, Config& obj) { void from_json(const json& j, Config& obj) {
// TODO - make a camelCase variant of json_serialize/deserialize macros
if (j.contains("nreplPort")) {
j.at("nreplPort").get_to(obj.nrepl_port);
}
if (j.contains("gameVersionFolder")) { if (j.contains("gameVersionFolder")) {
j.at("gameVersionFolder").get_to(obj.game_version_folder); j.at("gameVersionFolder").get_to(obj.game_version_folder);
} }
@ -55,6 +61,9 @@ void from_json(const json& j, Config& obj) {
if (j.contains("perGameHistory")) { if (j.contains("perGameHistory")) {
j.at("perGameHistory").get_to(obj.per_game_history); j.at("perGameHistory").get_to(obj.per_game_history);
} }
if (j.contains("permissiveRedefinitions")) {
j.at("permissiveRedefinitions").get_to(obj.permissive_redefinitions);
}
// if there is game specific configuration, override any values we just set // if there is game specific configuration, override any values we just set
if (j.contains(version_to_game_name(obj.game_version))) { if (j.contains(version_to_game_name(obj.game_version))) {
from_json(j.at(version_to_game_name(obj.game_version)), obj); from_json(j.at(version_to_game_name(obj.game_version)), obj);

View File

@ -26,11 +26,14 @@ struct KeyBind {
void to_json(json& j, const KeyBind& obj); void to_json(json& j, const KeyBind& obj);
void from_json(const json& j, KeyBind& obj); void from_json(const json& j, KeyBind& obj);
// TODO - per-game config
struct Config { struct Config {
GameVersion game_version; GameVersion game_version;
Config(GameVersion _game_version) : game_version(_game_version){}; Config(GameVersion _game_version) : game_version(_game_version){};
// this is the default REPL configuration // this is the default REPL configuration
int nrepl_port = 8181;
int temp_nrepl_port = -1;
std::string game_version_folder; std::string game_version_folder;
int target_connect_attempts = 30; int target_connect_attempts = 30;
std::vector<std::string> asm_file_search_dirs = {}; std::vector<std::string> asm_file_search_dirs = {};
@ -45,6 +48,14 @@ struct Config {
{KeyBind::Modifier::CTRL, "B", "Displays the most recently caught backtrace", "(:di)"}, {KeyBind::Modifier::CTRL, "B", "Displays the most recently caught backtrace", "(:di)"},
{KeyBind::Modifier::CTRL, "N", "Full build of the game", "(mi)"}}; {KeyBind::Modifier::CTRL, "N", "Full build of the game", "(mi)"}};
bool per_game_history = true; bool per_game_history = true;
bool permissive_redefinitions = false;
int get_nrepl_port() {
if (temp_nrepl_port != -1) {
return temp_nrepl_port;
}
return nrepl_port;
}
}; };
void to_json(json& j, const Config& obj); void to_json(json& j, const Config& obj);
void from_json(const json& j, Config& obj); void from_json(const json& j, Config& obj);

View File

@ -27,7 +27,18 @@ ReplServer::~ReplServer() {
void ReplServer::post_init() { void ReplServer::post_init() {
// Add the listening socket to our set of sockets // Add the listening socket to our set of sockets
lg::info("[nREPL:{}:{}] awaiting connections", tcp_port, listening_socket); lg::debug("[nREPL:{}:{}] awaiting connections", tcp_port, listening_socket);
}
void ReplServer::error_response(int socket, const std::string& error) {
std::string msg = fmt::format("[ERROR]: {}", error);
auto resp = write_to_socket(socket, msg.c_str(), msg.size());
if (resp == -1) {
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), socket);
close_socket(socket);
client_sockets.erase(socket);
}
} }
void ReplServer::ping_response(int socket) { void ReplServer::ping_response(int socket) {
@ -48,7 +59,6 @@ std::optional<std::string> ReplServer::get_msg() {
// Add the server's main listening socket (where we accept clients from) // Add the server's main listening socket (where we accept clients from)
FD_SET(listening_socket, &read_sockets); FD_SET(listening_socket, &read_sockets);
int max_sd = listening_socket; int max_sd = listening_socket;
for (const int& sock : client_sockets) { for (const int& sock : client_sockets) {
if (sock > max_sd) { if (sock > max_sd) {
@ -60,12 +70,11 @@ std::optional<std::string> ReplServer::get_msg() {
} }
// Wait for activity on _something_, with a timeout so we don't get stuck here on exit. // Wait for activity on _something_, with a timeout so we don't get stuck here on exit.
struct timeval timeout; struct timeval timeout = {0, 100000};
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
auto activity = select(max_sd + 1, &read_sockets, NULL, NULL, &timeout); auto activity = select(max_sd + 1, &read_sockets, NULL, NULL, &timeout);
if (activity < 0 && errno != EINTR) {
if (activity < 0) { // TODO - || error! lg::error("[nREPL:{}] select error, returned: {}, errno: {}", tcp_port, activity,
strerror(errno));
return std::nullopt; return std::nullopt;
} }
@ -74,44 +83,46 @@ std::optional<std::string> ReplServer::get_msg() {
socklen_t addr_len = sizeof(addr); socklen_t addr_len = sizeof(addr);
auto new_socket = accept_socket(listening_socket, (sockaddr*)&addr, &addr_len); auto new_socket = accept_socket(listening_socket, (sockaddr*)&addr, &addr_len);
if (new_socket < 0) { if (new_socket < 0) {
// TODO - handle error if (new_socket != -1) {
lg::error("[nREPL:{}] accept error, returned: {}, errono: {}", tcp_port, new_socket,
strerror(errno));
}
} else { } else {
lg::info("[nREPL:{}]: New socket connection: {}:{}:{}", tcp_port, address_to_string(addr), lg::info("[nREPL:{}]: New socket connection: {}:{}:{}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), new_socket); ntohs(addr.sin_port), new_socket);
// Say hello // Say hello
ping_response(new_socket); ping_response(new_socket);
// Track the new socket // Track the new socket
if ((int)client_sockets.size() < max_clients) { if ((int)client_sockets.size() < max_clients) {
client_sockets.insert(new_socket); client_sockets.insert(new_socket);
} else { } else {
// TODO - Respond with NO // Respond with NO and close the socket
lg::warn("[nREPL:{}]: Maximum clients reached. Rejecting connection.", tcp_port);
error_response(new_socket, "Maximum clients reached. Rejecting connection.");
close_socket(new_socket);
} }
} }
} }
// otherwise (and no matter what) check all the clients to see if they have sent us anything // Check all clients for activity
// else its some IO operation on some other socket for (auto it = client_sockets.begin(); it != client_sockets.end();) {
// int sock = *it;
// RACE - the first client wins
// TODO - there are ways to do this with iterators but, couldn't figure it out!
std::vector<int> sockets_to_scan(client_sockets.begin(), client_sockets.end());
for (const int& sock : sockets_to_scan) {
if (FD_ISSET(sock, &read_sockets)) { if (FD_ISSET(sock, &read_sockets)) {
// Attempt to read a header // Attempt to read a header
// TODO - should this be in a loop?
auto req_bytes = read_from_socket(sock, header_buffer.data(), header_buffer.size()); auto req_bytes = read_from_socket(sock, header_buffer.data(), header_buffer.size());
if (req_bytes == 0) { if (req_bytes <= 0) {
// Socket disconnected
// TODO - add a queue of messages in the REPL::Wrapper so we can print _BEFORE_ the prompt // TODO - add a queue of messages in the REPL::Wrapper so we can print _BEFORE_ the prompt
// is output // is output
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr), if (req_bytes == 0) {
ntohs(addr.sin_port), sock); lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr));
} else {
lg::warn("[nREPL:{}] Error reading from socket on {}: {}", tcp_port,
address_to_string(addr), strerror(errno));
}
// Cleanup the socket and remove it from our set // Cleanup the socket and remove it from our set
close_socket(sock); close_socket(sock);
client_sockets.erase(sock); it = client_sockets.erase(it); // Erase and move to the next element
continue;
} else { } else {
// Otherwise, process the message // Otherwise, process the message
auto* header = (ReplServerHeader*)(header_buffer.data()); auto* header = (ReplServerHeader*)(header_buffer.data());
@ -119,7 +130,12 @@ std::optional<std::string> ReplServer::get_msg() {
int expected_size = header->length; int expected_size = header->length;
int got = 0; int got = 0;
int tries = 0; int tries = 0;
bool skip_to_next_socket = false;
while (got < expected_size) { while (got < expected_size) {
if (want_exit_callback()) {
lg::warn("[nREPL:{}] Terminating nREPL early", tcp_port);
return std::nullopt;
}
tries++; tries++;
if (tries > 100) { if (tries > 100) {
break; break;
@ -131,11 +147,25 @@ std::optional<std::string> ReplServer::get_msg() {
tcp_port, got, expected_size, buffer.size()); tcp_port, got, expected_size, buffer.size());
return std::nullopt; return std::nullopt;
} }
auto x = read_from_socket(sock, buffer.data() + got, expected_size - got); auto bytes_read = read_from_socket(sock, buffer.data() + got, expected_size - got);
if (want_exit_callback()) { if (bytes_read <= 0) {
return std::nullopt; if (bytes_read == 0) {
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr));
} else {
lg::warn("[nREPL:{}] Error reading from socket on {}: {}", tcp_port,
address_to_string(addr), strerror(errno));
}
close_socket(sock);
it = client_sockets.erase(it); // Erase and move to the next element
skip_to_next_socket = true;
break;
} }
got += x > 0 ? x : 0; got += bytes_read;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
if (skip_to_next_socket) {
continue;
} }
switch (header->type) { switch (header->type) {
@ -149,6 +179,7 @@ std::optional<std::string> ReplServer::get_msg() {
} }
} }
} }
++it;
} }
return std::nullopt; return std::nullopt;
} }

View File

@ -27,5 +27,6 @@ class ReplServer : public XSocketServer {
fd_set read_sockets; fd_set read_sockets;
std::set<int> client_sockets = {}; std::set<int> client_sockets = {};
void error_response(int socket, const std::string& error);
void ping_response(int socket); void ping_response(int socket);
}; };

View File

@ -1,4 +1,4 @@
#include "util.h" #include "repl_wrapper.h"
#include "common/util/FileUtil.h" #include "common/util/FileUtil.h"
#include "common/util/json_util.h" #include "common/util/json_util.h"
@ -8,36 +8,66 @@
#include "fmt/color.h" #include "fmt/color.h"
#include "fmt/core.h" #include "fmt/core.h"
#include "third-party/replxx/include/replxx.hxx" #include "third-party/replxx/include/replxx.hxx"
// TODO - expand a list of hints (ie. a hint for defun to show at a glance how to write a function,
// or perhaps, show the docstring for the current function being used?)
namespace REPL { namespace REPL {
void Wrapper::clear_screen() { void Wrapper::clear_screen() {
repl.clear_screen(); repl.clear_screen();
} }
void Wrapper::print_welcome_message() { void Wrapper::print_welcome_message(const std::vector<std::string>& loaded_projects) {
// TODO - dont print on std-out std::string message;
// Welcome message / brief intro for documentation message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " ..:::::..\n");
std::string ascii; message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .:-----------:.\n");
ascii += " _____ _____ _____ _____ __ \n"; message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .-----.");
ascii += "| |___ ___ ___| __| | _ | | \n"; message += fmt::format(fmt::emphasis::bold, " Welcome to OpenGOAL {}.{} [{}]",
ascii += "| | | . | -_| | | | | | | |__ \n"; versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR,
ascii += "|_____| _|___|_|_|_____|_____|__|__|_____|\n"; fmt::format(fg(fmt::color::gray), "{}", build_revision()));
ascii += " |_| \n"; if (!username.empty() && username != "#f" && username != "unknown") {
fmt::print(fmt::emphasis::bold | fg(fmt::color::orange), ascii); message += fmt::format(fg(fmt::color::light_green), " {}", username);
}
fmt::print("Welcome to OpenGOAL {}.{}!\n", versions::GOAL_VERSION_MAJOR, message += "!\n";
versions::GOAL_VERSION_MINOR); message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .---.");
fmt::print("Run {} or {} for help with common commands and REPL usage.\n", if (repl_config.game_version == GameVersion::Jak1) {
fmt::styled("(repl-help)", fmt::emphasis::bold | fg(fmt::color::cyan)), message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::orange), "jak1"));
fmt::styled("(repl-keybinds)", fmt::emphasis::bold | fg(fmt::color::cyan))); } else if (repl_config.game_version == GameVersion::Jak2) {
fmt::print("Run "); message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::purple), "jak2"));
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)"); } else if (repl_config.game_version == GameVersion::Jak3) {
fmt::print(" to connect to the local target.\n"); message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::gold), "jak3"));
fmt::print("Run "); } else {
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(mi)"); message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::magenta), "jakx"));
fmt::print(" to rebuild the entire game.\n\n"); }
const auto loaded_projects_str = fmt::format("{}", fmt::join(loaded_projects, ","));
message += fmt::format(fg(fmt::color::gray), "{}\n", loaded_projects_str);
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " . --- .");
message +=
fmt::format(" Project Path: {}\n",
fmt::format(fg(fmt::color::gray), file_util::get_jak_project_dir().string()));
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " - :===: -");
message += " nREPL:";
if (!nrepl_alive) {
message += fmt::format(fg(fmt::color::red), "DISABLED\n");
} else {
message += fmt::format(fg(fmt::color::light_green), " Listening on {}\n",
repl_config.get_nrepl_port());
}
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " --. .--: :--. .--");
message += " Source File Search Dirs: ";
const auto search_dir_string =
fmt::format("{}", fmt::join(repl_config.asm_file_search_dirs, ","));
message += fmt::format("[{}]\n", fmt::format(fg(fmt::color::gray), search_dir_string));
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .=======. =======.");
message += fmt::format(" {} or {} for basic help and usage\n",
fmt::format(fg(fmt::color::cyan), "(repl-help)"),
fmt::format(fg(fmt::color::cyan), "(repl-keybinds)"));
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .-=====-. .-=====-");
message +=
fmt::format(" {} to connect to the game\n", fmt::format(fg(fmt::color::cyan), "(lt)"));
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .-===========-.");
message += fmt::format(" {} to recompile the active project.\n",
fmt::format(fg(fmt::color::cyan), "(mi)"));
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .-===-.\n");
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .\n");
fmt::print("{}", message);
} }
void Wrapper::print_to_repl(const std::string& str) { void Wrapper::print_to_repl(const std::string& str) {
@ -242,20 +272,24 @@ StartupFile load_user_startup_file(const std::string& username, const GameVersio
return startup_file; return startup_file;
} }
REPL::Config load_repl_config(const std::string& username, const GameVersion game_version) { REPL::Config load_repl_config(const std::string& username,
const GameVersion game_version,
const int nrepl_port) {
auto repl_config_path = auto repl_config_path =
file_util::get_jak_project_dir() / "goal_src" / "user" / username / "repl-config.json"; file_util::get_jak_project_dir() / "goal_src" / "user" / username / "repl-config.json";
REPL::Config loaded_config(game_version);
if (file_util::file_exists(repl_config_path.string())) { if (file_util::file_exists(repl_config_path.string())) {
try { try {
REPL::Config config(game_version);
auto repl_config_data = auto repl_config_data =
parse_commented_json(file_util::read_text_file(repl_config_path), "repl-config.json"); parse_commented_json(file_util::read_text_file(repl_config_path), "repl-config.json");
from_json(repl_config_data, config); from_json(repl_config_data, loaded_config);
return config; loaded_config.temp_nrepl_port = nrepl_port;
return loaded_config;
} catch (std::exception& e) { } catch (std::exception& e) {
REPL::Config config(game_version); // do nothing
} }
} }
return REPL::Config(game_version); loaded_config.temp_nrepl_port = nrepl_port;
return loaded_config;
} }
} // namespace REPL } // namespace REPL

View File

@ -17,18 +17,23 @@ struct StartupFile {
}; };
class Wrapper { class Wrapper {
replxx::Replxx repl;
public: public:
std::string username; std::string username;
Config repl_config; Config repl_config;
StartupFile startup_file; StartupFile startup_file;
bool nrepl_alive = false;
std::vector<std::string> examples{}; std::vector<std::string> examples{};
std::vector<std::pair<std::string, replxx::Replxx::Color>> regex_colors{}; std::vector<std::pair<std::string, replxx::Replxx::Color>> regex_colors{};
Wrapper(GameVersion version) : repl_config(version) {} Wrapper(GameVersion version) : repl_config(version) {}
Wrapper(const std::string& _username, const Config& config, const StartupFile& startup) Wrapper(const std::string& _username,
: username(_username), repl_config(config), startup_file(startup) {} const Config& config,
const StartupFile& startup,
bool _nrepl_alive)
: username(_username),
repl_config(config),
startup_file(startup),
nrepl_alive(_nrepl_alive) {}
replxx::Replxx& get_repl() { return repl; } replxx::Replxx& get_repl() { return repl; }
void init_settings(); void init_settings();
void reload_startup_file(); void reload_startup_file();
@ -36,7 +41,7 @@ class Wrapper {
// Functionality / Commands // Functionality / Commands
void clear_screen(); void clear_screen();
void print_to_repl(const std::string& str); void print_to_repl(const std::string& str);
void print_welcome_message(); void print_welcome_message(const std::vector<std::string>& loaded_projects);
void set_history_max_size(size_t len); void set_history_max_size(size_t len);
const char* readline(const std::string& prompt); const char* readline(const std::string& prompt);
void add_to_history(const std::string& line); void add_to_history(const std::string& line);
@ -47,11 +52,14 @@ class Wrapper {
std::pair<std::string, bool> get_current_repl_token(std::string const& context); std::pair<std::string, bool> get_current_repl_token(std::string const& context);
private: private:
replxx::Replxx repl;
replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit); replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit);
std::vector<REPL::KeyBind> keybindings = {}; std::vector<REPL::KeyBind> keybindings = {};
}; };
std::string find_repl_username(); std::string find_repl_username();
StartupFile load_user_startup_file(const std::string& username, const GameVersion game_version); StartupFile load_user_startup_file(const std::string& username, const GameVersion game_version);
REPL::Config load_repl_config(const std::string& username, const GameVersion game_version); REPL::Config load_repl_config(const std::string& username,
const GameVersion game_version,
const int nrepl_port);
} // namespace REPL } // namespace REPL

View File

@ -89,7 +89,9 @@ const std::unordered_map<std::string, u16> jak2_speaker_name_to_enum_val = {
{"citizen-male", 31}, {"citizen-male", 31},
{"citizen-female", 32}, {"citizen-female", 32},
{"oracle", 33}, {"oracle", 33},
{"precursor", 34}}; {"precursor", 34},
{"metalkor-before-consite", 35},
{"metalkor-intro", 36}};
GameSubtitlePackage read_json_files_v2(const GameSubtitleDefinitionFile& file_info) { GameSubtitlePackage read_json_files_v2(const GameSubtitleDefinitionFile& file_info) {
GameSubtitlePackage package; GameSubtitlePackage package;

View File

@ -43,7 +43,151 @@ std::vector<std::string> jak2_slots = {
}; };
std::vector<std::string> jak3_slots = { std::vector<std::string> jak3_slots = {
"skull-gem-dest", "jakc-arm", "jakc-eyebrow", "jakc-face", "jakc-finger", "jakc-hair", "skull-gem-dest",
"jakc-arm",
"jakc-eyebrow",
"jakc-face",
"jakc-finger",
"jakc-hair",
"jakchires-arm",
"jakchires-eye",
"jakchires-eyebrow",
"jakchires-eyelid",
"jakchires-facelft",
"jakchires-facert",
"jakchires-hair",
// default-water
"bomb-gradient",
"blue-beam-dest",
"lightjak-wings",
"mushroom-dest",
// default-warp
"shield-env-rim-dest",
// templea-water
"templea-waterfall-dest",
// templea-warp
"holograph-env-rim-dest",
// hanga-sprite
"glider-ring-dest2",
"glider-ring-dest",
// foresta-water
"fora-water-dest",
"fora-waterfall-01-dest",
"fora-water-wave-01-dest",
// forestb-water
"forb-water-dest",
"forb-waterfall-01-dest",
"forb-water-wave-01-dest",
// lforplnt-pris
"mh-gem-dest",
// lmhcitya-tfrag
"mhcitya-base-goo-01-dest",
// lmhcityb-tfrag
"mhcityb-base-goo-01-dest",
// mhcitya-pris
// "mhcity-de-door-skin-01-dest",
// templec-water
"tplc-water-dest",
// sewc-water
"sewer-water-01-c-dest",
"sewer-waterfall-01-c-dest",
"sewer-waterfall-02-c-dest",
"sewer-water-wave-01-c-dest",
"sewer-water-highlight-01-c-dest",
// sewd-water
"sewer-water-01-d-dest",
"sewer-waterfall-02-d-dest",
"sewer-water-highlight-01-d-dest",
"sewer-water-wave-01-d-dest",
"sewer-water-wave-02-d-dest",
"sewer-water-still-01-d-dest",
// sewe-water
"sewer-water-01-e-dest",
"sewer-waterfall-01-e-dest",
"sewer-waterfall-02-e-dest",
"sewer-water-highlight-01-e-dest",
// sewg-water
"sewer-water-01-g-dest",
"sewer-waterfall-02-g-dest",
"sewer-water-wave-01-g-dest",
// sewh-water
"sewer-water-01-h-dest",
"sewer-waterfall-02-h-dest",
"sewer-water-wave-02-h-dest",
"sewer-watefall-froth-01-h-dest",
// sewi-water
"sewer-water-still-01-i-dest",
"sewer-waterfall-02-i-dest",
"sewer-water-wave-01-i-dest",
// sewj-water
"sewer-waterfall-02-j-dest",
"sewer-watefall-froth-01-j-dest",
// sewl-water
"sewer-waterfall-02-l-dest",
"sewer-watefall-froth-01-l-dest",
// sewm-water
"sewer-water-01-m-dest",
"sewer-waterfall-01-m-dest",
"sewer-waterfall-02-m-dest",
"sewer-water-highlight-01-m-dest",
"sewer-water-wave-01-m-dest",
"sewer-water-still-01-m-dest",
"sewer-watefall-froth-01-m-dest",
// sewn-water
"sewer-waterfall-01-n-dest",
"sewer-waterfall-02-n-dest",
// "sewer-water-highlight-01-n-dest",
"sewer-water-wave-01-n-dest",
"sewer-water-still-01-n-dest",
// hanga-water
// "des-thermal-01-dest",
// desresc-warp
"sat-shield-dest",
// security
"security-env-dest",
"security-dot-dest",
// lgunnorm-water
"kg-target-c-forcefield-01-dest",
// templex-water
"temple-waterfall-dest",
// desertd-water
"des-waterfall-dest",
// towerb-water
"tow-energy-bridge-dest",
// factoryb-water
"hemi-gradient-dest",
"hemi-gradient-flames-dest",
// factoryc-alpha
"facc-convey-dest",
"facc-convey-02-dest",
// waspal-water
"waspala-water-dest",
"waspala-waterfall-dest",
// rubblea-water
"rub-water-dest",
// rubblea2-water
"rub-water-desta2",
// rubbleb-water
"rub-water-destb",
// rubblec-water
"rub-water-destc",
// nstb-quicksand
"nstb-quicksand-dest",
// ctyslumb-water
"ctyslumb-water-dest",
"ctyslumb-fountain-fall-dest",
// ctyslumc-water
"ctyslumc-water-dest",
"ctyslumc-fountain-fall-dest",
// mined-tfrag
"mined-pillar-side-dest",
"mined-pillar-top2side-dest",
"mined-pillar-top-dest",
// volcanoa-alpha
"vola-lava-01-dest",
"vola-lava-fall-dest",
// wasstada-alpha
"wstd-lava-base-dest",
}; };
} // namespace } // namespace

View File

@ -185,9 +185,11 @@ std::optional<std::string> try_get_jak_project_path() {
return try_get_project_path_from_path(get_current_executable_path()); return try_get_project_path_from_path(get_current_executable_path());
} }
std::optional<fs::path> try_get_data_dir() { std::optional<fs::path> try_get_data_dir(bool skip_logs) {
fs::path my_path = get_current_executable_path(); fs::path my_path = get_current_executable_path();
lg::info("Current executable directory - {}", my_path.string()); if (!skip_logs) {
lg::debug("Current executable directory - {}", my_path.string());
}
auto data_dir = my_path.parent_path() / "data"; auto data_dir = my_path.parent_path() / "data";
if (fs::exists(data_dir) && fs::is_directory(data_dir)) { if (fs::exists(data_dir) && fs::is_directory(data_dir)) {
return std::make_optional(data_dir); return std::make_optional(data_dir);
@ -196,7 +198,7 @@ std::optional<fs::path> try_get_data_dir() {
} }
} }
bool setup_project_path(std::optional<fs::path> project_path_override) { bool setup_project_path(std::optional<fs::path> project_path_override, bool skip_logs) {
if (g_file_path_info.initialized) { if (g_file_path_info.initialized) {
return true; return true;
} }
@ -204,16 +206,20 @@ bool setup_project_path(std::optional<fs::path> project_path_override) {
if (project_path_override) { if (project_path_override) {
g_file_path_info.path_to_data_folder = fs::absolute(project_path_override.value()); g_file_path_info.path_to_data_folder = fs::absolute(project_path_override.value());
g_file_path_info.initialized = true; g_file_path_info.initialized = true;
lg::info("Using explicitly set project path: {}", if (!skip_logs) {
g_file_path_info.path_to_data_folder.string()); lg::debug("Using explicitly set project path: {}",
g_file_path_info.path_to_data_folder.string());
}
return true; return true;
} }
auto data_path = try_get_data_dir(); auto data_path = try_get_data_dir(skip_logs);
if (data_path) { if (data_path) {
g_file_path_info.path_to_data_folder = *data_path; g_file_path_info.path_to_data_folder = *data_path;
g_file_path_info.initialized = true; g_file_path_info.initialized = true;
lg::info("Using data path: {}", data_path->string()); if (!skip_logs) {
lg::debug("Using data path: {}", data_path->string());
}
return true; return true;
} }
@ -221,7 +227,9 @@ bool setup_project_path(std::optional<fs::path> project_path_override) {
if (development_repo_path) { if (development_repo_path) {
g_file_path_info.path_to_data_folder = *development_repo_path; g_file_path_info.path_to_data_folder = *development_repo_path;
g_file_path_info.initialized = true; g_file_path_info.initialized = true;
lg::info("Using development repo path: {}", *development_repo_path); if (!skip_logs) {
lg::debug("Using development repo path: {}", *development_repo_path);
}
return true; return true;
} }

View File

@ -43,7 +43,7 @@ bool create_dir_if_needed_for_file(const std::string& path);
bool create_dir_if_needed_for_file(const fs::path& path); bool create_dir_if_needed_for_file(const fs::path& path);
std::string get_current_executable_path(); std::string get_current_executable_path();
std::optional<std::string> try_get_project_path_from_path(const std::string& path); std::optional<std::string> try_get_project_path_from_path(const std::string& path);
bool setup_project_path(std::optional<fs::path> project_path_override); bool setup_project_path(std::optional<fs::path> project_path_override, bool skip_logs = false);
void override_user_config_dir(fs::path user_config_dir_override, void override_user_config_dir(fs::path user_config_dir_override,
bool use_overridden_config_dir_for_saves); bool use_overridden_config_dir_for_saves);
std::string get_file_path(const std::vector<std::string>& path); std::string get_file_path(const std::vector<std::string>& path);

View File

@ -985,6 +985,8 @@ static std::vector<ReplaceInfo> s_replace_info_jak2 = {
{"o~Y~-26H~-4V'~Z", "ó"}, {"o~Y~-26H~-4V'~Z", "ó"},
{"U~Y~-24H~-3V'~Z", "Ú"}, {"U~Y~-24H~-3V'~Z", "Ú"},
{"u~Y~-24H~-3V'~Z", "ú"}, {"u~Y~-24H~-3V'~Z", "ú"},
{"Z~Y~-24H~-3V'~Z", "Ź"},
{"z~Y~-24H~-3V'~Z", "ź"},
// circumflex // circumflex
{"A~Y~-20H~-4V^~Z", "Â"}, {"A~Y~-20H~-4V^~Z", "Â"},
@ -1015,6 +1017,7 @@ static std::vector<ReplaceInfo> s_replace_info_jak2 = {
{"a~Y~-25H~-5V¨~Z", "ä"}, {"a~Y~-25H~-5V¨~Z", "ä"},
{"E~Y~-20H~-5V¨~Z", "Ë"}, {"E~Y~-20H~-5V¨~Z", "Ë"},
{"I~Y~-19H~-5V¨~Z", "Ï"}, {"I~Y~-19H~-5V¨~Z", "Ï"},
{"i~Y~-26H~-4V¨~Z", "ï"},
{"O~Y~-26H~-8V¨~Z", "Ö"}, {"O~Y~-26H~-8V¨~Z", "Ö"},
{"o~Y~-26H~-4V¨~Z", "ö"}, {"o~Y~-26H~-4V¨~Z", "ö"},
{"U~Y~-25H~-8V¨~Z", "Ü"}, {"U~Y~-25H~-8V¨~Z", "Ü"},

View File

@ -46,4 +46,17 @@ std::vector<u8> decompress_zstd(const void* data, size_t size) {
ASSERT(decomp_size == decompressed_size); ASSERT(decomp_size == decompressed_size);
return result; return result;
} }
std::vector<u8> compress_zstd_no_header(const void* data, size_t size) {
size_t max_compressed = ZSTD_compressBound(size);
std::vector<u8> result(max_compressed);
size_t compressed_size = ZSTD_compress(result.data(), max_compressed, data, size, 1);
if (ZSTD_isError(compressed_size)) {
ASSERT_MSG(false, fmt::format("ZSTD error: {}", ZSTD_getErrorName(compressed_size)));
}
result.resize(compressed_size);
return result;
}
} // namespace compression } // namespace compression

View File

@ -8,4 +8,5 @@ namespace compression {
// compress and decompress data with zstd // compress and decompress data with zstd
std::vector<u8> compress_zstd(const void* data, size_t size); std::vector<u8> compress_zstd(const void* data, size_t size);
std::vector<u8> decompress_zstd(const void* data, size_t size); std::vector<u8> decompress_zstd(const void* data, size_t size);
std::vector<u8> compress_zstd_no_header(const void* data, size_t size);
} // namespace compression } // namespace compression

19
common/util/fnv.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <string>
#include "common/common_types.h"
inline u64 fnv64(const void* data, u64 len) {
u64 ret = 0xcbf29ce484222325;
const auto* ptr = (const u8*)data;
for (u64 i = 0; i < len; i++) {
ret = 1099511628211 * (((u64)*ptr) ^ ret);
ptr++;
}
return ret;
}
inline u64 fnv64(const std::string& str) {
return fnv64(str.data(), str.length());
}

View File

@ -27,11 +27,11 @@ Range<int> parse_json_optional_integer_range(const nlohmann::json& json);
} }
template <typename T> template <typename T>
void json_get_optional(const nlohmann::json& json, void json_get_optional(const nlohmann::json& j,
const std::string& key, const std::string& key,
std::optional<T>& optionalValue) { std::optional<T>& optionalValue) {
if (json.contains(key) && !json[key].is_null()) { if (j.contains(key) && !j[key].is_null()) {
optionalValue = json[key].get<T>(); optionalValue = j[key].get<T>();
} }
} }

View File

@ -3,20 +3,29 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/log/log.h" #include "common/log/log.h"
#ifdef __linux__ #ifdef _WIN32
// clang-format off
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <psapi.h>
// clang-format on
size_t get_peak_rss() {
HANDLE hProcess = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
return pmc.PeakWorkingSetSize;
} else {
return 0;
}
}
#else
#include <sys/resource.h> #include <sys/resource.h>
size_t get_peak_rss() { size_t get_peak_rss() {
rusage x; rusage x;
getrusage(RUSAGE_SELF, &x); getrusage(RUSAGE_SELF, &x);
return x.ru_maxrss * 1024; return x.ru_maxrss * 1024;
} }
#else
size_t get_peak_rss() {
return 0;
}
#endif #endif
#ifdef _WIN32 #ifdef _WIN32

View File

@ -3,7 +3,6 @@
#include <cstddef> #include <cstddef>
#include <string> #include <string>
// Note: these are not implemented on windows and will return zero.
size_t get_peak_rss(); size_t get_peak_rss();
void setup_cpu_info(); void setup_cpu_info();

View File

@ -62,7 +62,7 @@
// Note: You will still have to add them to your level's .gd file. // Note: You will still have to add them to your level's .gd file.
// "art_groups": ["plat-ag"], // "art_groups": ["plat-ag"],
// If you have any custom models in the "custom_assets/jak1/models" folder that you want to use in your level, add them to this list. // If you have any custom models in the "custom_assets/jak1/models/custom_levels" folder that you want to use in your level, add them to this list.
// Note: Like with art groups, these should also be added to your level's .gd file. // Note: Like with art groups, these should also be added to your level's .gd file.
"custom_models": ["test-actor"], "custom_models": ["test-actor"],

View File

@ -68,6 +68,7 @@ add_library(
level_extractor/fr3_to_gltf.cpp level_extractor/fr3_to_gltf.cpp
level_extractor/MercData.cpp level_extractor/MercData.cpp
level_extractor/tfrag_tie_fixup.cpp level_extractor/tfrag_tie_fixup.cpp
level_extractor/merc_replacement.cpp
ObjectFile/LinkedObjectFile.cpp ObjectFile/LinkedObjectFile.cpp
ObjectFile/LinkedObjectFileCreation.cpp ObjectFile/LinkedObjectFileCreation.cpp
@ -89,7 +90,8 @@ add_library(
VuDisasm/VuDisassembler.cpp VuDisasm/VuDisassembler.cpp
VuDisasm/VuInstruction.cpp VuDisasm/VuInstruction.cpp
config.cpp) config.cpp
decompilation_process.cpp)
target_link_libraries(decomp target_link_libraries(decomp
lzokay lzokay

View File

@ -25,7 +25,7 @@ namespace decompiler {
*/ */
class LinkedObjectFile { class LinkedObjectFile {
public: public:
LinkedObjectFile(GameVersion version) : version(version){}; LinkedObjectFile(GameVersion _version) : version(_version) {}
void set_segment_count(int n_segs); void set_segment_count(int n_segs);
void push_back_word_to_segment(uint32_t word, int segment); void push_back_word_to_segment(uint32_t word, int segment);
int get_label_id_for(int seg, int offset); int get_label_id_for(int seg, int offset);

View File

@ -5,6 +5,7 @@
#include "ObjectFileDB.h" #include "ObjectFileDB.h"
#include "common/formatter/formatter.h"
#include "common/goos/PrettyPrinter.h" #include "common/goos/PrettyPrinter.h"
#include "common/link_types.h" #include "common/link_types.h"
#include "common/log/log.h" #include "common/log/log.h"
@ -872,9 +873,22 @@ void ObjectFileDB::ir2_write_results(const fs::path& output_dir,
auto file_name = output_dir / (obj.to_unique_name() + "_ir2.asm"); auto file_name = output_dir / (obj.to_unique_name() + "_ir2.asm");
file_util::write_text_file(file_name, file_text); file_util::write_text_file(file_name, file_text);
auto final = ir2_final_out(obj, imports, {}); auto unformatted_code = ir2_final_out(obj, imports, {});
auto final_name = output_dir / (obj.to_unique_name() + "_disasm.gc"); auto final_name = output_dir / (obj.to_unique_name() + "_disasm.gc");
file_util::write_text_file(final_name, final); if (config.format_code) {
const auto formatted_code = formatter::format_code(unformatted_code);
if (!formatted_code) {
lg::error(
"Was unable to format the decompiled result of {}, make a github issue. Writing "
"unformatted code",
obj.to_unique_name());
file_util::write_text_file(final_name, unformatted_code);
} else {
file_util::write_text_file(final_name, formatted_code.value());
}
} else {
file_util::write_text_file(final_name, unformatted_code);
}
} }
} }

View File

@ -54,12 +54,8 @@ void append_body_to_function_definition(goos::Object* top_form,
body_elements.insert(body_elements.end(), inline_body.begin(), inline_body.end()); body_elements.insert(body_elements.end(), inline_body.begin(), inline_body.end());
// If the first element in the body is a docstring, add it first // If the first element in the body is a docstring, add it first
if (body_elements.size() > 0 && body_elements.at(0).is_string()) { if (body_elements.size() > 0 && body_elements.at(0).is_string()) {
if (version > GameVersion::Jak2) { initial_top_level_forms.push_back(
initial_top_level_forms.push_back( goos::StringObject::make_new(fix_docstring_indent(inline_body.at(0).as_string()->data)));
goos::StringObject::make_new(fix_docstring_indent(inline_body.at(0).as_string()->data)));
} else {
initial_top_level_forms.push_back(inline_body.at(0));
}
body_elements.erase(body_elements.begin()); body_elements.erase(body_elements.begin());
} }

View File

@ -64,7 +64,6 @@ Config make_config_via_json(nlohmann::json& json) {
inputs_json.at("str_art_file_names").get<std::vector<std::string>>(); inputs_json.at("str_art_file_names").get<std::vector<std::string>>();
} }
config.audio_dir_file_name = inputs_json.at("audio_dir_file_name").get<std::string>();
config.streamed_audio_file_names = config.streamed_audio_file_names =
inputs_json.at("streamed_audio_file_names").get<std::vector<std::string>>(); inputs_json.at("streamed_audio_file_names").get<std::vector<std::string>>();
@ -96,6 +95,9 @@ Config make_config_via_json(nlohmann::json& json) {
} }
config.disassemble_code = json.at("disassemble_code").get<bool>(); config.disassemble_code = json.at("disassemble_code").get<bool>();
config.decompile_code = json.at("decompile_code").get<bool>(); config.decompile_code = json.at("decompile_code").get<bool>();
if (json.contains("format_code")) {
config.format_code = json.at("format_code").get<bool>();
}
config.write_hex_near_instructions = json.at("write_hex_near_instructions").get<bool>(); config.write_hex_near_instructions = json.at("write_hex_near_instructions").get<bool>();
config.write_scripts = json.at("write_scripts").get<bool>(); config.write_scripts = json.at("write_scripts").get<bool>();
config.disassemble_data = json.at("disassemble_data").get<bool>(); config.disassemble_data = json.at("disassemble_data").get<bool>();
@ -318,6 +320,9 @@ Config make_config_via_json(nlohmann::json& json) {
if (json.contains("save_texture_pngs")) { if (json.contains("save_texture_pngs")) {
config.save_texture_pngs = json.at("save_texture_pngs").get<bool>(); config.save_texture_pngs = json.at("save_texture_pngs").get<bool>();
} }
if (json.contains("rip_streamed_audio")) {
config.rip_streamed_audio = json.at("rip_streamed_audio").get<bool>();
}
if (inputs_json.contains("animated_textures")) { if (inputs_json.contains("animated_textures")) {
config.animated_textures = config.animated_textures =

View File

@ -102,8 +102,6 @@ struct Config {
std::vector<std::string> str_file_names; std::vector<std::string> str_file_names;
std::vector<std::string> str_texture_file_names; std::vector<std::string> str_texture_file_names;
std::vector<std::string> str_art_file_names; std::vector<std::string> str_art_file_names;
std::string audio_dir_file_name;
std::vector<std::string> streamed_audio_file_names; std::vector<std::string> streamed_audio_file_names;
std::string obj_file_name_map_file; std::string obj_file_name_map_file;
@ -111,6 +109,7 @@ struct Config {
bool disassemble_code = false; bool disassemble_code = false;
bool decompile_code = false; bool decompile_code = false;
bool format_code = false;
bool write_scripts = false; bool write_scripts = false;
bool disassemble_data = false; bool disassemble_data = false;
bool process_tpages = false; bool process_tpages = false;
@ -174,6 +173,7 @@ struct Config {
std::vector<std::string> levels_to_extract; std::vector<std::string> levels_to_extract;
bool levels_extract; bool levels_extract;
bool save_texture_pngs = false; bool save_texture_pngs = false;
bool rip_streamed_audio = false;
DecompileHacks hacks; DecompileHacks hacks;

View File

@ -21102,7 +21102,7 @@
:size-assert #x94 :size-assert #x94
:flag-assert #xf00300094 :flag-assert #xf00300094
(:methods (:methods
(dumb-15 (_type_) none)) (spawn-particles! (_type_) none))
(:states (:states
hud-normal hud-normal
hud-coming-in hud-coming-in
@ -22303,20 +22303,20 @@
;; - Functions ;; - Functions
(define-extern calculate-rotation-and-color-for-slice (function int float int int int hud-particle none)) (define-extern calculate-rotation-and-color-for-slice (function int float int int int matrix none))
(define-extern part-hud-health-01-func (function basic basic hud-particle none)) ;; TODO - i figured this out on another branch...all these particle functions get called by the particle system with the same beginning types iirc (define-extern part-hud-health-01-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-health-02-func (function basic basic hud-particle none)) (define-extern part-hud-health-02-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-health-03-func (function basic basic hud-particle none)) (define-extern part-hud-health-03-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern fuel-cell-hud-orbit-callback (function sparticle-system sparticle-cpuinfo matrix none)) (define-extern fuel-cell-hud-orbit-callback (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern fuel-cell-hud-starburst-3-callback (function sparticle-system sparticle-cpuinfo matrix none)) (define-extern fuel-cell-hud-starburst-3-callback (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern fuel-cell-hud-starburst-4-callback (function sparticle-system sparticle-cpuinfo matrix none)) (define-extern fuel-cell-hud-starburst-4-callback (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern fuel-cell-hud-center-callback (function sparticle-system sparticle-cpuinfo matrix none)) (define-extern fuel-cell-hud-center-callback (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-buzzer-func (function basic basic hud-particle none)) (define-extern part-hud-buzzer-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-01-func (function basic basic hud-particle none)) (define-extern part-hud-eco-timer-01-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-02-func (function basic basic hud-particle none)) (define-extern part-hud-eco-timer-02-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-03-func (function basic basic hud-particle none)) (define-extern part-hud-eco-timer-03-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-backing-func (function basic basic hud-particle none)) (define-extern part-hud-eco-timer-backing-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-func (function basic basic hud-particle none)) (define-extern part-hud-eco-timer-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern show-hud (function none)) (define-extern show-hud (function none))
;; - Unknowns ;; - Unknowns

View File

@ -23,6 +23,10 @@
// run the first pass of the decompiler // run the first pass of the decompiler
"find_functions": true, "find_functions": true,
// will attempt to run the decompiled output through the OpenGOAL formatter
// this will be skipped in offline tests
"format_code": true,
//////////////////////////// ////////////////////////////
// DATA ANALYSIS OPTIONS // DATA ANALYSIS OPTIONS
//////////////////////////// ////////////////////////////
@ -118,6 +122,9 @@
// save game textures as .png files to decompiler_out/<game>/textures // save game textures as .png files to decompiler_out/<game>/textures
"save_texture_pngs": false, "save_texture_pngs": false,
// whether or not to dump out streamed audio files to decompiler_out/<game>/audio
"rip_streamed_audio": false,
//////////////////////////// ////////////////////////////
// PATCHING OPTIONS // PATCHING OPTIONS
//////////////////////////// ////////////////////////////

View File

@ -258,10 +258,6 @@
"TEXT/6COMMON.TXT" "TEXT/6COMMON.TXT"
], ],
// uncomment the next line to extract audio to wave files.
//"audio_dir_file_name": "jak1/VAG",
"audio_dir_file_name": "",
"streamed_audio_file_names": [ "streamed_audio_file_names": [
"VAGWAD.ENG", "VAGWAD.ENG",
"VAGWAD.FRE", "VAGWAD.FRE",

View File

@ -228,9 +228,5 @@
"TEXT/6COMMON.TXT" "TEXT/6COMMON.TXT"
], ],
// uncomment the next line to extract audio to wave files.
//"audio_dir_file_name": "jak1/VAG",
"audio_dir_file_name": "",
"streamed_audio_file_names": ["VAGWAD.ENG", "VAGWAD.JAP"] "streamed_audio_file_names": ["VAGWAD.ENG", "VAGWAD.JAP"]
} }

View File

@ -2515,6 +2515,7 @@
(iop-mem 48) (iop-mem 48)
(cancel-dgo 49) (cancel-dgo 49)
(set-stereo-mode 50) ;; sound-rpc-set-stereo-mode (set-stereo-mode 50) ;; sound-rpc-set-stereo-mode
(set-mirror 201)
) )
(defenum sound-group (defenum sound-group
@ -2829,6 +2830,17 @@
:flag-assert #x900000004 :flag-assert #x900000004
) )
; added
(defenum sound-mirror-mode
:type uint8
(normal)
(mirrored)
)
; added for mirror mode
(deftype sound-rpc-set-mirror-mode (sound-rpc-cmd)
((mirror sound-mirror-mode)))
(deftype sound-rpc-union (structure) (deftype sound-rpc-union (structure)
((data uint32 20 :offset-assert 0) ((data uint32 20 :offset-assert 0)
(load-bank sound-rpc-load-bank :offset 0) (load-bank sound-rpc-load-bank :offset 0)
@ -2852,6 +2864,7 @@
(shutdown sound-rpc-shutdown :offset 0) (shutdown sound-rpc-shutdown :offset 0)
(list-sounds sound-rpc-list-sounds :offset 0) (list-sounds sound-rpc-list-sounds :offset 0)
(unload-music sound-rpc-unload-music :offset 0) (unload-music sound-rpc-unload-music :offset 0)
(mirror-mode sound-rpc-set-mirror-mode :overlay-at (-> data 0))
) )
:method-count-assert 9 :method-count-assert 9
:size-assert #x50 :size-assert #x50

View File

@ -128,6 +128,9 @@
// save game textures as .png files to decompiler_out/<game>/textures // save game textures as .png files to decompiler_out/<game>/textures
"save_texture_pngs": false, "save_texture_pngs": false,
// whether or not to dump out streamed audio files to decompiler_out/<game>/audio
"rip_streamed_audio": false,
//////////////////////////// ////////////////////////////
// PATCHING OPTIONS // PATCHING OPTIONS
//////////////////////////// ////////////////////////////

View File

@ -441,10 +441,6 @@
"TEXT/7COMMON.TXT" "TEXT/7COMMON.TXT"
], ],
// uncomment the next line to extract audio to wave files.
// "audio_dir_file_name": "jak2/VAG",
"audio_dir_file_name": "",
"streamed_audio_file_names": [ "streamed_audio_file_names": [
"VAGWAD.ENG", "VAGWAD.ENG",
"VAGWAD.FRE", "VAGWAD.FRE",

File diff suppressed because it is too large Load Diff

View File

@ -127,6 +127,9 @@
// save game textures as .png files to decompiler_out/<game>/textures // save game textures as .png files to decompiler_out/<game>/textures
"save_texture_pngs": false, "save_texture_pngs": false,
// whether or not to dump out streamed audio files to decompiler_out/<game>/audio
"rip_streamed_audio": false,
//////////////////////////// ////////////////////////////
// PATCHING OPTIONS // PATCHING OPTIONS
//////////////////////////// ////////////////////////////

View File

@ -138,7 +138,7 @@
[7, "(function none)"], [7, "(function none)"],
[3, "(function symbol :behavior process)"] [3, "(function symbol :behavior process)"]
], ],
"scene": [[4, "(function none :behavior scene-player)"]], "scene": [[4, "(function symbol :behavior scene-player)"]],
"pov-camera": [ "pov-camera": [
[ [
7, 7,

View File

@ -333,10 +333,6 @@
"TEXT/11COMMON.TXT" "TEXT/11COMMON.TXT"
], ],
// uncomment the next line to extract audio to wave files.
// "audio_dir_file_name": "jak3/VAG",
"audio_dir_file_name": "",
"streamed_audio_file_names": [ "streamed_audio_file_names": [
"VAGWAD.ENG", "VAGWAD.ENG",
"VAGWAD.FRE", "VAGWAD.FRE",
@ -349,15 +345,331 @@
"animated_textures": [ "animated_textures": [
// dark jak // dark jak
"jakc-arm", "jakc-eyebrow", "jakc-face", "jakc-finger", "jakc-hair", "jakc-arm",
"jakc-arm-norm", "jakc-eyebrow-norm", "jakc-face-norm", "jakc-finger-norm", "jakc-hair-norm", "jakc-eyebrow",
"jakc-arm-dark", "jakc-eyebrow-dark", "jakc-face-dark", "jakc-finger-dark", "jakc-hair-dark", "jakc-face",
"jakc-finger",
"jakc-hair",
"jakc-arm-norm",
"jakc-eyebrow-norm",
"jakc-face-norm",
"jakc-finger-norm",
"jakc-hair-norm",
"jakc-arm-dark",
"jakc-eyebrow-dark",
"jakc-face-dark",
"jakc-finger-dark",
"jakc-hair-dark",
// darkjak-highres
"jakchires-arm",
"jakchires-arm-norm",
"jakchires-arm-dark",
"jakchires-eye",
"jakchires-eye-norm",
"jakchires-eye-dark",
"jakchires-eyebrow",
"jakchires-eyebrow-norm",
"jakchires-eyebrow-dark",
"jakchires-eyelid",
"jakchires-eyelid-norm",
"jakchires-eyelid-dark",
"jakchires-facelft",
"jakchires-facelft-norm",
"jakchires-facelft-dark",
"jakchires-facert",
"jakchires-facert-norm",
"jakchires-facert-dark",
"jakchires-hair",
"jakchires-hair-norm",
"jakchires-hair-dark",
// Skull Gem // Skull Gem
"skull-gem-dest", "skull-gem-dest",
"skull-gem-alpha-00", "skull-gem-alpha-00",
"skull-gem-alpha-01", "skull-gem-alpha-01",
"skull-gem-alpha-02" "skull-gem-alpha-02",
// default-water
"bomb-gradient",
"bomb-gradient-rim",
"bomb-gradient-flames",
"lightjak-wings",
"lighjak-wings-u-src",
"lightjak-wings-v-src",
"mushroom-dest",
"mushroom-src",
// default-warp
"shield-env-rim-dest",
"common-white",
"shield-env-uscroll",
"shield-env-rim-src",
// hanga-sprite
"glider-ring-dest2",
"glider-ring-dest",
"splash-foam",
"racegate",
// templea-water
"templea-waterfall-dest",
"templea-waterfall",
// templea-warp
"holograph-env-rim-dest",
"holograph-env-noise",
"holograph-env-scan",
"holograph-env-rim",
"environment-phong-rim",
// foresta-water
"fora-water-dest",
"fora-water",
"fora-waterfall-01-dest",
"fora-waterfall-01",
"fora-water-wave-01-dest",
"fora-water-wave-01",
// forestb-water
"forb-water-dest",
"forb-water",
"forb-waterfall-01-dest",
"forb-waterfall-01",
"forb-water-wave-01-dest",
"forb-water-wave-01",
// lforplnt-pris
"mh-gem-dest",
"mh-gem",
"mh-gem-alpha-01",
"mh-gem-alpha-02",
// lmhcitya-tfrag
"mhcitya-base-goo-01-dest",
"mhcitya-base-goo-01",
// lmhcityb-tfrag
"mhcityb-base-goo-01-dest",
"mhcityb-base-goo-01",
// mhcitya-pris
// "mhcity-de-door-skin-01-dest",
// "mhcity-de-door-skin-01",
// "mhcity-de-door-skin-02"
// templec-water
"tplc-water-dest",
"tplc-water",
// sewc-water
"sewer-water-01-c-dest",
"sewer-water-01-c",
"sewer-waterfall-01-c-dest",
"sewer-waterfall-01-c",
"sewer-waterfall-02-c-dest",
"sewer-waterfall-02-c",
"sewer-water-wave-01-c-dest",
"sewer-water-wave-01-c",
"sewer-water-highlight-01-c-dest",
"sewer-water-highlight-01-c",
// sewd-water
"sewer-water-01-d-dest",
"sewer-water-01-d",
"sewer-waterfall-02-d-dest",
"sewer-waterfall-02-d",
"sewer-water-highlight-01-d-dest",
"sewer-water-highlight-01-d",
"sewer-water-wave-01-d-dest",
"sewer-water-wave-01-d",
"sewer-water-wave-02-d-dest",
"sewer-water-wave-02-d",
"sewer-water-still-01-d-dest",
"sewer-water-still-01-d",
// sewe-water
"sewer-water-01-e-dest",
"sewer-water-01-e",
"sewer-waterfall-01-e-dest",
"sewer-waterfall-01-e",
"sewer-waterfall-02-e-dest",
"sewer-waterfall-02-e",
"sewer-water-highlight-01-e-dest",
"sewer-water-highlight-01-e",
// sewg-water
"sewer-water-01-g-dest",
"sewer-water-01-g",
"sewer-waterfall-02-g-dest",
"sewer-waterfall-02-g",
"sewer-water-wave-01-g-dest",
"sewer-water-wave-01-g",
// sewh-water
"sewer-water-01-h-dest",
"sewer-water-01-h",
"sewer-waterfall-02-h-dest",
"sewer-waterfall-02-h",
"sewer-water-wave-02-h-dest",
"sewer-water-wave-02-h",
"sewer-watefall-froth-01-h-dest",
"sewer-watefall-froth-01-h",
// sewi-water
"sewer-water-still-01-i-dest",
"sewer-water-still-01-i",
"sewer-waterfall-02-i-dest",
"sewer-waterfall-02-i",
"sewer-water-wave-01-i-dest",
"sewer-water-wave-01-i",
// sewj-water
"sewer-waterfall-02-j-dest",
"sewer-waterfall-02-j",
"sewer-watefall-froth-01-j-dest",
"sewer-watefall-froth-01-j",
// sewl-water
"sewer-waterfall-02-l-dest",
"sewer-waterfall-02-l",
"sewer-watefall-froth-01-l-dest",
"sewer-watefall-froth-01-l",
// sewm-water
"sewer-water-01-m-dest",
"sewer-water-01-m",
"sewer-waterfall-01-m-dest",
"sewer-waterfall-01-m",
"sewer-waterfall-02-m-dest",
"sewer-waterfall-02-m",
"sewer-water-highlight-01-m-dest",
"sewer-water-highlight-01-m",
"sewer-water-wave-01-m-dest",
"sewer-water-wave-01-m",
"sewer-water-still-01-m-dest",
"sewer-water-still-01-m",
"sewer-watefall-froth-01-m-dest",
"sewer-watefall-froth-01-m",
// sewn-water
"sewer-waterfall-01-n-dest",
"sewer-waterfall-01-n",
"sewer-waterfall-02-n-dest",
"sewer-waterfall-02-n",
// "sewer-water-highlight-01-n-dest",
// "sewer-water-highlight-01-n",
"sewer-water-wave-01-n-dest",
"sewer-water-wave-01-n",
"sewer-water-still-01-n-dest",
"sewer-water-still-01-n",
// hanga-water
// "des-thermal-01-dest",
// "des-thermal-01"
// desresc-warp
"sat-shield-dest",
"sat-shield-env-uvscroll",
"sat-shield",
// security
"security-env-dest",
"security-env-uscroll",
"security-dot-dest",
"security-dot-src",
// lgunnorm-water
"kg-target-c-forcefield-01-dest",
"kg-target-c-forcefield-01",
// templex-water
"temple-waterfall-dest",
"temple-waterfall",
// desertd-water
"des-waterfall-dest",
"des-waterfall",
// towerb-water
"tow-energy-bridge-dest",
"tow-energy-bridge",
// factoryb-water
"hemi-gradient-dest",
"hemi-gradient-rim",
"hemi-gradient-flames-dest",
"hemi-gradient-flames",
// factoryc-alpha
"facc-convey-dest",
"facc-convey",
"facc-convey-02-dest",
"facc-convey-02",
// waspal-water
"waspala-water-dest",
"waspala-water",
"waspala-waterfall-dest",
"waspala-waterfall",
// rubblea-water
"rub-water-dest",
"rub-water",
// rubblea2-water
"rub-water-desta2",
"rub-watera2",
// rubbleb-water
"rub-water-destb",
"rub-waterb",
// rubblec-water
"rub-water-destc",
"rub-waterc",
// nstb-quicksand
"nstb-quicksand-dest",
"nstb-quicksand-scroll",
// ctyslumb-water
"ctyslumb-water-dest",
"ctyslumb-water",
"ctyslumb-fountain-fall-dest",
"ctyslumb-fountain-fall",
// ctyslumc-water
"ctyslumc-water-dest",
"ctyslumc-water",
"ctyslumc-fountain-fall-dest",
"ctyslumc-fountain-fall",
// mined-tfrag
"mined-pillar-side-dest",
"mined-pillar-side-cold",
"mined-pillar-side-cooling",
"mined-pillar-side-hot",
"mined-pillar-molten",
"mined-pillar-top2side-dest",
"mined-pillar-top2side-cold",
"mined-pillar-top2side-cooling",
"mined-pillar-top2side-hot",
"mined-pillar-top-dest",
"mined-pillar-top-cold",
"mined-pillar-top-cooling",
"mined-pillar-top-hot",
"mined-pillar-molten-top",
// volcanoa-alpha
"vola-lava-01-dest",
"vola-lava-01",
"vola-lava-fall-dest",
"vola-lava-fall",
// wasstada-alpha
"wstd-lava-base-dest",
"wstd-lava-base"
], ],
"levels_to_extract": [ "levels_to_extract": [

View File

@ -2508,5 +2508,10 @@
[16, "vector"], [16, "vector"],
[32, "vector"] [32, "vector"]
], ],
"texture-anim-layer-draw": [[16, "matrix"], [80, "matrix"], [144, "matrix"], [208, "matrix"]] "texture-anim-layer-draw": [
[16, "matrix"],
[80, "matrix"],
[144, "matrix"],
[208, "matrix"]
]
} }

View File

@ -1850,6 +1850,8 @@
["_stack_", 148, "float"], ["_stack_", 148, "float"],
["_stack_", 152, "float"], ["_stack_", 152, "float"],
["_stack_", 156, "float"], ["_stack_", 156, "float"],
[25, "a1", "light-trail-breadcrumb"],
[178, "s4", "light-trail-breadcrumb"],
[556, "a0", "vector"] [556, "a0", "vector"]
], ],
"compute-trail-scaled-t": [[17, "v1", "float"]], "compute-trail-scaled-t": [[17, "v1", "float"]],
@ -2683,8 +2685,13 @@
[[21, 25], "a0", "prim-strip"] [[21, 25], "a0", "prim-strip"]
], ],
"(event tracking light-trail-tracker)": [[55, "v1", "float"]], "(event tracking light-trail-tracker)": [[55, "v1", "float"]],
"(method 21 light-trail)": [[50, "v1", "light-trail-breadcrumb"]], "(method 21 light-trail)": [[[18, 64], "gp", "light-trail-breadcrumb"]],
"(method 14 light-trail)": [[47, "a0", "uint"]], "(method 14 light-trail)": [
[47, "a0", "uint"],
[[10, 32], "a1", "light-trail-breadcrumb"],
[[34, 60], "s3", "light-trail-breadcrumb"],
[[34, 73], "s2", "light-trail-breadcrumb"]
],
"debug-menu-item-var-update-display-str": [ "debug-menu-item-var-update-display-str": [
[48, "v1", "int"], [48, "v1", "int"],
[63, "v1", "int"], [63, "v1", "int"],
@ -2735,16 +2742,12 @@
[27, "gp", "process-drawable"], [27, "gp", "process-drawable"],
[30, "gp", "process-drawable"] [30, "gp", "process-drawable"]
], ],
"(method 23 weapon-trail)": [ "(method 23 weapon-trail)": [[[0, 100], "gp", "weapon-trail-crumb"]],
[62, "v1", "light-trail-breadcrumb"],
[65, "v1", "light-trail-breadcrumb"]
],
"(method 22 weapon-trail)": [[32, "v0", "light-trail-breadcrumb"]], "(method 22 weapon-trail)": [[32, "v0", "light-trail-breadcrumb"]],
"(method 22 tread-trail)": [[19, "v0", "light-trail-breadcrumb"]], "(method 22 tread-trail)": [[19, "v0", "light-trail-breadcrumb"]],
"(method 23 tread-trail)": [ "(method 23 tread-trail)": [[[0, 100], "s5", "tread-trail-crumb"]],
[51, "v1", "light-trail-breadcrumb"], "(method 23 tire-trail)": [[[0, 74], "s5", "tire-trail-crumb"]],
[67, "v0", "light-trail-breadcrumb"] "(method 22 tire-trail)": [[[18, 24], "v1", "tire-trail-crumb"]],
],
"(trans idle fma-sphere)": [[39, "a2", "process-drawable"]], "(trans idle fma-sphere)": [[39, "a2", "process-drawable"]],
"part-water-splash-callback": [[3, "v1", "float"]], "part-water-splash-callback": [[3, "v1", "float"]],
"(method 15 water-control)": [[48, "v1", "float"]], "(method 15 water-control)": [[48, "v1", "float"]],
@ -6428,11 +6431,11 @@
], ],
"cshape-reaction-scorp-shot": [[15, "v1", "v-scorp-shot"]], "cshape-reaction-scorp-shot": [[15, "v1", "v-scorp-shot"]],
"(method 78 wvehicle)": [ "(method 78 wvehicle)": [
[262, "s1", "process-focusable"], [262, "s1", "tire-trail-tracker"],
[271, "s1", "process-focusable"], [271, "s1", "tire-trail-tracker"],
[287, "s1", "process-focusable"], [287, "s1", "tire-trail-tracker"],
[309, "s1", "process-focusable"], [309, "s1", "tire-trail-tracker"],
[322, "s1", "process-focusable"], [322, "s1", "tire-trail-tracker"],
[358, "v1", "collide-shape-prim-group"] [358, "v1", "collide-shape-prim-group"]
], ],
"(method 97 wvehicle)": [ "(method 97 wvehicle)": [
@ -6551,7 +6554,10 @@
[15, "v1", "process-drawable"], [15, "v1", "process-drawable"],
[22, "v1", "process-drawable"] [22, "v1", "process-drawable"]
], ],
"(method 19 tire-trail)": [], "(method 19 tire-trail)": [
[[5, 28], "s5", "tire-trail-crumb"],
[[5, 28], "s4", "tire-trail-crumb"]
],
"vehicle-spawn": [[93, "gp", "vehicle"]], "vehicle-spawn": [[93, "gp", "vehicle"]],
"vehicle-spawn-hack": [[41, "gp", "vehicle"]], "vehicle-spawn-hack": [[41, "gp", "vehicle"]],
"(method 11 w-parking-spot)": [[42, "v0", "vector"]], "(method 11 w-parking-spot)": [[42, "v0", "vector"]],
@ -10851,9 +10857,7 @@
["_stack_", 76, "float"], ["_stack_", 76, "float"],
["_stack_", 100, "float"] ["_stack_", 100, "float"]
], ],
"real-wang-texture-anim-func": [ "real-wang-texture-anim-func": [[[3, 31], "v1", "mood-context"]],
[[3, 31], "v1", "mood-context"]
],
"(method 24 sky-work)": [ "(method 24 sky-work)": [
[256, "s4", "(pointer int32)"], [256, "s4", "(pointer int32)"],
[261, "s4", "(pointer int32)"] [261, "s4", "(pointer int32)"]
@ -11120,9 +11124,7 @@
[64, "a1", "(pointer gs-alpha)"], [64, "a1", "(pointer gs-alpha)"],
[66, "a1", "(pointer gs-reg64)"] [66, "a1", "(pointer gs-reg64)"]
], ],
"texture-anim-layer-draw": [ "texture-anim-layer-draw": [[[24, 182], "s4", "(pointer uint128)"]],
[[24, 182], "s4", "(pointer uint128)"]
],
"update-texture-anim": [ "update-texture-anim": [
[213, "v1", "(pointer uint128)"], [213, "v1", "(pointer uint128)"],
[[214, 230], "t0", "vector4w"], [[214, 230], "t0", "vector4w"],
@ -11264,7 +11266,5 @@
[[191, 249], "gp", "shadow-dcache"], [[191, 249], "gp", "shadow-dcache"],
[96, "v1", "shadow-dcache"] [96, "v1", "shadow-dcache"]
], ],
"real-fog-texture-anim-func": [ "real-fog-texture-anim-func": [[[6, 160], "s2", "(pointer uint32)"]]
[[6, 160], "s2", "(pointer uint32)"]
]
} }

View File

@ -515,8 +515,44 @@ TPageResultStats process_tpage(ObjectFileData& data,
// currently not needed. // currently not needed.
break; break;
case int(PSM::PSMT8): case int(PSM::PSMT8):
ASSERT(tex.clutpsm == int(CPSM::PSMCT32)); if (tex.clutpsm == (int)CPSM::PSMCT16) {
{ // will store output pixels, rgba (8888)
std::vector<u8> index_out;
// width is like the TEX0 register, in 64 texel units.
// not sure what the other widths are yet.
int read_width = 64 * tex.width[0];
// loop over pixels in output texture image
for (int y = 0; y < tex.h; y++) {
for (int x = 0; x < tex.w; x++) {
// read as the PSMT8 type. The dest field tells us a block offset.
auto addr8 = psmt8_addr(x, y, read_width) + tex.dest[0] * 256;
u8 value = *(u8*)(vram.data() + addr8);
index_out.push_back(value);
}
}
std::array<math::Vector4<u8>, 256> unscrambled_clut{};
for (int i = 0; i < 256; i++) {
u32 clut_chunk = i / 16;
u32 off_in_chunk = i % 16;
u8 clx = 0, cly = 0;
if (clut_chunk & 1) {
clx = 8;
}
cly = (clut_chunk >> 1) * 2;
if (off_in_chunk >= 8) {
off_in_chunk -= 8;
cly++;
}
clx += off_in_chunk;
u32 clut_addr = psmct16_addr(clx, cly, 64) + tex.clutdest * 256;
memcpy(&unscrambled_clut[i], vram.data() + clut_addr, 4);
}
texture_db.add_index_texture(texture_page.id, tex_id, index_out, unscrambled_clut,
tex.w, tex.h, tex.name, texture_page.name, level_names);
stats.successful_textures++;
} else if (tex.clutpsm == (int)PSM::PSMCT32) {
// will store output pixels, index (u8) // will store output pixels, index (u8)
std::vector<u8> index_out; std::vector<u8> index_out;
@ -555,10 +591,12 @@ TPageResultStats process_tpage(ObjectFileData& data,
texture_db.add_index_texture(texture_page.id, tex_id, index_out, unscrambled_clut, texture_db.add_index_texture(texture_page.id, tex_id, index_out, unscrambled_clut,
tex.w, tex.h, tex.name, texture_page.name, level_names); tex.w, tex.h, tex.name, texture_page.name, level_names);
stats.successful_textures++; stats.successful_textures++;
} else {
ASSERT_NOT_REACHED();
} }
break; break;
default: default:
lg::die("Animated texture {} format {}\n", tex.name, tex.psm); lg::die("Animated texture {} format {} clut {}\n", tex.name, tex.psm, tex.clutpsm);
} }
} }

View File

@ -0,0 +1,284 @@
#include "decompilation_process.h"
#include <string>
#include <vector>
#include "config.h"
#include "common/log/log.h"
#include "common/util/Timer.h"
#include "common/util/os.h"
#include "Disasm/OpcodeInfo.h"
#include "ObjectFile/ObjectFileDB.h"
#include "data/streamed_audio.h"
#include "level_extractor/extract_level.h"
int run_decompilation_process(decompiler::Config config,
const fs::path& in_folder,
const fs::path& out_folder,
const bool minimal_for_extractor) {
using namespace decompiler;
Timer decomp_timer;
lg::info("[Mem] Top of main: {} MB\n", get_peak_rss() / (1024 * 1024));
init_opcode_info();
lg::info("[Mem] After init: {} MB\n", get_peak_rss() / (1024 * 1024));
std::vector<fs::path> dgos, objs, strs, tex_strs, art_strs;
if (minimal_for_extractor) {
// TODO - does this even matter, or can we just make the DGOs lazily loaded (does it already
// happen?)
for (const auto& dgo_name : config.dgo_names) {
std::string common_name = "GAME.CGO";
if (dgo_name.length() > 3 && dgo_name.substr(dgo_name.length() - 3) == "DGO") {
// ends in DGO, it's a level
dgos.push_back(in_folder / dgo_name);
} else if (dgo_name.length() >= common_name.length() &&
dgo_name.substr(dgo_name.length() - common_name.length()) == common_name) {
// it's COMMON.CGO, we need that too.
dgos.push_back(in_folder / dgo_name);
}
}
} else {
for (const auto& dgo_name : config.dgo_names) {
dgos.push_back(in_folder / dgo_name);
}
}
if (minimal_for_extractor) {
// TODO - does this even matter, or can we just make the DGOs lazily loaded (does it already
// happen?)
for (const auto& obj_name : config.object_file_names) {
if (obj_name.length() > 3 && obj_name.substr(obj_name.length() - 3) == "TXT") {
// ends in TXT
objs.push_back(in_folder / obj_name);
}
}
} else {
for (const auto& obj_name : config.object_file_names) {
objs.push_back(in_folder / obj_name);
}
}
if (!minimal_for_extractor) {
for (const auto& str_name : config.str_file_names) {
strs.push_back(in_folder / str_name);
}
}
for (const auto& str_name : config.str_texture_file_names) {
tex_strs.push_back(in_folder / str_name);
}
for (const auto& str_name : config.str_art_file_names) {
art_strs.push_back(in_folder / str_name);
}
lg::info("[Mem] After config read: {} MB", get_peak_rss() / (1024 * 1024));
// build file database
lg::info("Setting up object file DB...");
ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, strs, tex_strs, art_strs,
config);
// Explicitly fail if a file in the 'allowed_objects' list wasn't found in the DB
// as this is another silent error that can be confusing
if (!config.allowed_objects.empty()) {
for (const auto& expected_obj : config.allowed_objects) {
if (db.obj_files_by_name.count(expected_obj) == 0) {
// TODO - this is wrong for jak1, fix eventually as this is now done in 3 places
lg::error(
"Expected to find '{}' in the ObjectFileDB but did not. Check "
"./decompiler/config/{}/inputs.jsonc",
expected_obj, config.game_name);
return 1;
}
}
}
lg::info("[Mem] After DB setup: {} MB", get_peak_rss() / (1024 * 1024));
// write out DGO file info
file_util::create_dir_if_needed(out_folder);
file_util::write_text_file(out_folder / "dgo.txt", db.generate_dgo_listing());
// write out object file map (used for future decompilations, if desired)
file_util::write_text_file(out_folder / "obj.txt",
db.generate_obj_listing(config.merged_objects));
// dump raw objs
if (config.dump_objs) {
auto path = out_folder / "raw_obj";
file_util::create_dir_if_needed(path);
db.dump_raw_objects(path);
}
// process files (required for all analysis)
db.process_link_data(config);
lg::info("[Mem] After link data: {} MB", get_peak_rss() / (1024 * 1024));
db.find_code(config);
db.process_labels();
lg::info("[Mem] After code: {} MB", get_peak_rss() / (1024 * 1024));
// top level decompile (do this before printing asm so we get function names)
if (config.find_functions) {
db.ir2_top_level_pass(config);
}
// print disassembly
if (config.disassemble_code || config.disassemble_data) {
db.write_disassembly(out_folder, config.disassemble_data, config.disassemble_code,
config.write_hex_near_instructions);
}
if (config.process_art_groups) {
db.extract_art_info();
// dump art info to json if requested
if (config.dump_art_group_info) {
auto ag_file_name = out_folder / "dump" / "art-group-info.min.json";
nlohmann::json ag_json = db.dts.art_group_info;
file_util::create_dir_if_needed_for_file(ag_file_name);
file_util::write_text_file(ag_file_name, ag_json.dump(-1));
lg::info("[DUMP] Dumped art group info to {}", ag_file_name.string());
}
if (config.dump_joint_geo_info) {
auto jg_file_name = out_folder / "dump" / "joint-node-info.min.json";
nlohmann::json jg_json = db.dts.jg_info;
file_util::create_dir_if_needed_for_file(jg_file_name);
file_util::write_text_file(jg_file_name, jg_json.dump(-1));
lg::info("[DUMP] Dumped joint node info to {}", jg_file_name.string());
}
} else if (!config.art_group_info_dump.empty() || !config.jg_info_dump.empty()) {
// process art groups (used in decompilation)
// - if the config has a path to the art info dump, just use that
// - otherwise (or if we want to dump it fresh) extract it
if (!config.art_group_info_dump.empty()) {
db.dts.art_group_info = config.art_group_info_dump;
}
if (!config.jg_info_dump.empty()) {
db.dts.jg_info = config.jg_info_dump;
}
} else {
lg::error("`process_art_groups` was false and no art-group-info dump was provided!");
return 1;
}
if (config.process_tpages && !config.texture_info_dump.empty()) {
db.dts.textures = config.texture_info_dump;
}
// main decompile.
if (config.decompile_code) {
db.analyze_functions_ir2(out_folder, config, {}, {}, {});
}
if (config.generate_all_types) {
ASSERT_MSG(config.decompile_code, "Must decompile code to generate all-types");
db.ir2_analyze_all_types(out_folder / "new-all-types.gc", config.old_all_types_file,
config.hacks.types_with_bad_inspect_methods);
}
lg::info("[Mem] After decomp: {} MB", get_peak_rss() / (1024 * 1024));
// write out all symbols
file_util::write_text_file(out_folder / "all-syms.gc", db.dts.dump_symbol_types());
// write art groups
if (config.process_art_groups) {
db.dump_art_info(out_folder);
}
if (config.hexdump_code || config.hexdump_data) {
db.write_object_file_words(out_folder, config.hexdump_data, config.hexdump_code);
}
// data stuff
if (config.write_scripts) {
db.find_and_write_scripts(out_folder);
}
// ensure asset dir exists
file_util::create_dir_if_needed(out_folder / "assets");
if (config.process_game_text) {
auto result = db.process_game_text_files(config);
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_text.txt", result);
}
}
lg::info("[Mem] After text: {} MB", get_peak_rss() / (1024 * 1024));
if (config.process_subtitle_text || config.process_subtitle_images) {
auto result = db.process_all_spool_subtitles(
config, config.process_subtitle_images ? out_folder / "assets" / "subtitle-images" : "");
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_subs.txt", result);
}
}
lg::info("[Mem] After spool handling: {} MB", get_peak_rss() / (1024 * 1024));
TextureDB tex_db;
if (config.process_tpages || config.levels_extract) {
auto textures_out = out_folder / "textures";
auto dump_out = out_folder / "import";
file_util::create_dir_if_needed(textures_out);
auto result = db.process_tpages(tex_db, textures_out, config, dump_out);
if (!result.empty() && config.process_tpages) {
file_util::write_text_file(textures_out / "tpage-dir.txt", result);
file_util::write_text_file(textures_out / "tex-remap.txt",
tex_db.generate_texture_dest_adjustment_table());
}
if (config.dump_tex_info) {
auto texture_file_name = out_folder / "dump" / "tex-info.min.json";
nlohmann::json texture_json = db.dts.textures;
file_util::create_dir_if_needed_for_file(texture_file_name);
file_util::write_text_file(texture_file_name, texture_json.dump(-1));
lg::info("[DUMP] Dumped texture info to {}", texture_file_name.string());
}
}
lg::info("[Mem] After textures: {} MB", get_peak_rss() / (1024 * 1024));
// Merge textures before replacing them, in other words, replacements take priority
auto texture_merge_path = file_util::get_jak_project_dir() / "game" / "assets" /
game_version_names[config.game_version] / "texture_merges";
if (fs::exists(texture_merge_path)) {
tex_db.merge_textures(texture_merge_path);
}
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
game_version_names[config.game_version] / "texture_replacements";
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}
if (config.process_game_count) {
auto result = db.process_game_count_file();
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_count.txt", result);
}
}
if (config.levels_extract) {
auto level_out_path =
file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3";
file_util::create_dir_if_needed(level_out_path);
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, level_out_path);
}
lg::info("[Mem] After extraction: {} MB", get_peak_rss() / (1024 * 1024));
if (config.rip_streamed_audio) {
auto streaming_audio_out = out_folder / "audio";
file_util::create_dir_if_needed(streaming_audio_out);
process_streamed_audio(config, streaming_audio_out, in_folder,
config.streamed_audio_file_names);
}
lg::info("Decompiler has finished successfully in {:.2f} seconds.", decomp_timer.getSeconds());
return 0;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "config.h"
int run_decompilation_process(decompiler::Config config,
const fs::path& in_folder,
const fs::path& out_folder,
const bool minimal_for_extractor);

View File

@ -5,10 +5,9 @@
#include "common/util/term_util.h" #include "common/util/term_util.h"
#include "common/util/unicode_util.h" #include "common/util/unicode_util.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "decompiler/config.h" #include "decompiler/config.h"
#include "decompiler/decompilation_process.h"
#include "decompiler/extractor/extractor_util.h" #include "decompiler/extractor/extractor_util.h"
#include "decompiler/level_extractor/extract_level.h"
#include "goalc/compiler/Compiler.h" #include "goalc/compiler/Compiler.h"
#include "third-party/CLI11.hpp" #include "third-party/CLI11.hpp"
@ -102,116 +101,24 @@ std::tuple<std::optional<ISOMetadata>, ExtractorErrorCode> validate(
}; };
} }
// TODO - remove code duplication, most of this is copying what happens in decompiler's `main.cpp` ExtractorErrorCode decompile(const fs::path& in_folder,
void decompile(const fs::path& iso_data_path, const std::string& data_subfolder,
const std::string& data_subfolder, const std::string& config_override) {
const std::string& config_override) {
using namespace decompiler;
// Determine which config to use from the database // Determine which config to use from the database
const auto version_info = get_version_info_or_default(iso_data_path); const auto version_info = get_version_info_or_default(in_folder);
Config config = read_config_file(file_util::get_jak_project_dir() / "decompiler" / "config" / decompiler::Config config = decompiler::read_config_file(
version_info.game_name / file_util::get_jak_project_dir() / "decompiler" / "config" / version_info.game_name /
fmt::format("{}_config.jsonc", version_info.game_name), fmt::format("{}_config.jsonc", version_info.game_name),
version_info.decomp_config_version, config_override); version_info.decomp_config_version, config_override);
std::vector<fs::path> dgos, objs, tex_strs, art_strs;
// grab all DGOS we need (level + common)
// TODO - Jak 2 - jak 1 specific code?
for (const auto& dgo_name : config.dgo_names) {
std::string common_name = "GAME.CGO";
if (dgo_name.length() > 3 && dgo_name.substr(dgo_name.length() - 3) == "DGO") {
// ends in DGO, it's a level
dgos.push_back(iso_data_path / dgo_name);
} else if (dgo_name.length() >= common_name.length() &&
dgo_name.substr(dgo_name.length() - common_name.length()) == common_name) {
// it's COMMON.CGO, we need that too.
dgos.push_back(iso_data_path / dgo_name);
}
}
// grab all the object files we need (just text)
for (const auto& obj_name : config.object_file_names) {
if (obj_name.length() > 3 && obj_name.substr(obj_name.length() - 3) == "TXT") {
// ends in TXT
objs.push_back(iso_data_path / obj_name);
}
}
for (const auto& str_name : config.str_texture_file_names) {
tex_strs.push_back(iso_data_path / str_name);
}
for (const auto& str_name : config.str_art_file_names) {
art_strs.push_back(iso_data_path / str_name);
}
// set up objects
ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, tex_strs, art_strs,
config);
// save object files
auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / data_subfolder; auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / data_subfolder;
auto raw_obj_folder = out_folder / "raw_obj";
file_util::create_dir_if_needed(raw_obj_folder);
db.dump_raw_objects(raw_obj_folder);
// analyze object file link data const auto result = run_decompilation_process(config, in_folder, out_folder, true);
db.process_link_data(config); if (result != 0) {
db.find_code(config); return ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR;
db.process_labels();
// ensure asset dir exists
file_util::create_dir_if_needed(out_folder / "assets");
// text files
{
auto result = db.process_game_text_files(config);
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_text.txt", result);
}
}
// textures
decompiler::TextureDB tex_db;
auto textures_out = out_folder / "textures";
auto dump_out = out_folder / "import";
file_util::create_dir_if_needed(textures_out);
file_util::write_text_file(textures_out / "tpage-dir.txt",
db.process_tpages(tex_db, textures_out, config, dump_out));
// texture merges
// TODO - put all this stuff in somewhere common
auto texture_merge_path = file_util::get_jak_project_dir() / "game" / "assets" /
game_version_names[config.game_version] / "texture_merges";
if (fs::exists(texture_merge_path)) {
tex_db.merge_textures(texture_merge_path);
}
// texture replacements
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
game_version_names[config.game_version] / "texture_replacements";
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}
// game count
{
auto result = db.process_game_count_file();
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_count.txt", result);
}
}
// levels
{
auto level_out_path =
file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3";
file_util::create_dir_if_needed(level_out_path);
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, level_out_path);
} }
return ExtractorErrorCode::SUCCESS;
} }
const std::unordered_map<std::string, GameIsoFlags> game_iso_flag_names = { const std::unordered_map<std::string, GameIsoFlags> game_iso_flag_names = {
@ -315,7 +222,6 @@ int main(int argc, char** argv) {
} }
// - SETUP // - SETUP
decompiler::init_opcode_info();
if (!project_path_override.empty()) { if (!project_path_override.empty()) {
if (!fs::exists(project_path_override)) { if (!fs::exists(project_path_override)) {
lg::error("Error: project path override '{}' does not exist", project_path_override.string()); lg::error("Error: project path override '{}' does not exist", project_path_override.string());
@ -426,7 +332,11 @@ int main(int argc, char** argv) {
// Get hash and file count // Get hash and file count
const auto [hash, file_count] = calculate_extraction_hash(iso_data_path); const auto [hash, file_count] = calculate_extraction_hash(iso_data_path);
// Validate // Validate
auto [version_info, code] = validate(iso_data_path, hash, file_count); auto [version_info, validate_code] = validate(iso_data_path, hash, file_count);
if (validate_code == ExtractorErrorCode::VALIDATION_BAD_EXTRACTION ||
(flag_fail_on_validation && validate_code != ExtractorErrorCode::SUCCESS)) {
return static_cast<int>(validate_code);
}
} }
// write out a json file with some metadata for the game // write out a json file with some metadata for the game
@ -455,7 +365,10 @@ int main(int argc, char** argv) {
if (flag_decompile) { if (flag_decompile) {
try { try {
decompile(iso_data_path, data_subfolder, decomp_config_override); const auto status_code = decompile(iso_data_path, data_subfolder, decomp_config_override);
if (status_code != ExtractorErrorCode::SUCCESS) {
return static_cast<int>(status_code);
}
} catch (std::exception& e) { } catch (std::exception& e) {
lg::error("Error during decompile: {}", e.what()); lg::error("Error during decompile: {}", e.what());
return static_cast<int>(ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR); return static_cast<int>(ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR);
@ -463,7 +376,10 @@ int main(int argc, char** argv) {
} }
if (flag_compile) { if (flag_compile) {
compile(iso_data_path, data_subfolder); const auto status_code = compile(iso_data_path, data_subfolder);
if (status_code != ExtractorErrorCode::SUCCESS) {
return static_cast<int>(status_code);
}
} }
if (flag_play) { if (flag_play) {

View File

@ -322,7 +322,8 @@ void extract_common(const ObjectFileDB& db,
compressed.data(), compressed.size()); compressed.data(), compressed.size());
if (config.rip_levels) { if (config.rip_levels) {
auto file_path = file_util::get_jak_project_dir() / "glb_out" / "common.glb"; auto file_path = file_util::get_jak_project_dir() / "glb_out" /
game_version_names[config.game_version] / "common.glb";
file_util::create_dir_if_needed_for_file(file_path); file_util::create_dir_if_needed_for_file(file_path);
save_level_foreground_as_gltf(tfrag_level, art_group_data, file_path); save_level_foreground_as_gltf(tfrag_level, art_group_data, file_path);
} }
@ -360,15 +361,17 @@ void extract_from_level(const ObjectFileDB& db,
if (config.rip_levels) { if (config.rip_levels) {
auto back_file_path = file_util::get_jak_project_dir() / "glb_out" / auto back_file_path = file_util::get_jak_project_dir() / "glb_out" /
fmt::format("{}_background.glb", level_data.level_name); game_version_names[config.game_version] /
fmt::format("{}-background.glb", level_data.level_name);
file_util::create_dir_if_needed_for_file(back_file_path); file_util::create_dir_if_needed_for_file(back_file_path);
save_level_background_as_gltf(level_data, back_file_path); save_level_background_as_gltf(level_data, back_file_path);
auto fore_file_path = file_util::get_jak_project_dir() / "glb_out" / auto fore_file_path = file_util::get_jak_project_dir() / "glb_out" /
fmt::format("{}_foreground.glb", level_data.level_name); game_version_names[config.game_version] /
fmt::format("{}-foreground.glb", level_data.level_name);
file_util::create_dir_if_needed_for_file(fore_file_path); file_util::create_dir_if_needed_for_file(fore_file_path);
save_level_foreground_as_gltf(level_data, art_group_data, fore_file_path); save_level_foreground_as_gltf(level_data, art_group_data, fore_file_path);
} }
file_util::write_text_file(entities_folder / fmt::format("{}_actors.json", level_data.level_name), file_util::write_text_file(entities_folder / fmt::format("{}-actors.json", level_data.level_name),
extract_actors_to_json(bsp_header.actors)); extract_actors_to_json(bsp_header.actors));
} }

View File

@ -1,5 +1,7 @@
#include "extract_merc.h" #include "extract_merc.h"
#include "merc_replacement.h"
#include "common/log/log.h" #include "common/log/log.h"
#include "common/util/BitUtils.h" #include "common/util/BitUtils.h"
#include "common/util/FileUtil.h" #include "common/util/FileUtil.h"
@ -1597,6 +1599,60 @@ void create_modifiable_vertex_data(
} }
} }
void replace_model(tfrag3::Level& lvl, tfrag3::MercModel& model, const fs::path& mdl_path) {
if (model.max_bones < 100) {
auto lvl_name = lvl.level_name == "" ? "common" : lvl.level_name;
lg::info("Replacing {} for {}: {} effects, {} max bones, {} max draws\n", model.name, lvl_name,
model.effects.size(), model.max_bones, model.max_draws);
std::vector<tfrag3::MercVertex> old_verts;
for (auto& e : model.effects) {
for (auto& d : e.all_draws) {
for (size_t i = 0; i < d.index_count; i++) {
auto idx = lvl.merc_data.indices.at(i + d.first_index);
if (idx != UINT32_MAX) {
old_verts.push_back(lvl.merc_data.vertices[idx]);
}
}
}
}
auto swap_info = load_replacement_merc_model(model.name, lvl.merc_data.indices.size(),
lvl.merc_data.vertices.size(), lvl.textures.size(),
mdl_path.string(), old_verts, false);
model = swap_info.new_model;
size_t old_start = lvl.merc_data.vertices.size();
for (auto& ind : swap_info.new_indices) {
ASSERT(ind >= old_start);
}
lvl.merc_data.indices.insert(lvl.merc_data.indices.end(), swap_info.new_indices.begin(),
swap_info.new_indices.end());
lvl.merc_data.vertices.insert(lvl.merc_data.vertices.end(), swap_info.new_vertices.begin(),
swap_info.new_vertices.end());
lvl.textures.insert(lvl.textures.end(), swap_info.new_textures.begin(),
swap_info.new_textures.end());
}
}
void add_custom_model_to_level(tfrag3::Level& lvl,
const std::string& name,
const fs::path& mdl_path) {
auto lvl_name = lvl.level_name == "" ? "common" : lvl.level_name;
lg::info("Adding custom model {} to {}", name, lvl_name);
auto merc_data =
load_replacement_merc_model(name, lvl.merc_data.indices.size(), lvl.merc_data.vertices.size(),
lvl.textures.size(), mdl_path.string(), {}, true);
for (auto& idx : merc_data.new_indices) {
lvl.merc_data.indices.push_back(idx);
}
for (auto& vert : merc_data.new_vertices) {
lvl.merc_data.vertices.push_back(vert);
}
lvl.merc_data.models.push_back(merc_data.new_model);
lvl.textures.insert(lvl.textures.end(), merc_data.new_textures.begin(),
merc_data.new_textures.end());
}
/*! /*!
* Top-level merc extraction * Top-level merc extraction
*/ */
@ -1731,5 +1787,33 @@ void extract_merc(const ObjectFileData& ag_data,
} }
} }
} }
// do model replacements if present
auto merc_replacement_folder = file_util::get_jak_project_dir() / "custom_assets" /
game_version_names[version] / "merc_replacements";
if (file_util::file_exists(merc_replacement_folder.string())) {
auto merc_replacements =
file_util::find_files_in_dir(merc_replacement_folder, std::regex(".*\\.glb"));
for (auto& path : merc_replacements) {
auto name = path.stem().string();
auto it = std::find_if(out.merc_data.models.begin(), out.merc_data.models.end(),
[&](const auto& m) { return m.name == name; });
if (it != out.merc_data.models.end()) {
auto& model = *it;
replace_model(out, model, path);
}
}
}
// add custom models if present
auto lvl_name = out.level_name == "" ? "common" : out.level_name;
auto models_folder = file_util::get_jak_project_dir() / "custom_assets" /
game_version_names[version] / "models" / lvl_name;
if (file_util::file_exists(models_folder.string())) {
auto custom_models = file_util::find_files_in_dir(models_folder, std::regex(".*\\.glb"));
for (auto& mdl : custom_models) {
add_custom_model_to_level(out, mdl.stem().string(), mdl);
}
}
} }
} // namespace decompiler } // namespace decompiler

View File

@ -0,0 +1,211 @@
#include "merc_replacement.h"
using namespace gltf_util;
namespace decompiler {
void extract(const std::string& name,
MercExtractData& out,
const tinygltf::Model& model,
const std::vector<NodeWithTransform>& all_nodes,
u32 index_offset,
u32 vertex_offset,
u32 tex_offset) {
ASSERT(out.new_vertices.empty());
std::map<int, tfrag3::MercDraw> draw_by_material;
int mesh_count = 0;
int prim_count = 0;
for (const auto& n : all_nodes) {
const auto& node = model.nodes[n.node_idx];
if (node.extras.Has("set_invisible") && node.extras.Get("set_invisible").Get<int>()) {
continue;
}
if (node.mesh >= 0) {
const auto& mesh = model.meshes[node.mesh];
mesh_count++;
for (const auto& prim : mesh.primitives) {
prim_count++;
// extract index buffer
std::vector<u32> prim_indices = gltf_util::gltf_index_buffer(
model, prim.indices, out.new_vertices.size() + vertex_offset);
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
// extract vertices
auto verts =
gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name);
out.new_vertices.insert(out.new_vertices.end(), verts.vtx.begin(), verts.vtx.end());
out.new_colors.insert(out.new_colors.end(), verts.vtx_colors.begin(),
verts.vtx_colors.end());
out.normals.insert(out.normals.end(), verts.normals.begin(), verts.normals.end());
ASSERT(out.new_colors.size() == out.new_vertices.size());
// TODO: just putting it all in one material
auto& draw = draw_by_material[prim.material];
draw.mode = gltf_util::make_default_draw_mode(); // todo rm
draw.tree_tex_id = 0; // todo rm
draw.num_triangles += prim_indices.size() / 3;
draw.no_strip = true;
draw.index_count = prim_indices.size();
draw.first_index = index_offset + out.new_indices.size();
out.new_indices.insert(out.new_indices.end(), prim_indices.begin(), prim_indices.end());
}
}
}
tfrag3::MercEffect e;
out.new_model.name = name;
out.new_model.max_bones = 120;
out.new_model.max_draws = 200;
for (const auto& [mat_idx, d_] : draw_by_material) {
e.all_draws.push_back(d_);
auto& draw = e.all_draws.back();
draw.mode = make_default_draw_mode();
if (mat_idx == -1) {
lg::warn("Draw had a material index of -1, using default texture.");
draw.tree_tex_id = 0;
continue;
}
const auto& mat = model.materials[mat_idx];
int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index;
if (tex_idx == -1) {
lg::warn("Material {} has no texture, using default texture.", mat.name);
draw.tree_tex_id = 0;
continue;
}
const auto& tex = model.textures[tex_idx];
ASSERT(tex.sampler >= 0);
ASSERT(tex.source >= 0);
draw.mode = draw_mode_from_sampler(model.samplers.at(tex.sampler));
const auto& img = model.images[tex.source];
draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img);
}
lg::info("total of {} unique materials", e.all_draws.size());
e.has_mod_draw = false;
out.new_model.effects.push_back(e);
out.new_model.effects.push_back(e);
out.new_model.effects.push_back(e);
out.new_model.effects.push_back(e);
lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count,
out.new_vertices.size());
}
const tfrag3::MercVertex& find_closest(const std::vector<tfrag3::MercVertex>& old,
float x,
float y,
float z) {
float best_dist = 1e10;
int best_idx = 0;
for (int i = 0; i < old.size(); i++) {
auto& v = old[i];
float dx = v.pos[0] - x;
float dy = v.pos[1] - y;
float dz = v.pos[2] - z;
float dist = (dx * dx) + (dy * dy) + (dz * dz);
if (dist < best_dist) {
best_dist = dist;
best_idx = i;
}
}
return old[best_idx];
}
void merc_convert_replacement(MercSwapData& out,
const MercExtractData& in,
const std::vector<tfrag3::MercVertex>& old_verts) {
out.new_model = in.new_model;
out.new_indices = in.new_indices;
out.new_textures = in.tex_pool.textures_by_idx;
// convert vertices
for (size_t i = 0; i < in.new_vertices.size(); i++) {
const auto& y = in.new_vertices[i];
const auto& copy_from = find_closest(old_verts, y.x, y.y, y.z);
auto& x = out.new_vertices.emplace_back();
x.pos[0] = y.x;
x.pos[1] = y.y;
x.pos[2] = y.z;
x.normal[0] = copy_from.normal[0];
x.normal[1] = copy_from.normal[1];
x.normal[2] = copy_from.normal[2];
x.weights[0] = copy_from.weights[0];
x.weights[1] = copy_from.weights[1];
x.weights[2] = copy_from.weights[2];
x.st[0] = y.s;
x.st[1] = y.t;
x.rgba[0] = in.new_colors[i][0];
x.rgba[1] = in.new_colors[i][1];
x.rgba[2] = in.new_colors[i][2];
x.rgba[3] = in.new_colors[i][3];
x.mats[0] = copy_from.mats[0];
x.mats[1] = copy_from.mats[1];
x.mats[2] = copy_from.mats[2];
}
}
void merc_convert_custom(MercSwapData& out, const MercExtractData& in) {
out.new_model = in.new_model;
out.new_indices = in.new_indices;
out.new_textures = in.tex_pool.textures_by_idx;
// convert vertices
for (size_t i = 0; i < in.new_vertices.size(); i++) {
const auto& y = in.new_vertices[i];
auto& x = out.new_vertices.emplace_back();
x.pos[0] = y.x;
x.pos[1] = y.y;
x.pos[2] = y.z;
x.normal[0] = in.normals.at(i).x();
x.normal[1] = in.normals.at(i).y();
x.normal[2] = in.normals.at(i).z();
x.weights[0] = 1.0f;
x.weights[1] = 0.0f;
x.weights[2] = 0.0f;
x.st[0] = y.s;
x.st[1] = y.t;
x.rgba[0] = in.new_colors[i][0];
x.rgba[1] = in.new_colors[i][1];
x.rgba[2] = in.new_colors[i][2];
x.rgba[3] = in.new_colors[i][3];
x.mats[0] = 3;
x.mats[1] = 0;
x.mats[2] = 0;
}
}
MercSwapData load_replacement_merc_model(const std::string& name,
u32 current_idx_count,
u32 current_vtx_count,
u32 current_tex_count,
const std::string& path,
const std::vector<tfrag3::MercVertex>& old_verts,
bool custom_mdl) {
MercSwapData result;
lg::info("Reading gltf mesh: {}", path);
tinygltf::TinyGLTF loader;
tinygltf::Model model;
std::string err, warn;
bool res = loader.LoadBinaryFromFile(&model, &err, &warn, path);
ASSERT_MSG(warn.empty(), warn.c_str());
ASSERT_MSG(err.empty(), err.c_str());
ASSERT_MSG(res, "Failed to load GLTF file!");
auto all_nodes = flatten_nodes_from_all_scenes(model);
MercExtractData extract_data;
extract(name, extract_data, model, all_nodes, current_idx_count, current_vtx_count,
current_tex_count);
if (custom_mdl) {
merc_convert_custom(result, extract_data);
} else {
merc_convert_replacement(result, extract_data, old_verts);
}
return result;
}
} // namespace decompiler

View File

@ -0,0 +1,32 @@
#pragma once
#include "common/log/log.h"
#include "common/util/gltf_util.h"
namespace decompiler {
struct MercExtractData {
gltf_util::TexturePool tex_pool;
std::vector<u32> new_indices;
std::vector<tfrag3::PreloadedVertex> new_vertices;
std::vector<math::Vector<u8, 4>> new_colors;
std::vector<math::Vector3f> normals;
tfrag3::MercModel new_model;
};
// Data produced by loading a replacement model
struct MercSwapData {
std::vector<u32> new_indices;
std::vector<tfrag3::MercVertex> new_vertices;
std::vector<tfrag3::Texture> new_textures;
tfrag3::MercModel new_model;
};
MercSwapData load_replacement_merc_model(const std::string& name,
u32 current_idx_count,
u32 current_vtx_count,
u32 current_tex_count,
const std::string& path,
const std::vector<tfrag3::MercVertex>& old_verts,
bool custom_mdl);
} // namespace decompiler

View File

@ -1,33 +1,15 @@
#include <cstdio>
#include <string> #include <string>
#include <vector>
#include "config.h" #include "config.h"
#include "decompilation_process.h"
#include "common/log/log.h" #include "common/log/log.h"
#include "common/util/FileUtil.h" #include "common/util/FileUtil.h"
#include "common/util/Timer.h"
#include "common/util/diff.h"
#include "common/util/os.h"
#include "common/util/set_util.h" #include "common/util/set_util.h"
#include "common/util/term_util.h" #include "common/util/term_util.h"
#include "common/util/unicode_util.h" #include "common/util/unicode_util.h"
#include "common/versions/versions.h"
#include "ObjectFile/ObjectFileDB.h"
#include "decompiler/data/TextureDB.h"
#include "decompiler/data/streamed_audio.h"
#include "decompiler/level_extractor/extract_level.h"
#include "third-party/CLI11.hpp" #include "third-party/CLI11.hpp"
#include "third-party/json.hpp"
template <typename... Args>
static void mem_log(const std::string& format, Args&&... args) {
#ifndef _WIN32
lg::info("[Mem] " + format, std::forward<Args>(args)...);
#endif
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
ArgumentGuard u8_guard(argc, argv); ArgumentGuard u8_guard(argc, argv);
@ -81,11 +63,9 @@ int main(int argc, char** argv) {
return 1; return 1;
} }
using namespace decompiler; decompiler::Config config;
Config config;
try { try {
config = read_config_file(config_path, config_game_version, config_override); config = decompiler::read_config_file(config_path, config_game_version, config_override);
} catch (const std::exception& e) { } catch (const std::exception& e) {
lg::error("Failed to parse config: {}", e.what()); lg::error("Failed to parse config: {}", e.what());
return 1; return 1;
@ -115,7 +95,7 @@ int main(int argc, char** argv) {
// Warning message if expected ELF isn't found, user could be using bad assets / didn't extract // Warning message if expected ELF isn't found, user could be using bad assets / didn't extract
// the ISO properly // the ISO properly
if (!config.expected_elf_name.empty() && !exists(in_folder / config.expected_elf_name)) { if (!config.expected_elf_name.empty() && !fs::exists(in_folder / config.expected_elf_name)) {
lg::error( lg::error(
"WARNING - '{}' does not contain the expected ELF file '{}'. Was the ISO extracted " "WARNING - '{}' does not contain the expected ELF file '{}'. Was the ISO extracted "
"properly or is there a version mismatch?", "properly or is there a version mismatch?",
@ -123,235 +103,5 @@ int main(int argc, char** argv) {
} }
// -- Begin the Decompilation! // -- Begin the Decompilation!
return run_decompilation_process(config, in_folder, out_folder, false);
Timer decomp_timer;
mem_log("Top of main: {} MB\n", get_peak_rss() / (1024 * 1024));
init_opcode_info();
mem_log("After init: {} MB\n", get_peak_rss() / (1024 * 1024));
std::vector<fs::path> dgos, objs, strs, tex_strs, art_strs;
for (const auto& dgo_name : config.dgo_names) {
dgos.push_back(in_folder / dgo_name);
}
for (const auto& obj_name : config.object_file_names) {
objs.push_back(in_folder / obj_name);
}
for (const auto& str_name : config.str_file_names) {
strs.push_back(in_folder / str_name);
}
for (const auto& str_name : config.str_texture_file_names) {
tex_strs.push_back(in_folder / str_name);
}
for (const auto& str_name : config.str_art_file_names) {
art_strs.push_back(in_folder / str_name);
}
mem_log("After config read: {} MB", get_peak_rss() / (1024 * 1024));
// build file database
lg::info("Setting up object file DB...");
ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, strs, tex_strs, art_strs,
config);
// Explicitly fail if a file in the 'allowed_objects' list wasn't found in the DB
// as this is another silent error that can be confusing
if (!config.allowed_objects.empty()) {
for (const auto& expected_obj : config.allowed_objects) {
if (db.obj_files_by_name.count(expected_obj) == 0) {
// TODO - this is wrong for jak1, fix eventually as this is now done in 3 places
lg::error(
"Expected to find '{}' in the ObjectFileDB but did not. Check "
"./decompiler/config/{}/inputs.jsonc",
expected_obj, config.game_name);
return 1;
}
}
}
mem_log("After DB setup: {} MB", get_peak_rss() / (1024 * 1024));
// write out DGO file info
file_util::write_text_file(out_folder / "dgo.txt", db.generate_dgo_listing());
// write out object file map (used for future decompilations, if desired)
file_util::write_text_file(out_folder / "obj.txt",
db.generate_obj_listing(config.merged_objects));
// dump raw objs
if (config.dump_objs) {
auto path = out_folder / "raw_obj";
file_util::create_dir_if_needed(path);
db.dump_raw_objects(path);
}
// process files (required for all analysis)
db.process_link_data(config);
mem_log("After link data: {} MB", get_peak_rss() / (1024 * 1024));
db.find_code(config);
db.process_labels();
mem_log("After code: {} MB", get_peak_rss() / (1024 * 1024));
// top level decompile (do this before printing asm so we get function names)
if (config.find_functions) {
db.ir2_top_level_pass(config);
}
// print disassembly
if (config.disassemble_code || config.disassemble_data) {
db.write_disassembly(out_folder, config.disassemble_data, config.disassemble_code,
config.write_hex_near_instructions);
}
if (config.process_art_groups) {
db.extract_art_info();
// dump art info to json if requested
if (config.dump_art_group_info) {
auto ag_file_name = out_folder / "dump" / "art-group-info.min.json";
nlohmann::json ag_json = db.dts.art_group_info;
file_util::create_dir_if_needed_for_file(ag_file_name);
file_util::write_text_file(ag_file_name, ag_json.dump(-1));
lg::info("[DUMP] Dumped art group info to {}", ag_file_name.string());
}
if (config.dump_joint_geo_info) {
auto jg_file_name = out_folder / "dump" / "joint-node-info.min.json";
nlohmann::json jg_json = db.dts.jg_info;
file_util::create_dir_if_needed_for_file(jg_file_name);
file_util::write_text_file(jg_file_name, jg_json.dump(-1));
lg::info("[DUMP] Dumped joint node info to {}", jg_file_name.string());
}
} else if (!config.art_group_info_dump.empty() || !config.jg_info_dump.empty()) {
// process art groups (used in decompilation)
// - if the config has a path to the art info dump, just use that
// - otherwise (or if we want to dump it fresh) extract it
if (!config.art_group_info_dump.empty()) {
db.dts.art_group_info = config.art_group_info_dump;
}
if (!config.jg_info_dump.empty()) {
db.dts.jg_info = config.jg_info_dump;
}
} else {
lg::error("`process_art_groups` was false and no art-group-info dump was provided!");
return 1;
}
if (config.process_tpages && !config.texture_info_dump.empty()) {
db.dts.textures = config.texture_info_dump;
}
// main decompile.
if (config.decompile_code) {
db.analyze_functions_ir2(out_folder, config, {}, {}, {});
}
if (config.generate_all_types) {
ASSERT_MSG(config.decompile_code, "Must decompile code to generate all-types");
db.ir2_analyze_all_types(out_folder / "new-all-types.gc", config.old_all_types_file,
config.hacks.types_with_bad_inspect_methods);
}
mem_log("After decomp: {} MB", get_peak_rss() / (1024 * 1024));
// write out all symbols
file_util::write_text_file(out_folder / "all-syms.gc", db.dts.dump_symbol_types());
// write art groups
if (config.process_art_groups) {
db.dump_art_info(out_folder);
}
if (config.hexdump_code || config.hexdump_data) {
db.write_object_file_words(out_folder, config.hexdump_data, config.hexdump_code);
}
// data stuff
if (config.write_scripts) {
db.find_and_write_scripts(out_folder);
}
if (config.process_game_text) {
auto result = db.process_game_text_files(config);
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_text.txt", result);
}
}
mem_log("After text: {} MB", get_peak_rss() / (1024 * 1024));
if (config.process_subtitle_text || config.process_subtitle_images) {
auto result = db.process_all_spool_subtitles(
config, config.process_subtitle_images ? out_folder / "assets" / "subtitle-images" : "");
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_subs.txt", result);
}
}
mem_log("After spool handling: {} MB", get_peak_rss() / (1024 * 1024));
TextureDB tex_db;
if (config.process_tpages || config.levels_extract) {
auto textures_out = out_folder / "textures";
auto dump_out = out_folder / "import";
file_util::create_dir_if_needed(textures_out);
auto result = db.process_tpages(tex_db, textures_out, config, dump_out);
if (!result.empty() && config.process_tpages) {
file_util::write_text_file(textures_out / "tpage-dir.txt", result);
file_util::write_text_file(textures_out / "tex-remap.txt",
tex_db.generate_texture_dest_adjustment_table());
}
if (config.dump_tex_info) {
auto texture_file_name = out_folder / "dump" / "tex-info.min.json";
nlohmann::json texture_json = db.dts.textures;
file_util::create_dir_if_needed_for_file(texture_file_name);
file_util::write_text_file(texture_file_name, texture_json.dump(-1));
lg::info("[DUMP] Dumped texture info to {}", texture_file_name.string());
}
}
mem_log("After textures: {} MB", get_peak_rss() / (1024 * 1024));
// Merge textures before replacing them, in other words, replacements take priority
auto texture_merge_path = file_util::get_jak_project_dir() / "game" / "assets" /
game_version_names[config.game_version] / "texture_merges";
if (fs::exists(texture_merge_path)) {
tex_db.merge_textures(texture_merge_path);
}
auto replacements_path = file_util::get_jak_project_dir() / "custom_assets" /
game_version_names[config.game_version] / "texture_replacements";
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}
if (config.process_game_count) {
auto result = db.process_game_count_file();
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_count.txt", result);
}
}
if (config.levels_extract) {
auto level_out_path =
file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3";
file_util::create_dir_if_needed(level_out_path);
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, level_out_path);
}
mem_log("After extraction: {} MB", get_peak_rss() / (1024 * 1024));
if (!config.audio_dir_file_name.empty()) {
auto streaming_audio_in = in_folder / "VAG";
auto streaming_audio_out = out_folder / "assets" / "streaming_audio";
file_util::create_dir_if_needed(streaming_audio_out);
process_streamed_audio(config, streaming_audio_out, in_folder,
config.streamed_audio_file_names);
}
lg::info("Decompiler has finished successfully in {:.2f} seconds.", decomp_timer.getSeconds());
return 0;
} }

View File

@ -2576,7 +2576,6 @@ void CallOp::propagate_types2(types2::Instruction& instr,
if (can_use_call_parent) { if (can_use_call_parent) {
out_types[Register(Reg::GPR, Reg::V0)]->type = TP_Type::make_from_ts(call_parent_result_type); out_types[Register(Reg::GPR, Reg::V0)]->type = TP_Type::make_from_ts(call_parent_result_type);
lg::print("used special {}\n", call_parent_result_type.print());
use_normal_last_arg = false; use_normal_last_arg = false;
} }
} }

View File

@ -11,7 +11,7 @@
- This file generates no code. - This file generates no code.
## `gkernel-h`: **Done** ## `gkernel-h`: **Done**
- The types `cpu-thread` and `catch-frame` have a slightly different layout in OpenGOAL to back up x86-64 registers - The types `cpu-thread` and `catch-frame` have a slightly different layout in OpenGOAL to back up x86-64 registers.
## `gkernel`: ## `gkernel`:
- Many changes for x86-64/OpenGOAL - Many changes for x86-64/OpenGOAL
@ -57,7 +57,7 @@
- No comments - No comments
## `euler-h`: **Done** ## `euler-h`: **Done**
- Uses boxed arrays - Uses boxed arrays.
## `transform-h`: **Done** ## `transform-h`: **Done**
- No comments - No comments
@ -69,7 +69,7 @@
- Empty - Empty
## `transformq-h`: waiting on stack stuff ## `transformq-h`: waiting on stack stuff
- Needs stack stuff - Needs stack stuff.
## `bounding-box`: asm ## `bounding-box`: asm
@ -84,7 +84,7 @@
## `geometry`: asm ## `geometry`: asm
## `trigonometry`: **Done** ## `trigonometry`: **Done**
- `sincos!` and `sincos-rad!` have a bug where cosine is slightly off - `sincos!` and `sincos-rad!` have a bug where cosine is slightly off.
## `gsound-h`: **Done** ## `gsound-h`: **Done**
@ -116,19 +116,19 @@
## `dma-disasm`: In progress ## `dma-disasm`: In progress
- Unused, but possibly useful debugging utilities for printing DMA chains. - Unused, but possibly useful debugging utilities for printing DMA chains.
- Needs stack stuff to do the last two functions - Needs stack stuff to do the last two functions.
- Needs static data disassembler for a lookup table. - Needs static data disassembler for a lookup table.
## `pad`: **Done** ## `pad`: **Done**
## `gs`: **Done** ## `gs`: **Done**
- Missing bitfields - Missing bitfields.
## `display-h`: **Done** ## `display-h`: **Done**
## `vector`: asm ## `vector`: asm
- Largely decompiled successfully and compiles! - Largely decompiled successfully and compiles!
- Functions are currently undocumented and still with rough variable names - Functions are currently undocumented and still with rough variable names.
- Some functions are currently failing to decompile: - Some functions are currently failing to decompile:
- `rand-vu-sphere-point!` - `rand-vu-sphere-point!`
- `vector-deg-lerp-clamp!` - `vector-deg-lerp-clamp!`

View File

@ -201,6 +201,7 @@ set(RUNTIME_SOURCE
mips2c/jak3_functions/sky.cpp mips2c/jak3_functions/sky.cpp
mips2c/jak3_functions/texture.cpp mips2c/jak3_functions/texture.cpp
mips2c/jak3_functions/particle_curves.cpp mips2c/jak3_functions/particle_curves.cpp
mips2c/jak3_functions/cloth.cpp
mips2c/jak3_functions/collide_cache.cpp mips2c/jak3_functions/collide_cache.cpp
mips2c/jak3_functions/collide_hash.cpp mips2c/jak3_functions/collide_hash.cpp
mips2c/jak3_functions/collide_edge_grab.cpp mips2c/jak3_functions/collide_edge_grab.cpp
@ -254,6 +255,29 @@ set(RUNTIME_SOURCE
overlord/jak2/streamlfo.cpp overlord/jak2/streamlfo.cpp
overlord/jak2/streamlist.cpp overlord/jak2/streamlist.cpp
overlord/jak2/vag.cpp overlord/jak2/vag.cpp
overlord/jak3/overlord.cpp
overlord/jak3/pagemanager.cpp
overlord/jak3/iso_cd.cpp
overlord/jak3/dma.cpp
overlord/jak3/iso.cpp
overlord/jak3/iso_queue.cpp
overlord/jak3/srpc.cpp
overlord/jak3/vag.cpp
overlord/jak3/ssound.cpp
overlord/jak3/iso_api.cpp
overlord/jak3/spustreams.cpp
overlord/jak3/list.cpp
overlord/jak3/vblank_handler.cpp
overlord/jak3/dvd_driver.cpp
overlord/jak3/basefile.cpp
overlord/jak3/basefilesystem.cpp
overlord/jak3/ramdisk.cpp
overlord/jak3/isocommon.cpp
overlord/jak3/init.cpp
overlord/jak3/stream.cpp
overlord/jak3/sbank.cpp
overlord/jak3/soundcommon.cpp
overlord/jak3/streamlist.cpp
runtime.cpp runtime.cpp
sce/deci2.cpp sce/deci2.cpp
sce/iop.cpp sce/iop.cpp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -84,9 +84,9 @@
"AGORA VÃO PARA A ILHA DAS NÉVOAS, EU VOU ENVIAR O ZOOMER PARA LÁ." "AGORA VÃO PARA A ILHA DAS NÉVOAS, EU VOU ENVIAR O ZOOMER PARA LÁ."
], ],
"assistant-village2-introduction": [ "assistant-village2-introduction": [
"WHOA!", "UAU!",
"ACHO QUE NUNCA VOU ME ACOSTUMAR COM ESSE FORMIGAMENTO DO TELEPORTE.", "ACHO QUE NUNCA VOU ME ACOSTUMAR COM ESSE FORMIGAMENTO DO TELEPORTE.",
"HEY!", "EI!",
"PARECE QUE O SÁBIO AZUL DEU UMA FESTA.", "PARECE QUE O SÁBIO AZUL DEU UMA FESTA.",
"MINHA NOSSA! VILA DA PEDRA ESTÁ EM CHAMAS!", "MINHA NOSSA! VILA DA PEDRA ESTÁ EM CHAMAS!",
"UMA BELA FESTA.", "UMA BELA FESTA.",
@ -200,8 +200,8 @@
"bird-lady-beach-resolution": [ "bird-lady-beach-resolution": [
"NOSSA!, EU ESPERO QUE O COITADINHO ESTEJA BEM,", "NOSSA!, EU ESPERO QUE O COITADINHO ESTEJA BEM,",
"TOMA, UMA POWER CELL PELA SUA CORAGEM.", "TOMA, UMA POWER CELL PELA SUA CORAGEM.",
"MAMA!", "MAMÃE!",
"MAMA!", "MAMÃE!",
"OH NÃO! NÃO, NÃO, NÃO, NÃO!", "OH NÃO! NÃO, NÃO, NÃO, NÃO!",
"OLHA SÓ... NÃO É FOFO? ELE ACHA QUE VOCÊ É A MÃE DELE.", "OLHA SÓ... NÃO É FOFO? ELE ACHA QUE VOCÊ É A MÃE DELE.",
"EH? EU NÃO SOU TUA MÃE! TÁ VENDO ALGUMA PENA AQUI?", "EH? EU NÃO SOU TUA MÃE! TÁ VENDO ALGUMA PENA AQUI?",
@ -290,14 +290,14 @@
"BONS SONHOS, JAK!" "BONS SONHOS, JAK!"
], ],
"evilbro-misty-end": [ "evilbro-misty-end": [
"THOSE TWO COULD PROVE TO BE TROUBLE.", "ESSES DOIS PODEM SE TORNAR UM PROBLEMA.",
"DON'T WORRY DEAR BROTHER, I'LL DISPATCH MY LURKER ARMY TO DEAL WITH THEM.", "NÃO SE PREOCUPE, QUERIDO IRMÃO, ENVIAREI MINHA TROPA DE LURKERS PARA LIDAR COM ELES.",
"JUST MAKE SURE THEY DON'T GET TOO CLOSE TO OUR NORTHERN OPERATIONS.", "SÓ GARANTA QUE ELES NÃO VÃO CHEGAR PERTO DEMAIS DAS OPERAÇÕES AO NORTE.",
"I DOUBT THEY'LL GET THAT FAR...", "EU DUVIDO QUE ELES CHEGUEM TÃO LONGE...",
"BUT IF THEY DO, A SURPRISE WILL BE WAITING FOR THEM IN ROCK VILLAGE.", "MAS SE CHEGAREM, TERÃO UMA SURPRESA NA VILA DA PEDRA.",
"SO...YOU'RE GOING TO LET...KLAWW OUT OF HIS CAGE?", "ENTÃO... VOCÊ VAI DEIXAR... O KLAWW SAIR DA GAIOLA?",
"I THINK IT'S WORTH THE RISK.", "ACHO QUE VALE O RISCO.",
"INDEED..." "DE FATO..."
], ],
"explorer-introduction": [ "explorer-introduction": [
"OLÁ MEU QUERIDO MENINO.", "OLÁ MEU QUERIDO MENINO.",
@ -486,7 +486,7 @@
"PARECE QUE ELES COMBINARAM PARTES FUNCIONAIS DO ROBÔ PRECURSOR", "PARECE QUE ELES COMBINARAM PARTES FUNCIONAIS DO ROBÔ PRECURSOR",
"COM ARTEFATOS RECOLHIDOS PELO MUNDO.", "COM ARTEFATOS RECOLHIDOS PELO MUNDO.",
"E ELES ADICIONARAM MECANISMOS DIABÓLICOS PRÓPRIOS,", "E ELES ADICIONARAM MECANISMOS DIABÓLICOS PRÓPRIOS,",
"CRIANDO ALGO CAPAZ DE ABRIR OS RESERVATÓRIOS DE ECO NEGRO.", "CRIANDO ALGO CAPAZ DE ABRIR OS RESERVATÓRIOS DE ECO SOMBRIO.",
"SE VOCÊS PUDEREM LIBERTAR NÓS 4, PODEMOS USAR NOSSOS PODERES COMBINADOS", "SE VOCÊS PUDEREM LIBERTAR NÓS 4, PODEMOS USAR NOSSOS PODERES COMBINADOS",
"PARA QUEBRAR O CAMPO DE FORÇA EM VOLTA DO ROBÔ,", "PARA QUEBRAR O CAMPO DE FORÇA EM VOLTA DO ROBÔ,",
"ANTES QUE ELES O USEM PARA DESTRUIR O MUNDO." "ANTES QUE ELES O USEM PARA DESTRUIR O MUNDO."
@ -507,7 +507,7 @@
"NADA DISSO SERIA POSSÍVEL.", "NADA DISSO SERIA POSSÍVEL.",
"HM-HM-HM. TALVEZ TENHAMOS ACHADO UMA NOVA SÁBIA AGORA", "HM-HM-HM. TALVEZ TENHAMOS ACHADO UMA NOVA SÁBIA AGORA",
"QUE GOL E MAIA SE FORAM...", "QUE GOL E MAIA SE FORAM...",
"SIM, GOL E MAIA... O ECO NEGRO PROVAVELMENTE DESTRUIU ELES.", "SIM, GOL E MAIA... O ECO SOMBRIO PROVAVELMENTE DESTRUIU ELES.",
"EH, PROVAVELMENTE...", "EH, PROVAVELMENTE...",
"QUEM LIGA? DEIXA VIR, A GENTE APAGA ELES DE NOVO NÉ, JAK?", "QUEM LIGA? DEIXA VIR, A GENTE APAGA ELES DE NOVO NÉ, JAK?",
"EU DISSE, NÉ, JAK?", "EU DISSE, NÉ, JAK?",
@ -532,10 +532,10 @@
], ],
"green-sagecage-outro-preboss": [ "green-sagecage-outro-preboss": [
"TARDE DEMAIS, SAMOS.", "TARDE DEMAIS, SAMOS.",
"ASSIM QUE EU TIVER ECO NEGRO ILIMITADO,", "ASSIM QUE EU TIVER ECO SOMBRIO ILIMITADO,",
"EU TEREI A CHAVE PARA A PRÓPRIA CRIAÇÃO!", "EU TEREI A CHAVE PARA A PRÓPRIA CRIAÇÃO!",
"ISSO É LOUCURA!", "ISSO É LOUCURA!",
"LIBERAR AQUELE TANTO DE ECO NEGRO IRÁ DESTRUIR TUDO QUE CONHECEMOS!", "LIBERAR AQUELE TANTO DE ECO SOMBRIO IRÁ DESTRUIR TUDO QUE CONHECEMOS!",
"OLHE O QUE ELE FEZ COM VOCÊ!", "OLHE O QUE ELE FEZ COM VOCÊ!",
"NOS TROUXE BELEZA ALÉM DA SUA COMPREENSÃO.", "NOS TROUXE BELEZA ALÉM DA SUA COMPREENSÃO.",
"BELEZA? VOCÊS SE OLHARAM NO ESPELHO RECENTEMENTE?", "BELEZA? VOCÊS SE OLHARAM NO ESPELHO RECENTEMENTE?",
@ -651,7 +651,7 @@
"WILLARD!!" "WILLARD!!"
], ],
"minershort-reminder-2-orbs": [ "minershort-reminder-2-orbs": [
"DUH, GORDY?", ", GORDY?",
"90 ORBES POR UMA POWER CELL, WILLARD! URGH!" "90 ORBES POR UMA POWER CELL, WILLARD! URGH!"
], ],
"minershort-resolution-1-orbs": [ "minershort-resolution-1-orbs": [
@ -730,7 +730,7 @@
"BEM, A SITUAÇÃO AQUI TÁ FEDENDO MAIS QUE SOVACO DE LURKER.", "BEM, A SITUAÇÃO AQUI TÁ FEDENDO MAIS QUE SOVACO DE LURKER.",
"ANTES DO SUMIÇO DO SÁBIO AZUL,", "ANTES DO SUMIÇO DO SÁBIO AZUL,",
"ELE REGISTROU PROBLEMAS SIGNIFICATIVOS EM TODA REGIÃO EM VOLTA.", "ELE REGISTROU PROBLEMAS SIGNIFICATIVOS EM TODA REGIÃO EM VOLTA.",
"DE INTERESSE PARTICULAR MEU, ESTÁ A INFESTAÇÃO DE ECO NEGRO", "DE INTERESSE PARTICULAR MEU, ESTÁ A INFECÇÃO DE ECO SOMBRIO",
"EM PLANTAS INOCENTES NA BACIA PRECURSOR.", "EM PLANTAS INOCENTES NA BACIA PRECURSOR.",
"KEIRA IRÁ TELEPORTAR O ZOOMER PARA O TRANS-PAD MAIS PRÓXIMO.", "KEIRA IRÁ TELEPORTAR O ZOOMER PARA O TRANS-PAD MAIS PRÓXIMO.",
"LEVE O ZOOMER PARA UMA FONTE DE ECO VERDE,", "LEVE O ZOOMER PARA UMA FONTE DE ECO VERDE,",
@ -754,7 +754,7 @@
"sage-bluehut-reminder-1-crop-dusting": [ "sage-bluehut-reminder-1-crop-dusting": [
"MEUS OLHOS DEVEM ESTAR ME ENGANANDO!", "MEUS OLHOS DEVEM ESTAR ME ENGANANDO!",
"PORQUE VOCÊS DOIS COM CERTEZA ESTÃO NA BACIA PRECURSOR,", "PORQUE VOCÊS DOIS COM CERTEZA ESTÃO NA BACIA PRECURSOR,",
"SALVANDO AS PLANTAS INFECTADAS COM ECO NEGRO." "SALVANDO AS PLANTAS INFECTADAS COM ECO SOMBRIO."
], ],
"sage-bluehut-reminder-1-prec-arm": [ "sage-bluehut-reminder-1-prec-arm": [
"O QUE VOCÊS ESTÃO FAZENDO VOLTANDO AQUI?", "O QUE VOCÊS ESTÃO FAZENDO VOLTANDO AQUI?",
@ -793,14 +793,14 @@
"NO ÚNICO LUGAR QUE EU DISSE PARA NÃO IREM: ILHA DAS NÉVOAS!", "NO ÚNICO LUGAR QUE EU DISSE PARA NÃO IREM: ILHA DAS NÉVOAS!",
"FOI ISSO MESMO! E ENTÃ-", "FOI ISSO MESMO! E ENTÃ-",
"E ENTÃO, DAXTER, VOCÊ FINALMENTE TOMOU UM BANHO,", "E ENTÃO, DAXTER, VOCÊ FINALMENTE TOMOU UM BANHO,",
"MAS FOI EM UM POÇO DE ECO NEGRO.", "MAS FOI EM UM POÇO DE ECO SOMBRIO.",
"ESCUTA AQUI, COROA, VOCÊ VAI FICAR DANDO ESPORRO,", "ESCUTA AQUI, COROA, VOCÊ VAI FICAR DANDO ESPORRO,",
"OU VOCÊ VAI ME AJUDAR A SAIR DESSA?", "OU VOCÊ VAI ME AJUDAR A SAIR DESSA?",
"VOU FICAR DANDO ESPORRO! PORQUE NA MINHA OPINIÃO PROFISSIONAL,", "VOU FICAR DANDO ESPORRO! PORQUE NA MINHA OPINIÃO PROFISSIONAL,",
"A MUDANÇA FOI UMA MELHORIA.", "A MUDANÇA FOI UMA MELHORIA.",
"E ALÉM DISSO... EU NÃO PODERIA TE AJUDAR MESMO SE QUISESSE", "E ALÉM DISSO... EU NÃO PODERIA TE AJUDAR MESMO SE QUISESSE",
"QUÊ!?", "QUÊ!?",
"SÓ EXISTE UMA PESSOA QUE ESTUDOU O ECO NEGRO POR TEMPO O BASTANTE", "SÓ EXISTE UMA PESSOA QUE ESTUDOU O ECO SOMBRIO POR TEMPO O BASTANTE",
"PARA TER UMA CHANCE DE TE TRAZER A SUA FORMA ANTIGA:", "PARA TER UMA CHANCE DE TE TRAZER A SUA FORMA ANTIGA:",
"GOL ACHERON, O SÁBIO.", "GOL ACHERON, O SÁBIO.",
"MAS ELE VIVE AO NORTE. LONGE, BEM LONGE AO NORTE.", "MAS ELE VIVE AO NORTE. LONGE, BEM LONGE AO NORTE.",
@ -832,7 +832,7 @@
"VOCÊS DOIS NÃO CONSEGUIRIAM NEM SAIR DA VILA SEM TREINAMENTO", "VOCÊS DOIS NÃO CONSEGUIRIAM NEM SAIR DA VILA SEM TREINAMENTO",
"ANTES DE QUALQUER COISA, VOCÊS VÃO ENTRAR NO TELEPORTADOR", "ANTES DE QUALQUER COISA, VOCÊS VÃO ENTRAR NO TELEPORTADOR",
"E TREINAR NA GEYSER ROCK .", "E TREINAR NA GEYSER ROCK .",
"UH, NÃO VAI TER NENHUM ECO NEGRO GOSMENTO LÁ NÉ?", "UH, NÃO VAI TER NENHUM ECO SOMBRIO GOSMENTO LÁ NÉ?",
"PORQUE EU ODIARIA CAIR NELE DE NOVO E TRANSFORMAR EM VOCÊ!", "PORQUE EU ODIARIA CAIR NELE DE NOVO E TRANSFORMAR EM VOCÊ!",
"VÃO LÁ AGORA! ANTES QUE EU TRANSFORME VOCÊS DOIS EM SAMAMBAIAS" "VÃO LÁ AGORA! ANTES QUE EU TRANSFORME VOCÊS DOIS EM SAMAMBAIAS"
], ],
@ -854,7 +854,7 @@
"ÓTIMO, EU QUERIA FALAR COM VOCÊS SOBRE ALGO SÉRIO.", "ÓTIMO, EU QUERIA FALAR COM VOCÊS SOBRE ALGO SÉRIO.",
"PARECE HAVER BASTANTE CIRCULAÇÃO DE LURKERS NA ILHA DAS NÉVOAS.", "PARECE HAVER BASTANTE CIRCULAÇÃO DE LURKERS NA ILHA DAS NÉVOAS.",
"EU CONSIGO VÊ-LOS BOMBARDEANDO O ARMAZÉM PRECURSOR PELA MINHA TORRE DE VIGIA.", "EU CONSIGO VÊ-LOS BOMBARDEANDO O ARMAZÉM PRECURSOR PELA MINHA TORRE DE VIGIA.",
"SE OS LURKERS CONSEGUIREM ABRIR E LIBERAR O ECO NEGRO, NÓS PODEMOS", "SE OS LURKERS CONSEGUIREM ABRIR E LIBERAR O ECO SOMBRIO, NÓS PODEMOS",
"ACABAR TENDO QUE ANDAR POR AÍ IGUAL A ESSE PEQUENO ESPÉCIME.", "ACABAR TENDO QUE ANDAR POR AÍ IGUAL A ESSE PEQUENO ESPÉCIME.",
"JAK, É HORA DE VOCÊ PROVAR O SEU VALOR.", "JAK, É HORA DE VOCÊ PROVAR O SEU VALOR.",
"PEÇA EMPRESTADO O BARCO DO PESCADOR PARA IR À ILHA DAS NÉVOAS", "PEÇA EMPRESTADO O BARCO DO PESCADOR PARA IR À ILHA DAS NÉVOAS",
@ -895,22 +895,22 @@
"NÃO FOI NEM UM POUCO DIVERTIDO.", "NÃO FOI NEM UM POUCO DIVERTIDO.",
"GOL? É VOCÊ?", "GOL? É VOCÊ?",
"VOCÊ FINALMENTE CHEGOU NO FUNDO DO POÇO, EH?", "VOCÊ FINALMENTE CHEGOU NO FUNDO DO POÇO, EH?",
"E, MAIA! EU DISSE QUE O ECO NEGRO IRIA AFETAR VOCÊS DOIS!", "E, MAIA! EU DISSE QUE O ECO SOMBRIO AFETARIA VOCÊS DOIS!",
"HNG, NINGUÉM DÁ OUVIDOS AO VELHO SAMOS...", "HNG, NINGUÉM DÁ OUVIDOS AO VELHO SAMOS...",
"O QUE VOCÊS FIZERAM COM OS SÁBIOS AZUL E VERMELHO?", "O QUE VOCÊS FIZERAM COM OS SÁBIOS AZUL E VERMELHO?",
"NÃO SE PREOCUPE COM SEUS AMIGOS COLORIDOS, SEU VELHO TOLO.", "NÃO SE PREOCUPE COM SEUS AMIGOS COLORIDOS, SEU VELHO TOLO.",
"ELES ESTÃO PERFEITAMENTE SEGUROS NA NOSSA CIDADELA. SÃO NOSSOS CONVIDADOS ESPECIAIS.", "ELES ESTÃO PERFEITAMENTE SEGUROS NA NOSSA CIDADELA. SÃO NOSSOS CONVIDADOS ESPECIAIS.",
"ELES GRACIOSAMENTE CONCORDARAM EM NOS AJUDAR EM NOSSO PEQUENO PROJETO.", "ELES GRACIOSAMENTE CONCORDARAM EM NOS AJUDAR EM NOSSO PEQUENO PROJETO.",
"VOCÊ ESTAVA ERRADO, SAMOS. O ECO NEGRO PODE SER CONTROLADO!", "VOCÊ ESTAVA ERRADO, SAMOS. O ECO SOMBRIO PODE SER CONTROLADO!",
"NÓS DESCOBRIMOS SEUS SEGREDOS, E AGORA PODEMOS REFAZER O MUNDO AO NOSSO GOSTO.", "NÓS DESCOBRIMOS SEUS SEGREDOS, E AGORA PODEMOS REFAZER O MUNDO AO NOSSO GOSTO.",
"NÃO SE PODE CONTROLAR O ECO NEGRO POR CONTA PRÓPRIA, NEM OS PRECURSORES CONSEGUIAM", "NÃO SE PODE CONTROLAR O ECO SOMBRIO POR CONTA PRÓPRIA, NEM OS PRECURSORES CONSEGUIAM",
"ATÉ AGORA, TIVEMOS QUE NOS VIRAR COM O", "ATÉ AGORA, TIVEMOS QUE NOS VIRAR COM O",
"POUCO QUE ACHÁVAMOS NA SUPERFÍCIE.", "POUCO QUE ACHÁVAMOS NA SUPERFÍCIE.",
"MAS EM BREVE, TEREMOS ACESSO AS VASTAS RESERVAS", "MAS EM BREVE, TEREMOS ACESSO AS VASTAS RESERVAS",
"DE ECO NEGRO ESCONDIDA NO FUNDO DO SUBSOLO.", "DE ECO SOMBRIO ESCONDIDA NO FUNDO DO SUBSOLO.",
"NÃO OS RESERVATÓRIOS!", "NÃO OS RESERVATÓRIOS!",
"SIM, OS RESERVATÓRIOS!", "SIM, OS RESERVATÓRIOS!",
"ELES SERÃO ABERTOS, E TODO ECO NEGRO DO MUNDO SERÁ NOSSO!", "ELES SERÃO ABERTOS, E TODO ECO SOMBRIO DO MUNDO SERÁ NOSSO!",
"MAS ISSO É IMPOSSÍVEL! APENAS UM ROBÔ PRECURSOR PODERIA-", "MAS ISSO É IMPOSSÍVEL! APENAS UM ROBÔ PRECURSOR PODERIA-",
"OH, NÃO FIQUE TÃO CHATEADO, SAMOS.", "OH, NÃO FIQUE TÃO CHATEADO, SAMOS.",
"TEMOS GRANDES PLANOS PARA VOCÊ.", "TEMOS GRANDES PLANOS PARA VOCÊ.",
@ -921,7 +921,7 @@
"GOL É O CARA QUE TÁ TENTANDO NOS MATAR?!", "GOL É O CARA QUE TÁ TENTANDO NOS MATAR?!",
"TÔ PERDIDO.", "TÔ PERDIDO.",
"TALVEZ ESTEJAMOS TODOS PERDIDOS.", "TALVEZ ESTEJAMOS TODOS PERDIDOS.",
"SE ELES ABRIREM OS RESERVATÓRIOS, O ECO NEGRO IRÁ", "SE ELES ABRIREM OS RESERVATÓRIOS, O ECO SOMBRIO IRÁ",
"CORROMPER E DESTRUIR TUDO QUE TOCAR!", "CORROMPER E DESTRUIR TUDO QUE TOCAR!",
"NÓS PRECISAMOS IR À CIDADELA E IMPEDI-LOS!", "NÓS PRECISAMOS IR À CIDADELA E IMPEDI-LOS!",
"O CAMINHO MAIS RÁPIDO ATÉ LÁ É PELO TÚNEL DE LAVA", "O CAMINHO MAIS RÁPIDO ATÉ LÁ É PELO TÚNEL DE LAVA",
@ -938,7 +938,7 @@
"DEIXA EU ADIVINHAR. TEM ARANHAS NA CAVERNA DAS ARANHAS, NÉ?", "DEIXA EU ADIVINHAR. TEM ARANHAS NA CAVERNA DAS ARANHAS, NÉ?",
"É CLARO QUE TEM ARANHAS NA CAVERNA DAS ARANHAS!", "É CLARO QUE TEM ARANHAS NA CAVERNA DAS ARANHAS!",
"MAS ESSE É O MENOR DOS PROBLEMAS!", "MAS ESSE É O MENOR DOS PROBLEMAS!",
"OS LURKERS ESTÃO ATRÁS DE CRISTAIS DE ECO NEGRO CONCENTRADO.", "OS LURKERS ESTÃO ATRÁS DE CRISTAIS DE ECO SOMBRIO CONCENTRADO.",
"VOCÊS PRECISAM DESTRUIR OS CRISTAIS ANTES QUE", "VOCÊS PRECISAM DESTRUIR OS CRISTAIS ANTES QUE",
"OS MONSTROS CONSIGAM LEVÁ-LOS!", "OS MONSTROS CONSIGAM LEVÁ-LOS!",
"É PRA ONTEM!" "É PRA ONTEM!"
@ -946,12 +946,12 @@
"sage-village3-introduction-rams": [ "sage-village3-introduction-rams": [
"QUE BOM QUE VOCÊS DOIS ESTÃO AQUI, HÁ UMA MOVIMENTAÇÃO LURKER NAS MONTANHAS.", "QUE BOM QUE VOCÊS DOIS ESTÃO AQUI, HÁ UMA MOVIMENTAÇÃO LURKER NAS MONTANHAS.",
"PARECE QUE ELES ACHARAM E AGORA ESTÃO TENTANDO LEVAR,", "PARECE QUE ELES ACHARAM E AGORA ESTÃO TENTANDO LEVAR,",
"ALGUMS TANQUES DE ECO NEGRO CONGELADOS NAS GELEIRAS.", "ALGUMS TANQUES DE ECO SOMBRIO CONGELADOS NAS GELEIRAS.",
"QUANDO ESTIVEREM LÁ RODANDO ATRÁS DE POWER CELLS, IMPEÇAM OS LURKERS,", "QUANDO ESTIVEREM LÁ RODANDO ATRÁS DE POWER CELLS, IMPEÇAM OS LURKERS,",
"E TIREM MAIS UM POUCO DE ECO NEGRO DA MÃO DO GOL." "E TIREM MAIS UM POUCO DE ECO SOMBRIO DA MÃO DO GOL."
], ],
"sage-village3-reminder-1-dark-eco": [ "sage-village3-reminder-1-dark-eco": [
"VOCÊ PRECISA DESTRUIR OS CRISTAIS DE ECO NEGRO NA CAVERNA DAS ARANHAS!" "VOCÊ PRECISA DESTRUIR OS CRISTAIS DE ECO SOMBRIO NA CAVERNA DAS ARANHAS!"
], ],
"sage-village3-reminder-1-rams": [ "sage-village3-reminder-1-rams": [
"NÃO PODEMOS DEIXAR OS LURKERS BOTAREM AS MÃOS NOS TANQUES DE ECO.", "NÃO PODEMOS DEIXAR OS LURKERS BOTAREM AS MÃOS NOS TANQUES DE ECO.",
@ -988,7 +988,7 @@
], ],
"sidekick-human-intro-sequence-c": [ "sidekick-human-intro-sequence-c": [
"O QUE ESTAMOS FAZENDO AQUI, JAK? ESSE LUGAR DÁ MO MEDO!", "O QUE ESTAMOS FAZENDO AQUI, JAK? ESSE LUGAR DÁ MO MEDO!",
"HUH?", "HÃ?",
"PORCARIA PRECURSOR IDIOTA!", "PORCARIA PRECURSOR IDIOTA!",
"EEK! QUE LAMA NEGRA É ESSA? NÃO PARECE MUITO AMIGÁVEL.", "EEK! QUE LAMA NEGRA É ESSA? NÃO PARECE MUITO AMIGÁVEL.",
"O SÁBIO FALA TODA HORA SOBRE OS PRECURSORES QUE CONSTRUÍRAM ESSE LUGAR.", "O SÁBIO FALA TODA HORA SOBRE OS PRECURSORES QUE CONSTRUÍRAM ESSE LUGAR.",
@ -1039,7 +1039,7 @@
"EU NÃO QUERO ALINHAR ESSAS PONTES TÃO CEDO." "EU NÃO QUERO ALINHAR ESSAS PONTES TÃO CEDO."
], ],
"warrior-resolution": [ "warrior-resolution": [
"OH.", "AH.",
"MARAVILHA.", "MARAVILHA.",
"VOCÊS ME TROUXERAM AS ORBES PRECURSORAS. (CHORA)", "VOCÊS ME TROUXERAM AS ORBES PRECURSORAS. (CHORA)",
"TÁ CERTO.", "TÁ CERTO.",
@ -1179,7 +1179,7 @@
"PEGOU ELE." "PEGOU ELE."
], ],
"BIL-TA09": [ "BIL-TA09": [
"YEEHAW!" "UHUU!"
], ],
"BIL-TA1A": [ "BIL-TA1A": [
"PEGA ELES!" "PEGA ELES!"
@ -1215,7 +1215,7 @@
"VAMOS LÁ, EMPURRE O OVO DO PENHASCO!" "VAMOS LÁ, EMPURRE O OVO DO PENHASCO!"
], ],
"BIR-AM06": [ "BIR-AM06": [
"OHH!" "AHH!"
], ],
"BIR-AM07": [ "BIR-AM07": [
"OH, AQUI, PASSARIN PASSARIN. AQUI, PASSARIN PASSARIN." "OH, AQUI, PASSARIN PASSARIN. AQUI, PASSARIN PASSARIN."
@ -1479,7 +1479,7 @@
"VOCÊ SÓ NÃO DESISTE, NÃO É?" "VOCÊ SÓ NÃO DESISTE, NÃO É?"
], ],
"GOL-AM02": [ "GOL-AM02": [
"EM BREVE, O ECO NEGRO SERÁ NOSSO!" "EM BREVE, O ECO SOMBRIO SERÁ NOSSO!"
], ],
"GOL-AM03": [ "GOL-AM03": [
"ACABE COM ELES!" "ACABE COM ELES!"
@ -1700,16 +1700,16 @@
"ISSO NÃO É BOM." "ISSO NÃO É BOM."
], ],
"SAGELP32": [ "SAGELP32": [
"CRISTAIS DE ECO NEGRO?" "CRISTAIS DE ECO SOMBRIO?"
], ],
"SAGELP33": [ "SAGELP33": [
"MAIA E GOL PERDERAM A CABEÇA!" "MAIA E GOL PERDERAM A CABEÇA!"
], ],
"SAGELP34": [ "SAGELP34": [
"HNG, O PODER DO ECO NEGRO NÃO PODE SER CONTROLADO!" "HNG, O PODER DO ECO SOMBRIO NÃO PODE SER CONTROLADO!"
], ],
"SAGELP35": [ "SAGELP35": [
"ECO NEGRO NAS GELEIRAS?" "ECO SOMBRIO NAS GELEIRAS?"
], ],
"SAGELP36": [ "SAGELP36": [
"AH! TEM TROPAS LURKERS NAS MONTANHAS!" "AH! TEM TROPAS LURKERS NAS MONTANHAS!"
@ -1951,7 +1951,7 @@
"EU TENHO QUE ADMITIR, ESTOU SURPRESO. VOCÊS DOIS NÃO ESTRAGARAM TUDO!", "EU TENHO QUE ADMITIR, ESTOU SURPRESO. VOCÊS DOIS NÃO ESTRAGARAM TUDO!",
"AGORA QUE OS LURKERS NÃO CONSEGUEM ABRIR O ARMAZÉM,", "AGORA QUE OS LURKERS NÃO CONSEGUEM ABRIR O ARMAZÉM,",
"ELES NÃO PODEM INUNDAR O MUNDO", "ELES NÃO PODEM INUNDAR O MUNDO",
"EM ECO NEGRO E CAUSAR UMA DESTRUIÇÃO INIMAGINÁVEL.", "EM ECO SOMBRIO E CAUSAR UMA DESTRUIÇÃO INIMAGINÁVEL.",
"TIRA ESSE SORRISO RIDÍCULO DA CARA, DAXTER,", "TIRA ESSE SORRISO RIDÍCULO DA CARA, DAXTER,",
"VOCÊS DOIS TEM MUITO O QUE FAZER.", "VOCÊS DOIS TEM MUITO O QUE FAZER.",
"AGORA MÃOS À OBRA!" "AGORA MÃOS À OBRA!"
@ -2045,7 +2045,7 @@
"HEY! AQUELA DELÍCINHA DA KEIRA TINHA RAZÃO, TEM TRANS-PADS AQUI." "HEY! AQUELA DELÍCINHA DA KEIRA TINHA RAZÃO, TEM TRANS-PADS AQUI."
], ],
"sksp0002": [ "sksp0002": [
"CONFIA EM MIM, ESSAS CAIXAS DE ECO NEGRO SÃO PROBLEMA!" "CONFIA EM MIM, ESSAS CAIXAS DE ECO SOMBRIO SÃO PROBLEMA!"
], ],
"sksp0003": [ "sksp0003": [
"APOSTO QUE SE ACHARMOS TODAS ESSAS MOSCAS DE BUSCA FOFINHAS EM CADA ÁREA", "APOSTO QUE SE ACHARMOS TODAS ESSAS MOSCAS DE BUSCA FOFINHAS EM CADA ÁREA",
@ -2114,7 +2114,7 @@
"YEAH HA HA HA HA HA!! WOO HO HO HO!" "YEAH HA HA HA HA HA!! WOO HO HO HO!"
], ],
"sksp0023": [ "sksp0023": [
"WOOHOO!" "UUUHUU!"
], ],
"sksp0024": [ "sksp0024": [
"YEAH HA HA! YEAH!" "YEAH HA HA! YEAH!"
@ -2232,7 +2232,7 @@
"NÓS PRECISAMOS DE ECO AZUL PRA ENERGIZAR ESSA PLATAFORMA!" "NÓS PRECISAMOS DE ECO AZUL PRA ENERGIZAR ESSA PLATAFORMA!"
], ],
"sksp0076": [ "sksp0076": [
"JAK! HIT SOME JUMPS TO KEEP US OFF THE HOT GROUND." "JAK! DÊ ALGUNS SALTOS PARA NOS MANTER LONGE DO SOLO QUENTE."
], ],
"sksp0077": [ "sksp0077": [
"BALÕES BOM, MAGMA QUENTE FLAMEJANTE RUIM!" "BALÕES BOM, MAGMA QUENTE FLAMEJANTE RUIM!"
@ -2251,7 +2251,7 @@
"AHH! TALVEZ EU DEVESSE PILOTAR!" "AHH! TALVEZ EU DEVESSE PILOTAR!"
], ],
"sksp0082": [ "sksp0082": [
"VOCÊ TÁ TENTANDO EVITAR AS CAIXAS DE ECO NEGRO, NÉ?!" "VOCÊ TÁ TENTANDO EVITAR AS CAIXAS DE ECO SOMBRIO, NÉ?!"
], ],
"sksp0083": [ "sksp0083": [
"OOH! VÊ SE DÁ PRA PEGAR IMPULSO BATENDO NOS LURKERS!" "OOH! VÊ SE DÁ PRA PEGAR IMPULSO BATENDO NOS LURKERS!"
@ -2302,7 +2302,7 @@
"AEEE, MAIS ORBES!" "AEEE, MAIS ORBES!"
], ],
"sksp009c": [ "sksp009c": [
"FAZ FAVOR E FICA LONGE DAS CAIXAS DE ECO NEGRO" "FAZ FAVOR E FICA LONGE DAS CAIXAS DE ECO SOMBRIO"
], ],
"sksp009d": [ "sksp009d": [
"YEAH-HA-HA-HA!" "YEAH-HA-HA-HA!"
@ -2330,7 +2330,7 @@
], ],
"sksp0110": [ "sksp0110": [
"EU JÁ FALEI ANTES, VOU FALAR DE NOVO...", "EU JÁ FALEI ANTES, VOU FALAR DE NOVO...",
"EVITE AS CAIXAS DE ECO NEGRO!" "EVITE AS CAIXAS DE ECO SOMBRIO!"
], ],
"sksp0111": [ "sksp0111": [
"VAMOS LEVAR ESSAS TOUPEIRAS DE VOLTA AO SUBSOLO." "VAMOS LEVAR ESSAS TOUPEIRAS DE VOLTA AO SUBSOLO."
@ -2385,10 +2385,10 @@
"CHUTA O CARINHA NO TOPO!" "CHUTA O CARINHA NO TOPO!"
], ],
"sksp0128": [ "sksp0128": [
"JAK! CORRE! O ECO NEGRO ESTÁ SUBINDO!" "JAK! CORRE! O ECO SOMBRIO ESTÁ SUBINDO!"
], ],
"sksp0129": [ "sksp0129": [
"AHH! O ECO NEGRO TÁ CHEGANDO PERTO" "AHH! O ECO SOMBRIO TÁ CHEGANDO PERTO"
], ],
"sksp0130": [ "sksp0130": [
"UH, ACHO QUE TEMOS QUE ATIVAR TODAS AS PLATAFORMAS." "UH, ACHO QUE TEMOS QUE ATIVAR TODAS AS PLATAFORMAS."
@ -2559,16 +2559,16 @@
"CONSEGUIMOS! IMPEDIMOS ELES DE DETONAR A PASSAGEM!" "CONSEGUIMOS! IMPEDIMOS ELES DE DETONAR A PASSAGEM!"
], ],
"sksp0327": [ "sksp0327": [
"ACHO QUE ESSE FOI O ÚLTIMO CRISTAL DE ECO NEGRO!" "ACHO QUE ESSE FOI O ÚLTIMO CRISTAL DE ECO SOMBRIO!"
], ],
"sksp0328": [ "sksp0328": [
"TALVEZ A GENTE ATIRE MELHOR SE VOCÊ MIRAR COM A SUA LENTE." "TALVEZ A GENTE ATIRE MELHOR SE VOCÊ MIRAR COM A SUA LENTE."
], ],
"sksp0329": [ "sksp0329": [
"OH, NÃO! MAIS ECO NEGRO!" "OH, NÃO! MAIS ECO SOMBRIO!"
], ],
"sksp0330": [ "sksp0330": [
"NÃO ACHO QUE PEGAMOS TODOS OS CRISTAIS DE ECO NEGRO." "NÃO ACHO QUE PEGAMOS TODOS OS CRISTAIS DE ECO SOMBRIO."
], ],
"sksp0331": [ "sksp0331": [
"ATIRE NOS LURKERS COMENDO AS PILARES!" "ATIRE NOS LURKERS COMENDO AS PILARES!"
@ -2655,7 +2655,7 @@
"DESVIE OU ATIRE NAS MINAS, JAK!" "DESVIE OU ATIRE NAS MINAS, JAK!"
], ],
"sksp0367": [ "sksp0367": [
"ACHE UM CAMINHO ENTRE OS BARRIS DE ECO NEGRO!" "ACHE UM CAMINHO ENTRE OS BARRIS DE ECO SOMBRIO!"
], ],
"sksp0368": [ "sksp0368": [
"CONTINUE ACERTANDO ESSES BALÕES DE RESFRIAMENTO!" "CONTINUE ACERTANDO ESSES BALÕES DE RESFRIAMENTO!"
@ -2854,7 +2854,7 @@
"KEIRA": "KEIRA", "KEIRA": "KEIRA",
"MAIA": "MAIA", "MAIA": "MAIA",
"MAYOR": "PREFEITO", "MAYOR": "PREFEITO",
"MINER": "MINER", "MINER": "MINEIRO",
"OLD MAN": "VELHO", "OLD MAN": "VELHO",
"ORACLE": "ORÁCULO", "ORACLE": "ORÁCULO",
"RED SAGE": "SÁBIO VERMELHO", "RED SAGE": "SÁBIO VERMELHO",

View File

@ -41,7 +41,7 @@
"THIS IS TERRIBLE! FATHER IS MISSING!", "THIS IS TERRIBLE! FATHER IS MISSING!",
"I THINK GOL AND MAIA MAY HAVE KIDNAPPED HIM AS WELL!", "I THINK GOL AND MAIA MAY HAVE KIDNAPPED HIM AS WELL!",
"RELAX, SWEETHEART. I GOT EVERYTHING UNDER CONTROL.", "RELAX, SWEETHEART. I GOT EVERYTHING UNDER CONTROL.",
"UNDER CONTROL?!", "SOBRE CONTROLO?!",
"LURKER ARMIES CONTINUE TO GROW ACROSS THE LAND,", "LURKER ARMIES CONTINUE TO GROW ACROSS THE LAND,",
"THE SAGES HAVE BEEN KIDNAPPED,", "THE SAGES HAVE BEEN KIDNAPPED,",
"GOL AND MAIA HAVE GATHERED ENOUGH ECO", "GOL AND MAIA HAVE GATHERED ENOUGH ECO",

View File

@ -1,85 +1,85 @@
{ {
"1000": "KAMERAN ASETUKSET", "1000": "CONFIGURACIÓ DE LA CÀMERA",
"1001": "NORMAL", "1001": "NORMAL",
"1002": "INVERTED", "1002": "INVERTIDA",
"1003": "1ST-PERSON HORIZONTAL CAMERA", "1003": "CÀMARA HORIZONTAL EN 1º PERSONA",
"1004": "1ST-PERSON VERTICAL CAMERA", "1004": "CÀMARA VERTICAL 1º PERSONA",
"1005": "3RD-PERSON HORIZONTAL CAMERA", "1005": "CÀMERA HORIZONTAL EN 3º PERSONA",
"1006": "3RD-PERSON VERTICAL CAMERA", "1006": "CÀMERA VERTICAL EN 3º PERSONA",
"1007": "RESTORE DEFAULTS", "1007": "RESTAURAR ELS VALORS PREDETERMINATS",
"1010": "ACCESSIBILITY", "1010": "ACCESSIBILITAT",
"1011": "PRECURSOR ORB GLOW", "1011": "ORBE PRECURSOR BRILLANT",
"1020": "PS2 OPTIONS", "1020": "OPCIONS PS2",
"1021": "PS2 LOAD SPEED", "1021": "VELOCITAT DE CÀRREGA PS2",
"1022": "PARTICLE CULLING", "1022": "COLLANT DE PARTÍCULES",
"1023": "MUSIC FADE-OUT", "1023": "FADE-OUT DE LA MÚSICA",
"1024": "MUSIC FADE-IN", "1024": "FADE-IN DE LA MÚSICA",
"1025": "ACTOR CULLING", "1025": "ACTOR CULLING",
"1026": "BACKGROUND CULLING", "1026": "CULLING DE FONAMENTS",
"1027": "FORCE ENVIRONMENT MAPPING", "1027": "CARTOGRAFIA DE L'AMBIENT DE FORÇA",
"1030": "DISCORD RICH-PRESENCE", "1030": "DISCORD RICA-PRESÈNCIA",
"1031": "DISPLAY MODE", "1031": "MODE DE PANTALLA",
"1032": "WINDOWED", "1032": "FINESTRES",
"1033": "BORDERLESS", "1033": "SENSE BORDES",
"1034": "FULLSCREEN", "1034": "PANTALLA COMPLETA",
"1035": "GAME RESOLUTION", "1035": "RESOLUCIÓ DEL JOC",
"1036": "~D X ~D", "1036": "~D X ~D",
"1037": "PS2 ASPECT RATIO", "1037": "RELACIÓ D'ASPECTES PS2",
"1038": "WHEN PS2 ASPECT RATIO IS ENABLED, ONLY 4X3 AND 16X9 ASPECT RATIO CAN BE SELECTED. CONTINUE?", "1038": "QUAN ESTÀ HABILITAT LA RELACIÓ D'ASPECTES PS2, NOMÉS ES POT SELECCIONAR LA RELACIÓ D'ASPECTES 4X3 I 16X9. CONTINUAR?",
"1039": "ASPECT RATIO (PS2)", "1039": "RELACIÓ D'ASPECT (PS2)",
"1040": "SUBTITLES ENABLED", "1040": "SUBTÍTOLS ACTIVATS",
"1041": "SUBTITLES DISABLED", "1041": "SUBTÍTOLS DISCAPACITATS",
"1042": "TEXT LANGUAGE", "1042": "LLENGUATGE DE TEXT",
"1043": "DISPLAY", "1043": "VISUALITZACIÓ",
"1044": "DISPLAY ~D", "1044": "PANTALLA ~D",
"1050": "MSAA", "1050": "MSAA",
"1051": "~DX", "1051": "~DX",
"1052": "2X", "1052": "2X",
"1053": "4X", "1053": "4X",
"1054": "8X", "1054": "8X",
"1055": "16X", "1055": "16X",
"1060": "FRAME RATE (EXPERIMENTAL)", "1060": "VELOCITAT DE FRAMES (EXPERIMENTAL)",
"1061": "60", "1061": "60",
"1062": "100", "1062": "100",
"1063": "150", "1063": "150",
"1070": "LEVEL OF DETAIL (BACKGROUND)", "1070": "NIVELL DE DETALLS (FONDS)",
"1071": "LEVEL OF DETAIL (FOREGROUND)", "1071": "NIVELL DE DETALL (PRIMER PLA)",
"1072": "MAXIMUM", "1072": "MÀXIM",
"1073": "HIGH", "1073": "ALTA",
"1074": "MEDIUM", "1074": "MITJÀ",
"1075": "LOW", "1075": "BAIX",
"1076": "MINIMUM", "1076": "MINIM",
"1077": "PS2", "1077": "PS2",
"1078": "SUBTITLES", "1078": "SUBTÍTOLS",
"1079": "HINT SUBTITLES", "1079": "SUBTÍTOLS CONSELLS",
"1080": "CHEATS", "1080": "TRAMPES",
"1081": "SECRETS", "1081": "SECRETS",
"1082": "SELECT TRACK", "1082": "SELECCIONA LA PISTA",
"1083": "SELECT FLAVOR", "1083": "SELECCIONA EL SABOR",
"1084": "FINAL BOSS", "1084": "CAP FINAL",
"1085": "CREDITS", "1085": "CRÈDITS",
"1086": "?????", "1086": "?????",
"1087": "KLAWW", "1087": "KLAWW",
"1088": "FISHING MINI-GAME", "1088": "MINI-JOC DE PESCA",
"1089": "CHALLENGE THEME", "1089": "TEMA REPTE",
"1090": "INFINITE BLUE ECO", "1090": "ECO BLAU INFINIT",
"1091": "INFINITE RED ECO", "1091": "VERMELL INFINIT ECO",
"1092": "INFINITE GREEN ECO", "1092": "ECO VERD INFINIT",
"1093": "INFINITE YELLOW ECO", "1093": "GROC INFINIT ECO",
"1094": "ALTERNATE DAXTER", "1094": "DAXTER ALTERN",
"1095": "INVINCIBILITY", "1095": "INVENCIBILITAT",
"1096": "ALL MUSIC TRACKS", "1096": "TOTES LES PETS DE MÚSICA",
"1097": "REAL TIME OF DAY", "1097": "HORA REAL DEL DIA",
"1098": "REACH 100% COMPLETION", "1098": "ACONSEGUIR EL 100% COMPLET",
"1099": "BEAT THE GAME", "1099": "GANA EL JOC",
"1100": "YELLOW SAGE", "1100": "SALVIA GROC",
"1101": "RED SAGE", "1101": "SALVIA VERMELLA",
"1102": "BLUE SAGE", "1102": "SALVIA BLAU",
"1103": "CITADEL HUB", "1103": "CENTRE CIUTADELLA",
"1104": "MIDDLE OF THE BOSS", "1104": "MITJÀ DEL CAP",
"1105": "END OF THE BOSS", "1105": "FINAL DEL CAP",
"1106": "FLAVOR 1", "1106": "SABOR 1",
"1107": "FLAVOR 2", "1107": "SABOR 2",
"1110": "ENGLISH (UK)", "1110": "ENGLISH (UK)",
"1111": "PORTUGUÊS", "1111": "PORTUGUÊS",
"1112": "PORTUGUÊS (BRASIL)", "1112": "PORTUGUÊS (BRASIL)",
@ -93,135 +93,135 @@
"111a": "ÍSLANDSKA", "111a": "ÍSLANDSKA",
"111b": "POLSKI", "111b": "POLSKI",
"111c": "LIETUVIŲ KALBA", "111c": "LIETUVIŲ KALBA",
"1500": "SPEEDRUNNER MODE", "1500": "MODE SPEEDRUNNER",
"138": "PLEASE DO NOT REMOVE OR INSERT ANY PERIPHERALS, TURN OFF YOUR SYSTEM OR SHUT DOWN THE GAME", "138": "SI US PLAU, NO TREURES NI INSERIS CAP PERIFÈRIC, NO APAGIS EL SISTEMA NI TANGAS EL JOC",
"161": "WHILE THIS ICON IS ON SCREEN, DO NOT REMOVE OR INSERT ANY PERIPHERALS, TURN OFF YOUR SYSTEM OR SHUT DOWN THE GAME", "161": "MENTRE AQUESTA ICONA ESTÀ A LA PANTALLA, NO TREURES NI INSEIXIS CAP PERIFÈRIC, NO APAGIS EL SISTEMA NI TANGAS EL JOC",
"100c": "AUTO-SAVE DISABLED", "100c": "DESA AUTOMÀTIC DESACTIVAT",
"100d": "ARE YOU SURE YOU WANT TO DISABLE AUTO-SAVE?", "100d": "ESTEU SEGUR QUE VOLEU DESACTIVAR EL DESA AUTOMÀTIC?",
"100e": "DISABLE AUTO-SAVE", "100e": "DESACTIVAR DESA AUTOMÀTIC",
"100f": "MISCELLANEOUS", "100f": "DIVERS",
"103a": "FIT TO SCREEN", "103a": "AJUSTE A LA PANTALLA",
"103b": "V-SYNC", "103b": "V-SYNC",
"103c": "4X3 (PS2)", "103c": "4X3 (PS2)",
"103d": "16X9 (PS2)", "103d": "16X9 (PS2)",
"103e": "~DX~D", "103e": "~DX~D",
"103f": "PRESS <PAD_SQUARE> TO TOGGLE SUBTITLES", "103f": "PREMEU <PAD_SQUARE> PER CANVIAR SUBTÍTOLS",
"107a": "SUBTITLE LANGUAGE", "107a": "LLENGUA DEL SUBTÍTOL",
"107b": "SHOW SPEAKER IN SUBTITLE", "107b": "MOSTRA L'ORADOR EN EL SUBTÍTOL",
"107c": "ALWAYS", "107c": "SEMPRE",
"107d": "NEVER", "107d": "MAI",
"107e": "OFF-SCREEN", "107e": "FORA DE PANTALLA",
"107f": "HINT LOG", "107f": "REGISTRE DE CONSELLS",
"109a": "BIG HEAD JAK", "109a": "CAP GRAN JAK",
"109b": "SMALL HEAD JAK", "109b": "CAP PETIT JAK",
"109c": "BIG FIST JAK", "109c": "GRAN PUNT COM",
"109d": "BIG HEAD CHARACTERS", "109d": "PERSONATGES DE CAP GRAN",
"109e": "NO TEXTURES MODE", "109e": "MODE SENSE TEXTURES",
"109f": "MIRRORED WORLD", "109f": "MÓN ESPIRAL",
"10a0": "HUGE HEAD JAK", "10a0": "CAP ENORME JAK",
"10c0": "MUSIC PLAYER", "10c0": "REPRODUCTOR DE MÚSICA",
"10c1": "SCENE PLAYER", "10c1": "REPRODUCTOR D'ESCENA",
"10c2": "PLAY CREDITS", "10c2": "CRÈDITS DE JOC",
"10c3": "SCRAPBOOK", "10c3": "LLIBRE DE RECORDS",
"10d0": "DEFAULT", "10d0": "PER DEFECTE",
"10d1": "UNUSED", "10d1": "NO UTILITZAT",
"10d2": "SAGE", "10d2": "SALVI",
"10d3": "SAGE'S HUT", "10d3": "CABANA DEL SAVI",
"10d4": "BIRDWATCHER", "10d4": "OBSERVADOR D'OCELLS",
"10d5": "FARMER", "10d5": "GRANGER",
"10d6": "KEIRA", "10d6": "KEIRA",
"10d7": "MAYOR", "10d7": "ALCALDE",
"10d8": "SCULPTOR", "10d8": "ESCULTOR",
"10d9": "JAK'S UNCLE", "10d9": "L'ONLE DE JAK",
"10da": "DOCK", "10da": "DOCK",
"10db": "FORBIDDEN TEMPLE EXIT", "10db": "SORTIDA PROHIBIDA DEL TEMPLE",
"10dc": "LURKER MACHINE", "10dc": "MÀQUINA LURKER",
"10dd": "TOP OF THE TOWER", "10dd": "TOP DE LA TORRE",
"10de": "BLUE VENT SWITCH", "10de": "INTERRUPTOR BLAU DE VENT",
"10df": "UNUSED", "10df": "NO UTILITZAT",
"10e0": "SENTINELS", "10e0": "SENTINELLES",
"10e1": "LURKER CANNON", "10e1": "CANON LURKER",
"10e2": "GROTTO", "10e2": "GROTTA",
"10e3": "UNUSED 1", "10e3": "NO UTILITZAT 1",
"10e4": "LURKER BOAT", "10e4": "BARCA LURKER",
"10e5": "UNUSED 2", "10e5": "NO UTILITZAT 2",
"10e6": "ZOOMER", "10e6": "ZOOM",
"10e7": "FLUT FLUT", "10e7": "INUNDACIÓ",
"10e8": "UNUSED", "10e8": "NO UTILITZAT",
"10e9": "WARRIOR", "10e9": "GUERRER",
"10ea": "GEOLOGIST", "10ea": "GEÒLOGA",
"10eb": "GAMBLER", "10eb": "JUGADOR",
"10ec": "LEVITATOR MACHINE", "10ec": "MÀQUINA LEVITADORA",
"10ed": "UNUSED 1", "10ed": "NO UTILITZAT 1",
"10ee": "LAUNCHER TRAPS", "10ee": "TRAMPES DE LLANÇADOR",
"10ef": "UNUSED 2", "10ef": "NO UTILITZAT 2",
"10f0": "DEAD MAN'S GORGE", "10f0": "GORGA DEL MORTE",
"10f1": "MIDDLE OF THE PASS", "10f1": "MITJÀ DEL PAS",
"10f2": "END OF THE PASS", "10f2": "FI DEL PAS",
"10f3": "TO SPIDER CAVE", "10f3": "A LA COVA DE L'ARANYA",
"10f4": "TO SNOWY MOUNTAIN", "10f4": "A LA MUNTANYA NEVADA",
"10f5": "MINERS", "10f5": "MINERS",
"10f6": "ROBOT CAVE SCAFFOLDING", "10f6": "BASTIDIA DE LA COVA DEL ROBOT",
"10f7": "PRECURSOR ROBOT TOP", "10f7": "TOP ROBOT PRECURSOR",
"10f8": "MAIN CAVE", "10f8": "COVA PRINCIPAL",
"10f9": "DARK CAVE", "10f9": "COVA FOSC",
"10fa": "UNUSED", "10fa": "NO UTILITZAT",
"10fb": "HIDDEN CAVE", "10fb": "COVA OCULTA",
"10fc": "LURKER FORT", "10fc": "LURKER FORT",
"10fd": "SNOWBALLS", "10fd": "BOLES DE NEU",
"10fe": "MIDDLE OF THE TUBE", "10fe": "MITJÀ DEL TUBO",
"10ff": "END OF THE TUBE", "10ff": "EXTREM DEL TUBO",
"1501": "CUTSCENE SKIPS", "1501": "SALTS DE CORTE",
"1502": "CHECKPOINT SELECT", "1502": "SELECCIONAR PUNT DE CONTROL",
"1503": "SPEEDRUN OPTIONS", "1503": "OPCIONS DE VELOCITAT",
"1504": "CAUTION: THESE OPTIONS WILL AUTO SAVE IN YOUR FIRST SAVE SLOT!", "1504": "ATENCIÓ: AQUESTES OPCIONS ES DESARAN AUTOMÀTICAMENT A LA VOSTRA PRIMER RANURA DE DESA.",
"1505": "RESET CURRENT SPEEDRUN", "1505": "RESTABLEIX LA VELOCITAT ACTUAL",
"1506": "NEW FULL GAME RUN", "1506": "NOVA JORNADA COMPLETA",
"150e": "NEW INDIVIDUAL LEVEL RUN", "150e": "NOU CURSA DE NIVELL INDIVIDUAL",
"150f": "GEYSER ROCK IL", "150f": "GEYSER ROCK IL",
"1510": "SANDOVER VILLAGE IL", "1510": "SANDOVER VILLAGE IL",
"1511": "SENTINEL BEACH IL", "1511": "PLATJA SENTINELLA IL",
"1512": "FORBIDDEN JUNGLE IL", "1512": "SELVA PROHIBIDA IL",
"1513": "MISTY ISLAND IL", "1513": "ILLA BOIRA IL",
"1514": "FIRE CANYON IL", "1514": "CANÓ DE FOC IL",
"1515": "ROCK VILLAGE IL", "1515": "ROCK VILLAGE IT",
"1516": "LOST PRECURSOR CITY IL", "1516": "CIUTAT PIONERA PERDUDA IL",
"1517": "BOGGY SWAMP IL", "1517": "BOGGY SAMP IL",
"1518": "PRECURSOR BASIN IL", "1518": "CONCA DEL PRECURSOR IL",
"1519": "MOUNTAIN PASS IL", "1519": "COLL DE MUNTANYA IL",
"151a": "VOLCANIC CRATER IL", "151a": "CRÀTER VOLCÀNIC IL",
"151b": "SNOWY MOUNTAIN IL", "151b": "MUNTANYA NEVADA IL",
"151c": "SPIDER CAVE IL", "151c": "COVA DE L'ARANYA IL",
"151d": "LAVA TUBE IL", "151d": "LAVA TUBE IT",
"151e": "GOL AND MAIA'S CITADEL IL", "151e": "GOL I CIUTADELLA DE MAIA IL",
"151f": "NEW CATEGORY EXTENSION RUN", "151f": "NOVA EXTENSIÓ DE CATEGORIA",
"1520": "NG+", "1520": "NG+",
"1521": "HUB 1 100%", "1521": "HUB 1 100%",
"1522": "HUB 2 100%", "1522": "HUB 2 100%",
"1523": "HUB 3 100%", "1523": "HUB 3 100%",
"1524": "ALL CUTSCENES", "1524": "TOTES LES ESCENES DE CORTE",
"1600": "INPUT OPTIONS", "1600": "OPCIONS D'ENTRADA",
"1601": "SELECT CONTROLLER", "1601": "SELECCIONA EL CONTROLADOR",
"1602": "ANALOG DEADZONE", "1602": "ZONA MORTA ANALÒGICA",
"1603": "IGNORE IF WINDOW UNFOCUSED", "1603": "IGNOREU SI LA FINESTRA ESTÀ DESENFOCADA",
"1604": "CONTROLLER LED FOR HP", "1604": "LED CONTROLADOR PER HP",
"1605": "CONTROLLER LED FOR ECO", "1605": "CONTROLADOR LED PER ECO",
"1606": "TRACK CAMERA", "1606": "CÀMARA DE PISTA",
"1607": "HORIZONTAL SENSITIVITY", "1607": "SENSIBILITAT HORIZONTAL",
"1608": "VERTICAL SENSITIVITY", "1608": "SENSIBILITAT VERTICAL",
"1609": "PLAYER MOVEMENT", "1609": "MOVIMENT DEL JUGADOR",
"160a": "CONTROLLER BINDS", "160a": "EL CONTROLADOR VINCULA",
"160b": "KEYBOARD BINDS", "160b": "ENLLAÇOS DE TECLAT",
"160c": "MOUSE BINDS", "160c": "ENLLAÇOS DEL RATOLÍ",
"160d": "CONTROLLER OPTIONS", "160d": "OPCIONS DEL CONTROLADOR",
"160e": "ENABLE KEYBOARD", "160e": "HABILITA EL TECLAT",
"160f": "ENABLE MOUSE", "160f": "HABILITA EL RATOLÍ",
"1610": "MOUSE OPTIONS", "1610": "OPCIONS DEL RATOLÍ",
"1611": "REASSIGN BINDS", "1611": "REASSIGNAR VINCULATS",
"1612": "CONTROLLER ~D", "1612": "CONTROLADOR ~D",
"1613": "AUTO HIDE CURSOR", "1613": "AMAGA EL CURSOR AUTOMÀTIC",
"1614": "UNSET", "1614": "UNSET",
"1615": "UNKNOWN", "1615": "DESCONEGUT",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO", "1616": "CAP ALTRES OPCIONS PER A LA RELACIÓ D'ASPECT",
"1617": "CONTROLLER LED FOR HEAT" "1617": "LED CONTROLADOR DE CALOR"
} }

View File

@ -219,7 +219,7 @@
"1610": "MAUS OPTIONEN", "1610": "MAUS OPTIONEN",
"1611": "TASTENBELEGUNG ÄNDERN", "1611": "TASTENBELEGUNG ÄNDERN",
"1612": "CONTROLLER ~D", "1612": "CONTROLLER ~D",
"1613": "CURSOR VERSTECKEN", "1613": "CURSOR AUTOMATISCH VERSTECKEN",
"1614": "UNBELEGT", "1614": "UNBELEGT",
"1615": "UNBEKANNT", "1615": "UNBEKANNT",
"1616": "KEINE WEITEREN OPTIONEN FÜR SEITENVERHÄLTNIS", "1616": "KEINE WEITEREN OPTIONEN FÜR SEITENVERHÄLTNIS",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Kor Cabezachapa", "metalkor": "Kor Cabezachapa",
"metalkor-before-consite": "Jefe Cabezachapa",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oráculo", "oracle": "Oráculo",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

View File

@ -120,6 +120,8 @@
"kor": "コール", "kor": "コール",
"krew": "クルー", "krew": "クルー",
"metalkor": "メタルコール", "metalkor": "メタルコール",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "モッグ", "mog": "モッグ",
"onin": "オニン", "onin": "オニン",
"oracle": "オラクル", "oracle": "オラクル",

View File

@ -24,6 +24,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

View File

@ -7839,6 +7839,8 @@
"kor": "Koras", "kor": "Koras",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metalinis Koras", "metalkor": "Metalinis Koras",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mogas", "mog": "Mogas",
"onin": "Oninas", "onin": "Oninas",
"oracle": "Oraklė", "oracle": "Oraklė",

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oracle", "oracle": "Oracle",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor", "kor": "Kor",
"krew": "Krew", "krew": "Krew",
"metalkor": "Metal Kor", "metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog", "mog": "Mog",
"onin": "Onin", "onin": "Onin",
"oracle": "Oráculo", "oracle": "Oráculo",

Some files were not shown because too many files have changed in this diff Show More