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/jak3/texture_replacements/*
# merc replacements
custom_assets/jak1/merc_replacements/*
custom_assets/jak2/merc_replacements/*
custom_assets/jak3/merc_replacements/*
# generated cmake files
svnrev.h
common/versions/revision.h

View File

@ -7,42 +7,58 @@
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Summary",
"args": ["--gtest_brief=1"]
"args": [
"--gtest_brief=1"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Verbose",
"args": ["--gtest_brief=0"]
"args": [
"--gtest_brief=0"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Draft Tests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""]
"args": [
"--gtest_brief=0",
"--gtest_filter=\"*Draft*\""
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - TypeConsistency - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""]
"args": [
"--gtest_brief=0",
"--gtest_filter=\"*TypeConsistency*\""
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - TypeConsistency - Jak 2 - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*Jak2TypeConsistency*\""]
"args": [
"--gtest_brief=0",
"--gtest_filter=\"*Jak2TypeConsistency*\""
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - WithGameTests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""]
"args": [
"--gtest_brief=0",
"--gtest_filter=\"*WithGameTests*\""
]
},
{
"type": "default",
@ -113,56 +129,109 @@
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 1 - Runtime",
"args": ["-v", "--game", "jak1", "--", "-fakeiso", "-debug"]
"args": [
"-v",
"--game",
"jak1",
"--",
"-fakeiso",
"-debug"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 1 - Runtime (boot)",
"args": ["-v", "--game", "jak1", "--", "-boot", "-fakeiso", "-debug"]
"args": [
"-v",
"--game",
"jak1",
"--",
"-boot",
"-fakeiso",
"-debug"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (no boot)",
"args": ["-v", "--game", "jak2", "--", "-fakeiso", "-debug"]
"args": [
"-v",
"--game",
"jak2",
"--",
"-fakeiso",
"-debug"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (boot)",
"args": ["-v", "--game", "jak2", "--", "-boot", "-fakeiso", "-debug"]
"args": [
"-v",
"--game",
"jak2",
"--",
"-boot",
"-fakeiso",
"-debug"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 2 - Runtime (release)",
"args": ["-v", "--game", "jak2", "--", "-boot", "-fakeiso"]
"args": [
"-v",
"--game",
"jak2",
"--",
"-boot",
"-fakeiso"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Game - Jak 3 - Runtime (boot)",
"args": ["-v", "--game", "jak3", "--", "-boot", "-fakeiso", "-debug"]
"args": [
"-v",
"--game",
"jak3",
"--",
"-boot",
"-fakeiso",
"-debug"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "REPL - Jak 1",
"args": ["--user-auto", "--game", "jak1"]
"args": [
"--user-auto",
"--game",
"jak1"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "REPL - Jak 2",
"args": ["--user-auto", "--game", "jak2"]
"args": [
"--user-auto",
"--game",
"jak2"
]
},
{
"type": "default",
@ -262,14 +331,26 @@
"project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"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",
"project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"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",
@ -286,35 +367,53 @@
"project": "CMakeLists.txt",
"projectTarget": "extractor.exe (bin\\extractor.exe)",
"name": "Tools - Extractor - Full",
"args": ["\"E:\\ISOs\\Jak\\Jak 1.iso\""]
"args": [
"\"E:\\ISOs\\Jak\\Jak 1.iso\""
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "lsp.exe (bin\\lsp.exe)",
"name" : "Tools - LSP",
"args" : []
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "lsp.exe (bin\\lsp.exe)",
"name": "Tools - LSP",
"args": []
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "type_searcher.exe (bin\\type_searcher.exe)",
"name" : "Tools - Type Searcher",
"args" : ["--game", "jak2", "--output-path", "./search-results.json", "--size", 255, "--fields", "[{\\\"type\\\":\\\"quaternion\\\",\\\"offset\\\":48}]"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "type_searcher.exe (bin\\type_searcher.exe)",
"name": "Tools - Type Searcher",
"args": [
"--game",
"jak2",
"--output-path",
"./search-results.json",
"--size",
255,
"--fields",
"[{\\\"type\\\":\\\"quaternion\\\",\\\"offset\\\":48}]"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "formatter.exe (bin\\formatter.exe)",
"name" : "Tools - Formatter - Inplace",
"args" : ["--write", "--file", "C:\\Users\\xtvas\\Repos\\opengoal\\jak-project\\decompiler_out\\jak3\\mood-h_disasm.gc"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "formatter.exe (bin\\formatter.exe)",
"name": "Tools - Formatter - Inplace",
"args": [
"--write",
"--file",
"C:\\Users\\xtvas\\Repositories\\opengoal\\jak-project\\goal_src\\jak1\\engine\\camera\\cam-states.gc"
]
},
{
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"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",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/scripts/gsrc/copy-common-naming.py",
"program": "${workspaceFolder}/scripts/gsrc/compare-compilation-outputs.py",
"console": "integratedTerminal",
"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/nrepl/ReplClient.cpp
repl/nrepl/ReplServer.cpp
repl/util.cpp
repl/repl_wrapper.cpp
serialization/subtitles/subtitles_v1.cpp
serialization/subtitles/subtitles_v2.cpp
serialization/subtitles/subtitles.cpp

View File

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

View File

@ -35,7 +35,7 @@ void XSocketServer::shutdown_server() {
close_server_socket();
}
bool XSocketServer::init_server() {
bool XSocketServer::init_server(bool failure_may_occur) {
listening_socket = open_socket(AF_INET, SOCK_STREAM, 0);
if (listening_socket < 0) {
listening_socket = -1;
@ -76,19 +76,27 @@ bool XSocketServer::init_server() {
addr.sin_port = htons(tcp_port);
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();
return false;
}
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();
return false;
}
server_initialized = true;
lg::info("[XSocketServer:{}] initialized", tcp_port);
lg::debug("[XSocketServer:{}] initialized", tcp_port);
post_init();
return true;
}

View File

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

View File

@ -492,6 +492,7 @@ void MercDraw::serialize(Serializer& ser) {
ser.from_ptr(&first_index);
ser.from_ptr(&index_count);
ser.from_ptr(&num_triangles);
ser.from_ptr(&no_strip);
}
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 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 {
TEXTURE,
@ -530,6 +530,8 @@ struct MercDraw {
u32 first_index;
u32 index_count;
u32 num_triangles;
// no strip hack for custom models
bool no_strip = false;
void serialize(Serializer& ser);
};

View File

@ -4,7 +4,7 @@
#include "fmt/core.h"
std::string DmaTag::print() {
std::string DmaTag::print() const {
std::string result;
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);
@ -15,7 +15,7 @@ std::string DmaTag::print() {
return result;
}
std::string VifCode::print() {
std::string VifCode::print() const {
std::string result;
switch (kind) {

View File

@ -52,7 +52,7 @@ struct DmaTag {
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) {
@ -148,7 +148,7 @@ struct VifCode {
u16 num;
u16 immediate;
std::string print();
std::string print() const;
};
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:
```mermaid
%%{init: {'theme': 'dark', "flowchart" : { "curve" : "basis" } } }%%
flowchart TB
subgraph top1 [Build a Formatting Tree]
direction TB

View File

@ -33,14 +33,16 @@ int hang_indentation_width(const FormatterTreeNode& curr_node) {
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) {
if (curr_node.token) {
return curr_node.token->length();
}
int width = 1;
for (const auto& ref : curr_node.refs) {
width += get_total_form_inlined_width(ref);
for (int i = 0; i < curr_node.refs.size(); i++) {
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;
}
@ -49,7 +51,9 @@ int get_total_form_inlined_width(const FormatterTreeNode& curr_node) {
void apply_formatting_config(
FormatterTreeNode& curr_node,
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;
// node is empty, base-case
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());
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) {
// TODO - doesn't merge just replaces, a bit inflexible
predefined_config = *config_from_parent.value();
@ -104,7 +115,7 @@ void apply_formatting_config(
if (curr_node.formatting_config.has_constant_pairs) {
for (int i = 0; i < (int)curr_node.refs.size(); 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() &&
constant_pairs::is_element_second_in_constant_pair(curr_node, child_ref, i)) {
child_ref.formatting_config.parent_mutable_extra_indent = 2;
@ -129,13 +140,16 @@ void apply_formatting_config(
auto& ref = curr_node.refs.at(i);
if (!ref.token) {
// If the child has a pre-defined configuration at that index, we pass it along
if (predefined_config &&
predefined_config->index_configs.find(i) != predefined_config->index_configs.end()) {
apply_formatting_config(ref, predefined_config->index_configs.at(i));
if (predefined_config && predefined_config->index_config_override.find(i) !=
predefined_config->index_config_override.end()) {
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) {
apply_formatting_config(ref, predefined_config->default_index_config);
apply_formatting_config(ref, predefined_config->default_index_config, {});
} else {
apply_formatting_config(ref);
apply_formatting_config(ref, {}, {});
}
}
}
@ -151,10 +165,27 @@ void apply_formatting_config(
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
std::vector<int> column_max_widths = {};
for (int col = 0; col < max_columns; col++) {
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) {
if ((int)field.refs.size() > 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) {
using namespace formatter_rules;
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;
}
// 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
if (ref.token) {
// Cleanup block-comments
std::string val = ref.token_str();
if (ref.metadata.node_type == "block_comment") {
// TODO - change this sanitization to return a list of lines instead of a single new-lined
// line
val = comments::format_block_comment(ref.token_str());
const auto comment_lines = comments::format_block_comment(ref.token_str());
for (const auto& line : comment_lines) {
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 &&
(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
@ -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 &&
(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.at(0) += fmt::format(" {}", str_util::ltrim(form_lines.at(1)));
form_lines.erase(form_lines.begin() + 1);
} else if ((i + 1) < (int)curr_node.refs.size()) {
const auto& next_ref = curr_node.refs.at(i + 1);
// combine the next inline comment or constant pair
if ((next_ref.metadata.node_type == "comment" && next_ref.metadata.is_inline) ||
(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
// has issues with not consolidating first lines, this should probably just be moved to
// outside this loop for simplicity, do it later
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++;
// 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)) {
const auto& lines = apply_formatting(next_ref, {}, cursor_pos); // TODO - cursor pos
for (const auto& line : lines) {
form_lines.at(form_lines.size() - 1) += fmt::format(" {}", line);
}
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" &&
(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
// 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
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 = {};
const auto original_form_head_width = str_util::split(form_lines.at(0), '\n').at(0).length();
bool consolidating_lines = true;
@ -374,22 +428,32 @@ std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
form_lines[form_lines.size() - 1] =
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) {
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 {
bool currently_in_block_comment = false;
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("{}{}",
str_util::repeat(curr_node.formatting_config.indentation_width_for_index(
curr_node.formatting_config, i),
" "),
line);
}
if (str_util::contains(line, "#|") && !str_util::contains(line, "|#")) {
currently_in_block_comment = true;
}
}
}
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 = {
{"list_lit", {"(", ")"}},
{"quoting_lit", {"'"}},
{"unquoting_lit", {","}},
{"quasi_quoting_lit", {"`"}}};
{"list_lit", {"(", ")"}}};
// TODO make an imperative version eventually
// TODO - cleanup duplication
void FormatterTree::construct_formatter_tree_recursive(const std::string& source,
TSNode curr_node,
FormatterTreeNode& tree_node,
std::optional<std::string> node_prefix) {
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;
}
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));
return;
} 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") {
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") {
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 = {};
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++) {
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);
bool skip_node = false;
for (const auto& skippable_content : skippable_nodes) {
@ -129,19 +156,22 @@ void FormatterTree::construct_formatter_tree_recursive(const std::string& source
continue;
}
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) {
list_node.node_prefix = node_prefix;
}
} else {
construct_formatter_tree_recursive(source, child_node, tree_node, next_node_prefix);
// TODO - im not sure if this is correct
construct_formatter_tree_recursive(source, child_node, tree_node, node_prefix);
if (node_prefix && !tree_node.refs.empty()) {
tree_node.refs.at(tree_node.refs.size() - 1).node_prefix = node_prefix;
}
}
}
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);
}
}

View File

@ -12,12 +12,16 @@ namespace formatter_rules {
// differentiate between a quoted symbol and a quoted form
const std::set<std::string> constant_types = {"kwd_lit", "num_lit", "str_lit",
"char_lit", "null_lit", "bool_lit"};
const std::set<std::string> constant_type_forms = {"meters", "seconds", "degrees"};
namespace constant_list {
bool is_constant_list(const FormatterTreeNode& node) {
if (!node.is_list() || node.refs.empty()) {
return false;
}
if (!node.refs.at(0).token) {
return true;
}
const auto& type = node.refs.at(0).metadata.node_type;
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) {
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() &&
containing_node.refs.at(index + 1).metadata.is_comment &&
containing_node.refs.at(index + 1).metadata.is_inline) {
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;
}
} // namespace blank_lines
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
// 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.
@ -71,12 +82,22 @@ std::string format_block_comment(const std::string& comment) {
// Remove trailing whitespace
comment_contents = str_util::rtrim(comment_contents);
// remove |#
// TODO - check suffix
comment_contents.pop_back();
comment_contents.pop_back();
if (str_util::ends_with(comment_contents, "|#")) {
comment_contents.pop_back();
comment_contents.pop_back();
}
comment_contents = str_util::rtrim(comment_contents);
new_comment += fmt::format("\n{}\n|#", comment_contents);
return new_comment;
std::vector<std::string> lines = {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
@ -101,6 +122,46 @@ bool is_element_second_in_constant_pair(const FormatterTreeNode& containing_node
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) {
// Criteria for a list to be constant paired:
// - 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
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
@ -72,6 +72,8 @@ const static int min_pair_amount = 4;
bool is_element_second_in_constant_pair(const FormatterTreeNode& containing_node,
const FormatterTreeNode& node,
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);
} // namespace constant_pairs

View File

@ -1,24 +1,146 @@
#include "rule_config.h"
#include "common/util/string_util.h"
namespace formatter_rules {
namespace config {
static FormFormattingConfig new_inlinable_simple_flow_rule() {
return {.config_set = true, .hang_forms = false};
}
static FormFormattingConfig new_permissive_flow_rule() {
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,
.hang_forms = false,
.inline_until_index = [start_index](const std::vector<std::string>& /*curr_lines*/) {
return start_index;
}};
.inline_until_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(
int start_index,
int num_columns_to_compute_widths,
const std::vector<int>& inlining_preventation_indices) {
FormFormattingConfig cfg;
cfg.has_constant_pairs = true;
cfg.config_set = true;
cfg.hang_forms = false;
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;
if (index == 3) {
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);
}
@ -74,6 +197,29 @@ static FormFormattingConfig new_binding_rule(int form_head_width) {
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) {
FormFormattingConfig cfg;
cfg.config_set = true;
@ -88,23 +234,71 @@ static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) {
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 = {
{"case", new_pair_rule(true)},
{"case-str", new_pair_rule(true)},
{"cond", new_pair_rule(false)},
{"defmethod", new_flow_rule(3)},
{"deftype", new_deftype_rule(3, {3, 4, 5, 6})},
{"#cond", new_pair_rule(false)},
{"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-recursive", new_flow_rule(4)},
{"defun-debug-recursive", new_flow_rule(4)},
{"defun-debug", new_flow_rule(3)},
{"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()},
{"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()},
{"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)},
{"dolist", new_flow_rule(2)},
{"process-spawn-function", new_flow_rule(2)},
{"let", new_binding_rule(4)},
{"protect", new_binding_rule(4)},
{"let*", new_binding_rule(5)},
{"rlet", new_binding_rule(5)},
{"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)},
{"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)}};
} // namespace config
} // namespace formatter_rules

View File

@ -7,7 +7,6 @@
#include <unordered_map>
#include <vector>
// TODO - some way to apply a config to all list elements (index configs with -1?)
namespace formatter_rules {
namespace config {
struct FormFormattingConfig {
@ -33,9 +32,14 @@ struct FormFormattingConfig {
int parent_mutable_extra_indent = 0;
std::optional<std::shared_ptr<FormFormattingConfig>> default_index_config;
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;
int num_columns_to_compute_widths = 0;
std::vector<int> list_element_column_widths = {};
bool elide_top_level_newline = false;
};
extern const std::unordered_map<std::string, FormFormattingConfig> opengoal_form_config;

View File

@ -26,6 +26,8 @@ u64 get_current_tid() {
}
#endif
#include "common/log/log.h"
#include "common/util/compress.h"
#include "common/util/string_util.h"
// clang-format on
u64 get_current_ts() {
@ -34,12 +36,12 @@ u64 get_current_ts() {
GlobalProfiler::GlobalProfiler() {
m_t0 = get_current_ts();
set_max_events(65536);
m_nodes.resize(m_max_events);
}
void GlobalProfiler::set_max_events(size_t event_count) {
ASSERT(!m_enabled);
m_nodes.resize(event_count);
void GlobalProfiler::update_event_buffer_size(size_t new_size) {
m_max_events = new_size;
m_nodes.resize(m_max_events);
}
void GlobalProfiler::set_waiting_for_event(const std::string& event_name) {
@ -72,6 +74,10 @@ void GlobalProfiler::root_event() {
instant_event("ROOT");
}
size_t GlobalProfiler::get_next_idx() {
return (m_next_idx % m_nodes.size());
}
void GlobalProfiler::begin_event(const char* name) {
event(name, ProfNode::BEGIN);
}
@ -96,8 +102,10 @@ void GlobalProfiler::set_enable(bool en) {
m_enabled = en;
}
void GlobalProfiler::dump_to_json(const std::string& path) {
ASSERT(!m_enabled);
void GlobalProfiler::dump_to_json() {
if (m_enabled) {
set_enable(false);
}
nlohmann::json json;
auto& trace_events = json["traceEvents"];
@ -185,7 +193,20 @@ void GlobalProfiler::dump_to_json(const std::string& path) {
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;

View File

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

View File

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

View File

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

View File

@ -140,7 +140,7 @@ void printstd(const char* format, va_list 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;
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(),
std::regex(fmt::format("{}\\.(\\d\\.)?log", filename)));
for (const auto& file : old_log_files) {
lg::info("removing {}", file.string());
lg::debug("removing {}", file.string());
fs::remove(file);
}
// 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
existing_log_files = file_util::sort_filepaths(existing_log_files, true);
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++) {
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));
}
}

View File

@ -7,15 +7,21 @@
namespace REPL {
void to_json(json& j, const Config& obj) {
j = json{
{"nreplPort", obj.nrepl_port},
{"gameVersionFolder", obj.game_version_folder},
{"numConnectToTargetAttempts", obj.target_connect_attempts},
{"asmFileSearchDirs", obj.asm_file_search_dirs},
{"keybinds", obj.keybinds},
{"perGameHistory", obj.per_game_history},
{"permissiveRedefinitions", obj.permissive_redefinitions},
};
}
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")) {
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")) {
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 (j.contains(version_to_game_name(obj.game_version))) {
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 from_json(const json& j, KeyBind& obj);
// TODO - per-game config
struct Config {
GameVersion game_version;
Config(GameVersion _game_version) : game_version(_game_version){};
// this is the default REPL configuration
int nrepl_port = 8181;
int temp_nrepl_port = -1;
std::string game_version_folder;
int target_connect_attempts = 30;
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, "N", "Full build of the game", "(mi)"}};
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 from_json(const json& j, Config& obj);

View File

@ -27,7 +27,18 @@ ReplServer::~ReplServer() {
void ReplServer::post_init() {
// 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) {
@ -48,7 +59,6 @@ std::optional<std::string> ReplServer::get_msg() {
// Add the server's main listening socket (where we accept clients from)
FD_SET(listening_socket, &read_sockets);
int max_sd = listening_socket;
for (const int& sock : client_sockets) {
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.
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
struct timeval timeout = {0, 100000};
auto activity = select(max_sd + 1, &read_sockets, NULL, NULL, &timeout);
if (activity < 0) { // TODO - || error!
if (activity < 0 && errno != EINTR) {
lg::error("[nREPL:{}] select error, returned: {}, errno: {}", tcp_port, activity,
strerror(errno));
return std::nullopt;
}
@ -74,44 +83,46 @@ std::optional<std::string> ReplServer::get_msg() {
socklen_t addr_len = sizeof(addr);
auto new_socket = accept_socket(listening_socket, (sockaddr*)&addr, &addr_len);
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 {
lg::info("[nREPL:{}]: New socket connection: {}:{}:{}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), new_socket);
// Say hello
ping_response(new_socket);
// Track the new socket
if ((int)client_sockets.size() < max_clients) {
client_sockets.insert(new_socket);
} 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
// else its some IO operation on some other socket
//
// 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) {
// Check all clients for activity
for (auto it = client_sockets.begin(); it != client_sockets.end();) {
int sock = *it;
if (FD_ISSET(sock, &read_sockets)) {
// 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());
if (req_bytes == 0) {
// Socket disconnected
if (req_bytes <= 0) {
// TODO - add a queue of messages in the REPL::Wrapper so we can print _BEFORE_ the prompt
// is output
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), sock);
if (req_bytes == 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));
}
// Cleanup the socket and remove it from our set
close_socket(sock);
client_sockets.erase(sock);
it = client_sockets.erase(it); // Erase and move to the next element
continue;
} else {
// Otherwise, process the message
auto* header = (ReplServerHeader*)(header_buffer.data());
@ -119,7 +130,12 @@ std::optional<std::string> ReplServer::get_msg() {
int expected_size = header->length;
int got = 0;
int tries = 0;
bool skip_to_next_socket = false;
while (got < expected_size) {
if (want_exit_callback()) {
lg::warn("[nREPL:{}] Terminating nREPL early", tcp_port);
return std::nullopt;
}
tries++;
if (tries > 100) {
break;
@ -131,11 +147,25 @@ std::optional<std::string> ReplServer::get_msg() {
tcp_port, got, expected_size, buffer.size());
return std::nullopt;
}
auto x = read_from_socket(sock, buffer.data() + got, expected_size - got);
if (want_exit_callback()) {
return std::nullopt;
auto bytes_read = read_from_socket(sock, buffer.data() + got, expected_size - got);
if (bytes_read <= 0) {
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) {
@ -149,6 +179,7 @@ std::optional<std::string> ReplServer::get_msg() {
}
}
}
++it;
}
return std::nullopt;
}

View File

@ -27,5 +27,6 @@ class ReplServer : public XSocketServer {
fd_set read_sockets;
std::set<int> client_sockets = {};
void error_response(int socket, const std::string& error);
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/json_util.h"
@ -8,36 +8,66 @@
#include "fmt/color.h"
#include "fmt/core.h"
#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 {
void Wrapper::clear_screen() {
repl.clear_screen();
}
void Wrapper::print_welcome_message() {
// TODO - dont print on std-out
// Welcome message / brief intro for documentation
std::string ascii;
ascii += " _____ _____ _____ _____ __ \n";
ascii += "| |___ ___ ___| __| | _ | | \n";
ascii += "| | | . | -_| | | | | | | |__ \n";
ascii += "|_____| _|___|_|_|_____|_____|__|__|_____|\n";
ascii += " |_| \n";
fmt::print(fmt::emphasis::bold | fg(fmt::color::orange), ascii);
fmt::print("Welcome to OpenGOAL {}.{}!\n", versions::GOAL_VERSION_MAJOR,
versions::GOAL_VERSION_MINOR);
fmt::print("Run {} or {} for help with common commands and REPL usage.\n",
fmt::styled("(repl-help)", fmt::emphasis::bold | fg(fmt::color::cyan)),
fmt::styled("(repl-keybinds)", fmt::emphasis::bold | fg(fmt::color::cyan)));
fmt::print("Run ");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)");
fmt::print(" to connect to the local target.\n");
fmt::print("Run ");
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(mi)");
fmt::print(" to rebuild the entire game.\n\n");
void Wrapper::print_welcome_message(const std::vector<std::string>& loaded_projects) {
std::string message;
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " ..:::::..\n");
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .:-----------:.\n");
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .-----.");
message += fmt::format(fmt::emphasis::bold, " Welcome to OpenGOAL {}.{} [{}]",
versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR,
fmt::format(fg(fmt::color::gray), "{}", build_revision()));
if (!username.empty() && username != "#f" && username != "unknown") {
message += fmt::format(fg(fmt::color::light_green), " {}", username);
}
message += "!\n";
message += fmt::format(fmt::emphasis::bold | fg(fmt::color::orange), " .---.");
if (repl_config.game_version == GameVersion::Jak1) {
message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::orange), "jak1"));
} else if (repl_config.game_version == GameVersion::Jak2) {
message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::purple), "jak2"));
} else if (repl_config.game_version == GameVersion::Jak3) {
message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::gold), "jak3"));
} else {
message += fmt::format(" [{}]: ", fmt::format(fg(fmt::color::magenta), "jakx"));
}
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) {
@ -242,20 +272,24 @@ StartupFile load_user_startup_file(const std::string& username, const GameVersio
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 =
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())) {
try {
REPL::Config config(game_version);
auto repl_config_data =
parse_commented_json(file_util::read_text_file(repl_config_path), "repl-config.json");
from_json(repl_config_data, config);
return config;
from_json(repl_config_data, loaded_config);
loaded_config.temp_nrepl_port = nrepl_port;
return loaded_config;
} 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

View File

@ -17,18 +17,23 @@ struct StartupFile {
};
class Wrapper {
replxx::Replxx repl;
public:
std::string username;
Config repl_config;
StartupFile startup_file;
bool nrepl_alive = false;
std::vector<std::string> examples{};
std::vector<std::pair<std::string, replxx::Replxx::Color>> regex_colors{};
Wrapper(GameVersion version) : repl_config(version) {}
Wrapper(const std::string& _username, const Config& config, const StartupFile& startup)
: username(_username), repl_config(config), startup_file(startup) {}
Wrapper(const std::string& _username,
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; }
void init_settings();
void reload_startup_file();
@ -36,7 +41,7 @@ class Wrapper {
// Functionality / Commands
void clear_screen();
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);
const char* readline(const std::string& prompt);
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);
private:
replxx::Replxx repl;
replxx::Replxx::key_press_handler_t commit_text_action(std::string text_to_commit);
std::vector<REPL::KeyBind> keybindings = {};
};
std::string find_repl_username();
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

View File

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

View File

@ -43,7 +43,151 @@ std::vector<std::string> jak2_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

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());
}
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();
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";
if (fs::exists(data_dir) && fs::is_directory(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) {
return true;
}
@ -204,16 +206,20 @@ bool setup_project_path(std::optional<fs::path> 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.initialized = true;
lg::info("Using explicitly set project path: {}",
g_file_path_info.path_to_data_folder.string());
if (!skip_logs) {
lg::debug("Using explicitly set project path: {}",
g_file_path_info.path_to_data_folder.string());
}
return true;
}
auto data_path = try_get_data_dir();
auto data_path = try_get_data_dir(skip_logs);
if (data_path) {
g_file_path_info.path_to_data_folder = *data_path;
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;
}
@ -221,7 +227,9 @@ bool setup_project_path(std::optional<fs::path> project_path_override) {
if (development_repo_path) {
g_file_path_info.path_to_data_folder = *development_repo_path;
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;
}

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

View File

@ -8,4 +8,5 @@ namespace compression {
// compress and decompress data with zstd
std::vector<u8> compress_zstd(const void* data, size_t size);
std::vector<u8> decompress_zstd(const void* data, size_t size);
} // namespace compression
std::vector<u8> compress_zstd_no_header(const void* data, size_t size);
} // 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>
void json_get_optional(const nlohmann::json& json,
void json_get_optional(const nlohmann::json& j,
const std::string& key,
std::optional<T>& optionalValue) {
if (json.contains(key) && !json[key].is_null()) {
optionalValue = json[key].get<T>();
if (j.contains(key) && !j[key].is_null()) {
optionalValue = j[key].get<T>();
}
}

View File

@ -3,20 +3,29 @@
#include "common/common_types.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>
size_t get_peak_rss() {
rusage x;
getrusage(RUSAGE_SELF, &x);
return x.ru_maxrss * 1024;
}
#else
size_t get_peak_rss() {
return 0;
}
#endif
#ifdef _WIN32

View File

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

View File

@ -62,7 +62,7 @@
// Note: You will still have to add them to your level's .gd file.
// "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.
"custom_models": ["test-actor"],

View File

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

View File

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

View File

@ -5,6 +5,7 @@
#include "ObjectFileDB.h"
#include "common/formatter/formatter.h"
#include "common/goos/PrettyPrinter.h"
#include "common/link_types.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");
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");
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());
// 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 (version > GameVersion::Jak2) {
initial_top_level_forms.push_back(
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));
}
initial_top_level_forms.push_back(
goos::StringObject::make_new(fix_docstring_indent(inline_body.at(0).as_string()->data)));
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>>();
}
config.audio_dir_file_name = inputs_json.at("audio_dir_file_name").get<std::string>();
config.streamed_audio_file_names =
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.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_scripts = json.at("write_scripts").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")) {
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")) {
config.animated_textures =

View File

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

View File

@ -21102,7 +21102,7 @@
:size-assert #x94
:flag-assert #xf00300094
(:methods
(dumb-15 (_type_) none))
(spawn-particles! (_type_) none))
(:states
hud-normal
hud-coming-in
@ -22303,20 +22303,20 @@
;; - Functions
(define-extern calculate-rotation-and-color-for-slice (function int float int int int hud-particle 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-02-func (function basic basic hud-particle none))
(define-extern part-hud-health-03-func (function basic basic 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 sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-health-02-func (function sparticle-system sparticle-cpuinfo matrix 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-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-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-eco-timer-01-func (function basic basic hud-particle none))
(define-extern part-hud-eco-timer-02-func (function basic basic hud-particle none))
(define-extern part-hud-eco-timer-03-func (function basic basic hud-particle none))
(define-extern part-hud-eco-timer-backing-func (function basic basic hud-particle none))
(define-extern part-hud-eco-timer-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 sparticle-system sparticle-cpuinfo matrix 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 sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-backing-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern part-hud-eco-timer-func (function sparticle-system sparticle-cpuinfo matrix none))
(define-extern show-hud (function none))
;; - Unknowns

View File

@ -23,6 +23,10 @@
// run the first pass of the decompiler
"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
////////////////////////////
@ -118,6 +122,9 @@
// save game textures as .png files to decompiler_out/<game>/textures
"save_texture_pngs": false,
// whether or not to dump out streamed audio files to decompiler_out/<game>/audio
"rip_streamed_audio": false,
////////////////////////////
// PATCHING OPTIONS
////////////////////////////

View File

@ -258,10 +258,6 @@
"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.FRE",

View File

@ -228,9 +228,5 @@
"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"]
}

View File

@ -2515,6 +2515,7 @@
(iop-mem 48)
(cancel-dgo 49)
(set-stereo-mode 50) ;; sound-rpc-set-stereo-mode
(set-mirror 201)
)
(defenum sound-group
@ -2829,6 +2830,17 @@
: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)
((data uint32 20 :offset-assert 0)
(load-bank sound-rpc-load-bank :offset 0)
@ -2852,6 +2864,7 @@
(shutdown sound-rpc-shutdown :offset 0)
(list-sounds sound-rpc-list-sounds :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
:size-assert #x50

View File

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

View File

@ -441,10 +441,6 @@
"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": [
"VAGWAD.ENG",
"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_texture_pngs": false,
// whether or not to dump out streamed audio files to decompiler_out/<game>/audio
"rip_streamed_audio": false,
////////////////////////////
// PATCHING OPTIONS
////////////////////////////

View File

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

View File

@ -333,10 +333,6 @@
"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": [
"VAGWAD.ENG",
"VAGWAD.FRE",
@ -349,15 +345,331 @@
"animated_textures": [
// dark jak
"jakc-arm", "jakc-eyebrow", "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",
"jakc-arm",
"jakc-eyebrow",
"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-dest",
"skull-gem-alpha-00",
"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": [

View File

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

View File

@ -515,8 +515,44 @@ TPageResultStats process_tpage(ObjectFileData& data,
// currently not needed.
break;
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)
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,
tex.w, tex.h, tex.name, texture_page.name, level_names);
stats.successful_textures++;
} else {
ASSERT_NOT_REACHED();
}
break;
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/unicode_util.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "decompiler/config.h"
#include "decompiler/decompilation_process.h"
#include "decompiler/extractor/extractor_util.h"
#include "decompiler/level_extractor/extract_level.h"
#include "goalc/compiler/Compiler.h"
#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`
void decompile(const fs::path& iso_data_path,
const std::string& data_subfolder,
const std::string& config_override) {
using namespace decompiler;
ExtractorErrorCode decompile(const fs::path& in_folder,
const std::string& data_subfolder,
const std::string& config_override) {
// 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" /
version_info.game_name /
fmt::format("{}_config.jsonc", version_info.game_name),
version_info.decomp_config_version, config_override);
decompiler::Config config = decompiler::read_config_file(
file_util::get_jak_project_dir() / "decompiler" / "config" / version_info.game_name /
fmt::format("{}_config.jsonc", version_info.game_name),
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 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
db.process_link_data(config);
db.find_code(config);
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);
const auto result = run_decompilation_process(config, in_folder, out_folder, true);
if (result != 0) {
return ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR;
}
return ExtractorErrorCode::SUCCESS;
}
const std::unordered_map<std::string, GameIsoFlags> game_iso_flag_names = {
@ -315,7 +222,6 @@ int main(int argc, char** argv) {
}
// - SETUP
decompiler::init_opcode_info();
if (!project_path_override.empty()) {
if (!fs::exists(project_path_override)) {
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
const auto [hash, file_count] = calculate_extraction_hash(iso_data_path);
// 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
@ -455,7 +365,10 @@ int main(int argc, char** argv) {
if (flag_decompile) {
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) {
lg::error("Error during decompile: {}", e.what());
return static_cast<int>(ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR);
@ -463,7 +376,10 @@ int main(int argc, char** argv) {
}
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) {

View File

@ -322,7 +322,8 @@ void extract_common(const ObjectFileDB& db,
compressed.data(), compressed.size());
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);
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) {
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);
save_level_background_as_gltf(level_data, back_file_path);
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);
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));
}

View File

@ -1,5 +1,7 @@
#include "extract_merc.h"
#include "merc_replacement.h"
#include "common/log/log.h"
#include "common/util/BitUtils.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
*/
@ -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

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 <vector>
#include "config.h"
#include "decompilation_process.h"
#include "common/log/log.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/term_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/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) {
ArgumentGuard u8_guard(argc, argv);
@ -81,11 +63,9 @@ int main(int argc, char** argv) {
return 1;
}
using namespace decompiler;
Config config;
decompiler::Config config;
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) {
lg::error("Failed to parse config: {}", e.what());
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
// 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(
"WARNING - '{}' does not contain the expected ELF file '{}'. Was the ISO extracted "
"properly or is there a version mismatch?",
@ -123,235 +103,5 @@ int main(int argc, char** argv) {
}
// -- Begin the Decompilation!
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;
return run_decompilation_process(config, in_folder, out_folder, false);
}

View File

@ -2576,7 +2576,6 @@ void CallOp::propagate_types2(types2::Instruction& instr,
if (can_use_call_parent) {
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;
}
}

View File

@ -11,7 +11,7 @@
- This file generates no code.
## `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`:
- Many changes for x86-64/OpenGOAL
@ -57,7 +57,7 @@
- No comments
## `euler-h`: **Done**
- Uses boxed arrays
- Uses boxed arrays.
## `transform-h`: **Done**
- No comments
@ -69,7 +69,7 @@
- Empty
## `transformq-h`: waiting on stack stuff
- Needs stack stuff
- Needs stack stuff.
## `bounding-box`: asm
@ -84,7 +84,7 @@
## `geometry`: asm
## `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**
@ -116,19 +116,19 @@
## `dma-disasm`: In progress
- 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.
## `pad`: **Done**
## `gs`: **Done**
- Missing bitfields
- Missing bitfields.
## `display-h`: **Done**
## `vector`: asm
- 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:
- `rand-vu-sphere-point!`
- `vector-deg-lerp-clamp!`
@ -158,4 +158,4 @@
## `fileio`: **Done**
## `loader-h`: **Done**
- Good one for playing with inlined basics.
- Good one for playing with inlined basics.

View File

@ -201,6 +201,7 @@ set(RUNTIME_SOURCE
mips2c/jak3_functions/sky.cpp
mips2c/jak3_functions/texture.cpp
mips2c/jak3_functions/particle_curves.cpp
mips2c/jak3_functions/cloth.cpp
mips2c/jak3_functions/collide_cache.cpp
mips2c/jak3_functions/collide_hash.cpp
mips2c/jak3_functions/collide_edge_grab.cpp
@ -254,6 +255,29 @@ set(RUNTIME_SOURCE
overlord/jak2/streamlfo.cpp
overlord/jak2/streamlist.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
sce/deci2.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Á."
],
"assistant-village2-introduction": [
"WHOA!",
"UAU!",
"ACHO QUE NUNCA VOU ME ACOSTUMAR COM ESSE FORMIGAMENTO DO TELEPORTE.",
"HEY!",
"EI!",
"PARECE QUE O SÁBIO AZUL DEU UMA FESTA.",
"MINHA NOSSA! VILA DA PEDRA ESTÁ EM CHAMAS!",
"UMA BELA FESTA.",
@ -200,8 +200,8 @@
"bird-lady-beach-resolution": [
"NOSSA!, EU ESPERO QUE O COITADINHO ESTEJA BEM,",
"TOMA, UMA POWER CELL PELA SUA CORAGEM.",
"MAMA!",
"MAMA!",
"MAMÃE!",
"MAMÃE!",
"OH NÃO! NÃO, NÃO, NÃO, NÃO!",
"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?",
@ -290,14 +290,14 @@
"BONS SONHOS, JAK!"
],
"evilbro-misty-end": [
"THOSE TWO COULD PROVE TO BE TROUBLE.",
"DON'T WORRY DEAR BROTHER, I'LL DISPATCH MY LURKER ARMY TO DEAL WITH THEM.",
"JUST MAKE SURE THEY DON'T GET TOO CLOSE TO OUR NORTHERN OPERATIONS.",
"I DOUBT THEY'LL GET THAT FAR...",
"BUT IF THEY DO, A SURPRISE WILL BE WAITING FOR THEM IN ROCK VILLAGE.",
"SO...YOU'RE GOING TO LET...KLAWW OUT OF HIS CAGE?",
"I THINK IT'S WORTH THE RISK.",
"INDEED..."
"ESSES DOIS PODEM SE TORNAR UM PROBLEMA.",
"NÃO SE PREOCUPE, QUERIDO IRMÃO, ENVIAREI MINHA TROPA DE LURKERS PARA LIDAR COM ELES.",
"SÓ GARANTA QUE ELES NÃO VÃO CHEGAR PERTO DEMAIS DAS OPERAÇÕES AO NORTE.",
"EU DUVIDO QUE ELES CHEGUEM TÃO LONGE...",
"MAS SE CHEGAREM, TERÃO UMA SURPRESA NA VILA DA PEDRA.",
"ENTÃO... VOCÊ VAI DEIXAR... O KLAWW SAIR DA GAIOLA?",
"ACHO QUE VALE O RISCO.",
"DE FATO..."
],
"explorer-introduction": [
"OLÁ MEU QUERIDO MENINO.",
@ -486,7 +486,7 @@
"PARECE QUE ELES COMBINARAM PARTES FUNCIONAIS DO ROBÔ PRECURSOR",
"COM ARTEFATOS RECOLHIDOS PELO MUNDO.",
"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",
"PARA QUEBRAR O CAMPO DE FORÇA EM VOLTA DO ROBÔ,",
"ANTES QUE ELES O USEM PARA DESTRUIR O MUNDO."
@ -507,7 +507,7 @@
"NADA DISSO SERIA POSSÍVEL.",
"HM-HM-HM. TALVEZ TENHAMOS ACHADO UMA NOVA SÁBIA AGORA",
"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...",
"QUEM LIGA? DEIXA VIR, A GENTE APAGA ELES DE NOVO NÉ, JAK?",
"EU DISSE, NÉ, JAK?",
@ -532,10 +532,10 @@
],
"green-sagecage-outro-preboss": [
"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!",
"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Ê!",
"NOS TROUXE BELEZA ALÉM DA SUA COMPREENSÃO.",
"BELEZA? VOCÊS SE OLHARAM NO ESPELHO RECENTEMENTE?",
@ -651,7 +651,7 @@
"WILLARD!!"
],
"minershort-reminder-2-orbs": [
"DUH, GORDY?",
", GORDY?",
"90 ORBES POR UMA POWER CELL, WILLARD! URGH!"
],
"minershort-resolution-1-orbs": [
@ -730,7 +730,7 @@
"BEM, A SITUAÇÃO AQUI TÁ FEDENDO MAIS QUE SOVACO DE LURKER.",
"ANTES DO SUMIÇO DO SÁBIO AZUL,",
"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.",
"KEIRA IRÁ TELEPORTAR O ZOOMER PARA O TRANS-PAD MAIS PRÓXIMO.",
"LEVE O ZOOMER PARA UMA FONTE DE ECO VERDE,",
@ -754,7 +754,7 @@
"sage-bluehut-reminder-1-crop-dusting": [
"MEUS OLHOS DEVEM ESTAR ME ENGANANDO!",
"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": [
"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!",
"FOI ISSO MESMO! E ENTÃ-",
"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,",
"OU VOCÊ VAI ME AJUDAR A SAIR DESSA?",
"VOU FICAR DANDO ESPORRO! PORQUE NA MINHA OPINIÃO PROFISSIONAL,",
"A MUDANÇA FOI UMA MELHORIA.",
"E ALÉM DISSO... EU NÃO PODERIA TE AJUDAR MESMO SE QUISESSE",
"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:",
"GOL ACHERON, O SÁBIO.",
"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",
"ANTES DE QUALQUER COISA, VOCÊS VÃO ENTRAR NO TELEPORTADOR",
"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Ê!",
"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.",
"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.",
"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.",
"JAK, É HORA DE VOCÊ PROVAR O SEU VALOR.",
"PEÇA EMPRESTADO O BARCO DO PESCADOR PARA IR À ILHA DAS NÉVOAS",
@ -895,22 +895,22 @@
"NÃO FOI NEM UM POUCO DIVERTIDO.",
"GOL? É VOCÊ?",
"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...",
"O QUE VOCÊS FIZERAM COM OS SÁBIOS AZUL E VERMELHO?",
"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 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Ã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",
"POUCO QUE ACHÁVAMOS NA SUPERFÍCIE.",
"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!",
"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-",
"OH, NÃO FIQUE TÃO CHATEADO, SAMOS.",
"TEMOS GRANDES PLANOS PARA VOCÊ.",
@ -921,7 +921,7 @@
"GOL É O CARA QUE TÁ TENTANDO NOS MATAR?!",
"TÔ PERDIDO.",
"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!",
"NÓS PRECISAMOS IR À CIDADELA E IMPEDI-LOS!",
"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É?",
"É CLARO QUE TEM ARANHAS NA CAVERNA DAS ARANHAS!",
"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",
"OS MONSTROS CONSIGAM LEVÁ-LOS!",
"É PRA ONTEM!"
@ -946,12 +946,12 @@
"sage-village3-introduction-rams": [
"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,",
"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,",
"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": [
"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": [
"NÃO PODEMOS DEIXAR OS LURKERS BOTAREM AS MÃOS NOS TANQUES DE ECO.",
@ -988,7 +988,7 @@
],
"sidekick-human-intro-sequence-c": [
"O QUE ESTAMOS FAZENDO AQUI, JAK? ESSE LUGAR DÁ MO MEDO!",
"HUH?",
"HÃ?",
"PORCARIA PRECURSOR IDIOTA!",
"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.",
@ -1039,7 +1039,7 @@
"EU NÃO QUERO ALINHAR ESSAS PONTES TÃO CEDO."
],
"warrior-resolution": [
"OH.",
"AH.",
"MARAVILHA.",
"VOCÊS ME TROUXERAM AS ORBES PRECURSORAS. (CHORA)",
"TÁ CERTO.",
@ -1179,7 +1179,7 @@
"PEGOU ELE."
],
"BIL-TA09": [
"YEEHAW!"
"UHUU!"
],
"BIL-TA1A": [
"PEGA ELES!"
@ -1215,7 +1215,7 @@
"VAMOS LÁ, EMPURRE O OVO DO PENHASCO!"
],
"BIR-AM06": [
"OHH!"
"AHH!"
],
"BIR-AM07": [
"OH, AQUI, PASSARIN PASSARIN. AQUI, PASSARIN PASSARIN."
@ -1479,7 +1479,7 @@
"VOCÊ SÓ NÃO DESISTE, NÃO É?"
],
"GOL-AM02": [
"EM BREVE, O ECO NEGRO SERÁ NOSSO!"
"EM BREVE, O ECO SOMBRIO SERÁ NOSSO!"
],
"GOL-AM03": [
"ACABE COM ELES!"
@ -1700,16 +1700,16 @@
"ISSO NÃO É BOM."
],
"SAGELP32": [
"CRISTAIS DE ECO NEGRO?"
"CRISTAIS DE ECO SOMBRIO?"
],
"SAGELP33": [
"MAIA E GOL PERDERAM A CABEÇA!"
],
"SAGELP34": [
"HNG, O PODER DO ECO NEGRO NÃO PODE SER CONTROLADO!"
"HNG, O PODER DO ECO SOMBRIO NÃO PODE SER CONTROLADO!"
],
"SAGELP35": [
"ECO NEGRO NAS GELEIRAS?"
"ECO SOMBRIO NAS GELEIRAS?"
],
"SAGELP36": [
"AH! TEM TROPAS LURKERS NAS MONTANHAS!"
@ -1951,7 +1951,7 @@
"EU TENHO QUE ADMITIR, ESTOU SURPRESO. VOCÊS DOIS NÃO ESTRAGARAM TUDO!",
"AGORA QUE OS LURKERS NÃO CONSEGUEM ABRIR O ARMAZÉM,",
"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,",
"VOCÊS DOIS TEM MUITO O QUE FAZER.",
"AGORA MÃOS À OBRA!"
@ -2045,7 +2045,7 @@
"HEY! AQUELA DELÍCINHA DA KEIRA TINHA RAZÃO, TEM TRANS-PADS AQUI."
],
"sksp0002": [
"CONFIA EM MIM, ESSAS CAIXAS DE ECO NEGRO SÃO PROBLEMA!"
"CONFIA EM MIM, ESSAS CAIXAS DE ECO SOMBRIO SÃO PROBLEMA!"
],
"sksp0003": [
"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!"
],
"sksp0023": [
"WOOHOO!"
"UUUHUU!"
],
"sksp0024": [
"YEAH HA HA! YEAH!"
@ -2232,7 +2232,7 @@
"NÓS PRECISAMOS DE ECO AZUL PRA ENERGIZAR ESSA PLATAFORMA!"
],
"sksp0076": [
"JAK! HIT SOME JUMPS TO KEEP US OFF THE HOT GROUND."
"JAK! DÊ ALGUNS SALTOS PARA NOS MANTER LONGE DO SOLO QUENTE."
],
"sksp0077": [
"BALÕES BOM, MAGMA QUENTE FLAMEJANTE RUIM!"
@ -2251,7 +2251,7 @@
"AHH! TALVEZ EU DEVESSE PILOTAR!"
],
"sksp0082": [
"VOCÊ TÁ TENTANDO EVITAR AS CAIXAS DE ECO NEGRO, NÉ?!"
"VOCÊ TÁ TENTANDO EVITAR AS CAIXAS DE ECO SOMBRIO, NÉ?!"
],
"sksp0083": [
"OOH! VÊ SE DÁ PRA PEGAR IMPULSO BATENDO NOS LURKERS!"
@ -2302,7 +2302,7 @@
"AEEE, MAIS ORBES!"
],
"sksp009c": [
"FAZ FAVOR E FICA LONGE DAS CAIXAS DE ECO NEGRO"
"FAZ FAVOR E FICA LONGE DAS CAIXAS DE ECO SOMBRIO"
],
"sksp009d": [
"YEAH-HA-HA-HA!"
@ -2330,7 +2330,7 @@
],
"sksp0110": [
"EU JÁ FALEI ANTES, VOU FALAR DE NOVO...",
"EVITE AS CAIXAS DE ECO NEGRO!"
"EVITE AS CAIXAS DE ECO SOMBRIO!"
],
"sksp0111": [
"VAMOS LEVAR ESSAS TOUPEIRAS DE VOLTA AO SUBSOLO."
@ -2385,10 +2385,10 @@
"CHUTA O CARINHA NO TOPO!"
],
"sksp0128": [
"JAK! CORRE! O ECO NEGRO ESTÁ SUBINDO!"
"JAK! CORRE! O ECO SOMBRIO ESTÁ SUBINDO!"
],
"sksp0129": [
"AHH! O ECO NEGRO TÁ CHEGANDO PERTO"
"AHH! O ECO SOMBRIO TÁ CHEGANDO PERTO"
],
"sksp0130": [
"UH, ACHO QUE TEMOS QUE ATIVAR TODAS AS PLATAFORMAS."
@ -2559,16 +2559,16 @@
"CONSEGUIMOS! IMPEDIMOS ELES DE DETONAR A PASSAGEM!"
],
"sksp0327": [
"ACHO QUE ESSE FOI O ÚLTIMO CRISTAL DE ECO NEGRO!"
"ACHO QUE ESSE FOI O ÚLTIMO CRISTAL DE ECO SOMBRIO!"
],
"sksp0328": [
"TALVEZ A GENTE ATIRE MELHOR SE VOCÊ MIRAR COM A SUA LENTE."
],
"sksp0329": [
"OH, NÃO! MAIS ECO NEGRO!"
"OH, NÃO! MAIS ECO SOMBRIO!"
],
"sksp0330": [
"NÃO ACHO QUE PEGAMOS TODOS OS CRISTAIS DE ECO NEGRO."
"NÃO ACHO QUE PEGAMOS TODOS OS CRISTAIS DE ECO SOMBRIO."
],
"sksp0331": [
"ATIRE NOS LURKERS COMENDO AS PILARES!"
@ -2655,7 +2655,7 @@
"DESVIE OU ATIRE NAS MINAS, JAK!"
],
"sksp0367": [
"ACHE UM CAMINHO ENTRE OS BARRIS DE ECO NEGRO!"
"ACHE UM CAMINHO ENTRE OS BARRIS DE ECO SOMBRIO!"
],
"sksp0368": [
"CONTINUE ACERTANDO ESSES BALÕES DE RESFRIAMENTO!"
@ -2854,7 +2854,7 @@
"KEIRA": "KEIRA",
"MAIA": "MAIA",
"MAYOR": "PREFEITO",
"MINER": "MINER",
"MINER": "MINEIRO",
"OLD MAN": "VELHO",
"ORACLE": "ORÁCULO",
"RED SAGE": "SÁBIO VERMELHO",

View File

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

View File

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

View File

@ -219,7 +219,7 @@
"1610": "MAUS OPTIONEN",
"1611": "TASTENBELEGUNG ÄNDERN",
"1612": "CONTROLLER ~D",
"1613": "CURSOR VERSTECKEN",
"1613": "CURSOR AUTOMATISCH VERSTECKEN",
"1614": "UNBELEGT",
"1615": "UNBEKANNT",
"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",
"krew": "Krew",
"metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog",
"onin": "Onin",
"oracle": "Oracle",
@ -7852,4 +7854,4 @@
"youngsamos": "Young Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

View File

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

View File

@ -7839,6 +7839,8 @@
"kor": "Kor",
"krew": "Krew",
"metalkor": "Kor Cabezachapa",
"metalkor-before-consite": "Jefe Cabezachapa",
"metalkor-intro": "???",
"mog": "Mog",
"onin": "Onin",
"oracle": "Oráculo",
@ -7852,4 +7854,4 @@
"youngsamos": "Joven Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

View File

@ -9900,4 +9900,4 @@
"youngsamos": "Nuori Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

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",
"krew": "Krew",
"metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog",
"onin": "Onin",
"oracle": "Oracle",
@ -7852,4 +7854,4 @@
"youngsamos": "Young Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor",
"krew": "Krew",
"metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog",
"onin": "Onin",
"oracle": "Oracle",
@ -7852,4 +7854,4 @@
"youngsamos": "Young Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

View File

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

View File

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

View File

@ -7839,6 +7839,8 @@
"kor": "Koras",
"krew": "Krew",
"metalkor": "Metalinis Koras",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mogas",
"onin": "Oninas",
"oracle": "Oraklė",
@ -7852,4 +7854,4 @@
"youngsamos": "Jaunasis Samosas",
"youngsamos-before-rescue": "Samosas"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7839,6 +7839,8 @@
"kor": "Kor",
"krew": "Krew",
"metalkor": "Metal Kor",
"metalkor-before-consite": "Metal Head Leader",
"metalkor-intro": "???",
"mog": "Mog",
"onin": "Onin",
"oracle": "Oracle",
@ -7852,4 +7854,4 @@
"youngsamos": "Young Samos",
"youngsamos-before-rescue": "Samos"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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