all-types: Improve Jak 2's all-types (#1728)

* all-types: improve all-types generation

* all-types: re-generate all-types

* tests: remove the test reporting feature

the format indeed doesn't work, and all current actions require too many permissions for forked PRs.

I'll make my own eventually that works properly (use the new markdown feature)

* all-types: put the states in the method table instead

* all-types: replace all `*time*...uint64` fields with `time-frame` type

* all-types: address feedback
This commit is contained in:
Tyler Wilding
2022-08-05 17:39:32 -04:00
committed by GitHub
parent c269374e24
commit a66ec7c601
14 changed files with 8227 additions and 8389 deletions
-8
View File
@@ -56,14 +56,6 @@ jobs:
GTEST_OUTPUT: "xml:opengoal-test-report.xml"
run: ./test.sh
- name: Generate Test Report
uses: dorny/test-reporter@v1
if: success() || failure() # run this step even if previous step failed
with:
name: Linux Clang - Test Report
path: ${{ github.workspace }}/**/opengoal-test-report.xml
reporter: jest-junit
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
-8
View File
@@ -58,14 +58,6 @@ jobs:
GTEST_OUTPUT: "xml:opengoal-test-report.xml"
run: ninja goalc-test_coverage -w dupbuild=warn
- name: Generate Test Report
uses: dorny/test-reporter@v1
if: success() || failure() # run this step even if previous step failed
with:
name: Linux GCC - Test Report
path: ${{ github.workspace }}/**/opengoal-test-report.xml
reporter: jest-junit
- name: Submit Coverage Report to Codacy
uses: codacy/codacy-coverage-reporter-action@v1
continue-on-error: true
@@ -52,17 +52,3 @@ jobs:
GTEST_OUTPUT: "xml:opengoal-test-report.xml"
run: ./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=1 --gtest_filter="-*MANUAL_TEST*"
- name: Generate Test Report
uses: dorny/test-reporter@v1
if: success() || failure() # run this step even if previous step failed
with:
name: Windows Clang - Test Report
path: ./opengoal-test-report.xml
reporter: jest-junit
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: opengoal-windows-${{ inputs.cachePrefix }}
if-no-files-found: error
path: ./build/bin
@@ -54,10 +54,3 @@ jobs:
run: |
./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=1 --gtest_filter="-*MANUAL_TEST*"
- name: Generate Test Report
uses: dorny/test-reporter@v1
if: success() || failure() # run this step even if previous step failed
with:
name: Windows MSVC - Test Report
path: ./opengoal-test-report.xml
reporter: jest-junit
+2
View File
@@ -9,6 +9,8 @@ dotenv:
tasks:
# SETTINGS / CONFIGURATION
settings:
- 'python ./scripts/tasks/update-env.py --info'
set-game-jak1:
- 'python ./scripts/tasks/update-env.py --game jak1'
set-game-jak2:
+12
View File
@@ -192,6 +192,18 @@ class ObjectFileDB {
void ir2_do_segment_analysis_phase2(int seg, const Config& config, ObjectFileData& data);
void ir2_setup_labels(const Config& config, ObjectFileData& data);
void ir2_run_mips2c(const Config& config, ObjectFileData& data);
struct PerObjectAllTypeInfo {
std::string object_name;
std::unordered_set<std::string> already_seen_symbols;
// type-name : { method id : state name }
std::unordered_map<std::string, std::unordered_map<int, std::string>> state_methods;
// symbol-name : type-name
std::unordered_map<std::string, std::string> symbol_types;
std::vector<std::string> type_defs;
std::string symbol_defs;
};
void ir2_analyze_all_types(const fs::path& output_file,
const std::optional<std::string>& previous_game_types,
const std::unordered_set<std::string>& bad_types);
+21 -14
View File
@@ -301,40 +301,47 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
void ObjectFileDB::ir2_analyze_all_types(const fs::path& output_file,
const std::optional<std::string>& previous_game_types,
const std::unordered_set<std::string>& bad_types) {
struct PerObject {
std::string object_name;
std::vector<std::string> type_defs;
std::string symbol_defs;
};
std::vector<PerObject> per_object;
std::vector<PerObjectAllTypeInfo> per_object;
DecompilerTypeSystem previous_game_ts(GameVersion::Jak1); // version here doesn't matter.
if (previous_game_types) {
previous_game_ts.parse_type_defs({*previous_game_types});
}
std::unordered_set<std::string> already_seen;
TypeInspectorCache ti_cache;
for_each_obj([&](ObjectFileData& data) {
if (data.obj_version != 3) {
return;
}
auto& object_result = per_object.emplace_back();
object_result.object_name = data.to_unique_name();
// Go through the top-level segment first to identify the type names associated with each symbol
// def
for_each_function_in_seg_in_obj(TOP_LEVEL_SEGMENT, data, [&](Function& f) {
inspect_top_level_for_metadata(f, data.linked_data, dts, previous_game_ts, object_result);
});
// Handle the top level last, which is fine as all symbol_defs are always written after typedefs
for_each_function_def_order_in_obj(data, [&](Function& f, int seg) {
if (seg == TOP_LEVEL_SEGMENT) {
object_result.symbol_defs += inspect_top_level_symbol_defines(
already_seen, f, data.linked_data, dts, previous_game_ts);
} else {
if (seg != TOP_LEVEL_SEGMENT) {
if (f.is_inspect_method && bad_types.find(f.guessed_name.type_name) == bad_types.end()) {
object_result.type_defs.push_back(inspect_inspect_method(
f, f.guessed_name.type_name, dts, data.linked_data, previous_game_ts.ts, ti_cache));
object_result.type_defs.push_back(
inspect_inspect_method(f, f.guessed_name.type_name, dts, data.linked_data,
previous_game_ts, ti_cache, object_result));
} else {
// no inspect methods
// - can we solve custom print methods in a generic way? ie `entity-links`
}
}
});
for_each_function_in_seg_in_obj(TOP_LEVEL_SEGMENT, data, [&](Function& f) {
object_result.symbol_defs += inspect_top_level_symbol_defines(
f, data.linked_data, dts, previous_game_ts, object_result);
});
});
std::string result;
+200 -19
View File
@@ -526,8 +526,7 @@ int get_start_idx(Function& function,
if (!type_name_str) {
fmt::print("[iim] op7 bad in {}: {} (bad string)\n", aos.ops.at(op_idx)->to_string(env),
function.name());
}
if (type_name_str != "[~8x] ~A~%") {
} else if (type_name_str != "[~8x] ~A~%") {
fmt::print("[iim] op7 bad in {}: {} (bad string: {})\n", aos.ops.at(op_idx)->to_string(env),
function.name(), *type_name_str);
}
@@ -907,8 +906,9 @@ std::string inspect_inspect_method(Function& inspect_method,
const std::string& type_name,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
TypeSystem& previous_game_ts,
TypeInspectorCache& ti_cache) {
DecompilerTypeSystem& previous_game_ts,
TypeInspectorCache& ti_cache,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta) {
fmt::print(" iim: {}\n", inspect_method.name());
TypeInspectorResult result;
ASSERT(type_name == inspect_method.guessed_name.type_name);
@@ -920,7 +920,17 @@ std::string inspect_inspect_method(Function& inspect_method,
result.flags = flags.flag;
result.type_size = flags.size;
result.type_method_count = flags.methods;
result.type_heap_base = flags.heap_base;
// Only set heap-base if it's different from the automatic one
// A child (or child of a child) of process ALWAYS has heap-base set.
if (flags.heap_base > 0) {
auto process_type = dts.ts.get_type_of_type<BasicType>("process");
auto auto_hb = (flags.size - process_type->size() + 0xf) & ~0xf;
if (auto_hb != flags.heap_base) {
result.type_heap_base = std::make_optional(flags.heap_base);
}
}
{
TypeFlags parent_flags;
@@ -942,25 +952,27 @@ std::string inspect_inspect_method(Function& inspect_method,
idx = get_start_idx_process(inspect_method, result.parent_type_name, inspect_method.ir2.env);
}
StructureType* old_game_type = nullptr;
if (previous_game_ts.fully_defined_type_exists(type_name)) {
old_game_type = dynamic_cast<StructureType*>(previous_game_ts.lookup_type(type_name));
if (previous_game_ts.ts.fully_defined_type_exists(type_name)) {
old_game_type = dynamic_cast<StructureType*>(previous_game_ts.ts.lookup_type(type_name));
}
if (idx <= 0) {
// can't get any field...
result.warnings += "Failed to read fields. ";
result.warnings += "Failed to read fields.";
idx = -2;
ti_cache.previous_results[type_name] = result;
return result.print_as_deftype(old_game_type, ti_cache.previous_results);
return result.print_as_deftype(old_game_type, ti_cache.previous_results, previous_game_ts,
object_file_meta);
}
while (idx < int(inspect_method.ir2.atomic_ops->ops.size()) - 2 && idx != -1) {
idx = detect(idx, inspect_method, file, &result);
}
if (idx == -1) {
result.warnings += "Failed to read some fields. ";
result.warnings += "Failed to read some fields.";
}
ti_cache.previous_results[type_name] = result;
return result.print_as_deftype(old_game_type, ti_cache.previous_results);
return result.print_as_deftype(old_game_type, ti_cache.previous_results, previous_game_ts,
object_file_meta);
}
std::string old_method_string(const MethodInfo& info) {
@@ -1013,7 +1025,9 @@ bool allow_guess(const Field& field) {
*/
std::string TypeInspectorResult::print_as_deftype(
StructureType* old_game_type,
std::unordered_map<std::string, TypeInspectorResult>& previous_results) {
std::unordered_map<std::string, TypeInspectorResult>& previous_results,
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta) {
std::string result;
result += "#|\n";
@@ -1148,6 +1162,9 @@ std::string TypeInspectorResult::print_as_deftype(
result.append(fmt::format(" :method-count-assert {}\n", type_method_count));
result.append(fmt::format(" :size-assert #x{:x}\n", type_size));
if (type_heap_base.has_value()) {
result.append(fmt::format(" :heap-base #x{:x}\n", type_heap_base.value()));
}
result.append(fmt::format(" :flag-assert #x{:x}\n ", flags));
if (!warnings.empty()) {
result.append(";; ");
@@ -1155,6 +1172,11 @@ std::string TypeInspectorResult::print_as_deftype(
result.append("\n ");
}
std::unordered_map<int, std::string> method_states = {};
if (object_file_meta.state_methods.count(type_name) != 0) {
method_states = object_file_meta.state_methods.at(type_name);
}
if (type_method_count > 9) {
result.append("(:methods\n ");
MethodInfo old_new_method;
@@ -1163,7 +1185,12 @@ std::string TypeInspectorResult::print_as_deftype(
result.append("\n ");
}
for (int i = parent_method_count; i < type_method_count; i++) {
result.append(fmt::format("(dummy-{} () none {})", i, i));
// If the method is actually a state, skip it!
if (method_states.count(i) != 0) {
result.append(fmt::format("({} () _type_ :state {})", method_states.at(i), i));
} else {
result.append(fmt::format("({}-method-{} () none {})", type_name, i, i));
}
if (old_game_type) {
MethodInfo info;
if (old_game_type->get_my_method(i, &info)) {
@@ -1174,17 +1201,165 @@ std::string TypeInspectorResult::print_as_deftype(
}
result.append(")\n ");
}
// Print out states if we have em
// - Could probably assume the process name comes first and associate it with the right type
// but that may or may not be risky so, edit the types yourself...
// if (method_states.size() > 0) {
// result.append("(:states\n ");
// for (const auto& [id, name] : method_states) {
// result.append(name);
// // Append old symbol def if we have it
// auto it = previous_game_ts.symbol_types.find(name);
// if (it != previous_game_ts.symbol_types.end()) {
// result.append(fmt::format(" ;; {}", it->second.print()));
// }
// // Add symbol name to `already_seen_symbols`
// object_file_meta.already_seen_symbols.insert(name);
// result.append("\n ");
// }
// result.append(")\n ");
//}
result.append(")\n");
result += "|#\n";
return result;
}
std::string inspect_top_level_symbol_defines(std::unordered_set<std::string>& already_seen,
Function& top_level,
std::string get_regex_match(std::string form, std::regex regex) {
std::smatch matches;
if (std::regex_search(form, matches, regex)) {
if (matches.size() == 2) {
return matches[1];
}
}
return "";
}
std::string get_state_symbol_name(LinkedObjectFile& file, std::string label_name) {
try {
auto& label = file.get_label_by_name(label_name);
auto& label_words = file.words_by_seg.at(label.target_segment);
int start_word_idx = (label.offset / 4) - 1;
auto& first_word = label_words.at(start_word_idx);
if (first_word.kind() != LinkedWord::TYPE_PTR || first_word.symbol_name() != "state") {
return "";
}
auto& name_word = label_words.at(start_word_idx + 1);
if (name_word.kind() != LinkedWord::SYM_PTR) {
return "";
}
return name_word.symbol_name();
} catch (std::exception& e) {
return "";
}
}
std::string get_label_type_name(LinkedObjectFile& file, std::string label_name) {
try {
auto& label = file.get_label_by_name(label_name);
auto& label_words = file.words_by_seg.at(label.target_segment);
int start_word_idx = (label.offset / 4) - 1;
auto& first_word = label_words.at(start_word_idx);
if (first_word.kind() != LinkedWord::TYPE_PTR) {
return "";
}
return first_word.symbol_name();
} catch (std::exception& e) {
return "";
}
}
std::string inspect_top_level_for_metadata(Function& top_level,
LinkedObjectFile& file,
DecompilerTypeSystem& dts,
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& objectFile) {
// State as a method:
/*
lui v1, L267 ;; [ 77] (set! gp-0 L267) [] -> [gp: <uninitialized> ]
ori gp, v1, L267
lw t9, method-set!(s7) ;; [ 78] (set! t9-12 method-set!) [] -> [t9: <uninitialized> ]
lw a0, com-airlock(s7) ;; [ 79] (set! a0-12 com-airlock) [] -> [a0: <uninitialized> ]
addiu a1, r0, 21 ;; [ 80] (set! a1-10 21) [] -> [a1: <uninitialized> ]
or a2, gp, r0 ;; [ 81] (set! a2-10 gp-0) [gp: <uninitialized> ] -> [a2:
<uninitialized> ]
*/
// State as symbol:
/*
lui v1, L753 ;; [354] (set! v1-38 L753) [] -> [v1: <uninitialized> ]
ori v1, v1, L753
sw v1, target-roll(s7) ;; [355] (s.w! target-roll v1-38) [v1: <uninitialized> ] -> []
*/
if (!top_level.ir2.atomic_ops) {
return "";
}
std::string result;
std::string last_seen_label = "";
// TODO - safely increment op number
for (int i = 0; i < top_level.ir2.atomic_ops->ops.size(); i++) {
const auto& aop = top_level.ir2.atomic_ops->ops.at(i);
const std::string as_str = aop.get()->to_string(top_level.ir2.env);
// Keep track of the last seen label so we can easily reference it if a later operation uses it
auto label_match = get_regex_match(as_str, std::regex("\\(set!\\s[^\\s]*\\s(L.*)\\)"));
if (!label_match.empty()) {
last_seen_label = label_match;
// Check if the next operation is storing the label
std::string curr_op =
top_level.ir2.atomic_ops->ops.at(i + 1).get()->to_string(top_level.ir2.env);
auto symbol_name = get_regex_match(curr_op, std::regex("\\(s\\.w!\\s([^\\(\\)\\s]*)\\s"));
if (symbol_name.empty()) {
continue;
}
// Check that the label is a state
auto label_type_name = get_label_type_name(file, last_seen_label);
if (label_type_name.empty()) {
continue;
}
objectFile.symbol_types[symbol_name] = label_type_name;
}
if (as_str.find("method-set!") != std::string::npos) {
// The next operation should have the type name
i++;
std::string curr_op = top_level.ir2.atomic_ops->ops.at(i).get()->to_string(top_level.ir2.env);
auto type_match = get_regex_match(curr_op, std::regex("\\(set!\\s[^\\s]*\\s(.*)\\)"));
if (type_match.empty()) {
continue;
}
i++;
// The next operation should have the method id
curr_op = top_level.ir2.atomic_ops->ops.at(i).get()->to_string(top_level.ir2.env);
auto method_id_match = get_regex_match(curr_op, std::regex("\\(set!\\s[^\\s]*\\s(\\d*)\\)"));
if (method_id_match.empty()) {
continue;
}
int method_id = std::stoi(method_id_match);
// Now check the last seen label to see if it's a state
auto state_name = get_state_symbol_name(file, last_seen_label);
if (state_name.empty()) {
continue;
}
objectFile.state_methods[type_match][method_id] = state_name;
}
}
return "";
}
std::string inspect_top_level_symbol_defines(Function& top_level,
LinkedObjectFile& /*file*/,
DecompilerTypeSystem& dts,
DecompilerTypeSystem& previous_game_ts) {
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta) {
if (!top_level.ir2.atomic_ops) {
return {};
}
@@ -1194,12 +1369,18 @@ std::string inspect_top_level_symbol_defines(std::unordered_set<std::string>& al
if (as_store && as_store->addr().kind() == SimpleExpression::Kind::IDENTITY &&
as_store->addr().get_arg(0).is_sym_val()) {
auto& sym_name = as_store->addr().get_arg(0).get_str();
if (already_seen.find(sym_name) == already_seen.end()) {
already_seen.insert(sym_name);
if (object_file_meta.already_seen_symbols.find(sym_name) ==
object_file_meta.already_seen_symbols.end()) {
object_file_meta.already_seen_symbols.insert(sym_name);
if (dts.ts.partially_defined_type_exists(sym_name)) {
continue;
}
result += fmt::format(";; (define-extern {} object)", sym_name);
std::string type_name = "object";
// Look to see if we know the type name
if (object_file_meta.symbol_types.count(sym_name) != 0) {
type_name = object_file_meta.symbol_types.at(sym_name);
}
result += fmt::format(";; (define-extern {} {})", sym_name, type_name);
auto it = previous_game_ts.symbol_types.find(sym_name);
if (it != previous_game_ts.symbol_types.end()) {
result += fmt::format(" ;; {}", it->second.print());
+19 -9
View File
@@ -3,6 +3,7 @@
#include <string>
#include <unordered_map>
#include <decompiler/ObjectFile/ObjectFileDB.h>
#include "decompiler/Function/Function.h"
#include "decompiler/util/DecompilerTypeSystem.h"
@@ -13,7 +14,7 @@ struct TypeInspectorResult {
int type_size = -1;
int type_method_count = -1;
int parent_method_count = 9;
int type_heap_base = -1;
std::optional<int> type_heap_base = {};
std::string warnings;
std::vector<Field> fields_of_type;
@@ -22,11 +23,13 @@ struct TypeInspectorResult {
std::string type_name;
std::string parent_type_name;
u64 flags = 0;
u64 flags;
std::string print_as_deftype(
StructureType* old_game_type,
std::unordered_map<std::string, TypeInspectorResult>& previous_results);
std::unordered_map<std::string, TypeInspectorResult>& previous_results,
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta);
};
struct TypeInspectorCache {
@@ -37,13 +40,20 @@ std::string inspect_inspect_method(Function& inspect_method,
const std::string& type_name,
DecompilerTypeSystem& dts,
LinkedObjectFile& file,
TypeSystem& previous_game_ts,
TypeInspectorCache& ti_cache);
DecompilerTypeSystem& previous_game_ts,
TypeInspectorCache& ti_cache,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta);
std::string inspect_top_level_symbol_defines(std::unordered_set<std::string>& already_seen,
Function& top_level,
std::string inspect_top_level_for_metadata(Function& top_level,
LinkedObjectFile& file,
DecompilerTypeSystem& dts,
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta);
std::string inspect_top_level_symbol_defines(Function& top_level,
LinkedObjectFile& file,
DecompilerTypeSystem& dts,
DecompilerTypeSystem& previous_game_ts);
DecompilerTypeSystem& previous_game_ts,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta);
} // namespace decompiler
} // namespace decompiler
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -212,7 +212,7 @@
(deactivate (_type_) none 10)
(init-from-entity! (_type_ entity-actor) none 11) ;; todo check
(run-logic? (_type_) symbol 12)
(dummy-13 () none 13)
(process-tree-method-13 () none 13)
)
:size-assert #x24
:method-count-assert 14
@@ -698,4 +698,4 @@
`(rlet ((pp :reg r13 :reset-here #t :type process))
(deactivate pp)
)
)
)
-390
View File
@@ -1,390 +0,0 @@
# This does a (currently) 3 pass cleanup on all-types
# 1. Cleanup any symbol definitions that are redundant
# 2. Reorder symbol definitions based on file build order
# 3. Check for any necessary forward declarations
import os
# First pass!
print("First Pass - Cleaning up File")
new_file = []
with open("./decompiler/config/all-types.gc") as f:
symbols_found = []
lines = f.readlines()
for line in lines:
if line.startswith("(deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern"):
symbol = line.split(" ")[1]
if symbol in symbols_found and "unknown type" in line:
continue
else:
symbols_found.append(symbol)
new_file.append(line)
os.remove("./decompiler/config/all-types.gc")
with open("./decompiler/config/all-types.gc", "w") as f:
f.writelines(new_file)
# Second Pass!
# I try to preserve comments as best I can:
# - comments that are file names are discarded, they are now redundant!
# - comments prior to a symbol definition are considered part of that symbol definition, comments after are NOT
# Build up a mapping of symbol-name -> symbol-definitions and then we can easily drop them into place
# Symbols are defined by reading line-by-line until we see the next:
# - deftype
# - define-extern
# - defenum
# declare-types can be discarded, they are handled in the third pass
# If something is defined more than once, take the longer definition
from jak1_file_list import file_list
import json
script_comments = [
";; ----------------------",
";; File -",
";; Source Path -",
";; Containing DGOs -",
";; Version -",
";; - Types",
";; - Functions",
";; - Symbols",
";; - Unknowns",
";; NO FILE",
";; Unknowns / Built-Ins / Non-Original Types",
";; - Nothing Defined in This File!",
";; Unknowns with No Definition"
]
def is_filename_comment(line):
if not line.startswith(";"):
return False
if "define" in line or "deftype" in line:
return False
cleaned_line = line.replace(";", "").strip()
for item in file_list:
file_name = item[0]
if file_name == cleaned_line:
return True
return False
with open('./scripts/jak1-symbol-mapping.json') as f:
data = json.load(f)
all_symbols = []
for object_file, symbols in data.items():
for symbol in symbols:
all_symbols.append(symbol)
def no_runtime_type(definition):
for line in definition:
if "no-runtime-type" in line:
return True
return False
def strip_trailing_new_lines(definition):
new_definition = []
found_content = False
for line in reversed(definition):
if found_content:
new_definition.insert(0, line)
else:
cleaned_line = line.strip()
if len(cleaned_line) != 0:
found_content = True
new_definition.insert(0, line)
# Check if the last line has a new-line or not, if it doesn't add one
if not new_definition[len(new_definition)-1].endswith("\n"):
new_definition[len(new_definition)-1].append("\n")
return new_definition
symbol_definitions = {}
# Anything that is custom / not part of the game, I'll place at the top of the file because I have no idea where it should go
unknown_symbol_definitions = []
def is_script_comment(line):
for comment in script_comments:
if line.startswith(comment):
return True
return False
print("Second Pass - Re-Organizing File")
with open("./decompiler/config/all-types.gc") as f:
lines = f.readlines()
current_symbol = ""
current_symbol_definition = []
comment_buffer = []
commented_type = False
for i, line in enumerate(lines):
# Ignore the following lines:
# - declare-types
# - empty lines
# - file name comments
# - comments i generate
if is_filename_comment(line) or is_script_comment(line) or (commented_type is False and ("declare-type" in line or (line == "\n" and i != len(lines) - 1))):
continue
# Handle the first line of the file properly
if len(current_symbol_definition) == 0: # The only time this variable should be empty, is at the beginning, after that it should always be in use
if line.startswith("(deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern") or line.startswith("(defenum"):
current_symbol_definition.append(line)
current_symbol = line.split(" ")[1].rstrip("\n")
continue
# To support comments being associated with the following symbol def, we have to keep track of them
# then either associate them with the new symbol OR realize they are part of the current one!
if not commented_type and line.startswith(";") and not (line.startswith("; (deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern") or line.startswith("(defenum")):
current_symbol_definition.append(line)
comment_buffer.append(line)
continue
# Check if we've reached a new symbol or reached the end of the file
if i == len(lines) - 1 or line.startswith("(deftype") or line.startswith("; (deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern") or line.startswith("(defenum"):
# Remove any comments from the previous symbol def
if not commented_type and len(comment_buffer) > 0:
current_symbol_definition = current_symbol_definition[:-len(comment_buffer)]
# Check if the symbol we found is valid or invalid
if current_symbol in all_symbols:
if current_symbol in symbol_definitions:
# print("Symbol re-defintion found for '{}', choosing the bigger one!".format(current_symbol))
if len(current_symbol_definition) > len(symbol_definitions[current_symbol]):
if no_runtime_type(current_symbol_definition):
current_symbol_definition.insert(0, "(define-extern {} type) ; deftype provided by C Kernel\n".format(current_symbol))
symbol_definitions[current_symbol] = strip_trailing_new_lines(current_symbol_definition.copy())
else:
if no_runtime_type(current_symbol_definition):
current_symbol_definition.insert(0, "(define-extern {} type) ; deftype provided by C Kernel\n".format(current_symbol))
symbol_definitions[current_symbol] = strip_trailing_new_lines(current_symbol_definition.copy())
else:
print("Found a symbol '{}' that is not part of Jak 1!".format(current_symbol))
unknown_symbol_definitions.append(current_symbol_definition.copy())
if i != len(lines) - 1:
if line.startswith("; (deftype"):
current_symbol = line.split(" ")[2].rstrip("\n")
commented_type = True
else:
current_symbol = line.split(" ")[1].rstrip("\n")
commented_type = False
current_symbol_definition.clear()
current_symbol_definition += comment_buffer
comment_buffer.clear()
current_symbol_definition.append(line)
elif line.startswith("(deftype") or line.startswith("; (deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern") or line.startswith("(defenum"):
if line.startswith("; (deftype"):
current_symbol = line.split(" ")[2].rstrip("\n")
else:
current_symbol = line.split(" ")[1].rstrip("\n")
current_symbol_definition.clear()
current_symbol_definition += comment_buffer
comment_buffer.clear()
current_symbol_definition.append(line)
symbol_definitions[current_symbol] = strip_trailing_new_lines(current_symbol_definition.copy())
else:
current_symbol_definition.append(line)
if len(comment_buffer) > 0:
comment_buffer.clear()
# Now armed with our complete list of symbols, print them out in a nice organized manner.
# Precedence:
# - unknown symbols (after kernel definitions - `gstate` is the last)
# - file build order (include info about src file path / DGOs it's contained in)
# - types
# - functions
# - unknown types/symbols/functions
with open('./scripts/jak1-symbol-mapping.json') as f:
symbol_mapping = json.load(f)
def first_relevant_line(definition):
if len(definition) == 1:
return definition[0]
for line in definition:
if line.startswith("; (deftype") or not line.startswith(";"):
return line
def was_previous_definition_multi_line(definition):
line_count = 0
for line in definition:
if "; (deftype" in line:
return True
if not line.strip().startswith(";"):
line_count = line_count + 1
return line_count > 1
def print_definition_block(file_lines, header, prev_block_exists, def_list):
if len(def_list) == 0:
return False
if prev_block_exists:
file_lines.append("\n")
file_lines.append(";; - {}\n\n".format(header))
prev_definition = None
for definition in def_list:
if prev_definition is not None and was_previous_definition_multi_line(prev_definition):
file_lines.append("\n")
file_lines.append("".join(definition))
prev_definition = definition
return True
def print_definition_blocks(file_lines, types, functions, symbols, unknowns):
if not types and not functions and not symbols and not unknowns:
file_lines.append(";; - Nothing Defined in This File!\n")
else:
prev_block_exists = False
prev_block_exists |= print_definition_block(file_lines, "Types", prev_block_exists, types)
prev_block_exists |= print_definition_block(file_lines, "Functions", prev_block_exists, functions)
prev_block_exists |= print_definition_block(file_lines, "Symbols", prev_block_exists, symbols)
prev_block_exists |= print_definition_block(file_lines, "Unknowns", prev_block_exists, unknowns)
file_lines.append("\n")
new_file = []
for item in file_list:
file_name = item[0]
extension = "gc"
if item[2] == 4:
extension = "gd"
src_path = "{}/{}.{}".format(item[4], file_name, extension)
new_file.append("\n;; ----------------------\n;; File - {}\n;; Source Path - {}\n;; Containing DGOs - {}\n;; Version - {}\n\n".format(file_name, src_path, item[3], item[2]))
types = []
functions = []
symbols = []
unknowns = []
if file_name in symbol_mapping:
for symbol in symbol_mapping[file_name]:
if symbol not in symbol_definitions:
print("Could not find definition for '{}'".format(symbol))
else:
symbol_definition = symbol_definitions[symbol]
if ";;(define-extern" in first_relevant_line(symbol_definition):
unknowns.append(symbol_definition)
elif "(function" in first_relevant_line(symbol_definition) or "function)" in first_relevant_line(symbol_definition):
functions.append(symbol_definition)
elif "deftype" in first_relevant_line(symbol_definition):
types.append(symbol_definition)
elif "define-extern" in first_relevant_line(symbol_definition):
symbols.append(symbol_definition)
else:
print("Could not find associated symbol def for '{}'".format(symbol))
print_definition_blocks(new_file, types, functions, symbols, unknowns)
cleaned_unknown_symbol_defs = []
if file_name == "gcommon":
new_file.append("\n;; ----------------------\n;; NO FILE\n;; Unknowns / Built-Ins / Non-Original Types\n\n")
for definition in unknown_symbol_definitions:
if not definition[0].startswith(";;(define-extern"):
new_file.append("".join(definition) + "\n")
else:
cleaned_unknown_symbol_defs.append(definition)
if len(cleaned_unknown_symbol_defs) > 0:
new_file.append("\n;; ----------------------\n;; NO FILE\n;; Unknowns with No Definition\n\n")
for definition in cleaned_unknown_symbol_defs:
if definition[0].startswith(";;(define-extern"):
new_file.append("".join(definition).rstrip() + "\n")
os.remove("./decompiler/config/all-types.gc")
with open("./decompiler/config/all-types.gc", "w") as f:
f.writelines(new_file)
# Third pass! Add any necessary forward declarations
# - First, let's identify the line numbers where types are defined, and used
# - Then, repeat the process, adding forward declarations when appropriate
type_usages = {}
def get_root_parent_type(t):
if t["parent_type"] in ["basic", "structure", "symbol", "object", "integer", "pair", "number", "binteger", "function", "array", "type", "string", "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "uint128", "int128", "float", "kheap"]:
parent_type = t["parent_type"]
if parent_type not in ["basic", "structure"]:
return "type" # NOTE - this does not work currently!!! but is VERY RARE
return t["parent_type"]
return get_root_parent_type(type_usages[t["parent_type"]])
def get_safe_parent_type(current_type, all_types, earliest_usage_line):
parent_type_name = current_type["parent_type"]
if parent_type_name in ["basic", "structure", "type"]:
return parent_type_name
parent_type = all_types[parent_type_name]
if parent_type["declared_on_line"] < earliest_usage_line:
return parent_type["type_name"]
return get_root_parent_type(current_type)
def symbol_usage(line, sym):
if line.strip().startswith(";"):
return False
tokens = line.strip().split(" ")
for token in tokens[1:]:
sanitized_token = token.replace("(", "").replace(")", "").strip()
if sanitized_token == sym:
return True
return False
new_file = []
print("Third Pass - Adding Forward Type Declarations")
with open("./decompiler/config/all-types.gc") as f:
lines = f.readlines()
# Get the types
for i, line in enumerate(lines):
clean_line = line.replace(";", "").strip()
if clean_line.startswith("(deftype"):
symbol = clean_line.split(" ")[1]
parent_type = clean_line.split(" ")[2].rstrip("\n").replace("(", "").replace(")", "")
if parent_type == "UNKNOWN":
continue
type_usages[symbol] = {
"type_name": symbol,
"parent_type": parent_type,
"declared_on_line": i,
"first_symbol_usage": "",
"used_on_lines": [],
"commented_out_type": line.startswith(";")
}
# Identify Usages - heavy loop
for symbol, usage_info in type_usages.items():
symbol_index = list(type_usages.keys()).index(symbol)
if symbol_index % 100 == 0:
print("[{}/{}]: Finding Type Usages".format(symbol_index, len(type_usages)))
current_symbol = ""
for i, line in enumerate(lines):
if i > usage_info["declared_on_line"]:
break # For speed reasons, we don't care about usages after the declaration
if "; deftype provided by C Kernel" in line:
continue
if line.startswith("(deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern") or line.startswith("(defenum"):
current_symbol = line.split(" ")[1]
if i != usage_info["declared_on_line"] and symbol_usage(line, usage_info["type_name"]):
if len(usage_info["used_on_lines"]) == 0:
usage_info["first_symbol_usage"] = current_symbol
usage_info["used_on_lines"].append(i)
# Identify Necessary Forward Declarations
forward_declarations = {}
for symbol, usage_info in type_usages.items():
declaration_line = usage_info["declared_on_line"]
if len(usage_info["used_on_lines"]) == 0:
continue
earliest_usage = usage_info["used_on_lines"][0]
if declaration_line > earliest_usage or usage_info["commented_out_type"]:
if usage_info["first_symbol_usage"] not in forward_declarations:
forward_declarations[usage_info["first_symbol_usage"]] = ["(declare-type {} {})\n".format(symbol, get_safe_parent_type(usage_info, type_usages, earliest_usage))]
else:
forward_declarations[usage_info["first_symbol_usage"]].append("(declare-type {} {})\n".format(symbol, get_safe_parent_type(usage_info, type_usages, earliest_usage)))
# FINALLY - add the forward declarations\
skip_next = False
for i, line in enumerate(lines):
if skip_next:
skip_next = False
new_file.append(line)
continue
if "; deftype provided by C Kernel" in line:
skip_next = True
if line.startswith("(deftype") or line.startswith("(define-extern") or line.startswith(";;(define-extern"):
current_symbol = line.split(" ")[1]
if current_symbol in forward_declarations:
new_file.append("".join(forward_declarations[current_symbol]))
new_file.append(line)
os.remove("./decompiler/config/all-types.gc")
with open("./decompiler/config/all-types.gc", "w") as f:
f.writelines(new_file)
+5
View File
@@ -6,6 +6,7 @@ import sys
parser = argparse.ArgumentParser("update-env")
parser.add_argument("--game", help="The name of the game", type=str)
parser.add_argument("--decomp_config", help="The decompiler config file", type=str)
parser.add_argument("--info", help="Just print out current settings", action='store_true')
args = parser.parse_args()
# TODO - read from defaults
@@ -28,6 +29,10 @@ with open(env_path, 'r') as env_file:
if tokens[0] in file:
file[tokens[0]] = tokens[1].strip()
if args.info:
print(file)
sys.exit(0)
valid_games = ["jak1", "jak2"]
decomp_config_map = {
-4
View File
@@ -1686,7 +1686,3 @@
;; failed to figure out what this is:
(kmemclose)